Compare commits

...

205 Commits

Author SHA1 Message Date
a57811b0d3 🔖 1.11.0
Some checks failed
Code Coverage / Test (macos-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.12) (push) Has been cancelled
TypeCheck / TypeCheck (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Code Coverage / check (push) Has been cancelled
2025-07-28 01:41:59 +08:00
呵呵です
7a5170936b 🧑‍💻添加更多实用开发配置项 (#555) 2025-07-28 01:31:55 +08:00
呵呵です
068c508f57 IO添加重新验证命令 (#554)
* 🐛 修复更新绑定时验证状态没有正确更新的bug

*  IO添加重新验证命令

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-28 01:11:41 +08:00
renovate[bot]
0648ca021b ⬆️ Upgrade re-actors/alls-green digest to 2765efe (#551)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-27 15:00:04 +00:00
呵呵です
65e7fed32b ♻️ 重构模板截图部分以解决导航导致的报错 (#553)
* ♻️ 把 path 放到数据模型里

* ♻️ 使用通用函数来生成模板图片

* 🎨 同步模板项目结构

* 🐛 修正导入路径
2025-07-27 22:58:41 +08:00
呵呵です
fdbb2f3f6e 通过io账号绑定的discord验证归属权 #64 (#552)
Some checks are pending
Code Coverage / Test (macos-latest, 3.10) (push) Waiting to run
Code Coverage / Test (macos-latest, 3.11) (push) Waiting to run
Code Coverage / Test (macos-latest, 3.12) (push) Waiting to run
Code Coverage / Test (ubuntu-latest, 3.10) (push) Waiting to run
Code Coverage / Test (ubuntu-latest, 3.11) (push) Waiting to run
Code Coverage / Test (ubuntu-latest, 3.12) (push) Waiting to run
Code Coverage / Test (windows-latest, 3.10) (push) Waiting to run
Code Coverage / Test (windows-latest, 3.11) (push) Waiting to run
Code Coverage / Test (windows-latest, 3.12) (push) Waiting to run
Code Coverage / check (push) Blocked by required conditions
TypeCheck / TypeCheck (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
* 🗃️ 添加 verify 字段到 Bind 模型,并在创建或更新绑定时支持该字段

*  通过io账号绑定的discord验证归属权
2025-07-27 05:01:33 +08:00
144c223fe9 🔖 1.10.2
Some checks failed
Code Coverage / Test (macos-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.12) (push) Has been cancelled
TypeCheck / TypeCheck (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Code Coverage / check (push) Has been cancelled
2025-07-19 22:40:47 +08:00
呵呵です
52a6d95434 🐛 移除 julianday 的使用,兼容更多数据库 (#550) 2025-07-19 22:40:04 +08:00
d8255756ca 🔖 1.10.1 2025-07-19 19:55:54 +08:00
呵呵です
13c6d53b6a 🐛 修改用户唯一标识符字段长度 (#549) 2025-07-19 19:54:40 +08:00
6493aba7e0 🔖 1.10.0
Some checks failed
Code Coverage / Test (macos-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.12) (push) Has been cancelled
TypeCheck / TypeCheck (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Code Coverage / check (push) Has been cancelled
2025-07-18 05:53:41 +08:00
pre-commit-ci[bot]
b82053be11 ⬆️ auto update by pre-commit hooks (#548)
* ⬆️ auto update by pre-commit hooks

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.11.13 → v0.12.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.13...v0.12.3)

* 🚨 auto fix by pre-commit hooks

* ⬆️ Upgrade dependency ruff to v0.12.4

* 🚨 修复 lint 警告

* 🚨 添加一个 noqa(

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: 呵呵です <51957264+shoucandanghehe@users.noreply.github.com>
Co-authored-by: shoucandanghehe <wallfjjd@gmail.com>
2025-07-17 21:49:16 +00:00
renovate[bot]
11bc486420 ⬆️ Upgrade dependency prettier to v3.6.2 (#547)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-17 21:35:20 +00:00
呵呵です
9916902c10 🐛 修复 postgresql 标识符大于63字符的错误 (#545)
* 🗃️ 自定义表名

*  添加开发依赖 nonebot-plugin-orm[postgresql]

* 🗃️ postgresql 跳过所有旧迁移脚本

* 🗃️ 修正方言

* 🗃️ 添加迁移脚本

* 🚨 auto fix by pre-commit hooks

* 🚨 添加一个 noqa(

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-07-18 05:32:50 +08:00
pre-commit-ci[bot]
e347b41ba6 ⬆️ auto update by pre-commit hooks (#546)
Some checks failed
Code Coverage / Test (macos-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.12) (push) Has been cancelled
TypeCheck / TypeCheck (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Code Coverage / check (push) Has been cancelled
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.11.12 → v0.11.13](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.12...v0.11.13)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-06-10 20:28:52 +08:00
pre-commit-ci[bot]
40d0bf06bb ⬆️ auto update by pre-commit hooks (#544)
Some checks failed
Code Coverage / Test (macos-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.12) (push) Has been cancelled
TypeCheck / TypeCheck (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Code Coverage / check (push) Has been cancelled
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.11.11 → v0.11.12](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.11...v0.11.12)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-06-07 19:32:27 +08:00
026ceaec6c 🔖 1.9.0
Some checks failed
Code Coverage / Test (macos-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.12) (push) Has been cancelled
TypeCheck / TypeCheck (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Code Coverage / check (push) Has been cancelled
2025-05-29 04:14:12 +08:00
pre-commit-ci[bot]
e80e1bcda3 ⬆️ auto update by pre-commit hooks (#543)
Some checks failed
Code Coverage / Test (macos-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.12) (push) Has been cancelled
TypeCheck / TypeCheck (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Code Coverage / check (push) Has been cancelled
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.11.10 → v0.11.11](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.10...v0.11.11)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-05-26 19:54:00 +00:00
pre-commit-ci[bot]
72e9a2fd87 ⬆️ auto update by pre-commit hooks (#541)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.11.8 → v0.11.10](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.8...v0.11.10)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-05-27 03:33:08 +08:00
呵呵です
f0b1a1c4f5 ♻️ 迁移至 nonebot_plugin_uninfo (#542)
*  添加依赖 nonebot-plugin-uninfo

* ♻️ 使用 nonebot_plugin_uninfo 替代 nonebot_plugin_session nonebot_plugin_session_orm nonebot_plugin_userinfo

* 🗃️ 迁移数据库数据至 nonebot_plugin_uninfo

*  删除依赖 nonebot-plugin-session nonebot-plugin-session-orm nonebot-plugin-userinfo

*  添加依赖 nonebot-session-to-uninfo
2025-05-27 03:28:55 +08:00
0bc3b86820 🔖 1.8.3
Some checks failed
Code Coverage / Test (macos-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.12) (push) Has been cancelled
TypeCheck / TypeCheck (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Code Coverage / check (push) Has been cancelled
2025-05-08 00:00:12 +08:00
pre-commit-ci[bot]
57d0b15242 ⬆️ auto update by pre-commit hooks (#538)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.11.6 → v0.11.8](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.6...v0.11.8)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-05-07 15:52:35 +00:00
呵呵です
56fe45efcf 适配新成就模板 (#540)
*  完善Achievement模型

*  添加一些alias

*  更新模板 schemas

* 🐛 修复类型错误
2025-05-07 23:51:13 +08:00
13f005179f 添加 schema 注释到 pyproject.toml
Some checks failed
Code Coverage / Test (macos-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.12) (push) Has been cancelled
TypeCheck / TypeCheck (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Code Coverage / check (push) Has been cancelled
2025-04-28 04:13:05 +08:00
243a8a286d 🔖 1.8.2 2025-04-28 04:09:04 +08:00
8b7de6745b 添加 development 模式 2025-04-28 04:08:55 +08:00
d0fc82d88d 添加开发依赖 nonebot-plugin-tarina-lang-turbo 2025-04-28 04:06:48 +08:00
bb4da8accc 🔖 1.8.1
Some checks are pending
Code Coverage / Test (macos-latest, 3.10) (push) Waiting to run
Code Coverage / Test (macos-latest, 3.11) (push) Waiting to run
Code Coverage / Test (macos-latest, 3.12) (push) Waiting to run
Code Coverage / Test (ubuntu-latest, 3.10) (push) Waiting to run
Code Coverage / Test (ubuntu-latest, 3.11) (push) Waiting to run
Code Coverage / Test (ubuntu-latest, 3.12) (push) Waiting to run
Code Coverage / Test (windows-latest, 3.10) (push) Waiting to run
Code Coverage / Test (windows-latest, 3.11) (push) Waiting to run
Code Coverage / Test (windows-latest, 3.12) (push) Waiting to run
Code Coverage / check (push) Blocked by required conditions
TypeCheck / TypeCheck (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
2025-04-27 15:18:25 +08:00
56e06a7001 🐛 修复 _lang 为私有变量不会默认序列化的bug 2025-04-27 15:17:12 +08:00
renovate[bot]
7c0b3cd240 ⬆️ Upgrade astral-sh/setup-uv action to v6 (#537)
Some checks failed
Code Coverage / Test (macos-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.12) (push) Has been cancelled
TypeCheck / TypeCheck (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Code Coverage / check (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-24 22:50:26 +08:00
dca0619021 🔖 1.8.0
Some checks are pending
Code Coverage / Test (macos-latest, 3.10) (push) Waiting to run
Code Coverage / Test (macos-latest, 3.11) (push) Waiting to run
Code Coverage / Test (macos-latest, 3.12) (push) Waiting to run
Code Coverage / Test (ubuntu-latest, 3.10) (push) Waiting to run
Code Coverage / Test (ubuntu-latest, 3.11) (push) Waiting to run
Code Coverage / Test (ubuntu-latest, 3.12) (push) Waiting to run
Code Coverage / Test (windows-latest, 3.10) (push) Waiting to run
Code Coverage / Test (windows-latest, 3.11) (push) Waiting to run
Code Coverage / Test (windows-latest, 3.12) (push) Waiting to run
Code Coverage / check (push) Blocked by required conditions
TypeCheck / TypeCheck (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
2025-04-24 03:07:04 +08:00
pre-commit-ci[bot]
f56dce6918 ⬆️ auto update by pre-commit hooks (#535)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.11.2 → v0.11.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.2...v0.11.6)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-04-24 03:01:28 +08:00
呵呵です
ff3eb79967 迁移到新模板 (#536)
Some checks are pending
Code Coverage / Test (macos-latest, 3.10) (push) Waiting to run
Code Coverage / Test (macos-latest, 3.11) (push) Waiting to run
Code Coverage / Test (macos-latest, 3.12) (push) Waiting to run
Code Coverage / Test (ubuntu-latest, 3.10) (push) Waiting to run
Code Coverage / Test (ubuntu-latest, 3.11) (push) Waiting to run
Code Coverage / Test (ubuntu-latest, 3.12) (push) Waiting to run
Code Coverage / Test (windows-latest, 3.10) (push) Waiting to run
Code Coverage / Test (windows-latest, 3.11) (push) Waiting to run
Code Coverage / Test (windows-latest, 3.12) (push) Waiting to run
Code Coverage / check (push) Blocked by required conditions
TypeCheck / TypeCheck (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
*  添加依赖 strenum

* 🐛 优化等待逻辑,修复截图爆炸

*  使用新模板

* ️ 关闭自动转译

*  同步新模板 schemas

* 🌐 添加模板语言的映射

*  适配 bind

*  更新模板

*  全部适配

* 🚨 make mypy happy

* Update nonebot_plugin_tetris_stats/games/tos/query.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

*  使用用户设置语言

* 🚨 auto fix by pre-commit hooks

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-04-23 19:25:50 +08:00
pre-commit-ci[bot]
0ac917f95e ⬆️ auto update by pre-commit hooks (#534)
Some checks failed
Code Coverage / Test (macos-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.12) (push) Has been cancelled
TypeCheck / TypeCheck (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Code Coverage / check (push) Has been cancelled
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.11.0 → v0.11.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.0...v0.11.2)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-03-28 16:35:48 +08:00
9806050e33 🔖 1.7.2
Some checks failed
Code Coverage / Test (macos-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.12) (push) Has been cancelled
TypeCheck / TypeCheck (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Code Coverage / check (push) Has been cancelled
2025-03-20 03:35:19 +08:00
461327749f 🐛 修爆炸 2025-03-20 03:34:34 +08:00
renovate[bot]
208582d313 ⬆️ Upgrade dependency prettier to v3.5.3 (#533)
Some checks are pending
Code Coverage / Test (macos-latest, 3.10) (push) Waiting to run
Code Coverage / Test (macos-latest, 3.11) (push) Waiting to run
Code Coverage / Test (macos-latest, 3.12) (push) Waiting to run
Code Coverage / Test (ubuntu-latest, 3.10) (push) Waiting to run
Code Coverage / Test (ubuntu-latest, 3.11) (push) Waiting to run
Code Coverage / Test (ubuntu-latest, 3.12) (push) Waiting to run
Code Coverage / Test (windows-latest, 3.10) (push) Waiting to run
Code Coverage / Test (windows-latest, 3.11) (push) Waiting to run
Code Coverage / Test (windows-latest, 3.12) (push) Waiting to run
Code Coverage / check (push) Blocked by required conditions
TypeCheck / TypeCheck (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-19 06:13:30 +08:00
pre-commit-ci[bot]
90f655259d ⬆️ auto update by pre-commit hooks (#532)
* ⬆️ auto update by pre-commit hooks

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.9.6 → v0.11.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.6...v0.11.0)

* 🚨 auto fix by pre-commit hooks

* 🚨 添加一个 noqa(

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: shoucandanghehe <wallfjjd@gmail.com>
2025-03-19 06:11:18 +08:00
renovate[bot]
2ef400ca28 ⬆️ Upgrade dependency prettier to v3.5.2 (#531)
Some checks failed
Code Coverage / Test (macos-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.12) (push) Has been cancelled
TypeCheck / TypeCheck (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Code Coverage / check (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-25 02:02:57 +08:00
renovate[bot]
616a64bd6a ⬆️ Upgrade dependency prettier to v3.5.1 (#530)
Some checks failed
Code Coverage / Test (macos-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.12) (push) Has been cancelled
TypeCheck / TypeCheck (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Code Coverage / check (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-14 02:40:56 +08:00
pre-commit-ci[bot]
bb8943d4c3 ⬆️ auto update by pre-commit hooks (#529)
Some checks failed
Code Coverage / Test (macos-latest, 3.10) (push) Waiting to run
Code Coverage / Test (macos-latest, 3.11) (push) Waiting to run
Code Coverage / Test (macos-latest, 3.12) (push) Waiting to run
Code Coverage / Test (ubuntu-latest, 3.10) (push) Waiting to run
Code Coverage / Test (ubuntu-latest, 3.11) (push) Waiting to run
Code Coverage / Test (ubuntu-latest, 3.12) (push) Waiting to run
Code Coverage / Test (windows-latest, 3.10) (push) Waiting to run
Code Coverage / Test (windows-latest, 3.11) (push) Waiting to run
Code Coverage / Test (windows-latest, 3.12) (push) Waiting to run
Code Coverage / check (push) Blocked by required conditions
TypeCheck / TypeCheck (push) Waiting to run
CodeQL / Analyze (python) (push) Has been cancelled
* ⬆️ auto update by pre-commit hooks

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.8.3 → v0.9.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.8.3...v0.9.6)

* 🚨 auto fix by pre-commit hooks

* ♻️ 重命名 typing 为 typedefs

* ♻️ 使用 Annotated 代替默认值

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: shoucandanghehe <wallfjjd@gmail.com>
2025-02-13 04:18:36 +08:00
5e45db8cf5 🔖 1.7.1
Some checks failed
Code Coverage / Test (macos-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.12) (push) Has been cancelled
TypeCheck / TypeCheck (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Code Coverage / check (push) Has been cancelled
2024-12-21 01:46:08 +08:00
renovate[bot]
2020deadac ⬆️ Upgrade astral-sh/setup-uv action to v5 (#527)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-20 17:43:24 +00:00
呵呵です
ce7bce6e20 🐛 修爆炸 (#528)
* 🐛 修爆炸

* 🐛 修复类型错误
2024-12-21 01:41:58 +08:00
pre-commit-ci[bot]
d4b690f682 ⬆️ auto update by pre-commit hooks (#524)
Some checks failed
Code Coverage / Test (macos-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.12) (push) Has been cancelled
TypeCheck / TypeCheck (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Code Coverage / check (push) Has been cancelled
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.8.1 → v0.8.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.8.1...v0.8.3)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-12-18 21:28:36 +00:00
c4bde71546 🔖 1.7.0 2024-12-19 05:26:15 +08:00
呵呵です
f56f993c69 使用随机/特殊 UA (#526)
*  添加依赖 fake-useragent

*  使用随机/特殊 UA
2024-12-19 05:24:48 +08:00
呵呵です
cfcda6f597 添加 unbind 指令 (#525)
*  添加依赖 nonebot-plugin-waiter

*  添加 unbind 指令
2024-12-19 03:59:22 +08:00
renovate[bot]
96f5d4559d ⬆️ Upgrade dependency prettier to v3.4.2 (#523)
Some checks failed
Code Coverage / Test (macos-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.12) (push) Has been cancelled
TypeCheck / TypeCheck (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Code Coverage / check (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-08 02:59:36 +08:00
renovate[bot]
23f412b4f4 ⬆️ Upgrade dependency prettier to v3.4.1 (#522)
Some checks failed
Code Coverage / Test (macos-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.12) (push) Has been cancelled
TypeCheck / TypeCheck (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Code Coverage / check (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-03 12:27:03 +00:00
renovate[bot]
25b0d2bcdc ⬆️ Upgrade astral-sh/setup-uv action to v4 (#521)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-03 12:25:13 +00:00
pre-commit-ci[bot]
a116f9901c ⬆️ auto update by pre-commit hooks (#520)
* ⬆️ auto update by pre-commit hooks

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.7.3 → v0.8.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.7.3...v0.8.1)

* 🚨 auto fix by pre-commit hooks

* 🚨 fix ruff

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: shoucandanghehe <wallfjjd@gmail.com>
2024-12-03 20:23:19 +08:00
renovate[bot]
82befd631e ⬆️ Upgrade codecov/codecov-action action to v5 (#519)
Some checks failed
Code Coverage / Test (macos-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.12) (push) Has been cancelled
TypeCheck / TypeCheck (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Code Coverage / check (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-15 12:50:50 +08:00
232389dd07 🔖 1.6.3
Some checks failed
Code Coverage / Test (macos-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.12) (push) Has been cancelled
TypeCheck / TypeCheck (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Code Coverage / check (push) Has been cancelled
2024-11-12 19:08:32 +08:00
呵呵です
ce81015406 🐛 修正 TETR.IO 的字段类型 (#518) 2024-11-12 19:07:34 +08:00
pre-commit-ci[bot]
3d7b903f59 ⬆️ auto update by pre-commit hooks (#517)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.7.2 → v0.7.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.7.2...v0.7.3)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-11-12 18:54:12 +08:00
pre-commit-ci[bot]
c5d499434e ⬆️ auto update by pre-commit hooks (#516)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.7.1 → v0.7.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.7.1...v0.7.2)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-11-05 17:53:14 +08:00
呵呵です
194fed24c9 🔧 配置 prettier (#515)
*  添加开发依赖 prettier

* 🎨 格式化
2024-11-01 01:45:56 +08:00
renovate[bot]
1173c39e7a ⬆️ Upgrade re-actors/alls-green digest to 223e4bb (#514)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-01 01:38:39 +08:00
呵呵です
dfb19f150a 添加单元测试 (#513)
*  添加测试依赖 nonebug

*  添加测试依赖 pytest-asyncio

* 🔧 配置 pytest

* 🙈 更新 .gitignore

*  添加 test 依赖 pytest-cov

* 🔧 配置 pytest

*  添加测试

* 👷 添加 Test CI

* 💚 暂时移除 3.13 的测试
2024-11-01 01:33:18 +08:00
呵呵です
7555297e1e 🐛 修正 S1ValidRank (#512) 2024-10-29 04:19:51 +00:00
pre-commit-ci[bot]
587aa4a0de ⬆️ auto update by pre-commit hooks (#511)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.7.0 → v0.7.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.7.0...v0.7.1)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-10-29 12:03:27 +08:00
08a1a427b4 🔖 1.6.2 2024-10-29 00:44:55 +08:00
呵呵です
d4e91c8521 🐛 修复被 ban 的爆炸 (#510) 2024-10-28 16:43:38 +00:00
dbde1181ce 🔖 1.6.1 2024-10-28 22:06:30 +08:00
呵呵です
86fe4f0766 🐛 修复 handle_history_data 的索引越界问题 (#509) 2024-10-28 14:01:11 +00:00
呵呵です
381f2505d6 🐛 修复 leagueflow 没有有效数据爆炸 (#508) 2024-10-28 13:58:38 +00:00
b3a77f5296 💚 修复 basedpyright
https://github.com/DetachHead/basedpyright/issues/819
2024-10-27 21:28:19 +08:00
274f30f82a 🔖 1.6.0 2024-10-27 21:06:12 +08:00
efb1ddb260 🔧 更新 renovate 配置 2024-10-27 20:58:39 +08:00
7e3f49bc9e 🔧 更新 bumpversion 配置 2024-10-27 20:53:06 +08:00
665772ed66 添加 release 依赖 bump-my-version 2024-10-27 20:49:56 +08:00
renovate[bot]
44fda8a19e ⬆️ Upgrade github/codeql-action action to v3 (#506)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-27 12:07:27 +00:00
renovate[bot]
6921bf4e37 ⬆️ Upgrade actions/checkout action to v4 (#504)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-27 20:05:17 +08:00
renovate[bot]
c3c97c1c8b 🔧 Configure Renovate (#500)
* Add renovate.json

* 🔧 更新配置文件

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: shoucandanghehe <wallfjjd@gmail.com>
2024-10-27 19:58:46 +08:00
呵呵です
1d33872c9b 🧑‍💻 使用 uv 管理项目 (#502)
* 🧑‍💻 使用 uv 管理项目

* 📝 更新 CONTRIBUTING.md

* 🔥 移除 Dependabot

*  修改默认安装的依赖组

* 💚 使用 uv 运行 CI

*  将特殊适配的适配器移动到 dev 依赖组

* 🚨 make mypy happy
2024-10-27 18:46:46 +08:00
呵呵です
b2d5a1e729 🌐 支持i18n (#501)
*  支持 i18n #410

* 🚨 更改noqa方式

* 📝 添加 CONTRIBUTING.md 文件

* 🌐 将 i18n 默认语言设置为 en-US

* 📝 添加英文版 CONTRIBUTING.md
2024-10-26 18:29:51 +08:00
呵呵です
a0fd9eaed3 历史tr图表 (#499)
*  实现获取 leagueflow

*  TETR.IO 适配 v1 模板

*  限制 v2 history 数量
2024-10-26 18:05:58 +08:00
呵呵です
593723aa76 🎨 重命名变量 (#495) 2024-10-25 04:29:54 +00:00
dependabot[bot]
73d97d8458 ⬆️ Bump mypy from 1.12.1 to 1.13.0 (#491)
Bumps [mypy](https://github.com/python/mypy) from 1.12.1 to 1.13.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.12.1...v1.13.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-25 04:25:03 +00:00
dependabot[bot]
d6f11655c1 ⬆️ Bump ruff from 0.7.0 to 0.7.1 (#493)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.7.0 to 0.7.1.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.7.0...0.7.1)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-25 04:22:01 +00:00
dependabot[bot]
376e85e36e ⬆️ Bump basedpyright from 1.19.0 to 1.19.1 (#490)
Bumps [basedpyright](https://github.com/detachhead/basedpyright) from 1.19.0 to 1.19.1.
- [Release notes](https://github.com/detachhead/basedpyright/releases)
- [Commits](https://github.com/detachhead/basedpyright/compare/v1.19.0...v1.19.1)

---
updated-dependencies:
- dependency-name: basedpyright
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-25 04:19:02 +00:00
dependabot[bot]
45116a1418 ⬆️ Bump rich from 13.9.2 to 13.9.3 (#492)
Bumps [rich](https://github.com/Textualize/rich) from 13.9.2 to 13.9.3.
- [Release notes](https://github.com/Textualize/rich/releases)
- [Changelog](https://github.com/Textualize/rich/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Textualize/rich/compare/v13.9.2...v13.9.3)

---
updated-dependencies:
- dependency-name: rich
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-25 04:16:12 +00:00
dependabot[bot]
a42d3e3837 ⬆️ Bump nonebot-adapter-onebot from 2.4.5 to 2.4.6 (#494)
Bumps [nonebot-adapter-onebot](https://github.com/nonebot/adapter-onebot) from 2.4.5 to 2.4.6.
- [Release notes](https://github.com/nonebot/adapter-onebot/releases)
- [Commits](https://github.com/nonebot/adapter-onebot/compare/v2.4.5...v2.4.6)

---
updated-dependencies:
- dependency-name: nonebot-adapter-onebot
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-25 12:13:02 +08:00
pre-commit-ci[bot]
b333c54c7d ⬆️ auto update by pre-commit hooks (#487)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.6.9 → v0.7.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.9...v0.7.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-10-23 14:57:41 +00:00
dependabot[bot]
8840402d2f ⬆️ Bump playwright from 1.47.0 to 1.48.0 (#483)
Bumps [playwright](https://github.com/Microsoft/playwright-python) from 1.47.0 to 1.48.0.
- [Release notes](https://github.com/Microsoft/playwright-python/releases)
- [Commits](https://github.com/Microsoft/playwright-python/compare/v1.47.0...v1.48.0)

---
updated-dependencies:
- dependency-name: playwright
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-22 16:49:43 +00:00
dependabot[bot]
e8b64b23f5 ⬆️ Bump mypy from 1.12.0 to 1.12.1 (#486)
Bumps [mypy](https://github.com/python/mypy) from 1.12.0 to 1.12.1.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.12.0...v1.12.1)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-22 16:45:55 +00:00
dependabot[bot]
40762a3180 ⬆️ Bump nonebot-plugin-user from 0.4.3 to 0.4.4 (#485)
Bumps [nonebot-plugin-user](https://github.com/he0119/nonebot-plugin-user) from 0.4.3 to 0.4.4.
- [Release notes](https://github.com/he0119/nonebot-plugin-user/releases)
- [Changelog](https://github.com/he0119/nonebot-plugin-user/blob/main/CHANGELOG.md)
- [Commits](https://github.com/he0119/nonebot-plugin-user/compare/v0.4.3...v0.4.4)

---
updated-dependencies:
- dependency-name: nonebot-plugin-user
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-22 16:42:11 +00:00
dependabot[bot]
a2c6ad8328 ⬆️ Bump yarl from 1.15.4 to 1.16.0 (#488)
Bumps [yarl](https://github.com/aio-libs/yarl) from 1.15.4 to 1.16.0.
- [Release notes](https://github.com/aio-libs/yarl/releases)
- [Changelog](https://github.com/aio-libs/yarl/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/yarl/compare/v1.15.4...v1.16.0)

---
updated-dependencies:
- dependency-name: yarl
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-22 16:38:33 +00:00
呵呵です
c7d93069ef 🐛 兼容pydantic v1 (#489) 2024-10-22 16:00:22 +00:00
4b514df2db 移除依赖 zstandard 2024-10-22 22:39:11 +08:00
47c83be1b5 🔖 1.5.5 2024-10-19 18:55:07 +08:00
6c0e092f51 🔊 优化 limit 日志 2024-10-19 18:52:18 +08:00
04b9cd9eae 🚨 type ignore 2024-10-19 18:52:17 +08:00
呵呵です
61b5fcb137 为 TETR.IO 引入全局速率限制 (#481) 2024-10-19 10:34:56 +00:00
c0540769c8 🔖 1.5.4 2024-10-19 17:40:00 +08:00
dependabot[bot]
0e19943046 ⬆️ Bump ruff from 0.6.9 to 0.7.0 (#479)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.6.9 to 0.7.0.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.6.9...0.7.0)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-19 09:37:36 +00:00
dependabot[bot]
7e1d2e8cb0 ⬆️ Bump nonebot-plugin-alconna from 0.53.0 to 0.53.1 (#478)
Bumps [nonebot-plugin-alconna](https://github.com/nonebot/plugin-alconna) from 0.53.0 to 0.53.1.
- [Release notes](https://github.com/nonebot/plugin-alconna/releases)
- [Commits](https://github.com/nonebot/plugin-alconna/compare/v0.53.0...v0.53.1)

---
updated-dependencies:
- dependency-name: nonebot-plugin-alconna
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-19 09:33:54 +00:00
dependabot[bot]
8931cfb5a7 ⬆️ Bump basedpyright from 1.18.4 to 1.19.0 (#474)
Bumps [basedpyright](https://github.com/detachhead/basedpyright) from 1.18.4 to 1.19.0.
- [Release notes](https://github.com/detachhead/basedpyright/releases)
- [Commits](https://github.com/detachhead/basedpyright/compare/v1.18.4...v1.19.0)

---
updated-dependencies:
- dependency-name: basedpyright
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-19 09:30:23 +00:00
呵呵です
ea8a18c1b1 🐛 修复 茶服 api 查询链接拼接错误 (#480) 2024-10-19 09:27:13 +00:00
ef1acb0f16 🚨 ignore reportGeneralTypeIssues
https://github.com/microsoft/pyright/issues/9220
2024-10-19 17:20:42 +08:00
dependabot[bot]
f7bb667254 ⬆️ Bump yarl from 1.15.3 to 1.15.4 (#476)
Bumps [yarl](https://github.com/aio-libs/yarl) from 1.15.3 to 1.15.4.
- [Release notes](https://github.com/aio-libs/yarl/releases)
- [Changelog](https://github.com/aio-libs/yarl/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/yarl/compare/v1.15.3...v1.15.4)

---
updated-dependencies:
- dependency-name: yarl
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-18 10:34:24 +08:00
dependabot[bot]
fa94c1beeb ⬆️ Bump viztracer from 0.16.3 to 0.17.0 (#471)
Bumps [viztracer](https://github.com/gaogaotiantian/viztracer) from 0.16.3 to 0.17.0.
- [Release notes](https://github.com/gaogaotiantian/viztracer/releases)
- [Commits](https://github.com/gaogaotiantian/viztracer/compare/0.16.3...0.17.0)

---
updated-dependencies:
- dependency-name: viztracer
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-16 03:09:52 +00:00
dependabot[bot]
4e1e91a977 ⬆️ Bump yarl from 1.14.0 to 1.15.3 (#473)
Bumps [yarl](https://github.com/aio-libs/yarl) from 1.14.0 to 1.15.3.
- [Release notes](https://github.com/aio-libs/yarl/releases)
- [Changelog](https://github.com/aio-libs/yarl/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/yarl/compare/v1.14.0...v1.15.3)

---
updated-dependencies:
- dependency-name: yarl
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-16 03:06:00 +00:00
dependabot[bot]
0f6a00819b ⬆️ Bump mypy from 1.11.2 to 1.12.0 (#469)
Bumps [mypy](https://github.com/python/mypy) from 1.11.2 to 1.12.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.11.2...v1.12.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-16 03:02:21 +00:00
dependabot[bot]
b56385b412 ⬆️ Bump pillow from 10.4.0 to 11.0.0 (#470)
Bumps [pillow](https://github.com/python-pillow/Pillow) from 10.4.0 to 11.0.0.
- [Release notes](https://github.com/python-pillow/Pillow/releases)
- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)
- [Commits](https://github.com/python-pillow/Pillow/compare/10.4.0...11.0.0)

---
updated-dependencies:
- dependency-name: pillow
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-16 02:58:27 +00:00
dependabot[bot]
7eea235f52 ⬆️ Bump starlette from 0.38.2 to 0.40.0 (#472)
Bumps [starlette](https://github.com/encode/starlette) from 0.38.2 to 0.40.0.
- [Release notes](https://github.com/encode/starlette/releases)
- [Changelog](https://github.com/encode/starlette/blob/master/docs/release-notes.md)
- [Commits](https://github.com/encode/starlette/compare/0.38.2...0.40.0)

---
updated-dependencies:
- dependency-name: starlette
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-16 10:54:36 +08:00
dependabot[bot]
8a06b572ed ⬆️ Bump arclet-alconna from 1.8.30 to 1.8.31 (#468)
Bumps [arclet-alconna](https://github.com/ArcletProject/Alconna) from 1.8.30 to 1.8.31.
- [Release notes](https://github.com/ArcletProject/Alconna/releases)
- [Changelog](https://github.com/ArcletProject/Alconna/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/ArcletProject/Alconna/compare/v1.8.30...v1.8.31)

---
updated-dependencies:
- dependency-name: arclet-alconna
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-15 23:06:13 +08:00
6867245be3 🔖 1.5.3 2024-10-13 01:14:14 +08:00
dependabot[bot]
eebff0a8ad ⬆️ Bump pandas-stubs from 2.2.2.240909 to 2.2.3.241009 (#466)
Bumps [pandas-stubs](https://github.com/pandas-dev/pandas-stubs) from 2.2.2.240909 to 2.2.3.241009.
- [Changelog](https://github.com/pandas-dev/pandas-stubs/blob/main/docs/release_procedure.md)
- [Commits](https://github.com/pandas-dev/pandas-stubs/compare/v2.2.2.240909...v2.2.3.241009)

---
updated-dependencies:
- dependency-name: pandas-stubs
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-12 16:48:51 +00:00
74eef41506 🏷️ 添加一些 TypeAlias 2024-10-13 00:46:11 +08:00
5eb4771259 TETR.IO list 自动将 country 转大写 2024-10-13 00:45:36 +08:00
7a3a4d936d 🐛 修正一些 trigger 2024-10-13 00:38:37 +08:00
pre-commit-ci[bot]
03ca7c4486 ⬆️ auto update by pre-commit hooks (#463)
* ⬆️ auto update by pre-commit hooks

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.6.8 → v0.6.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.8...v0.6.9)

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-10-10 21:43:13 +00:00
dependabot[bot]
b043d1da59 ⬆️ Bump yarl from 1.13.1 to 1.14.0 (#464)
Bumps [yarl](https://github.com/aio-libs/yarl) from 1.13.1 to 1.14.0.
- [Release notes](https://github.com/aio-libs/yarl/releases)
- [Changelog](https://github.com/aio-libs/yarl/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/yarl/compare/v1.13.1...v1.14.0)

---
updated-dependencies:
- dependency-name: yarl
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-10 21:24:46 +00:00
dependabot[bot]
c9659201b1 ⬆️ Bump nonebot-plugin-alconna from 0.52.3 to 0.53.0 (#462)
Bumps [nonebot-plugin-alconna](https://github.com/nonebot/plugin-alconna) from 0.52.3 to 0.53.0.
- [Release notes](https://github.com/nonebot/plugin-alconna/releases)
- [Commits](https://github.com/nonebot/plugin-alconna/compare/v0.52.3...v0.53.0)

---
updated-dependencies:
- dependency-name: nonebot-plugin-alconna
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-10 21:20:07 +00:00
dependabot[bot]
617d3ec658 ⬆️ Bump ruff from 0.6.8 to 0.6.9 (#461)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.6.8 to 0.6.9.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.6.8...0.6.9)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-10 21:16:06 +00:00
dependabot[bot]
57a1992675 ⬆️ Bump rich from 13.9.1 to 13.9.2 (#460)
Bumps [rich](https://github.com/Textualize/rich) from 13.9.1 to 13.9.2.
- [Release notes](https://github.com/Textualize/rich/releases)
- [Changelog](https://github.com/Textualize/rich/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Textualize/rich/compare/v13.9.1...v13.9.2)

---
updated-dependencies:
- dependency-name: rich
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-10 21:11:59 +00:00
dependabot[bot]
8d1d2f329e ⬆️ Bump basedpyright from 1.18.3 to 1.18.4 (#465)
Bumps [basedpyright](https://github.com/detachhead/basedpyright) from 1.18.3 to 1.18.4.
- [Release notes](https://github.com/detachhead/basedpyright/releases)
- [Commits](https://github.com/detachhead/basedpyright/compare/v1.18.3...v1.18.4)

---
updated-dependencies:
- dependency-name: basedpyright
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-11 05:07:53 +08:00
dependabot[bot]
fa6cbd5c6d ⬆️ Bump rich from 13.8.1 to 13.9.1 (#459)
Bumps [rich](https://github.com/Textualize/rich) from 13.8.1 to 13.9.1.
- [Release notes](https://github.com/Textualize/rich/releases)
- [Changelog](https://github.com/Textualize/rich/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Textualize/rich/compare/v13.8.1...v13.9.1)

---
updated-dependencies:
- dependency-name: rich
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-03 04:33:33 +08:00
pre-commit-ci[bot]
9f0f0b87f4 ⬆️ auto update by pre-commit hooks (#457)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.6.7 → v0.6.8](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.7...v0.6.8)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-10-02 15:59:02 +00:00
dependabot[bot]
96c298b1b8 ⬆️ Bump ruff from 0.6.7 to 0.6.8 (#451)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.6.7 to 0.6.8.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.6.7...0.6.8)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-02 15:35:25 +00:00
dependabot[bot]
df5ced235d ⬆️ Bump basedpyright from 1.17.5 to 1.18.3 (#458)
Bumps [basedpyright](https://github.com/detachhead/basedpyright) from 1.17.5 to 1.18.3.
- [Release notes](https://github.com/detachhead/basedpyright/releases)
- [Commits](https://github.com/detachhead/basedpyright/compare/v1.17.5...v1.18.3)

---
updated-dependencies:
- dependency-name: basedpyright
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-02 15:31:34 +00:00
dependabot[bot]
af83c7a2d9 ⬆️ Bump nonebot-adapter-qq from 1.5.1 to 1.5.2 (#455)
Bumps [nonebot-adapter-qq](https://github.com/nonebot/adapter-qq) from 1.5.1 to 1.5.2.
- [Release notes](https://github.com/nonebot/adapter-qq/releases)
- [Commits](https://github.com/nonebot/adapter-qq/compare/v1.5.1...v1.5.2)

---
updated-dependencies:
- dependency-name: nonebot-adapter-qq
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-02 15:26:22 +00:00
dependabot[bot]
bc41a91034 ⬆️ Bump yarl from 1.11.1 to 1.13.1 (#454)
Bumps [yarl](https://github.com/aio-libs/yarl) from 1.11.1 to 1.13.1.
- [Release notes](https://github.com/aio-libs/yarl/releases)
- [Changelog](https://github.com/aio-libs/yarl/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/yarl/compare/v1.11.1...v1.13.1)

---
updated-dependencies:
- dependency-name: yarl
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-02 15:22:33 +00:00
dependabot[bot]
d97291d1bc ⬆️ Bump aiocache from 0.12.2 to 0.12.3 (#449)
Bumps [aiocache](https://github.com/aio-libs/aiocache) from 0.12.2 to 0.12.3.
- [Release notes](https://github.com/aio-libs/aiocache/releases)
- [Changelog](https://github.com/aio-libs/aiocache/blob/master/.gitchangelog.rc)
- [Commits](https://github.com/aio-libs/aiocache/compare/v0.12.2...v0.12.3)

---
updated-dependencies:
- dependency-name: aiocache
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-02 23:18:54 +08:00
5b56de9de1 🚨 添加一个 noqa( 2024-10-02 03:42:34 +08:00
0898a81331 优化数据库迁移脚本 2024-10-02 03:22:43 +08:00
pre-commit-ci[bot]
d464059c0a ⬆️ auto update by pre-commit hooks (#447)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.6.5 → v0.6.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.5...v0.6.7)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-09-24 06:21:04 +00:00
dependabot[bot]
6ea8b9328c ⬆️ Bump nonebot-plugin-alconna from 0.52.2 to 0.52.3 (#446)
Bumps [nonebot-plugin-alconna](https://github.com/nonebot/plugin-alconna) from 0.52.2 to 0.52.3.
- [Release notes](https://github.com/nonebot/plugin-alconna/releases)
- [Commits](https://github.com/nonebot/plugin-alconna/compare/v0.52.2...v0.52.3)

---
updated-dependencies:
- dependency-name: nonebot-plugin-alconna
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-24 06:18:24 +00:00
dependabot[bot]
773ff5545c ⬆️ Bump ruff from 0.6.6 to 0.6.7 (#445)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.6.6 to 0.6.7.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.6.6...0.6.7)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-24 06:14:09 +00:00
dependabot[bot]
94710b938b ⬆️ Bump nonebot-adapter-satori from 0.12.5 to 0.12.6 (#444)
Bumps [nonebot-adapter-satori](https://github.com/nonebot/adapter-satori) from 0.12.5 to 0.12.6.
- [Release notes](https://github.com/nonebot/adapter-satori/releases)
- [Commits](https://github.com/nonebot/adapter-satori/compare/v0.12.5...v0.12.6)

---
updated-dependencies:
- dependency-name: nonebot-adapter-satori
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-24 14:10:06 +08:00
ec09bb734d 📌 将 arclet-alconna 固定至 2.0.0 以下 2024-09-24 14:03:38 +08:00
dependabot[bot]
9e9a642847 ⬆️ Bump pandas from 2.2.2 to 2.2.3 (#443)
Bumps [pandas](https://github.com/pandas-dev/pandas) from 2.2.2 to 2.2.3.
- [Release notes](https://github.com/pandas-dev/pandas/releases)
- [Commits](https://github.com/pandas-dev/pandas/compare/v2.2.2...v2.2.3)

---
updated-dependencies:
- dependency-name: pandas
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-21 06:48:48 +00:00
dependabot[bot]
04e0b14e72 ⬆️ Bump ruff from 0.6.5 to 0.6.6 (#442)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.6.5 to 0.6.6.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.6.5...0.6.6)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-21 14:44:44 +08:00
dependabot[bot]
20ce9c64be ⬆️ Bump nonebot-adapter-onebot from 2.4.4 to 2.4.5 (#440)
Bumps [nonebot-adapter-onebot](https://github.com/nonebot/adapter-onebot) from 2.4.4 to 2.4.5.
- [Release notes](https://github.com/nonebot/adapter-onebot/releases)
- [Commits](https://github.com/nonebot/adapter-onebot/compare/v2.4.4...v2.4.5)

---
updated-dependencies:
- dependency-name: nonebot-adapter-onebot
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-19 15:16:22 +00:00
dependabot[bot]
8af07bf031 ⬆️ Bump basedpyright from 1.17.4 to 1.17.5 (#441)
Bumps [basedpyright](https://github.com/detachhead/basedpyright) from 1.17.4 to 1.17.5.
- [Release notes](https://github.com/detachhead/basedpyright/releases)
- [Commits](https://github.com/detachhead/basedpyright/compare/v1.17.4...v1.17.5)

---
updated-dependencies:
- dependency-name: basedpyright
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-19 13:20:49 +00:00
pre-commit-ci[bot]
3a904f67ad ⬆️ auto update by pre-commit hooks (#439)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.6.4 → v0.6.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.4...v0.6.5)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-09-19 12:24:51 +00:00
dependabot[bot]
fc9b751ac4 ⬆️ Bump types-lxml from 2024.8.7 to 2024.9.16 (#438)
Bumps [types-lxml](https://github.com/abelcheung/types-lxml) from 2024.8.7 to 2024.9.16.
- [Release notes](https://github.com/abelcheung/types-lxml/releases)
- [Commits](https://github.com/abelcheung/types-lxml/compare/2024.08.07...2024.09.16)

---
updated-dependencies:
- dependency-name: types-lxml
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-19 20:21:19 +08:00
cb4c6b96f0 🔖 1.5.2 2024-09-14 05:10:32 +08:00
dependabot[bot]
25c3777c0f ⬆️ Bump playwright from 1.46.0 to 1.47.0 (#437)
Bumps [playwright](https://github.com/Microsoft/playwright-python) from 1.46.0 to 1.47.0.
- [Release notes](https://github.com/Microsoft/playwright-python/releases)
- [Commits](https://github.com/Microsoft/playwright-python/compare/v1.46.0...v1.47.0)

---
updated-dependencies:
- dependency-name: playwright
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-13 21:05:13 +00:00
dependabot[bot]
193fd1da2a ⬆️ Bump ruff from 0.6.4 to 0.6.5 (#436)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.6.4 to 0.6.5.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.6.4...0.6.5)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-13 21:02:31 +00:00
dependabot[bot]
2cd609dd40 ⬆️ Bump basedpyright from 1.17.3 to 1.17.4 (#435)
Bumps [basedpyright](https://github.com/detachhead/basedpyright) from 1.17.3 to 1.17.4.
- [Release notes](https://github.com/detachhead/basedpyright/releases)
- [Commits](https://github.com/detachhead/basedpyright/compare/v1.17.3...v1.17.4)

---
updated-dependencies:
- dependency-name: basedpyright
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-13 20:58:38 +00:00
a206098805 ️ 试图提高截图速度 2024-09-14 04:50:37 +08:00
dependabot[bot]
d493ba5f0d ⬆️ Bump yarl from 1.11.0 to 1.11.1 (#433)
Bumps [yarl](https://github.com/aio-libs/yarl) from 1.11.0 to 1.11.1.
- [Release notes](https://github.com/aio-libs/yarl/releases)
- [Changelog](https://github.com/aio-libs/yarl/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/yarl/compare/v1.11.0...v1.11.1)

---
updated-dependencies:
- dependency-name: yarl
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-11 11:58:41 +00:00
dependabot[bot]
581d1f9674 ⬆️ Bump pandas-stubs from 2.2.2.240807 to 2.2.2.240909 (#434)
Bumps [pandas-stubs](https://github.com/pandas-dev/pandas-stubs) from 2.2.2.240807 to 2.2.2.240909.
- [Changelog](https://github.com/pandas-dev/pandas-stubs/blob/main/docs/release_procedure.md)
- [Commits](https://github.com/pandas-dev/pandas-stubs/compare/v2.2.2.240807...v2.2.2.240909)

---
updated-dependencies:
- dependency-name: pandas-stubs
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-11 11:54:55 +00:00
dependabot[bot]
01c99e8a8c ⬆️ Bump rich from 13.8.0 to 13.8.1 (#432)
Bumps [rich](https://github.com/Textualize/rich) from 13.8.0 to 13.8.1.
- [Release notes](https://github.com/Textualize/rich/releases)
- [Changelog](https://github.com/Textualize/rich/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Textualize/rich/compare/v13.8.0...v13.8.1)

---
updated-dependencies:
- dependency-name: rich
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-11 19:51:24 +08:00
dependabot[bot]
eb3f4bea04 ⬆️ Bump yarl from 1.10.0 to 1.11.0 (#431)
Bumps [yarl](https://github.com/aio-libs/yarl) from 1.10.0 to 1.11.0.
- [Release notes](https://github.com/aio-libs/yarl/releases)
- [Changelog](https://github.com/aio-libs/yarl/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/yarl/compare/v1.10.0...v1.11.0)

---
updated-dependencies:
- dependency-name: yarl
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-09 19:38:35 +00:00
dependabot[bot]
ebbbd68b05 ⬆️ Bump basedpyright from 1.17.2 to 1.17.3 (#430)
Bumps [basedpyright](https://github.com/detachhead/basedpyright) from 1.17.2 to 1.17.3.
- [Release notes](https://github.com/detachhead/basedpyright/releases)
- [Commits](https://github.com/detachhead/basedpyright/compare/v1.17.2...v1.17.3)

---
updated-dependencies:
- dependency-name: basedpyright
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-10 03:35:03 +08:00
pre-commit-ci[bot]
10e0eb815e ⬆️ auto update by pre-commit hooks (#422)
* ⬆️ auto update by pre-commit hooks

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.6.2 → v0.6.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.2...v0.6.4)

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-09-08 16:49:56 +08:00
dependabot[bot]
a57b04e181 ⬆️ Bump yarl from 1.9.4 to 1.10.0 (#429)
Bumps [yarl](https://github.com/aio-libs/yarl) from 1.9.4 to 1.10.0.
- [Release notes](https://github.com/aio-libs/yarl/releases)
- [Changelog](https://github.com/aio-libs/yarl/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/yarl/compare/v1.9.4...v1.10.0)

---
updated-dependencies:
- dependency-name: yarl
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-08 08:45:09 +00:00
dependabot[bot]
cc2e71f1a5 ⬆️ Bump nonebot-adapter-satori from 0.12.3 to 0.12.5 (#426)
Bumps [nonebot-adapter-satori](https://github.com/nonebot/adapter-satori) from 0.12.3 to 0.12.5.
- [Release notes](https://github.com/nonebot/adapter-satori/releases)
- [Commits](https://github.com/nonebot/adapter-satori/compare/v0.12.3...v0.12.5)

---
updated-dependencies:
- dependency-name: nonebot-adapter-satori
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-08 08:41:25 +00:00
dependabot[bot]
3384263bb2 ⬆️ Bump basedpyright from 1.17.1 to 1.17.2 (#425)
Bumps [basedpyright](https://github.com/detachhead/basedpyright) from 1.17.1 to 1.17.2.
- [Release notes](https://github.com/detachhead/basedpyright/releases)
- [Commits](https://github.com/detachhead/basedpyright/compare/v1.17.1...v1.17.2)

---
updated-dependencies:
- dependency-name: basedpyright
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-08 08:37:45 +00:00
dependabot[bot]
68f210dc4f ⬆️ Bump ruff from 0.6.2 to 0.6.4 (#428)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.6.2 to 0.6.4.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.6.2...0.6.4)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-08 16:34:07 +08:00
dependabot[bot]
00a85fe3e9 ⬆️ Bump nonebot-plugin-alconna from 0.52.1 to 0.52.2 (#419)
Bumps [nonebot-plugin-alconna](https://github.com/nonebot/plugin-alconna) from 0.52.1 to 0.52.2.
- [Release notes](https://github.com/nonebot/plugin-alconna/releases)
- [Commits](https://github.com/nonebot/plugin-alconna/compare/v0.52.1...v0.52.2)

---
updated-dependencies:
- dependency-name: nonebot-plugin-alconna
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-29 17:02:45 +00:00
dependabot[bot]
a10a7584ae ⬆️ Bump nonebot-plugin-user from 0.4.2 to 0.4.3 (#418)
Bumps [nonebot-plugin-user](https://github.com/he0119/nonebot-plugin-user) from 0.4.2 to 0.4.3.
- [Release notes](https://github.com/he0119/nonebot-plugin-user/releases)
- [Changelog](https://github.com/he0119/nonebot-plugin-user/blob/main/CHANGELOG.md)
- [Commits](https://github.com/he0119/nonebot-plugin-user/compare/v0.4.2...v0.4.3)

---
updated-dependencies:
- dependency-name: nonebot-plugin-user
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-29 16:58:37 +00:00
dependabot[bot]
95aac5e321 ⬆️ Bump basedpyright from 1.17.0 to 1.17.1 (#417)
Bumps [basedpyright](https://github.com/detachhead/basedpyright) from 1.17.0 to 1.17.1.
- [Release notes](https://github.com/detachhead/basedpyright/releases)
- [Commits](https://github.com/detachhead/basedpyright/compare/v1.17.0...v1.17.1)

---
updated-dependencies:
- dependency-name: basedpyright
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-30 00:56:00 +08:00
89d8c938e2 👷 改成在 CI 中运行 2024-08-28 00:50:19 +08:00
84db42f1ce 👷 改用 basedpyright 2024-08-28 00:40:24 +08:00
dependabot[bot]
0a660922bb ⬆️ Bump nonebot-plugin-alconna from 0.51.4 to 0.52.1 (#411)
Bumps [nonebot-plugin-alconna](https://github.com/nonebot/plugin-alconna) from 0.51.4 to 0.52.1.
- [Release notes](https://github.com/nonebot/plugin-alconna/releases)
- [Commits](https://github.com/nonebot/plugin-alconna/compare/v0.51.4...v0.52.1)

---
updated-dependencies:
- dependency-name: nonebot-plugin-alconna
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-26 22:35:47 +00:00
dependabot[bot]
56bc98cc79 ⬆️ Bump mypy from 1.11.1 to 1.11.2 (#412)
Bumps [mypy](https://github.com/python/mypy) from 1.11.1 to 1.11.2.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.11.1...v1.11.2)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-26 22:33:40 +00:00
pre-commit-ci[bot]
be61683b51 ⬆️ auto update by pre-commit hooks (#413)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.6.1 → v0.6.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.1...v0.6.2)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-08-27 06:31:38 +08:00
ccd5706a95 🔖 1.5.1 2024-08-26 11:43:51 +08:00
b69240caa5 👷 添加 Pyright 类型检查 2024-08-26 11:41:03 +08:00
49d00f4d0e 添加开发依赖 pyright 2024-08-26 11:38:44 +08:00
389a850025 🐛 修复打过但是没数据的爆炸
为啥会没数据??
2024-08-26 11:38:14 +08:00
20dcc2bc3d 🔖 1.5.0 2024-08-25 23:17:32 +08:00
606dddbca2 适配新赛季 list 2024-08-25 23:16:33 +08:00
f509b03cd0 🔖 1.4.18 2024-08-24 21:09:28 +08:00
6293d088db 适配新赛季 rank 2024-08-24 21:06:45 +08:00
97e2abed78 添加 debug 依赖 matplotlib pyqt6 2024-08-24 21:01:30 +08:00
dependabot[bot]
5ea3fcb234 ⬆️ Bump types-pillow from 10.2.0.20240520 to 10.2.0.20240822 (#409)
Bumps [types-pillow](https://github.com/python/typeshed) from 10.2.0.20240520 to 10.2.0.20240822.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-pillow
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-22 19:37:03 +00:00
dependabot[bot]
ca33ba1310 ⬆️ Bump ruff from 0.6.1 to 0.6.2 (#408)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.6.1 to 0.6.2.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.6.1...0.6.2)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-23 03:34:57 +08:00
3629a2ff4a 🚨 make type checker happy 2024-08-22 01:14:59 +08:00
a2108c9776 localstore 使用 get_plugin_xxx_dir 2024-08-21 05:11:36 +08:00
7133cd9384 🔖 1.4.17 2024-08-20 08:58:31 +08:00
pre-commit-ci[bot]
406bc7674e ⬆️ auto update by pre-commit hooks (#406)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.5.7 → v0.6.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.7...v0.6.1)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-08-19 23:40:59 +00:00
呵呵です
259b38fda5 支持设置代理 (#407)
*  添加依赖 yarl

*  添加依赖 msgspec

*  移除依赖 ujson

* ♻️ 重构 request 使其支持分别设置代理

* ♻️ 重构 resource 接口

* ️ 不再重复获取 Config

* ♻️ 使用 yarl 替换 urllib.parse

* ️ 给 get_self_netloc 加个 cache

*  request 使用 proxy

*  更新模板使用 proxy

* 🐛 修复删除 ujson 依赖后 迁移脚本报错的bug
2024-08-19 23:37:51 +00:00
dependabot[bot]
414345ae5c ⬆️ Bump ruff from 0.6.0 to 0.6.1 (#405)
* ⬆️ Bump ruff from 0.6.0 to 0.6.1

Bumps [ruff](https://github.com/astral-sh/ruff) from 0.6.0 to 0.6.1.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.6.0...0.6.1)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* 🚨 auto fix by pre-commit hooks

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-08-19 17:51:20 +00:00
dependabot[bot]
341cbd86cd ⬆️ Bump nonebot-plugin-user from 0.4.1 to 0.4.2 (#404)
* ⬆️ Bump nonebot-plugin-user from 0.4.1 to 0.4.2

Bumps [nonebot-plugin-user](https://github.com/he0119/nonebot-plugin-user) from 0.4.1 to 0.4.2.
- [Release notes](https://github.com/he0119/nonebot-plugin-user/releases)
- [Changelog](https://github.com/he0119/nonebot-plugin-user/blob/main/CHANGELOG.md)
- [Commits](https://github.com/he0119/nonebot-plugin-user/compare/v0.4.1...v0.4.2)

---
updated-dependencies:
- dependency-name: nonebot-plugin-user
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* 🚨 auto fix by pre-commit hooks

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-08-19 16:06:57 +00:00
dependabot[bot]
bf7804738e ⬆️ Bump nonebot-adapter-qq from 1.5.0 to 1.5.1 (#403)
* ⬆️ Bump nonebot-adapter-qq from 1.5.0 to 1.5.1

Bumps [nonebot-adapter-qq](https://github.com/nonebot/adapter-qq) from 1.5.0 to 1.5.1.
- [Release notes](https://github.com/nonebot/adapter-qq/releases)
- [Commits](https://github.com/nonebot/adapter-qq/compare/v1.5.0...v1.5.1)

---
updated-dependencies:
- dependency-name: nonebot-adapter-qq
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* 🚨 auto fix by pre-commit hooks

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-08-19 15:52:37 +00:00
dependabot[bot]
553f373671 ⬆️ Bump nonebot2 from 2.3.2 to 2.3.3 (#402)
* ⬆️ Bump nonebot2 from 2.3.2 to 2.3.3

Bumps [nonebot2](https://github.com/nonebot/nonebot2) from 2.3.2 to 2.3.3.
- [Release notes](https://github.com/nonebot/nonebot2/releases)
- [Changelog](https://github.com/nonebot/nonebot2/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nonebot/nonebot2/compare/v2.3.2...v2.3.3)

---
updated-dependencies:
- dependency-name: nonebot2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* 🚨 auto fix by pre-commit hooks

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-08-19 15:49:27 +00:00
e53e164a52 🔖 1.4.16 2024-08-18 01:25:43 +08:00
2cd7d89c3e 截图前等待 networkidle
还是得等)
2024-08-18 01:19:03 +08:00
b8b6d5f6c8 🔖 1.4.15 2024-08-17 22:41:32 +08:00
7a44c0dca5 🐛 修 s1 没打的爆炸 2024-08-17 22:40:47 +08:00
4155d8eb42 🔖 1.4.14 2024-08-17 19:50:52 +08:00
4cc942d226 🐛 修 40l 无 hold 爆炸 2024-08-17 19:50:25 +08:00
996dd565d8 🔖 1.4.13 2024-08-17 18:43:11 +08:00
5b0660e45b 🐛 修第一赛季最后没有段位爆炸 2024-08-17 18:41:31 +08:00
8d1ebc06d1 🔖 1.4.12 2024-08-17 05:07:27 +08:00
c57aa48048 🐛 修没打过的爆炸 2024-08-17 05:06:59 +08:00
ad90562fdf 🐛 修国家为空爆炸 2024-08-17 04:45:06 +08:00
cbc96fc09e 🔖 1.4.11 2024-08-17 04:37:18 +08:00
8e10cfe0d0 🐛 修最佳段位为 z 爆炸 2024-08-17 04:31:14 +08:00
d192f0506d 🔖 1.4.10 2024-08-17 04:21:57 +08:00
44aed656b8 🐛 忘记 push schema 2024-08-17 04:21:33 +08:00
feb662b980 🔖 1.4.9 2024-08-17 04:17:57 +08:00
ed6eb9a5cf 💩 迅速的适配第二赛季 2024-08-17 04:17:41 +08:00
25e281a4c5 🎨 localstore 一律从 config 导入常量使用 2024-08-16 18:55:13 +08:00
a2d69b9113 ️ 尝试提高截图性能 2024-08-16 18:53:12 +08:00
c8907a47a4 💥 插件配置现在使用 ScopedConfig 2024-08-16 18:52:47 +08:00
9fb176b4bc 确保同一个账号生成的随机头像一致 2024-08-16 03:42:11 +08:00
dependabot[bot]
53740265b6 ⬆️ Bump ruff from 0.5.7 to 0.6.0 (#401)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.5.7 to 0.6.0.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.5.7...0.6.0)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-15 19:16:44 +00:00
dependabot[bot]
e6119074ce ⬆️ Bump nonebot-plugin-user from 0.4.0 to 0.4.1 (#400)
Bumps [nonebot-plugin-user](https://github.com/he0119/nonebot-plugin-user) from 0.4.0 to 0.4.1.
- [Release notes](https://github.com/he0119/nonebot-plugin-user/releases)
- [Changelog](https://github.com/he0119/nonebot-plugin-user/blob/main/CHANGELOG.md)
- [Commits](https://github.com/he0119/nonebot-plugin-user/compare/v0.4.0...v0.4.1)

---
updated-dependencies:
- dependency-name: nonebot-plugin-user
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-16 03:12:41 +08:00
160 changed files with 10237 additions and 5400 deletions

View File

@@ -1,12 +0,0 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "pip" # See documentation for possible values
directory: "/" # Location of package manifests
target-branch: "main"
schedule:
interval: "daily"

View File

@@ -3,7 +3,7 @@ name: Release CI
on:
push:
tags:
- "*"
- '*'
jobs:
release:
@@ -12,24 +12,25 @@ jobs:
id-token: write
contents: write
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install poetry
run: pipx install poetry
shell: bash
- uses: actions/setup-python@v4
- uses: astral-sh/setup-uv@v6
name: Setup UV
with:
python-version: '3.11'
cache: "poetry"
enable-cache: true
- run: poetry install
- name: 'Set up Python'
uses: actions/setup-python@v5
with:
python-version-file: '.python-version'
- run: uv sync
shell: bash
- name: Get Version
id: version
run: |
echo "VERSION=$(poetry version -s)" >> $GITHUB_OUTPUT
echo "VERSION=$(uvx pdm show --version)" >> $GITHUB_OUTPUT
echo "TAG_VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
echo "TAG_NAME=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
@@ -38,10 +39,10 @@ jobs:
run: exit 1
- name: Build Package
run: poetry build
run: uv build
- name: Publish Package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
run: uv publish
- name: Publish Package to GitHub Release
run: gh release create ${{ steps.version.outputs.TAG_NAME }} dist/*.tar.gz dist/*.whl -t "🔖 ${{ steps.version.outputs.TAG_NAME }}" --generate-notes

58
.github/workflows/Test.yml vendored Normal file
View File

@@ -0,0 +1,58 @@
name: Code Coverage
on:
push:
branches:
- 'main'
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
name: Test
runs-on: ${{ matrix.os }}
strategy:
matrix:
# python-version: ['3.10', '3.11', '3.12', '3.13']
python-version: ['3.10', '3.11', '3.12']
os: [ubuntu-latest, windows-latest, macos-latest]
fail-fast: false
env:
OS: ${{ matrix.os }}
PYTHON_VERSION: ${{ matrix.python-version }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
cache-suffix: ${{ env.PYTHON_VERSION }}_${{ env.OS }}
- name: Install Dependencies
run: |
uv python pin ${{ env.PYTHON_VERSION }}
uv sync --group test
- name: Run tests
run: uv run pytest --cov=nonebot_plugin_tetris_stats --cov-report xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
env_vars: OS,PYTHON_VERSION
check:
if: always()
needs: test
runs-on: ubuntu-latest
steps:
- name: Decide whether the needed jobs succeeded or failed
uses: re-actors/alls-green@2765efec08f0fd63e83ad900f5fd75646be69ff6
with:
jobs: ${{ toJSON(needs) }}

View File

@@ -1,27 +1,33 @@
name: TypeCheck
on:
push:
push:
jobs:
Mypy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
TypeCheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install poetry
run: pipx install poetry
shell: bash
- uses: astral-sh/setup-uv@v6
name: Setup UV
with:
enable-cache: true
- uses: actions/setup-python@v5
with:
python-version: '3.10'
cache: 'poetry'
- name: 'Set up Python'
uses: actions/setup-python@v5
with:
python-version-file: '.python-version'
- run: poetry install
shell: bash
- run: uv sync
shell: bash
- name: Run Mypy
shell: bash
run: |
poetry run mypy ./nonebot_plugin_tetris_stats
- name: Run Mypy
shell: bash
run: |
uv run mypy ./nonebot_plugin_tetris_stats
- name: Run BasedPyright
shell: bash
run: |
uv run basedpyright ./nonebot_plugin_tetris_stats/

View File

@@ -9,14 +9,14 @@
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
name: 'CodeQL'
on:
push:
branches: [ main ]
branches: [main]
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]
branches: [main]
schedule:
- cron: '17 6 * * 5'
@@ -32,41 +32,40 @@ jobs:
strategy:
fail-fast: false
matrix:
language: [ 'python' ]
language: ['python']
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Checkout repository
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v3
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3

546
.gitignore vendored
View File

@@ -1,24 +1,528 @@
.idea
# Created by https://www.toptal.com/developers/gitignore/api/linux,macos,python,pycharm,windows,visualstudiocode,node
# Edit at https://www.toptal.com/developers/gitignore?templates=linux,macos,python,pycharm,windows,visualstudiocode,node
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### macOS Patch ###
# iCloud generated files
*.icloud
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
test_*
Untitled*
*copy*
.vscode
*dev*
*_cache*
*backup*
*.pyc
node_modules
.prettier*
package.json
pnpm-lock.yaml
*.drawio.svg
package-lock.json
*Zone.Identifier
.env*
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
### Node Patch ###
# Serverless Webpack directories
.webpack/
# Optional stylelint cache
# SvelteKit build / generate output
.svelte-kit
### PyCharm ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### PyCharm Patch ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
# *.iml
# modules.xml
# .idea/misc.xml
# *.ipr
# Sonarlint plugin
# https://plugins.jetbrains.com/plugin/7973-sonarlint
.idea/**/sonarlint/
# SonarQube Plugin
# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
.idea/**/sonarIssues.xml
# Markdown Navigator plugin
# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
.idea/**/markdown-navigator.xml
.idea/**/markdown-navigator-enh.xml
.idea/**/markdown-navigator/
# Cache file creation bug
# See https://youtrack.jetbrains.com/issue/JBR-2257
.idea/$CACHE_FILE$
# CodeStream plugin
# https://plugins.jetbrains.com/plugin/12206-codestream
.idea/codestream.xml
# Azure Toolkit for IntelliJ plugin
# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
.idea/**/azureSettings.xml
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
### Python Patch ###
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
poetry.toml
# ruff
.ruff_cache/
# LSP config files
pyrightconfig.json
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
### Windows ###
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# End of https://www.toptal.com/developers/gitignore/api/linux,macos,python,pycharm,windows,visualstudiocode,node
# NoneBot2
bot.py
TODO
*.fish
extracted_skin_mino_*
sample_*
.env*
# Misc
ignore_*
*.backup
TODO*

View File

@@ -1,22 +1,22 @@
default_install_hook_types: [pre-commit, prepare-commit-msg]
ci:
autofix_commit_msg: ':rotating_light: auto fix by pre-commit hooks'
autofix_prs: true
autoupdate_branch: main
autoupdate_schedule: weekly
autoupdate_commit_msg: ':arrow_up: auto update by pre-commit hooks'
autofix_commit_msg: ':rotating_light: auto fix by pre-commit hooks'
autofix_prs: true
autoupdate_branch: main
autoupdate_schedule: weekly
autoupdate_commit_msg: ':arrow_up: auto update by pre-commit hooks'
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.5.7
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
stages: [commit]
- id: ruff-format
stages: [commit]
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.4
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
stages: [pre-commit]
- id: ruff-format
stages: [pre-commit]
- repo: https://github.com/nonebot/nonemoji
rev: v0.1.4
hooks:
- id: nonemoji
stages: [prepare-commit-msg]
- repo: https://github.com/nonebot/nonemoji
rev: v0.1.4
hooks:
- id: nonemoji
stages: [prepare-commit-msg]

1
.python-version Normal file
View File

@@ -0,0 +1 @@
3.10

58
CONTRIBUTING.en-US.md Normal file
View File

@@ -0,0 +1,58 @@
# How to Contribute?
## Setting Up the Environment
### For Developers with Basic Python Knowledge
First, you need install [uv](https://docs.astral.sh/uv/).
Then:
```bash
# Set up the basic Python environment
uv python install 3.10
# Clone the repository
git clone https://github.com/A-Minos/nonebot-plugin-tetris-stats.git
cd nonebot-plugin-tetris-stats
# Install dependencies
uv sync
```
## Development
### Code Development
1. For static code analysis, use [ruff](https://docs.astral.sh/ruff/). You can install the corresponding plugin for your IDE or use the command line with `ruff check ./nonebot_plugin_tetris_stats/` to check the code.
2. For code formatting, use [ruff](https://docs.astral.sh/ruff/). You can install the corresponding plugin for your IDE or use the command line with `ruff format ./nonebot_plugin_tetris_stats/` to format the code.
3. For type checking, use both [basedpyright](https://docs.basedpyright.com/latest/) and [mypy](https://www.mypy-lang.org/). You can install the corresponding plugins for your IDE or use the following commands in the terminal to check the code:
```bash
# basedpyright
basedpyright ./nonebot_plugin_tetris_stats/
# mypy
mypy ./nonebot_plugin_tetris_stats/
```
### Internationalization
This project uses [Tarina](https://github.com/ArcletProject/Tarina) for internationalization support.
#### Adding a New Language
1. Navigate to the `./nonebot_plugin_tetris_stats/i18n/` directory.
2. Run `tarina-lang create {language_code}` \* Please note that the language code should preferably follow the [IETF language tag](https://en.wikipedia.org/wiki/IETF_language_tag) standard.
3. Edit the generated `./nonebot_plugin_tetris_stats/i18n/{language_code}.json` file.
#### Updating an Existing Language
1. Navigate to the `./nonebot_plugin_tetris_stats/i18n/` directory.
2. Edit the corresponding `./nonebot_plugin_tetris_stats/i18n/{language_code}.json` file.
#### Adding New Entries
1. Navigate to the `./nonebot_plugin_tetris_stats/i18n/` directory.
2. Edit the `.template.json` file.
3. Run `tarina-lang schema && tarina-lang model`.
4. Modify the language files, adding new entries at least to `en-US.json`.

57
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,57 @@
# 我该如何参与开发?
## 配置环境
首先你需要安装 [uv](https://docs.astral.sh/uv/)。
然后:
```bash
# 配置基础 Python 环境
uv python install 3.10
# 克隆仓库
git clone https://github.com/A-Minos/nonebot-plugin-tetris-stats.git
cd nonebot-plugin-tetris-stats
# 安装依赖
uv sync
```
## 开发
### 代码开发
1. 代码静态检查使用 [ruff](https://docs.astral.sh/ruff/)你可以为你的ide安装对应插件来使用也可以在命令行使用`ruff check ./nonebot_plugin_tetris_stats/`来检查代码。
2. 代码格式化使用 [ruff](https://docs.astral.sh/ruff/)你可以为你的ide安装对应插件来使用也可以在命令行使用`ruff format ./nonebot_plugin_tetris_stats/`来格式化代码。
3. 类型检查同时使用 [basedpyright](https://docs.basedpyright.com/latest/) 和 [mypy](https://www.mypy-lang.org/)你可以为你的ide安装对应插件来使用。
也可以在命令行使用下面的命令来检查代码:
```bash
# basedpyright
basedpyright ./nonebot_plugin_tetris_stats/
# mypy
mypy ./nonebot_plugin_tetris_stats/
```
### 国际化
本项目使用 [Tarina](https://github.com/ArcletProject/Tarina) 提供国际化支持。
#### 添加新的语言
1. 进入 `./nonebot_plugin_tetris_stats/i18n/` 目录。
2. 运行 `tarina-lang create {语言代码}` \* 请注意,语言代码最好符合 [IETF语言标签](https://zh.wikipedia.org/wiki/IETF%E8%AF%AD%E8%A8%80%E6%A0%87%E7%AD%BE) 的规范。
3. 编辑生成的 `./nonebot_plugin_tetris_stats/i18n/{语言代码}.json` 文件。
#### 更新已有语言
1. 进入 `./nonebot_plugin_tetris_stats/i18n/` 目录。
2. 编辑对应的 `./nonebot_plugin_tetris_stats/i18n/{语言代码}.json` 文件。
#### 添加新的条目
1. 进入 `./nonebot_plugin_tetris_stats/i18n/` 目录。
2. 编辑 `.template.json` 文件。
3. 运行 `tarina-lang schema && tarina-lang model`
4. 修改语言文件,至少为`en-US.json`添加新的条目。

View File

@@ -6,10 +6,9 @@ require_plugins = {
'nonebot_plugin_apscheduler',
'nonebot_plugin_localstore',
'nonebot_plugin_orm',
'nonebot_plugin_session_orm',
'nonebot_plugin_session',
'nonebot_plugin_uninfo',
'nonebot_plugin_user',
'nonebot_plugin_userinfo',
'nonebot_plugin_waiter',
}
for i in require_plugins:

View File

@@ -1,13 +1,37 @@
from pathlib import Path
from nonebot_plugin_localstore import get_cache_dir
from pydantic import BaseModel
from nonebot import get_driver, get_plugin_config
from nonebot_plugin_localstore import get_plugin_cache_dir, get_plugin_data_dir
from pydantic import BaseModel, Field
CACHE_PATH: Path = get_cache_dir('nonebot_plugin_tetris_stats')
CACHE_PATH = get_plugin_cache_dir()
DATA_PATH = get_plugin_data_dir()
class Proxy(BaseModel):
main: str | None = None
github: str | None = None
tetrio: str | None = None
tos: str | None = None
top: str | None = None
class Dev(BaseModel):
enabled: bool = False
template_path: Path | None = None
enable_template_check: bool = True
class ScopedConfig(BaseModel):
request_timeout: float = 30.0
screenshot_quality: float = 2
proxy: Proxy = Field(default_factory=Proxy)
dev: Dev = Field(default_factory=Dev)
class Config(BaseModel):
"""配置类"""
tetris: ScopedConfig = Field(default_factory=ScopedConfig)
tetris_req_timeout: float = 30.0
tetris_screenshot_quality: float = 2
config = get_plugin_config(Config)
global_config = get_driver().config

View File

@@ -25,6 +25,8 @@ depends_on: str | Sequence[str] | None = None
def upgrade(name: str = '') -> None:
if name:
return
if op.get_bind().dialect.name == 'postgresql':
return
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('nonebot_plugin_tetris_stats_iorank', schema=None) as batch_op:
batch_op.alter_column('create_time', new_column_name='update_time', existing_type=sa.DateTime())
@@ -41,6 +43,8 @@ def upgrade(name: str = '') -> None:
def downgrade(name: str = '') -> None:
if name:
return
if op.get_bind().dialect.name == 'postgresql':
return
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('nonebot_plugin_tetris_stats_iorank', schema=None) as batch_op:
batch_op.alter_column('update_time', new_column_name='create_time')

View File

@@ -25,6 +25,8 @@ depends_on: str | Sequence[str] | None = None
def upgrade(name: str = '') -> None:
if name:
return
if op.get_bind().dialect.name == 'postgresql':
return
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('nonebot_plugin_tetris_stats_iorank', schema=None) as batch_op:
batch_op.add_column(sa.Column('file_hash', sa.String(length=128), nullable=True))
@@ -38,6 +40,8 @@ def upgrade(name: str = '') -> None:
def downgrade(name: str = '') -> None:
if name:
return
if op.get_bind().dialect.name == 'postgresql':
return
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('nonebot_plugin_tetris_stats_iorank', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_iorank_file_hash'))

View File

@@ -0,0 +1,42 @@
"""add verify field
迁移 ID: 2ff388a8c486
父迁移: 3588702dd3a4
创建时间: 2025-07-22 18:09:09.734164
"""
from __future__ import annotations
from typing import TYPE_CHECKING
import sqlalchemy as sa
from alembic import op
if TYPE_CHECKING:
from collections.abc import Sequence
revision: str = '2ff388a8c486'
down_revision: str | Sequence[str] | None = '3588702dd3a4'
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade(name: str = '') -> None:
if name:
return
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('nb_t_bind', schema=None) as batch_op:
batch_op.add_column(sa.Column('verify', sa.Boolean(), nullable=False, server_default='false'))
# ### end Alembic commands ###
def downgrade(name: str = '') -> None:
if name:
return
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('nb_t_bind', schema=None) as batch_op:
batch_op.drop_column('verify')
# ### end Alembic commands ###

View File

@@ -0,0 +1,52 @@
"""modify field length
迁移 ID: 3588702dd3a4
父迁移: bc6abd57928f
创建时间: 2025-07-19 17:21:17.927162
"""
from __future__ import annotations
from typing import TYPE_CHECKING
import sqlalchemy as sa
from alembic import op
if TYPE_CHECKING:
from collections.abc import Sequence
revision: str = '3588702dd3a4'
down_revision: str | Sequence[str] | None = 'bc6abd57928f'
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade(name: str = '') -> None:
if name:
return
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('nb_t_tos_hist_data', schema=None) as batch_op:
batch_op.alter_column(
'user_unique_identifier',
existing_type=sa.VARCHAR(length=24),
type_=sa.String(length=256),
existing_nullable=False,
)
# ### end Alembic commands ###
def downgrade(name: str = '') -> None:
if name:
return
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('nb_t_tos_hist_data', schema=None) as batch_op:
batch_op.alter_column(
'user_unique_identifier',
existing_type=sa.String(length=256),
type_=sa.VARCHAR(length=24),
existing_nullable=False,
)
# ### end Alembic commands ###

View File

@@ -16,10 +16,8 @@ from alembic import op
from nonebot.log import logger
from rich.progress import BarColumn, MofNCompleteColumn, Progress, TaskProgressColumn, TextColumn, TimeRemainingColumn
from sqlalchemy import desc, select
from sqlalchemy.dialects import sqlite
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session
from ujson import dumps, loads
if TYPE_CHECKING:
from collections.abc import Sequence
@@ -30,7 +28,9 @@ branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def migrate_old_data() -> None:
def migrate_old_data() -> None: # noqa: C901
from json import dumps, loads # noqa: PLC0415
Base = automap_base() # noqa: N806
Base.prepare(autoload_with=op.get_bind())
OldHistoricalData = Base.classes.nonebot_plugin_tetris_stats_historicaldata # noqa: N806
@@ -46,6 +46,9 @@ def migrate_old_data() -> None:
TimeRemainingColumn(),
) as progress,
):
if session.query(OldHistoricalData).count() == 0:
logger.info('空表, 跳过')
return
task_id = progress.add_task('[cyan]Migrating:', total=session.query(OldHistoricalData).count())
pointer = 0
while pointer < session.query(OldHistoricalData).order_by(desc(OldHistoricalData.id)).limit(1).one().id:
@@ -105,6 +108,8 @@ def migrate_old_data() -> None:
def upgrade(name: str = '') -> None:
if name:
return
if op.get_bind().dialect.name == 'postgresql':
return
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
'nonebot_plugin_tetris_stats_tetriohistoricaldata',
@@ -215,23 +220,25 @@ def upgrade(name: str = '') -> None:
def downgrade(name: str = '') -> None:
if name:
return
if op.get_bind().dialect.name == 'postgresql':
return
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
'nonebot_plugin_tetris_stats_historicaldata',
sa.Column('id', sa.INTEGER(), nullable=False),
sa.Column('trigger_time', sa.DATETIME(), nullable=False),
sa.Column('bot_platform', sa.VARCHAR(length=32), nullable=True),
sa.Column('bot_account', sa.VARCHAR(), nullable=True),
sa.Column('source_type', sa.VARCHAR(length=32), nullable=True),
sa.Column('source_account', sa.VARCHAR(), nullable=True),
sa.Column('message', sa.BLOB(), nullable=True),
sa.Column('game_platform', sa.VARCHAR(length=32), nullable=False),
sa.Column('command_type', sa.VARCHAR(length=16), nullable=False),
sa.Column('command_args', sqlite.JSON(), nullable=False),
sa.Column('game_user', sqlite.JSON(), nullable=False),
sa.Column('processed_data', sqlite.JSON(), nullable=False),
sa.Column('finish_time', sa.DATETIME(), nullable=False),
sa.Column('user_unique_identifier', sa.VARCHAR(length=32), nullable=False),
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('trigger_time', sa.DateTime(), nullable=False),
sa.Column('bot_platform', sa.String(length=32), nullable=True),
sa.Column('bot_account', sa.String(), nullable=True),
sa.Column('source_type', sa.String(length=32), nullable=True),
sa.Column('source_account', sa.String(), nullable=True),
sa.Column('message', sa.PickleType(), nullable=True),
sa.Column('game_platform', sa.String(length=32), nullable=False),
sa.Column('command_type', sa.String(length=16), nullable=False),
sa.Column('command_args', sa.JSON(), nullable=False),
sa.Column('game_user', sa.JSON(), nullable=False),
sa.Column('processed_data', sa.JSON(), nullable=False),
sa.Column('finish_time', sa.DateTime(), nullable=False),
sa.Column('user_unique_identifier', sa.String(length=32), nullable=False),
sa.PrimaryKeyConstraint('id', name='pk_nonebot_plugin_tetris_stats_historicaldata'),
)
with op.batch_alter_table('nonebot_plugin_tetris_stats_historicaldata', schema=None) as batch_op:

View File

@@ -0,0 +1,82 @@
"""migrate nonebot_plugin_tetris_stats_tetrioleaguestats
迁移 ID: 3d900bb0e8d4
父迁移: 405c6936a164
创建时间: 2025-07-18 02:22:03.771903
"""
from __future__ import annotations
from typing import TYPE_CHECKING
from alembic import op
from nonebot.log import logger
from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn, TextColumn
from sqlalchemy import inspect
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session
if TYPE_CHECKING:
from collections.abc import Sequence
revision: str = '3d900bb0e8d4'
down_revision: str | Sequence[str] | None = '405c6936a164'
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def data_migrate() -> None:
conn = op.get_bind()
insp = inspect(conn)
table_names = insp.get_table_names()
if 'nonebot_plugin_tetris_stats_tetrioleaguestats' not in table_names:
return
Base = automap_base() # noqa: N806
Base.prepare(autoload_with=conn)
Old = Base.classes.nonebot_plugin_tetris_stats_tetrioleaguestats # noqa: N806
New = Base.classes.nb_t_io_tl_stats # noqa: N806
with Session(conn) as db_session:
count = db_session.query(Old).count()
if count == 0:
return
logger.warning('tetris_stats: 正在迁移数据, 请不要关闭程序...')
with Progress(
SpinnerColumn(),
TextColumn('[progress.description]{task.description}'),
BarColumn(),
TaskProgressColumn(),
) as progress:
task = progress.add_task('迁移数据...', total=count)
for i in db_session.query(Old).yield_per(1):
db_session.add(
New(
id=i.id,
update_time=i.update_time,
)
)
progress.update(task, advance=1)
if progress.tasks[task].completed % 100 == 0:
db_session.commit()
db_session.commit()
logger.success('tetris_stats: 数据迁移完成!')
def upgrade(name: str = '') -> None:
if name:
return
data_migrate()
def downgrade(name: str = '') -> None:
if name:
return

View File

@@ -0,0 +1,85 @@
"""migrate nonebot_plugin_tetris_stats_tetrioleaguehistorical
迁移 ID: 405c6936a164
父迁移: bbbdfd94e6fa
创建时间: 2025-07-18 01:55:27.406032
"""
from __future__ import annotations
from typing import TYPE_CHECKING
from alembic import op
from nonebot.log import logger
from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn, TextColumn
from sqlalchemy import inspect
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session
if TYPE_CHECKING:
from collections.abc import Sequence
revision: str = '405c6936a164'
down_revision: str | Sequence[str] | None = 'bbbdfd94e6fa'
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def data_migrate() -> None:
conn = op.get_bind()
insp = inspect(conn)
table_names = insp.get_table_names()
if 'nonebot_plugin_tetris_stats_tetrioleaguehistorical' not in table_names:
return
Base = automap_base() # noqa: N806
Base.prepare(autoload_with=conn)
Old = Base.classes.nonebot_plugin_tetris_stats_tetrioleaguehistorical # noqa: N806
New = Base.classes.nb_t_io_tl_hist # noqa: N806
with Session(conn) as db_session:
count = db_session.query(Old).count()
if count == 0:
return
logger.warning('tetris_stats: 正在迁移数据, 请不要关闭程序...')
with Progress(
SpinnerColumn(),
TextColumn('[progress.description]{task.description}'),
BarColumn(),
TaskProgressColumn(),
) as progress:
task = progress.add_task('迁移数据...', total=count)
for i in db_session.query(Old).yield_per(1):
db_session.add(
New(
id=i.id,
request_id=i.request_id,
data=i.data,
update_time=i.update_time,
stats_id=i.stats_id,
)
)
progress.update(task, advance=1)
if progress.tasks[task].completed % 100 == 0:
db_session.commit()
db_session.commit()
logger.success('tetris_stats: 数据迁移完成!')
def upgrade(name: str = '') -> None:
if name:
return
data_migrate()
def downgrade(name: str = '') -> None:
if name:
return

View File

@@ -0,0 +1,123 @@
"""add TETRIOLeagueStats
迁移 ID: 5a1b93948494
父迁移: cfeab6961dce
创建时间: 2024-08-24 00:22:41.359500
"""
from __future__ import annotations
from typing import TYPE_CHECKING
import sqlalchemy as sa
from alembic import op
if TYPE_CHECKING:
from collections.abc import Sequence
revision: str = '5a1b93948494'
down_revision: str | Sequence[str] | None = 'cfeab6961dce'
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade(name: str = '') -> None:
if name:
return
if op.get_bind().dialect.name == 'postgresql':
return
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
'nonebot_plugin_tetris_stats_tetrioleaguestats',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('update_time', sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint('id', name=op.f('pk_nonebot_plugin_tetris_stats_tetrioleaguestats')),
info={'bind_key': 'nonebot_plugin_tetris_stats'},
)
with op.batch_alter_table('nonebot_plugin_tetris_stats_tetrioleaguestats', schema=None) as batch_op:
batch_op.create_index(
batch_op.f('ix_nonebot_plugin_tetris_stats_tetrioleaguestats_update_time'), ['update_time'], unique=False
)
op.create_table(
'nonebot_plugin_tetris_stats_tetrioleaguehistorical',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('request_id', sa.Uuid(), nullable=False),
sa.Column('data', sa.JSON(), nullable=False),
sa.Column('update_time', sa.DateTime(), nullable=False),
sa.Column('stats_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(
['stats_id'],
['nonebot_plugin_tetris_stats_tetrioleaguestats.id'],
name=op.f(
'fk_nonebot_plugin_tetris_stats_tetrioleaguehistorical_stats_id_nonebot_plugin_tetris_stats_tetrioleaguestats'
),
),
sa.PrimaryKeyConstraint('id', name=op.f('pk_nonebot_plugin_tetris_stats_tetrioleaguehistorical')),
info={'bind_key': 'nonebot_plugin_tetris_stats'},
)
with op.batch_alter_table('nonebot_plugin_tetris_stats_tetrioleaguehistorical', schema=None) as batch_op:
batch_op.create_index(
batch_op.f('ix_nonebot_plugin_tetris_stats_tetrioleaguehistorical_request_id'), ['request_id'], unique=False
)
batch_op.create_index(
batch_op.f('ix_nonebot_plugin_tetris_stats_tetrioleaguehistorical_update_time'),
['update_time'],
unique=False,
)
op.create_table(
'nonebot_plugin_tetris_stats_tetrioleaguestatsfield',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('rank', sa.String(length=2), nullable=False),
sa.Column('tr_line', sa.Float(), nullable=False),
sa.Column('player_count', sa.Integer(), nullable=False),
sa.Column('low_pps', sa.JSON(), nullable=False),
sa.Column('low_apm', sa.JSON(), nullable=False),
sa.Column('low_vs', sa.JSON(), nullable=False),
sa.Column('avg_pps', sa.Float(), nullable=False),
sa.Column('avg_apm', sa.Float(), nullable=False),
sa.Column('avg_vs', sa.Float(), nullable=False),
sa.Column('high_pps', sa.JSON(), nullable=False),
sa.Column('high_apm', sa.JSON(), nullable=False),
sa.Column('high_vs', sa.JSON(), nullable=False),
sa.Column('stats_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(
['stats_id'],
['nonebot_plugin_tetris_stats_tetrioleaguestats.id'],
name=op.f(
'fk_nonebot_plugin_tetris_stats_tetrioleaguestatsfield_stats_id_nonebot_plugin_tetris_stats_tetrioleaguestats'
),
),
sa.PrimaryKeyConstraint('id', name=op.f('pk_nonebot_plugin_tetris_stats_tetrioleaguestatsfield')),
info={'bind_key': 'nonebot_plugin_tetris_stats'},
)
with op.batch_alter_table('nonebot_plugin_tetris_stats_tetrioleaguestatsfield', schema=None) as batch_op:
batch_op.create_index(
batch_op.f('ix_nonebot_plugin_tetris_stats_tetrioleaguestatsfield_rank'), ['rank'], unique=False
)
# ### end Alembic commands ###
def downgrade(name: str = '') -> None:
if name:
return
if op.get_bind().dialect.name == 'postgresql':
return
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('nonebot_plugin_tetris_stats_tetrioleaguestatsfield', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_tetrioleaguestatsfield_rank'))
op.drop_table('nonebot_plugin_tetris_stats_tetrioleaguestatsfield')
with op.batch_alter_table('nonebot_plugin_tetris_stats_tetrioleaguehistorical', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_tetrioleaguehistorical_update_time'))
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_tetrioleaguehistorical_request_id'))
op.drop_table('nonebot_plugin_tetris_stats_tetrioleaguehistorical')
with op.batch_alter_table('nonebot_plugin_tetris_stats_tetrioleaguestats', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_tetrioleaguestats_update_time'))
op.drop_table('nonebot_plugin_tetris_stats_tetrioleaguestats')
# ### end Alembic commands ###

View File

@@ -0,0 +1,64 @@
"""Create a new table
迁移 ID: 612d8b00d9ac
父迁移: 5a1b93948494
创建时间: 2025-05-26 04:49:29.664480
"""
from __future__ import annotations
from typing import TYPE_CHECKING
import sqlalchemy as sa
from alembic import op
if TYPE_CHECKING:
from collections.abc import Sequence
revision: str = '612d8b00d9ac'
down_revision: str | Sequence[str] | None = '5a1b93948494'
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade(name: str = '') -> None:
if name:
return
if op.get_bind().dialect.name == 'postgresql':
return
op.create_table(
'nonebot_plugin_tetris_stats_triggerhistoricaldatav2',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('trigger_time', sa.DateTime(), nullable=False),
sa.Column('session_persist_id', sa.Integer(), nullable=False),
sa.Column('game_platform', sa.String(length=32), nullable=False),
sa.Column('command_type', sa.String(length=16), nullable=False),
sa.Column('command_args', sa.JSON(), nullable=False),
sa.Column('finish_time', sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint('id', name=op.f('pk_nonebot_plugin_tetris_stats_triggerhistoricaldatav2')),
info={'bind_key': 'nonebot_plugin_tetris_stats'},
)
with op.batch_alter_table('nonebot_plugin_tetris_stats_triggerhistoricaldatav2', schema=None) as batch_op:
batch_op.create_index(
batch_op.f('ix_nonebot_plugin_tetris_stats_triggerhistoricaldatav2_command_type'),
['command_type'],
unique=False,
)
batch_op.create_index(
batch_op.f('ix_nonebot_plugin_tetris_stats_triggerhistoricaldatav2_game_platform'),
['game_platform'],
unique=False,
)
def downgrade(name: str = '') -> None:
if name:
return
if op.get_bind().dialect.name == 'postgresql':
return
with op.batch_alter_table('nonebot_plugin_tetris_stats_triggerhistoricaldatav2', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_triggerhistoricaldatav2_game_platform'))
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_triggerhistoricaldatav2_command_type'))
op.drop_table('nonebot_plugin_tetris_stats_triggerhistoricaldatav2')

View File

@@ -13,7 +13,6 @@ from typing import TYPE_CHECKING
from alembic import op
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session
from ujson import dumps, loads
if TYPE_CHECKING:
from collections.abc import Sequence
@@ -27,6 +26,9 @@ depends_on: str | Sequence[str] | None = None
def upgrade(name: str = '') -> None:
if name:
return
if op.get_bind().dialect.name == 'postgresql':
return
from json import dumps, loads # noqa: PLC0415
Base = automap_base() # noqa: N806
connection = op.get_bind()
@@ -50,6 +52,9 @@ def upgrade(name: str = '') -> None:
def downgrade(name: str = '') -> None:
if name:
return
if op.get_bind().dialect.name == 'postgresql':
return
from json import dumps, loads # noqa: PLC0415
Base = automap_base() # noqa: N806
connection = op.get_bind()

View File

@@ -0,0 +1,120 @@
"""Migrate to uninfo
迁移 ID: 766cc7e75a62
父迁移: 612d8b00d9ac
创建时间: 2025-05-26 04:51:54.665200
"""
from __future__ import annotations
import math
from typing import TYPE_CHECKING
from alembic import op
from nonebot.log import logger
from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn, TextColumn
from sqlalchemy import inspect
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session
if TYPE_CHECKING:
from collections.abc import Sequence
revision: str = '766cc7e75a62'
down_revision: str | Sequence[str] | None = '612d8b00d9ac'
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def data_migrate() -> None:
conn = op.get_bind()
insp = inspect(conn)
table_names = insp.get_table_names()
if 'nonebot_plugin_tetris_stats_triggerhistoricaldata' not in table_names:
return
Base = automap_base() # noqa: N806
Base.prepare(autoload_with=conn)
TriggerHistoricalData = Base.classes.nonebot_plugin_tetris_stats_triggerhistoricaldata # noqa: N806
TriggerHistoricalDataV2 = Base.classes.nonebot_plugin_tetris_stats_triggerhistoricaldatav2 # noqa: N806
with Session(conn) as db_session:
count = db_session.query(TriggerHistoricalData).count()
if count == 0:
return
try:
from nonebot_session_to_uninfo import ( # type: ignore[import-untyped] # noqa: PLC0415
check_tables,
get_id_map,
)
except ImportError as err:
msg = '请安装 `nonebot-session-to-uninfo` 以迁移数据'
raise ValueError(msg) from err
check_tables()
migration_limit = 10000 # 每次迁移的数据量为 10000 条
last_id = -1
id_map: dict[int, int] = {}
logger.warning('tetris_stats: 正在迁移数据, 请不要关闭程序...')
with Progress(
SpinnerColumn(),
TextColumn('[progress.description]{task.description}'),
BarColumn(),
TaskProgressColumn(),
) as progress:
task = progress.add_task('迁移数据...', total=count)
for _ in range(math.ceil(count / migration_limit)):
records = (
db_session.query(TriggerHistoricalData)
.order_by(TriggerHistoricalData.id)
.where(TriggerHistoricalData.id > last_id)
.limit(migration_limit)
.all()
)
last_id = records[-1].id
session_ids = [
record.session_persist_id for record in records if record.session_persist_id not in id_map
]
if session_ids:
id_map.update(get_id_map(session_ids))
db_session.add_all(
TriggerHistoricalDataV2(
id=record.id,
session_persist_id=id_map[record.session_persist_id],
trigger_time=record.trigger_time,
game_platform=record.game_platform,
command_type=record.command_type,
command_args=record.command_args,
finish_time=record.finish_time,
)
for record in records
)
progress.update(task, advance=len(records))
db_session.commit()
logger.success('tetris_stats: 数据迁移完成!')
def upgrade(name: str = '') -> None:
if name:
return
if op.get_bind().dialect.name == 'postgresql':
return
data_migrate()
def downgrade(name: str = '') -> None:
if name:
return
if op.get_bind().dialect.name == 'postgresql':
return

View File

@@ -0,0 +1,94 @@
"""migrate nonebot_plugin_tetris_stats_tetrioleaguestatsfield
迁移 ID: 8459b2a4b7a3
父迁移: 3d900bb0e8d4
创建时间: 2025-07-18 02:24:59.560252
"""
from __future__ import annotations
from typing import TYPE_CHECKING
from alembic import op
from nonebot.log import logger
from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn, TextColumn
from sqlalchemy import inspect
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session
if TYPE_CHECKING:
from collections.abc import Sequence
revision: str = '8459b2a4b7a3'
down_revision: str | Sequence[str] | None = '3d900bb0e8d4'
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def data_migrate() -> None:
conn = op.get_bind()
insp = inspect(conn)
table_names = insp.get_table_names()
if 'nonebot_plugin_tetris_stats_tetrioleaguestatsfield' not in table_names:
return
Base = automap_base() # noqa: N806
Base.prepare(autoload_with=conn)
Old = Base.classes.nonebot_plugin_tetris_stats_tetrioleaguestatsfield # noqa: N806
New = Base.classes.nb_t_io_tl_stats_field # noqa: N806
with Session(conn) as db_session:
count = db_session.query(Old).count()
if count == 0:
return
logger.warning('tetris_stats: 正在迁移数据, 请不要关闭程序...')
with Progress(
SpinnerColumn(),
TextColumn('[progress.description]{task.description}'),
BarColumn(),
TaskProgressColumn(),
) as progress:
task = progress.add_task('迁移数据...', total=count)
for i in db_session.query(Old).yield_per(1):
db_session.add(
New(
id=i.id,
rank=i.rank,
tr_line=i.tr_line,
player_count=i.player_count,
low_pps=i.low_pps,
low_apm=i.low_apm,
low_vs=i.low_vs,
avg_pps=i.avg_pps,
avg_apm=i.avg_apm,
avg_vs=i.avg_vs,
high_pps=i.high_pps,
high_apm=i.high_apm,
high_vs=i.high_vs,
stats_id=i.stats_id,
)
)
progress.update(task, advance=1)
if progress.tasks[task].completed % 100 == 0:
db_session.commit()
db_session.commit()
logger.success('tetris_stats: 数据迁移完成!')
def upgrade(name: str = '') -> None:
if name:
return
data_migrate()
def downgrade(name: str = '') -> None:
if name:
return

View File

@@ -28,16 +28,12 @@ depends_on: str | Sequence[str] | None = None
def upgrade(name: str = '') -> None: # noqa: C901
if name:
return
from nonebot_plugin_tetris_stats.version import __version__
if op.get_bind().dialect.name == 'postgresql':
return
if __version__ != '1.0.3':
msg = '本迁移需要1.0.3版本, 请先锁定版本至1.0.3版本再执行本迁移'
logger.critical(msg)
raise RuntimeError(msg)
from nonebot.compat import PYDANTIC_V2, type_validate_json
from pydantic import BaseModel, ValidationError
from rich.progress import (
from nonebot.compat import PYDANTIC_V2, type_validate_json # noqa: PLC0415
from pydantic import BaseModel, ValidationError # noqa: PLC0415
from rich.progress import ( # noqa: PLC0415
BarColumn,
MofNCompleteColumn,
Progress,
@@ -46,10 +42,6 @@ def upgrade(name: str = '') -> None: # noqa: C901
TimeRemainingColumn,
)
from nonebot_plugin_tetris_stats.game_data_processor.schemas import ( # type: ignore[import-untyped]
BaseProcessedData,
)
Base = automap_base() # noqa: N806
Base.prepare(autoload_with=op.get_bind())
HistoricalData = Base.classes.nonebot_plugin_tetris_stats_historicaldata # noqa: N806
@@ -62,18 +54,33 @@ def upgrade(name: str = '') -> None: # noqa: C901
def model_to_json(value: BaseModel) -> str:
return value.json(by_alias=True)
models = BaseProcessedData.__subclasses__()
def json_to_model(value: str) -> BaseModel:
for i in models:
try:
return type_validate_json(i, value)
except ValidationError: # noqa: PERF203
...
raise ValueError
with Session(op.get_bind()) as session:
count = session.query(HistoricalData).count()
if count == 0:
logger.info('空表, 跳过')
return
from nonebot_plugin_tetris_stats.version import __version__ # noqa: PLC0415
if __version__ != '1.0.3':
msg = '本迁移需要1.0.3版本, 请先锁定版本至1.0.3版本再执行本迁移'
logger.critical(msg)
raise RuntimeError(msg)
from nonebot_plugin_tetris_stats.game_data_processor.schemas import ( # type: ignore[import-untyped] # noqa: PLC0415
BaseProcessedData,
)
models = BaseProcessedData.__subclasses__()
def json_to_model(value: str) -> BaseModel:
for i in models:
try:
return type_validate_json(i, value)
except ValidationError: # noqa: PERF203
...
raise ValueError
with Progress(
TextColumn('[progress.description]{task.description}'),
BarColumn(),
@@ -96,3 +103,5 @@ def upgrade(name: str = '') -> None: # noqa: C901
def downgrade(name: str = '') -> None:
if name:
return
if op.get_bind().dialect.name == 'postgresql':
return

View File

@@ -25,6 +25,8 @@ depends_on: str | Sequence[str] | None = None
def upgrade(name: str = '') -> None:
if name:
return
if op.get_bind().dialect.name == 'postgresql':
return
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
'nonebot_plugin_tetris_stats_bind',
@@ -122,6 +124,8 @@ def upgrade(name: str = '') -> None:
def downgrade(name: str = '') -> None:
if name:
return
if op.get_bind().dialect.name == 'postgresql':
return
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('nonebot_plugin_tetris_stats_iorank', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_iorank_rank'))

View File

@@ -63,6 +63,8 @@ def migrate_old_data(connection: Connection) -> None:
def upgrade(name: str = '') -> None:
if name:
return
if op.get_bind().dialect.name == 'postgresql':
return
try:
db_path = Path(config.db_url)
except AttributeError:
@@ -91,3 +93,5 @@ def upgrade(name: str = '') -> None:
def downgrade(name: str = '') -> None:
if name:
return
if op.get_bind().dialect.name == 'postgresql':
return

View File

@@ -12,7 +12,6 @@ from typing import TYPE_CHECKING
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import sqlite
if TYPE_CHECKING:
from collections.abc import Sequence
@@ -26,6 +25,8 @@ depends_on: str | Sequence[str] | None = None
def upgrade(name: str = '') -> None:
if name:
return
if op.get_bind().dialect.name == 'postgresql':
return
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('nonebot_plugin_tetris_stats_historicaldata', schema=None) as batch_op:
batch_op.drop_index('ix_nonebot_plugin_tetris_stats_historicaldata_command_type')
@@ -71,6 +72,8 @@ def upgrade(name: str = '') -> None:
def downgrade(name: str = '') -> None:
if name:
return
if op.get_bind().dialect.name == 'postgresql':
return
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('nonebot_plugin_tetris_stats_historicaldata', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_historicaldata_source_type'))
@@ -82,19 +85,19 @@ def downgrade(name: str = '') -> None:
op.create_table(
'nonebot_plugin_tetris_stats_historicaldata',
sa.Column('id', sa.INTEGER(), nullable=False),
sa.Column('trigger_time', sa.DATETIME(), nullable=False),
sa.Column('bot_platform', sa.VARCHAR(length=32), nullable=True),
sa.Column('bot_account', sa.VARCHAR(), nullable=True),
sa.Column('source_type', sa.VARCHAR(length=32), nullable=True),
sa.Column('source_account', sa.VARCHAR(), nullable=True),
sa.Column('message', sa.BLOB(), nullable=True),
sa.Column('game_platform', sa.VARCHAR(length=32), nullable=False),
sa.Column('command_type', sa.VARCHAR(length=16), nullable=False),
sa.Column('command_args', sqlite.JSON(), nullable=False),
sa.Column('game_user', sa.BLOB(), nullable=False),
sa.Column('processed_data', sa.BLOB(), nullable=False),
sa.Column('finish_time', sa.DATETIME(), nullable=False),
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('trigger_time', sa.DateTime(), nullable=False),
sa.Column('bot_platform', sa.String(length=32), nullable=True),
sa.Column('bot_account', sa.String(), nullable=True),
sa.Column('source_type', sa.String(length=32), nullable=True),
sa.Column('source_account', sa.String(), nullable=True),
sa.Column('message', sa.PickleType(), nullable=True),
sa.Column('game_platform', sa.String(length=32), nullable=False),
sa.Column('command_type', sa.String(length=16), nullable=False),
sa.Column('command_args', sa.JSON(), nullable=False),
sa.Column('game_user', sa.PickleType(), nullable=False),
sa.Column('processed_data', sa.PickleType(), nullable=False),
sa.Column('finish_time', sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint('id', name='pk_nonebot_plugin_tetris_stats_historicaldata'),
)
with op.batch_alter_table('nonebot_plugin_tetris_stats_historicaldata', schema=None) as batch_op:

View File

@@ -25,6 +25,8 @@ depends_on: str | Sequence[str] | None = None
def upgrade(name: str = '') -> None:
if name:
return
if op.get_bind().dialect.name == 'postgresql':
return
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
'nonebot_plugin_tetris_stats_tetriouserconfig',
@@ -39,6 +41,8 @@ def upgrade(name: str = '') -> None:
def downgrade(name: str = '') -> None:
if name:
return
if op.get_bind().dialect.name == 'postgresql':
return
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('nonebot_plugin_tetris_stats_tetriouserconfig')
# ### end Alembic commands ###

View File

@@ -25,6 +25,8 @@ depends_on: str | Sequence[str] | None = None
def upgrade(name: str = '') -> None:
if name:
return
if op.get_bind().dialect.name == 'postgresql':
return
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('nonebot_plugin_tetris_stats_bind', schema=None) as batch_op:
batch_op.drop_index('ix_nonebot_plugin_tetris_stats_bind_chat_account')
@@ -49,6 +51,8 @@ def upgrade(name: str = '') -> None:
def downgrade(name: str = '') -> None:
if name:
return
if op.get_bind().dialect.name == 'postgresql':
return
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('nonebot_plugin_tetris_stats_bind', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_bind_user_id'))
@@ -57,11 +61,11 @@ def downgrade(name: str = '') -> None:
op.create_table(
'nonebot_plugin_tetris_stats_bind',
sa.Column('id', sa.INTEGER(), nullable=False),
sa.Column('chat_platform', sa.VARCHAR(length=32), nullable=False),
sa.Column('chat_account', sa.VARCHAR(), nullable=False),
sa.Column('game_platform', sa.VARCHAR(length=32), nullable=False),
sa.Column('game_account', sa.VARCHAR(), nullable=False),
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('chat_platform', sa.String(length=32), nullable=False),
sa.Column('chat_account', sa.String(), nullable=False),
sa.Column('game_platform', sa.String(length=32), nullable=False),
sa.Column('game_account', sa.String(), nullable=False),
sa.PrimaryKeyConstraint('id', name='pk_nonebot_plugin_tetris_stats_bind'),
)
with op.batch_alter_table('nonebot_plugin_tetris_stats_bind', schema=None) as batch_op:

View File

@@ -0,0 +1,215 @@
"""create new tables
迁移 ID: b2075a5ce371
父迁移: 766cc7e75a62
创建时间: 2025-07-17 22:57:32.245327
"""
from __future__ import annotations
from typing import TYPE_CHECKING
import sqlalchemy as sa
from alembic import op
if TYPE_CHECKING:
from collections.abc import Sequence
revision: str = 'b2075a5ce371'
down_revision: str | Sequence[str] | None = '766cc7e75a62'
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade(name: str = '') -> None:
if name:
return
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
'nb_t_bind',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('game_platform', sa.String(length=32), nullable=False),
sa.Column('game_account', sa.String(), nullable=False),
sa.PrimaryKeyConstraint('id', name=op.f('pk_nb_t_bind')),
info={'bind_key': 'nonebot_plugin_tetris_stats'},
)
with op.batch_alter_table('nb_t_bind', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_nb_t_bind_user_id'), ['user_id'], unique=False)
op.create_table(
'nb_t_io_hist_data',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_unique_identifier', sa.String(length=24), nullable=False),
sa.Column('api_type', sa.String(length=32), nullable=False),
sa.Column('data', sa.JSON(), nullable=False),
sa.Column('update_time', sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint('id', name=op.f('pk_nb_t_io_hist_data')),
info={'bind_key': 'nonebot_plugin_tetris_stats'},
)
with op.batch_alter_table('nb_t_io_hist_data', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_nb_t_io_hist_data_api_type'), ['api_type'], unique=False)
batch_op.create_index(batch_op.f('ix_nb_t_io_hist_data_update_time'), ['update_time'], unique=False)
batch_op.create_index(
batch_op.f('ix_nb_t_io_hist_data_user_unique_identifier'), ['user_unique_identifier'], unique=False
)
op.create_table(
'nb_t_io_tl_stats',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('update_time', sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint('id', name=op.f('pk_nb_t_io_tl_stats')),
info={'bind_key': 'nonebot_plugin_tetris_stats'},
)
with op.batch_alter_table('nb_t_io_tl_stats', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_nb_t_io_tl_stats_update_time'), ['update_time'], unique=False)
op.create_table(
'nb_t_io_u_cfg',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('query_template', sa.String(length=2), nullable=False),
sa.PrimaryKeyConstraint('id', name=op.f('pk_nb_t_io_u_cfg')),
info={'bind_key': 'nonebot_plugin_tetris_stats'},
)
op.create_table(
'nb_t_top_hist_data',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_unique_identifier', sa.String(length=24), nullable=False),
sa.Column('api_type', sa.String(length=16), nullable=False),
sa.Column('data', sa.JSON(), nullable=False),
sa.Column('update_time', sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint('id', name=op.f('pk_nb_t_top_hist_data')),
info={'bind_key': 'nonebot_plugin_tetris_stats'},
)
with op.batch_alter_table('nb_t_top_hist_data', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_nb_t_top_hist_data_api_type'), ['api_type'], unique=False)
batch_op.create_index(batch_op.f('ix_nb_t_top_hist_data_update_time'), ['update_time'], unique=False)
batch_op.create_index(
batch_op.f('ix_nb_t_top_hist_data_user_unique_identifier'), ['user_unique_identifier'], unique=False
)
op.create_table(
'nb_t_tos_hist_data',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_unique_identifier', sa.String(length=24), nullable=False),
sa.Column('api_type', sa.String(length=16), nullable=False),
sa.Column('data', sa.JSON(), nullable=False),
sa.Column('update_time', sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint('id', name=op.f('pk_nb_t_tos_hist_data')),
info={'bind_key': 'nonebot_plugin_tetris_stats'},
)
with op.batch_alter_table('nb_t_tos_hist_data', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_nb_t_tos_hist_data_api_type'), ['api_type'], unique=False)
batch_op.create_index(batch_op.f('ix_nb_t_tos_hist_data_update_time'), ['update_time'], unique=False)
batch_op.create_index(
batch_op.f('ix_nb_t_tos_hist_data_user_unique_identifier'), ['user_unique_identifier'], unique=False
)
op.create_table(
'nb_t_trigger_hist_v2',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('trigger_time', sa.DateTime(), nullable=False),
sa.Column('session_persist_id', sa.Integer(), nullable=False),
sa.Column('game_platform', sa.String(length=32), nullable=False),
sa.Column('command_type', sa.String(length=16), nullable=False),
sa.Column('command_args', sa.JSON(), nullable=False),
sa.Column('finish_time', sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint('id', name=op.f('pk_nb_t_trigger_hist_v2')),
info={'bind_key': 'nonebot_plugin_tetris_stats'},
)
with op.batch_alter_table('nb_t_trigger_hist_v2', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_nb_t_trigger_hist_v2_command_type'), ['command_type'], unique=False)
batch_op.create_index(batch_op.f('ix_nb_t_trigger_hist_v2_game_platform'), ['game_platform'], unique=False)
op.create_table(
'nb_t_io_tl_hist',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('request_id', sa.Uuid(), nullable=False),
sa.Column('data', sa.JSON(), nullable=False),
sa.Column('update_time', sa.DateTime(), nullable=False),
sa.Column('stats_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(
['stats_id'], ['nb_t_io_tl_stats.id'], name=op.f('fk_nb_t_io_tl_hist_stats_id_nb_t_io_tl_stats')
),
sa.PrimaryKeyConstraint('id', name=op.f('pk_nb_t_io_tl_hist')),
info={'bind_key': 'nonebot_plugin_tetris_stats'},
)
with op.batch_alter_table('nb_t_io_tl_hist', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_nb_t_io_tl_hist_request_id'), ['request_id'], unique=False)
batch_op.create_index(batch_op.f('ix_nb_t_io_tl_hist_update_time'), ['update_time'], unique=False)
op.create_table(
'nb_t_io_tl_stats_field',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('rank', sa.String(length=2), nullable=False),
sa.Column('tr_line', sa.Float(), nullable=False),
sa.Column('player_count', sa.Integer(), nullable=False),
sa.Column('low_pps', sa.JSON(), nullable=False),
sa.Column('low_apm', sa.JSON(), nullable=False),
sa.Column('low_vs', sa.JSON(), nullable=False),
sa.Column('avg_pps', sa.Float(), nullable=False),
sa.Column('avg_apm', sa.Float(), nullable=False),
sa.Column('avg_vs', sa.Float(), nullable=False),
sa.Column('high_pps', sa.JSON(), nullable=False),
sa.Column('high_apm', sa.JSON(), nullable=False),
sa.Column('high_vs', sa.JSON(), nullable=False),
sa.Column('stats_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(
['stats_id'], ['nb_t_io_tl_stats.id'], name=op.f('fk_nb_t_io_tl_stats_field_stats_id_nb_t_io_tl_stats')
),
sa.PrimaryKeyConstraint('id', name=op.f('pk_nb_t_io_tl_stats_field')),
info={'bind_key': 'nonebot_plugin_tetris_stats'},
)
with op.batch_alter_table('nb_t_io_tl_stats_field', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_nb_t_io_tl_stats_field_rank'), ['rank'], unique=False)
# ### end Alembic commands ###
def downgrade(name: str = '') -> None:
if name:
return
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('nb_t_io_tl_stats_field', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_nb_t_io_tl_stats_field_rank'))
op.drop_table('nb_t_io_tl_stats_field')
with op.batch_alter_table('nb_t_io_tl_hist', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_nb_t_io_tl_hist_update_time'))
batch_op.drop_index(batch_op.f('ix_nb_t_io_tl_hist_request_id'))
op.drop_table('nb_t_io_tl_hist')
with op.batch_alter_table('nb_t_trigger_hist_v2', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_nb_t_trigger_hist_v2_game_platform'))
batch_op.drop_index(batch_op.f('ix_nb_t_trigger_hist_v2_command_type'))
op.drop_table('nb_t_trigger_hist_v2')
with op.batch_alter_table('nb_t_tos_hist_data', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_nb_t_tos_hist_data_user_unique_identifier'))
batch_op.drop_index(batch_op.f('ix_nb_t_tos_hist_data_update_time'))
batch_op.drop_index(batch_op.f('ix_nb_t_tos_hist_data_api_type'))
op.drop_table('nb_t_tos_hist_data')
with op.batch_alter_table('nb_t_top_hist_data', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_nb_t_top_hist_data_user_unique_identifier'))
batch_op.drop_index(batch_op.f('ix_nb_t_top_hist_data_update_time'))
batch_op.drop_index(batch_op.f('ix_nb_t_top_hist_data_api_type'))
op.drop_table('nb_t_top_hist_data')
op.drop_table('nb_t_io_u_cfg')
with op.batch_alter_table('nb_t_io_tl_stats', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_nb_t_io_tl_stats_update_time'))
op.drop_table('nb_t_io_tl_stats')
with op.batch_alter_table('nb_t_io_hist_data', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_nb_t_io_hist_data_user_unique_identifier'))
batch_op.drop_index(batch_op.f('ix_nb_t_io_hist_data_update_time'))
batch_op.drop_index(batch_op.f('ix_nb_t_io_hist_data_api_type'))
op.drop_table('nb_t_io_hist_data')
with op.batch_alter_table('nb_t_bind', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_nb_t_bind_user_id'))
op.drop_table('nb_t_bind')
# ### end Alembic commands ###

View File

@@ -23,18 +23,15 @@ branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade(name: str = '') -> None:
def upgrade(name: str = '') -> None: # noqa: C901
if name:
return
from nonebot_plugin_tetris_stats.version import __version__
if op.get_bind().dialect.name == 'postgresql':
return
if __version__ != '1.0.4':
msg = '本迁移需要1.0.4版本, 请先锁定版本至1.0.4版本再执行本迁移'
logger.critical(msg)
raise RuntimeError(msg)
from nonebot.compat import type_validate_json
from pydantic import ValidationError
from rich.progress import (
from nonebot.compat import type_validate_json # noqa: PLC0415
from pydantic import ValidationError # noqa: PLC0415
from rich.progress import ( # noqa: PLC0415
BarColumn,
MofNCompleteColumn,
Progress,
@@ -42,11 +39,9 @@ def upgrade(name: str = '') -> None:
TextColumn,
TimeRemainingColumn,
)
from sqlalchemy import select
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session
from nonebot_plugin_tetris_stats.game_data_processor.schemas import BaseUser # type: ignore[import-untyped]
from sqlalchemy import select # noqa: PLC0415
from sqlalchemy.ext.automap import automap_base # noqa: PLC0415
from sqlalchemy.orm import Session # noqa: PLC0415
with op.batch_alter_table('nonebot_plugin_tetris_stats_historicaldata', schema=None) as batch_op:
batch_op.add_column(sa.Column('user_unique_identifier', sa.String(length=32), nullable=True))
@@ -60,37 +55,50 @@ def upgrade(name: str = '') -> None:
Base.prepare(autoload_with=connection)
HistoricalData = Base.classes.nonebot_plugin_tetris_stats_historicaldata # noqa: N806
models: list[type[BaseUser]] = BaseUser.__subclasses__()
def json_to_model(value: str) -> BaseUser:
for i in models:
try:
return type_validate_json(i, value)
except ValidationError: # noqa: PERF203
...
raise ValueError
with Session(op.get_bind()) as session:
count = session.query(HistoricalData).count()
with Progress(
TextColumn('[progress.description]{task.description}'),
BarColumn(),
MofNCompleteColumn(),
TaskProgressColumn(),
TimeRemainingColumn(),
) as progress:
task_id = progress.add_task('[cyan]Updateing:', total=count)
for i in range(0, count, 100):
for j in session.scalars(
select(HistoricalData).where(HistoricalData.id > i).order_by(HistoricalData.id).limit(100)
):
model = json_to_model(j.game_user)
if count == 0:
logger.info('空表, 跳过')
else:
from nonebot_plugin_tetris_stats.version import __version__ # noqa: PLC0415
if __version__ != '1.0.4':
msg = '本迁移需要1.0.4版本, 请先锁定版本至1.0.4版本再执行本迁移'
logger.critical(msg)
raise RuntimeError(msg)
from nonebot_plugin_tetris_stats.game_data_processor.schemas import ( # type: ignore[import-untyped] # noqa: PLC0415
BaseUser,
)
models: list[type[BaseUser]] = BaseUser.__subclasses__()
def json_to_model(value: str) -> BaseUser:
for i in models:
try:
j.user_unique_identifier = model.unique_identifier
except ValueError:
session.delete(j)
progress.update(task_id, advance=1)
session.commit()
return type_validate_json(i, value)
except ValidationError: # noqa: PERF203
...
raise ValueError
with Progress(
TextColumn('[progress.description]{task.description}'),
BarColumn(),
MofNCompleteColumn(),
TaskProgressColumn(),
TimeRemainingColumn(),
) as progress:
task_id = progress.add_task('[cyan]Updateing:', total=count)
for i in range(0, count, 100):
for j in session.scalars(
select(HistoricalData).where(HistoricalData.id > i).order_by(HistoricalData.id).limit(100)
):
model = json_to_model(j.game_user)
try:
j.user_unique_identifier = model.unique_identifier
except ValueError:
session.delete(j)
progress.update(task_id, advance=1)
session.commit()
with op.batch_alter_table('nonebot_plugin_tetris_stats_historicaldata', schema=None) as batch_op:
batch_op.alter_column('user_unique_identifier', existing_type=sa.VARCHAR(length=32), nullable=False)
logger.success('database upgrade success')
@@ -99,6 +107,8 @@ def upgrade(name: str = '') -> None:
def downgrade(name: str = '') -> None:
if name:
return
if op.get_bind().dialect.name == 'postgresql':
return
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('nonebot_plugin_tetris_stats_historicaldata', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_historicaldata_user_unique_identifier'))

View File

@@ -0,0 +1,82 @@
"""migrate nonebot_plugin_tetris_stats_tetriouserconfig
迁移 ID: b96c8c18b79a
父迁移: 8459b2a4b7a3
创建时间: 2025-07-18 04:25:44.190319
"""
from __future__ import annotations
from typing import TYPE_CHECKING
from alembic import op
from nonebot.log import logger
from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn, TextColumn
from sqlalchemy import inspect
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session
if TYPE_CHECKING:
from collections.abc import Sequence
revision: str = 'b96c8c18b79a'
down_revision: str | Sequence[str] | None = '8459b2a4b7a3'
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def data_migrate() -> None:
conn = op.get_bind()
insp = inspect(conn)
table_names = insp.get_table_names()
if 'nonebot_plugin_tetris_stats_tetriouserconfig' not in table_names:
return
Base = automap_base() # noqa: N806
Base.prepare(autoload_with=conn)
Old = Base.classes.nonebot_plugin_tetris_stats_tetriouserconfig # noqa: N806
New = Base.classes.nb_t_io_u_cfg # noqa: N806
with Session(conn) as db_session:
count = db_session.query(Old).count()
if count == 0:
return
logger.warning('tetris_stats: 正在迁移数据, 请不要关闭程序...')
with Progress(
SpinnerColumn(),
TextColumn('[progress.description]{task.description}'),
BarColumn(),
TaskProgressColumn(),
) as progress:
task = progress.add_task('迁移数据...', total=count)
for i in db_session.query(Old).yield_per(1):
db_session.add(
New(
id=i.id,
query_template=i.query_template,
)
)
progress.update(task, advance=1)
if progress.tasks[task].completed % 100 == 0:
db_session.commit()
db_session.commit()
logger.success('tetris_stats: 数据迁移完成!')
def upgrade(name: str = '') -> None:
if name:
return
data_migrate()
def downgrade(name: str = '') -> None:
if name:
return

View File

@@ -26,6 +26,8 @@ depends_on: str | Sequence[str] | None = None
def upgrade(name: str = '') -> None:
if name:
return
if op.get_bind().dialect.name == 'postgresql':
return
Base = automap_base() # noqa: N806
connection = op.get_bind()
@@ -40,3 +42,5 @@ def upgrade(name: str = '') -> None:
def downgrade(name: str = '') -> None:
if name:
return
if op.get_bind().dialect.name == 'postgresql':
return

View File

@@ -0,0 +1,85 @@
"""migrate nonebot_plugin_tetris_stats_tetriohistoricaldata
迁移 ID: bbbdfd94e6fa
父迁移: d61e6ae36586
创建时间: 2025-07-18 00:42:33.730885
"""
from __future__ import annotations
from typing import TYPE_CHECKING
from alembic import op
from nonebot.log import logger
from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn, TextColumn
from sqlalchemy import inspect
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session
if TYPE_CHECKING:
from collections.abc import Sequence
revision: str = 'bbbdfd94e6fa'
down_revision: str | Sequence[str] | None = 'd61e6ae36586'
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def data_migrate() -> None:
conn = op.get_bind()
insp = inspect(conn)
table_names = insp.get_table_names()
if 'nonebot_plugin_tetris_stats_tetriohistoricaldata' not in table_names:
return
Base = automap_base() # noqa: N806
Base.prepare(autoload_with=conn)
Old = Base.classes.nonebot_plugin_tetris_stats_tetriohistoricaldata # noqa: N806
New = Base.classes.nb_t_io_hist_data # noqa: N806
with Session(conn) as db_session:
count = db_session.query(Old).count()
if count == 0:
return
logger.warning('tetris_stats: 正在迁移数据, 请不要关闭程序...')
with Progress(
SpinnerColumn(),
TextColumn('[progress.description]{task.description}'),
BarColumn(),
TaskProgressColumn(),
) as progress:
task = progress.add_task('迁移数据...', total=count)
for i in db_session.query(Old).yield_per(1):
db_session.add(
New(
id=i.id,
user_unique_identifier=i.user_unique_identifier,
api_type=i.api_type,
data=i.data,
update_time=i.update_time,
)
)
progress.update(task, advance=1)
if progress.tasks[task].completed % 100 == 0:
db_session.commit()
db_session.commit()
logger.success('tetris_stats: 数据迁移完成!')
def upgrade(name: str = '') -> None:
if name:
return
data_migrate()
def downgrade(name: str = '') -> None:
if name:
return

View File

@@ -0,0 +1,87 @@
"""migrate nonebot_plugin_tetris_stats_triggerhistoricaldatav2
迁移 ID: bc6abd57928f
父迁移: ee76ae37d70a
创建时间: 2025-07-18 04:33:04.222045
"""
from __future__ import annotations
from typing import TYPE_CHECKING
from alembic import op
from nonebot.log import logger
from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn, TextColumn
from sqlalchemy import inspect
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session
if TYPE_CHECKING:
from collections.abc import Sequence
revision: str = 'bc6abd57928f'
down_revision: str | Sequence[str] | None = 'ee76ae37d70a'
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def data_migrate() -> None:
conn = op.get_bind()
insp = inspect(conn)
table_names = insp.get_table_names()
if 'nonebot_plugin_tetris_stats_triggerhistoricaldatav2' not in table_names:
return
Base = automap_base() # noqa: N806
Base.prepare(autoload_with=conn)
Old = Base.classes.nonebot_plugin_tetris_stats_triggerhistoricaldatav2 # noqa: N806
New = Base.classes.nb_t_trigger_hist_v2 # noqa: N806
with Session(conn) as db_session:
count = db_session.query(Old).count()
if count == 0:
return
logger.warning('tetris_stats: 正在迁移数据, 请不要关闭程序...')
with Progress(
SpinnerColumn(),
TextColumn('[progress.description]{task.description}'),
BarColumn(),
TaskProgressColumn(),
) as progress:
task = progress.add_task('迁移数据...', total=count)
for i in db_session.query(Old).yield_per(1):
db_session.add(
New(
id=i.id,
trigger_time=i.trigger_time,
session_persist_id=i.session_persist_id,
game_platform=i.game_platform,
command_type=i.command_type,
command_args=i.command_args,
finish_time=i.finish_time,
)
)
progress.update(task, advance=1)
if progress.tasks[task].completed % 100 == 0:
db_session.commit()
db_session.commit()
logger.success('tetris_stats: 数据迁移完成!')
def upgrade(name: str = '') -> None:
if name:
return
data_migrate()
def downgrade(name: str = '') -> None:
if name:
return

View File

@@ -0,0 +1,85 @@
"""migrate nonebot_plugin_tetris_stats_tophistoricaldata
迁移 ID: ce073d279d19
父迁移: b96c8c18b79a
创建时间: 2025-07-18 04:28:13.820635
"""
from __future__ import annotations
from typing import TYPE_CHECKING
from alembic import op
from nonebot.log import logger
from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn, TextColumn
from sqlalchemy import inspect
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session
if TYPE_CHECKING:
from collections.abc import Sequence
revision: str = 'ce073d279d19'
down_revision: str | Sequence[str] | None = 'b96c8c18b79a'
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def data_migrate() -> None:
conn = op.get_bind()
insp = inspect(conn)
table_names = insp.get_table_names()
if 'nonebot_plugin_tetris_stats_tophistoricaldata' not in table_names:
return
Base = automap_base() # noqa: N806
Base.prepare(autoload_with=conn)
Old = Base.classes.nonebot_plugin_tetris_stats_tophistoricaldata # noqa: N806
New = Base.classes.nb_t_top_hist_data # noqa: N806
with Session(conn) as db_session:
count = db_session.query(Old).count()
if count == 0:
return
logger.warning('tetris_stats: 正在迁移数据, 请不要关闭程序...')
with Progress(
SpinnerColumn(),
TextColumn('[progress.description]{task.description}'),
BarColumn(),
TaskProgressColumn(),
) as progress:
task = progress.add_task('迁移数据...', total=count)
for i in db_session.query(Old).yield_per(1):
db_session.add(
New(
id=i.id,
user_unique_identifier=i.user_unique_identifier,
api_type=i.api_type,
data=i.data,
update_time=i.update_time,
)
)
progress.update(task, advance=1)
if progress.tasks[task].completed % 100 == 0:
db_session.commit()
db_session.commit()
logger.success('tetris_stats: 数据迁移完成!')
def upgrade(name: str = '') -> None:
if name:
return
data_migrate()
def downgrade(name: str = '') -> None:
if name:
return

View File

@@ -26,6 +26,8 @@ depends_on: str | Sequence[str] | None = None
def upgrade(name: str = '') -> None:
if name:
return
if op.get_bind().dialect.name == 'postgresql':
return
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('nonebot_plugin_tetris_stats_tetriohistoricaldata', schema=None) as batch_op:
batch_op.alter_column(
@@ -38,6 +40,8 @@ def upgrade(name: str = '') -> None:
def downgrade(name: str = '') -> None:
if name:
return
if op.get_bind().dialect.name == 'postgresql':
return
# ### commands auto generated by Alembic - please adjust! ###
logger.warning('新数据可能不支持降级!')
logger.warning('请确认数据库内数据可以迁移到旧版本!')

View File

@@ -0,0 +1,84 @@
"""migrate nonebot_plugin_tetris_stats_bind
迁移 ID: d61e6ae36586
父迁移: b2075a5ce371
创建时间: 2025-07-17 23:58:13.408384
"""
from __future__ import annotations
from typing import TYPE_CHECKING
from alembic import op
from nonebot.log import logger
from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn, TextColumn
from sqlalchemy import inspect
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session
if TYPE_CHECKING:
from collections.abc import Sequence
revision: str = 'd61e6ae36586'
down_revision: str | Sequence[str] | None = 'b2075a5ce371'
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def data_migrate() -> None:
conn = op.get_bind()
insp = inspect(conn)
table_names = insp.get_table_names()
if 'nonebot_plugin_tetris_stats_bind' not in table_names:
return
Base = automap_base() # noqa: N806
Base.prepare(autoload_with=conn)
Old = Base.classes.nonebot_plugin_tetris_stats_bind # noqa: N806
New = Base.classes.nb_t_bind # noqa: N806
with Session(conn) as db_session:
count = db_session.query(Old).count()
if count == 0:
return
logger.warning('tetris_stats: 正在迁移数据, 请不要关闭程序...')
with Progress(
SpinnerColumn(),
TextColumn('[progress.description]{task.description}'),
BarColumn(),
TaskProgressColumn(),
) as progress:
task = progress.add_task('迁移数据...', total=count)
for i in db_session.query(Old).yield_per(100):
db_session.add(
New(
id=i.id,
user_id=i.user_id,
game_platform=i.game_platform,
game_account=i.game_account,
)
)
progress.update(task, advance=1)
if progress.tasks[task].completed % 100 == 0:
db_session.commit()
db_session.commit()
logger.success('tetris_stats: 数据迁移完成!')
def upgrade(name: str = '') -> None:
if name:
return
data_migrate()
def downgrade(name: str = '') -> None:
if name:
return

View File

@@ -0,0 +1,85 @@
"""migrate nonebot_plugin_tetris_stats_toshistoricaldata
迁移 ID: ee76ae37d70a
父迁移: ce073d279d19
创建时间: 2025-07-18 04:29:52.976624
"""
from __future__ import annotations
from typing import TYPE_CHECKING
from alembic import op
from nonebot.log import logger
from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn, TextColumn
from sqlalchemy import inspect
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session
if TYPE_CHECKING:
from collections.abc import Sequence
revision: str = 'ee76ae37d70a'
down_revision: str | Sequence[str] | None = 'ce073d279d19'
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def data_migrate() -> None:
conn = op.get_bind()
insp = inspect(conn)
table_names = insp.get_table_names()
if 'nonebot_plugin_tetris_stats_toshistoricaldata' not in table_names:
return
Base = automap_base() # noqa: N806
Base.prepare(autoload_with=conn)
Old = Base.classes.nonebot_plugin_tetris_stats_toshistoricaldata # noqa: N806
New = Base.classes.nb_t_tos_hist_data # noqa: N806
with Session(conn) as db_session:
count = db_session.query(Old).count()
if count == 0:
return
logger.warning('tetris_stats: 正在迁移数据, 请不要关闭程序...')
with Progress(
SpinnerColumn(),
TextColumn('[progress.description]{task.description}'),
BarColumn(),
TaskProgressColumn(),
) as progress:
task = progress.add_task('迁移数据...', total=count)
for i in db_session.query(Old).yield_per(1):
db_session.add(
New(
id=i.id,
user_unique_identifier=i.user_unique_identifier,
api_type=i.api_type,
data=i.data,
update_time=i.update_time,
)
)
progress.update(task, advance=1)
if progress.tasks[task].completed % 100 == 0:
db_session.commit()
db_session.commit()
logger.success('tetris_stats: 数据迁移完成!')
def upgrade(name: str = '') -> None:
if name:
return
data_migrate()
def downgrade(name: str = '') -> None:
if name:
return

View File

@@ -25,6 +25,8 @@ depends_on: str | Sequence[str] | None = None
def upgrade(name: str = '') -> None:
if name:
return
if op.get_bind().dialect.name == 'postgresql':
return
with op.batch_alter_table('nonebot_plugin_tetris_stats_iorank', schema=None) as batch_op:
batch_op.drop_index('ix_nonebot_plugin_tetris_stats_iorank_file_hash')
batch_op.drop_index('ix_nonebot_plugin_tetris_stats_iorank_rank')
@@ -66,23 +68,25 @@ def upgrade(name: str = '') -> None:
def downgrade(name: str = '') -> None:
if name:
return
if op.get_bind().dialect.name == 'postgresql':
return
op.create_table(
'nonebot_plugin_tetris_stats_iorank',
sa.Column('id', sa.INTEGER(), nullable=False),
sa.Column('rank', sa.VARCHAR(length=2), nullable=False),
sa.Column('tr_line', sa.FLOAT(), nullable=False),
sa.Column('player_count', sa.INTEGER(), nullable=False),
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('rank', sa.String(length=2), nullable=False),
sa.Column('tr_line', sa.Float(), nullable=False),
sa.Column('player_count', sa.Integer(), nullable=False),
sa.Column('low_pps', sa.JSON(), nullable=False),
sa.Column('low_apm', sa.JSON(), nullable=False),
sa.Column('low_vs', sa.JSON(), nullable=False),
sa.Column('avg_pps', sa.FLOAT(), nullable=False),
sa.Column('avg_apm', sa.FLOAT(), nullable=False),
sa.Column('avg_vs', sa.FLOAT(), nullable=False),
sa.Column('avg_pps', sa.Float(), nullable=False),
sa.Column('avg_apm', sa.Float(), nullable=False),
sa.Column('avg_vs', sa.Float(), nullable=False),
sa.Column('high_pps', sa.JSON(), nullable=False),
sa.Column('high_apm', sa.JSON(), nullable=False),
sa.Column('high_vs', sa.JSON(), nullable=False),
sa.Column('update_time', sa.DATETIME(), nullable=False),
sa.Column('file_hash', sa.VARCHAR(length=128), nullable=True),
sa.Column('update_time', sa.DateTime(), nullable=False),
sa.Column('file_hash', sa.String(length=128), nullable=True),
sa.PrimaryKeyConstraint('id', name='pk_nonebot_plugin_tetris_stats_iorank'),
)
with op.batch_alter_table('nonebot_plugin_tetris_stats_iorank', schema=None) as batch_op:

View File

@@ -11,8 +11,8 @@ from nonebot_plugin_orm import AsyncSession, get_session
from nonebot_plugin_user import User
from sqlalchemy import select
from ..utils.typing import AllCommandType, BaseCommandType, GameType, TETRIOCommandType
from .models import Bind, TriggerHistoricalData
from ..utils.typedefs import AllCommandType, BaseCommandType, GameType, TETRIOCommandType
from .models import Bind, TriggerHistoricalDataV2
UTC = timezone.utc
@@ -42,6 +42,8 @@ async def create_or_update_bind(
user: User,
game_platform: GameType,
game_account: str,
*,
verify: bool = False,
) -> BindStatus:
bind = await query_bind_info(
session=session,
@@ -53,14 +55,33 @@ async def create_or_update_bind(
user_id=user.id,
game_platform=game_platform,
game_account=game_account,
verify=verify,
)
session.add(bind)
message = BindStatus.SUCCESS
status = BindStatus.SUCCESS
else:
bind.game_account = game_account
message = BindStatus.UPDATE
bind.verify = verify
status = BindStatus.UPDATE
await session.commit()
return message
return status
async def remove_bind(
session: AsyncSession,
user: User,
game_platform: GameType,
) -> bool:
bind = await query_bind_info(
session=session,
user=user,
game_platform=game_platform,
)
if bind is not None:
await session.delete(bind)
await session.commit()
return True
return False
T = TypeVar('T', 'TETRIOHistoricalData', 'TOPHistoricalData', 'TOSHistoricalData')
@@ -122,7 +143,7 @@ async def trigger(
except FinishedException:
async with get_session() as session:
session.add(
TriggerHistoricalData(
TriggerHistoricalDataV2(
trigger_time=trigger_time,
session_persist_id=session_persist_id,
game_platform=game_platform,

View File

@@ -9,7 +9,7 @@ from sqlalchemy import JSON, DateTime, Dialect, String, TypeDecorator
from sqlalchemy.orm import Mapped, MappedAsDataclass, mapped_column
from typing_extensions import override
from ..utils.typing import AllCommandType, GameType
from ..utils.typedefs import AllCommandType, GameType
class PydanticType(TypeDecorator):
@@ -65,13 +65,18 @@ class PydanticType(TypeDecorator):
class Bind(MappedAsDataclass, Model):
__tablename__ = 'nb_t_bind'
id: Mapped[int] = mapped_column(init=False, primary_key=True)
user_id: Mapped[int] = mapped_column(index=True)
game_platform: Mapped[GameType] = mapped_column(String(32))
game_account: Mapped[str]
verify: Mapped[bool]
class TriggerHistoricalData(MappedAsDataclass, Model):
class TriggerHistoricalDataV2(MappedAsDataclass, Model):
__tablename__ = 'nb_t_trigger_hist_v2'
id: Mapped[int] = mapped_column(init=False, primary_key=True)
trigger_time: Mapped[datetime] = mapped_column(DateTime)
session_persist_id: Mapped[int]

View File

@@ -7,6 +7,7 @@ from nonebot.typing import T_Handler
from nonebot_plugin_alconna import AlcMatches, Alconna, At, CommandMeta, on_alconna
from .. import ns
from ..i18n.model import Lang
from ..utils.exception import MessageFormatError, NeedCatchError
command: Alconna = Alconna(
@@ -30,7 +31,7 @@ def add_block_handlers(handler: Callable[[T_Handler], T_Handler]) -> None:
@handler
async def _(bot: Bot, matcher: Matcher, target: At):
if isinstance(target, At) and target.target == bot.self_id:
await matcher.finish('不能查询bot的信息')
await matcher.finish(Lang.interaction.wrong.query_bot())
from . import tetrio, top, tos # noqa: F401, E402
@@ -43,7 +44,7 @@ async def _(matcher: Matcher, account: MessageFormatError):
@alc.handle()
async def _(matcher: Matcher, matches: AlcMatches):
if matches.head_matched and matches.options != {} or matches.main_args == {}:
if (matches.head_matched and matches.options != {}) or matches.main_args == {}:
await matcher.finish(
(f'{matches.error_info!r}\n' if matches.error_info is not None else '')
+ f'输入"{matches.header_result} --help"查看帮助'

View File

@@ -1 +0,0 @@
CANT_VERIFY_MESSAGE = '* 由于无法验证绑定信息, 不能保证查询到的用户为本人\n'

View File

@@ -3,7 +3,7 @@ from typing import Generic, TypeVar
from pydantic import BaseModel
from ..utils.typing import GameType
from ..utils.typedefs import GameType
T = TypeVar('T', bound=GameType)
@@ -22,3 +22,5 @@ class BaseUser(BaseModel, ABC, Generic[T]):
@abstractmethod
def unique_identifier(self) -> str:
raise NotImplementedError
__hash__ = BaseModel.__hash__

View File

@@ -23,7 +23,7 @@ command = Subcommand(
)
from . import bind, config, query, record # noqa: E402
from . import bind, config, list, query, rank, record, unbind, verify # noqa: A004, E402
main_command.add(command)
@@ -31,6 +31,10 @@ __all__ = [
'alc',
'bind',
'config',
'list',
'query',
'rank',
'record',
'unbind',
'verify',
]

View File

@@ -1,6 +1,5 @@
from .player import Player
from .schemas.user import User
from .schemas.user_info import UserInfoSuccess
from .tetra_league import full_export as tetra_league_full_export
__all__ = ['Player', 'User', 'UserInfoSuccess', 'tetra_league_full_export']
__all__ = ['Player', 'User', 'UserInfoSuccess']

View File

@@ -1,30 +1,37 @@
from asyncio import Lock
from datetime import datetime, timezone
from datetime import datetime, timedelta, timezone
from typing import ClassVar
from weakref import WeakValueDictionary
from aiocache import Cache as ACache # type: ignore[import-untyped]
from nonebot.compat import type_validate_json
from nonebot.log import logger
from yarl import URL
from ....config.config import config
from ....utils.limit import limit
from ....utils.request import Request
from .schemas.base import FailedModel, SuccessModel
UTC = timezone.utc
request = Request(config.tetris.proxy.tetrio or config.tetris.proxy.main)
request.request = limit(timedelta(seconds=1))(request.request) # type: ignore[method-assign]
class Cache:
cache = ACache(ACache.MEMORY)
task: ClassVar[WeakValueDictionary[str, Lock]] = WeakValueDictionary()
task: ClassVar[WeakValueDictionary[URL, Lock]] = WeakValueDictionary()
@classmethod
async def get(cls, url: str) -> bytes:
async def get(cls, url: URL, extra_headers: dict | None = None) -> bytes:
lock = cls.task.setdefault(url, Lock())
async with lock:
if (cached_data := await cls.cache.get(url)) is not None:
logger.debug(f'{url}: Cache hit!')
return cached_data
response_data = await Request.request(url)
response_data = await request.request(url, extra_headers, enable_anti_cloudflare=True)
parsed_data: SuccessModel | FailedModel = type_validate_json(SuccessModel | FailedModel, response_data) # type: ignore[arg-type]
if isinstance(parsed_data, SuccessModel):
await cls.cache.add(

View File

@@ -0,0 +1,96 @@
from typing import Literal, overload
from uuid import UUID
from nonebot import __version__ as __nonebot_version__
from nonebot.compat import type_validate_json
from yarl import URL
from ....utils.exception import RequestError
from ....version import __version__
from ..constant import BASE_URL
from .cache import Cache
from .schemas.base import FailedModel
from .schemas.leaderboards import Parameter
from .schemas.leaderboards.by import By, BySuccessModel
from .schemas.leaderboards.solo import Solo, SoloSuccessModel
from .schemas.leaderboards.zenith import Zenith, ZenithSuccessModel
async def by(
by_type: Literal['league', 'xp', 'ar'], parameter: Parameter, x_session_id: UUID | None = None
) -> BySuccessModel:
model: By = type_validate_json(
By, # type: ignore[arg-type]
await get(
BASE_URL / f'users/by/{by_type}',
parameter,
{
'X-Session-ID': str(x_session_id),
'User-Agent': f'nonebot-plugin-tetris-stats/{__version__} (Windows NT 10.0; Win64; x64) NoneBot2/{__nonebot_version__}',
}
if x_session_id is not None
else None,
),
)
if isinstance(model, FailedModel):
msg = f'排行榜信息请求错误:\n{model.error}'
raise RequestError(msg)
return model
@overload
async def records(
records_type: Literal['40l', 'blitz'],
scope: str = '_global',
revolution_id: str | None = None,
*,
parameter: Parameter,
) -> SoloSuccessModel: ...
@overload
async def records(
records_type: Literal['zenith', 'zenithex'],
scope: str = '_global',
revolution_id: str | None = None,
*,
parameter: Parameter,
) -> ZenithSuccessModel: ...
async def records(
records_type: Literal['40l', 'blitz', 'zenith', 'zenithex'],
scope: str = '_global',
revolution_id: str | None = None,
*,
parameter: Parameter,
) -> SoloSuccessModel | ZenithSuccessModel:
model: Solo | Zenith
match records_type:
case '40l' | 'blitz':
model = type_validate_json(
Solo, # type: ignore[arg-type]
await get(
BASE_URL / 'records' / f'{records_type}{scope}{revolution_id if revolution_id is not None else ""}',
parameter,
),
)
case 'zenith' | 'zenithex':
model = type_validate_json(
Zenith, # type: ignore[arg-type]
await get(
BASE_URL / 'records' / f'{records_type}{scope}{revolution_id if revolution_id is not None else ""}',
parameter,
),
)
case _:
msg = f'records_type: {records_type} is not supported'
raise ValueError(msg)
if isinstance(model, FailedModel):
msg = f'排行榜信息请求错误:\n{model.error}' # type: ignore[attr-defined]
raise RequestError(msg)
return model
async def get(url: URL, parameter: Parameter, extra_headers: dict | None = None) -> bytes:
return await Cache.get(url % parameter.to_params(), extra_headers)

View File

@@ -7,10 +7,12 @@ from sqlalchemy.orm import Mapped, MappedAsDataclass, mapped_column
from ....db.models import PydanticType
from .schemas.base import SuccessModel
from .typing import Records, Summaries
from .typedefs import Records, Summaries
class TETRIOHistoricalData(MappedAsDataclass, Model):
__tablename__ = 'nb_t_io_hist_data'
id: Mapped[int] = mapped_column(init=False, primary_key=True)
user_unique_identifier: Mapped[str] = mapped_column(String(24), index=True)
api_type: Mapped[Literal['User Info', Records, Summaries]] = mapped_column(String(32), index=True)

View File

@@ -7,11 +7,11 @@ from nonebot.compat import type_validate_json
from ....db import anti_duplicate_add
from ....utils.exception import RequestError
from ....utils.request import splice_url
from ..constant import BASE_URL, USER_ID, USER_NAME
from .cache import Cache
from .models import TETRIOHistoricalData
from .schemas.base import FailedModel
from .schemas.labs.leagueflow import LeagueFlow, LeagueFlowSuccess
from .schemas.records.solo import Solo as SoloRecord
from .schemas.records.solo import SoloSuccessModel as RecordsSoloSuccessModel
from .schemas.summaries import (
@@ -24,9 +24,10 @@ from .schemas.summaries import (
SoloSuccessModel as SummariesSoloSuccessModel,
)
from .schemas.summaries.base import User as SummariesUser
from .schemas.summaries.league import LeagueSuccessModel
from .schemas.user import User
from .schemas.user_info import UserInfo, UserInfoSuccess
from .typing import Records, Summaries
from .typedefs import Records, Summaries
class RecordModeType(str, Enum):
@@ -45,7 +46,7 @@ class RecordKey(NamedTuple):
record_type: RecordType
def to_records(self) -> Records:
return cast(Records, f'{self.mode_type.value}_{self.record_type.value}')
return cast('Records', f'{self.mode_type.value}_{self.record_type.value}')
class Player:
@@ -55,6 +56,7 @@ class Player:
'blitz': SummariesSoloSuccessModel,
'zenith': ZenithSuccessModel,
'zenithex': ZenithSuccessModel,
'league': LeagueSuccessModel,
'zen': ZenSuccessModel,
'achievements': AchievementsSuccessModel,
}
@@ -83,15 +85,11 @@ class Player:
self._user_info: UserInfoSuccess | None = None
self._summaries: dict[Summaries, SummariesModel] = {}
self._records: dict[RecordKey, RecordsSoloSuccessModel] = {}
self._leagueflow: LeagueFlowSuccess | None = None
@property
def _request_user_parameter(self) -> str:
if self.user_id is not None:
return self.user_id
if self.user_name is not None:
return self.user_name.lower()
msg = 'Invalid user'
raise ValueError(msg)
return self.user_id or cast('str', self.user_name).lower()
@property
async def user(self) -> User:
@@ -115,7 +113,7 @@ class Player:
async def get_info(self) -> UserInfoSuccess:
"""Get User Info"""
if self._user_info is None:
raw_user_info = await Cache.get(splice_url([BASE_URL, 'users/', f'{self._request_user_parameter}']))
raw_user_info = await Cache.get(BASE_URL / 'users' / self._request_user_parameter)
user_info: UserInfo = type_validate_json(UserInfo, raw_user_info) # type: ignore[arg-type]
if isinstance(user_info, FailedModel):
msg = f'用户信息请求错误:\n{user_info.error}'
@@ -138,12 +136,14 @@ class Player:
@overload
async def get_summaries(self, summaries_type: Literal['zen']) -> ZenSuccessModel: ...
@overload
async def get_summaries(self, summaries_type: Literal['league']) -> LeagueSuccessModel: ...
@overload
async def get_summaries(self, summaries_type: Literal['achievements']) -> AchievementsSuccessModel: ...
async def get_summaries(self, summaries_type: Summaries) -> SummariesModel:
if summaries_type not in self._summaries:
raw_summaries = await Cache.get(
splice_url([BASE_URL, 'users/', f'{self._request_user_parameter}/', 'summaries/', summaries_type])
BASE_URL / 'users' / self._request_user_parameter / 'summaries' / summaries_type
)
summaries: SummariesModel | FailedModel = type_validate_json(
self.__SUMMARIES_MAPPING[summaries_type] | FailedModel, # type: ignore[arg-type]
@@ -163,21 +163,34 @@ class Player:
)
return self._summaries[summaries_type]
async def get_leagueflow(self) -> LeagueFlowSuccess:
if self._leagueflow is None:
leagueflow: LeagueFlow = type_validate_json(
LeagueFlow, # type: ignore[arg-type]
await Cache.get(BASE_URL / 'labs/leagueflow' / self._request_user_parameter),
)
if isinstance(leagueflow, FailedModel):
msg = f'League 历史记录请求错误:\n{leagueflow.error}'
raise RequestError(msg)
self._leagueflow = leagueflow
return self._leagueflow
@property
@alru_cache
async def sprint(self) -> SummariesSoloSuccessModel:
return await self.get_summaries('40l')
@property
@alru_cache
async def blitz(self) -> SummariesSoloSuccessModel:
return await self.get_summaries('blitz')
@property
@alru_cache
async def zen(self) -> ZenSuccessModel:
return await self.get_summaries('zen')
@property
async def league(self) -> LeagueSuccessModel:
return await self.get_summaries('league')
async def _get_local_summaries_user(self) -> SummariesUser | None:
allow_summaries: set[Literal['40l', 'blitz', 'zenith', 'zenithex']] = {
'40l',
@@ -212,16 +225,7 @@ class Player:
async def get_records(self, mode_type: RecordModeType, records_type: RecordType) -> RecordsSoloSuccessModel:
if (record_key := RecordKey(mode_type, records_type)) not in self._records:
raw_records = await Cache.get(
splice_url(
[
BASE_URL,
'users/',
f'{self._request_user_parameter}/',
'records/',
f'{mode_type}/',
records_type,
]
)
BASE_URL / 'users' / self._request_user_parameter / 'records' / mode_type / records_type,
)
records: RecordsSoloSuccessModel | FailedModel = type_validate_json(SoloRecord, raw_records) # type: ignore[arg-type]
if isinstance(records, FailedModel):

View File

@@ -1,7 +1,9 @@
from datetime import datetime
from typing import Literal
from pydantic import BaseModel
from pydantic import BaseModel, Field
from ...typedefs import Prisecter
class AggregateStats(BaseModel):
@@ -39,11 +41,31 @@ class Garbage(BaseModel):
cleared: int
class P(BaseModel): # what is P
class P(BaseModel):
pri: float
sec: float
ter: float
def to_prisecter(self) -> Prisecter:
return Prisecter(f'{self.pri}:{self.sec}:{self.ter}')
# fmt: off
class ArCounts(BaseModel):
bronze: int | None = Field(default=None, alias='1') # pyright: ignore [reportGeneralTypeIssues]
silver: int | None = Field(default=None, alias='2') # pyright: ignore [reportGeneralTypeIssues]
gold: int | None = Field(default=None, alias='3') # pyright: ignore [reportGeneralTypeIssues]
platinum: int | None = Field(default=None, alias='4') # pyright: ignore [reportGeneralTypeIssues]
diamond: int | None = Field(default=None, alias='5') # pyright: ignore [reportGeneralTypeIssues]
issued: int | None = Field(default=None, alias='100') # pyright: ignore [reportGeneralTypeIssues]
top3: int | None = Field(default=None, alias='t3')
top5: int | None = Field(default=None, alias='t5')
top10: int | None = Field(default=None, alias='t10')
top25: int | None = Field(default=None, alias='t25')
top50: int | None = Field(default=None, alias='t50')
top100: int | None = Field(default=None, alias='t100')
# fmt: on
class Cache(BaseModel):
status: str

View File

@@ -12,20 +12,20 @@ class Time(BaseModel):
zero: bool
locked: bool
prev: int
frameoffset: int
frameoffset: int | None = None
class Stats(BaseModel):
seed: int | None = None # ?: 不知道是之后都没有了还是还会有
seed: float | None = None # ?: 不知道是之后都没有了还是还会有
lines: int
level_lines: int
level_lines_needed: int
inputs: int
holds: int
holds: int = 0
time: Time | None = None # ?: 不知道是之后都没有了还是还会有
score: int
zenlevel: int
zenprogress: int
zenlevel: int | None = None
zenprogress: int | None = None
level: int
combo: int
currentcombopower: int | None = None

View File

@@ -0,0 +1,43 @@
from datetime import datetime
from enum import IntEnum
from typing import Literal, NamedTuple
from pydantic import BaseModel, Field
from ..base import FailedModel
from ..base import SuccessModel as BaseSuccessModel
class Result(IntEnum):
VICTORY = 1
DEFEAT = 2
VICTORY_BY_DISQUALIFICATION = 3
DEFEAT_BY_DISQUALIFICATION = 4
TIE = 5
NO_CONTEST = 6
MATCH_NULLIFIED = 7
class Point(NamedTuple):
timestamp_offset: int
result: Result
post_match_tr: int
opponent_pre_match_tr: int
"""If the opponent was unranked, same as post_match_tr."""
class Data(BaseModel):
start_time: datetime = Field(..., alias='startTime')
points: list[Point] = Field(..., min_length=1)
class Empty(BaseModel):
start_time: Literal[9007199254740991] = Field(..., alias='startTime')
points: list = Field(..., max_length=0)
class LeagueFlowSuccess(BaseSuccessModel):
data: Data | Empty
LeagueFlow = LeagueFlowSuccess | FailedModel

View File

@@ -0,0 +1,18 @@
from typing import Any
from nonebot.compat import PYDANTIC_V2
from pydantic import BaseModel, Field
from ...typedefs import Prisecter
class Parameter(BaseModel):
after: Prisecter | None = None
before: Prisecter | None = None
limit: int = Field(default=25, ge=1, le=100)
country: str | None = None
def to_params(self) -> dict[str, Any]:
if PYDANTIC_V2:
return self.model_dump(exclude_defaults=True)
return self.dict(exclude_defaults=True)

View File

@@ -1,27 +0,0 @@
from pydantic import BaseModel, Field
from ..base import SuccessModel
from .base import Entry as BaseEntry
class ArCounts(BaseModel):
bronze: int | None = Field(None, alias='1')
silver: int | None = Field(None, alias='2')
gold: int | None = Field(None, alias='3')
platinum: int | None = Field(None, alias='4')
diamond: int | None = Field(None, alias='5')
issued: int | None = Field(None, alias='100')
top10: int | None = Field(None, alias='t10')
class Entry(BaseEntry):
ar: int
ar_counts: ArCounts
class Data(BaseModel):
entries: list[Entry]
class ArSuccessModel(SuccessModel):
data: Data

View File

@@ -1,30 +0,0 @@
from datetime import datetime
from pydantic import BaseModel, Field
from ...typing import Rank
from ..base import P
class League(BaseModel):
gamesplayed: int
gameswon: int
rating: int
rank: Rank
decaying: bool
class Entry(BaseModel):
id: str = Field(..., alias='_id')
username: str
role: str
xp: float
league: League
supporter: bool | None = None
verified: bool
country: str | None = None
ts: datetime
gamesplayed: int
gameswon: int
gametime: float
p: P

View File

@@ -0,0 +1,66 @@
from datetime import datetime
from typing import Literal
from pydantic import BaseModel, Field
from ...typedefs import Rank, ValidRank
from ..base import ArCounts, FailedModel, P, SuccessModel
class BaseLeague(BaseModel):
gamesplayed: int
gameswon: int
tr: float
gxe: float
rank: Rank
bestrank: ValidRank
glicko: float
rd: float
decaying: bool
class InvalidLeague(BaseLeague):
pps: float | None
apm: None
vs: None
class League(BaseLeague):
pps: float
apm: float
vs: float
class BaseEntry(BaseModel):
id: str = Field(..., alias='_id')
username: str
role: Literal['anon', 'user', 'bot', 'halfmod', 'mod', 'admin', 'sysop']
ts: datetime | None = None
xp: float
country: str | None = None
supporter: bool | None = None
gamesplayed: int
gameswon: int
gametime: float
ar: int
ar_counts: ArCounts
p: P
class InvalidEntry(BaseEntry):
league: InvalidLeague
class Entry(BaseEntry):
league: League
class Data(BaseModel):
entries: list[Entry | InvalidEntry]
class BySuccessModel(SuccessModel):
data: Data
By = BySuccessModel | FailedModel

View File

@@ -1,6 +1,6 @@
from pydantic import BaseModel
from ..base import SuccessModel
from ..base import FailedModel, SuccessModel
from ..summaries.solo import Record
@@ -10,3 +10,6 @@ class Data(BaseModel):
class SoloSuccessModel(SuccessModel):
data: Data
Solo = SoloSuccessModel | FailedModel

View File

@@ -1,12 +0,0 @@
from pydantic import BaseModel
from ..base import SuccessModel
from .base import Entry
class Data(BaseModel):
entries: list[Entry]
class XpSuccessModel(SuccessModel):
data: Data

View File

@@ -1,6 +1,6 @@
from pydantic import BaseModel
from ..base import SuccessModel
from ..base import FailedModel, SuccessModel
from ..summaries.zenith import Record
@@ -10,3 +10,6 @@ class Data(BaseModel):
class ZenithSuccessModel(SuccessModel):
data: Data
Zenith = ZenithSuccessModel | FailedModel

View File

@@ -1,19 +1,21 @@
from .achievements import Achievements, AchievementsSuccessModel
from .league import LeagueSuccessModel
from .solo import Solo, SoloSuccessModel
from .zen import Zen, ZenSuccessModel
from .zenith import Zenith, ZenithEx, ZenithSuccessModel
SummariesModel = AchievementsSuccessModel | SoloSuccessModel | ZenSuccessModel | ZenithSuccessModel
SummariesModel = AchievementsSuccessModel | SoloSuccessModel | ZenSuccessModel | LeagueSuccessModel | ZenithSuccessModel
__all__ = [
'Achievements',
'AchievementsSuccessModel',
'LeagueSuccessModel',
'Solo',
'SoloSuccessModel',
'SummariesModel',
'Zen',
'ZenSuccessModel',
'Zenith',
'ZenithEx',
'ZenithSuccessModel',
'SummariesModel',
]

View File

@@ -1,25 +1,93 @@
from typing import TypeAlias
from datetime import datetime
from enum import IntEnum
from typing import Literal, TypeAlias
from pydantic import BaseModel
from pydantic import BaseModel, Field
from ..base import FailedModel, SuccessModel
class RankType(IntEnum):
PERCENTILE = 1
ISSUE = 2
ZENITH = 3
PERCENTILELAX = 4
PERCENTILEVLAX = 5
PERCENTILEMLAX = 6
class ValueType(IntEnum):
NONE = 0
NUMBER = 1
TIME = 2
TIME_INV = 3
FLOOR = 4
ISSUE = 5
NUMBER_INV = 6
class ArType(IntEnum):
UNRANKED = 0
RANKED = 1
COMPETITIVE = 2
class Rank(IntEnum):
NONE = 0
BRONZE = 1
SILVER = 2
GOLD = 3
PLATINUM = 4
DIAMOND = 5
ISSUED = 100
class Ally(BaseModel):
id: str = Field(alias='_id')
username: str
role: Literal['anon', 'user', 'bot', 'halfmod', 'mod', 'admin', 'sysop', 'hidden', 'banned']
country: str | None = None
supporter: bool
avatar_revision: int | None = None
class X(BaseModel):
ally: Ally | None = None
class Achievement(BaseModel):
# 这**都是些啥
k: int
o: int
rt: int
vt: int
achievement_id: int = Field(alias='k')
category: str
primary_name: str = Field(alias='name')
objective: str = Field(alias='object')
flavor_text: str = Field(alias='desc')
order: int = Field(alias='o')
rank_type: RankType = Field(alias='rt')
value_type: ValueType = Field(alias='vt')
ar_type: ArType = Field(alias='art')
min: int
deci: int
name: str
object: str
category: str
hidden: bool
desc: str
nolb: bool
event: str | None = None
event_past: bool | None = None
disabled: bool | None = None
pair: bool | None = None
achieved_score: float | None = Field(None, alias='v')
a: float | None = None
t: datetime | None = None
pos: int | None = None
total: int | None = None
rank: Rank | None = None
x: X | None = None
n: str
stub: bool
tiebreak: int
notifypb: bool
id: str | None = Field(None, alias='_id')
progress: float | None = None
stub: bool | None = None
class AchievementsSuccessModel(SuccessModel):

View File

@@ -7,5 +7,4 @@ class User(BaseModel):
avatar_revision: int | None
banner_revision: int | None
country: str | None
verified: int
supporter: int

View File

@@ -0,0 +1,130 @@
from typing import Literal
from nonebot.compat import PYDANTIC_V2
from pydantic import BaseModel, Field
from ...typedefs import Rank, S1Rank, S1ValidRank
from ..base import SuccessModel
if PYDANTIC_V2:
from pydantic import field_validator
else:
from pydantic import validator
class PastInner(BaseModel):
season: str
username: str
country: str | None = None
placement: int | None = None
gamesplayed: int
gameswon: int
glicko: float
gxe: float
tr: float
rd: float
rank: S1Rank
bestrank: S1ValidRank
ranked: bool
apm: float
pps: float
vs: float
class Past(BaseModel):
first: PastInner | None = Field(default=None, alias='1') # pyright: ignore [reportGeneralTypeIssues]
class BaseData(BaseModel):
decaying: bool
past: Past
class NeverPlayedData(BaseData):
gamesplayed: Literal[0]
gameswon: Literal[0]
glicko: Literal[-1]
rd: Literal[-1]
gxe: Literal[-1]
tr: Literal[-1]
rank: Literal['z']
apm: None = None
pps: None = None
vs: None = None
standing: Literal[-1]
standing_local: Literal[-1]
prev_rank: None
prev_at: Literal[-1]
next_rank: None
next_at: Literal[-1]
percentile: Literal[-1]
percentile_rank: Literal['z']
class NeverRatedData(BaseData):
gamesplayed: Literal[1, 2, 3, 4, 5, 6, 7, 8, 9]
gameswon: int
glicko: Literal[-1]
rd: Literal[-1]
gxe: Literal[-1]
tr: Literal[-1]
apm: float
pps: float
vs: float
rank: Literal['z']
standing: Literal[-1]
standing_local: Literal[-1]
prev_rank: None
prev_at: Literal[-1]
next_rank: None
next_at: Literal[-1]
percentile: Literal[-1]
percentile_rank: Literal['z']
if PYDANTIC_V2:
@field_validator('apm', 'pps', 'vs', mode='before')
@classmethod
def _(cls, value: float | None) -> float:
if value is None:
return 0
return value
else:
@validator('apm', 'pps', 'vs', pre=True, always=True)
@classmethod
def _(cls, value: float | None) -> float:
if value is None:
return 0
return value
class RatedData(BaseData):
gamesplayed: int
gameswon: int
glicko: float
rd: float
gxe: float
tr: float
rank: Rank
bestrank: Rank
standing: int
apm: float
pps: float
vs: float
standing_local: int
prev_rank: Rank | None = None
prev_at: int
next_rank: Rank | None = None
next_at: int
percentile: float
percentile_rank: str
class InvalidData(BaseModel):
"""I don't know what osk is doing, but the return value is an empty dictionary"""
class LeagueSuccessModel(SuccessModel):
data: NeverPlayedData | NeverRatedData | RatedData | InvalidData

View File

@@ -86,7 +86,7 @@ class Record(BaseModel):
pb: bool
oncepb: bool
ts: datetime
revolution: None
revolution: str | None
user: User
otherusers: list
leaderboards: list[str]
@@ -97,7 +97,7 @@ class Record(BaseModel):
class Best(BaseModel):
record: None # WTF
record: Record | None
rank: int

View File

@@ -1,59 +0,0 @@
from pydantic import BaseModel, Field
from ..typing import Rank
from .base import FailedModel
from .base import SuccessModel as BaseSuccessModel
class _User(BaseModel):
id: str = Field(..., alias='_id')
username: str
role: str
xp: float
supporter: bool | None = None
verified: bool
country: str | None = None
class _League(BaseModel):
gamesplayed: int
gameswon: int
rating: float
rank: Rank
bestrank: Rank
decaying: bool
class ValidLeague(_League):
glicko: float
rd: float
apm: float
pps: float
vs: float
class ValidUser(_User):
league: ValidLeague
class InvalidLeague(_League):
glicko: float | None = None
rd: float | None = None
apm: float | None = None
pps: float | None = None
vs: float | None = None
class InvalidUser(_User):
league: InvalidLeague
class Data(BaseModel):
users: list[ValidUser | InvalidUser]
class TetraLeagueSuccess(BaseSuccessModel):
data: Data
TetraLeague = TetraLeagueSuccess | FailedModel

View File

@@ -3,7 +3,7 @@ from typing import Literal
from pydantic import BaseModel, Field
from .base import FailedModel
from .base import ArCounts, FailedModel
from .base import SuccessModel as BaseSuccessModel
@@ -14,13 +14,19 @@ class Badge(BaseModel):
ts: datetime | Literal[False] | None = None
class Discord(BaseModel):
class Connection(BaseModel):
id: str
username: str
display_username: str
class Connections(BaseModel):
discord: Discord | None = None
discord: Connection | None = None
twitch: Connection | None = None
twitter: Connection | None = None
reddit: Connection | None = None
youtube: Connection | None = None
steam: Connection | None = None
class Distinguishment(BaseModel):
@@ -28,9 +34,9 @@ class Distinguishment(BaseModel):
class Data(BaseModel):
id: str = Field(..., alias='_id')
id: str = Field(default=..., alias='_id')
username: str
role: Literal['anon', 'user', 'bot', 'halfmod', 'mod', 'admin', 'sysop', 'banned']
role: Literal['anon', 'user', 'bot', 'halfmod', 'mod', 'admin', 'sysop', 'hidden', 'banned']
ts: datetime | None = None
botmaster: str | None = None
badges: list[Badge]
@@ -42,7 +48,6 @@ class Data(BaseModel):
badstanding: bool | None = None
supporter: bool | None = None # osk说是必有, 但实际上不是 fkosk
supporter_tier: int
verified: bool
avatar_revision: int | None = None
"""This user's avatar ID. Get their avatar at
@@ -57,6 +62,9 @@ class Data(BaseModel):
connections: Connections
friend_count: int | None = None
distinguishment: Distinguishment | None = None
achievements: list[int]
ar: int
ar_counts: ArCounts
class UserInfoSuccess(BaseSuccessModel):

View File

@@ -1,55 +0,0 @@
from typing import Literal, NamedTuple, TypedDict, overload
from urllib.parse import urlencode
from nonebot.compat import type_validate_json
from ....utils.exception import RequestError
from ....utils.request import splice_url
from ..constant import BASE_URL
from .cache import Cache
from .schemas.base import FailedModel
from .schemas.tetra_league import TetraLeague, TetraLeagueSuccess
class Parameter(TypedDict, total=False):
after: float
before: float
limit: int
country: str
async def leaderboard(parameter: Parameter | None = None) -> TetraLeagueSuccess:
league: TetraLeague = type_validate_json(
TetraLeague, # type: ignore[arg-type]
(await Cache.get(splice_url([BASE_URL, 'users/lists/league', f'?{urlencode(parameter or {})}']))),
)
if isinstance(league, FailedModel):
msg = f'排行榜数据请求错误:\n{league.error}'
raise RequestError(msg)
return league
class FullExport(NamedTuple):
model: TetraLeagueSuccess
original: bytes
@overload
async def full_export(*, with_original: Literal[False]) -> TetraLeagueSuccess: ...
@overload
async def full_export(*, with_original: Literal[True]) -> FullExport: ...
async def full_export(*, with_original: bool) -> TetraLeagueSuccess | FullExport:
full: TetraLeague = type_validate_json(
TetraLeague, # type: ignore[arg-type]
(data := await Cache.get(splice_url([BASE_URL, 'users/lists/league/all']))),
)
if isinstance(full, FailedModel):
msg = f'排行榜数据请求错误:\n{full.error}'
raise RequestError(msg)
if with_original:
return FullExport(full, data)
return full

View File

@@ -1,6 +1,6 @@
from typing import Literal
from typing import Literal, NewType
ValidRank = Literal[
S1ValidRank = Literal[
'x',
'u',
'ss',
@@ -19,7 +19,9 @@ ValidRank = Literal[
'd+',
'd',
]
S1Rank = S1ValidRank | Literal['z']
ValidRank = Literal['x+'] | S1ValidRank
Rank = ValidRank | Literal['z'] # 未定级
Summaries = Literal[
@@ -27,7 +29,7 @@ Summaries = Literal[
'blitz',
'zenith',
'zenithex',
# 'league', # 等待正式赛季开始
'league',
'zen',
'achievements',
]
@@ -40,3 +42,5 @@ Records = Literal[
'blitz_recent',
'blitz_progression',
]
Prisecter = NewType('Prisecter', str)

View File

@@ -1,21 +1,25 @@
from asyncio import gather
from hashlib import md5
from urllib.parse import urlencode
from secrets import choice
from arclet.alconna import Arg, ArgFlag
from nonebot_plugin_alconna import Args, Subcommand
from nonebot_plugin_alconna.uniseg import UniMessage
from nonebot_plugin_orm import get_session
from nonebot_plugin_session import EventSession
from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[import-untyped]
from nonebot_plugin_uninfo import QryItrface, Uninfo
from nonebot_plugin_uninfo import User as UninfoUser
from nonebot_plugin_uninfo.orm import get_session_persist_id
from nonebot_plugin_user import User
from nonebot_plugin_userinfo import BotUserInfo, UserInfo
from yarl import URL
from ...config.config import global_config
from ...db import BindStatus, create_or_update_bind, trigger
from ...utils.host import HostPage, get_self_netloc
from ...utils.host import get_self_netloc
from ...utils.image import get_avatar
from ...utils.render import Bind, render
from ...utils.lang import get_lang
from ...utils.render import render_image
from ...utils.render.schemas.base import Avatar, People
from ...utils.screenshot import screenshot
from ...utils.render.schemas.bind import Bind
from . import alc, command, get_player
from .api import Player
from .constant import GAME_TYPE
@@ -41,9 +45,45 @@ alc.shortcut(
humanized='io绑定',
)
try:
from nonebot.adapters.discord import MessageCreateEvent
@alc.assign('TETRIO.bind')
async def _(_: MessageCreateEvent, nb_user: User, account: Player, event_session: Uninfo, interface: QryItrface):
async with trigger(
session_persist_id=await get_session_persist_id(event_session),
game_platform=GAME_TYPE,
command_type='bind',
command_args=[],
):
user, user_info = await gather(account.user, account.get_info())
verify = (
user_info.data.connections.discord is not None
and user_info.data.connections.discord.id == event_session.user.id
)
async with get_session() as session:
bind_status = await create_or_update_bind(
session=session,
user=nb_user,
game_platform=GAME_TYPE,
game_account=user.unique_identifier,
verify=verify,
)
if bind_status in (BindStatus.SUCCESS, BindStatus.UPDATE):
await UniMessage.image(
raw=await make_bind_image(
player=account,
event_session=event_session,
interface=interface,
verify=verify,
)
).finish()
except ImportError:
pass
@alc.assign('TETRIO.bind')
async def _(nb_user: User, account: Player, event_session: EventSession, bot_info: UserInfo = BotUserInfo()): # noqa: B008
async def _(nb_user: User, account: Player, event_session: Uninfo, interface: QryItrface):
async with trigger(
session_persist_id=await get_session_persist_id(event_session),
game_platform=GAME_TYPE,
@@ -59,25 +99,45 @@ async def _(nb_user: User, account: Player, event_session: EventSession, bot_inf
game_account=user.unique_identifier,
)
if bind_status in (BindStatus.SUCCESS, BindStatus.UPDATE):
netloc = get_self_netloc()
async with HostPage(
await render(
'v1/binding',
Bind(
platform='TETR.IO',
status='unknown',
user=People(
avatar=f'http://{netloc}/host/resource/tetrio/avatars/{user.ID}?{urlencode({"revision": avatar_revision})}'
if (avatar_revision := (await account.avatar_revision)) is not None and avatar_revision != 0
else Avatar(type='identicon', hash=md5(user.ID.encode()).hexdigest()), # noqa: S324
name=user.name.upper(),
),
bot=People(
avatar=await get_avatar(bot_info, 'Data URI', '../../static/logo/logo.svg'),
name=bot_info.user_name,
),
command='io查我',
),
await UniMessage.image(
raw=await make_bind_image(
player=account,
event_session=event_session,
interface=interface,
verify=None,
)
) as page_hash:
await UniMessage.image(raw=await screenshot(f'http://{netloc}/host/{page_hash}.html')).finish()
).finish()
async def make_bind_image(
player: Player, event_session: Uninfo, interface: QryItrface, *, verify: bool | None = None
) -> bytes:
(user, avatar_revision) = await gather(player.user, player.avatar_revision)
return await render_image(
Bind(
platform='TETR.IO',
type='unknown' if verify is None else 'success' if verify else 'unverified',
user=People(
avatar=str(
URL(f'http://{get_self_netloc()}/host/resource/tetrio/avatars/{user.ID}')
% {'revision': avatar_revision}
)
if avatar_revision is not None and avatar_revision != 0
else Avatar(type='identicon', hash=md5(user.ID.encode()).hexdigest()), # noqa: S324
name=user.name.upper(),
),
bot=People(
avatar=await get_avatar(
(
bot_user := await interface.get_user(event_session.self_id)
or UninfoUser(id=event_session.self_id)
),
'Data URI',
'../../static/logo/logo.svg',
),
name=bot_user.nick or bot_user.name or choice(list(global_config.nickname) or ['bot']),
),
prompt='io查我',
lang=get_lang(),
),
)

View File

@@ -2,8 +2,8 @@ from arclet.alconna import Arg
from nonebot_plugin_alconna import Option, Subcommand
from nonebot_plugin_alconna.uniseg import UniMessage
from nonebot_plugin_orm import async_scoped_session
from nonebot_plugin_session import EventSession
from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[import-untyped]
from nonebot_plugin_uninfo import Uninfo
from nonebot_plugin_uninfo.orm import get_session_persist_id
from nonebot_plugin_user import User
from sqlalchemy import select
@@ -11,7 +11,7 @@ from ...db import trigger
from . import alc, command
from .constant import GAME_TYPE
from .models import TETRIOUserConfig
from .typing import Template
from .typedefs import Template
command.add(
Subcommand(
@@ -34,7 +34,7 @@ alc.shortcut(
@alc.assign('TETRIO.config')
async def _(user: User, session: async_scoped_session, event_session: EventSession, template: Template):
async def _(user: User, session: async_scoped_session, event_session: Uninfo, template: Template):
async with trigger(
session_persist_id=await get_session_persist_id(event_session),
game_platform=GAME_TYPE,

View File

@@ -1,13 +1,16 @@
from re import compile
from re import compile # noqa: A004
from typing import Literal
from .api.typing import ValidRank
from yarl import URL
from .api.typedefs import ValidRank
GAME_TYPE: Literal['IO'] = 'IO'
BASE_URL = 'https://ch.tetr.io/api/'
BASE_URL = URL('https://ch.tetr.io/api/')
RANK_PERCENTILE: dict[ValidRank, float] = {
'x+': 0.2,
'x': 1,
'u': 5,
'ss': 11,

View File

@@ -0,0 +1,92 @@
from nonebot_plugin_alconna import Args, Option, Subcommand
from nonebot_plugin_alconna.uniseg import UniMessage
from nonebot_plugin_uninfo import Uninfo
from nonebot_plugin_uninfo.orm import get_session_persist_id
from ...db import trigger
from ...utils.lang import get_lang
from ...utils.metrics import get_metrics
from ...utils.render import render_image
from ...utils.render.schemas.v2.tetrio.user.list import Data, List, TetraLeague, User
from .. import alc
from . import command
from .api.leaderboards import by
from .api.schemas.base import P
from .api.schemas.leaderboards import Parameter
from .api.schemas.leaderboards.by import Entry
from .constant import GAME_TYPE
command.add(
Subcommand(
'list',
Option('--max-tr', Args['max_tr', float], help_text='TR的上限'),
Option('--min-tr', Args['min_tr', float], help_text='TR的下限'),
Option('--limit', Args['limit', int], help_text='查询数量'),
Option('--country', Args['country', str], help_text='国家代码'),
help_text='查询 TETR.IO 段位排行榜',
)
)
@alc.assign('TETRIO.list')
async def _(
event_session: Uninfo,
max_tr: float | None = None,
min_tr: float | None = None,
limit: int | None = None,
country: str | None = None,
):
country = country.upper() if country is not None else None
async with trigger(
session_persist_id=await get_session_persist_id(event_session),
game_platform=GAME_TYPE,
command_type='list',
command_args=[
f'{key} {value}'
for key, value in zip(
('--max-tr', '--min-tr', '--limit', '--country'), (max_tr, min_tr, limit, country), strict=True
)
if value is not None
],
):
parameter = Parameter(
# ?: 似乎是只需要 pri 至少 league 榜的返回值只有 pri
after=P(pri=max_tr, sec=0, ter=0).to_prisecter() if max_tr is not None else None,
before=P(pri=min_tr, sec=0, ter=0).to_prisecter() if min_tr is not None else None,
limit=limit or 25,
country=country,
)
league = await by('league', parameter)
await UniMessage.image(
raw=await render_image(
List(
show_index=True,
data=[
Data(
user=User(
id=i.id,
name=i.username.upper(),
avatar=f'https://tetr.io/user-content/avatars/{i.id}.jpg',
country=i.country,
xp=i.xp,
),
tetra_league=TetraLeague(
rank=i.league.rank,
tr=round(i.league.tr, 2),
glicko=round(i.league.glicko, 2),
rd=round(i.league.rd, 2),
decaying=i.league.decaying,
pps=(metrics := get_metrics(pps=i.league.pps, apm=i.league.apm, vs=i.league.vs)).pps,
apm=metrics.apm,
apl=metrics.apl,
vs=metrics.vs,
adpl=metrics.adpl,
),
)
for i in league.data.entries
if isinstance(i, Entry)
],
lang=get_lang(),
),
)
).finish()

View File

@@ -1,10 +1,61 @@
from nonebot_plugin_orm import Model
from sqlalchemy import String
from sqlalchemy.orm import Mapped, MappedAsDataclass, mapped_column
from datetime import datetime
from uuid import UUID
from .typing import Template
from nonebot_plugin_orm import Model
from sqlalchemy import DateTime, ForeignKey, String
from sqlalchemy.orm import Mapped, MappedAsDataclass, mapped_column, relationship
from ...db.models import PydanticType
from .api.schemas.leaderboards.by import BySuccessModel, Entry
from .api.typedefs import ValidRank
from .typedefs import Template
class TETRIOUserConfig(MappedAsDataclass, Model):
__tablename__ = 'nb_t_io_u_cfg'
id: Mapped[int] = mapped_column(primary_key=True)
query_template: Mapped[Template] = mapped_column(String(2))
class TETRIOLeagueStats(MappedAsDataclass, Model):
__tablename__ = 'nb_t_io_tl_stats'
id: Mapped[int] = mapped_column(init=False, primary_key=True)
raw: Mapped[list['TETRIOLeagueHistorical']] = relationship(back_populates='stats', lazy='noload')
fields: Mapped[list['TETRIOLeagueStatsField']] = relationship(back_populates='stats')
update_time: Mapped[datetime] = mapped_column(DateTime, index=True)
class TETRIOLeagueHistorical(MappedAsDataclass, Model):
__tablename__ = 'nb_t_io_tl_hist'
id: Mapped[int] = mapped_column(init=False, primary_key=True)
request_id: Mapped[UUID] = mapped_column(index=True)
data: Mapped[BySuccessModel] = mapped_column(PydanticType([], {BySuccessModel}))
update_time: Mapped[datetime] = mapped_column(DateTime, index=True)
stats_id: Mapped[int] = mapped_column(ForeignKey('nb_t_io_tl_stats.id'), init=False)
stats: Mapped['TETRIOLeagueStats'] = relationship(back_populates='raw')
entry_type = PydanticType([], {Entry})
class TETRIOLeagueStatsField(MappedAsDataclass, Model):
__tablename__ = 'nb_t_io_tl_stats_field'
id: Mapped[int] = mapped_column(init=False, primary_key=True)
rank: Mapped[ValidRank] = mapped_column(String(2), index=True)
tr_line: Mapped[float]
player_count: Mapped[int]
low_pps: Mapped[Entry] = mapped_column(entry_type)
low_apm: Mapped[Entry] = mapped_column(entry_type)
low_vs: Mapped[Entry] = mapped_column(entry_type)
avg_pps: Mapped[float]
avg_apm: Mapped[float]
avg_vs: Mapped[float]
high_pps: Mapped[Entry] = mapped_column(entry_type)
high_apm: Mapped[Entry] = mapped_column(entry_type)
high_vs: Mapped[Entry] = mapped_column(entry_type)
stats_id: Mapped[int] = mapped_column(ForeignKey('nb_t_io_tl_stats.id'), init=False)
stats: Mapped['TETRIOLeagueStats'] = relationship(back_populates='fields')

View File

@@ -1,241 +0,0 @@
from asyncio import gather
from datetime import datetime, timedelta, timezone
from hashlib import md5
from typing import TYPE_CHECKING, TypeVar
from urllib.parse import urlencode
from arclet.alconna import Arg, ArgFlag
from nonebot import get_driver
from nonebot.adapters import Event
from nonebot.matcher import Matcher
from nonebot_plugin_alconna import Args, At, Option, Subcommand
from nonebot_plugin_alconna.uniseg import UniMessage
from nonebot_plugin_orm import get_session
from nonebot_plugin_session import EventSession
from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[import-untyped]
from nonebot_plugin_user import User as NBUser
from nonebot_plugin_user import get_user
from sqlalchemy import select
from ...db import query_bind_info, trigger
from ...utils.host import HostPage, get_self_netloc
from ...utils.render import render
from ...utils.render.schemas.base import Avatar
from ...utils.render.schemas.tetrio.user.info_v2 import Badge, Blitz, Sprint, Statistic, Zen
from ...utils.render.schemas.tetrio.user.info_v2 import Info as V2TemplateInfo
from ...utils.render.schemas.tetrio.user.info_v2 import User as V2TemplateUser
from ...utils.screenshot import screenshot
from ...utils.typing import Me
from .. import add_block_handlers, alc
from ..constant import CANT_VERIFY_MESSAGE
from . import command, get_player
from .api import Player
from .constant import GAME_TYPE
from .models import TETRIOUserConfig
from .typing import Template
if TYPE_CHECKING:
from .api.schemas.summaries import SoloSuccessModel, ZenSuccessModel
from .api.schemas.user import User
from .api.schemas.user_info import UserInfoSuccess
UTC = timezone.utc
driver = get_driver()
command.add(
Subcommand(
'query',
Args(
Arg(
'target',
At | Me,
notice='@想要查询的人 / 自己',
flags=[ArgFlag.HIDDEN, ArgFlag.OPTIONAL],
),
Arg(
'account',
get_player,
notice='TETR.IO 用户名 / ID',
flags=[ArgFlag.HIDDEN, ArgFlag.OPTIONAL],
),
),
Option(
'--template',
Arg('template', Template),
alias=['-T'],
help_text='要使用的查询模板',
),
help_text='查询 TETR.IO 游戏信息',
),
)
alc.shortcut(
'(?i:io)(?i:查询|查|query|stats)',
command='tstats TETR.IO query',
humanized='io查',
)
alc.shortcut(
'fkosk',
command='tstats TETR.IO query',
arguments=[''],
fuzzy=False,
humanized='An Easter egg!',
)
add_block_handlers(alc.assign('TETRIO.query'))
@alc.assign('TETRIO.query')
async def _( # noqa: PLR0913
user: NBUser,
event: Event,
matcher: Matcher,
target: At | Me,
event_session: EventSession,
template: Template | None = None,
):
async with trigger(
session_persist_id=await get_session_persist_id(event_session),
game_platform=GAME_TYPE,
command_type='query',
command_args=[f'--default-template {template}'] if template is not None else [],
):
async with get_session() as session:
bind = await query_bind_info(
session=session,
user=await get_user(
event_session.platform, target.target if isinstance(target, At) else event.get_user_id()
),
game_platform=GAME_TYPE,
)
if template is None:
template = await session.scalar(
select(TETRIOUserConfig.query_template).where(TETRIOUserConfig.id == user.id)
)
if bind is None:
await matcher.finish('未查询到绑定信息')
message = UniMessage(CANT_VERIFY_MESSAGE)
player = Player(user_id=bind.game_account, trust=True)
await (message + UniMessage.image(raw=await make_query_image_v2(player))).finish()
@alc.assign('TETRIO.query')
async def _(user: NBUser, account: Player, event_session: EventSession, template: Template | None = None):
async with trigger(
session_persist_id=await get_session_persist_id(event_session),
game_platform=GAME_TYPE,
command_type='query',
command_args=[f'--default-template {template}'] if template is not None else [],
):
async with get_session() as session:
if template is None:
template = await session.scalar(
select(TETRIOUserConfig.query_template).where(TETRIOUserConfig.id == user.id)
)
await (UniMessage.image(raw=await make_query_image_v2(account))).finish()
N = TypeVar('N', int, float)
def handling_special_value(value: N) -> N | None:
return value if value != -1 else None
async def make_query_image_v2(player: Player) -> bytes:
user: User
user_info: UserInfoSuccess
sprint: SoloSuccessModel
blitz: SoloSuccessModel
zen: ZenSuccessModel
avatar_revision: int | None
banner_revision: int | None
# TODO)) 有没有什么办法能让这类型推导成功)
user, user_info, sprint, blitz, zen, avatar_revision, banner_revision = await gather( # type: ignore[assignment]
player.user,
player.get_info(),
player.sprint,
player.blitz,
player.zen,
player.avatar_revision,
player.banner_revision,
)
if sprint.data.record is not None:
duration = timedelta(milliseconds=sprint.data.record.results.stats.finaltime).total_seconds()
sprint_value = f'{duration:.3f}s' if duration < 60 else f'{duration // 60:.0f}m {duration % 60:.3f}s' # noqa: PLR2004
else:
sprint_value = 'N/A'
play_time: str | None
if (game_time := handling_special_value(user_info.data.gametime)) is not None:
if game_time // 3600 > 0:
play_time = f'{game_time//3600:.0f}h {game_time % 3600 // 60:.0f}m {game_time % 60:.0f}s'
elif game_time // 60 > 0:
play_time = f'{game_time//60:.0f}m {game_time % 60:.0f}s'
else:
play_time = f'{game_time:.0f}s'
else:
play_time = game_time
netloc = get_self_netloc()
async with HostPage(
await render(
'v2/tetrio/user/info',
V2TemplateInfo(
user=V2TemplateUser(
id=user.ID,
name=user.name.upper(),
bio=user_info.data.bio,
banner=f'http://{netloc}/host/resource/tetrio/banners/{user.ID}?{urlencode({"revision": banner_revision})}'
if banner_revision is not None and banner_revision != 0
else None,
avatar=f'http://{netloc}/host/resource/tetrio/avatars/{user.ID}?{urlencode({"revision": avatar_revision})}'
if avatar_revision is not None and avatar_revision != 0
else Avatar(
type='identicon',
hash=md5(user.ID.encode()).hexdigest(), # noqa: S324
),
badges=[
Badge(
id=i.id,
description=i.label,
group=i.group,
receive_at=i.ts if isinstance(i.ts, datetime) else None,
)
for i in user_info.data.badges
],
country=user_info.data.country,
role=user_info.data.role,
xp=user_info.data.xp,
friend_count=user_info.data.friend_count,
supporter_tier=user_info.data.supporter_tier,
bad_standing=user_info.data.badstanding or False,
verified=user_info.data.verified,
playtime=play_time,
join_at=user_info.data.ts,
),
tetra_league=None,
statistic=Statistic(
total=handling_special_value(user_info.data.gamesplayed),
wins=handling_special_value(user_info.data.gameswon),
),
sprint=Sprint(
time=sprint_value,
global_rank=sprint.data.rank,
play_at=sprint.data.record.ts,
)
if sprint.data.record is not None
else None,
blitz=Blitz(
score=blitz.data.record.results.stats.score,
global_rank=blitz.data.rank,
play_at=blitz.data.record.ts,
)
if blitz.data.record is not None
else None,
zen=Zen(level=zen.data.level, score=zen.data.score),
),
),
) as page_hash:
return await screenshot(f'http://{netloc}/host/{page_hash}.html')

View File

@@ -0,0 +1,135 @@
from datetime import timezone
from arclet.alconna import Arg, ArgFlag
from nonebot import get_driver
from nonebot.adapters import Event
from nonebot.matcher import Matcher
from nonebot_plugin_alconna import Args, At, Option, Subcommand
from nonebot_plugin_alconna.uniseg import UniMessage
from nonebot_plugin_orm import get_session
from nonebot_plugin_uninfo import Uninfo
from nonebot_plugin_uninfo.orm import get_session_persist_id
from nonebot_plugin_user import User as NBUser
from nonebot_plugin_user import get_user
from sqlalchemy import select
from ....db import query_bind_info, trigger
from ....i18n import Lang
from ....utils.exception import FallbackError
from ....utils.typedefs import Me
from ... import add_block_handlers, alc
from .. import command, get_player
from ..api import Player
from ..constant import GAME_TYPE
from ..models import TETRIOUserConfig
from ..typedefs import Template
from .v1 import make_query_image_v1
from .v2 import make_query_image_v2
UTC = timezone.utc
driver = get_driver()
command.add(
Subcommand(
'query',
Args(
Arg(
'target',
At | Me,
notice='@想要查询的人 / 自己',
flags=[ArgFlag.HIDDEN, ArgFlag.OPTIONAL],
),
Arg(
'account',
get_player,
notice='TETR.IO 用户名 / ID',
flags=[ArgFlag.HIDDEN, ArgFlag.OPTIONAL],
),
),
Option(
'--template',
Arg('template', Template),
alias=['-T'],
help_text='要使用的查询模板',
),
help_text='查询 TETR.IO 游戏信息',
),
)
alc.shortcut(
'(?i:io)(?i:查询|查|query|stats)',
command='tstats TETR.IO query',
humanized='io查',
)
alc.shortcut(
'fkosk',
command='tstats TETR.IO query',
arguments=[''],
fuzzy=False,
humanized='An Easter egg!',
)
add_block_handlers(alc.assign('TETRIO.query'))
async def make_query_result(player: Player, template: Template) -> UniMessage:
if template == 'v1':
try:
return UniMessage.image(raw=await make_query_image_v1(player))
except FallbackError:
template = 'v2'
if template == 'v2':
return UniMessage.image(raw=await make_query_image_v2(player))
return None
@alc.assign('TETRIO.query')
async def _( # noqa: PLR0913
user: NBUser,
event: Event,
matcher: Matcher,
target: At | Me,
event_session: Uninfo,
template: Template | None = None,
):
async with trigger(
session_persist_id=await get_session_persist_id(event_session),
game_platform=GAME_TYPE,
command_type='query',
command_args=[f'--template {template}'] if template is not None else [],
):
async with get_session() as session:
bind = await query_bind_info(
session=session,
user=await get_user(
event_session.scope, target.target if isinstance(target, At) else event.get_user_id()
),
game_platform=GAME_TYPE,
)
if template is None:
template = await session.scalar(
select(TETRIOUserConfig.query_template).where(TETRIOUserConfig.id == user.id)
)
if bind is None:
await matcher.finish('未查询到绑定信息')
player = Player(user_id=bind.game_account, trust=True)
await (
UniMessage.i18n(Lang.interaction.warning.unverified) + await make_query_result(player, template or 'v1')
).finish()
@alc.assign('TETRIO.query')
async def _(user: NBUser, account: Player, event_session: Uninfo, template: Template | None = None):
async with trigger(
session_persist_id=await get_session_persist_id(event_session),
game_platform=GAME_TYPE,
command_type='query',
command_args=[f'--template {template}'] if template is not None else [],
):
async with get_session() as session:
if template is None:
template = await session.scalar(
select(TETRIOUserConfig.query_template).where(TETRIOUserConfig.id == user.id)
)
await (await make_query_result(account, template or 'v1')).finish()

View File

@@ -0,0 +1,56 @@
from collections.abc import Callable
from datetime import timedelta
from typing import TypeVar, overload
from zoneinfo import ZoneInfo
from ....utils.exception import FallbackError
from ....utils.render.schemas.base import HistoryData
from ..api.schemas.labs.leagueflow import Empty, LeagueFlowSuccess
from ..api.schemas.summaries.league import InvalidData, LeagueSuccessModel, NeverPlayedData, NeverRatedData, RatedData
def flow_to_history(
leagueflow: LeagueFlowSuccess,
handle: Callable[[list[HistoryData]], list[HistoryData]] | None = None,
) -> list[HistoryData]:
if isinstance(leagueflow.data, Empty):
raise FallbackError
start_time = leagueflow.data.start_time.astimezone(ZoneInfo('Asia/Shanghai'))
ret = [
HistoryData(
record_at=start_time + timedelta(milliseconds=i.timestamp_offset),
score=i.post_match_tr,
)
for i in leagueflow.data.points
if start_time + timedelta(milliseconds=i.timestamp_offset)
]
return ret if handle is None else handle(ret)
N = TypeVar('N', int, float)
def handling_special_value(value: N) -> N | None:
return value if value != -1 else None
L = TypeVar('L', NeverPlayedData, NeverRatedData, RatedData)
@overload
def get_league_data(user_info: LeagueSuccessModel, league_type: type[L]) -> L: ...
@overload
def get_league_data(
user_info: LeagueSuccessModel, league_type: None = None
) -> NeverPlayedData | NeverRatedData | RatedData: ...
def get_league_data(
user_info: LeagueSuccessModel, league_type: type[L] | None = None
) -> L | NeverPlayedData | NeverRatedData | RatedData:
league = user_info.data
if isinstance(league, InvalidData):
raise FallbackError
if league_type is None:
return league
if isinstance(league, league_type):
return league
raise FallbackError

View File

@@ -0,0 +1,95 @@
from asyncio import gather
from datetime import timedelta
from hashlib import md5
from yarl import URL
from ....utils.chart import get_split, get_value_bounds, handle_history_data
from ....utils.exception import FallbackError
from ....utils.host import get_self_netloc
from ....utils.lang import get_lang
from ....utils.metrics import get_metrics
from ....utils.render import render_image
from ....utils.render.schemas.base import Avatar, Trending
from ....utils.render.schemas.v1.base import History
from ....utils.render.schemas.v1.tetrio.info import Info, Multiplayer, Singleplayer, User
from ..api import Player
from ..api.schemas.summaries.league import RatedData
from ..constant import TR_MAX, TR_MIN
from .tools import flow_to_history, get_league_data
async def make_query_image_v1(player: Player) -> bytes:
(
(user, user_info, league, sprint, blitz, leagueflow),
(avatar_revision,),
) = await gather(
gather(player.user, player.get_info(), player.league, player.sprint, player.blitz, player.get_leagueflow()),
gather(player.avatar_revision),
)
league_data = get_league_data(league, RatedData)
if league_data.vs is None:
raise FallbackError
histories = flow_to_history(leagueflow, handle_history_data)
values = get_value_bounds([i.score for i in histories])
split_value, offset = get_split(values, TR_MAX, TR_MIN)
if sprint.data.record is not None:
duration = timedelta(milliseconds=sprint.data.record.results.stats.finaltime).total_seconds()
sprint_value = f'{duration:.3f}s' if duration < 60 else f'{duration // 60:.0f}m {duration % 60:.3f}s' # noqa: PLR2004
else:
sprint_value = 'N/A'
blitz_value = f'{blitz.data.record.results.stats.score:,}' if blitz.data.record is not None else 'N/A'
dsps: float
dspp: float
# make mypy happy
return await render_image(
Info(
user=User(
avatar=str(
URL(f'http://{get_self_netloc()}/host/resource/tetrio/avatars/{user.ID}')
% {'revision': avatar_revision}
)
if avatar_revision is not None and avatar_revision != 0
else Avatar(
type='identicon',
hash=md5(user.ID.encode()).hexdigest(), # noqa: S324
),
name=user.name.upper(),
bio=user_info.data.bio,
),
multiplayer=Multiplayer(
glicko=f'{round(league_data.glicko, 2):,}',
rd=round(league_data.rd, 2),
rank=league_data.rank,
tr=f'{round(league_data.tr, 2):,}',
global_rank=league_data.standing,
history=History(
data=histories,
split_interval=split_value,
min_value=values.value_min,
max_value=values.value_max,
offset=offset,
),
lpm=(metrics := get_metrics(pps=league_data.pps, apm=league_data.apm, vs=league_data.vs)).lpm,
pps=metrics.pps,
lpm_trending=Trending.KEEP,
apm=metrics.apm,
apl=metrics.apl,
apm_trending=Trending.KEEP,
adpm=metrics.adpm,
vs=metrics.vs,
adpl=metrics.adpl,
adpm_trending=Trending.KEEP,
app=(app := (league_data.apm / (60 * league_data.pps))),
dsps=(dsps := ((league_data.vs / 100) - (league_data.apm / 60))),
dspp=(dspp := (dsps / league_data.pps)),
ci=150 * dspp - 125 * app + 50 * (league_data.vs / league_data.apm) - 25,
ge=2 * ((app * dsps) / league_data.pps),
),
singleplayer=Singleplayer(
sprint=sprint_value,
blitz=blitz_value,
),
lang=get_lang(),
),
)

View File

@@ -0,0 +1,205 @@
from asyncio import gather
from datetime import datetime, timedelta
from hashlib import md5
from yarl import URL
from ....utils.exception import FallbackError
from ....utils.host import get_self_netloc
from ....utils.lang import get_lang
from ....utils.metrics import get_metrics
from ....utils.render import render_image
from ....utils.render.schemas.base import Avatar
from ....utils.render.schemas.v2.tetrio.user.info import (
Achievement,
Badge,
Best,
Blitz,
Info,
Sprint,
Statistic,
TetraLeague,
TetraLeagueStatistic,
User,
Week,
Zen,
Zenith,
)
from ..api import Player
from ..api.schemas.summaries.league import InvalidData, NeverPlayedData, NeverRatedData
from .tools import flow_to_history, handling_special_value
async def make_query_image_v2(player: Player) -> bytes:
(
(user, user_info, league, sprint, blitz, zen),
(avatar_revision, banner_revision, leagueflow, zenith, zenithex, achievements),
) = await gather(
gather(
player.user,
player.get_info(),
player.league,
player.sprint,
player.blitz,
player.zen,
),
gather(
player.avatar_revision,
player.banner_revision,
player.get_leagueflow(),
player.get_summaries('zenith'),
player.get_summaries('zenithex'),
player.get_summaries('achievements'),
),
)
if sprint.data.record is not None:
duration = timedelta(milliseconds=sprint.data.record.results.stats.finaltime).total_seconds()
sprint_value = f'{duration:.3f}s' if duration < 60 else f'{duration // 60:.0f}m {duration % 60:.3f}s' # noqa: PLR2004
else:
sprint_value = 'N/A'
play_time: str | None
if (game_time := handling_special_value(user_info.data.gametime)) is not None:
if game_time // 3600 > 0:
play_time = f'{game_time // 3600:.0f}h {game_time % 3600 // 60:.0f}m {game_time % 60:.0f}s'
elif game_time // 60 > 0:
play_time = f'{game_time // 60:.0f}m {game_time % 60:.0f}s'
else:
play_time = f'{game_time:.0f}s'
else:
play_time = game_time
try:
history = flow_to_history(leagueflow, lambda x: x[-100:])
except FallbackError:
history = None
netloc = get_self_netloc()
return await render_image(
Info(
user=User(
id=user.ID,
name=user.name.upper(),
country=user_info.data.country,
role=user_info.data.role,
botmaster=user_info.data.botmaster,
avatar=str(
URL(f'http://{netloc}/host/resource/tetrio/avatars/{user.ID}') % {'revision': avatar_revision}
)
if avatar_revision is not None and avatar_revision != 0
else Avatar(
type='identicon',
hash=md5(user.ID.encode()).hexdigest(), # noqa: S324
),
banner=str(
URL(f'http://{netloc}/host/resource/tetrio/banners/{user.ID}') % {'revision': banner_revision}
)
if banner_revision is not None and banner_revision != 0
else None,
bio=user_info.data.bio,
friend_count=user_info.data.friend_count,
supporter_tier=user_info.data.supporter_tier,
bad_standing=user_info.data.badstanding or False,
badges=[
Badge(
id=i.id,
description=i.label,
group=i.group,
receive_at=i.ts if isinstance(i.ts, datetime) else None,
)
for i in user_info.data.badges
],
xp=user_info.data.xp,
ar=user_info.data.ar,
achievements=[
Achievement(
key=i.achievement_id,
rank_type=i.rank_type,
ar_type=i.ar_type,
stub=i.stub,
rank=i.rank,
achieved_score=i.achieved_score,
pos=i.pos,
progress=i.progress,
total=i.total,
)
for i in achievements.data
],
playtime=play_time,
join_at=user_info.data.ts,
),
tetra_league=TetraLeague(
rank=league.data.rank,
highest_rank='z' if isinstance(league.data, NeverRatedData) else league.data.bestrank,
tr=round(league.data.tr, 2),
glicko=round(league.data.glicko, 2),
rd=round(league.data.rd, 2),
global_rank=league.data.standing,
country_rank=league.data.standing_local,
pps=(metrics := get_metrics(pps=league.data.pps, apm=league.data.apm, vs=league.data.vs)).pps,
apm=metrics.apm,
apl=metrics.apl,
vs=metrics.vs,
adpl=metrics.adpl,
statistic=TetraLeagueStatistic(total=league.data.gamesplayed, wins=league.data.gameswon),
decaying=league.data.decaying,
history=history,
)
if not isinstance(league.data, NeverPlayedData | InvalidData)
else None,
zenith=Zenith(
week=Week(
altitude=zenith.data.record.results.stats.zenith.altitude,
global_rank=zenith.data.rank,
country_rank=zenith.data.rank_local,
play_at=zenith.data.record.ts,
)
if zenith.data.record is not None
else None,
best=Best(
altitude=zenith.data.best.record.results.stats.zenith.altitude,
global_rank=zenith.data.best.rank,
play_at=zenith.data.best.record.ts,
)
if zenith.data.best.record is not None
else None,
),
zenithex=Zenith(
week=Week(
altitude=zenithex.data.record.results.stats.zenith.altitude,
global_rank=zenithex.data.rank,
country_rank=zenithex.data.rank_local,
play_at=zenithex.data.record.ts,
)
if zenithex.data.record is not None
else None,
best=Best(
altitude=zenithex.data.best.record.results.stats.zenith.altitude,
global_rank=zenithex.data.best.rank,
play_at=zenithex.data.best.record.ts,
)
if zenithex.data.best.record is not None
else None,
),
statistic=Statistic(
total=handling_special_value(user_info.data.gamesplayed),
wins=handling_special_value(user_info.data.gameswon),
),
sprint=Sprint(
time=sprint_value,
global_rank=sprint.data.rank,
country_rank=sprint.data.rank_local,
play_at=sprint.data.record.ts,
)
if sprint.data.record is not None
else None,
blitz=Blitz(
score=blitz.data.record.results.stats.score,
global_rank=blitz.data.rank,
country_rank=blitz.data.rank_local,
play_at=blitz.data.record.ts,
)
if blitz.data.record is not None
else None,
zen=Zen(level=zen.data.level, score=zen.data.score),
lang=get_lang(),
),
)

View File

@@ -0,0 +1,156 @@
from collections import defaultdict
from collections.abc import Callable, Sequence
from datetime import datetime, timedelta, timezone
from math import floor
from statistics import mean
from typing import TYPE_CHECKING
from uuid import uuid4
from nonebot import get_driver
from nonebot_plugin_alconna import Subcommand
from nonebot_plugin_apscheduler import scheduler
from nonebot_plugin_orm import get_session
from sqlalchemy import select
from ....config.config import config
from ....utils.exception import RequestError
from ....utils.retry import retry
from .. import alc
from .. import command as base_command
from ..api.leaderboards import by
from ..api.schemas.base import P
from ..api.schemas.leaderboards import Parameter
from ..api.schemas.leaderboards.by import Entry
from ..constant import RANK_PERCENTILE
from ..models import TETRIOLeagueHistorical, TETRIOLeagueStats, TETRIOLeagueStatsField
if TYPE_CHECKING:
from ..api.schemas.leaderboards.by import BySuccessModel
from ..api.typedefs import Rank
UTC = timezone.utc
driver = get_driver()
command = Subcommand('rank', help_text='查询 TETR.IO 段位信息')
def wrapper(slot: int | str, content: str | None) -> str | None:
if slot == 'rank' and not content:
return '--all'
if content is not None:
return f'--detail {content.lower()}'
return content
alc.shortcut(
r'(?i:io)(?i:段位|段|rank)\s*(?P<rank>[a-zA-Z+-]{0,2})',
command='tstats TETR.IO rank {rank}',
humanized='iorank',
fuzzy=False,
wrapper=wrapper,
)
def _pps(user: Entry) -> float:
return user.league.pps
def _apm(user: Entry) -> float:
return user.league.apm
def _vs(user: Entry) -> float:
return user.league.vs
def _min(users: Sequence[Entry], field: Callable[[Entry], float]) -> Entry:
return min(users, key=field)
def _max(users: Sequence[Entry], field: Callable[[Entry], float]) -> Entry:
return max(users, key=field)
def find_special_player(
users: Sequence[Entry],
field: Callable[[Entry], float],
sort: Callable[[Sequence[Entry], Callable[[Entry], float]], Entry],
) -> Entry:
return sort(users, field)
@scheduler.scheduled_job('cron', hour='0,6,12,18', minute=0)
async def get_tetra_league_data() -> None:
x_session_id = uuid4()
retry_by = retry(max_attempts=10, exception_type=RequestError)(by)
prisecter = P(pri=9007199254740991, sec=9007199254740991, ter=9007199254740991) # * from ch.tetr.io
results: list[BySuccessModel] = []
while True:
model = await retry_by('league', Parameter(after=prisecter.to_prisecter(), limit=100), x_session_id)
prisecter = model.data.entries[-1].p
results.append(model)
if len(model.data.entries) < 100: # 分页值 # noqa: PLR2004
break
players: list[Entry] = []
for result in results:
players.extend([i for i in result.data.entries if isinstance(i, Entry)])
players.sort(key=lambda x: x.league.tr, reverse=True)
rank_player_mapping: defaultdict[Rank, list[Entry]] = defaultdict(list)
for player in players:
rank_player_mapping[player.league.rank].append(player)
stats = TETRIOLeagueStats(raw=[], fields=[], update_time=datetime.now(UTC))
fields: list[TETRIOLeagueStatsField] = []
for rank, percentile in RANK_PERCENTILE.items():
offset = floor((percentile / 100) * len(players)) - 1
tr_line = players[offset].league.tr
rank_players = rank_player_mapping[rank]
fields.append(
TETRIOLeagueStatsField(
rank=rank,
tr_line=tr_line,
player_count=len(rank_players),
low_pps=find_special_player(rank_players, _pps, _min),
low_apm=find_special_player(rank_players, _apm, _min),
low_vs=find_special_player(rank_players, _vs, _min),
avg_pps=mean(_pps(i) for i in rank_players),
avg_apm=mean(_apm(i) for i in rank_players),
avg_vs=mean(_vs(i) for i in rank_players),
high_pps=find_special_player(rank_players, _pps, _max),
high_apm=find_special_player(rank_players, _apm, _max),
high_vs=find_special_player(rank_players, _vs, _max),
stats=stats,
)
)
historicals = [
TETRIOLeagueHistorical(request_id=x_session_id, data=model, update_time=model.cache.cached_at, stats=stats)
for model in results
]
stats.raw = historicals
stats.fields = fields
async with get_session() as session:
session.add(stats)
await session.commit()
if not config.tetris.dev.enabled:
@driver.on_startup
async def _() -> None:
async with get_session() as session:
latest_time = await session.scalar(
select(TETRIOLeagueStats.update_time).order_by(TETRIOLeagueStats.id.desc()).limit(1)
)
if latest_time is None or datetime.now(tz=UTC) - latest_time.replace(tzinfo=UTC) > timedelta(hours=6):
await get_tetra_league_data()
from . import all, detail # noqa: A004, E402
base_command.add(command)
__all__ = ['all', 'detail']

View File

@@ -0,0 +1,132 @@
from datetime import timedelta
from arclet.alconna import Arg
from nonebot_plugin_alconna import Option, Subcommand, UniMessage
from nonebot_plugin_orm import get_session
from nonebot_plugin_uninfo import Uninfo
from nonebot_plugin_uninfo.orm import get_session_persist_id
from sqlalchemy import select
from sqlalchemy.orm import selectinload
from ....db import trigger
from ....utils.lang import get_lang
from ....utils.metrics import get_metrics
from ....utils.render import render_image
from ....utils.render.schemas.v1.tetrio.rank import Data as DataV1
from ....utils.render.schemas.v1.tetrio.rank import ItemData as ItemDataV1
from ....utils.render.schemas.v2.tetrio.rank import AverageData as AverageDataV2
from ....utils.render.schemas.v2.tetrio.rank import Data as DataV2
from ....utils.render.schemas.v2.tetrio.rank import ItemData as ItemDataV2
from .. import alc
from ..constant import GAME_TYPE
from ..models import TETRIOLeagueStats
from ..typedefs import Template
from . import command
command.add(
Subcommand(
'--all', Option('--template', Arg('template', Template), alias=['-T'], help_text='要使用的查询模板'), dest='all'
)
)
@alc.assign('TETRIO.rank.all')
async def _(event_session: Uninfo, template: Template | None = None):
async with trigger(
session_persist_id=await get_session_persist_id(event_session),
game_platform=GAME_TYPE,
command_type='rank',
command_args=['--all'] + ([f'--template {template}'] if template is not None else []),
):
async with get_session() as session:
# 获取最新记录
latest_data = (
await session.scalars(
select(TETRIOLeagueStats)
.order_by(TETRIOLeagueStats.id.desc())
.limit(1)
.options(selectinload(TETRIOLeagueStats.fields))
)
).one()
# 计算目标时间点 (24小时前)
target_time = latest_data.update_time - timedelta(hours=24)
# 查询目标时间点之前的最近记录
before = (
await session.scalar(
select(TETRIOLeagueStats)
.where(TETRIOLeagueStats.update_time <= target_time)
.order_by(TETRIOLeagueStats.update_time.desc())
.limit(1)
.options(selectinload(TETRIOLeagueStats.fields))
)
or latest_data
)
# 查询目标时间点之后的最近记录
after = (
await session.scalar(
select(TETRIOLeagueStats)
.where(TETRIOLeagueStats.update_time >= target_time) # 使用 >= 避免间隙
.order_by(TETRIOLeagueStats.update_time.asc())
.limit(1)
.options(selectinload(TETRIOLeagueStats.fields))
)
or latest_data
)
# 确定最接近的记录
compare_data = (
before
if abs((target_time - before.update_time).total_seconds())
< abs((target_time - after.update_time).total_seconds())
else after
)
match template:
case 'v1' | None:
await UniMessage.image(raw=await make_image_v1(latest_data, compare_data)).finish()
case 'v2':
await UniMessage.image(raw=await make_image_v2(latest_data, compare_data)).finish()
async def make_image_v1(latest_data: TETRIOLeagueStats, compare_data: TETRIOLeagueStats) -> bytes:
return await render_image(
DataV1(
items={
i[0].rank: ItemDataV1(
trending=round(i[0].tr_line - i[1].tr_line, 2),
require_tr=round(i[0].tr_line, 2),
players=i[0].player_count,
)
for i in zip(latest_data.fields, compare_data.fields, strict=True)
},
updated_at=latest_data.update_time,
lang=get_lang(),
),
)
async def make_image_v2(latest_data: TETRIOLeagueStats, compare_data: TETRIOLeagueStats) -> bytes:
return await render_image(
DataV2(
items={
i[0].rank: ItemDataV2(
require_tr=round(i[0].tr_line, 2),
trending=round(i[0].tr_line - i[1].tr_line, 2),
average_data=AverageDataV2(
pps=(metrics := get_metrics(pps=i[0].avg_pps, apm=i[0].avg_apm, vs=i[0].avg_vs)).pps,
apm=metrics.apm,
apl=metrics.apl,
vs=metrics.vs,
adpl=metrics.adpl,
),
players=i[0].player_count,
)
for i in zip(latest_data.fields, compare_data.fields, strict=True)
},
updated_at=latest_data.update_time,
lang=get_lang(),
),
)

View File

@@ -0,0 +1,147 @@
from datetime import timedelta, timezone
from zoneinfo import ZoneInfo
from arclet.alconna import Arg
from nonebot import get_driver
from nonebot_plugin_alconna import Option, UniMessage
from nonebot_plugin_orm import get_session
from nonebot_plugin_uninfo import Uninfo
from nonebot_plugin_uninfo.orm import get_session_persist_id
from sqlalchemy import select
from sqlalchemy.orm import selectinload
from ....db import trigger
from ....utils.lang import get_lang
from ....utils.metrics import get_metrics
from ....utils.render import render_image
from ....utils.render.schemas.v2.tetrio.rank.detail import Data, SpecialData
from .. import alc
from ..api.typedefs import ValidRank
from ..constant import GAME_TYPE
from ..models import TETRIOLeagueStats
from . import command
UTC = timezone.utc
driver = get_driver()
command.add(Option('--detail', Arg('rank', ValidRank), alias=['-D']))
@alc.assign('TETRIO.rank')
async def _(rank: ValidRank, event_session: Uninfo):
async with trigger(
session_persist_id=await get_session_persist_id(event_session),
game_platform=GAME_TYPE,
command_type='rank',
command_args=[f'--detail {rank}'],
):
async with get_session() as session:
# 获取最新记录
latest_data = (
await session.scalars(
select(TETRIOLeagueStats)
.order_by(TETRIOLeagueStats.id.desc())
.limit(1)
.options(selectinload(TETRIOLeagueStats.fields))
)
).one()
# 计算目标时间点 (24小时前)
target_time = latest_data.update_time - timedelta(hours=24)
# 查询目标时间点之前的最近记录
before = (
await session.scalar(
select(TETRIOLeagueStats)
.where(TETRIOLeagueStats.update_time <= target_time)
.order_by(TETRIOLeagueStats.update_time.desc())
.limit(1)
.options(selectinload(TETRIOLeagueStats.fields))
)
or latest_data # 回退到最新记录
)
# 查询目标时间点之后的最近记录
after = (
await session.scalar(
select(TETRIOLeagueStats)
.where(TETRIOLeagueStats.update_time >= target_time)
.order_by(TETRIOLeagueStats.update_time.asc())
.limit(1)
.options(selectinload(TETRIOLeagueStats.fields))
)
or latest_data # 回退到最新记录
)
# 确定最接近的记录
compare_data = (
before
if abs((target_time - before.update_time).total_seconds())
< abs((target_time - after.update_time).total_seconds())
else after
)
await UniMessage.image(
raw=await make_image(
rank,
latest_data,
compare_data,
)
).finish()
async def make_image(rank: ValidRank, latest: TETRIOLeagueStats, compare: TETRIOLeagueStats) -> bytes:
latest_data = next(filter(lambda x: x.rank == rank, latest.fields))
compare_data = next(filter(lambda x: x.rank == rank, compare.fields))
avg = get_metrics(pps=latest_data.avg_pps, apm=latest_data.avg_apm, vs=latest_data.avg_vs)
low_pps = get_metrics(
pps=latest_data.low_pps.league.pps, apm=latest_data.low_pps.league.apm, vs=latest_data.low_pps.league.vs
)
low_apm = get_metrics(
pps=latest_data.low_apm.league.pps, apm=latest_data.low_apm.league.apm, vs=latest_data.low_apm.league.vs
)
low_vs = get_metrics(
pps=latest_data.low_vs.league.pps, apm=latest_data.low_vs.league.apm, vs=latest_data.low_vs.league.vs
)
max_pps = get_metrics(
pps=latest_data.high_pps.league.pps, apm=latest_data.high_pps.league.apm, vs=latest_data.high_pps.league.vs
)
max_apm = get_metrics(
pps=latest_data.high_apm.league.pps, apm=latest_data.high_apm.league.apm, vs=latest_data.high_apm.league.vs
)
max_vs = get_metrics(
pps=latest_data.high_vs.league.pps, apm=latest_data.high_vs.league.apm, vs=latest_data.high_vs.league.vs
)
return await render_image(
Data(
name=latest_data.rank,
trending=round(latest_data.tr_line - compare_data.tr_line, 2),
require_tr=round(latest_data.tr_line, 2),
players=latest_data.player_count,
minimum_data=SpecialData(
apm=low_apm.apm,
pps=low_pps.pps,
lpm=low_pps.lpm,
vs=low_vs.vs,
adpm=low_vs.adpm,
apm_holder=latest_data.low_apm.username.upper(),
pps_holder=latest_data.low_pps.username.upper(),
vs_holder=latest_data.low_vs.username.upper(),
),
average_data=SpecialData(
apm=avg.apm, pps=avg.pps, lpm=avg.lpm, vs=avg.vs, adpm=avg.adpm, apl=avg.apl, adpl=avg.adpl
),
maximum_data=SpecialData(
apm=max_apm.apm,
pps=max_pps.pps,
lpm=max_pps.lpm,
vs=max_vs.vs,
adpm=max_vs.adpm,
apm_holder=latest_data.high_apm.username.upper(),
pps_holder=latest_data.high_pps.username.upper(),
vs_holder=latest_data.high_vs.username.upper(),
),
updated_at=latest.update_time.replace(tzinfo=UTC).astimezone(ZoneInfo('Asia/Shanghai')),
lang=get_lang(),
),
)

View File

@@ -1,7 +1,7 @@
from arclet.alconna import Arg, ArgFlag
from nonebot_plugin_alconna import Args, At, Subcommand
from ....utils.typing import Me
from ....utils.typedefs import Me
from .. import command as base_command
from .. import get_player

View File

@@ -1,28 +1,28 @@
from asyncio import gather
from datetime import timedelta
from hashlib import md5
from urllib.parse import urlencode
from nonebot.adapters import Event
from nonebot.matcher import Matcher
from nonebot_plugin_alconna import At, Option
from nonebot_plugin_alconna.uniseg import UniMessage
from nonebot_plugin_orm import get_session
from nonebot_plugin_session import EventSession
from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[import-untyped]
from nonebot_plugin_uninfo import Uninfo
from nonebot_plugin_uninfo.orm import get_session_persist_id
from nonebot_plugin_user import get_user
from yarl import URL
from ....db import query_bind_info, trigger
from ....i18n import Lang
from ....utils.exception import RecordNotFoundError
from ....utils.host import HostPage, get_self_netloc
from ....utils.host import get_self_netloc
from ....utils.lang import get_lang
from ....utils.metrics import get_metrics
from ....utils.render import render
from ....utils.render import render_image
from ....utils.render.schemas.base import Avatar
from ....utils.render.schemas.tetrio.record.base import Finesse, Max, Mini, Tspins, User
from ....utils.render.schemas.tetrio.record.blitz import Record, Statistic
from ....utils.screenshot import screenshot
from ....utils.typing import Me
from ...constant import CANT_VERIFY_MESSAGE
from ....utils.render.schemas.v2.tetrio.record.base import Finesse, Max, Mini, Tspins, User
from ....utils.render.schemas.v2.tetrio.record.blitz import Record, Statistic
from ....utils.typedefs import Me
from .. import alc
from ..api.player import Player
from ..constant import GAME_TYPE
@@ -42,7 +42,7 @@ async def _(
event: Event,
matcher: Matcher,
target: At | Me,
event_session: EventSession,
event_session: Uninfo,
):
async with trigger(
session_persist_id=await get_session_persist_id(event_session),
@@ -54,19 +54,20 @@ async def _(
bind = await query_bind_info(
session=session,
user=await get_user(
event_session.platform, target.target if isinstance(target, At) else event.get_user_id()
event_session.scope, target.target if isinstance(target, At) else event.get_user_id()
),
game_platform=GAME_TYPE,
)
if bind is None:
await matcher.finish('未查询到绑定信息')
message = UniMessage(CANT_VERIFY_MESSAGE)
player = Player(user_id=bind.game_account, trust=True)
await (message + UniMessage.image(raw=await make_blitz_image(player))).finish()
await (
UniMessage.i18n(Lang.interaction.warning.unverified) + UniMessage.image(raw=await make_blitz_image(player))
).finish()
@alc.assign('TETRIO.record.blitz')
async def _(account: Player, event_session: EventSession):
async def _(account: Player, event_session: Uninfo):
async with trigger(
session_persist_id=await get_session_persist_id(event_session),
game_platform=GAME_TYPE,
@@ -86,63 +87,62 @@ async def make_blitz_image(player: Player) -> bytes:
duration = timedelta(milliseconds=stats.finaltime).total_seconds()
metrics = get_metrics(pps=stats.piecesplaced / duration)
netloc = get_self_netloc()
async with HostPage(
page=await render(
'v2/tetrio/record/blitz',
Record(
type='best',
user=User(
id=user.ID,
name=user.name.upper(),
avatar=f'http://{netloc}/host/resource/tetrio/avatars/{user.ID}?{urlencode({"revision": avatar_revision})}'
if (avatar_revision := (await player.avatar_revision)) is not None and avatar_revision != 0
else Avatar(
type='identicon',
hash=md5(user.ID.encode()).hexdigest(), # noqa: S324
),
return await render_image(
Record(
type='best',
user=User(
id=user.ID,
name=user.name.upper(),
avatar=str(
URL(f'http://{netloc}/host/resource/tetrio/avatars/{user.ID}') % {'revision': avatar_revision}
)
if (avatar_revision := (await player.avatar_revision)) is not None and avatar_revision != 0
else Avatar(
type='identicon',
hash=md5(user.ID.encode()).hexdigest(), # noqa: S324
),
replay_id=blitz.data.record.replayid,
rank=blitz.data.rank,
personal_rank=1,
statistic=Statistic(
keys=stats.inputs,
kpp=round(stats.inputs / stats.piecesplaced, 2),
kps=round(stats.inputs / duration, 2),
max=Max(
combo=max((0, stats.topcombo - 1)),
btb=max((0, stats.topbtb - 1)),
),
pieces=stats.piecesplaced,
pps=metrics.pps,
lines=stats.lines,
lpm=metrics.lpm,
holds=stats.holds,
score=stats.score,
spp=round(stats.score / stats.piecesplaced, 2),
single=clears.singles,
double=clears.doubles,
triple=clears.triples,
quad=clears.quads,
tspins=Tspins(
total=clears.realtspins,
single=clears.tspinsingles,
double=clears.tspindoubles,
triple=clears.tspintriples,
mini=Mini(
total=clears.minitspins,
single=clears.minitspinsingles,
double=clears.minitspindoubles,
),
),
all_clear=clears.allclear,
finesse=Finesse(
faults=stats.finesse.faults,
accuracy=round(stats.finesse.perfectpieces / stats.piecesplaced * 100, 2),
),
level=stats.level,
),
play_at=blitz.data.record.ts,
),
)
) as page_hash:
return await screenshot(f'http://{netloc}/host/{page_hash}.html')
replay_id=blitz.data.record.replayid,
rank=blitz.data.rank,
personal_rank=1,
statistic=Statistic(
keys=stats.inputs,
kpp=round(stats.inputs / stats.piecesplaced, 2),
kps=round(stats.inputs / duration, 2),
max=Max(
combo=max((0, stats.topcombo - 1)),
btb=max((0, stats.topbtb - 1)),
),
pieces=stats.piecesplaced,
pps=metrics.pps,
lines=stats.lines,
lpm=metrics.lpm,
holds=stats.holds,
score=stats.score,
spp=round(stats.score / stats.piecesplaced, 2),
single=clears.singles,
double=clears.doubles,
triple=clears.triples,
quad=clears.quads,
tspins=Tspins(
total=clears.realtspins,
single=clears.tspinsingles,
double=clears.tspindoubles,
triple=clears.tspintriples,
mini=Mini(
total=clears.minitspins,
single=clears.minitspinsingles,
double=clears.minitspindoubles,
),
),
all_clear=clears.allclear,
finesse=Finesse(
faults=stats.finesse.faults,
accuracy=round(stats.finesse.perfectpieces / stats.piecesplaced * 100, 2),
),
level=stats.level,
),
play_at=blitz.data.record.ts,
lang=get_lang(),
),
)

View File

@@ -1,28 +1,28 @@
from asyncio import gather
from datetime import timedelta
from hashlib import md5
from urllib.parse import urlencode
from nonebot.adapters import Event
from nonebot.matcher import Matcher
from nonebot_plugin_alconna import At, Option
from nonebot_plugin_alconna.uniseg import UniMessage
from nonebot_plugin_orm import get_session
from nonebot_plugin_session import EventSession
from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[import-untyped]
from nonebot_plugin_uninfo import Uninfo
from nonebot_plugin_uninfo.orm import get_session_persist_id
from nonebot_plugin_user import get_user
from yarl import URL
from ....db import query_bind_info, trigger
from ....i18n import Lang
from ....utils.exception import RecordNotFoundError
from ....utils.host import HostPage, get_self_netloc
from ....utils.host import get_self_netloc
from ....utils.lang import get_lang
from ....utils.metrics import get_metrics
from ....utils.render import render
from ....utils.render import render_image
from ....utils.render.schemas.base import Avatar
from ....utils.render.schemas.tetrio.record.base import Finesse, Max, Mini, Statistic, Tspins, User
from ....utils.render.schemas.tetrio.record.sprint import Record
from ....utils.screenshot import screenshot
from ....utils.typing import Me
from ...constant import CANT_VERIFY_MESSAGE
from ....utils.render.schemas.v2.tetrio.record.base import Finesse, Max, Mini, Statistic, Tspins, User
from ....utils.render.schemas.v2.tetrio.record.sprint import Record
from ....utils.typedefs import Me
from .. import alc
from ..api.player import Player
from ..constant import GAME_TYPE
@@ -42,7 +42,7 @@ async def _(
event: Event,
matcher: Matcher,
target: At | Me,
event_session: EventSession,
event_session: Uninfo,
):
async with trigger(
session_persist_id=await get_session_persist_id(event_session),
@@ -54,19 +54,20 @@ async def _(
bind = await query_bind_info(
session=session,
user=await get_user(
event_session.platform, target.target if isinstance(target, At) else event.get_user_id()
event_session.scope, target.target if isinstance(target, At) else event.get_user_id()
),
game_platform=GAME_TYPE,
)
if bind is None:
await matcher.finish('未查询到绑定信息')
message = UniMessage(CANT_VERIFY_MESSAGE)
player = Player(user_id=bind.game_account, trust=True)
await (message + UniMessage.image(raw=await make_sprint_image(player))).finish()
await (
UniMessage.i18n(Lang.interaction.warning.unverified) + UniMessage.image(raw=await make_sprint_image(player))
).finish()
@alc.assign('TETRIO.record.sprint')
async def _(account: Player, event_session: EventSession):
async def _(account: Player, event_session: Uninfo):
async with trigger(
session_persist_id=await get_session_persist_id(event_session),
game_platform=GAME_TYPE,
@@ -87,62 +88,61 @@ async def make_sprint_image(player: Player) -> bytes:
sprint_value = f'{duration:.3f}s' if duration < 60 else f'{duration // 60:.0f}m {duration % 60:.3f}s' # noqa: PLR2004
metrics = get_metrics(pps=stats.piecesplaced / duration)
netloc = get_self_netloc()
async with HostPage(
page=await render(
'v2/tetrio/record/40l',
Record(
type='best',
user=User(
id=user.ID,
name=user.name.upper(),
avatar=f'http://{netloc}/host/resource/tetrio/avatars/{user.ID}?{urlencode({"revision": avatar_revision})}'
if (avatar_revision := (await player.avatar_revision)) is not None and avatar_revision != 0
else Avatar(
type='identicon',
hash=md5(user.ID.encode()).hexdigest(), # noqa: S324
),
return await render_image(
Record(
type='best',
user=User(
id=user.ID,
name=user.name.upper(),
avatar=str(
URL(f'http://{netloc}/host/resource/tetrio/avatars/{user.ID}') % {'revision': avatar_revision}
)
if (avatar_revision := (await player.avatar_revision)) is not None and avatar_revision != 0
else Avatar(
type='identicon',
hash=md5(user.ID.encode()).hexdigest(), # noqa: S324
),
time=sprint_value,
replay_id=sprint.data.record.replayid,
rank=sprint.data.rank,
personal_rank=1,
statistic=Statistic(
keys=stats.inputs,
kpp=round(stats.inputs / stats.piecesplaced, 2),
kps=round(stats.inputs / duration, 2),
max=Max(
combo=max((0, stats.topcombo - 1)),
btb=max((0, stats.topbtb - 1)),
),
pieces=stats.piecesplaced,
pps=metrics.pps,
lines=stats.lines,
lpm=metrics.lpm,
holds=stats.holds,
score=stats.score,
single=clears.singles,
double=clears.doubles,
triple=clears.triples,
quad=clears.quads,
tspins=Tspins(
total=clears.realtspins,
single=clears.tspinsingles,
double=clears.tspindoubles,
triple=clears.tspintriples,
mini=Mini(
total=clears.minitspins,
single=clears.minitspinsingles,
double=clears.minitspindoubles,
),
),
all_clear=clears.allclear,
finesse=Finesse(
faults=stats.finesse.faults,
accuracy=round(stats.finesse.perfectpieces / stats.piecesplaced * 100, 2),
),
),
play_at=sprint.data.record.ts,
),
)
) as page_hash:
return await screenshot(f'http://{netloc}/host/{page_hash}.html')
time=sprint_value,
replay_id=sprint.data.record.replayid,
rank=sprint.data.rank,
personal_rank=1,
statistic=Statistic(
keys=stats.inputs,
kpp=round(stats.inputs / stats.piecesplaced, 2),
kps=round(stats.inputs / duration, 2),
max=Max(
combo=max((0, stats.topcombo - 1)),
btb=max((0, stats.topbtb - 1)),
),
pieces=stats.piecesplaced,
pps=metrics.pps,
lines=stats.lines,
lpm=metrics.lpm,
holds=stats.holds,
score=stats.score,
single=clears.singles,
double=clears.doubles,
triple=clears.triples,
quad=clears.quads,
tspins=Tspins(
total=clears.realtspins,
single=clears.tspinsingles,
double=clears.tspindoubles,
triple=clears.tspintriples,
mini=Mini(
total=clears.minitspins,
single=clears.minitspinsingles,
double=clears.minitspindoubles,
),
),
all_clear=clears.allclear,
finesse=Finesse(
faults=stats.finesse.faults,
accuracy=round(stats.finesse.perfectpieces / stats.piecesplaced * 100, 2),
),
),
play_at=sprint.data.record.ts,
lang=get_lang(),
),
)

View File

@@ -0,0 +1,84 @@
from hashlib import md5
from secrets import choice
from nonebot_plugin_alconna import Subcommand
from nonebot_plugin_alconna.uniseg import UniMessage
from nonebot_plugin_orm import get_session
from nonebot_plugin_uninfo import QryItrface, Uninfo
from nonebot_plugin_uninfo import User as UninfoUser
from nonebot_plugin_uninfo.orm import get_session_persist_id
from nonebot_plugin_user import User
from nonebot_plugin_waiter import suggest # type: ignore[import-untyped]
from yarl import URL
from ...config.config import global_config
from ...db import query_bind_info, remove_bind, trigger
from ...utils.host import get_self_netloc
from ...utils.image import get_avatar
from ...utils.lang import get_lang
from ...utils.render import render_image
from ...utils.render.schemas.base import Avatar, People
from ...utils.render.schemas.bind import Bind
from . import alc, command
from .api import Player
from .constant import GAME_TYPE
command.add(Subcommand('unbind', help_text='解除绑定 TETR.IO 账号'))
alc.shortcut(
'(?i:io)(?i:解除绑定|解绑|unbind)',
command='tstats TETR.IO unbind',
humanized='io解绑',
)
@alc.assign('TETRIO.unbind')
async def _(nb_user: User, event_session: Uninfo, interface: QryItrface):
async with (
trigger(
session_persist_id=await get_session_persist_id(event_session),
game_platform=GAME_TYPE,
command_type='unbind',
command_args=[],
),
get_session() as session,
):
if (bind := await query_bind_info(session=session, user=nb_user, game_platform=GAME_TYPE)) is None:
await UniMessage('您还未绑定 TETR.IO 账号').finish()
resp = await suggest('您确定要解绑吗?', ['', ''])
if resp is None or resp.extract_plain_text() == '':
return
player = Player(user_id=bind.game_account, trust=True)
user = await player.user
netloc = get_self_netloc()
await UniMessage.image(
raw=await render_image(
Bind(
platform='TETR.IO',
type='unlink',
user=People(
avatar=str(
URL(f'http://{netloc}/host/resource/tetrio/avatars/{user.ID}')
% {'revision': avatar_revision}
)
if (avatar_revision := (await player.avatar_revision)) is not None and avatar_revision != 0
else Avatar(type='identicon', hash=md5(user.ID.encode()).hexdigest()), # noqa: S324
name=user.name.upper(),
),
bot=People(
avatar=await get_avatar(
(
bot_user := await interface.get_user(event_session.self_id)
or UninfoUser(id=event_session.self_id)
),
'Data URI',
'../../static/logo/logo.svg',
),
name=bot_user.nick or bot_user.name or choice(list(global_config.nickname) or ['bot']),
),
prompt='io绑定{游戏ID}',
lang=get_lang(),
),
)
).send()
await remove_bind(session=session, user=nb_user, game_platform=GAME_TYPE)

View File

@@ -0,0 +1,110 @@
from asyncio import gather
from hashlib import md5
from secrets import choice
from nonebot_plugin_alconna import Subcommand
from nonebot_plugin_alconna.uniseg import UniMessage
from nonebot_plugin_orm import get_session
from nonebot_plugin_uninfo import QryItrface, Uninfo
from nonebot_plugin_uninfo import User as UninfoUser
from nonebot_plugin_uninfo.orm import get_session_persist_id
from nonebot_plugin_user import User
from yarl import URL
from ...config.config import global_config
from ...db import create_or_update_bind, query_bind_info, trigger
from ...utils.host import get_self_netloc
from ...utils.image import get_avatar
from ...utils.lang import get_lang
from ...utils.render import render_image
from ...utils.render.schemas.base import Avatar, People
from ...utils.render.schemas.bind import Bind
from . import alc, command
from .api import Player
from .constant import GAME_TYPE
command.add(Subcommand('verify', help_text='验证 TETR.IO 账号'))
alc.shortcut(
'(?i:io)(?i:验证|verify)',
command='tstats TETR.IO verify',
humanized='io验证',
)
try:
from nonebot.adapters.discord import MessageCreateEvent
@alc.assign('TETRIO.verify')
async def _(_: MessageCreateEvent, nb_user: User, event_session: Uninfo, interface: QryItrface):
async with (
trigger(
session_persist_id=await get_session_persist_id(event_session),
game_platform=GAME_TYPE,
command_type='verify',
command_args=[],
),
get_session() as session,
):
if (bind := await query_bind_info(session=session, user=nb_user, game_platform=GAME_TYPE)) is None:
await UniMessage('您还未绑定 TETR.IO 账号').finish()
if bind.verify is True:
await UniMessage('您已经完成了验证.').finish()
player = Player(user_id=bind.game_account, trust=True)
user_info = await player.get_info()
verify = (
user_info.data.connections.discord is not None
and user_info.data.connections.discord.id == event_session.user.id
)
if verify is False:
await UniMessage('您未通过验证, 请确认目标 TETR.IO 账号绑定了当前 Discord 账号').finish()
await create_or_update_bind(
session=session,
user=nb_user,
game_platform=GAME_TYPE,
game_account=bind.game_account,
verify=verify,
)
user, avatar_revision = await gather(player.user, player.avatar_revision)
await UniMessage.image(
raw=await render_image(
Bind(
platform='TETR.IO',
type='success',
user=People(
avatar=str(
URL(f'http://{get_self_netloc()}/host/resource/tetrio/avatars/{user.ID}')
% {'revision': avatar_revision}
)
if avatar_revision is not None and avatar_revision != 0
else Avatar(type='identicon', hash=md5(user.ID.encode()).hexdigest()), # noqa: S324
name=user.name.upper(),
),
bot=People(
avatar=await get_avatar(
(
bot_user := await interface.get_user(event_session.self_id)
or UninfoUser(id=event_session.self_id)
),
'Data URI',
'../../static/logo/logo.svg',
),
name=bot_user.nick or bot_user.name or choice(list(global_config.nickname) or ['bot']),
),
prompt='io查我',
lang=get_lang(),
),
)
).finish()
except ImportError:
pass
@alc.assign('TETRIO.verify')
async def _(event_session: Uninfo):
async with trigger(
session_persist_id=await get_session_persist_id(event_session),
game_platform=GAME_TYPE,
command_type='verify',
command_args=[],
):
await UniMessage('目前仅支持 Discord 账号验证').finish()

View File

@@ -2,7 +2,7 @@ from arclet.alconna import Arg, ArgFlag
from nonebot_plugin_alconna import Args, At, Subcommand
from ...utils.exception import MessageFormatError
from ...utils.typing import Me
from ...utils.typedefs import Me
from .. import add_block_handlers, alc, command
from .api import Player
from .constant import USER_NAME
@@ -29,6 +29,10 @@ command.add(
),
help_text='绑定 TOP 账号',
),
Subcommand(
'unbind',
help_text='解除绑定 TOP 账号',
),
Subcommand(
'query',
Args(
@@ -51,9 +55,22 @@ command.add(
)
)
alc.shortcut('(?i:top)(?i:绑定|绑|bind)', {'command': 'tstats TOP bind', 'humanized': 'top绑定'})
alc.shortcut('(?i:top)(?i:查询|查|query|stats)', {'command': 'tstats TOP query', 'humanized': 'top查'})
alc.shortcut(
'(?i:top)(?i:绑定|绑|bind)',
command='tstats TOP bind',
humanized='top绑定',
)
alc.shortcut(
'(?i:top)(?i:解除绑定|解绑|unbind)',
command='tstats TOP unbind',
humanized='top解绑',
)
alc.shortcut(
'(?i:top)(?i:查询|查|query|stats)',
command='tstats TOP query',
humanized='top查',
)
add_block_handlers(alc.assign('TOP.query'))
from . import bind, query # noqa: E402, F401
from . import bind, query, unbind # noqa: E402, F401

View File

@@ -10,6 +10,8 @@ from .schemas.user_profile import UserProfile
class TOPHistoricalData(MappedAsDataclass, Model):
__tablename__ = 'nb_t_top_hist_data'
id: Mapped[int] = mapped_column(init=False, primary_key=True)
user_unique_identifier: Mapped[str] = mapped_column(String(24), index=True)
api_type: Mapped[Literal['User Profile']] = mapped_column(String(16), index=True)

View File

@@ -1,13 +1,13 @@
from contextlib import suppress
from datetime import datetime, timezone
from io import StringIO
from urllib.parse import urlencode
from lxml import etree
from pandas import read_html
from ....config.config import config
from ....db import anti_duplicate_add
from ....utils.request import Request, splice_url
from ....utils.request import Request
from ..constant import BASE_URL, USER_NAME
from .models import TOPHistoricalData
from .schemas.user import User
@@ -15,6 +15,8 @@ from .schemas.user_profile import Data, UserProfile
UTC = timezone.utc
request = Request(config.tetris.proxy.top or config.tetris.proxy.main)
class Player:
def __init__(self, *, user_name: str, trust: bool = False) -> None:
@@ -35,8 +37,7 @@ class Player:
async def get_profile(self) -> UserProfile:
"""获取用户信息"""
if self._user_profile is None:
url = splice_url([BASE_URL, 'profile.php', f'?{urlencode({"user":self.user_name})}'])
raw_user_profile = await Request.request(url, is_json=False)
raw_user_profile = await request.request(BASE_URL / 'profile.php' % {'user': self.user_name}, is_json=False)
self._user_profile = self._parse_profile(raw_user_profile)
await anti_duplicate_add(
TOPHistoricalData(

View File

@@ -1,29 +1,26 @@
from secrets import choice
from nonebot_plugin_alconna.uniseg import UniMessage
from nonebot_plugin_orm import get_session
from nonebot_plugin_session import EventSession
from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[import-untyped]
from nonebot_plugin_uninfo import QryItrface, Uninfo
from nonebot_plugin_uninfo import User as UninfoUser
from nonebot_plugin_uninfo.orm import get_session_persist_id
from nonebot_plugin_user import User
from nonebot_plugin_userinfo import BotUserInfo, EventUserInfo, UserInfo
from ...config.config import global_config
from ...db import BindStatus, create_or_update_bind, trigger
from ...utils.host import HostPage, get_self_netloc
from ...utils.image import get_avatar
from ...utils.render import Bind, render
from ...utils.lang import get_lang
from ...utils.render import render_image
from ...utils.render.schemas.base import People
from ...utils.screenshot import screenshot
from ...utils.render.schemas.bind import Bind
from . import alc
from .api import Player
from .constant import GAME_TYPE
@alc.assign('TOP.bind')
async def _(
nb_user: User,
account: Player,
event_session: EventSession,
event_user_info: UserInfo = EventUserInfo(), # noqa: B008
bot_info: UserInfo = BotUserInfo(), # noqa: B008
):
async def _(nb_user: User, account: Player, event_session: Uninfo, interface: QryItrface):
async with trigger(
session_persist_id=await get_session_persist_id(event_session),
game_platform=GAME_TYPE,
@@ -39,24 +36,32 @@ async def _(
game_account=user.unique_identifier,
)
if bind_status in (BindStatus.SUCCESS, BindStatus.UPDATE):
async with HostPage(
await render(
'v1/binding',
await UniMessage.image(
raw=await render_image(
Bind(
platform=GAME_TYPE,
status='unknown',
type='unknown',
user=People(
avatar=await get_avatar(event_user_info, 'Data URI', None),
avatar=await get_avatar(
event_session.user,
'Data URI',
None,
),
name=user.user_name,
),
bot=People(
avatar=await get_avatar(bot_info, 'Data URI', '../../static/logo/logo.svg'),
name=bot_info.user_name,
avatar=await get_avatar(
(
bot_user := await interface.get_user(event_session.self_id)
or UninfoUser(id=event_session.self_id)
),
'Data URI',
'../../static/logo/logo.svg',
),
name=bot_user.nick or bot_user.name or choice(list(global_config.nickname) or ['bot']),
),
command='top查我',
prompt='top查我',
lang=get_lang(),
),
)
) as page_hash:
await UniMessage.image(
raw=await screenshot(f'http://{get_self_netloc()}/host/{page_hash}.html')
).finish()
).finish()

View File

@@ -1,8 +1,10 @@
from re import compile
from re import compile # noqa: A004
from typing import Literal
from yarl import URL
GAME_TYPE: Literal['TOP'] = 'TOP'
BASE_URL = 'http://tetrisonline.pl/top/'
BASE_URL = URL('http://tetrisonline.pl/top/')
USER_NAME = compile(r'^[a-zA-Z0-9_]{1,16}$')

View File

@@ -3,22 +3,21 @@ from nonebot.matcher import Matcher
from nonebot_plugin_alconna import At
from nonebot_plugin_alconna.uniseg import UniMessage
from nonebot_plugin_orm import get_session
from nonebot_plugin_session import EventSession
from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[import-untyped]
from nonebot_plugin_uninfo import Uninfo
from nonebot_plugin_uninfo.orm import get_session_persist_id
from nonebot_plugin_user import get_user
from ...db import query_bind_info, trigger
from ...i18n import Lang
from ...utils.exception import FallbackError
from ...utils.host import HostPage, get_self_netloc
from ...utils.lang import get_lang
from ...utils.metrics import TetrisMetricsBasicWithLPM, get_metrics
from ...utils.render import render
from ...utils.render import render_image
from ...utils.render.avatar import get_avatar
from ...utils.render.schemas.base import People
from ...utils.render.schemas.top_info import Data as InfoData
from ...utils.render.schemas.top_info import Info
from ...utils.screenshot import screenshot
from ...utils.typing import Me
from ..constant import CANT_VERIFY_MESSAGE
from ...utils.render.schemas.base import People, Trending
from ...utils.render.schemas.v1.top.info import Data as InfoData
from ...utils.render.schemas.v1.top.info import Info
from ...utils.typedefs import Me
from . import alc
from .api import Player
from .api.schemas.user_profile import Data, UserProfile
@@ -26,7 +25,7 @@ from .constant import GAME_TYPE
@alc.assign('TOP.query')
async def _(event: Event, matcher: Matcher, target: At | Me, event_session: EventSession):
async def _(event: Event, matcher: Matcher, target: At | Me, event_session: Uninfo):
async with trigger(
session_persist_id=await get_session_persist_id(event_session),
game_platform=GAME_TYPE,
@@ -37,20 +36,20 @@ async def _(event: Event, matcher: Matcher, target: At | Me, event_session: Even
bind = await query_bind_info(
session=session,
user=await get_user(
event_session.platform, target.target if isinstance(target, At) else event.get_user_id()
event_session.scope, target.target if isinstance(target, At) else event.get_user_id()
),
game_platform=GAME_TYPE,
)
if bind is None:
await matcher.finish('未查询到绑定信息')
await (
UniMessage(CANT_VERIFY_MESSAGE)
UniMessage.i18n(Lang.interaction.warning.unverified)
+ await make_query_result(await Player(user_name=bind.game_account, trust=True).get_profile())
).finish()
@alc.assign('TOP.query')
async def _(account: Player, event_session: EventSession):
async def _(account: Player, event_session: Uninfo):
async with trigger(
session_persist_id=await get_session_persist_id(event_session),
game_platform=GAME_TYPE,
@@ -74,17 +73,28 @@ async def make_query_image(profile: UserProfile) -> bytes:
raise FallbackError
today = get_metrics(lpm=profile.today.lpm, apm=profile.today.apm)
history = get_avg_metrics(profile.total)
async with HostPage(
await render(
'v1/top/info',
Info(
user=People(avatar=get_avatar(), name=profile.user_name),
today=InfoData(pps=today.pps, lpm=today.lpm, apm=today.apm, apl=today.apl),
history=InfoData(pps=history.pps, lpm=history.lpm, apm=history.apm, apl=history.apl),
return await render_image(
Info(
user=People(avatar=get_avatar(profile.user_name), name=profile.user_name),
today=InfoData(
pps=today.pps,
lpm=today.lpm,
lpm_trending=Trending.KEEP,
apm=today.apm,
apl=today.apl,
apm_trending=Trending.KEEP,
),
)
) as page_hash:
return await screenshot(f'http://{get_self_netloc()}/host/{page_hash}.html')
historical=InfoData(
pps=history.pps,
lpm=history.lpm,
lpm_trending=Trending.KEEP,
apm=history.apm,
apl=history.apl,
apm_trending=Trending.KEEP,
),
lang=get_lang(),
),
)
def make_query_text(profile: UserProfile) -> UniMessage:

View File

@@ -0,0 +1,74 @@
from secrets import choice
from nonebot_plugin_alconna.uniseg import UniMessage
from nonebot_plugin_orm import get_session
from nonebot_plugin_uninfo import QryItrface, Uninfo
from nonebot_plugin_uninfo import User as UninfoUser
from nonebot_plugin_uninfo.orm import get_session_persist_id
from nonebot_plugin_user import User
from nonebot_plugin_waiter import suggest # type: ignore[import-untyped]
from ...config.config import global_config
from ...db import query_bind_info, remove_bind, trigger
from ...utils.image import get_avatar
from ...utils.lang import get_lang
from ...utils.render import render_image
from ...utils.render.schemas.base import People
from ...utils.render.schemas.bind import Bind
from . import alc
from .api import Player
from .constant import GAME_TYPE
@alc.assign('TOP.unbind')
async def _(
nb_user: User,
event_session: Uninfo,
interface: QryItrface,
):
async with (
trigger(
session_persist_id=await get_session_persist_id(event_session),
game_platform=GAME_TYPE,
command_type='unbind',
command_args=[],
),
get_session() as session,
):
if (bind := await query_bind_info(session=session, user=nb_user, game_platform=GAME_TYPE)) is None:
await UniMessage('您还未绑定 TOP 账号').finish()
resp = await suggest('您确定要解绑吗?', ['', ''])
if resp is None or resp.extract_plain_text() == '':
return
player = Player(user_name=bind.game_account, trust=True)
user = await player.user
await UniMessage.image(
raw=await render_image(
Bind(
platform='TOP',
type='unlink',
user=People(
avatar=await get_avatar(
event_session.user,
'Data URI',
None,
),
name=user.user_name,
),
bot=People(
avatar=await get_avatar(
(
bot_user := await interface.get_user(event_session.self_id)
or UninfoUser(id=event_session.self_id)
),
'Data URI',
'../../static/logo/logo.svg',
),
name=bot_user.nick or bot_user.name or choice(list(global_config.nickname) or ['bot']),
),
prompt='top绑定{游戏ID}',
lang=get_lang(),
),
)
).send()
await remove_bind(session=session, user=nb_user, game_platform=GAME_TYPE)

View File

@@ -2,7 +2,7 @@ from arclet.alconna import Arg, ArgFlag
from nonebot_plugin_alconna import Args, At, Subcommand
from ...utils.exception import MessageFormatError
from ...utils.typing import Me
from ...utils.typedefs import Me
from .. import add_block_handlers, alc, command
from .api import Player
from .constant import USER_NAME
@@ -34,6 +34,10 @@ command.add(
),
help_text='绑定 茶服 账号',
),
Subcommand(
'unbind',
help_text='解除绑定 TOS 账号',
),
Subcommand(
'query',
Args(
@@ -56,9 +60,22 @@ command.add(
)
)
alc.shortcut('(?i:tos|茶服)(?i:绑定|绑|bind)', {'command': 'tstats TOS bind', 'humanized': '茶服绑定'})
alc.shortcut('(?i:tos|茶服)(?i:查询|查|query|stats)', {'command': 'tstats TOS query', 'humanized': '茶服查'})
alc.shortcut(
'(?i:tos|茶服)(?i:绑定|绑|bind)',
command='tstats TOS bind',
humanized='茶服绑定',
)
alc.shortcut(
'(?i:tos|茶服)(?i:解除绑定|解绑|unbind)',
command='tstats TOS unbind',
humanized='茶服解绑',
)
alc.shortcut(
'(?i:tos|茶服)(?i:查询|查|query|stats)',
command='tstats TOS query',
humanized='茶服查',
)
add_block_handlers(alc.assign('TOS.query'))
from . import bind, query # noqa: E402, F401
from . import bind, query, unbind # noqa: E402, F401

View File

@@ -11,8 +11,10 @@ from .schemas.user_profile import UserProfile
class TOSHistoricalData(MappedAsDataclass, Model):
__tablename__ = 'nb_t_tos_hist_data'
id: Mapped[int] = mapped_column(init=False, primary_key=True)
user_unique_identifier: Mapped[str] = mapped_column(String(24), index=True)
user_unique_identifier: Mapped[str] = mapped_column(String(256), index=True)
api_type: Mapped[Literal['User Info', 'User Profile']] = mapped_column(String(16), index=True)
data: Mapped[UserInfoSuccess | UserProfile] = mapped_column(
PydanticType(get_model=[], models={UserInfoSuccess, UserProfile})

View File

@@ -1,13 +1,14 @@
from datetime import datetime, timezone
from typing import overload
from urllib.parse import urlencode
from typing import cast, overload
from httpx import TimeoutException
from nonebot.compat import type_validate_json
from yarl import URL
from ....config.config import config
from ....db import anti_duplicate_add
from ....utils.exception import RequestError
from ....utils.request import Request, splice_url
from ....utils.request import Request
from ..constant import BASE_URL, USER_NAME
from .models import TOSHistoricalData
from .schemas.user import User
@@ -16,6 +17,8 @@ from .schemas.user_profile import UserProfile
UTC = timezone.utc
request = Request(config.tetris.proxy.tos or config.tetris.proxy.main)
class Player:
@overload
@@ -57,28 +60,14 @@ class Player:
"""获取用户信息"""
if self._user_info is None:
if self.teaid is not None:
url = [
splice_url(
[
i,
'getTeaIdInfo',
f'?{urlencode({"teaId":self.teaid})}',
]
)
for i in BASE_URL
]
path = 'getTeaIdInfo'
query = {'teaId': self.teaid}
else:
url = [
splice_url(
[
i,
'getUsernameInfo',
f'?{urlencode({"username":self.user_name})}',
]
)
for i in BASE_URL
]
raw_user_info = await Request.failover_request(url, failover_code=[502], failover_exc=(TimeoutException,))
path = 'getUsernameInfo'
query = {'username': cast('str', self.user_name)}
raw_user_info = await request.failover_request(
[i / path % query for i in BASE_URL], failover_code=[502], failover_exc=(TimeoutException,)
)
user_info: UserInfo = type_validate_json(UserInfo, raw_user_info) # type: ignore[arg-type]
if not isinstance(user_info, UserInfoSuccess):
msg = f'用户信息请求错误:\n{user_info.error}'
@@ -98,17 +87,11 @@ class Player:
"""获取用户数据"""
if other_parameter is None:
other_parameter = {}
params = urlencode(dict(sorted(other_parameter.items())))
params = (URL('') % dict(sorted(other_parameter.items()))).human_repr()
if self._user_profile.get(params) is None:
raw_user_profile = await Request.failover_request(
raw_user_profile = await request.failover_request(
[
splice_url(
[
i,
'getProfile',
f'?{urlencode({"id":self.teaid or self.user_name,**other_parameter})}',
]
)
i / 'getProfile' % {'id': self.teaid or cast('str', self.user_name), **other_parameter}
for i in BASE_URL
],
failover_code=[502],

Some files were not shown because too many files have changed in this diff Show More