Compare commits

...

495 Commits

Author SHA1 Message Date
99e475b75b 🔖 1.12.0 2026-02-23 01:06:59 +08:00
呵呵です
ba0d1677cf 适配 Trending (#539)
*  适配 v1 tetrio 的 Trending

* 🗃️ 添加 compare_delta 配置项

* 🗃️ 添加 TETRIOLeagueUserMap 索引表

*  添加对比时间配置项

*  添加 compare_delta 解析函数

*  添加 Trending 类的 compare 方法

* 🗃️ 移除不正确的复合索引

*  定时任务拉取tl数据时同步更新索引

*  适配 trending

* 🐛 修复 find_entry 在无 uid 时的索引返回逻辑

* 📝 修正 compare_delta 迁移父迁移注释

* 🗃️ 为非 PostgreSQL 回填迁移补充外键约束

* 🔒 迁移中使用参数绑定设置 PG 内存参数

*  修正 Trends 的 vs 为 adpm

* 🐛 修正获取玩家 ID 的范围
2026-02-23 01:04:01 +08:00
pre-commit-ci[bot]
14f3e6960e ⬆️ auto update by pre-commit hooks (#594)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.15.0 → v0.15.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.0...v0.15.1)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-02-17 11:12:41 +08:00
renovate[bot]
61c798de1a ⬆️ Lock file maintenance (#593)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-16 19:05:03 +08:00
呵呵です
d63976b881 🐛 support achievement rt=7 rank type (#592) 2026-02-15 11:20:23 +00:00
renovate[bot]
72042220d8 ⬆️ Lock file maintenance (#585)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-12 04:10:36 +08:00
pre-commit-ci[bot]
15d3a33cb8 ⬆️ auto update by pre-commit hooks (#587)
* ⬆️ auto update by pre-commit hooks

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

* Fix ASYNC240: Use async file I/O in check_hash (#591)

* Initial plan

* Fix ASYNC240: Replace synchronous Path.read_text() with async aiofiles operation

Co-authored-by: shoucandanghehe <51957264+shoucandanghehe@users.noreply.github.com>

* Add explicit UTF-8 encoding to ensure consistent behavior across platforms

Co-authored-by: shoucandanghehe <51957264+shoucandanghehe@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: shoucandanghehe <51957264+shoucandanghehe@users.noreply.github.com>

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: shoucandanghehe <51957264+shoucandanghehe@users.noreply.github.com>
2026-02-12 04:06:46 +08:00
renovate[bot]
f8fde51009 ⬆️ Upgrade re-actors/alls-green digest to a638d64 (#586)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-12 00:23:52 +08:00
renovate[bot]
6256053a82 ⬆️ Upgrade dependency pillow to v12.1.1 [SECURITY] (#590)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-12 00:17:37 +08:00
dependabot[bot]
689a2c72b8 ⬆️ Bump cryptography from 46.0.3 to 46.0.5 (#588)
Bumps [cryptography](https://github.com/pyca/cryptography) from 46.0.3 to 46.0.5.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/46.0.3...46.0.5)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-version: 46.0.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-12 00:08:00 +08:00
呵呵です
cdea262335 🐛 enforce object JSON storage (#584)
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 (macos-latest, 3.13) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.14) (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 (ubuntu-latest, 3.13) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.14) (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
Code Coverage / Test (windows-latest, 3.13) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.14) (push) Has been cancelled
TypeCheck / TypeCheck (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Code Coverage / check (push) Has been cancelled
Migrate Pydantic JSON columns to objects and tighten serialization to avoid string-encoded JSON.
2026-02-01 01:21:58 +08:00
呵呵です
28a02aec0f migrate user-facing text to i18n (#581) 2026-01-27 05:20:41 +08:00
呵呵です
95aa00e2cd ⬆️ update nonebot dependency config (#580) 2026-01-27 05:18:42 +08:00
pre-commit-ci[bot]
73bdf93d88 ⬆️ auto update by pre-commit hooks (#577)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.14.10 → v0.14.14](https://github.com/astral-sh/ruff-pre-commit/compare/v0.14.10...v0.14.14)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-01-26 21:18:13 +00:00
renovate[bot]
397047162e ⬆️ Lock file maintenance (#576)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-27 05:14:34 +08:00
renovate[bot]
653bccf48a ⬆️ Upgrade dependency prettier to v3.8.1 (#579)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-26 21:10:53 +00:00
呵呵です
bce73489dd 🐛 fix TOS unbind label (#582) 2026-01-27 05:06:47 +08:00
renovate[bot]
f4e1e8b1a2 ⬆️ Upgrade dependency prettier to v3.8.0 (#578)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-16 01:30:43 +08:00
renovate[bot]
b3bb425c44 ⬆️ Lock file maintenance (#575)
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 (macos-latest, 3.13) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.14) (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 (ubuntu-latest, 3.13) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.14) (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
Code Coverage / Test (windows-latest, 3.13) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.14) (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>
2026-01-05 01:16:03 +08:00
呵呵です
59a1c80ce5 🐛 修复 io rank 比较或展示顺序可能有问题的bug (#574)
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 (macos-latest, 3.13) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.14) (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 (ubuntu-latest, 3.13) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.14) (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
Code Coverage / Test (windows-latest, 3.13) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.14) (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-12-25 23:58:51 +08:00
呵呵です
fcecf5a01f 🔧 修改 basedpyright 配置 (#573)
* 🔧 修改 basedpyright 配置

* 🚨 auto fix by pre-commit hooks

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-12-25 22:32:25 +08:00
pre-commit-ci[bot]
a4ffb6bbc6 ⬆️ auto update by pre-commit hooks (#569)
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 (macos-latest, 3.13) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.14) (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 (ubuntu-latest, 3.13) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.14) (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
Code Coverage / Test (windows-latest, 3.13) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.14) (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.14.9 → v0.14.10](https://github.com/astral-sh/ruff-pre-commit/compare/v0.14.9...v0.14.10)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-12-25 05:18:41 +08:00
renovate[bot]
81150a36be ⬆️ Lock file maintenance (#572)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-25 05:18:20 +08:00
呵呵です
2ddf1dc336 🔧 让 renovate 定期更新 lock 文件 (#571) 2025-12-25 05:15:15 +08:00
呵呵です
3615a87926 👷 为测试矩阵添加更多 python 版本 (#568)
* 👷 为测试矩阵添加更多 python 版本

* ⬆️ 更新 lock 包版本

* 🚨 修复 mypy 报错
2025-12-25 05:02:18 +08:00
呵呵です
0228e1a480 📝 Update Wakatime badge link (#570) 2025-12-25 03:09:42 +08:00
呵呵です
d920c90936 🐛 修复重复创建浏览器上下文的bug (#567)
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-12-21 18:25:20 +00:00
pre-commit-ci[bot]
024418f032 ⬆️ auto update by pre-commit hooks (#565)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.14.7 → v0.14.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.14.7...v0.14.9)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-12-22 02:03:47 +08:00
呵呵です
3945da6655 🐛 修正 pyproject schema (#566) 2025-12-21 18:00:14 +00:00
pre-commit-ci[bot]
bc59e287d8 ⬆️ auto update by pre-commit hooks (#558)
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.12.10 → v0.14.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.12.10...v0.14.7)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-12-03 17:50:03 +00:00
renovate[bot]
f8382150c7 ⬆️ Upgrade astral-sh/setup-uv action to v7 (#562)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-03 17:48:35 +00:00
renovate[bot]
4fd17204c4 ⬆️ Upgrade github/codeql-action action to v4 (#560)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-03 17:46:11 +00:00
renovate[bot]
9d4333812d ⬆️ Upgrade actions/setup-python action to v6 (#559)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-03 17:43:40 +00:00
renovate[bot]
2c07a1e337 ⬆️ Upgrade dependency prettier to v3.7.4 (#564)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-03 17:41:18 +00:00
renovate[bot]
2f95650282 ⬆️ Upgrade actions/checkout action to v6 (#563)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-04 01:38:25 +08:00
pre-commit-ci[bot]
0b4d49ee6a ⬆️ auto update by pre-commit hooks (#556)
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.12.4 → v0.12.10](https://github.com/astral-sh/ruff-pre-commit/compare/v0.12.4...v0.12.10)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-08-30 15:57:34 +08:00
renovate[bot]
9392fefdf7 ⬆️ Upgrade actions/checkout action to v5 (#557)
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-08-26 00:28:00 +08:00
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
f7a2e89274 🔖 1.4.8 2024-08-15 17:39:59 +08:00
3fe5a19c4a 🐛 修复 top 没有 recent games 时 出现 0 长 list 的 bug 2024-08-15 17:39:28 +08:00
d35469cdef 🔖 1.4.7 2024-08-15 17:01:27 +08:00
0cbae117aa ️ 将 _parse_profile 改成静态方法 2024-08-15 16:56:10 +08:00
25dc57d911 top query 使用图片回复 close #61 2024-08-15 16:55:13 +08:00
6042417b65 ️ 删除不需要的 async 2024-08-15 16:50:11 +08:00
63cd94a0d7 🔖 1.4.6 2024-08-14 22:25:41 +08:00
eb810d4bd2 茶服查别人时使用随机头像 2024-08-14 20:42:30 +08:00
52df4cf170 绘制随机头像
皮肤来源:[Techmino](https://github.com/26F-Studio/Techmino)
2024-08-14 20:41:54 +08:00
dependabot[bot]
7a6615f6c9 ⬆️ Bump nonebot-plugin-alconna from 0.51.2 to 0.51.4 (#399)
Bumps [nonebot-plugin-alconna](https://github.com/nonebot/plugin-alconna) from 0.51.2 to 0.51.4.
- [Release notes](https://github.com/nonebot/plugin-alconna/releases)
- [Commits](https://github.com/nonebot/plugin-alconna/compare/v0.51.2...v0.51.4)

---
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-14 19:44:14 +08:00
dependabot[bot]
c363908434 ⬆️ Bump playwright from 1.45.1 to 1.46.0 (#396)
Bumps [playwright](https://github.com/Microsoft/playwright-python) from 1.45.1 to 1.46.0.
- [Release notes](https://github.com/Microsoft/playwright-python/releases)
- [Commits](https://github.com/Microsoft/playwright-python/compare/v1.45.1...v1.46.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-08-13 08:34:28 +00:00
dependabot[bot]
e26fb44106 ⬆️ Bump lxml from 5.2.2 to 5.3.0 (#397)
Bumps [lxml](https://github.com/lxml/lxml) from 5.2.2 to 5.3.0.
- [Release notes](https://github.com/lxml/lxml/releases)
- [Changelog](https://github.com/lxml/lxml/blob/master/CHANGES.txt)
- [Commits](https://github.com/lxml/lxml/compare/lxml-5.2.2...lxml-5.3.0)

---
updated-dependencies:
- dependency-name: lxml
  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-13 08:30:38 +00:00
dependabot[bot]
7e2c04426a ⬆️ Bump nonebot-plugin-alconna from 0.51.1 to 0.51.2 (#398)
Bumps [nonebot-plugin-alconna](https://github.com/nonebot/plugin-alconna) from 0.51.1 to 0.51.2.
- [Release notes](https://github.com/nonebot/plugin-alconna/releases)
- [Commits](https://github.com/nonebot/plugin-alconna/compare/v0.51.1...v0.51.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-13 16:27:04 +08:00
dependabot[bot]
5910f05dfe ⬆️ Bump aiohttp from 3.10.1 to 3.10.2 (#395)
* ⬆️ Bump aiohttp from 3.10.1 to 3.10.2

Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.10.1 to 3.10.2.
- [Release notes](https://github.com/aio-libs/aiohttp/releases)
- [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/aiohttp/compare/v3.10.1...v3.10.2)

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

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-12 11:20:21 +00:00
eebbd08551 🔧 更新 pre-commit 配置 2024-08-12 19:14:09 +08:00
f035b844ab 🔧 toml 使用单引号 2024-08-12 16:20:04 +08:00
197b81f9cf 🔧 启用一些 ruff 规则 2024-08-12 16:10:14 +08:00
5c2ffe13b0 💡 将 TODO 改成 Maple Font 的连字样式) 2024-08-12 14:40:21 +08:00
b0cff16dc6 🚨 修复 pyright 警告
改用 standard 标准(
2024-08-10 14:56:31 +08:00
dependabot[bot]
3c952530d1 ⬆️ Bump ruff from 0.5.6 to 0.5.7 (#394)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.5.6 to 0.5.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.5.6...0.5.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-08-09 15:53:36 +00:00
dependabot[bot]
57dfc8b94a ⬆️ Bump nonebot-plugin-orm from 0.7.5 to 0.7.6 (#393)
Bumps [nonebot-plugin-orm](https://github.com/nonebot/plugin-orm) from 0.7.5 to 0.7.6.
- [Release notes](https://github.com/nonebot/plugin-orm/releases)
- [Commits](https://github.com/nonebot/plugin-orm/compare/v0.7.5...v0.7.6)

---
updated-dependencies:
- dependency-name: nonebot-plugin-orm
  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-09 23:50:07 +08:00
1065e62d11 优化 anti_duplicate_add 2024-08-09 16:25:37 +08:00
02e703ea91 适配新 records API 2024-08-09 16:23:12 +08:00
429f99f77e 🗃️ 增加 TETRIOHistoricalData.api_type 字段长度 2024-08-09 16:20:53 +08:00
9a16e2fa21 新赛季 records 模型 2024-08-08 21:46:24 +08:00
0719d549b5 🔥 删除旧 API 的 user_records 模型 2024-08-08 21:46:23 +08:00
9cb2a90197 🐛 修复 茶服 没有 40l 记录时数值显示错误的bug 2024-08-08 20:48:45 +08:00
dependabot[bot]
bb0606a144 ⬆️ Bump types-lxml from 2024.4.14 to 2024.8.7 (#391)
Bumps [types-lxml](https://github.com/abelcheung/types-lxml) from 2024.4.14 to 2024.8.7.
- [Release notes](https://github.com/abelcheung/types-lxml/releases)
- [Commits](https://github.com/abelcheung/types-lxml/compare/2024.04.14...2024.08.07)

---
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-08-08 07:34:23 +00:00
dependabot[bot]
41068f7152 ⬆️ Bump nonebot-plugin-user from 0.3.0 to 0.4.0 (#392)
Bumps [nonebot-plugin-user](https://github.com/he0119/nonebot-plugin-user) from 0.3.0 to 0.4.0.
- [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.3.0...v0.4.0)

---
updated-dependencies:
- dependency-name: nonebot-plugin-user
  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-08 07:30:50 +00:00
dependabot[bot]
6f98136c0f ⬆️ Bump nonebot-plugin-alconna from 0.51.0 to 0.51.1 (#390)
Bumps [nonebot-plugin-alconna](https://github.com/nonebot/plugin-alconna) from 0.51.0 to 0.51.1.
- [Release notes](https://github.com/nonebot/plugin-alconna/releases)
- [Commits](https://github.com/nonebot/plugin-alconna/compare/v0.51.0...v0.51.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-08-08 07:27:38 +00:00
dependabot[bot]
62335abaa6 ⬆️ Bump pandas-stubs from 2.2.2.240805 to 2.2.2.240807 (#389)
Bumps [pandas-stubs](https://github.com/pandas-dev/pandas-stubs) from 2.2.2.240805 to 2.2.2.240807.
- [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.240805...v2.2.2.240807)

---
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-08-08 15:24:06 +08:00
12a934566d 🔖 1.4.5 2024-08-07 16:44:13 +08:00
ff71dba516 给截图加个耗时统计 2024-08-07 16:41:52 +08:00
e029d51494 🔥 忘记删测试用配置了 2024-08-07 14:38:09 +08:00
dependabot[bot]
b1f48da6fe ⬆️ Bump nonebot-plugin-alconna from 0.50.3 to 0.51.0 (#388)
Bumps [nonebot-plugin-alconna](https://github.com/nonebot/plugin-alconna) from 0.50.3 to 0.51.0.
- [Release notes](https://github.com/nonebot/plugin-alconna/releases)
- [Commits](https://github.com/nonebot/plugin-alconna/compare/v0.50.3...v0.51.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-08-06 23:43:04 +08:00
9a2927542a 🔥 移除不需要的常量 2024-08-06 20:43:00 +08:00
5117e7dbd9 🔖 1.4.4 2024-08-06 20:12:20 +08:00
4bb00cdeb7 👽️ 移除茶服不可用地址 2024-08-06 20:11:50 +08:00
b7cbe2b2a0 🔥 删除不必要的类型转换
上游修了hhh
2024-08-06 16:35:35 +08:00
8bb460fce0 ⬆️ 更新依赖 2024-08-06 16:34:14 +08:00
41bbcdb66c 🔖 1.4.3 2024-08-06 15:46:11 +08:00
160d81476a 🔥 删除不需要的 type: ignore 2024-08-06 15:35:53 +08:00
1e5b00a280 初步重新适配 TETR.IO query 2024-08-06 15:29:32 +08:00
ee53b92559 🔥 删除不需要的函数调用 2024-08-06 15:28:26 +08:00
cd9d29b748 🚨 修复 pyright 类型报错 2024-08-06 15:27:42 +08:00
214ebc5073 移除对 arclet-alconna 的显式依赖声明 2024-08-06 13:41:13 +08:00
485706267e 🐛 更新 TETR.IO summaries solo 模型 2024-08-06 01:34:07 +08:00
12cb5193b3 🎨 优化模板模型路径
~~真的是优化吗~~
2024-08-06 00:03:02 +08:00
dependabot[bot]
461d3450d6 ⬆️ Bump ruff from 0.5.5 to 0.5.6 (#386)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.5.5 to 0.5.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.5.5...0.5.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-08-05 15:55:56 +00:00
dependabot[bot]
64d77dbff2 ⬆️ Bump pandas-stubs from 2.2.2.240603 to 2.2.2.240805 (#385)
Bumps [pandas-stubs](https://github.com/pandas-dev/pandas-stubs) from 2.2.2.240603 to 2.2.2.240805.
- [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.240603...v2.2.2.240805)

---
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-08-05 15:52:59 +00:00
dependabot[bot]
e5b4d3bc08 ⬆️ Bump arclet-alconna from 1.8.19 to 1.8.21 (#387)
Bumps [arclet-alconna](https://github.com/ArcletProject/Alconna) from 1.8.19 to 1.8.21.
- [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.19...v1.8.21)

---
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-08-05 15:49:32 +00:00
dependabot[bot]
4208018caf ⬆️ Bump nonebot-plugin-localstore from 0.7.0 to 0.7.1 (#384)
Bumps [nonebot-plugin-localstore](https://github.com/nonebot/plugin-localstore) from 0.7.0 to 0.7.1.
- [Release notes](https://github.com/nonebot/plugin-localstore/releases)
- [Commits](https://github.com/nonebot/plugin-localstore/compare/v0.7.0...v0.7.1)

---
updated-dependencies:
- dependency-name: nonebot-plugin-localstore
  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-05 15:45:53 +00:00
dependabot[bot]
5032a3eb9a ⬆️ Bump nonebot-plugin-session from 0.3.1 to 0.3.2 (#383)
Bumps [nonebot-plugin-session](https://github.com/noneplugin/nonebot-plugin-session) from 0.3.1 to 0.3.2.
- [Release notes](https://github.com/noneplugin/nonebot-plugin-session/releases)
- [Commits](https://github.com/noneplugin/nonebot-plugin-session/compare/v0.3.1...v0.3.2)

---
updated-dependencies:
- dependency-name: nonebot-plugin-session
  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-05 23:42:23 +08:00
dependabot[bot]
bf9a9953dd ⬆️ Bump nonebot-adapter-qq from 1.4.4 to 1.5.0 (#381)
Bumps [nonebot-adapter-qq](https://github.com/nonebot/adapter-qq) from 1.4.4 to 1.5.0.
- [Release notes](https://github.com/nonebot/adapter-qq/releases)
- [Commits](https://github.com/nonebot/adapter-qq/compare/v1.4.4...v1.5.0)

---
updated-dependencies:
- dependency-name: nonebot-adapter-qq
  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-05 04:10:49 +00:00
dependabot[bot]
85feb9cb41 ⬆️ Bump mypy from 1.11.0 to 1.11.1 (#382)
Bumps [mypy](https://github.com/python/mypy) from 1.11.0 to 1.11.1.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.11...v1.11.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-08-05 04:07:14 +00:00
5a7c54528c 🐛 修正 record 使用的 type 2024-08-05 12:02:50 +08:00
afce74afe8 修改命令注册逻辑 2024-08-05 11:56:48 +08:00
435850819c 🔖 1.4.2 2024-08-04 19:57:47 +08:00
6f439ad357 适配新模板 2024-08-04 19:22:36 +08:00
b74cc1f4a0 🐛 修复 TETR.IO 获取 user 时出现 UnboundLocalError 2024-08-04 19:21:52 +08:00
1a1c2675d1 再次更新模板仓库处理逻辑 2024-08-03 23:52:45 +08:00
1f02c107f5 AR排行榜 API 模型 2024-08-03 16:47:57 +08:00
89c319a500 完善 PluginMetadata 2024-08-02 22:46:00 +08:00
56f9a69c4d 🙈 更新 .gitignore 2024-08-02 22:19:59 +08:00
50431fe7cb 新赛季排行榜 API 模型 2024-08-02 22:15:46 +08:00
71ad53a1f9 适配 TETR.IO rank v1 模板 2024-08-02 22:15:46 +08:00
820393f216 🎨 减少两个 overload 2024-08-02 22:15:45 +08:00
27994cea6b 🗃️ 清空 TETR.IO 旧赛季数据 2024-08-02 22:15:45 +08:00
呵呵です
eb753cb059 🔖 1.4.1 2024-07-29 17:04:21 +08:00
呵呵です
256d13d1df 适配 TETR.IO 新赛季 (#380)
*  新 api 的 schemas

* 👽️ 更新新赛季 api 的 schemas

*  添加依赖 async-lru

* 👽️ 更新新赛季 api 封装

* 👽️ 适配新赛季 api 40l

* 🐛 api_type 忘记更新了

* 👽️ 适配新赛季 api blitz

* 👽️ 适配新赛季 api bind

* 🔥 暂时删除一些指令
等待新赛季开始

* 🚨 auto fix by pre-commit hooks

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-07-29 17:03:05 +08:00
dependabot[bot]
d8d56b44db ⬆️ Bump ruff from 0.5.4 to 0.5.5 (#378)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.5.4 to 0.5.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.5.4...0.5.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-07-27 08:26:39 +08:00
dependabot[bot]
57a57f0259 ⬆️ Bump playwright from 1.45.0 to 1.45.1 (#377)
Bumps [playwright](https://github.com/Microsoft/playwright-python) from 1.45.0 to 1.45.1.
- [Release notes](https://github.com/Microsoft/playwright-python/releases)
- [Commits](https://github.com/Microsoft/playwright-python/compare/v1.45.0...v1.45.1)

---
updated-dependencies:
- dependency-name: playwright
  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-07-24 02:23:47 +08:00
dependabot[bot]
4f7f4a3e33 ⬆️ Bump mypy from 1.10.1 to 1.11.0 (#375)
Bumps [mypy](https://github.com/python/mypy) from 1.10.1 to 1.11.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.10.1...v1.11)

---
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-07-23 04:48:04 +00:00
dependabot[bot]
367a9a8297 ⬆️ Bump nonebot-plugin-alconna from 0.50.0 to 0.50.2 (#376)
Bumps [nonebot-plugin-alconna](https://github.com/nonebot/plugin-alconna) from 0.50.0 to 0.50.2.
- [Release notes](https://github.com/nonebot/plugin-alconna/releases)
- [Commits](https://github.com/nonebot/plugin-alconna/compare/v0.50.0...v0.50.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-07-23 04:44:34 +00:00
dependabot[bot]
009dd90609 ⬆️ Bump ruff from 0.5.2 to 0.5.4 (#374)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.5.2 to 0.5.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.5.2...0.5.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-07-23 12:41:02 +08:00
8e6e0dc274 更新模板仓库处理逻辑 2024-07-23 12:33:52 +08:00
dependabot[bot]
df7efc6707 ⬆️ Bump nonebot-plugin-orm from 0.7.4 to 0.7.5 (#372)
Bumps [nonebot-plugin-orm](https://github.com/nonebot/plugin-orm) from 0.7.4 to 0.7.5.
- [Release notes](https://github.com/nonebot/plugin-orm/releases)
- [Commits](https://github.com/nonebot/plugin-orm/compare/v0.7.4...v0.7.5)

---
updated-dependencies:
- dependency-name: nonebot-plugin-orm
  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-07-18 20:07:05 +00:00
dependabot[bot]
d783ecd3eb ⬆️ Bump nonebot-plugin-alconna from 0.49.0 to 0.50.0 (#371)
Bumps [nonebot-plugin-alconna](https://github.com/nonebot/plugin-alconna) from 0.49.0 to 0.50.0.
- [Release notes](https://github.com/nonebot/plugin-alconna/releases)
- [Commits](https://github.com/nonebot/plugin-alconna/compare/v0.49.0...v0.50.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-07-19 04:03:31 +08:00
呵呵です
13f3e34f79 📝 添加 star 历史 2024-07-17 13:01:51 +08:00
566509dd46 🔖 1.4.0 2024-07-17 08:13:31 +08:00
737671d7a8 TETR.IO rank 命令使用图片回复 2024-07-17 08:12:30 +08:00
e4f19d1d81 显式声明依赖 arclet-alconna 2024-07-17 07:40:22 +08:00
呵呵です
29c12e9249 👷 更新 TypeCheck CI 2024-07-16 03:59:06 +08:00
dependabot[bot]
1e22dae6f9 ⬆️ Bump ruff from 0.5.1 to 0.5.2 (#369)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.5.1 to 0.5.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.5.1...0.5.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-07-15 15:54:46 +00:00
dependabot[bot]
7b308d30bc ⬆️ Bump zstandard from 0.22.0 to 0.23.0 (#370)
Bumps [zstandard](https://github.com/indygreg/python-zstandard) from 0.22.0 to 0.23.0.
- [Release notes](https://github.com/indygreg/python-zstandard/releases)
- [Changelog](https://github.com/indygreg/python-zstandard/blob/main/docs/news.rst)
- [Commits](https://github.com/indygreg/python-zstandard/compare/0.22.0...0.23.0)

---
updated-dependencies:
- dependency-name: zstandard
  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-07-15 23:50:58 +08:00
dependabot[bot]
ec13ebc43d ⬆️ Bump nonebot-plugin-alconna from 0.48.0 to 0.49.0 (#367)
Bumps [nonebot-plugin-alconna](https://github.com/nonebot/plugin-alconna) from 0.48.0 to 0.49.0.
- [Release notes](https://github.com/nonebot/plugin-alconna/releases)
- [Commits](https://github.com/nonebot/plugin-alconna/compare/v0.48.0...v0.49.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-07-09 15:26:32 +00:00
dependabot[bot]
640ecaea85 ⬆️ Bump nonebot-adapter-onebot from 2.4.3 to 2.4.4 (#368)
Bumps [nonebot-adapter-onebot](https://github.com/nonebot/adapter-onebot) from 2.4.3 to 2.4.4.
- [Release notes](https://github.com/nonebot/adapter-onebot/releases)
- [Commits](https://github.com/nonebot/adapter-onebot/compare/v2.4.3...v2.4.4)

---
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-07-09 15:23:13 +00:00
dependabot[bot]
ef772a97ba ⬆️ Bump nonebot2 from 2.3.1 to 2.3.2 (#366)
Bumps [nonebot2](https://github.com/nonebot/nonebot2) from 2.3.1 to 2.3.2.
- [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.1...v2.3.2)

---
updated-dependencies:
- dependency-name: nonebot2
  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-07-09 23:20:00 +08:00
dependabot[bot]
e6cc6a8451 ⬆️ Bump ruff from 0.5.0 to 0.5.1 (#364)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.5.0 to 0.5.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.5.0...0.5.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-07-09 23:15:35 +08:00
0989090456 🔖 1.3.6 2024-07-08 00:00:53 +08:00
f8fc9ebdf8 🐛 修正 TETR.IO records Tspins 数据异常 2024-07-07 23:19:04 +08:00
dependabot[bot]
ac2b115bd6 ⬆️ Bump certifi from 2024.6.2 to 2024.7.4 (#365)
Bumps [certifi](https://github.com/certifi/python-certifi) from 2024.6.2 to 2024.7.4.
- [Commits](https://github.com/certifi/python-certifi/compare/2024.06.02...2024.07.04)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-06 18:18:10 +08:00
dependabot[bot]
46eb9b4517 ⬆️ Bump playwright from 1.44.0 to 1.45.0 (#363)
Bumps [playwright](https://github.com/Microsoft/playwright-python) from 1.44.0 to 1.45.0.
- [Release notes](https://github.com/Microsoft/playwright-python/releases)
- [Commits](https://github.com/Microsoft/playwright-python/compare/v1.44.0...v1.45.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-07-04 03:05:47 +00:00
dependabot[bot]
eaa81f8157 ⬆️ Bump pillow from 10.3.0 to 10.4.0 (#362)
Bumps [pillow](https://github.com/python-pillow/Pillow) from 10.3.0 to 10.4.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.3.0...10.4.0)

---
updated-dependencies:
- dependency-name: pillow
  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-07-04 11:02:02 +08:00
e2eb288b90 🔖 1.3.5 2024-07-01 14:49:15 +08:00
ed95c8c9fa 更新模板 2024-07-01 14:33:27 +08:00
dependabot[bot]
7138e91b2e ⬆️ Bump nonebot-plugin-apscheduler from 0.4.0 to 0.5.0 (#360)
Bumps [nonebot-plugin-apscheduler](https://github.com/nonebot/plugin-apscheduler) from 0.4.0 to 0.5.0.
- [Release notes](https://github.com/nonebot/plugin-apscheduler/releases)
- [Commits](https://github.com/nonebot/plugin-apscheduler/compare/v0.4.0...v0.5.0)

---
updated-dependencies:
- dependency-name: nonebot-plugin-apscheduler
  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-06-29 04:24:56 +00:00
dependabot[bot]
50642866b2 ⬆️ Bump nonebot-plugin-user from 0.2.0 to 0.3.0 (#361)
Bumps [nonebot-plugin-user](https://github.com/he0119/nonebot-plugin-user) from 0.2.0 to 0.3.0.
- [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.2.0...v0.3.0)

---
updated-dependencies:
- dependency-name: nonebot-plugin-user
  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-06-29 04:23:44 +00:00
dependabot[bot]
92c91f2388 ⬆️ Bump nonebot-plugin-orm from 0.7.3 to 0.7.4 (#359)
Bumps [nonebot-plugin-orm](https://github.com/nonebot/plugin-orm) from 0.7.3 to 0.7.4.
- [Release notes](https://github.com/nonebot/plugin-orm/releases)
- [Commits](https://github.com/nonebot/plugin-orm/compare/v0.7.3...v0.7.4)

---
updated-dependencies:
- dependency-name: nonebot-plugin-orm
  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-06-29 12:22:31 +08:00
dependabot[bot]
42d1bc9a5f ⬆️ Bump nonebot-plugin-localstore from 0.6.0 to 0.7.0 (#358)
Bumps [nonebot-plugin-localstore](https://github.com/nonebot/plugin-localstore) from 0.6.0 to 0.7.0.
- [Release notes](https://github.com/nonebot/plugin-localstore/releases)
- [Commits](https://github.com/nonebot/plugin-localstore/compare/v0.6.0...v0.7.0)

---
updated-dependencies:
- dependency-name: nonebot-plugin-localstore
  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-06-29 12:22:12 +08:00
c8013a080c 🔖 1.3.4 2024-06-28 15:52:45 +08:00
8bdde936f8 完善 trigger 2024-06-28 15:52:07 +08:00
aacf518004 TETR.IO 添加 list 命令 2024-06-28 15:07:48 +08:00
34c857387e 👷 添加 TypeCheck CI 2024-06-28 14:09:37 +08:00
abc2ac07ef 🚨 ruff auto fix 2024-06-28 06:10:59 +08:00
dependabot[bot]
43d7972cc1 ⬆️ Bump ruff from 0.4.10 to 0.5.0 (#357)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.10 to 0.5.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/v0.4.10...0.5.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-06-28 04:24:05 +08:00
84a7a70183 🔥 移除不需要的 type alias 2024-06-27 14:00:06 +08:00
f61bbd00b7 ️ 优化截图逻辑 2024-06-27 13:53:34 +08:00
84b74278a6 添加截图质量配置项 2024-06-27 13:52:15 +08:00
dependabot[bot]
1438ad5efb ⬆️ Bump nonebot-plugin-alconna from 0.46.6 to 0.48.0 (#356)
* ⬆️ Bump nonebot-plugin-alconna from 0.46.6 to 0.48.0

Bumps [nonebot-plugin-alconna](https://github.com/nonebot/plugin-alconna) from 0.46.6 to 0.48.0.
- [Release notes](https://github.com/nonebot/plugin-alconna/releases)
- [Commits](https://github.com/nonebot/plugin-alconna/compare/v0.46.6...v0.48.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>

* Update pyproject.toml

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: 呵呵です <51957264+shoucandanghehe@users.noreply.github.com>
2024-06-27 11:45:18 +08:00
01e85960fa 为 TETR.IO config record 添加命令历史记录 2024-06-27 11:40:42 +08:00
dependabot[bot]
c705610c1d ⬆️ Bump types-aiofiles from 23.2.0.20240623 to 24.1.0.20240626 (#354)
Bumps [types-aiofiles](https://github.com/python/typeshed) from 23.2.0.20240623 to 24.1.0.20240626.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-aiofiles
  dependency-type: direct:development
  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-06-27 02:06:27 +08:00
5f0799d505 📌 暂时固定 nonebot-plugin-alconna 版本为 0.46.6 2024-06-26 18:32:05 +08:00
dependabot[bot]
3454e0afbe ⬆️ Bump mypy from 1.10.0 to 1.10.1 (#353)
Bumps [mypy](https://github.com/python/mypy) from 1.10.0 to 1.10.1.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.10.0...v1.10.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-06-26 16:39:02 +08:00
95d9b74cd7 🔖 1.3.3 2024-06-25 21:50:27 +08:00
4b5f0263e4 🐛 修正 TETR.IO records max 数据异常 2024-06-25 21:49:42 +08:00
7500640330 👷 添加 pre-commit hook 2024-06-25 09:50:50 +08:00
967a028235 🐛 修复 AlconnaMatcher 对 Alconna 的引用变成弱引用导致的问题 2024-06-25 09:24:39 +08:00
dependabot[bot]
abe5e30ede ⬆️ Bump nonebot-adapter-discord from 0.1.7 to 0.1.8 (#349)
Bumps [nonebot-adapter-discord](https://github.com/nonebot/adapter-discord) from 0.1.7 to 0.1.8.
- [Release notes](https://github.com/nonebot/adapter-discord/releases)
- [Commits](https://github.com/nonebot/adapter-discord/compare/v0.1.7...v0.1.8)

---
updated-dependencies:
- dependency-name: nonebot-adapter-discord
  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-06-25 08:49:27 +08:00
dependabot[bot]
d9d3f63118 ⬆️ Bump aiofiles from 23.2.1 to 24.1.0 (#350)
Bumps [aiofiles](https://github.com/Tinche/aiofiles) from 23.2.1 to 24.1.0.
- [Release notes](https://github.com/Tinche/aiofiles/releases)
- [Commits](https://github.com/Tinche/aiofiles/compare/v23.2.1...v24.1.0)

---
updated-dependencies:
- dependency-name: aiofiles
  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-06-25 08:49:09 +08:00
dependabot[bot]
4f864c54bc ⬆️ Bump types-aiofiles from 23.2.0.20240403 to 23.2.0.20240623 (#351)
Bumps [types-aiofiles](https://github.com/python/typeshed) from 23.2.0.20240403 to 23.2.0.20240623.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-aiofiles
  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-06-25 08:49:00 +08:00
dependabot[bot]
2474f77291 ⬆️ Bump nonebot-plugin-alconna from 0.47.1 to 0.47.2 (#352)
Bumps [nonebot-plugin-alconna](https://github.com/nonebot/plugin-alconna) from 0.47.1 to 0.47.2.
- [Release notes](https://github.com/nonebot/plugin-alconna/releases)
- [Commits](https://github.com/nonebot/plugin-alconna/compare/v0.47.1...v0.47.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-06-25 08:48:51 +08:00
渣渣120
6291a2ba70 更新 TETR.IO 模板 (#348)
*  更新路径以匹配模板

*  添加历史数据

* 🎨 优化模板模型代码结构

---------

Co-authored-by: shoucandanghehe <wallfjjd@gmail.com>
2024-06-25 08:47:35 +08:00
77b10a858e 🔖 1.3.2 2024-06-22 11:58:42 +08:00
e908b3b67f 🐛 修复 v2 头图 revision 参数错误 2024-06-22 11:57:58 +08:00
dependabot[bot]
bc98c0a3e6 ⬆️ Bump ruff from 0.4.9 to 0.4.10 (#347)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.9 to 0.4.10.
- [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/v0.4.9...v0.4.10)

---
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-06-22 01:26:49 +08:00
渣渣120
f29caf4dc6 ✏️ 修正提示文本 (#344) 2024-06-20 22:43:35 +08:00
dependabot[bot]
a1e88dd1c9 ⬆️ Bump nonebot-adapter-satori from 0.12.2 to 0.12.3 (#342)
Bumps [nonebot-adapter-satori](https://github.com/nonebot/adapter-satori) from 0.12.2 to 0.12.3.
- [Release notes](https://github.com/nonebot/adapter-satori/releases)
- [Commits](https://github.com/nonebot/adapter-satori/compare/v0.12.2...v0.12.3)

---
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-06-20 08:55:02 +08:00
dependabot[bot]
0dcfa53bcc ⬆️ Bump nonebot-plugin-alconna from 0.46.6 to 0.47.1 (#343)
Bumps [nonebot-plugin-alconna](https://github.com/nonebot/plugin-alconna) from 0.46.6 to 0.47.1.
- [Release notes](https://github.com/nonebot/plugin-alconna/releases)
- [Commits](https://github.com/nonebot/plugin-alconna/compare/v0.46.6...v0.47.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-06-20 08:54:45 +08:00
bf4ccdfd61 🔖 1.3.1 2024-06-18 17:26:45 +08:00
ae65b5140f 🐛 修正 max 数据 2024-06-18 17:26:04 +08:00
95aa5b0419 🐛 修正 spp 算法 2024-06-18 17:22:22 +08:00
b7b92cd785 🔖 1.3.0 2024-06-16 10:48:35 +08:00
f97ae15969 TETR.IO 添加 record 命令 2024-06-16 10:47:50 +08:00
aae43df953 🔥 删除不必要的导入 2024-06-16 09:05:48 +08:00
c58f124f0c 命令历史记录添加两种类型 2024-06-16 09:02:29 +08:00
2f900d0538 🐛 avatar_revision 为 0 时 使用 identicon 2024-06-16 09:01:17 +08:00
3e75a4b4e2 适配 TETR.IO record 模板 2024-06-16 08:11:48 +08:00
e285ccfa15 ⬆️ 更新依赖 2024-06-16 08:11:47 +08:00
dependabot[bot]
d2acbaa0ad ⬆️ Bump nonebot-plugin-alconna from 0.46.5 to 0.46.6 (#340)
Bumps [nonebot-plugin-alconna](https://github.com/nonebot/plugin-alconna) from 0.46.5 to 0.46.6.
- [Release notes](https://github.com/nonebot/plugin-alconna/releases)
- [Commits](https://github.com/nonebot/plugin-alconna/compare/v0.46.5...v0.46.6)

---
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-06-16 08:11:47 +08:00
dependabot[bot]
c81be48585 ⬆️ Bump ruff from 0.4.8 to 0.4.9 (#339)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.8 to 0.4.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/v0.4.8...v0.4.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-06-16 08:11:47 +08:00
dependabot[bot]
93ec0d8808 ⬆️ Bump nonebot-adapter-satori from 0.11.5 to 0.12.0 (#338)
Bumps [nonebot-adapter-satori](https://github.com/nonebot/adapter-satori) from 0.11.5 to 0.12.0.
- [Release notes](https://github.com/nonebot/adapter-satori/releases)
- [Commits](https://github.com/nonebot/adapter-satori/compare/v0.11.5...v0.12.0)

---
updated-dependencies:
- dependency-name: nonebot-adapter-satori
  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-06-16 08:11:46 +08:00
d5e07880fd TETR.IO api 添加一些属性
快捷方式(x
2024-06-16 08:11:46 +08:00
8b370f152d 复用 Zen 的模型 2024-06-14 07:56:05 +08:00
e8527c7ba4 添加 RecordNotFoundError 异常类型 2024-06-14 07:46:51 +08:00
1dd3d310c9 适配 TETR.IO record 模板 2024-06-14 07:45:47 +08:00
b08685086a 🐛 忘记 require nonebot_plugin_userinfo 了 2024-06-14 01:41:22 +08:00
dependabot[bot]
c2b6fe920f ⬆️ Bump nonebot-plugin-alconna from 0.46.4 to 0.46.5 (#337)
Bumps [nonebot-plugin-alconna](https://github.com/nonebot/plugin-alconna) from 0.46.4 to 0.46.5.
- [Release notes](https://github.com/nonebot/plugin-alconna/releases)
- [Commits](https://github.com/nonebot/plugin-alconna/compare/v0.46.4...v0.46.5)

---
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-06-13 04:53:50 +08:00
a1ad86d0c7 🔖 1.2.15 2024-06-11 02:02:25 +08:00
e6260ce170 添加 TETR.IO rank 的快捷指令 2024-06-11 02:01:23 +08:00
b0e53bc8c8 🔖 1.2.14 2024-06-10 12:03:34 +08:00
2267bc8f14 🐛 修复快捷方式 2024-06-10 12:03:04 +08:00
607a0927bc TETR.IO 默认模板可配置 2024-06-10 11:57:01 +08:00
7b3ca9eb2a 🚚 TETRIOUserConfig 写错地方了 2024-06-10 11:17:49 +08:00
37c12e439c 🔖 1.2.13 2024-06-10 10:58:19 +08:00
504579710e TETR.IO 适配 v2模板 2024-06-10 10:56:56 +08:00
ce94aee0f4 🔖 1.2.12 2024-06-10 02:37:29 +08:00
b9c58ae125 修改插件元数据 2024-06-10 02:37:12 +08:00
92159e93b8 直接使用 fstring 生成链接 2024-06-10 02:35:07 +08:00
f9b11895e2 使用缓存 2024-06-10 02:34:30 +08:00
f7c3d493ea TETR.IO 适配 v2模板 2024-06-10 02:13:03 +08:00
4954ab3d60 TETR.IO 适配 v2模板 2024-06-10 00:48:13 +08:00
bcca869e72 重置命令为shell style,并使用快捷方式保留之前的行为 2024-06-10 00:47:17 +08:00
a4247abdad 添加 TETR.IO 部分资源的缓存 2024-06-10 00:39:36 +08:00
2c1d43601a 🙈 更新.gitignore 2024-06-10 00:31:30 +08:00
c929c463ec 添加 img_to_png 方法 2024-06-10 00:30:54 +08:00
314e1dede3 🔥 去除截图等待状态时的超时 2024-06-10 00:26:26 +08:00
d5b0ef34c5 更新 v2 模板模型 2024-06-10 00:25:21 +08:00
3d9ef841b1 添加两个新异常类型 2024-06-10 00:09:41 +08:00
b98871f170 🔥 删除 HandleNotFinishedError 2024-06-10 00:07:03 +08:00
38ab872dd8 🗃️ 添加 TETRIOUserConfig 2024-06-09 04:24:48 +08:00
f44c0baa2e 🚨 添加一些 type: ignore 2024-06-09 03:59:08 +08:00
9b8d17577e 🔥 删除 get_platform 函数 2024-06-08 12:13:20 +08:00
f301bee2b0 🔥 不小心把测试用注释传上去了) 2024-06-08 12:12:28 +08:00
fbe018e56a 🔖 1.2.11 2024-06-08 12:04:44 +08:00
ab046fe786 使用 nonebot-plugin-user 进行身份绑定 close #63 2024-06-08 12:03:07 +08:00
ce95d8f977 完善 retry 装饰器 2024-06-08 11:57:45 +08:00
fa05b80e61 修改截图方式 2024-06-08 11:57:21 +08:00
0ab0d11a98 添加依赖 nonebot-plugin-user 2024-06-08 11:55:47 +08:00
7f469540b2 ️ 删除一个不必要的async 2024-06-08 00:55:42 +08:00
21bee29146 适配 v2 模板 2024-06-07 21:18:12 +08:00
dependabot[bot]
c2dd9c5d86 ⬆️ Bump nonebot-plugin-alconna from 0.46.3 to 0.46.4 (#333)
Bumps [nonebot-plugin-alconna](https://github.com/nonebot/plugin-alconna) from 0.46.3 to 0.46.4.
- [Release notes](https://github.com/nonebot/plugin-alconna/releases)
- [Commits](https://github.com/nonebot/plugin-alconna/compare/v0.46.3...v0.46.4)

---
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-06-06 21:33:51 +08:00
dependabot[bot]
5927cb2bb5 ⬆️ Bump pandas-stubs from 2.2.2.240514 to 2.2.2.240603 (#334)
Bumps [pandas-stubs](https://github.com/pandas-dev/pandas-stubs) from 2.2.2.240514 to 2.2.2.240603.
- [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.240514...v2.2.2.240603)

---
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-06-06 21:33:35 +08:00
dependabot[bot]
4a4a215b61 ⬆️ Bump ruff from 0.4.6 to 0.4.8 (#336)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.6 to 0.4.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/v0.4.6...v0.4.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-06-06 21:33:15 +08:00
bfe931d3bf 为截图添加自动重试 2024-06-04 20:15:41 +08:00
b7b152d84d 完善 retry 装饰器的类型,并添加消息提示功能 2024-06-04 20:14:19 +08:00
b6f6eb1170 sprint 成绩保留三位小数 2024-06-04 20:12:56 +08:00
934800aae0 🐛 修正 UniMessage 的使用方式 2024-06-04 20:10:57 +08:00
dependabot[bot]
d19c37e99a ⬆️ Bump nonebot-plugin-alconna from 0.46.1 to 0.46.3 (#331)
Bumps [nonebot-plugin-alconna](https://github.com/nonebot/plugin-alconna) from 0.46.1 to 0.46.3.
- [Release notes](https://github.com/nonebot/plugin-alconna/releases)
- [Commits](https://github.com/nonebot/plugin-alconna/compare/v0.46.1...v0.46.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-06-01 14:50:10 +08:00
dependabot[bot]
43167fe9bd ⬆️ Bump ruff from 0.4.4 to 0.4.6 (#330)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.4 to 0.4.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/v0.4.4...v0.4.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-06-01 14:50:00 +08:00
dependabot[bot]
db8de88667 ⬆️ Bump nonebot-plugin-orm from 0.7.2 to 0.7.3 (#327)
updated-dependencies:
- dependency-name: nonebot-plugin-orm
  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-06-01 14:49:44 +08:00
318b42dbd2 🔖 1.2.10 2024-05-22 22:12:46 +08:00
af4a9f33b0 🐛 修复查用户名/id时数据不更新的bug
player实例被alconna缓存
2024-05-22 22:01:19 +08:00
dependabot[bot]
5e5bc4da2c ⬆️ Bump playwright from 1.43.0 to 1.44.0 (#322)
Bumps [playwright](https://github.com/Microsoft/playwright-python) from 1.43.0 to 1.44.0.
- [Release notes](https://github.com/Microsoft/playwright-python/releases)
- [Commits](https://github.com/Microsoft/playwright-python/compare/v1.43.0...v1.44.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-05-22 15:27:42 +08:00
dependabot[bot]
594ea9a76f ⬆️ Bump types-pillow from 10.2.0.20240511 to 10.2.0.20240520 (#324)
Bumps [types-pillow](https://github.com/python/typeshed) from 10.2.0.20240511 to 10.2.0.20240520.
- [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-05-22 15:27:33 +08:00
dependabot[bot]
69e9ca7933 ⬆️ Bump nonebot2 from 2.3.0 to 2.3.1 (#325)
Bumps [nonebot2](https://github.com/nonebot/nonebot2) from 2.3.0 to 2.3.1.
- [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.0...v2.3.1)

---
updated-dependencies:
- dependency-name: nonebot2
  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-05-22 15:27:23 +08:00
dependabot[bot]
b1bc111b7a ⬆️ Bump nonebot-plugin-alconna from 0.45.4 to 0.46.1 (#326)
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-05-22 15:26:35 +08:00
43970f4853 TOS 查数据使用图片回复 2024-05-19 15:04:42 +08:00
48b200697c 添加开发依赖 nonebot2[all] 2024-05-19 13:33:22 +08:00
1a791f5ef8 🎨 重命名 TETRIOInfo 类 2024-05-17 10:45:21 +08:00
9b13a9e87c 🔖 1.2.9 2024-05-16 18:04:48 +08:00
ecad6b8070 🐛 修复 TETR.IO User Records 解析失败的bug 2024-05-16 18:04:05 +08:00
1e6932b3de 🔥 删除无用代码 2024-05-16 06:06:16 +08:00
3ef7605e11 🎨 重命名一些模块 2024-05-16 05:59:18 +08:00
dependabot[bot]
e8539c15cc ⬆️ Bump types-ujson from 5.9.0.0 to 5.10.0.20240515 (#321)
Bumps [types-ujson](https://github.com/python/typeshed) from 5.9.0.0 to 5.10.0.20240515.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-ujson
  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-05-16 05:42:52 +08:00
dependabot[bot]
9ace65f9df ⬆️ Bump pandas-stubs from 2.2.1.240316 to 2.2.2.240514 (#320)
Bumps [pandas-stubs](https://github.com/pandas-dev/pandas-stubs) from 2.2.1.240316 to 2.2.2.240514.
- [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.1.240316...v2.2.2.240514)

---
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-05-16 05:42:41 +08:00
dependabot[bot]
d727a0bc53 ⬆️ Bump ujson from 5.9.0 to 5.10.0 (#319)
Bumps [ujson](https://github.com/ultrajson/ultrajson) from 5.9.0 to 5.10.0.
- [Release notes](https://github.com/ultrajson/ultrajson/releases)
- [Commits](https://github.com/ultrajson/ultrajson/compare/5.9.0...5.10.0)

---
updated-dependencies:
- dependency-name: ujson
  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-05-16 05:42:30 +08:00
52947556a4 🐛 忘记把注释的代码取消注释了 2024-05-16 05:41:33 +08:00
7fe9a6fd3d 🔖 1.2.8 2024-05-15 14:56:26 +08:00
6dbfd31eab 🐛 修复 TETR.IO User Records 解析失败的bug 2024-05-15 14:53:43 +08:00
1788d40ed2 🔖 1.2.7 2024-05-15 13:34:55 +08:00
18d8e0cdcc 将本地存储的 TetraLeague FullExport 数据聚合进查询图 2024-05-15 13:34:28 +08:00
b37f927be6 添加 debug 依赖 memory-profiler 2024-05-15 09:14:13 +08:00
314bf4c2f0 🔇 忘记删 debug 日志了 2024-05-15 09:12:24 +08:00
c9f6817c6a 🔖 1.2.6 2024-05-15 08:56:46 +08:00
4c7cd00a76 🐛 修复 histories 只有一条时推算数据出现除数为0的bug 2024-05-15 08:56:22 +08:00
b8cf10b45d 🔖 1.2.5 2024-05-15 04:43:30 +08:00
4ec5c3bde1 🐛 修复 TETR.IO 大写用户名查询失败 2024-05-15 04:42:27 +08:00
270b953bc9 🔖 1.2.4 2024-05-15 04:22:58 +08:00
13bd0da592 🐛 修复去重添加没有正确工作的bug 2024-05-15 04:22:19 +08:00
9545f0b5d0 🔖 1.2.3 2024-05-14 17:26:07 +08:00
12f320cbb4 🐛 修复 PydanticType 过早加载导致获取不到子类的bug 2024-05-14 17:25:42 +08:00
7ff59cfc01 🔖 1.2.2 2024-05-14 17:09:53 +08:00
498781f376 ✏️ 变量名写错了 2024-05-14 17:09:29 +08:00
a3c00dbd93 🔖 1.2.1 2024-05-14 17:00:33 +08:00
069d5953f9 🐛 修复 TETR.IO User Records 解析失败的bug 2024-05-14 17:00:07 +08:00
3721d92f52 🔇 忘记删 debug 日志了 2024-05-14 16:20:57 +08:00
98b58866e1 🔖 1.2.0 2024-05-14 15:40:05 +08:00
dependabot[bot]
189c3999f7 ⬆️ Bump lxml from 5.2.1 to 5.2.2 (#316)
Bumps [lxml](https://github.com/lxml/lxml) from 5.2.1 to 5.2.2.
- [Release notes](https://github.com/lxml/lxml/releases)
- [Changelog](https://github.com/lxml/lxml/blob/master/CHANGES.txt)
- [Commits](https://github.com/lxml/lxml/compare/lxml-5.2.1...lxml-5.2.2)

---
updated-dependencies:
- dependency-name: lxml
  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-05-14 15:39:31 +08:00
dependabot[bot]
a2622d5102 ⬆️ Bump types-pillow from 10.2.0.20240423 to 10.2.0.20240511 (#317)
Bumps [types-pillow](https://github.com/python/typeshed) from 10.2.0.20240423 to 10.2.0.20240511.
- [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-05-14 15:04:19 +08:00
呵呵です
c8832bd1c9 ♻️ Refactor (#318)
* 🔧 启用一些 ruff 的新规则

*  添加开发依赖 nonebot-adapter-qq

*  添加依赖 nonebot-plugin-session

*  添加依赖 nonebot-plugin-session-orm

* 🔧 忽略 ruff 规则 ISC001
format 冲突风险

* 🚨 修复 ruff 警报

* ♻️ 重构!

* ♻️ 恢复定时获取 TetraLeague 数据的功能

*  统一处理需要捕获的错误

*  记录用户触发的指令
2024-05-14 15:03:46 +08:00
e6c3a32532 🔖 1.1.5 2024-05-13 04:20:02 +08:00
b3015aaa91 格式化 rating 新增千分位分隔符 2024-05-13 04:19:27 +08:00
dependabot[bot]
abc1038082 ⬆️ Bump ruff from 0.4.3 to 0.4.4 (#315)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.3 to 0.4.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/v0.4.3...v0.4.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-05-11 00:44:54 +08:00
dependabot[bot]
dd91455890 ⬆️ Bump viztracer from 0.16.2 to 0.16.3 (#314)
Bumps [viztracer](https://github.com/gaogaotiantian/viztracer) from 0.16.2 to 0.16.3.
- [Release notes](https://github.com/gaogaotiantian/viztracer/releases)
- [Commits](https://github.com/gaogaotiantian/viztracer/compare/0.16.2...0.16.3)

---
updated-dependencies:
- dependency-name: viztracer
  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-05-11 00:44:30 +08:00
dependabot[bot]
4b17b0b907 ⬆️ Bump nonebot-plugin-alconna from 0.45.3 to 0.45.4 (#311)
Bumps [nonebot-plugin-alconna](https://github.com/nonebot/plugin-alconna) from 0.45.3 to 0.45.4.
- [Release notes](https://github.com/nonebot/plugin-alconna/releases)
- [Commits](https://github.com/nonebot/plugin-alconna/compare/v0.45.3...v0.45.4)

---
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-05-10 12:39:57 +08:00
ac4631d1f3 🔖 1.1.4 2024-05-10 12:20:48 +08:00
b0ee7fe6c7 🐛 修复 TETR.IO 默认头像传参错误 2024-05-10 12:20:25 +08:00
5bcecc0623 🔖 1.1.3 2024-05-10 12:11:19 +08:00
9cf048fce4 新增 更新模板 指令 2024-05-10 12:10:45 +08:00
aff2fa120a 🔖 1.1.2 2024-05-10 11:49:05 +08:00
1c057661c2 🐛 防止历史记录一条都没有的数组越界 2024-05-10 11:48:47 +08:00
83bcd14012 🔖 1.1.1 2024-05-10 11:30:08 +08:00
70f53a2c76 🐛 修复 init_templates 和 mount assets 的运行顺序问题 2024-05-10 11:29:46 +08:00
6df70f621e 🔖 1.1.0 2024-05-10 11:14:38 +08:00
8ba3f3c3f4 🎨 拆分函数 2024-05-10 11:13:39 +08:00
a5c4e7df5c 🐛 修正 TETR.IO Badge 模型定义 2024-05-10 09:46:09 +08:00
66db7a8a28 🚨 判断 cookie 是否拥有对应字段 2024-05-10 09:42:15 +08:00
呵呵です
716e392a3a 使用新版模板 (#313)
* 🔥 删除现有模板

*  自动克隆模板仓库

* 🔥 删除 identicon 相关代码

* 🚚 修改静态文件路径

*  使用新模板进行渲染

*  每次渲染都获取一次模板, 以应对实时更新

*  TETR.IO 绑定图使用新模板

* 🚚 修改网络路径

*  TOP 绑定图使用新模板

*  TOS 绑定图使用新模板

* 🐛 防止截图超时

* 🐛 Pydantic V1 会把 float 转换成 int

* ✏️ 模板字段名写错了

*  兼容 Pydantic V1

*  TETR.IO 查询图使用新模板

* 🐛 在查询的用户没有历史记录时不去查询更多记录
2024-05-10 09:41:05 +08:00
呵呵です
e47f1bb6f9 渲染 历史tr 曲线图 (#312) 2024-05-08 18:26:08 +08:00
03d34c5572 🔖 1.0.4 2024-05-07 17:22:46 +08:00
04b480ef52 🗃️ HistoricalData 添加 user_unique_identifier 字段 2024-05-07 17:21:52 +08:00
5563b01937 Revert " 为使用了 alias 的 pydantic model 设置 populate_by_name"
This reverts commit 17690e673f.
2024-05-07 08:51:55 +08:00
273 changed files with 16568 additions and 7226 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@v6
- name: Install poetry
run: pipx install poetry
shell: bash
- uses: actions/setup-python@v4
- uses: astral-sh/setup-uv@v7
name: Setup UV
with:
python-version: '3.11'
cache: "poetry"
enable-cache: true
- run: poetry install
- name: 'Set up Python'
uses: actions/setup-python@v6
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

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

@@ -0,0 +1,57 @@
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", "3.14"]
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@v6
- name: Setup uv
uses: astral-sh/setup-uv@v7
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@a638d6464689bbb24c325bb3fe9404d63a913030
with:
jobs: ${{ toJSON(needs) }}

33
.github/workflows/TypeCheck.yml vendored Normal file
View File

@@ -0,0 +1,33 @@
name: TypeCheck
on:
push:
jobs:
TypeCheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: astral-sh/setup-uv@v7
name: Setup UV
with:
enable-cache: true
- name: 'Set up Python'
uses: actions/setup-python@v6
with:
python-version-file: '.python-version'
- run: uv sync
shell: bash
- 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@v6
# 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@v4
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@v4
# 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@v4

545
.gitignore vendored
View File

@@ -1,21 +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
.env*
# Misc
ignore_*
*.backup
TODO*

22
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +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'
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.1
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]

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

@@ -41,10 +41,10 @@
alt="Gitmoji"
/>
</a>
<a href="https://wakatime.com/badge/user/138b2226-8e02-42be-b99d-35c05198836f/project/65f5bdf7-45ec-479a-8dd2-18c498c910ca">
<img
src="https://wakatime.com/badge/user/138b2226-8e02-42be-b99d-35c05198836f/project/65f5bdf7-45ec-479a-8dd2-18c498c910ca.svg"
alt="wakatime"
<a href="https://wakatime.com/badge/user/138b2226-8e02-42be-b99d-35c05198836f/project/e26c7985-a236-4e76-90a6-a72f71d305ef">
<img
src="https://wakatime.com/badge/user/138b2226-8e02-42be-b99d-35c05198836f/project/e26c7985-a236-4e76-90a6-a72f71d305ef.svg"
alt="wakatime"
/>
</a>
</p>
@@ -87,3 +87,13 @@ pip install nonebot-plugin-tetris-stats
## 📝 开源
本项目使用 [AGPL-3.0](https://github.com/shoucandanghehe/nonebot-plugin-tetris-stats/blob/main/LICENSE) 许可证开源
## 🤓☝ 给个 star 吧
<a href="https://star-history.com/#A-Minos/nonebot-plugin-tetris-stats&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=A-Minos/nonebot-plugin-tetris-stats&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=A-Minos/nonebot-plugin-tetris-stats&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=A-Minos/nonebot-plugin-tetris-stats&type=Date" />
</picture>
</a>

View File

@@ -1,23 +1,38 @@
from nonebot import require
from nonebot.plugin import PluginMetadata
from nonebot.plugin import PluginMetadata, inherit_supported_adapters
require('nonebot_plugin_localstore')
require('nonebot_plugin_orm')
require('nonebot_plugin_alconna')
require('nonebot_plugin_apscheduler')
require_plugins = {
'nonebot_plugin_alconna',
'nonebot_plugin_apscheduler',
'nonebot_plugin_localstore',
'nonebot_plugin_orm',
'nonebot_plugin_uninfo',
'nonebot_plugin_user',
'nonebot_plugin_waiter',
}
from .config.config import migrations # noqa: E402
for i in require_plugins:
require(i)
from nonebot_plugin_alconna import namespace # noqa: E402
with namespace('tetris_stats') as ns:
ns.enable_message_cache = False
from .config import migrations # noqa: E402
from .config.config import Config # noqa: E402
__plugin_meta__ = PluginMetadata(
name='Tetris Stats',
description='一个用于查询 Tetris 相关游戏玩家数据的插件',
usage='发送 {游戏名} --help 查询使用方法',
usage='发送 tstats --help 查询使用方法',
type='application',
homepage='https://github.com/A-minos/nonebot-plugin-tetris-stats',
config=Config,
supported_adapters=inherit_supported_adapters(*require_plugins),
extra={
'orm_version_location': migrations,
},
)
from . import game_data_processor # noqa: F401, E402
from .utils import host # noqa: F401, E402
from . import games # noqa: F401, E402

View File

@@ -1,14 +1,37 @@
from pathlib import Path
from nonebot_plugin_localstore import get_cache_dir # type: ignore[import-untyped]
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
from . import migrations # noqa: F401
CACHE_PATH = get_plugin_cache_dir()
DATA_PATH = get_plugin_data_dir()
CACHE_PATH: Path = get_cache_dir('nonebot_plugin_tetris_stats')
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
config = get_plugin_config(Config)
global_config = get_driver().config

View File

@@ -8,11 +8,14 @@
from __future__ import annotations
from collections.abc import Sequence
from typing import TYPE_CHECKING
import sqlalchemy as sa
from alembic import op
if TYPE_CHECKING:
from collections.abc import Sequence
revision: str = '09d4bb60160d'
down_revision: str | Sequence[str] | None = 'b9d65badc713'
branch_labels: str | Sequence[str] | None = None
@@ -22,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())
@@ -38,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

@@ -8,11 +8,14 @@
from __future__ import annotations
from collections.abc import Sequence
from typing import TYPE_CHECKING
import sqlalchemy as sa
from alembic import op
if TYPE_CHECKING:
from collections.abc import Sequence
revision: str = '0d50142b780f'
down_revision: str | Sequence[str] | None = '09d4bb60160d'
branch_labels: str | Sequence[str] | None = None
@@ -22,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))
@@ -35,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,160 @@
"""fix json storage
迁移 ID: 1c5346b657d4
父迁移: 2ff388a8c486
创建时间: 2026-01-30 03:35:00
"""
from __future__ import annotations
from typing import TYPE_CHECKING
import msgspec
import sqlalchemy as sa
from alembic import op
from nonebot.log import logger
from sqlalchemy.dialects.postgresql import ARRAY, JSONB
if TYPE_CHECKING:
from collections.abc import Sequence
from sqlalchemy.engine import Connection
_LOG_INTERVAL = 10000
_BATCH_SIZE = 1000
_PG_CHUNK_SIZE = 50000
_SQLITE_FETCH_SIZE = 500
revision: str = '1c5346b657d4'
down_revision: str | Sequence[str] | None = '2ff388a8c486'
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
tables: dict[str, list[str]] = {
'nb_t_io_tl_stats_field': [
'low_pps',
'low_apm',
'low_vs',
'high_pps',
'high_apm',
'high_vs',
],
'nb_t_io_hist_data': ['data'],
'nb_t_top_hist_data': ['data'],
'nb_t_tos_hist_data': ['data'],
'nb_t_io_tl_hist': ['data'],
}
def _pg_convert_column(conn: Connection, table: str, column: str) -> None:
tbl = sa.table(table, sa.column('id'), sa.column(column))
col = getattr(tbl.c, column)
path = sa.cast(sa.literal('{}'), ARRAY(sa.Text))
payload = sa.cast(sa.cast(col, JSONB).op('#>>')(path), sa.JSON)
base = sa.func.json_typeof(col) == 'string'
min_max_stmt = sa.select(sa.func.min(tbl.c.id), sa.func.max(tbl.c.id)).where(base)
result = conn.execute(min_max_stmt).one()
if result[0] is None or result[1] is None:
return
start_id, end_id = result
total = end_id - start_id + 1
processed = 0
context = op.get_context()
with context.autocommit_block():
for chunk_start in range(start_id, end_id + 1, _PG_CHUNK_SIZE):
chunk_end = min(chunk_start + _PG_CHUNK_SIZE - 1, end_id)
stmt = (
sa.update(tbl).values({column: payload}).where(base).where(sa.between(tbl.c.id, chunk_start, chunk_end))
)
conn.execute(stmt)
processed += chunk_end - chunk_start + 1
logger.warning(
f'tetris_stats: converting {table}.{column} chunk {chunk_start}-{chunk_end} '
f'processed={processed}/{total}'
)
remaining_stmt = sa.select(sa.func.count()).select_from(tbl).where(base)
remaining = conn.execute(remaining_stmt).scalar()
if remaining:
msg = f'json storage fix failed: {table}.{column} still has string rows'
raise ValueError(msg)
def _pg_convert(conn: Connection) -> None:
for table, columns in tables.items():
for column in columns:
logger.warning(f'tetris_stats: converting {table}.{column} from json string to object')
_pg_convert_column(conn, table, column)
def _convert_table_python(conn: Connection, table_name: str, columns: list[str]) -> None: # noqa: C901
meta = sa.MetaData()
table = sa.Table(table_name, meta, autoload_with=conn)
update_stmt = (
table.update().where(table.c.id == sa.bindparam('b_id')).values(**{col: sa.bindparam(col) for col in columns})
)
batch: list[dict[str, object]] = []
last_id = 0
processed = 0
while True:
rows = (
conn.execute(
sa.select(table.c.id, *[table.c[col] for col in columns])
.where(table.c.id > last_id)
.order_by(table.c.id)
.limit(_SQLITE_FETCH_SIZE)
)
.mappings()
.all()
)
if not rows:
break
for row in rows:
last_id = row['id']
processed += 1
update_values: dict[str, object] = {'b_id': row['id']}
changed = False
for column in columns:
value = row[column]
if isinstance(value, str | bytes):
parsed = msgspec.json.decode(value)
if not isinstance(parsed, dict | list):
msg = f'json storage fix failed: {table_name}.{column} value is not object'
raise TypeError(msg)
update_values[column] = parsed
changed = True
elif isinstance(value, dict | list):
update_values[column] = value
else:
msg = f'json storage fix failed: {table_name}.{column} invalid type {type(value)}'
raise TypeError(msg)
if changed:
batch.append(update_values)
if processed % _LOG_INTERVAL == 0:
logger.warning(f'tetris_stats: converting {table_name} processed={processed}')
if len(batch) >= _BATCH_SIZE:
conn.execute(update_stmt, batch)
batch.clear()
if batch:
conn.execute(update_stmt, batch)
def _generic_convert(conn: Connection) -> None:
for table, columns in tables.items():
logger.warning(f'tetris_stats: converting {table} via python')
_convert_table_python(conn, table, columns)
def upgrade(name: str = '') -> None:
if name:
return
conn = op.get_bind()
if conn.dialect.name == 'postgresql':
_pg_convert(conn)
else:
_generic_convert(conn)
def downgrade(name: str = '') -> None:
if name:
return

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

@@ -0,0 +1,353 @@
"""add io tl map
迁移 ID: 3a294ff14610
父迁移: 6ecf383d646a
创建时间: 2026-01-28 03:25:40.714853
"""
from __future__ import annotations
import os
import re
import time
from typing import TYPE_CHECKING
import sqlalchemy as sa
from alembic import op
from nonebot.log import logger
from rich.progress import (
BarColumn,
MofNCompleteColumn,
Progress,
ProgressColumn,
Task,
TaskProgressColumn,
TextColumn,
TimeRemainingColumn,
filesize,
)
from rich.text import Text
from sqlalchemy import Connection, text
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session
from typing_extensions import override
if TYPE_CHECKING:
from collections.abc import Sequence
revision: str = '3a294ff14610'
down_revision: str | Sequence[str] | None = '6ecf383d646a'
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
class RateColumn(ProgressColumn):
"""Renders human readable processing rate."""
@override
def render(self, task: Task) -> Text:
"""Render the speed in iterations per second."""
def calculate_speed() -> float | None:
now = time.monotonic()
if task.start_time is not None:
elapsed = (task.finished_time or now) - task.start_time
if elapsed > 0:
return task.completed / elapsed
return None
speed = task.finished_speed or task.speed or calculate_speed()
if speed is None:
return Text('', style='progress.percentage')
unit, suffix = filesize.pick_unit_and_suffix(
int(speed),
['', '×10³', '×10⁶', '×10⁹', '×10¹²'], # noqa: RUF001
1000,
)
data_speed = speed / unit
return Text(f'{data_speed:.1f}{suffix} it/s', style='progress.percentage')
def _backfill_postgresql(conn: Connection, chunk_size: int = 20000) -> None:
result = conn.execute(text('SELECT min(id), max(id) FROM nb_t_io_tl_hist')).one()
if result[0] is None or result[1] is None:
return
min_id, max_id = result
total = max_id - min_id + 1
logger.warning('PG backfill: Disabling foreign key constraints...')
work_mem = os.getenv('TETRIS_STATS_MIGRATION_WORK_MEM', '256MB')
if not re.fullmatch(r'\d+(kB|MB|GB)', work_mem):
work_mem = '256MB'
conn.execute(
text("SELECT set_config('work_mem', :work_mem, true)"),
{'work_mem': work_mem},
)
temp_buffers = os.getenv('TETRIS_STATS_MIGRATION_TEMP_BUFFERS', '128MB')
if not re.fullmatch(r'\d+(kB|MB|GB)', temp_buffers):
temp_buffers = '128MB'
conn.execute(
text("SELECT set_config('temp_buffers', :temp_buffers, true)"),
{'temp_buffers': temp_buffers},
)
conn.execute(text('SET LOCAL synchronous_commit = off'))
logger.warning('tetris_stats: PG backfill synchronous_commit=off')
logger.warning(f'tetris_stats: PG backfill work_mem={work_mem}')
logger.warning(f'tetris_stats: PG backfill temp_buffers={temp_buffers}')
conn.execute(text('SET LOCAL max_parallel_workers_per_gather = 8'))
conn.execute(text('SET LOCAL parallel_setup_cost = 10'))
conn.execute(text('SET LOCAL parallel_tuple_cost = 0.01'))
logger.warning('tetris_stats: PG backfill max_parallel_workers_per_gather=8')
logger.warning('tetris_stats: PG backfill parallel_setup_cost=10')
logger.warning('tetris_stats: PG backfill parallel_tuple_cost=0.01')
with Progress(
TextColumn('[progress.description]{task.description}'),
BarColumn(),
MofNCompleteColumn(),
TaskProgressColumn(),
RateColumn(),
TimeRemainingColumn(),
) as progress:
task = progress.add_task('生成索引...', total=total)
for start_id in range(min_id, max_id + 1, chunk_size):
end_id = min(start_id + chunk_size - 1, max_id)
conn.execute(
text(
"""
WITH entries AS (
SELECT
h.stats_id,
h.id AS hist_id,
e.ordinality - 1 AS entry_index,
COALESCE(e.entry->>'_id', e.entry->>'id') AS uid_str
FROM nb_t_io_tl_hist h
CROSS JOIN LATERAL jsonb_array_elements(h.data::jsonb->'data'->'entries')
WITH ORDINALITY AS e(entry, ordinality)
WHERE h.id BETWEEN :start_id AND :end_id
AND COALESCE(e.entry->>'_id', e.entry->>'id') IS NOT NULL
),
upserted_uids AS (
INSERT INTO nb_t_io_uid (user_unique_identifier)
SELECT DISTINCT uid_str FROM entries
ON CONFLICT (user_unique_identifier)
DO UPDATE SET user_unique_identifier = EXCLUDED.user_unique_identifier
RETURNING id, user_unique_identifier
)
INSERT INTO nb_t_io_tl_map (stats_id, uid_id, hist_id, entry_index)
SELECT e.stats_id, u.id, e.hist_id, e.entry_index
FROM entries e
JOIN upserted_uids u ON u.user_unique_identifier = e.uid_str
"""
),
{'start_id': start_id, 'end_id': end_id},
)
progress.update(task, advance=end_id - start_id + 1)
def _add_foreign_keys_postgresql(conn: Connection) -> None:
logger.warning('PG backfill: Re-adding foreign key constraints (validating)...')
conn.execute(
text("""
ALTER TABLE nb_t_io_tl_map
ADD CONSTRAINT fk_nb_t_io_tl_map_hist_id_nb_t_io_tl_hist
FOREIGN KEY (hist_id) REFERENCES nb_t_io_tl_hist(id)
NOT VALID
""")
)
conn.execute(
text("""
ALTER TABLE nb_t_io_tl_map
VALIDATE CONSTRAINT fk_nb_t_io_tl_map_hist_id_nb_t_io_tl_hist
""")
)
conn.execute(
text("""
ALTER TABLE nb_t_io_tl_map
ADD CONSTRAINT fk_nb_t_io_tl_map_stats_id_nb_t_io_tl_stats
FOREIGN KEY (stats_id) REFERENCES nb_t_io_tl_stats(id)
NOT VALID
""")
)
conn.execute(
text("""
ALTER TABLE nb_t_io_tl_map
VALIDATE CONSTRAINT fk_nb_t_io_tl_map_stats_id_nb_t_io_tl_stats
""")
)
conn.execute(
text("""
ALTER TABLE nb_t_io_tl_map
ADD CONSTRAINT fk_nb_t_io_tl_map_uid_id_nb_t_io_uid
FOREIGN KEY (uid_id) REFERENCES nb_t_io_uid(id)
NOT VALID
""")
)
conn.execute(
text("""
ALTER TABLE nb_t_io_tl_map
VALIDATE CONSTRAINT fk_nb_t_io_tl_map_uid_id_nb_t_io_uid
""")
)
logger.success('PG backfill: Foreign keys validated successfully')
def _backfill_generic(conn: Connection) -> None:
Base = automap_base() # noqa: N806
Base.prepare(autoload_with=conn)
Hist = Base.classes.nb_t_io_tl_hist # noqa: N806
Uid = Base.classes.nb_t_io_uid # noqa: N806
Map = Base.classes.nb_t_io_tl_map # noqa: N806
with Session(conn) as session:
count = session.query(Hist).count()
if count == 0:
return
logger.warning('tetris_stats: 正在生成 TETR.IO 玩家分页索引, 请不要关闭程序...')
uid_map: dict[str, int] = {}
def refresh_uid_map() -> None:
uids = session.query(Uid).all()
uid_map.clear()
uid_map.update({uid.user_unique_identifier: uid.id for uid in uids})
with Progress(
TextColumn('[progress.description]{task.description}'),
BarColumn(),
MofNCompleteColumn(),
TaskProgressColumn(),
RateColumn(),
TimeRemainingColumn(),
) as progress:
total = progress.add_task('生成索引...', total=count)
for hist in session.query(Hist).yield_per(1):
data = hist.data
if isinstance(data, str | bytes):
msg = 'io tl map migration requires json object data'
raise TypeError(msg)
entries = data.get('data', {}).get('entries', []) if isinstance(data, dict) else []
entry_info: list[tuple[str, int]] = []
for index, entry in enumerate(entries):
if isinstance(entry, dict):
uid = entry.get('_id')
if isinstance(uid, str):
entry_info.append((uid, index))
if not entry_info:
progress.update(total, advance=1)
continue
session.add_all([Uid(user_unique_identifier=uid) for uid, _ in entry_info if uid not in uid_map])
session.flush()
refresh_uid_map()
session.add_all(
[
Map(
stats_id=hist.stats_id,
uid_id=uid_map[uid],
hist_id=hist.id,
entry_index=index,
)
for uid, index in entry_info
]
)
session.flush()
progress.update(total, advance=1)
def backfill_mapping(conn: Connection) -> None:
if conn.dialect.name == 'postgresql':
logger.warning('tetris_stats: 检测到 PostgreSQL, 使用快速索引回填...')
_backfill_postgresql(conn)
_add_foreign_keys_postgresql(conn)
return
_backfill_generic(conn)
def upgrade(name: str = '') -> None:
if name:
return
conn = op.get_bind()
op.create_table(
'nb_t_io_uid',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_unique_identifier', sa.String(length=24), nullable=False),
sa.PrimaryKeyConstraint('id', name=op.f('pk_nb_t_io_uid')),
info={'bind_key': 'nonebot_plugin_tetris_stats'},
)
with op.batch_alter_table('nb_t_io_uid', schema=None) as batch_op:
batch_op.create_index(
batch_op.f('ix_nb_t_io_uid_user_unique_identifier'),
['user_unique_identifier'],
unique=True,
)
if conn.dialect.name == 'postgresql':
op.create_table(
'nb_t_io_tl_map',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('stats_id', sa.Integer(), nullable=False),
sa.Column('uid_id', sa.Integer(), nullable=False),
sa.Column('hist_id', sa.Integer(), nullable=False),
sa.Column('entry_index', sa.Integer(), nullable=False),
sa.PrimaryKeyConstraint('id', name=op.f('pk_nb_t_io_tl_map')),
sa.UniqueConstraint('uid_id', 'hist_id', name='uq_nb_t_io_tl_map_uid_hist'),
info={'bind_key': 'nonebot_plugin_tetris_stats'},
)
else:
op.create_table(
'nb_t_io_tl_map',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('stats_id', sa.Integer(), nullable=False),
sa.Column('uid_id', sa.Integer(), nullable=False),
sa.Column('hist_id', sa.Integer(), nullable=False),
sa.Column('entry_index', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(
['stats_id'],
['nb_t_io_tl_stats.id'],
name=op.f('fk_nb_t_io_tl_map_stats_id_nb_t_io_tl_stats'),
),
sa.ForeignKeyConstraint(
['uid_id'],
['nb_t_io_uid.id'],
name=op.f('fk_nb_t_io_tl_map_uid_id_nb_t_io_uid'),
),
sa.ForeignKeyConstraint(
['hist_id'],
['nb_t_io_tl_hist.id'],
name=op.f('fk_nb_t_io_tl_map_hist_id_nb_t_io_tl_hist'),
),
sa.PrimaryKeyConstraint('id', name=op.f('pk_nb_t_io_tl_map')),
sa.UniqueConstraint('uid_id', 'hist_id', name='uq_nb_t_io_tl_map_uid_hist'),
info={'bind_key': 'nonebot_plugin_tetris_stats'},
)
backfill_mapping(conn)
with op.batch_alter_table('nb_t_io_tl_map', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_nb_t_io_tl_map_stats_id'), ['stats_id'], unique=False)
batch_op.create_index(batch_op.f('ix_nb_t_io_tl_map_uid_id'), ['uid_id'], unique=False)
def downgrade(name: str = '') -> None:
if name:
return
with op.batch_alter_table('nb_t_io_tl_map', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_nb_t_io_tl_map_uid_id'))
batch_op.drop_index(batch_op.f('ix_nb_t_io_tl_map_stats_id'))
op.drop_table('nb_t_io_tl_map')
with op.batch_alter_table('nb_t_io_uid', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_nb_t_io_uid_user_unique_identifier'))
op.drop_table('nb_t_io_uid')

View File

@@ -0,0 +1,286 @@
"""Refactor Historical
迁移 ID: 3c25a5a8c050
父迁移: b7fbdafc339a
创建时间: 2024-05-14 09:16:35.193001
"""
from __future__ import annotations
from datetime import datetime
from typing import TYPE_CHECKING, Any
import sqlalchemy as sa
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.ext.automap import automap_base
from sqlalchemy.orm import Session
if TYPE_CHECKING:
from collections.abc import Sequence
revision: str = '3c25a5a8c050'
down_revision: str | Sequence[str] | None = 'b7fbdafc339a'
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = 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
TETRIOHistoricalData = Base.classes.nonebot_plugin_tetris_stats_tetriohistoricaldata # noqa: N806
TOSHistoricalData = Base.classes.nonebot_plugin_tetris_stats_toshistoricaldata # noqa: N806
with (
Session(op.get_bind()) as session,
Progress(
TextColumn('[progress.description]{task.description}'),
BarColumn(),
MofNCompleteColumn(),
TaskProgressColumn(),
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:
result = session.scalars(
select(OldHistoricalData)
.where(OldHistoricalData.id > pointer)
.order_by(OldHistoricalData.id)
.limit(100)
).all()
for j in result:
processed_data: dict[str, Any] = loads(j.processed_data)
if j.game_platform == 'IO':
if (data := processed_data.get('user_info')) is not None:
session.add(
TETRIOHistoricalData(
user_unique_identifier=j.user_unique_identifier,
api_type='User Info',
data=dumps(data),
update_time=datetime.fromisoformat(data['cache']['cached_at']),
)
)
if (data := processed_data.get('user_records')) is not None:
session.add(
TETRIOHistoricalData(
user_unique_identifier=j.user_unique_identifier,
api_type='User Records',
data=dumps(data),
update_time=datetime.fromisoformat(data['cache']['cached_at']),
)
)
if j.game_platform == 'TOS' and not j.user_unique_identifier.isdigit():
if (data := processed_data.get('user_info')) is not None:
session.add(
TOSHistoricalData(
user_unique_identifier=j.user_unique_identifier,
api_type='User Info',
data=dumps(data),
update_time=j.finish_time,
)
)
if (data := processed_data.get('user_profile')) is not None:
for v in data.values():
session.add(
TOSHistoricalData(
user_unique_identifier=j.user_unique_identifier,
api_type='User Profile',
data=dumps(v),
update_time=j.finish_time,
)
)
progress.update(task_id, advance=1)
session.commit()
pointer = result[-1].id
logger.success('Migrate successfully')
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',
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_nonebot_plugin_tetris_stats_tetriohistoricaldata')),
info={'bind_key': 'nonebot_plugin_tetris_stats'},
)
with op.batch_alter_table('nonebot_plugin_tetris_stats_tetriohistoricaldata', schema=None) as batch_op:
batch_op.create_index(
batch_op.f('ix_nonebot_plugin_tetris_stats_tetriohistoricaldata_api_type'), ['api_type'], unique=False
)
batch_op.create_index(
batch_op.f('ix_nonebot_plugin_tetris_stats_tetriohistoricaldata_update_time'), ['update_time'], unique=False
)
batch_op.create_index(
batch_op.f('ix_nonebot_plugin_tetris_stats_tetriohistoricaldata_user_unique_identifier'),
['user_unique_identifier'],
unique=False,
)
op.create_table(
'nonebot_plugin_tetris_stats_tophistoricaldata',
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_nonebot_plugin_tetris_stats_tophistoricaldata')),
info={'bind_key': 'nonebot_plugin_tetris_stats'},
)
with op.batch_alter_table('nonebot_plugin_tetris_stats_tophistoricaldata', schema=None) as batch_op:
batch_op.create_index(
batch_op.f('ix_nonebot_plugin_tetris_stats_tophistoricaldata_api_type'), ['api_type'], unique=False
)
batch_op.create_index(
batch_op.f('ix_nonebot_plugin_tetris_stats_tophistoricaldata_update_time'), ['update_time'], unique=False
)
batch_op.create_index(
batch_op.f('ix_nonebot_plugin_tetris_stats_tophistoricaldata_user_unique_identifier'),
['user_unique_identifier'],
unique=False,
)
op.create_table(
'nonebot_plugin_tetris_stats_toshistoricaldata',
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_nonebot_plugin_tetris_stats_toshistoricaldata')),
info={'bind_key': 'nonebot_plugin_tetris_stats'},
)
with op.batch_alter_table('nonebot_plugin_tetris_stats_toshistoricaldata', schema=None) as batch_op:
batch_op.create_index(
batch_op.f('ix_nonebot_plugin_tetris_stats_toshistoricaldata_api_type'), ['api_type'], unique=False
)
batch_op.create_index(
batch_op.f('ix_nonebot_plugin_tetris_stats_toshistoricaldata_update_time'), ['update_time'], unique=False
)
batch_op.create_index(
batch_op.f('ix_nonebot_plugin_tetris_stats_toshistoricaldata_user_unique_identifier'),
['user_unique_identifier'],
unique=False,
)
op.create_table(
'nonebot_plugin_tetris_stats_triggerhistoricaldata',
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_triggerhistoricaldata')),
info={'bind_key': 'nonebot_plugin_tetris_stats'},
)
with op.batch_alter_table('nonebot_plugin_tetris_stats_triggerhistoricaldata', schema=None) as batch_op:
batch_op.create_index(
batch_op.f('ix_nonebot_plugin_tetris_stats_triggerhistoricaldata_command_type'),
['command_type'],
unique=False,
)
batch_op.create_index(
batch_op.f('ix_nonebot_plugin_tetris_stats_triggerhistoricaldata_game_platform'),
['game_platform'],
unique=False,
)
migrate_old_data()
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')
batch_op.drop_index('ix_nonebot_plugin_tetris_stats_historicaldata_game_platform')
batch_op.drop_index('ix_nonebot_plugin_tetris_stats_historicaldata_source_account')
batch_op.drop_index('ix_nonebot_plugin_tetris_stats_historicaldata_source_type')
batch_op.drop_index('ix_nonebot_plugin_tetris_stats_historicaldata_user_unique_identifier')
op.drop_table('nonebot_plugin_tetris_stats_historicaldata')
# ### 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! ###
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.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:
batch_op.create_index(
'ix_nonebot_plugin_tetris_stats_historicaldata_user_unique_identifier',
['user_unique_identifier'],
unique=False,
)
batch_op.create_index(
'ix_nonebot_plugin_tetris_stats_historicaldata_source_type', ['source_type'], unique=False
)
batch_op.create_index(
'ix_nonebot_plugin_tetris_stats_historicaldata_source_account', ['source_account'], unique=False
)
batch_op.create_index(
'ix_nonebot_plugin_tetris_stats_historicaldata_game_platform', ['game_platform'], unique=False
)
batch_op.create_index(
'ix_nonebot_plugin_tetris_stats_historicaldata_command_type', ['command_type'], unique=False
)
with op.batch_alter_table('nonebot_plugin_tetris_stats_triggerhistoricaldata', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_triggerhistoricaldata_game_platform'))
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_triggerhistoricaldata_command_type'))
op.drop_table('nonebot_plugin_tetris_stats_triggerhistoricaldata')
with op.batch_alter_table('nonebot_plugin_tetris_stats_toshistoricaldata', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_toshistoricaldata_user_unique_identifier'))
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_toshistoricaldata_update_time'))
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_toshistoricaldata_api_type'))
op.drop_table('nonebot_plugin_tetris_stats_toshistoricaldata')
with op.batch_alter_table('nonebot_plugin_tetris_stats_tophistoricaldata', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_tophistoricaldata_user_unique_identifier'))
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_tophistoricaldata_update_time'))
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_tophistoricaldata_api_type'))
op.drop_table('nonebot_plugin_tetris_stats_tophistoricaldata')
with op.batch_alter_table('nonebot_plugin_tetris_stats_tetriohistoricaldata', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_tetriohistoricaldata_user_unique_identifier'))
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_tetriohistoricaldata_update_time'))
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_tetriohistoricaldata_api_type'))
op.drop_table('nonebot_plugin_tetris_stats_tetriohistoricaldata')
# ### end Alembic commands ###

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

@@ -5,14 +5,17 @@
创建时间: 2023-11-26 20:15:56.033892
"""
from __future__ import annotations
from collections.abc import Sequence
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
revision: str = '6c3206f90cc3'
down_revision: str | Sequence[str] | None = '9f6582279ce2'
@@ -23,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()
@@ -46,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,53 @@
"""add compare delta config
迁移 ID: 6ecf383d646a
父迁移: 1c5346b657d4
创建时间: 2026-01-27 06:05:04.481654
"""
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 = '6ecf383d646a'
down_revision: str | Sequence[str] | None = '1c5346b657d4'
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade(name: str = '') -> None:
if name:
return
op.create_table(
'nb_t_top_u_cfg',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('compare_delta', sa.Interval(), nullable=True),
sa.PrimaryKeyConstraint('id', name=op.f('pk_nb_t_top_u_cfg')),
info={'bind_key': 'nonebot_plugin_tetris_stats'},
)
op.create_table(
'nb_t_tos_u_cfg',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('compare_delta', sa.Interval(), nullable=True),
sa.PrimaryKeyConstraint('id', name=op.f('pk_nb_t_tos_u_cfg')),
info={'bind_key': 'nonebot_plugin_tetris_stats'},
)
with op.batch_alter_table('nb_t_io_u_cfg', schema=None) as batch_op:
batch_op.add_column(sa.Column('compare_delta', sa.Interval(), nullable=True))
def downgrade(name: str = '') -> None:
if name:
return
with op.batch_alter_table('nb_t_io_u_cfg', schema=None) as batch_op:
batch_op.drop_column('compare_delta')
op.drop_table('nb_t_tos_u_cfg')
op.drop_table('nb_t_top_u_cfg')

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

@@ -8,7 +8,7 @@
from __future__ import annotations
from collections.abc import Sequence
from typing import TYPE_CHECKING
from alembic import op
from nonebot.log import logger
@@ -16,24 +16,24 @@ from sqlalchemy import select
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session
if TYPE_CHECKING:
from collections.abc import Sequence
revision: str = '8a91210ce14d'
down_revision: str | Sequence[str] | None = '0d50142b780f'
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.3':
logger.critical('本迁移需要1.0.3版本, 请先锁定版本至1.0.3版本再执行本迁移')
raise RuntimeError('本迁移需要1.0.3版本, 请先锁定版本至1.0.3版本再执行本迁移')
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,
@@ -42,8 +42,6 @@ def upgrade(name: str = '') -> None:
TimeRemainingColumn,
)
from nonebot_plugin_tetris_stats.game_data_processor.schemas import BaseProcessedData
Base = automap_base() # noqa: N806
Base.prepare(autoload_with=op.get_bind())
HistoricalData = Base.classes.nonebot_plugin_tetris_stats_historicaldata # noqa: N806
@@ -56,18 +54,33 @@ def upgrade(name: str = '') -> None:
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] # pyright: ignore[reportMissingImports] # 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(),
@@ -90,3 +103,5 @@ def upgrade(name: str = '') -> None:
def downgrade(name: str = '') -> None:
if name:
return
if op.get_bind().dialect.name == 'postgresql':
return

View File

@@ -5,13 +5,17 @@
创建时间: 2023-11-11 16:24:11.826667
"""
from __future__ import annotations
from collections.abc import Sequence
from typing import TYPE_CHECKING
import sqlalchemy as sa
from alembic import op
if TYPE_CHECKING:
from collections.abc import Sequence
revision: str = '9866f53ce44f'
down_revision: str | Sequence[str] | None = None
branch_labels: str | Sequence[str] | None = ('nonebot_plugin_tetris_stats',)
@@ -21,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',
@@ -118,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

@@ -5,11 +5,12 @@
创建时间: 2023-11-11 16:51:30.718277
"""
from __future__ import annotations
from collections.abc import Sequence
from pathlib import Path
from shutil import copyfile
from typing import TYPE_CHECKING
from alembic import op
from nonebot import get_driver
@@ -18,6 +19,9 @@ from sqlalchemy import Connection, create_engine, inspect, text
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session
if TYPE_CHECKING:
from collections.abc import Sequence
revision: str = '9cd1647db502'
down_revision: str | Sequence[str] | None = '9866f53ce44f'
branch_labels: str | Sequence[str] | None = None
@@ -33,14 +37,10 @@ def migrate_old_data(connection: Connection) -> None:
Bind = Base.classes.nonebot_plugin_tetris_stats_bind # noqa: N806
def non_empty(obj: str) -> bool:
if obj != '' and not obj.isspace():
return True
return False
return bool(obj != '' and not obj.isspace())
def is_int(obj: int | str) -> bool:
if isinstance(obj, int) or obj.isdigit():
return True
return False
return bool(isinstance(obj, int) or obj.isdigit())
bind_list = [
Bind(chat_platform='OneBot V11', chat_account=int(row.QQ), game_platform='IO', game_account=row.USER)
@@ -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:
@@ -80,8 +82,9 @@ def upgrade(name: str = '') -> None:
logger.success('nonebot_plugin_tetris_stats: 跳过迁移')
return
if 'IORANK' not in tables:
logger.warning('nonebot_plugin_tetris_stats: 发现过早版本的数据, 请先更新到 0.4.4 版本')
raise RuntimeError('nonebot_plugin_tetris_stats: 请先安装 0.4.4 版本完成迁移之后再升级')
msg = 'nonebot_plugin_tetris_stats: 请先安装 0.4.4 版本完成迁移之后再升级'
logger.warning(msg)
raise RuntimeError(msg)
logger.info('nonebot_plugin_tetris_stats: 发现来自老版本的数据, 正在迁移...')
migrate_old_data(connection)
db_path.unlink()
@@ -90,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

@@ -5,15 +5,16 @@
创建时间: 2023-11-21 08:35:50.393246
"""
from __future__ import annotations
from collections.abc import Sequence
from typing import TYPE_CHECKING
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import sqlite
from nonebot_plugin_tetris_stats.db.models import PydanticType
if TYPE_CHECKING:
from collections.abc import Sequence
revision: str = '9f6582279ce2'
down_revision: str | Sequence[str] | None = '9cd1647db502'
@@ -24,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')
@@ -45,8 +48,8 @@ def upgrade(name: str = '') -> None:
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', PydanticType(list), nullable=False),
sa.Column('processed_data', PydanticType(list), 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.PrimaryKeyConstraint('id', name=op.f('pk_nonebot_plugin_tetris_stats_historicaldata')),
)
@@ -69,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'))
@@ -80,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

@@ -0,0 +1,48 @@
"""Add TETRIO user configuration
迁移 ID: a1195e989cc6
父迁移: b15844837693
创建时间: 2024-06-09 04:20:07.819194
"""
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 = 'a1195e989cc6'
down_revision: str | Sequence[str] | None = 'b15844837693'
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_tetriouserconfig',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('query_template', sa.String(length=2), nullable=False),
sa.PrimaryKeyConstraint('id', name=op.f('pk_nonebot_plugin_tetris_stats_tetriouserconfig')),
info={'bind_key': 'nonebot_plugin_tetris_stats'},
)
# ### 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! ###
op.drop_table('nonebot_plugin_tetris_stats_tetriouserconfig')
# ### end Alembic commands ###

View File

@@ -0,0 +1,75 @@
"""Migrate to nonobot-plugin-user
迁移 ID: b15844837693
父迁移: 3c25a5a8c050
创建时间: 2024-06-08 02:27:35.227596
"""
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 = 'b15844837693'
down_revision: str | Sequence[str] | None = '3c25a5a8c050'
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! ###
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')
batch_op.drop_index('ix_nonebot_plugin_tetris_stats_bind_chat_platform')
op.drop_table('nonebot_plugin_tetris_stats_bind')
op.create_table(
'nonebot_plugin_tetris_stats_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_nonebot_plugin_tetris_stats_bind')),
info={'bind_key': 'nonebot_plugin_tetris_stats'},
)
with op.batch_alter_table('nonebot_plugin_tetris_stats_bind', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_nonebot_plugin_tetris_stats_bind_user_id'), ['user_id'], 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_bind', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_bind_user_id'))
op.drop_table('nonebot_plugin_tetris_stats_bind')
op.create_table(
'nonebot_plugin_tetris_stats_bind',
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:
batch_op.create_index('ix_nonebot_plugin_tetris_stats_bind_chat_platform', ['chat_platform'], unique=False)
batch_op.create_index('ix_nonebot_plugin_tetris_stats_bind_chat_account', ['chat_account'], unique=False)
# ### end Alembic commands ###

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

@@ -0,0 +1,117 @@
"""Add user_unique_identifier field to HistoricalData
迁移 ID: b7fbdafc339a
父迁移: 8a91210ce14d
创建时间: 2024-05-07 16:55:29.527215
"""
from __future__ import annotations
from typing import TYPE_CHECKING
import sqlalchemy as sa
from alembic import op
from nonebot.log import logger
if TYPE_CHECKING:
from collections.abc import Sequence
revision: str = 'b7fbdafc339a'
down_revision: str | Sequence[str] | None = '8a91210ce14d'
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade(name: str = '') -> None: # noqa: C901
if name:
return
if op.get_bind().dialect.name == 'postgresql':
return
from nonebot.compat import type_validate_json # noqa: PLC0415
from pydantic import ValidationError # noqa: PLC0415
from rich.progress import ( # noqa: PLC0415
BarColumn,
MofNCompleteColumn,
Progress,
TaskProgressColumn,
TextColumn,
TimeRemainingColumn,
)
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))
batch_op.create_index(
batch_op.f('ix_nonebot_plugin_tetris_stats_historicaldata_user_unique_identifier'),
['user_unique_identifier'],
unique=False,
)
Base = automap_base() # noqa: N806
connection = op.get_bind()
Base.prepare(autoload_with=connection)
HistoricalData = Base.classes.nonebot_plugin_tetris_stats_historicaldata # noqa: N806
with Session(op.get_bind()) as session:
count = session.query(HistoricalData).count()
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] # pyright: ignore[reportMissingImports] # noqa: PLC0415
BaseUser,
)
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 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')
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'))
batch_op.drop_column('user_unique_identifier')
# ### end Alembic commands ###

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

@@ -5,14 +5,18 @@
创建时间: 2023-12-30 00:27:40.991704
"""
from __future__ import annotations
from collections.abc import Sequence
from typing import TYPE_CHECKING
from alembic import op
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session
if TYPE_CHECKING:
from collections.abc import Sequence
revision: str = 'b9d65badc713'
down_revision: str | Sequence[str] | None = '6c3206f90cc3'
branch_labels: str | Sequence[str] | None = None
@@ -22,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()
@@ -36,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

@@ -0,0 +1,54 @@
"""Extend api_type field length
迁移 ID: cfeab6961dce
父迁移: f5b4a6d1325b
创建时间: 2024-08-09 14:20:59.789030
"""
from __future__ import annotations
from typing import TYPE_CHECKING
import sqlalchemy as sa
from alembic import op
from nonebot.log import logger
if TYPE_CHECKING:
from collections.abc import Sequence
revision: str = 'cfeab6961dce'
down_revision: str | Sequence[str] | None = 'f5b4a6d1325b'
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! ###
with op.batch_alter_table('nonebot_plugin_tetris_stats_tetriohistoricaldata', schema=None) as batch_op:
batch_op.alter_column(
'api_type', existing_type=sa.VARCHAR(length=16), type_=sa.String(length=32), existing_nullable=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! ###
logger.warning('新数据可能不支持降级!')
logger.warning('请确认数据库内数据可以迁移到旧版本!')
input('如果确认可以迁移, 请按回车键继续!')
with op.batch_alter_table('nonebot_plugin_tetris_stats_tetriohistoricaldata', schema=None) as batch_op:
batch_op.alter_column(
'api_type', existing_type=sa.String(length=32), type_=sa.VARCHAR(length=16), existing_nullable=False
)
# ### end Alembic commands ###

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

@@ -0,0 +1,95 @@
"""TETR.IO new season
迁移 ID: f5b4a6d1325b
父迁移: a1195e989cc6
创建时间: 2024-08-01 20:44:48.644912
"""
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 = 'f5b4a6d1325b'
down_revision: str | Sequence[str] | None = 'a1195e989cc6'
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
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')
batch_op.drop_index('ix_nonebot_plugin_tetris_stats_iorank_update_time')
op.drop_table('nonebot_plugin_tetris_stats_iorank')
with op.batch_alter_table('nonebot_plugin_tetris_stats_tetriohistoricaldata', schema=None) as batch_op:
batch_op.drop_index('ix_nonebot_plugin_tetris_stats_tetriohistoricaldata_api_type')
batch_op.drop_index('ix_nonebot_plugin_tetris_stats_tetriohistoricaldata_update_time')
batch_op.drop_index('ix_nonebot_plugin_tetris_stats_tetriohistoricaldata_user_unique_identifier')
op.drop_table('nonebot_plugin_tetris_stats_tetriohistoricaldata')
op.create_table(
'nonebot_plugin_tetris_stats_tetriohistoricaldata',
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_nonebot_plugin_tetris_stats_tetriohistoricaldata')),
info={'bind_key': 'nonebot_plugin_tetris_stats'},
)
with op.batch_alter_table('nonebot_plugin_tetris_stats_tetriohistoricaldata', schema=None) as batch_op:
batch_op.create_index(
batch_op.f('ix_nonebot_plugin_tetris_stats_tetriohistoricaldata_api_type'), ['api_type'], unique=False
)
batch_op.create_index(
batch_op.f('ix_nonebot_plugin_tetris_stats_tetriohistoricaldata_update_time'), ['update_time'], unique=False
)
batch_op.create_index(
batch_op.f('ix_nonebot_plugin_tetris_stats_tetriohistoricaldata_user_unique_identifier'),
['user_unique_identifier'],
unique=False,
)
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.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('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:
batch_op.create_index('ix_nonebot_plugin_tetris_stats_iorank_update_time', ['update_time'], unique=False)
batch_op.create_index('ix_nonebot_plugin_tetris_stats_iorank_rank', ['rank'], unique=False)
batch_op.create_index('ix_nonebot_plugin_tetris_stats_iorank_file_hash', ['file_hash'], unique=False)

View File

@@ -1,10 +1,29 @@
from asyncio import Lock
from collections.abc import AsyncGenerator
from contextlib import asynccontextmanager
from datetime import datetime, timedelta, timezone
from enum import Enum, auto
from typing import TYPE_CHECKING, Literal, TypeVar, overload
from nonebot_plugin_orm import AsyncSession
from nonebot.exception import FinishedException
from nonebot.log import logger
from nonebot_plugin_orm import AsyncSession, get_session
from nonebot_plugin_user import User
from sqlalchemy import select
from ..utils.typing import GameType
from .models import Bind
from ..utils.duration import DEFAULT_COMPARE_DELTA
from ..utils.typedefs import AllCommandType, BaseCommandType, GameType, TETRIOCommandType
from .models import Bind, TriggerHistoricalDataV2
UTC = timezone.utc
if TYPE_CHECKING:
from ..games.tetrio.api.models import TETRIOHistoricalData
from ..games.tetrio.models import TETRIOUserConfig
from ..games.top.api.models import TOPHistoricalData
from ..games.top.models import TOPUserConfig
from ..games.tos.api.models import TOSHistoricalData
from ..games.tos.models import TOSUserConfig
class BindStatus(Enum):
@@ -14,44 +33,141 @@ class BindStatus(Enum):
async def query_bind_info(
session: AsyncSession,
chat_platform: str,
chat_account: str,
user: User,
game_platform: GameType,
) -> Bind | None:
return (
await session.scalars(
select(Bind)
.where(Bind.chat_platform == chat_platform)
.where(Bind.chat_account == chat_account)
.where(Bind.game_platform == game_platform)
)
await session.scalars(select(Bind).where(Bind.user_id == user.id).where(Bind.game_platform == game_platform))
).one_or_none()
async def create_or_update_bind(
session: AsyncSession,
chat_platform: str,
chat_account: str,
user: User,
game_platform: GameType,
game_account: str,
*,
verify: bool = False,
) -> BindStatus:
bind = await query_bind_info(
session=session,
chat_platform=chat_platform,
chat_account=chat_account,
user=user,
game_platform=game_platform,
)
if bind is None:
bind = Bind(
chat_platform=chat_platform,
chat_account=chat_account,
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_HistoricalData = TypeVar('T_HistoricalData', 'TETRIOHistoricalData', 'TOPHistoricalData', 'TOSHistoricalData')
lock = Lock()
async def anti_duplicate_add(model: T_HistoricalData) -> None:
async with lock, get_session() as session:
result = (
await session.scalars(
select(cls := model.__class__)
.where(cls.update_time == model.update_time)
.where(cls.user_unique_identifier == model.user_unique_identifier)
.where(cls.api_type == model.api_type)
)
).all()
if result:
for i in result:
if i.data == model.data:
logger.debug('Anti duplicate successfully')
return
session.add(model)
await session.commit()
T_CONFIG = TypeVar('T_CONFIG', 'TETRIOUserConfig', 'TOPUserConfig', 'TOSUserConfig')
async def resolve_compare_delta(
config: type[T_CONFIG], session: AsyncSession, user_id: int, compare: timedelta | None
) -> timedelta:
return (
compare
or await session.scalar(select(config.compare_delta).where(config.id == user_id))
or DEFAULT_COMPARE_DELTA
)
@asynccontextmanager
@overload
async def trigger(
session_persist_id: int,
game_platform: Literal['IO'],
command_type: TETRIOCommandType,
command_args: list[str],
) -> AsyncGenerator:
yield
@asynccontextmanager
@overload
async def trigger(
session_persist_id: int,
game_platform: GameType,
command_type: BaseCommandType,
command_args: list[str],
) -> AsyncGenerator:
yield
@asynccontextmanager
async def trigger(
session_persist_id: int,
game_platform: GameType,
command_type: AllCommandType,
command_args: list[str],
) -> AsyncGenerator:
trigger_time = datetime.now(UTC)
try:
yield
except FinishedException:
async with get_session() as session:
session.add(
TriggerHistoricalDataV2(
trigger_time=trigger_time,
session_persist_id=session_persist_id,
game_platform=game_platform,
command_type=command_type,
command_args=command_args,
finish_time=datetime.now(UTC),
)
)
await session.commit()
raise

View File

@@ -2,76 +2,85 @@ from collections.abc import Callable, Sequence
from datetime import datetime
from typing import Any
from nonebot.adapters import Message
from nonebot.compat import PYDANTIC_V2, type_validate_json
from nonebot.compat import PYDANTIC_V2, type_validate_python
from nonebot_plugin_orm import Model
from pydantic import BaseModel, ValidationError
from sqlalchemy import JSON, DateTime, Dialect, PickleType, String, TypeDecorator
from sqlalchemy import JSON, DateTime, Dialect, String, TypeDecorator
from sqlalchemy.orm import Mapped, MappedAsDataclass, mapped_column
from typing_extensions import override
from ..game_data_processor.schemas import BaseProcessedData, BaseUser
from ..utils.typing import CommandType, GameType
from ..utils.typedefs import AllCommandType, GameType
class PydanticType(TypeDecorator):
impl = JSON
@override
def __init__(self, get_model: Callable[[], Sequence[type[BaseModel]]], *args: Any, **kwargs: Any):
def __init__(
self,
get_model: Sequence[Callable[[], Sequence[type[BaseModel]]]],
models: set[type[BaseModel]],
*args: Any,
**kwargs: Any,
):
self.get_model = get_model
self._models = models
super().__init__(*args, **kwargs)
if PYDANTIC_V2:
@override
def process_bind_param(self, value: Any | None, dialect: Dialect) -> str:
def process_bind_param(self, value: Any | None, dialect: Dialect) -> dict | list:
# 将 Pydantic 模型实例转换为 JSON
if isinstance(value, tuple(self.get_model())):
return value.model_dump_json(by_alias=True) # type: ignore[union-attr]
if isinstance(value, tuple(self.models)):
return value.model_dump(mode='json', by_alias=True) # type: ignore[union-attr]
raise TypeError
else:
@override
def process_bind_param(self, value: Any | None, dialect: Dialect) -> str:
def process_bind_param(self, value: Any | None, dialect: Dialect) -> dict | list:
# 将 Pydantic 模型实例转换为 JSON
if isinstance(value, tuple(self.get_model())):
return value.json(by_alias=True) # type: ignore[union-attr]
if isinstance(value, tuple(self.models)):
return value.dict(by_alias=True) # type: ignore[union-attr]
raise TypeError
@override
def process_result_value(self, value: Any | None, dialect: Dialect) -> BaseModel:
# 将 JSON 转换回 Pydantic 模型实例
if isinstance(value, str | bytes):
for i in self.get_model():
if isinstance(value, dict | list):
for i in self.models:
try:
return type_validate_json(i, value)
return type_validate_python(i, value)
except ValidationError: # noqa: PERF203
...
raise ValueError
@property
def models(self) -> tuple[type[BaseModel], ...]:
models: set[type[BaseModel]] = set()
for i in self.get_model:
models.update(i())
models.update(self._models)
return tuple(models)
class Bind(MappedAsDataclass, Model):
__tablename__ = 'nb_t_bind'
id: Mapped[int] = mapped_column(init=False, primary_key=True)
chat_platform: Mapped[str] = mapped_column(String(32), index=True)
chat_account: Mapped[str] = mapped_column(index=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 HistoricalData(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)
bot_platform: Mapped[str | None] = mapped_column(String(32))
bot_account: Mapped[str | None]
source_type: Mapped[str | None] = mapped_column(String(32), index=True)
source_account: Mapped[str | None] = mapped_column(index=True)
message: Mapped[Message | None] = mapped_column(PickleType)
game_platform: Mapped[GameType] = mapped_column(String(32), index=True, init=False)
command_type: Mapped[CommandType] = mapped_column(String(16), index=True, init=False)
command_args: Mapped[list[str]] = mapped_column(JSON, init=False)
game_user: Mapped[BaseUser] = mapped_column(PydanticType(get_model=BaseUser.__subclasses__), init=False)
processed_data: Mapped[BaseProcessedData] = mapped_column(
PydanticType(get_model=BaseProcessedData.__subclasses__), init=False
)
finish_time: Mapped[datetime] = mapped_column(DateTime, init=False)
session_persist_id: Mapped[int]
game_platform: Mapped[GameType] = mapped_column(String(32), index=True)
command_type: Mapped[AllCommandType] = mapped_column(String(16), index=True)
command_args: Mapped[list[str]] = mapped_column(JSON)
finish_time: Mapped[datetime] = mapped_column(DateTime)

View File

@@ -1,99 +0,0 @@
from abc import ABC, abstractmethod
from datetime import datetime, timezone
from typing import Any
from nonebot.matcher import Matcher
from nonebot_plugin_alconna import AlcMatches, AlconnaMatcher
from nonebot_plugin_alconna.uniseg import UniMessage
from nonebot_plugin_userinfo import UserInfo # type: ignore[import-untyped]
from ..utils.exception import MessageFormatError
from ..utils.recorder import Recorder
from ..utils.typing import CommandType, GameType
from .schemas import BaseProcessedData as ProcessedData
from .schemas import BaseRawResponse as RawResponse
from .schemas import BaseUser as User
UTC = timezone.utc
class Processor(ABC):
event_id: int
command_type: CommandType
command_args: list[str]
user: User
raw_response: RawResponse
processed_data: ProcessedData
@abstractmethod
def __init__(
self,
event_id: int,
user: User,
command_args: list[str],
) -> None:
self.event_id = event_id
self.user = user
self.command_args = command_args
@property
@abstractmethod
def game_platform(self) -> GameType:
"""游戏平台"""
raise NotImplementedError
@abstractmethod
async def handle_bind(
self,
platform: str,
account: str,
bot_info: UserInfo,
*args: Any, # noqa: ANN401
**kwargs: Any, # noqa: ANN401
) -> UniMessage:
"""处理绑定消息"""
raise NotImplementedError
@abstractmethod
async def handle_query(self) -> UniMessage:
"""处理查询消息"""
raise NotImplementedError
def __del__(self) -> None:
finish_time = datetime.now(tz=UTC)
if Recorder.is_error_event(self.event_id):
Recorder.del_error_event(self.event_id)
return
historical_data = Recorder.get_historical_data(self.event_id)
historical_data.game_platform = self.game_platform
historical_data.command_type = self.command_type
historical_data.command_args = self.command_args
historical_data.game_user = self.user
historical_data.processed_data = self.processed_data
historical_data.finish_time = finish_time
Recorder.update_historical_data(self.event_id, historical_data)
def add_default_handlers(matcher: type[AlconnaMatcher]) -> None:
@matcher.handle()
async def _(matcher: Matcher, account: MessageFormatError):
await matcher.finish(str(account))
@matcher.handle()
async def _(matcher: Matcher, matches: AlcMatches):
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"查看帮助'
)
@matcher.handle()
async def _(matcher: Matcher, other: Any): # noqa: ANN401
await matcher.finish()
from . import ( # noqa: F401, E402
io_data_processor,
top_data_processor,
tos_data_processor,
)

View File

@@ -1,2 +0,0 @@
BIND_COMMAND: list[str] = ['绑定', 'bind']
QUERY_COMMAND: list[str] = ['', '查询', 'query', 'stats']

View File

@@ -1,196 +0,0 @@
from datetime import datetime, timedelta, timezone
from zoneinfo import ZoneInfo
from arclet.alconna import Alconna, AllParam, Arg, ArgFlag, Args, CommandMeta, Option
from nonebot.adapters import Bot, Event
from nonebot.matcher import Matcher
from nonebot_plugin_alconna import At, on_alconna
from nonebot_plugin_alconna.uniseg import UniMessage
from nonebot_plugin_orm import get_session
from nonebot_plugin_userinfo import BotUserInfo, UserInfo # type: ignore[import-untyped]
from sqlalchemy import func, select
from ...db import query_bind_info
from ...utils.exception import HandleNotFinishedError, NeedCatchError
from ...utils.metrics import get_metrics
from ...utils.platform import get_platform
from ...utils.typing import Me
from .. import add_default_handlers
from ..constant import BIND_COMMAND, QUERY_COMMAND
from .constant import GAME_TYPE
from .model import IORank
from .processor import Processor, User, identify_user_info
from .typing import Rank
UTC = timezone.utc
alc = on_alconna(
Alconna(
'io',
Option(
BIND_COMMAND[0],
Args(
Arg(
'account',
identify_user_info,
notice='IO 用户名 / ID',
flags=[ArgFlag.HIDDEN],
)
),
alias=BIND_COMMAND[1:],
compact=True,
dest='bind',
help_text='绑定 IO 账号',
),
Option(
QUERY_COMMAND[0],
Args(
Arg(
'target',
At | Me,
notice='@想要查询的人 | 自己',
flags=[ArgFlag.HIDDEN, ArgFlag.OPTIONAL],
),
Arg(
'account',
identify_user_info,
notice='IO 用户名 / ID',
flags=[ArgFlag.HIDDEN, ArgFlag.OPTIONAL],
),
),
alias=QUERY_COMMAND[1:],
compact=True,
dest='query',
help_text='查询 IO 游戏信息',
),
Option(
'rank',
Args(Arg('rank', Rank, notice='IO 段位')),
alias={'Rank', 'RANK', '段位'},
compact=True,
dest='rank',
help_text='查询 IO 段位信息',
),
Arg('other', AllParam, flags=[ArgFlag.HIDDEN, ArgFlag.OPTIONAL]),
meta=CommandMeta(
description='查询 TETR.IO 的信息',
example='io绑定scdhh\nio查我\niorankx',
compact=True,
fuzzy_match=True,
),
),
skip_for_unmatch=False,
auto_send_output=True,
aliases={'IO'},
)
alc.shortcut('fkosk', {'command': 'io查', 'args': ['']})
@alc.assign('bind')
async def _(bot: Bot, event: Event, matcher: Matcher, account: User, bot_info: UserInfo = BotUserInfo()): # noqa: B008
proc = Processor(event_id=id(event), user=account, command_args=[])
try:
await (
await proc.handle_bind(platform=get_platform(bot), account=event.get_user_id(), bot_info=bot_info)
).finish()
except NeedCatchError as e:
await matcher.send(str(e))
raise HandleNotFinishedError from e
@alc.assign('query')
async def _(bot: Bot, event: Event, matcher: Matcher, target: At | Me):
async with get_session() as session:
bind = await query_bind_info(
session=session,
chat_platform=get_platform(bot),
chat_account=(target.target if isinstance(target, At) else event.get_user_id()),
game_platform=GAME_TYPE,
)
if bind is None:
await matcher.finish('未查询到绑定信息')
message = '* 由于无法验证绑定信息, 不能保证查询到的用户为本人\n'
proc = Processor(
event_id=id(event),
user=User(ID=bind.game_account),
command_args=[],
)
try:
await (UniMessage(message) + await proc.handle_query()).finish()
except NeedCatchError as e:
await matcher.send(str(e))
raise HandleNotFinishedError from e
@alc.assign('query')
async def _(event: Event, matcher: Matcher, account: User):
proc = Processor(
event_id=id(event),
user=account,
command_args=[],
)
try:
await (await proc.handle_query()).finish()
except NeedCatchError as e:
await matcher.send(str(e))
raise HandleNotFinishedError from e
@alc.assign('rank')
async def _(matcher: Matcher, rank: Rank):
if rank == 'z':
await matcher.finish('暂不支持查询未知段位')
async with get_session() as session:
latest_data = (
await session.scalars(select(IORank).where(IORank.rank == rank).order_by(IORank.id.desc()).limit(1))
).one()
compare_data = (
await session.scalars(
select(IORank)
.where(IORank.rank == rank)
.order_by(
func.abs(
func.julianday(IORank.update_time)
- func.julianday(latest_data.update_time - timedelta(hours=24))
)
)
.limit(1)
)
).one()
message = ''
if (datetime.now(UTC) - latest_data.update_time.replace(tzinfo=UTC)) > timedelta(hours=7):
message += 'Warning: 数据超过7小时未更新, 请联系Bot主人查看后台\n'
message += f'{rank.upper()} 段 分数线 {latest_data.tr_line:.2f} TR, {latest_data.player_count} 名玩家\n'
if compare_data.id != latest_data.id:
message += f'对比 {(latest_data.update_time-compare_data.update_time).total_seconds()/3600:.2f} 小时前趋势: {f"{difference:.2f}" if (difference:=latest_data.tr_line-compare_data.tr_line) > 0 else f"{-difference:.2f}" if difference < 0 else ""}'
else:
message += '暂无对比数据'
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[1])
low_vs = get_metrics(vs=latest_data.low_vs[1])
max_pps = get_metrics(pps=latest_data.high_pps[1])
max_vs = get_metrics(vs=latest_data.high_vs[1])
message += (
'\n'
'平均数据:\n'
f"L'PM: {avg.lpm} ( {avg.pps} pps )\n"
f'APM: {avg.apm} ( x{avg.apl} )\n'
f'ADPM: {avg.adpm} ( x{avg.adpl} ) ( {avg.vs}vs )\n'
'\n'
'最低数据:\n'
f"L'PM: {low_pps.lpm} ( {low_pps.pps} pps ) By: {latest_data.low_pps[0]['name'].upper()}\n"
f'APM: {latest_data.low_apm[1]} By: {latest_data.low_apm[0]["name"].upper()}\n'
f'ADPM: {low_vs.adpm} ( {low_vs.vs}vs ) By: {latest_data.low_vs[0]["name"].upper()}\n'
'\n'
'最高数据:\n'
f"L'PM: {max_pps.lpm} ( {max_pps.pps} pps ) By: {latest_data.high_pps[0]['name'].upper()}\n"
f'APM: {latest_data.high_apm[1]} By: {latest_data.high_apm[0]["name"].upper()}\n'
f'ADPM: {max_vs.adpm} ( {max_vs.vs}vs ) By: {latest_data.high_vs[0]["name"].upper()}\n'
'\n'
f'数据更新时间: {latest_data.update_time.replace(tzinfo=UTC).astimezone(ZoneInfo("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")}'
)
await matcher.finish(message)
add_default_handlers(alc)

View File

@@ -1,30 +0,0 @@
from datetime import datetime, timezone
from aiocache import Cache as ACache # type: ignore[import-untyped]
from nonebot.compat import type_validate_json
from nonebot.log import logger
from ...utils.request import Request
from .schemas.base import FailedModel, SuccessModel
UTC = timezone.utc
class Cache:
cache = ACache(ACache.MEMORY)
@classmethod
async def get(cls, url: str) -> bytes:
cached_data = await cls.cache.get(url)
if cached_data is None:
response_data = await Request.request(url)
parsed_data: SuccessModel | FailedModel = type_validate_json(SuccessModel | FailedModel, response_data) # type: ignore[arg-type]
if isinstance(parsed_data, SuccessModel):
await cls.cache.add(
url,
response_data,
(parsed_data.cache.cached_until - datetime.now(UTC)).total_seconds(),
)
return response_data
logger.debug(f'{url}: Cache hit!')
return cached_data

View File

@@ -1,25 +0,0 @@
from typing import Literal
from .typing import Rank
GAME_TYPE: Literal['IO'] = 'IO'
BASE_URL = 'https://ch.tetr.io/api/'
RANK_PERCENTILE: dict[Rank, float] = {
'x': 1,
'u': 5,
'ss': 11,
's+': 17,
's': 23,
's-': 30,
'a+': 38,
'a': 46,
'a-': 54,
'b+': 62,
'b': 70,
'b-': 78,
'c+': 84,
'c': 90,
'c-': 95,
'd+': 97.5,
'd': 100,
}

View File

@@ -1,30 +0,0 @@
from datetime import datetime, timezone
from nonebot_plugin_orm import Model
from sqlalchemy import JSON, DateTime, String
from sqlalchemy.orm import Mapped, MappedAsDataclass, mapped_column
from .typing import Rank
UTC = timezone.utc
class IORank(MappedAsDataclass, Model):
id: Mapped[int] = mapped_column(init=False, primary_key=True)
rank: Mapped[Rank] = mapped_column(String(2), index=True)
tr_line: Mapped[float]
player_count: Mapped[int]
low_pps: Mapped[tuple[dict[str, str], float]] = mapped_column(JSON)
low_apm: Mapped[tuple[dict[str, str], float]] = mapped_column(JSON)
low_vs: Mapped[tuple[dict[str, str], float]] = mapped_column(JSON)
avg_pps: Mapped[float]
avg_apm: Mapped[float]
avg_vs: Mapped[float]
high_pps: Mapped[tuple[dict[str, str], float]] = mapped_column(JSON)
high_apm: Mapped[tuple[dict[str, str], float]] = mapped_column(JSON)
high_vs: Mapped[tuple[dict[str, str], float]] = mapped_column(JSON)
update_time: Mapped[datetime] = mapped_column(
DateTime,
index=True,
)
file_hash: Mapped[str | None] = mapped_column(String(128), index=True)

View File

@@ -1,324 +0,0 @@
from asyncio import gather
from collections import defaultdict
from collections.abc import Callable
from datetime import datetime, timedelta, timezone
from hashlib import md5, sha512
from math import floor
from re import match
from statistics import mean
from typing import Literal
from urllib.parse import urlunparse
from aiofiles import open
from nonebot import get_driver
from nonebot.compat import type_validate_json
from nonebot.utils import run_sync
from nonebot_plugin_alconna.uniseg import UniMessage
from nonebot_plugin_apscheduler import scheduler # type: ignore[import-untyped]
from nonebot_plugin_localstore import get_data_file # type: ignore[import-untyped]
from nonebot_plugin_orm import get_session
from nonebot_plugin_userinfo import UserInfo as NBUserInfo # type: ignore[import-untyped]
from sqlalchemy import select
from typing_extensions import override
from zstandard import ZstdCompressor
from ...db import BindStatus, create_or_update_bind
from ...utils.avatar import get_avatar
from ...utils.exception import MessageFormatError, RequestError, WhatTheFuckError
from ...utils.host import HostPage, get_self_netloc
from ...utils.render import render
from ...utils.request import splice_url
from ...utils.retry import retry
from ...utils.screenshot import screenshot
from .. import Processor as ProcessorMeta
from .cache import Cache
from .constant import BASE_URL, GAME_TYPE, RANK_PERCENTILE
from .model import IORank
from .schemas.base import FailedModel
from .schemas.league_all import LeagueAll
from .schemas.league_all import ValidUser as LeagueAllUser
from .schemas.response import ProcessedData, RawResponse
from .schemas.user import User
from .schemas.user_info import NeverPlayedLeague, NeverRatedLeague, RatedLeague, UserInfo
from .schemas.user_info import SuccessModel as InfoSuccess
from .schemas.user_records import SoloRecord, UserRecords
from .schemas.user_records import SuccessModel as RecordsSuccess
from .typing import Rank
UTC = timezone.utc
driver = get_driver()
def identify_user_info(info: str) -> User | MessageFormatError:
if match(r'^[a-f0-9]{24}$', info):
return User(ID=info)
if match(r'^[a-zA-Z0-9_-]{3,16}$', info):
return User(name=info.lower())
return MessageFormatError('用户名/ID不合法')
class Processor(ProcessorMeta):
user: User
raw_response: RawResponse
processed_data: ProcessedData
@override
def __init__(self, event_id: int, user: User, command_args: list[str]) -> None:
super().__init__(event_id, user, command_args)
self.raw_response = RawResponse()
self.processed_data = ProcessedData()
@property
@override
def game_platform(self) -> Literal['IO']:
return GAME_TYPE
@override
async def handle_bind(self, platform: str, account: str, bot_info: NBUserInfo) -> UniMessage:
"""处理绑定消息"""
self.command_type = 'bind'
await self.get_user()
if self.user.ID is None:
raise # FIXME: 不知道怎么才能把这类型给变过来了
async with get_session() as session:
bind_status = await create_or_update_bind(
session=session,
chat_platform=platform,
chat_account=account,
game_platform=GAME_TYPE,
game_account=self.user.ID,
)
bot_avatar = await get_avatar(bot_info, 'Data URI', '../../static/logo/logo.svg')
user_info = await self.get_user_info()
if bind_status in (BindStatus.SUCCESS, BindStatus.UPDATE):
async with HostPage(
await render(
'bind.j2.html',
user_avatar=f'https://tetr.io/user-content/avatars/{user_info.data.user.id}.jpg?rv={user_info.data.user.avatar_revision}'
if user_info.data.user.avatar_revision is not None
else f'../../identicon?md5={md5(user_info.data.user.id.encode()).hexdigest()}', # noqa: S324
state='unknown',
bot_avatar=bot_avatar,
game_type=self.game_platform,
user_name=user_info.data.user.username.upper(),
bot_name=bot_info.user_name,
command='io查我',
)
) as page_hash:
message = UniMessage.image(
raw=await screenshot(
urlunparse(('http', get_self_netloc(), f'/host/page/{page_hash}.html', '', '', ''))
)
)
return message
@override
async def handle_query(self) -> UniMessage:
"""处理查询消息"""
self.command_type = 'query'
await self.get_user()
user_info, user_records = await gather(self.get_user_info(), self.get_user_records())
user_name = user_info.data.user.username.upper()
league = user_info.data.user.league
sprint = user_records.data.records.sprint
blitz = user_records.data.records.blitz
if isinstance(league, RatedLeague) and league.vs is not None:
if sprint.record is None:
sprint_value = 'N/A'
else:
if not isinstance(sprint.record, SoloRecord):
raise WhatTheFuckError('40L记录不是单人记录')
duration = timedelta(milliseconds=sprint.record.endcontext.final_time).total_seconds()
sprint_value = f'{duration:.1f}s' if duration < 60 else f'{duration // 60:.0f}m {duration % 60:.1f}s' # noqa: PLR2004
if blitz.record is None:
blitz_value = 'N/A'
else:
if not isinstance(blitz.record, SoloRecord):
raise WhatTheFuckError('Blitz记录不是单人记录')
blitz_value = f'{blitz.record.endcontext.score:,}'
async with HostPage(
await render(
'data.j2.html',
user_avatar=f'https://tetr.io/user-content/avatars/{user_info.data.user.id}.jpg?rv={user_info.data.user.avatar_revision}'
if user_info.data.user.avatar_revision is not None
else f'../../identicon?md5={md5(user_info.data.user.id.encode()).hexdigest()}', # noqa: S324
user_name=user_name,
user_sign=user_info.data.user.bio,
game_type='TETR.IO',
ranking=round(league.glicko, 2),
rd=round(league.rd, 2),
rank=league.rank,
TR=round(league.rating, 2),
global_rank=league.standing,
lpm=round(lpm := (league.pps * 24), 2),
pps=league.pps,
apm=league.apm,
apl=round(league.apm / lpm, 2),
adpm=round(adpm := (league.vs * 0.6), 2),
adpl=round(adpm / lpm, 2),
vs=league.vs,
sprint=sprint_value,
blitz=blitz_value,
data=[[0, 0]],
split_value=0,
value_max=0,
value_min=0,
offset=0,
app=(app := (league.apm / (60 * league.pps))),
dsps=(dsps := ((league.vs / 100) - (league.apm / 60))),
dspp=(dspp := (dsps / league.pps)),
ci=150 * dspp - 125 * app + 50 * (league.vs / league.apm) - 25,
ge=2 * ((app * dsps) / league.pps),
)
) as page_hash:
return UniMessage.image(
raw=await screenshot(
urlunparse(('http', get_self_netloc(), f'/host/page/{page_hash}.html', '', '', ''))
)
)
# call back
ret_message = ''
if isinstance(league, NeverPlayedLeague):
ret_message += f'用户 {user_name} 没有排位统计数据'
else:
if isinstance(league, NeverRatedLeague):
ret_message += f'用户 {user_name} 暂未完成定级赛, 最近十场的数据:'
else:
if league.rank == 'z':
ret_message += f'用户 {user_name} 暂无段位, {round(league.rating,2)} TR'
else:
ret_message += (
f'{league.rank.upper()} 段用户 {user_name} {round(league.rating,2)} TR (#{league.standing})'
)
ret_message += f', 段位分 {round(league.glicko,2)}±{round(league.rd,2)}, 最近十场的数据:'
lpm = league.pps * 24
ret_message += f"\nL'PM: {round(lpm, 2)} ( {league.pps} pps )"
ret_message += f'\nAPM: {league.apm} ( x{round(league.apm/lpm,2)} )'
if league.vs is not None:
adpm = league.vs * 0.6
ret_message += f'\nADPM: {round(adpm,2)} ( x{round(adpm/lpm,2)} ) ( {league.vs}vs )'
user_records = await self.get_user_records()
sprint = user_records.data.records.sprint
if sprint.record is not None:
if not isinstance(sprint.record, SoloRecord):
raise WhatTheFuckError('40L记录不是单人记录')
ret_message += f'\n40L: {round(sprint.record.endcontext.final_time/1000,2)}s'
ret_message += f' ( #{sprint.rank} )' if sprint.rank is not None else ''
blitz = user_records.data.records.blitz
if blitz.record is not None:
if not isinstance(blitz.record, SoloRecord):
raise WhatTheFuckError('Blitz记录不是单人记录')
ret_message += f'\nBlitz: {blitz.record.endcontext.score}'
ret_message += f' ( #{blitz.rank} )' if blitz.rank is not None else ''
return UniMessage(ret_message)
async def get_user(self) -> None:
"""
用于获取 UserName 和 UserID 的函数
"""
if self.user.name is None:
self.user.name = (await self.get_user_info()).data.user.username
if self.user.ID is None:
self.user.ID = (await self.get_user_info()).data.user.id
async def get_user_info(self) -> InfoSuccess:
"""获取用户数据"""
if self.processed_data.user_info is None:
self.raw_response.user_info = await Cache.get(
splice_url([BASE_URL, 'users/', f'{self.user.ID or self.user.name}'])
)
user_info: UserInfo = type_validate_json(UserInfo, self.raw_response.user_info) # type: ignore[arg-type]
if isinstance(user_info, FailedModel):
raise RequestError(f'用户信息请求错误:\n{user_info.error}')
self.processed_data.user_info = user_info
return self.processed_data.user_info
async def get_user_records(self) -> RecordsSuccess:
"""获取Solo数据"""
if self.processed_data.user_records is None:
self.raw_response.user_records = await Cache.get(
splice_url([BASE_URL, 'users/', f'{self.user.ID or self.user.name}/', 'records'])
)
user_records: UserRecords = type_validate_json(UserRecords, self.raw_response.user_records) # type: ignore[arg-type]
if isinstance(user_records, FailedModel):
raise RequestError(f'用户Solo数据请求错误:\n{user_records.error}')
self.processed_data.user_records = user_records
return self.processed_data.user_records
@scheduler.scheduled_job('cron', hour='0,6,12,18', minute=0)
@retry(exception_type=RequestError, delay=timedelta(minutes=15))
async def get_io_rank_data() -> None:
league_all: LeagueAll = type_validate_json(
LeagueAll, # type: ignore[arg-type]
(data := await Cache.get(splice_url([BASE_URL, 'users/lists/league/all']))),
)
if isinstance(league_all, FailedModel):
raise RequestError(f'排行榜数据请求错误:\n{league_all.error}')
def pps(user: LeagueAllUser) -> float:
return user.league.pps
def apm(user: LeagueAllUser) -> float:
return user.league.apm
def vs(user: LeagueAllUser) -> float:
return user.league.vs
def _min(users: list[LeagueAllUser], field: Callable[[LeagueAllUser], float]) -> LeagueAllUser:
return min(users, key=field)
def _max(users: list[LeagueAllUser], field: Callable[[LeagueAllUser], float]) -> LeagueAllUser:
return max(users, key=field)
def build_extremes_data(
users: list[LeagueAllUser],
field: Callable[[LeagueAllUser], float],
sort: Callable[[list[LeagueAllUser], Callable[[LeagueAllUser], float]], LeagueAllUser],
) -> tuple[dict[str, str], float]:
user = sort(users, field)
return User(ID=user.id, name=user.username).dict(), field(user)
data_hash: str | None = await run_sync((await run_sync(sha512)(data)).hexdigest)()
async with open(get_data_file('nonebot_plugin_tetris_stats', f'{data_hash}.json.zst'), mode='wb') as file:
await file.write(await run_sync(ZstdCompressor(level=12, threads=-1).compress)(data))
users = [i for i in league_all.data.users if isinstance(i, LeagueAllUser)]
rank_to_users: defaultdict[Rank, list[LeagueAllUser]] = defaultdict(list)
for i in users:
rank_to_users[i.league.rank].append(i)
rank_info: list[IORank] = []
for rank, percentile in RANK_PERCENTILE.items():
offset = floor((percentile / 100) * len(users)) - 1
tr_line = users[offset].league.rating
rank_users = rank_to_users[rank]
rank_info.append(
IORank(
rank=rank,
tr_line=tr_line,
player_count=len(rank_users),
low_pps=(build_extremes_data(rank_users, pps, _min)),
low_apm=(build_extremes_data(rank_users, apm, _min)),
low_vs=(build_extremes_data(rank_users, vs, _min)),
avg_pps=mean({i.league.pps for i in rank_users}),
avg_apm=mean({i.league.apm for i in rank_users}),
avg_vs=mean({i.league.vs for i in rank_users}),
high_pps=(build_extremes_data(rank_users, pps, _max)),
high_apm=(build_extremes_data(rank_users, apm, _max)),
high_vs=(build_extremes_data(rank_users, vs, _max)),
update_time=league_all.cache.cached_at,
file_hash=data_hash,
)
)
async with get_session() as session:
session.add_all(rank_info)
await session.commit()
@driver.on_startup
async def _() -> None:
async with get_session() as session:
latest_time = await session.scalar(select(IORank.update_time).order_by(IORank.id.desc()).limit(1))
if latest_time is None or datetime.now(tz=UTC) - latest_time.replace(tzinfo=UTC) > timedelta(hours=6):
await get_io_rank_data()

View File

@@ -1,20 +0,0 @@
from datetime import datetime
from typing import Literal
from pydantic import BaseModel
class Cache(BaseModel):
status: str
cached_at: datetime
cached_until: datetime
class SuccessModel(BaseModel):
success: Literal[True]
cache: Cache
class FailedModel(BaseModel):
success: Literal[False]
error: str

View File

@@ -1,66 +0,0 @@
from nonebot.compat import PYDANTIC_V2
from pydantic import BaseModel, ConfigDict, 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
verified: bool
country: str | None = None
if PYDANTIC_V2:
model_config = ConfigDict(populate_by_name=True)
else:
class Config:
allow_population_by_field_name = True
class SuccessModel(BaseSuccessModel):
class Data(BaseModel):
class ValidUser(_User):
class League(BaseModel):
gamesplayed: int
gameswon: int
rating: float
glicko: float
rd: float
rank: Rank
bestrank: Rank
apm: float
pps: float
vs: float
decaying: bool
league: League
class InvalidUser(_User):
class League(BaseModel):
gamesplayed: int
gameswon: int
rating: float
glicko: float | None = None
rd: float | None = None
rank: Rank
bestrank: Rank
apm: float | None = None
pps: float | None = None
vs: float | None = None
decaying: bool
league: League
users: list[ValidUser | InvalidUser]
data: Data
LeagueAll = SuccessModel | FailedModel
ValidUser = SuccessModel.Data.ValidUser
InvalidUser = SuccessModel.Data.InvalidUser

View File

@@ -1,21 +0,0 @@
from typing import Literal
from ... import ProcessedData as ProcessedDataMeta
from ... import RawResponse as RawResponseMeta
from ..constant import GAME_TYPE
from .user_info import SuccessModel as InfoSuccess
from .user_records import SuccessModel as RecordsSuccess
class RawResponse(RawResponseMeta):
platform: Literal['IO'] = GAME_TYPE
user_info: bytes | None = None
user_records: bytes | None = None
class ProcessedData(ProcessedDataMeta):
platform: Literal['IO'] = GAME_TYPE
user_info: InfoSuccess | None = None
user_records: RecordsSuccess | None = None

View File

@@ -1,17 +0,0 @@
from typing import Literal
from ...schemas import BaseUser
from ..constant import GAME_TYPE
class User(BaseUser):
platform: Literal['IO'] = GAME_TYPE
ID: str | None = None
name: str | None = None
@property
def unique_identifier(self) -> str:
if self.ID is None:
raise ValueError('不完整的User!')
return self.ID

View File

@@ -1,132 +0,0 @@
from datetime import datetime
from typing import Literal
from nonebot.compat import PYDANTIC_V2
from pydantic import BaseModel, ConfigDict, Field
from ..typing import Rank
from .base import FailedModel
from .base import SuccessModel as BaseSuccessModel
class SuccessModel(BaseSuccessModel):
class Data(BaseModel):
class User(BaseModel):
class Badge(BaseModel):
id: str
label: str
ts: datetime | None = None
class NeverPlayedLeague(BaseModel):
gamesplayed: Literal[0]
gameswon: Literal[0]
rating: Literal[-1]
rank: Literal['z']
standing: Literal[-1]
standing_local: Literal[-1]
next_rank: None
prev_rank: None
next_at: Literal[-1]
prev_at: Literal[-1]
percentile: Literal[-1]
percentile_rank: Literal['z']
apm: None = Field(None)
pps: None = Field(None)
vs: None = Field(None)
decaying: bool
class NeverRatedLeague(BaseModel):
gamesplayed: Literal[1, 2, 3, 4, 5, 6, 7, 8, 9]
gameswon: int
rating: Literal[-1]
rank: Literal['z']
standing: Literal[-1]
standing_local: Literal[-1]
next_rank: None
prev_rank: None
next_at: Literal[-1]
prev_at: Literal[-1]
percentile: Literal[-1]
percentile_rank: Literal['z']
apm: float
pps: float
vs: float
decaying: bool
class RatedLeague(BaseModel):
gamesplayed: int
gameswon: int
rating: float
rank: Rank
bestrank: Rank
standing: int
standing_local: int
next_rank: Rank | None = None
prev_rank: Rank | None = None
next_at: int
prev_at: int
percentile: float
percentile_rank: str
glicko: float
rd: float
apm: float
pps: float
vs: float | None = None
decaying: bool
class Connections(BaseModel):
class Discord(BaseModel):
id: str
username: str
discord: Discord | None = None
class Distinguishment(BaseModel):
type: str
id: str = Field(..., alias='_id')
username: str
role: Literal['anon', 'user', 'bot', 'halfmod', 'mod', 'admin', 'sysop', 'banned']
ts: datetime | None = None
botmaster: str | None = None
badges: list[Badge]
xp: float
gamesplayed: int
gameswon: int
gametime: float
country: str | None = None
badstanding: bool | None = None
supporter: bool | None = None # osk说是必有, 但实际上不是 fk osk
supporter_tier: int
verified: bool
league: NeverPlayedLeague | NeverRatedLeague | RatedLeague
avatar_revision: int | None = None
"""This user's avatar ID. Get their avatar at
https://tetr.io/user-content/avatars/{ USERID }.jpg?rv={ AVATAR_REVISION }"""
banner_revision: int | None = None
"""This user's banner ID. Get their banner at
https://tetr.io/user-content/banners/{ USERID }.jpg?rv={ BANNER_REVISION }
Ignore this field if the user is not a supporter."""
bio: str | None = None
connections: Connections
friend_count: int | None = None
distinguishment: Distinguishment | None = None
if PYDANTIC_V2:
model_config = ConfigDict(populate_by_name=True)
else:
class Config:
allow_population_by_field_name = True
user: User
data: Data
NeverPlayedLeague = SuccessModel.Data.User.NeverPlayedLeague
NeverRatedLeague = SuccessModel.Data.User.NeverRatedLeague
RatedLeague = SuccessModel.Data.User.RatedLeague
UserInfo = SuccessModel | FailedModel

View File

@@ -1,143 +0,0 @@
from datetime import datetime
from nonebot.compat import PYDANTIC_V2
from pydantic import BaseModel, ConfigDict, Field
from .base import FailedModel
from .base import SuccessModel as BaseSuccessModel
class EndContext(BaseModel):
class Time(BaseModel):
start: int
zero: bool
locked: bool
prev: int
frameoffset: int | None = None
class Clears(BaseModel):
singles: int
doubles: int
triples: int
quads: int
pentas: int | None = None
realtspins: int
minitspins: int
minitspinsingles: int
tspinsingles: int
minitspindoubles: int
tspindoubles: int
tspintriples: int
tspinquads: int
allclear: int
class Garbage(BaseModel):
sent: int
received: int
attack: int | None = None
cleared: int
class Finesse(BaseModel):
combo: int
faults: int
perfectpieces: int
seed: int
lines: int
level_lines: int
level_lines_needed: int
inputs: int
holds: int | None = None
time: Time
score: int
zenlevel: int | None = None
zenprogress: int | None = None
level: int
combo: int
currentcombopower: int | None = None # WTF
topcombo: int
btb: int
topbtb: int
currentbtbchainpower: int | None = None # WTF * 2
tspins: int
piecesplaced: int
clears: Clears
garbage: Garbage
kills: int
finesse: Finesse
final_time: float = Field(..., alias='finalTime')
gametype: str
if PYDANTIC_V2:
model_config = ConfigDict(populate_by_name=True)
else:
class Config:
allow_population_by_field_name = True
class _User(BaseModel):
id: str = Field(..., alias='_id')
username: str
if PYDANTIC_V2:
model_config = ConfigDict(populate_by_name=True)
else:
class Config:
allow_population_by_field_name = True
class _Record(BaseModel):
id: str = Field(..., alias='_id')
stream: str
replayid: str
user: _User
ts: datetime
ismulti: bool | None = None
if PYDANTIC_V2:
model_config = ConfigDict(populate_by_name=True)
else:
class Config:
allow_population_by_field_name = True
class BaseModeRecord(BaseModel):
class SoloRecord(_Record):
endcontext: EndContext
class MultiRecord(_Record):
endcontext: list[EndContext]
record: SoloRecord | MultiRecord | None = None
rank: int | None = None
class SuccessModel(BaseSuccessModel):
class Data(BaseModel):
class Records(BaseModel):
class Sprint(BaseModeRecord): ...
class Blitz(BaseModeRecord): ...
sprint: Sprint = Field(..., alias='40l')
blitz: Blitz
if PYDANTIC_V2:
model_config = ConfigDict(populate_by_name=True)
else:
class Config:
allow_population_by_field_name = True
class Zen(BaseModel):
level: int
score: int
records: Records
zen: Zen
data: Data
SoloRecord = BaseModeRecord.SoloRecord
MultiRecord = BaseModeRecord.MultiRecord
UserRecords = SuccessModel | FailedModel

View File

@@ -1,22 +0,0 @@
from typing import Literal
Rank = Literal[
'x',
'u',
'ss',
's+',
's',
's-',
'a+',
'a',
'a-',
'b+',
'b',
'b-',
'c+',
'c',
'c-',
'd+',
'd',
'z', # 未定级
]

View File

@@ -1,31 +0,0 @@
from abc import ABC, abstractmethod
from pydantic import BaseModel
from ..utils.typing import GameType
class Base(BaseModel):
platform: GameType
class BaseUser(ABC, Base):
"""游戏用户"""
def __eq__(self, __value: object) -> bool:
if isinstance(__value, BaseUser):
return self.unique_identifier == __value.unique_identifier
return False
@property
@abstractmethod
def unique_identifier(self) -> str:
raise NotImplementedError
class BaseRawResponse(Base):
"""原始请求数据"""
class BaseProcessedData(Base):
"""处理/验证后的数据"""

View File

@@ -1,134 +0,0 @@
from arclet.alconna import Alconna, AllParam, Arg, ArgFlag, Args, CommandMeta, Option
from nonebot.adapters import Bot, Event
from nonebot.matcher import Matcher
from nonebot_plugin_alconna import At, on_alconna
from nonebot_plugin_alconna.uniseg import UniMessage
from nonebot_plugin_orm import get_session
from nonebot_plugin_userinfo import BotUserInfo, EventUserInfo, UserInfo # type: ignore[import-untyped]
from ...db import query_bind_info
from ...utils.exception import HandleNotFinishedError, NeedCatchError
from ...utils.platform import get_platform
from ...utils.typing import Me
from .. import add_default_handlers
from ..constant import BIND_COMMAND, QUERY_COMMAND
from .constant import GAME_TYPE
from .processor import Processor, User, identify_user_info
alc = on_alconna(
Alconna(
'top',
Option(
BIND_COMMAND[0],
Args(
Arg(
'account',
identify_user_info,
notice='TOP 用户名',
flags=[ArgFlag.HIDDEN],
)
),
alias=BIND_COMMAND[1:],
compact=True,
dest='bind',
help_text='绑定 TOP 账号',
),
Option(
QUERY_COMMAND[0],
Args(
Arg(
'target',
At | Me,
notice='@想要查询的人 | 自己',
flags=[ArgFlag.HIDDEN, ArgFlag.OPTIONAL],
),
Arg(
'account',
identify_user_info | Me | At,
notice='TOP 用户名',
flags=[ArgFlag.HIDDEN, ArgFlag.OPTIONAL],
),
),
alias=QUERY_COMMAND[1:],
compact=True,
dest='query',
help_text='查询 TOP 游戏信息',
),
Arg('other', AllParam, flags=[ArgFlag.HIDDEN, ArgFlag.OPTIONAL]),
meta=CommandMeta(
description='查询 TetrisOnline波兰服 的信息',
example='top绑定scdhh\ntop查我',
compact=True,
fuzzy_match=True,
),
),
skip_for_unmatch=False,
auto_send_output=True,
aliases={'TOP'},
)
@alc.assign('bind')
async def _( # noqa: PLR0913
bot: Bot,
event: Event,
matcher: Matcher,
account: User,
bot_info: UserInfo = BotUserInfo(), # noqa: B008
user_info: UserInfo = EventUserInfo(), # noqa: B008
):
proc = Processor(
event_id=id(event),
user=account,
command_args=[],
)
try:
await (
await proc.handle_bind(
platform=get_platform(bot), account=event.get_user_id(), bot_info=bot_info, user_info=user_info
)
).finish()
except NeedCatchError as e:
await matcher.send(str(e))
raise HandleNotFinishedError from e
@alc.assign('query')
async def _(bot: Bot, event: Event, matcher: Matcher, target: At | Me):
async with get_session() as session:
bind = await query_bind_info(
session=session,
chat_platform=get_platform(bot),
chat_account=(target.target if isinstance(target, At) else event.get_user_id()),
game_platform=GAME_TYPE,
)
if bind is None:
await matcher.finish('未查询到绑定信息')
message = '* 由于无法验证绑定信息, 不能保证查询到的用户为本人\n'
proc = Processor(
event_id=id(event),
user=User(name=bind.game_account),
command_args=[],
)
try:
await (UniMessage(message) + await proc.handle_query()).finish()
except NeedCatchError as e:
await matcher.send(str(e))
raise HandleNotFinishedError from e
@alc.assign('query')
async def _(event: Event, matcher: Matcher, account: User):
proc = Processor(
event_id=id(event),
user=account,
command_args=[],
)
try:
await (await proc.handle_query()).finish()
except NeedCatchError as e:
await matcher.send(str(e))
raise HandleNotFinishedError from e
add_default_handlers(alc)

View File

@@ -1,4 +0,0 @@
from typing import Literal
GAME_TYPE: Literal['TOP'] = 'TOP'
BASE_URL = 'http://tetrisonline.pl/top/'

View File

@@ -1,162 +0,0 @@
from contextlib import suppress
from dataclasses import dataclass
from io import StringIO
from re import match
from typing import Literal
from urllib.parse import urlencode, urlunparse
from lxml import etree
from nonebot_plugin_alconna.uniseg import UniMessage
from nonebot_plugin_orm import get_session
from nonebot_plugin_userinfo import UserInfo # type: ignore[import-untyped]
from pandas import read_html
from typing_extensions import override
from ...db import BindStatus, create_or_update_bind
from ...utils.avatar import get_avatar
from ...utils.exception import MessageFormatError, RequestError
from ...utils.host import HostPage, get_self_netloc
from ...utils.render import render
from ...utils.request import Request, splice_url
from ...utils.screenshot import screenshot
from .. import Processor as ProcessorMeta
from ..schemas import BaseUser
from .constant import BASE_URL, GAME_TYPE
from .schemas.response import ProcessedData, RawResponse
class User(BaseUser):
platform: Literal['TOP'] = GAME_TYPE
name: str
@property
@override
def unique_identifier(self) -> str:
return self.name
@dataclass
class Data:
lpm: float
apm: float
@dataclass
class GameData:
day: Data | None
total: Data | None
def identify_user_info(info: str) -> User | MessageFormatError:
if match(r'^[a-zA-Z0-9_]{1,16}$', info):
return User(name=info)
return MessageFormatError('用户名不合法')
class Processor(ProcessorMeta):
user: User
raw_response: RawResponse
processed_data: ProcessedData
@override
def __init__(self, event_id: int, user: User, command_args: list[str]) -> None:
super().__init__(event_id, user, command_args)
self.raw_response = RawResponse()
self.processed_data = ProcessedData()
@property
@override
def game_platform(self) -> Literal['TOP']:
return GAME_TYPE
@override
async def handle_bind(self, platform: str, account: str, bot_info: UserInfo, user_info: UserInfo) -> UniMessage:
"""处理绑定消息"""
self.command_type = 'bind'
await self.check_user()
async with get_session() as session:
bind_status = await create_or_update_bind(
session=session,
chat_platform=platform,
chat_account=account,
game_platform=GAME_TYPE,
game_account=self.user.name,
)
bot_avatar = await get_avatar(bot_info, 'Data URI', '../../static/logo/logo.svg')
if bind_status in (BindStatus.SUCCESS, BindStatus.UPDATE):
async with HostPage(
await render(
'bind.j2.html',
user_avatar='../../static/static/logo/top.ico',
state='unknown',
bot_avatar=bot_avatar,
game_type=self.game_platform,
user_name=(await self.get_user_name()).upper(),
bot_name=bot_info.user_name,
command='top查我',
)
) as page_hash:
message = UniMessage.image(
raw=await screenshot(
urlunparse(('http', get_self_netloc(), f'/host/page/{page_hash}.html', '', '', ''))
)
)
return message
@override
async def handle_query(self) -> UniMessage:
"""处理查询消息"""
self.command_type = 'query'
await self.check_user()
game_data = await self.get_game_data()
message = ''
if game_data.day is not None:
message += f'用户 {self.user.name} 24小时内统计数据为: '
message += f"\nL'PM: {round(game_data.day.lpm,2)} ( {round(game_data.day.lpm/24,2)} pps )"
message += f'\nAPM: {round(game_data.day.apm,2)} ( x{round(game_data.day.apm/game_data.day.lpm,2)} )'
else:
message += f'用户 {self.user.name} 暂无24小时内统计数据'
if game_data.total is not None:
message += '\n历史统计数据为: '
message += f"\nL'PM: {round(game_data.total.lpm,2)} ( {round(game_data.total.lpm/24,2)} pps )"
message += f'\nAPM: {round(game_data.total.apm,2)} ( x{round(game_data.total.apm/game_data.total.lpm,2)} )'
else:
message += '\n暂无历史统计数据'
return UniMessage(message)
async def get_user_profile(self) -> str:
"""获取用户信息"""
if self.processed_data.user_profile is None:
url = splice_url([BASE_URL, 'profile.php', f'?{urlencode({"user":self.user.name})}'])
self.raw_response.user_profile = await Request.request(url, is_json=False)
self.processed_data.user_profile = self.raw_response.user_profile.decode()
return self.processed_data.user_profile
async def check_user(self) -> None:
if 'user not found!' in await self.get_user_profile():
raise RequestError('用户不存在!')
async def get_user_name(self) -> str:
"""获取用户名"""
data = etree.HTML(await self.get_user_profile()).xpath('//div[@class="mycontent"]/h1/text()')
return data[0].replace("'s profile", '')
async def get_game_data(self) -> GameData:
"""获取游戏统计数据"""
html = etree.HTML(await self.get_user_profile())
day = None
with suppress(ValueError):
day = Data(
lpm=float(str(html.xpath('//div[@class="mycontent"]/text()[3]')[0]).replace('lpm:', '').strip()),
apm=float(str(html.xpath('//div[@class="mycontent"]/text()[4]')[0]).replace('apm:', '').strip()),
)
table = StringIO(
etree.tostring(
html.xpath('//div[@class="mycontent"]/table[@class="mytable"]')[0],
encoding='utf-8',
).decode()
)
dataframe = read_html(table, encoding='utf-8', header=0)[0]
total = Data(lpm=dataframe['lpm'].mean(), apm=dataframe['apm'].mean()) if len(dataframe) != 0 else None
return GameData(day=day, total=total)

View File

@@ -1,16 +0,0 @@
from typing import Literal
from ...schemas import BaseProcessedData, BaseRawResponse
from ..constant import GAME_TYPE
class RawResponse(BaseRawResponse):
platform: Literal['TOP'] = GAME_TYPE
user_profile: bytes | None = None
class ProcessedData(BaseProcessedData):
platform: Literal['TOP'] = GAME_TYPE
user_profile: str | None = None

View File

@@ -1,188 +0,0 @@
from typing import NoReturn
from arclet.alconna import Alconna, AllParam, Arg, ArgFlag, Args, CommandMeta, Option
from nonebot.adapters import Bot, Event
from nonebot.matcher import Matcher
from nonebot_plugin_alconna import At, on_alconna
from nonebot_plugin_alconna.uniseg import UniMessage
from nonebot_plugin_orm import get_session
from nonebot_plugin_userinfo import BotUserInfo, UserInfo # type: ignore[import-untyped]
from ...db import query_bind_info
from ...utils.exception import HandleNotFinishedError, NeedCatchError, RequestError
from ...utils.platform import get_platform
from ...utils.typing import Me
from .. import add_default_handlers
from ..constant import BIND_COMMAND, QUERY_COMMAND
from .constant import GAME_TYPE
from .processor import Processor, User, identify_user_info
alc = on_alconna(
Alconna(
'茶服',
Option(
BIND_COMMAND[0],
Args(
Arg(
'account',
identify_user_info,
notice='茶服 用户名 / TeaID',
flags=[ArgFlag.HIDDEN],
)
),
alias=BIND_COMMAND[1:],
compact=True,
dest='bind',
help_text='绑定 茶服 账号',
),
Option(
QUERY_COMMAND[0],
Args(
Arg(
'target',
At | Me,
notice='@想要查询的人 | 自己',
flags=[ArgFlag.HIDDEN, ArgFlag.OPTIONAL],
),
Arg(
'account',
identify_user_info,
notice='茶服 用户名 / TeaID',
flags=[ArgFlag.HIDDEN, ArgFlag.OPTIONAL],
),
# 如果放在一个 Union Args 里, 验证顺序不能保证, 可能出错
),
alias=QUERY_COMMAND[1:],
compact=True,
dest='query',
help_text='查询 茶服 游戏信息',
),
Arg('other', AllParam, flags=[ArgFlag.HIDDEN, ArgFlag.OPTIONAL]),
meta=CommandMeta(
description='查询 TetrisOnline茶服 的信息',
example='茶服查我',
compact=True,
fuzzy_match=True,
),
),
skip_for_unmatch=False,
auto_send_output=True,
aliases={'tos', 'TOS'},
)
async def finish_special_query(matcher: Matcher, proc: Processor) -> NoReturn:
try:
await (await proc.handle_query()).finish()
except NeedCatchError as e:
if isinstance(e, RequestError) and '未找到此用户' in e.message:
matcher.skip()
await matcher.send(str(e))
raise HandleNotFinishedError from e
try:
from nonebot.adapters.onebot.v11 import GROUP as OB11GROUP
from nonebot.adapters.onebot.v11 import Bot as OB11Bot
from nonebot.adapters.onebot.v11 import MessageEvent as OB11MessageEvent
@alc.assign('query')
async def _(bot: OB11Bot, event: OB11MessageEvent, matcher: Matcher, target: At | Me):
if event.is_tome() and await OB11GROUP(bot, event):
await matcher.finish('不能查询bot的信息')
proc = Processor(
event_id=id(event),
user=User(teaid=f'onebot-{target.target}' if isinstance(target, At) else f'onebot-{event.get_user_id()}'),
command_args=[],
)
await finish_special_query(matcher, proc)
except ImportError:
pass
try:
from nonebot.adapters.kaiheila.event import MessageEvent as KookMessageEvent
@alc.assign('query')
async def _(event: KookMessageEvent, matcher: Matcher, target: At | Me):
proc = Processor(
event_id=id(event),
user=User(teaid=f'kook-{target.target}' if isinstance(target, At) else f'kook-{event.get_user_id()}'),
command_args=[],
)
await finish_special_query(matcher, proc)
except ImportError:
pass
try:
from nonebot.adapters.discord import MessageEvent as DiscordMessageEvent
@alc.assign('query')
async def _(event: DiscordMessageEvent, matcher: Matcher, target: At | Me):
proc = Processor(
event_id=id(event),
user=User(teaid=f'discord-{target.target}' if isinstance(target, At) else f'discord-{event.get_user_id()}'),
command_args=[],
)
await finish_special_query(matcher, proc)
except ImportError:
pass
@alc.assign('bind')
async def _(bot: Bot, event: Event, matcher: Matcher, account: User, bot_info: UserInfo = BotUserInfo()): # noqa: B008
proc = Processor(
event_id=id(event),
user=account,
command_args=[],
)
try:
await (
await proc.handle_bind(platform=get_platform(bot), account=event.get_user_id(), bot_info=bot_info)
).finish()
except NeedCatchError as e:
await matcher.send(str(e))
raise HandleNotFinishedError from e
@alc.assign('query')
async def _(bot: Bot, event: Event, matcher: Matcher, target: At | Me):
async with get_session() as session:
bind = await query_bind_info(
session=session,
chat_platform=get_platform(bot),
chat_account=(target.target if isinstance(target, At) else event.get_user_id()),
game_platform=GAME_TYPE,
)
if bind is None:
await matcher.finish('未查询到绑定信息')
message = '* 由于无法验证绑定信息, 不能保证查询到的用户为本人\n'
proc = Processor(
event_id=id(event),
user=User(teaid=bind.game_account),
command_args=[],
)
try:
await (UniMessage(message) + await proc.handle_query()).finish()
except NeedCatchError as e:
await matcher.send(str(e))
raise HandleNotFinishedError from e
@alc.assign('query')
async def _(event: Event, matcher: Matcher, account: User):
proc = Processor(
event_id=id(event),
user=account,
command_args=[],
)
try:
await (await proc.handle_query()).finish()
except NeedCatchError as e:
await matcher.send(str(e))
raise HandleNotFinishedError from e
add_default_handlers(alc)

View File

@@ -1,10 +0,0 @@
from typing import Literal
GAME_TYPE: Literal['TOS'] = 'TOS'
BASE_URL = {
'https://teatube.cn:8888/',
'http://cafuuchino1.studio26f.org:19970',
'http://cafuuchino2.studio26f.org:19970',
'http://cafuuchino3.studio26f.org:19970',
'http://cafuuchino4.studio26f.org:19970',
}

View File

@@ -1,253 +0,0 @@
from dataclasses import dataclass
from re import match
from typing import Literal
from urllib.parse import urlencode, urlunparse
from httpx import TimeoutException
from nonebot.compat import type_validate_json
from nonebot_plugin_alconna.uniseg import UniMessage
from nonebot_plugin_orm import get_session
from nonebot_plugin_userinfo import UserInfo as NBUserInfo # type: ignore[import-untyped]
from typing_extensions import override
from ...db import BindStatus, create_or_update_bind
from ...utils.avatar import get_avatar
from ...utils.exception import MessageFormatError, RequestError
from ...utils.host import HostPage, get_self_netloc
from ...utils.render import render
from ...utils.request import Request, splice_url
from ...utils.screenshot import screenshot
from .. import Processor as ProcessorMeta
from ..schemas import BaseUser
from .constant import BASE_URL, GAME_TYPE
from .schemas.response import ProcessedData, RawResponse
from .schemas.user_info import SuccessModel as InfoSuccess
from .schemas.user_info import UserInfo
from .schemas.user_profile import UserProfile
class User(BaseUser):
platform: Literal['TOS'] = GAME_TYPE
teaid: str | None = None
name: str | None = None
@property
@override
def unique_identifier(self) -> str:
if self.teaid is None:
raise ValueError('不完整的User!')
return self.teaid
@dataclass
class GameData:
num: int
pps: float
lpm: float
apm: float
adpm: float
apl: float
adpl: float
vs: float
def identify_user_info(info: str) -> User | MessageFormatError:
if (
match(
r'^(?!\.)(?!com[0-9]$)(?!con$)(?!lpt[0-9]$)(?!nul$)(?!prn$)[^\-][^\+][^\|\*\?\\\s\!:<>/$"]*[^\.\|\*\?\\\s\!:<>/$"]+$',
info,
)
and info.isdigit() is False
and 2 <= len(info) <= 18 # noqa: PLR2004
):
return User(name=info)
if info.startswith(('onebot-', 'qqguild-', 'kook-', 'discord-')) and info.split('-', maxsplit=1)[1].isdigit():
return User(teaid=info)
return MessageFormatError('用户名/QQ号不合法')
class Processor(ProcessorMeta):
user: User
raw_response: RawResponse
processed_data: ProcessedData
@override
def __init__(self, event_id: int, user: User, command_args: list[str]) -> None:
super().__init__(event_id, user, command_args)
self.raw_response = RawResponse(user_profile={})
self.processed_data = ProcessedData(user_profile={})
@property
@override
def game_platform(self) -> Literal['TOS']:
return GAME_TYPE
@override
async def handle_bind(self, platform: str, account: str, bot_info: NBUserInfo) -> UniMessage:
"""处理绑定消息"""
self.command_type = 'bind'
await self.get_user()
async with get_session() as session:
bind_status = await create_or_update_bind(
session=session,
chat_platform=platform,
chat_account=account,
game_platform=GAME_TYPE,
game_account=self.user.unique_identifier,
)
bot_avatar = await get_avatar(bot_info, 'Data URI', '../../static/logo/logo.svg')
user_info = await self.get_user_info()
if bind_status in (BindStatus.SUCCESS, BindStatus.UPDATE):
async with HostPage(
await render(
'bind.j2.html',
user_avatar='../../static/static/logo/tos.ico',
state='unknown',
bot_avatar=bot_avatar,
game_type=self.game_platform,
user_name=user_info.data.name.upper(),
bot_name=bot_info.user_name,
command='茶服查我',
)
) as page_hash:
message = UniMessage.image(
raw=await screenshot(
urlunparse(('http', get_self_netloc(), f'/host/page/{page_hash}.html', '', '', ''))
)
)
return message
@override
async def handle_query(self) -> UniMessage:
"""处理查询消息"""
self.command_type = 'query'
await self.get_user()
user_info = (await self.get_user_info()).data
message = f'用户 {user_info.name} ({user_info.teaid}) '
if user_info.ranked_games == '0':
message += '暂无段位统计数据'
else:
message += f', 段位分 {round(float(user_info.rating_now),2)}±{round(float(user_info.rd_now),2)} ({round(float(user_info.vol_now),2)}) '
game_data = await self.get_game_data()
if game_data is None:
message += ', 暂无游戏数据'
else:
message += f', 最近 {game_data.num} 局数据'
message += f"\nL'PM: {game_data.lpm} ( {game_data.pps} pps )"
message += f'\nAPM: {game_data.apm} ( x{game_data.apl} )'
message += f'\nADPM: {game_data.adpm} ( x{game_data.adpl} ) ( {game_data.vs}vs )'
message += f'\n40L: {float(user_info.pb_sprint)/1000:.2f}s' if user_info.pb_sprint != '2147483647' else ''
message += f'\nMarathon: {user_info.pb_marathon}' if user_info.pb_marathon != '0' else ''
message += f'\nChallenge: {user_info.pb_challenge}' if user_info.pb_challenge != '0' else ''
return UniMessage(message)
async def get_user(self) -> None:
"""
用于获取 UserName 和 UserID 的函数
"""
if self.user.name is None:
self.user.name = (await self.get_user_info()).data.name
if self.user.teaid is None:
self.user.teaid = (await self.get_user_info()).data.teaid
async def get_user_info(self) -> InfoSuccess:
"""获取用户信息"""
if self.processed_data.user_info is None:
if self.user.teaid is not None:
url = [
splice_url(
[
i,
'getTeaIdInfo',
f'?{urlencode({"teaId":self.user.teaid})}',
]
)
for i in BASE_URL
]
else:
url = [
splice_url(
[
i,
'getUsernameInfo',
f'?{urlencode({"username":self.user.name})}',
]
)
for i in BASE_URL
]
self.raw_response.user_info = await Request.failover_request(
url, failover_code=[502], failover_exc=(TimeoutException,)
)
user_info: UserInfo = type_validate_json(UserInfo, self.raw_response.user_info) # type: ignore[arg-type]
if not isinstance(user_info, InfoSuccess):
raise RequestError(f'用户信息请求错误:\n{user_info.error}')
self.processed_data.user_info = user_info
return self.processed_data.user_info
async def get_user_profile(self, other_parameter: dict[str, str | bytes] | None = None) -> UserProfile:
"""获取用户数据"""
if other_parameter is None:
other_parameter = {}
params = urlencode(dict(sorted(other_parameter.items())))
if self.processed_data.user_profile.get(params) is None:
self.raw_response.user_profile[params] = await Request.failover_request(
[
splice_url(
[
i,
'getProfile',
f'?{urlencode({"id":self.user.teaid or self.user.name,**other_parameter})}',
]
)
for i in BASE_URL
],
failover_code=[502],
failover_exc=(TimeoutException,),
)
self.processed_data.user_profile[params] = type_validate_json(
UserProfile, self.raw_response.user_profile[params]
)
return self.processed_data.user_profile[params]
async def get_game_data(self) -> GameData | None:
"""获取游戏数据"""
user_profile = await self.get_user_profile()
if user_profile.data == []:
return None
weighted_total_lpm = weighted_total_apm = weighted_total_adpm = 0.0
total_time = 0.0
num = 0
for i in user_profile.data:
# 排除单人局和时间为0的游戏
# 茶: 不计算没挖掘的局, 即使apm和lpm也如此
if i.num_players == 1 or i.time == 0 or i.dig is None:
continue
# 加权计算
time = i.time / 1000
lpm = 24 * (i.pieces / time)
apm = (i.attack / time) * 60
adpm = ((i.attack + i.dig) / time) * 60
weighted_total_lpm += lpm * time
weighted_total_apm += apm * time
weighted_total_adpm += adpm * time
total_time += time
num += 1
if num == 50: # noqa: PLR2004 # TODO: 将查询局数作为可选命令参数
break
if num == 0:
return None
# TODO: 如果有效局数不满50, 没有无dig信息的局, 且userData['data']内有50个局, 则继续往前获取信息
lpm = weighted_total_lpm / total_time
apm = weighted_total_apm / total_time
adpm = weighted_total_adpm / total_time
return GameData(
num=num,
pps=round(lpm / 24, 2),
lpm=round(lpm, 2),
apm=round(apm, 2),
adpm=round(adpm, 2),
apl=round((apm / lpm), 2),
adpl=round((adpm / lpm), 2),
vs=round((adpm / 60 * 100), 2),
)

View File

@@ -1,20 +0,0 @@
from typing import Literal
from ...schemas import BaseProcessedData, BaseRawResponse
from ..constant import GAME_TYPE
from .user_info import SuccessModel as InfoSuccess
from .user_profile import UserProfile
class RawResponse(BaseRawResponse):
platform: Literal['TOS'] = GAME_TYPE
user_profile: dict[str, bytes]
user_info: bytes | None = None
class ProcessedData(BaseProcessedData):
platform: Literal['TOS'] = GAME_TYPE
user_profile: dict[str, UserProfile]
user_info: InfoSuccess | None = None

View File

@@ -1,106 +0,0 @@
from datetime import datetime
from typing import Literal
from nonebot.compat import PYDANTIC_V2
from pydantic import BaseModel, ConfigDict, Field
class SuccessModel(BaseModel):
class Data(BaseModel):
class PeriodMatch(BaseModel):
name: str
teaid: str = Field(..., alias='teaId')
rating: str
rd: str
start_time: datetime = Field(..., alias='startTime')
end_time: datetime = Field(..., alias='endTime')
win: str
lose: str
score: str
if PYDANTIC_V2:
model_config = ConfigDict(populate_by_name=True)
else:
class Config:
allow_population_by_field_name = True
class UserDataTotalItem(BaseModel):
time_map: str = Field(..., alias='timeMap')
pieces_map: str = Field(..., alias='piecesMap')
clear_lines_map: str = Field(..., alias='clearLinesMap')
attacks_map: str = Field(..., alias='attacksMap')
dig_map: str = Field(..., alias='digMap')
send_map: str = Field(..., alias='sendMap')
rise_map: str = Field(..., alias='riseMap')
offset_map: str = Field(..., alias='offsetMap')
receive_map: str = Field(..., alias='receiveMap')
games_map: str = Field(..., alias='gamesMap')
tetris_map: str = Field(..., alias='tetrisMap')
combo_map: str = Field(..., alias='comboMap')
tspin_map: str = Field(..., alias='tspinMap')
b2b_map: str = Field(..., alias='b2bMap')
perfect_clear_map: str = Field(..., alias='perfectClearMap')
time_no_map: str = Field(..., alias='timeNoMap')
pieces_no_map: str = Field(..., alias='piecesNoMap')
clear_lines_no_map: str = Field(..., alias='clearLinesNoMap')
attacks_no_map: str = Field(..., alias='attacksNoMap')
dig_no_map: str = Field(..., alias='digNoMap')
send_no_map: str = Field(..., alias='sendNoMap')
rise_no_map: str = Field(..., alias='riseNoMap')
offset_no_map: str = Field(..., alias='offsetNoMap')
receive_no_map: str = Field(..., alias='receiveNoMap')
games_no_map: str = Field(..., alias='gamesNoMap')
tetris_no_map: str = Field(..., alias='tetrisNoMap')
combo_no_map: str = Field(..., alias='comboNoMap')
tspin_no_map: str = Field(..., alias='tspinNoMap')
b2b_no_map: str = Field(..., alias='b2bNoMap')
perfect_clear_no_map: str = Field(..., alias='perfectClearNoMap')
if PYDANTIC_V2:
model_config = ConfigDict(populate_by_name=True)
else:
class Config:
allow_population_by_field_name = True
teaid: str = Field(..., alias='teaId')
name: str
total_exp: str = Field(..., alias='totalExp')
ranking: str
ranked_games: str = Field(..., alias='rankedGames')
rating_now: str = Field(..., alias='ratingNow')
rd_now: str = Field(..., alias='rdNow')
vol_now: str = Field(..., alias='volNow')
rating_last: str = Field(..., alias='ratingLast')
rd_last: str = Field(..., alias='rdLast')
vol_last: str = Field(..., alias='volLast')
period_matches: list[PeriodMatch] = Field(..., alias='periodMatches')
user_data_total: list[UserDataTotalItem] = Field(..., alias='userDataTotal')
ranking_items: str = Field(..., alias='rankingItems')
ranking_game_items: str = Field(..., alias='rankingGameItems')
training_level: str = Field(..., alias='trainingLevel')
training_wins: str = Field(..., alias='trainingWins')
pb_sprint: str = Field(..., alias='PBSprint')
pb_marathon: str = Field(..., alias='PBMarathon')
pb_challenge: str = Field(..., alias='PBChallenge')
register_date: datetime = Field(..., alias='registerDate')
last_login_date: datetime = Field(..., alias='lastLoginDate')
if PYDANTIC_V2:
model_config = ConfigDict(populate_by_name=True)
else:
class Config:
allow_population_by_field_name = True
code: int
success: Literal[True]
data: Data
class FailedModel(BaseModel):
code: int
success: Literal[False]
error: str
UserInfo = SuccessModel | FailedModel

View File

@@ -1,33 +0,0 @@
from datetime import datetime
from typing import Literal
from pydantic import BaseModel
class UserProfile(BaseModel):
class Data(BaseModel):
idmultiplayergameresult: int
iduser: str
teaid: str
time: int
clear_lines: int
attack: int
send: int
offset: int
receive: int
rise: int
dig: int
pieces: int
max_combo: int
pc_count: int
place: int
num_players: int
fumen_code: Literal['0', '1'] # wtf
rule_set: str
garbage: str
idmultiplayergame: int
datetime: datetime
code: int
success: bool
data: list[Data]

View File

@@ -0,0 +1,56 @@
from collections.abc import Callable
from nonebot.adapters import Bot
from nonebot.matcher import Matcher
from nonebot.message import run_postprocessor
from nonebot.typing import T_Handler
from nonebot_plugin_alconna import AlcMatches, Alconna, At, CommandMeta, on_alconna
from .. import ns
from ..i18n import Lang
from ..utils.exception import MessageFormatError, NeedCatchError
command: Alconna = Alconna(
['tetris-stats', 'tstats'],
namespace=ns,
meta=CommandMeta(
description='俄罗斯方块相关游戏数据查询',
fuzzy_match=True,
),
)
alc = on_alconna(
command=command,
skip_for_unmatch=False,
auto_send_output=True,
use_origin=True,
)
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(Lang.interaction.wrong.query_bot())
from . import tetrio, top, tos # noqa: F401, E402
@alc.handle()
async def _(matcher: Matcher, account: MessageFormatError):
await matcher.finish(str(account))
@alc.handle()
async def _(matcher: Matcher, matches: AlcMatches):
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 '')
+ Lang.help.usage(command=matches.header_result)
)
@run_postprocessor
async def _(matcher: Matcher, exception: NeedCatchError):
await matcher.send(str(exception))

View File

@@ -0,0 +1,28 @@
from abc import ABC, abstractmethod
from typing import Generic, TypeVar
from pydantic import BaseModel
from typing_extensions import override
from ..utils.typedefs import GameType
T = TypeVar('T', bound=GameType)
class BaseUser(BaseModel, ABC, Generic[T]):
"""游戏用户"""
platform: T
@override
def __eq__(self, other: object) -> bool:
if isinstance(other, BaseUser):
return self.unique_identifier == other.unique_identifier
return False
@property
@abstractmethod
def unique_identifier(self) -> str:
raise NotImplementedError
__hash__ = BaseModel.__hash__

View File

@@ -0,0 +1,40 @@
from nonebot_plugin_alconna import Subcommand
from ...utils.exception import MessageFormatError
from .. import alc
from .. import command as main_command
from .api import Player
from .constant import USER_ID, USER_NAME
def get_player(user_id_or_name: str) -> Player | MessageFormatError:
if USER_ID.match(user_id_or_name):
return Player(user_id=user_id_or_name, trust=True)
if USER_NAME.match(user_id_or_name):
return Player(user_name=user_id_or_name, trust=True)
return MessageFormatError('用户名/ID不合法')
command = Subcommand(
'TETR.IO',
alias=['TETRIO', 'tetr.io', 'tetrio', 'io'],
dest='TETRIO',
help_text='TETR.IO 游戏相关指令',
)
from . import bind, config, list, query, rank, record, unbind, verify # noqa: A004, E402
main_command.add(command)
__all__ = [
'alc',
'bind',
'config',
'list',
'query',
'rank',
'record',
'unbind',
'verify',
]

View File

@@ -0,0 +1,5 @@
from .player import Player
from .schemas.user import User
from .schemas.user_info import UserInfoSuccess
__all__ = ['Player', 'User', 'UserInfoSuccess']

View File

@@ -0,0 +1,42 @@
from asyncio import Lock
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] # pyright: ignore[reportAttributeAccessIssue]
class Cache:
cache = ACache(ACache.MEMORY)
task: ClassVar[WeakValueDictionary[URL, Lock]] = WeakValueDictionary()
@classmethod
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, extra_headers, enable_anti_cloudflare=True)
parsed_data: SuccessModel | FailedModel = type_validate_json(SuccessModel | FailedModel, response_data) # type: ignore[arg-type] # pyright: ignore[reportArgumentType]
if isinstance(parsed_data, SuccessModel):
await cls.cache.add(
url,
response_data,
(parsed_data.cache.cached_until - datetime.now(UTC)).total_seconds(),
)
return response_data

View File

@@ -0,0 +1,93 @@
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] # pyright: ignore[reportArgumentType]
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] # pyright: ignore[reportArgumentType]
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] # pyright: ignore[reportArgumentType]
await get(
BASE_URL / 'records' / f'{records_type}{scope}{revolution_id if revolution_id is not None else ""}',
parameter,
),
)
if isinstance(model, FailedModel):
msg = f'排行榜信息请求错误:\n{model.error}'
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

@@ -0,0 +1,20 @@
from datetime import datetime
from typing import Literal
from nonebot_plugin_orm import Model
from sqlalchemy import DateTime, String
from sqlalchemy.orm import Mapped, MappedAsDataclass, mapped_column
from ....db.models import PydanticType
from .schemas.base import SuccessModel
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)
data: Mapped[SuccessModel] = mapped_column(PydanticType(get_model=[SuccessModel.__subclasses__], models=set()))
update_time: Mapped[datetime] = mapped_column(DateTime, index=True)

View File

@@ -0,0 +1,243 @@
from enum import Enum
from types import MappingProxyType
from typing import Literal, NamedTuple, cast, overload
from async_lru import alru_cache
from nonebot.compat import type_validate_json
from ....db import anti_duplicate_add
from ....utils.exception import RequestError
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 (
AchievementsSuccessModel,
SummariesModel,
ZenithSuccessModel,
ZenSuccessModel,
)
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 .typedefs import Records, Summaries
class RecordModeType(str, Enum):
Sprint = '40l'
Blitz = 'blitz'
class RecordType(str, Enum):
Top = 'top'
Recent = 'recent'
Progression = 'progression'
class RecordKey(NamedTuple):
mode_type: RecordModeType
record_type: RecordType
def to_records(self) -> Records:
return cast('Records', f'{self.mode_type.value}_{self.record_type.value}')
class Player:
__SUMMARIES_MAPPING: MappingProxyType[Summaries, type[SummariesModel]] = MappingProxyType(
{
'40l': SummariesSoloSuccessModel,
'blitz': SummariesSoloSuccessModel,
'zenith': ZenithSuccessModel,
'zenithex': ZenithSuccessModel,
'league': LeagueSuccessModel,
'zen': ZenSuccessModel,
'achievements': AchievementsSuccessModel,
}
)
@overload
def __init__(self, *, user_id: str, trust: bool = False): ...
@overload
def __init__(self, *, user_name: str, trust: bool = False): ...
def __init__(self, *, user_id: str | None = None, user_name: str | None = None, trust: bool = False):
self.user_id = user_id
self.user_name = user_name
if not trust:
if self.user_id is not None:
if not USER_ID.match(self.user_id):
msg = 'Invalid user id'
raise ValueError(msg)
elif self.user_name is not None:
if not USER_NAME.match(self.user_name):
msg = 'Invalid user name'
raise ValueError(msg)
else:
msg = 'Invalid user'
raise ValueError(msg)
self.__user: User | None = None
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:
return self.user_id or cast('str', self.user_name).lower()
@property
async def user(self) -> User:
if self.__user is not None:
return self.__user
if (user := (await self._get_local_summaries_user())) is not None:
self.__user = User(
ID=user.id,
name=user.username,
)
else:
user_info = await self.get_info()
self.__user = User(
ID=user_info.data.id,
name=user_info.data.username,
)
self.user_id = self.__user.ID
self.user_name = self.__user.name
return self.__user
async def get_info(self) -> UserInfoSuccess:
"""Get User Info"""
if self._user_info is None:
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] # pyright: ignore[reportArgumentType]
if isinstance(user_info, FailedModel):
msg = f'用户信息请求错误:\n{user_info.error}'
raise RequestError(msg)
self._user_info = user_info
await anti_duplicate_add(
TETRIOHistoricalData(
user_unique_identifier=(await self.user).unique_identifier,
api_type='User Info',
data=user_info,
update_time=user_info.cache.cached_at,
),
)
return self._user_info
@overload
async def get_summaries(self, summaries_type: Literal['40l', 'blitz']) -> SummariesSoloSuccessModel: ...
@overload
async def get_summaries(self, summaries_type: Literal['zenith', 'zenithex']) -> ZenithSuccessModel: ...
@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(
BASE_URL / 'users' / self._request_user_parameter / 'summaries' / summaries_type
)
summaries: SummariesModel | FailedModel = type_validate_json(
self.__SUMMARIES_MAPPING[summaries_type] | FailedModel, # type: ignore[assignment, arg-type] # pyright: ignore[reportArgumentType] #! waiting for [PEP 747](https://peps.python.org/pep-0747/)
raw_summaries,
)
if isinstance(summaries, FailedModel):
msg = f'用户Summaries数据请求错误:\n{summaries.error}'
raise RequestError(msg)
self._summaries[summaries_type] = summaries
await anti_duplicate_add(
TETRIOHistoricalData(
user_unique_identifier=(await self.user).unique_identifier,
api_type=summaries_type,
data=summaries,
update_time=summaries.cache.cached_at,
),
)
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] # pyright: ignore[reportArgumentType]
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
async def sprint(self) -> SummariesSoloSuccessModel:
return await self.get_summaries('40l')
@property
async def blitz(self) -> SummariesSoloSuccessModel:
return await self.get_summaries('blitz')
@property
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',
'blitz',
'zenith',
'zenithex',
}
if has_summaries := (allow_summaries & self._summaries.keys()):
for i in has_summaries:
if (record := (await self.get_summaries(i)).data.record) is not None:
return record.user
return None
@property
@alru_cache
async def avatar_revision(self) -> int | None:
if self._user_info is not None:
return self._user_info.data.avatar_revision
if (user := (await self._get_local_summaries_user())) is not None:
return user.avatar_revision
return (await self.get_info()).data.avatar_revision
@property
@alru_cache
async def banner_revision(self) -> int | None:
if self._user_info is not None:
return self._user_info.data.banner_revision
if (user := (await self._get_local_summaries_user())) is not None:
return user.banner_revision
return (await self.get_info()).data.banner_revision
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(
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] # pyright: ignore[reportArgumentType]
if isinstance(records, FailedModel):
msg = f'用户Summaries数据请求错误:\n{records.error}'
raise RequestError(msg)
self._records[record_key] = records
await anti_duplicate_add(
TETRIOHistoricalData(
user_unique_identifier=(await self.user).unique_identifier,
api_type=record_key.to_records(),
data=records,
update_time=records.cache.cached_at,
),
)
return self._records[record_key]

View File

@@ -0,0 +1,83 @@
from datetime import datetime
from typing import Literal
from pydantic import BaseModel, Field
from ...typedefs import Prisecter
class AggregateStats(BaseModel):
apm: float
pps: float
vsscore: float
class Finesse(BaseModel):
combo: int
faults: int
perfectpieces: int
class Clears(BaseModel):
singles: int
doubles: int
triples: int
quads: int
realtspins: int
minitspins: int
minitspinsingles: int
tspinsingles: int
minitspindoubles: int
tspindoubles: int
tspintriples: int
tspinquads: int
allclear: int
class Garbage(BaseModel):
sent: int
received: int
attack: int | None
cleared: int
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')
silver: int | None = Field(default=None, alias='2')
gold: int | None = Field(default=None, alias='3')
platinum: int | None = Field(default=None, alias='4')
diamond: int | None = Field(default=None, alias='5')
issued: int | None = Field(default=None, alias='100')
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
cached_at: datetime
cached_until: datetime
class SuccessModel(BaseModel):
success: Literal[True]
cache: Cache
class FailedModel(BaseModel):
success: Literal[False]
error: str

View File

@@ -0,0 +1,65 @@
from datetime import datetime
from typing import Literal
from pydantic import BaseModel, Field
from ..base import P
from . import AggregateStats, Clears, Finesse, Garbage
class Time(BaseModel):
start: int
zero: bool
locked: bool
prev: int
frameoffset: int | None = None
class Stats(BaseModel):
seed: float | None = None # ?: 不知道是之后都没有了还是还会有
lines: int
level_lines: int
level_lines_needed: int
inputs: int
holds: int = 0
time: Time | None = None # ?: 不知道是之后都没有了还是还会有
score: int
zenlevel: int | None = None
zenprogress: int | None = None
level: int
combo: int
currentcombopower: int | None = None
topcombo: int
btb: int
topbtb: int
currentbtbchainpower: int | None = None
tspins: int
piecesplaced: int
clears: Clears
garbage: Garbage
kills: int
finesse: Finesse
finaltime: float
class Results(BaseModel):
aggregatestats: AggregateStats
stats: Stats
gameoverreason: str
class Record(BaseModel):
id: str = Field(..., alias='_id')
replayid: str
stub: bool
gamemode: Literal['40l', 'blitz']
pb: bool
oncepb: bool
ts: datetime
revolution: None
otherusers: list
leaderboards: list[str]
results: Results
extras: dict
disputed: bool
p: P

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

@@ -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

@@ -0,0 +1,15 @@
from pydantic import BaseModel
from ..base import FailedModel, SuccessModel
from ..summaries.solo import Record
class Data(BaseModel):
entries: list[Record]
class SoloSuccessModel(SuccessModel):
data: Data
Solo = SoloSuccessModel | FailedModel

View File

@@ -0,0 +1,15 @@
from pydantic import BaseModel
from ..base import FailedModel, SuccessModel
from ..summaries.zenith import Record
class Data(BaseModel):
entries: list[Record]
class ZenithSuccessModel(SuccessModel):
data: Data
Zenith = ZenithSuccessModel | FailedModel

View File

@@ -0,0 +1,17 @@
from typing import TypeAlias
from pydantic import BaseModel
from ..base import FailedModel, SuccessModel
from ..base.solo import Record
class Data(BaseModel):
entries: list[Record]
class SoloSuccessModel(SuccessModel):
data: Data
Solo: TypeAlias = SoloSuccessModel | FailedModel

View File

@@ -0,0 +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 | LeagueSuccessModel | ZenithSuccessModel
__all__ = [
'Achievements',
'AchievementsSuccessModel',
'LeagueSuccessModel',
'Solo',
'SoloSuccessModel',
'SummariesModel',
'Zen',
'ZenSuccessModel',
'Zenith',
'ZenithEx',
'ZenithSuccessModel',
]

View File

@@ -0,0 +1,98 @@
from datetime import datetime
from enum import IntEnum
from typing import Literal, TypeAlias
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
PERCENTILEINVARIANT = 7
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):
# 这**都是些啥
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
hidden: bool
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
tiebreak: int
notifypb: bool
id: str | None = Field(None, alias='_id')
progress: float | None = None
stub: bool | None = None
class AchievementsSuccessModel(SuccessModel):
data: list[Achievement]
Achievements: TypeAlias = AchievementsSuccessModel | FailedModel

View File

@@ -0,0 +1,10 @@
from pydantic import BaseModel
class User(BaseModel):
id: str
username: str
avatar_revision: int | None
banner_revision: int | None
country: str | None
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')
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

@@ -0,0 +1,24 @@
from typing import TypeAlias
from pydantic import BaseModel
from ..base import FailedModel, SuccessModel
from ..base.solo import Record as BaseRecord
from .base import User
class Record(BaseRecord):
user: User
class Data(BaseModel):
record: Record | None
rank: int
rank_local: int
class SoloSuccessModel(SuccessModel):
data: Data
Solo: TypeAlias = SoloSuccessModel | FailedModel

View File

@@ -0,0 +1,17 @@
from typing import TypeAlias
from pydantic import BaseModel
from ..base import FailedModel, SuccessModel
class Data(BaseModel):
level: int
score: int
class ZenSuccessModel(SuccessModel):
data: Data
Zen: TypeAlias = ZenSuccessModel | FailedModel

View File

@@ -0,0 +1,116 @@
from datetime import datetime
from typing import Literal, TypeAlias
from pydantic import BaseModel, Field
from ..base import AggregateStats, FailedModel, Finesse, P, SuccessModel
from ..base import Clears as BaseClears
from ..base import Garbage as BaseGarbage
from .base import User
class Clears(BaseClears):
pentas: int
minitspintriples: int
minitspinquads: int
tspinpentas: int
class Garbage(BaseGarbage):
sent_nomult: int
maxspike: int
maxspike_nomult: int
class _Zenith(BaseModel):
altitude: float
rank: float
peakrank: float
avgrankpts: float
floor: int
targetingfactor: float
targetinggrace: float
totalbonus: float
revives: int
revives_total: int = Field(..., alias='revivesTotal')
speedrun: bool
speedrun_seen: bool
splits: list[int]
class Stats(BaseModel):
lines: int
level_lines: int
level_lines_needed: int
inputs: int
holds: int
score: int
zenlevel: int
zenprogress: int
level: int
combo: int
topcombo: int
combopower: int
btb: int
topbtb: int
btbpower: int
tspins: int
piecesplaced: int
clears: Clears
garbage: Garbage
kills: int
finesse: Finesse
zenith: _Zenith
finaltime: float
class Results(BaseModel):
aggregatestats: AggregateStats
stats: Stats
gameoverreason: str
class ExtrasZenith(BaseModel):
mods: list[str]
class Extras(BaseModel):
zenith: ExtrasZenith
class Record(BaseModel):
id: str = Field(..., alias='_id')
replayid: str
stub: bool
gamemode: Literal['zenith', 'zenithex']
pb: bool
oncepb: bool
ts: datetime
revolution: str | None
user: User
otherusers: list
leaderboards: list[str]
results: Results
extras: Extras
disputed: bool
p: P
class Best(BaseModel):
record: Record | None
rank: int
class Data(BaseModel):
record: Record | None
rank: int
rank_local: int
best: Best
class ZenithSuccessModel(SuccessModel):
data: Data
Zenith: TypeAlias = ZenithSuccessModel | FailedModel
ZenithEx: TypeAlias = ZenithSuccessModel | FailedModel

View File

@@ -0,0 +1,18 @@
from typing import Literal
from typing_extensions import override
from ....schemas import BaseUser
from ...constant import GAME_TYPE
class User(BaseUser[Literal['IO']]):
platform: Literal['IO'] = GAME_TYPE
ID: str
name: str
@property
@override
def unique_identifier(self) -> str:
return self.ID

View File

@@ -0,0 +1,74 @@
from datetime import datetime
from typing import Literal
from pydantic import BaseModel, Field
from .base import ArCounts, FailedModel
from .base import SuccessModel as BaseSuccessModel
class Badge(BaseModel):
id: str
label: str
group: str | None = None
ts: datetime | Literal[False] | None = None
class Connection(BaseModel):
id: str
username: str
display_username: str
class Connections(BaseModel):
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):
type: str
class Data(BaseModel):
id: str = Field(default=..., alias='_id')
username: str
role: Literal['anon', 'user', 'bot', 'halfmod', 'mod', 'admin', 'sysop', 'hidden', 'banned']
ts: datetime | None = None
botmaster: str | None = None
badges: list[Badge]
xp: float
gamesplayed: int
gameswon: int
gametime: float
country: str | None = None
badstanding: bool | None = None
supporter: bool | None = None # osk说是必有, 但实际上不是 fkosk
supporter_tier: int
avatar_revision: int | None = None
"""This user's avatar ID. Get their avatar at
https://tetr.io/user-content/avatars/{ USERID }.jpg?rv={ AVATAR_REVISION }"""
banner_revision: int | None = None
"""This user's banner ID. Get their banner at
https://tetr.io/user-content/banners/{ USERID }.jpg?rv={ BANNER_REVISION }
Ignore this field if the user is not a supporter."""
bio: str | None = None
connections: Connections
friend_count: int | None = None
distinguishment: Distinguishment | None = None
achievements: list[int]
ar: int
ar_counts: ArCounts
class UserInfoSuccess(BaseSuccessModel):
data: Data
UserInfo = UserInfoSuccess | FailedModel

View File

@@ -0,0 +1,46 @@
from typing import Literal, NewType
S1ValidRank = Literal[
'x',
'u',
'ss',
's+',
's',
's-',
'a+',
'a',
'a-',
'b+',
'b',
'b-',
'c+',
'c',
'c-',
'd+',
'd',
]
S1Rank = S1ValidRank | Literal['z']
ValidRank = Literal['x+'] | S1ValidRank
Rank = ValidRank | Literal['z'] # 未定级
Summaries = Literal[
'40l',
'blitz',
'zenith',
'zenithex',
'league',
'zen',
'achievements',
]
Records = Literal[
'40l_top',
'40l_recent',
'40l_progression',
'blitz_top',
'blitz_recent',
'blitz_progression',
]
Prisecter = NewType('Prisecter', str)

View File

@@ -0,0 +1,144 @@
from asyncio import gather
from hashlib import md5
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_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 BindStatus, create_or_update_bind, trigger
from ...i18n import Lang
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, get_player
from .api import Player
from .constant import GAME_TYPE
command.add(
Subcommand(
'bind',
Args(
Arg(
'account',
get_player,
notice='TETR.IO 用户名 / ID',
flags=[ArgFlag.HIDDEN],
)
),
help_text='绑定 TETR.IO 账号',
)
)
alc.shortcut(
'(?i:io)(?i:绑定|绑|bind)',
command='tstats TETR.IO bind',
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: 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 = await account.user
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,
)
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=None,
)
).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=Lang.prompt.io_check(),
lang=get_lang(),
),
)

View File

@@ -0,0 +1,71 @@
from datetime import timedelta
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_uninfo import Uninfo
from nonebot_plugin_uninfo.orm import get_session_persist_id
from nonebot_plugin_user import User
from sqlalchemy import select
from ...db import trigger
from ...i18n import Lang
from ...utils.duration import parse_duration
from . import alc, command
from .constant import GAME_TYPE
from .models import TETRIOUserConfig
from .typedefs import Template
command.add(
Subcommand(
'config',
Option(
'--default-template',
Arg('template', Template, notice='模板版本'),
alias=['-DT', 'DefaultTemplate'],
help_text='设置默认查询模板',
),
Option(
'--default-compare',
Arg('compare', parse_duration, notice='对比时间距离'),
alias=['-DC', 'DefaultCompare'],
help_text='设置默认对比时间距离',
),
help_text='TETR.IO 查询个性化配置',
),
)
alc.shortcut(
'(?i:io)(?i:配置|配|config)',
command='tstats TETR.IO config',
humanized='io配置',
)
@alc.assign('TETRIO.config')
async def _(
user: User,
session: async_scoped_session,
event_session: Uninfo,
template: Template | None = None,
compare: timedelta | None = None,
):
async with trigger(
session_persist_id=await get_session_persist_id(event_session),
game_platform=GAME_TYPE,
command_type='config',
command_args=([f'--default-template {template}'] if template is not None else [])
+ ([f'--default-compare {compare}'] if compare is not None else []),
):
config = (await session.scalars(select(TETRIOUserConfig).where(TETRIOUserConfig.id == user.id))).one_or_none()
if config is None:
config = TETRIOUserConfig(id=user.id, query_template=template or 'v1', compare_delta=compare)
session.add(config)
else:
if template is not None:
config.query_template = template
if compare is not None:
config.compare_delta = compare
await session.commit()
await UniMessage(Lang.bind.config_success()).finish()

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