🔖 Release 1.0.0.a1 (#80)

* 使用 `pathlib` 替代 `os`

* 防止建立多个数据库连接对象

* 调整数据库结构 # 破坏性更新

* 格式化代码

*  去除依赖Brotli
 去除开发依赖autopep8 pylint
 添加开发依赖ruff black
🔥 删除.pylintrc
🎨 使用black格式化代码

* 📝 一些很赞的小牌子

* ✏️ 修正`config`变量名

* 🐛 修复OperationalError语法错误

*  添加 debug 依赖 objprint

* 🚧 数据记录器demo

* 🔥 这个init好像没什么用(

* 💡 ✏️ 修改错误的注释

* 📝 🍱 添加一个logo

* 📝 添加logo的悬浮提示

* 🙈 更新 .gitignore

* 🚨 消除了一些 init 文件中的错误警告

* ♻️ 💩 重构 IO 的 processor 模块
🐛 修复了 bind user_id 不能正确处理的bug
🎨 使用一些自定义类型和基于异常的编程(

* 🐛 忘记写try了

* 👷 将 Release CI 切换到 Python 3.11 版本

* 🎨 修改 Exception 类的变量名

* 🎨 修改捕获的 aiohttp 的错误类型

* 🎨 将 AsyncCallable 放进 typing 模块

* 🏗️ 将 recorder 装饰器中执行函数的部分放在 collector 函数中
🚧 完善数据收集部分

* 🚧 receive 记录添加 message_id 以辅助消息上下文识别

*  添加依赖 tortoise-orm

* ♻️ 🗃️ 将数据库操作替换成 tortoise-orm

* 🎨 显式传递 locals 字典

* 🎨 将装饰器封装到类里

* 🐛 忘记 exec 需要拿变量了

* 🗃️ 微调数据类型

* 🗃️ 调整数据库索引

* Bump playwright from 1.29.0 to 1.30.0 (#72)

* Bump ujson from 5.6.0 to 5.7.0 (#69)

* Bump pandas-stubs from 1.5.2.221213 to 1.5.2.230105 (#57)

* Bump types-ujson from 5.6.0.0 to 5.7.0.0 (#68)

*  存储命令历史 #58

* Bump nonebot2 from 2.0.0rc2 to 2.0.0rc3 (#73)

* Bump nonebot-adapter-onebot from 2.2.0 to 2.2.1 (#74)

* Bump tortoise-orm from 0.19.2 to 0.19.3 (#75)

* Bump black from 23.1a1 to 23.1.0 (#77)

* Bump pandas from 1.5.2 to 1.5.3 (#76)

* ⬆️ 更新 ruff

* 🔧 启用更多的检查规则

* 🎨 使用单引号编写配置文件

* 💡 为 ignore 添加注释

* 🎨 使用 ruff 规范化引号

* 🔧 启用 PEP8 命名规范检查

* 🚨 添加一些 noqa(

* 💡 添加和修改了一些注释

* 🔊 添加一条日志

* 🎨 🚨 使用 replace 替换 strip

* 🎨 🚨 规范化命名

* 🎨 格式化代码

* 🔊 修改日志等级

* 🎨 去除重复的 get_driver() 调用

*  自动安装 playwright 浏览器 close #71

* 🙈 更新 gitignore

*  将所有 playwright 相关整合进 BrowserManager 类

* 📝 更换开源许可证 (#78)

* 📝 更新 README

* Bump pandas-stubs from 1.5.2.230105 to 1.5.3.230203 (#79)

*  添加依赖 nonebot-plugin-datastore

*  使用 nonebot_plugin_datastore 提供的路径存储缓存

* Bump nonebot-plugin-datastore from 0.5.7 to 0.5.8 (#81)

* ⬆️ Bump aiohttp from 3.8.3 to 3.8.4 (#82)

Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.8.3 to 3.8.4.
- [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.8.3...v3.8.4)

---
updated-dependencies:
- dependency-name: aiohttp
  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>

* ⬆️ Bump pandas-stubs from 1.5.3.230203 to 1.5.3.230214 (#84)

Bumps [pandas-stubs](https://github.com/pandas-dev/pandas-stubs) from 1.5.3.230203 to 1.5.3.230214.
- [Release notes](https://github.com/pandas-dev/pandas-stubs/releases)
- [Changelog](https://github.com/pandas-dev/pandas-stubs/blob/main/docs/release_procedure.md)
- [Commits](https://github.com/pandas-dev/pandas-stubs/compare/v1.5.3.230203...v1.5.3.230214)

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

* ⬆️ Bump ruff from 0.0.239 to 0.0.253 (#90)

* ⬆️ Bump playwright from 1.30.0 to 1.31.1 (#89)

* ⬆️ Bump types-ujson from 5.7.0.0 to 5.7.0.1 (#86)

* ⬆️ Bump pandas-stubs from 1.5.3.230214 to 1.5.3.230227 (#88)

* ⬆️ Bump ruff from 0.0.253 to 0.0.254 (#91)

* ⬆️ Bump pandas-stubs from 1.5.3.230227 to 1.5.3.230304 (#92)

* ⬆️ Bump mypy from 0.991 to 1.0.1 (#93)

* ⬆️ Bump mypy from 1.0.1 to 1.1.1 (#94)

* ⬆️ Bump ruff from 0.0.254 to 0.0.284 (#139)

Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.254 to 0.0.284.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/BREAKING_CHANGES.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.0.254...v0.0.284)

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

* ⬆️ Bump nonebot-adapter-onebot from 2.2.1 to 2.2.4 (#137)

Bumps [nonebot-adapter-onebot](https://github.com/nonebot/adapter-onebot) from 2.2.1 to 2.2.4.
- [Release notes](https://github.com/nonebot/adapter-onebot/releases)
- [Commits](https://github.com/nonebot/adapter-onebot/compare/v2.2.1...v2.2.4)

---
updated-dependencies:
- dependency-name: nonebot-adapter-onebot
  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>

* ⬆️ Bump playwright from 1.31.1 to 1.36.0 (#133)

Bumps [playwright](https://github.com/Microsoft/playwright-python) from 1.31.1 to 1.36.0.
- [Release notes](https://github.com/Microsoft/playwright-python/releases)
- [Commits](https://github.com/Microsoft/playwright-python/compare/v1.31.1...v1.36.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>

* ⬆️ Bump pandas-stubs from 1.5.3.230304 to 2.0.2.230605 (#124)

* ⬆️ Bump mypy from 1.1.1 to 1.5.1 (#144)

Bumps [mypy](https://github.com/python/mypy) from 1.1.1 to 1.5.1.
- [Commits](https://github.com/python/mypy/compare/v1.1.1...v1.5.1)

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

* ⬆️ Bump types-ujson from 5.7.0.1 to 5.8.0.1 (#142)

Bumps [types-ujson](https://github.com/python/typeshed) from 5.7.0.1 to 5.8.0.1.
- [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>

* ⬆️ Bump nonebot2 from 2.0.0rc3 to 2.0.1 (#141)

Bumps [nonebot2](https://github.com/nonebot/nonebot2) from 2.0.0rc3 to 2.0.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.0.0rc3...v2.0.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>

* ⬆️ Bump lxml from 4.9.2 to 4.9.3 (#140)

Bumps [lxml](https://github.com/lxml/lxml) from 4.9.2 to 4.9.3.
- [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-4.9.2...lxml-4.9.3)

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

* ⬆️ Bump black from 23.1.0 to 23.9.1 (#148)

Bumps [black](https://github.com/psf/black) from 23.1.0 to 23.9.1.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/23.1.0...23.9.1)

---
updated-dependencies:
- dependency-name: black
  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>

* ⬆️ Bump pandas-stubs from 2.0.2.230605 to 2.0.3.230814 (#149)

Bumps [pandas-stubs](https://github.com/pandas-dev/pandas-stubs) from 2.0.2.230605 to 2.0.3.230814.
- [Changelog](https://github.com/pandas-dev/pandas-stubs/blob/main/docs/release_procedure.md)
- [Commits](https://github.com/pandas-dev/pandas-stubs/compare/v2.0.2.230605...v2.0.3.230814)

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

* ⬆️ Bump pandas from 1.5.3 to 2.1.1 (#152)

Bumps [pandas](https://github.com/pandas-dev/pandas) from 1.5.3 to 2.1.1.
- [Release notes](https://github.com/pandas-dev/pandas/releases)
- [Commits](https://github.com/pandas-dev/pandas/compare/v1.5.3...v2.1.1)

---
updated-dependencies:
- dependency-name: pandas
  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>

* ⬆️ Bump aiohttp from 3.8.4 to 3.8.5 (#155)

Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.8.4 to 3.8.5.
- [Release notes](https://github.com/aio-libs/aiohttp/releases)
- [Changelog](https://github.com/aio-libs/aiohttp/blob/v3.8.5/CHANGES.rst)
- [Commits](https://github.com/aio-libs/aiohttp/compare/v3.8.4...v3.8.5)

---
updated-dependencies:
- dependency-name: aiohttp
  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>

* ⬆️ Bump ruff from 0.0.284 to 0.0.291 (#154)

Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.284 to 0.0.291.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/BREAKING_CHANGES.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.0.284...v0.0.291)

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

* ⬆️ Bump playwright from 1.36.0 to 1.38.0 (#153)

Bumps [playwright](https://github.com/Microsoft/playwright-python) from 1.36.0 to 1.38.0.
- [Release notes](https://github.com/Microsoft/playwright-python/releases)
- [Commits](https://github.com/Microsoft/playwright-python/compare/v1.36.0...v1.38.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>

* ⬆️ Bump nonebot-plugin-datastore from 0.5.8 to 1.1.2 (#151)

Bumps [nonebot-plugin-datastore](https://github.com/he0119/nonebot-plugin-datastore) from 0.5.8 to 1.1.2.
- [Release notes](https://github.com/he0119/nonebot-plugin-datastore/releases)
- [Changelog](https://github.com/he0119/nonebot-plugin-datastore/blob/main/CHANGELOG.md)
- [Commits](https://github.com/he0119/nonebot-plugin-datastore/compare/v0.5.8...v1.1.2)

---
updated-dependencies:
- dependency-name: nonebot-plugin-datastore
  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>

* ⬆️ Bump nonebot2 from 2.0.1 to 2.1.0 (#156)

Bumps [nonebot2](https://github.com/nonebot/nonebot2) from 2.0.1 to 2.1.0.
- [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.0.1...v2.1.0)

---
updated-dependencies:
- dependency-name: nonebot2
  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>

* ⬆️ Bump ujson from 5.7.0 to 5.8.0 (#157)

Bumps [ujson](https://github.com/ultrajson/ultrajson) from 5.7.0 to 5.8.0.
- [Release notes](https://github.com/ultrajson/ultrajson/releases)
- [Commits](https://github.com/ultrajson/ultrajson/compare/5.7.0...5.8.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>

* ️ 移除无意义的async

* 🎨 修正 type hint

*  添加依赖 aiofiles

* 🎨 将文件读写操作换成 aiofiles

* ️ 移除无意义的async

* 🎨 重命名一些函数

* 🎨 去除不需要的转换

* ⬆️ Bump pandas-stubs from 2.0.3.230814 to 2.1.1.230928 (#158)

Bumps [pandas-stubs](https://github.com/pandas-dev/pandas-stubs) from 2.0.3.230814 to 2.1.1.230928.
- [Changelog](https://github.com/pandas-dev/pandas-stubs/blob/main/docs/release_procedure.md)
- [Commits](https://github.com/pandas-dev/pandas-stubs/compare/v2.0.3.230814...v2.1.1.230928)

---
updated-dependencies:
- dependency-name: pandas-stubs
  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>

* ⬆️ Bump nonebot2 from 2.1.0 to 2.1.1 (#159)

Bumps [nonebot2](https://github.com/nonebot/nonebot2) from 2.1.0 to 2.1.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.1.0...v2.1.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>

*  添加依赖 nonebot-plugin-orm
 添加依赖 nonebot-plugin-localstore
 移除依赖 nonebot-plugin-datastore

* 📌 取消 python 最高版本限制

* ⬆️ Bump objprint from 0.2.2 to 0.2.3 (#161)

Bumps [objprint](https://github.com/gaogaotiantian/objprint) from 0.2.2 to 0.2.3.
- [Release notes](https://github.com/gaogaotiantian/objprint/releases)
- [Commits](https://github.com/gaogaotiantian/objprint/compare/0.2.2...0.2.3)

---
updated-dependencies:
- dependency-name: objprint
  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>

* ⬆️ Bump ruff from 0.0.291 to 0.0.292 (#160)

Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.291 to 0.0.292.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/BREAKING_CHANGES.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.0.291...v0.0.292)

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

* ⬆️ Bump mypy from 1.5.1 to 1.6.0 (#163)

Bumps [mypy](https://github.com/python/mypy) from 1.5.1 to 1.6.0.
- [Commits](https://github.com/python/mypy/compare/v1.5.1...v1.6.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>

*  添加依赖 httpx

* ⬆️ Bump nonebot-plugin-orm from 0.1.1 to 0.2.1 (#166)

Bumps [nonebot-plugin-orm](https://github.com/nonebot/plugin-orm) from 0.1.1 to 0.2.1.
- [Release notes](https://github.com/nonebot/plugin-orm/releases)
- [Commits](https://github.com/nonebot/plugin-orm/compare/v0.1.1...v0.2.1)

---
updated-dependencies:
- dependency-name: nonebot-plugin-orm
  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>

*  添加开发依赖 nonebot2

* 🎨 改为直接使用 nonebot_plugin_localstore 提供缓存路径

* 🐛 忘记 require

* 🐛 顺序错了

* 🗃️ 使用 nb orm

* 🏗️ 再次重构 IO 模块

* 🐛 忘记 push 这个了

* 🏗️ 将 request 改成通用的

* ⬆️ Bump nonebot-plugin-orm from 0.2.1 to 0.2.2 (#167)

Bumps [nonebot-plugin-orm](https://github.com/nonebot/plugin-orm) from 0.2.1 to 0.2.2.
- [Release notes](https://github.com/nonebot/plugin-orm/releases)
- [Commits](https://github.com/nonebot/plugin-orm/compare/v0.2.1...v0.2.2)

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

* ⬆️ Bump ruff from 0.0.292 to 0.1.0 (#168)

Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.292 to 0.1.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.0.292...v0.1.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>

* ⬆️ Bump nonebot-adapter-onebot from 2.3.0 to 2.3.1 (#165)

Bumps [nonebot-adapter-onebot](https://github.com/nonebot/adapter-onebot) from 2.3.0 to 2.3.1.
- [Release notes](https://github.com/nonebot/adapter-onebot/releases)
- [Commits](https://github.com/nonebot/adapter-onebot/compare/v2.3.0...v2.3.1)

---
updated-dependencies:
- dependency-name: nonebot-adapter-onebot
  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>

* 🏗️ 重构 TOS 模块

* 🩹 补充返回值类型标注

* 🐛 写错变量了

* 🐛 缺个 else

* 🐛 忘记声明变量

* 🐛 忘记初始化变量

* 🎨 去除不需要的 else

* 🎨 去除不需要的判断

* 🎨 减少一次函数调用

* 🐛 写错命令了

* ⬆️ Bump nonebot-plugin-orm from 0.2.2 to 0.2.3 (#170)

Bumps [nonebot-plugin-orm](https://github.com/nonebot/plugin-orm) from 0.2.2 to 0.2.3.
- [Release notes](https://github.com/nonebot/plugin-orm/releases)
- [Commits](https://github.com/nonebot/plugin-orm/compare/v0.2.2...v0.2.3)

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

* ⬆️ Bump black from 23.9.1 to 23.10.0 (#171)

Bumps [black](https://github.com/psf/black) from 23.9.1 to 23.10.0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/23.9.1...23.10.0)

---
updated-dependencies:
- dependency-name: black
  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>

* ⬆️ Bump mypy from 1.6.0 to 1.6.1 (#172)

Bumps [mypy](https://github.com/python/mypy) from 1.6.0 to 1.6.1.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.6.0...v1.6.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>

* ⬆️ Bump playwright from 1.38.0 to 1.39.0 (#169)

Bumps [playwright](https://github.com/Microsoft/playwright-python) from 1.38.0 to 1.39.0.
- [Release notes](https://github.com/Microsoft/playwright-python/releases)
- [Commits](https://github.com/Microsoft/playwright-python/compare/v1.38.0...v1.39.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>

*  去除开发依赖 lxml-stubs
 添加开发依赖 types-lxml

* 🏗️ 重构 TOP 模块

* 🐛 忘记传参了

* 🐛 忘记判断有没有绑定了

* 🎨 忘记用封好的函数了

* 📝 把 wakatime 的小牌牌放上去 并且格式化

* ⬆️ Bump ruff from 0.1.0 to 0.1.1 (#174)

Bumps [ruff](https://github.com/astral-sh/ruff) from 0.1.0 to 0.1.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/v0.1.0...v0.1.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>

* ⬆️ Bump nonebot-plugin-orm from 0.2.3 to 0.2.4 (#173)

Bumps [nonebot-plugin-orm](https://github.com/nonebot/plugin-orm) from 0.2.3 to 0.2.4.
- [Release notes](https://github.com/nonebot/plugin-orm/releases)
- [Commits](https://github.com/nonebot/plugin-orm/compare/v0.2.3...v0.2.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>

* 🏗️ 把 config 从 utils 里拿出来

* 🔥 orm 搞错用法了

* 🗃️ 创建新的迁移脚本

*  添加插件元数据

* 🐛 导包顺序又错了

*  添加依赖 nonebot-adapter-qq

*  添加依赖 nonebot-plugin-alconna

*  移除依赖 tortoise-orm

* ⬆️ Bump nonebot-plugin-orm from 0.2.4 to 0.3.0 (#175)

* ⬆️ Bump types-lxml from 2023.3.28 to 2023.10.21 (#176)

* ⬆️ Bump black from 23.10.0 to 23.10.1 (#177)

* ⬆️ Bump ruff from 0.1.1 to 0.1.2 (#178)

*  添加debug依赖 viztracer

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

*  IO 基础查询功能适配所有平台

* ⬆️ Bump nonebot-plugin-alconna from 0.30.3 to 0.30.6 (#179)

Bumps [nonebot-plugin-alconna](https://github.com/nonebot/plugin-alconna) from 0.30.3 to 0.30.6.
- [Release notes](https://github.com/nonebot/plugin-alconna/releases)
- [Commits](https://github.com/nonebot/plugin-alconna/compare/v0.30.3...v0.30.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>

* 🩹 防止在使用其他命令的时候意外触发

* 🚧 暂时去除记录器 待重构

* 🐛 修正输出信息格式

*  TOP 基础查询功能适配所有平台

* ⬆️ Bump ruff from 0.1.2 to 0.1.3 (#180)

* ⬆️ Bump nonebot-plugin-alconna from 0.30.6 to 0.30.7 (#181)

*  TOS 基础查询功能适配所有平台

* 🐛 修复一些输出消息问题

* 🔥 删除一些不需要的常量

* 🔥 把手搓的解析器爆了

* 🏷️ 添加一个不知道有什么用的类型注释

* 🎨 从 black 迁移 到 ruff format
 删除开发依赖 black

*  启用 pyupgrade 规则

*  启用 flake8-2020 规则

*  启用 flake8-annotations 规则

*  启用 flake8-async 规则

*  启用 flake8-bandit 规则

*  启用 flake8-blind-except 规则

*  启用 flake8-boolean-trap 规则

*  启用 flake8-builtins 规则

*  启用 flake8-datetimez 规则

*  启用 flake8-future-annotations 规则

*  启用 flake8-implicit-str-concat 规则

*  启用 flake8-pie 规则

*  启用 flake8-print 规则

*  启用 flake8-raise 规则

*  启用 flake8-return 规则

*  启用 flake8-simplify 规则

*  启用 flake8-use-pathlib 规则

*  启用 pandas-vet 规则

*  启用 tryceratops 规则

*  启用 flynt 规则

*  启用 Perflint 规则

* ⬆️ Bump nonebot-plugin-alconna from 0.30.7 to 0.31.0 (#183)

Bumps [nonebot-plugin-alconna](https://github.com/nonebot/plugin-alconna) from 0.30.7 to 0.31.0.
- [Release notes](https://github.com/nonebot/plugin-alconna/releases)
- [Commits](https://github.com/nonebot/plugin-alconna/compare/v0.30.7...v0.31.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>

* ⬆️ Bump nonebot-plugin-orm from 0.3.0 to 0.4.0 (#182)

* ⬆️ Bump nonebot-plugin-orm from 0.4.0 to 0.4.1 (#186)

Bumps [nonebot-plugin-orm](https://github.com/nonebot/plugin-orm) from 0.4.0 to 0.4.1.
- [Release notes](https://github.com/nonebot/plugin-orm/releases)
- [Commits](https://github.com/nonebot/plugin-orm/compare/v0.4.0...v0.4.1)

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

* ⬆️ Bump nonebot2 from 2.1.1 to 2.1.2 (#185)

Bumps [nonebot2](https://github.com/nonebot/nonebot2) from 2.1.1 to 2.1.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.1.1...v2.1.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>

* ⬆️ Bump nonebot-plugin-alconna from 0.31.0 to 0.31.3 (#187)

Bumps [nonebot-plugin-alconna](https://github.com/nonebot/plugin-alconna) from 0.31.0 to 0.31.3.
- [Release notes](https://github.com/nonebot/plugin-alconna/releases)
- [Commits](https://github.com/nonebot/plugin-alconna/compare/v0.31.0...v0.31.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>

* 🐛 修复拼接 url 的 bug

* ⬆️ Bump nonebot-plugin-orm from 0.4.1 to 0.5.0 (#189)

Bumps [nonebot-plugin-orm](https://github.com/nonebot/plugin-orm) from 0.4.1 to 0.5.0.
- [Release notes](https://github.com/nonebot/plugin-orm/releases)
- [Commits](https://github.com/nonebot/plugin-orm/compare/v0.4.1...v0.5.0)

---
updated-dependencies:
- dependency-name: nonebot-plugin-orm
  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>

* ⬆️ Bump nonebot-plugin-alconna from 0.31.3 to 0.31.7 (#191)

Bumps [nonebot-plugin-alconna](https://github.com/nonebot/plugin-alconna) from 0.31.3 to 0.31.7.
- [Release notes](https://github.com/nonebot/plugin-alconna/releases)
- [Commits](https://github.com/nonebot/plugin-alconna/compare/v0.31.3...v0.31.7)

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

* ⬆️ Bump ruff from 0.1.3 to 0.1.4 (#190)

* ⬆️ Bump httpx from 0.25.0 to 0.25.1 (#188)

*  绑定适配所有平台

* ⬆️ Bump nonebot-plugin-alconna from 0.31.7 to 0.32.0 (#192)

Bumps [nonebot-plugin-alconna](https://github.com/nonebot/plugin-alconna) from 0.31.7 to 0.32.0.
- [Release notes](https://github.com/nonebot/plugin-alconna/releases)
- [Commits](https://github.com/nonebot/plugin-alconna/compare/v0.31.7...v0.32.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>

* ♻️ 重构 recorder

*  添加依赖 nonebot-plugin-apscheduler

* ⬆️ Bump mypy from 1.6.1 to 1.7.0 (#194)

* ⬆️ Bump ruff from 0.1.4 to 0.1.5 (#193)

*  将数据库内存储时间时区切换为UTC

*  添加 iorank 指令

* 🎨 将行长限制改为120

* 🥚 :fkosk:

* 🗃️ 迁移旧版本 sqlite 中的数据

* 🚨 添加 type: ignore

*  更新 PluginMetadata

*  移除所有 nonebot-adapter 依赖

*  移除依赖 aiohttp

*  移除依赖 asyncio
(为什么会有这个)

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

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

* 📝 更新 readme

* 🔖 1.0.0.a1

* 🔒️ 修复 Incomplete URL substring sanitization

* 🔒️ 修复 Incomplete URL substring sanitization

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
呵呵です
2023-11-13 23:37:51 +08:00
committed by GitHub
parent cced0ca1d5
commit ca8ab5871b
49 changed files with 5031 additions and 2510 deletions

View File

@@ -1 +0,0 @@
from . import config, database, message_analyzer

View File

@@ -0,0 +1,92 @@
import sys
from os import environ
from platform import system
from nonebot import get_driver
from nonebot.log import logger
from playwright.__main__ import main
from playwright.async_api import Browser, async_playwright
driver = get_driver()
global_config = driver.config
@driver.on_startup
async def _():
await BrowserManager._init_playwright()
@driver.on_shutdown
async def _():
await BrowserManager._close_browser()
class BrowserManager:
"""浏览器管理类"""
_browser: Browser | None = None
@classmethod
async def _init_playwright(cls) -> None:
if system() == 'Windows' and getattr(global_config, 'fastapi_reload', False):
raise ImportError('加载失败, Windows 必须设置 FASTAPI_RELOAD=false 才能正常运行 playwright')
logger.info('开始 安装/更新 playwright 浏览器')
environ['PLAYWRIGHT_DOWNLOAD_HOST'] = 'https://npmmirror.com/mirrors/playwright/'
if cls._handle_error(cls._call_playwright(['', 'install', 'firefox'])):
logger.success('安装/更新 playwright 浏览器成功')
else:
logger.warning('playwright 浏览器 安装/更新 失败, 尝试使用原始仓库下载')
del environ['PLAYWRIGHT_DOWNLOAD_HOST']
if cls._handle_error(cls._call_playwright(['', 'install', 'firefox'])):
logger.success('安装/更新 playwright 浏览器成功')
else:
logger.error('安装/更新 playwright 浏览器失败')
try:
await cls._start_browser()
except BaseException as e: # noqa: BLE001 不知道会有什么异常, 交给用户解决
raise ImportError(
'playwright 启动失败, 请尝试在命令行运行 playwright install-deps firefox, 如果仍然启动失败, 请参考上面的报错👆'
) from e
else:
logger.success('playwright 启动成功')
@classmethod
def _call_playwright(cls, argv: list[str]) -> BaseException:
"""等价于调用 playwright 的命令行程序"""
argv_backup = sys.argv.copy()
from re import sub
sys.argv[0] = sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.argv = argv
try:
main()
except BaseException as e: # noqa: BLE001 不在这里处理 playwright 的异常
return e
finally:
sys.argv = argv_backup
return SystemExit(0)
@classmethod
def _handle_error(cls, error: BaseException) -> bool:
if isinstance(error, SystemExit) and error.code == 0:
return True
return False
@classmethod
async def _start_browser(cls) -> Browser:
"""启动浏览器实例"""
playwright = await async_playwright().start()
cls._browser = await playwright.firefox.launch()
return cls._browser
@classmethod
async def get_browser(cls) -> Browser:
"""获取浏览器实例"""
return cls._browser or await cls._start_browser()
@classmethod
async def _close_browser(cls) -> None:
"""关闭浏览器实例"""
if isinstance(cls._browser, Browser):
await cls._browser.close()

View File

@@ -1,7 +0,0 @@
from pydantic import BaseModel
class Config(BaseModel):
'''配置类'''
cache_path: str = 'cache/nonebot_plugin_tetris_stats/cache'
db_path: str = 'data/nonebot_plugin_tetris_stats/data.db'

View File

@@ -1,141 +0,0 @@
import datetime
import os
from asyncio import gather
from sqlite3 import Connection, connect
from nonebot import get_driver
from nonebot.log import logger
from .config import Config
driver = get_driver()
config = Config.parse_obj(get_driver().config)
@driver.on_startup
async def _():
await DataBase.init_db()
@driver.on_shutdown
async def _():
await DataBase.close_db()
class DataBase():
'''数据库交互类'''
_db: Connection | None = None
@classmethod
async def init_db(cls) -> Connection:
'''初始化数据库'''
if not os.path.exists(os.path.dirname(config.db_path)):
os.makedirs(os.path.dirname(config.db_path))
cls._db = connect(config.db_path)
cursor = cls._db.cursor()
cursor.execute('''CREATE TABLE IF NOT EXISTS IOBIND
(QQ INTEGER NOT NULL,
USER TEXT NOT NULL)''')
cursor.execute('''CREATE TABLE IF NOT EXISTS TOPBIND
(QQ INTEGER NOT NULL,
USER TEXT NOT NULL)''')
cursor.execute('''CREATE TABLE IF NOT EXISTS IORANK
(RANK VARCHAR(2) NOT NULL,
TRENDING CHAR(1) NOT NULL,
TRLINE FLOAT NOT NULL,
PLAYERCOUNT INTEGER NOT NULL,
AVGAPM FLOAT NOT NULL,
AVGPPS FLOAT NOT NULL,
ARGVS FLOAT NOT NULL,
DATE TEXT NOT NULL)''')
cls._db.commit()
logger.info('数据库初始化完成')
return cls._db
@classmethod
async def query_rank_info_today(cls, rank: str) -> list | None:
'''查询段位信息'''
db = await cls._get_db()
cursor = db.cursor()
cursor.execute('''SELECT TRENDING, TRLINE, PLAYERCOUNT, AVGAPM, AVGPPS, ARGVS, DATE
FROM IORANK
WHERE RANK = ? AND DATE = ?''', (rank, datetime.date.today()))
result = cursor.fetchone()
if result is None:
return None
return list(result)
@classmethod
async def write_rank_info_today(cls, rank: str, trline: int, playercount: int, avgapm: float, avgpps: float, avgvs: float):
'''写入段位信息'''
db = await cls._get_db()
cursor = db.cursor()
cursor.execute('''SELECT TRLINE
FROM IORANK
WHERE RANK = ? AND DATE = ?''', (rank, datetime.date.today() - datetime.timedelta(days=1)))
result = cursor.fetchone()
if result is None:
trending = '?'
elif trline > result[0]:
trending = ''
elif trline == result[0]:
trending = '-'
elif trline < result[0]:
trending = ''
cursor.execute('''INSERT INTO IORANK
(RANK, TRENDING, TRLINE, PLAYERCOUNT, AVGAPM, AVGPPS, ARGVS, DATE)
VALUES (?,?,?,?,?,?,?,?)''',
(rank, trending, trline, playercount, avgapm, avgpps, avgvs, datetime.date.today()))
db.commit()
@classmethod
async def _get_db(cls) -> Connection:
'''获取数据库对象'''
return cls._db or await cls.init_db()
@classmethod
async def query_bind_info(cls, qq_number: str | int, game_type: str) -> str | None:
'''查询绑定信息'''
db = await cls._get_db()
cursor = db.cursor()
cursor.execute(
f'SELECT USER FROM {game_type}BIND WHERE QQ = {qq_number}')
user = cursor.fetchone()
if user is None:
return None
return user[0]
@classmethod
async def write_bind_info(cls, qq_number: str | int, user: str, game_type: str) -> str:
'''写入绑定信息'''
bind_info, db = await gather(
cls.query_bind_info(qq_number=qq_number, game_type=game_type),
cls._get_db()
)
cursor = db.cursor()
if bind_info is not None:
cursor.execute(
f'UPDATE {game_type}BIND SET USER = ? WHERE QQ = ?', (user, qq_number))
message = '更新成功'
elif bind_info is None:
cursor.execute(
f'INSERT INTO {game_type}BIND (QQ, USER) VALUES (?, ?)', (qq_number, user))
message = '绑定成功'
else:
raise ValueError('预期外行为, 请上报GitHub')
db.commit()
return message
@classmethod
async def close_db(cls) -> None:
'''关闭数据库对象'''
if isinstance(cls._db, Connection):
cls._db.close()

View File

@@ -0,0 +1,35 @@
class TetrisStatsError(Exception):
"""所有 TetrisStats 发生的异常基类"""
def __init__(self, message: str = ''):
self.message = message
def __str__(self) -> str:
return self.message
def __repr__(self) -> str:
return self.message
class NeedCatchError(TetrisStatsError):
"""需要被捕获的异常基类"""
class DoNotCatchError(TetrisStatsError):
"""不应该被捕获的异常基类"""
class RequestError(NeedCatchError):
"""请求错误"""
class MessageFormatError(NeedCatchError):
"""用户发送的消息格式不正确"""
class DatabaseVersionError(DoNotCatchError):
"""数据库版本错误"""
class WhatTheFuckError(DoNotCatchError):
"""用于表示不应该出现的情况 ("""

View File

@@ -1,91 +0,0 @@
from re import match, sub
async def handle_bind_message(message: str, game_type: str) -> tuple[str | None, tuple]:
'''返回值为tuple[gameType, tuple[message, user]]'''
_cmd_aliases = {'IO': ['io绑定', 'iobind'],
'TOP': ['top绑定', 'topbind']}
# 剔除命令前缀
for i in _cmd_aliases[game_type]:
if match(rf'(?i){i}', message):
message = sub(rf'(?i){i}', '', message)
message = message.strip()
break
else:
raise ValueError('预期外行为, 请上报GitHub')
if message == '' or message.isspace():
return None, ('用户名为空', None)
return await check_name(message, game_type)
async def handle_stats_query_message(message: str, game_type: str) -> tuple[str | None, tuple]:
'''返回值为tuple[gameType, tuple[message, user]]'''
_cmd_aliases = {'IO': ['io查', 'iostats'],
'TOS': ['tos查', 'tostats', 'tosstats', '茶服查', '茶服stats'],
'TOP': ['top查', 'topstats']}
_me = [
'', '自己', '我等', '卑人', '', '老身', '', '老娘', '本姑娘', '本大爷', '鄙人', '寡人',
'小生', '贫僧', '本人', '', '', '', '', '', 'me', '洒家', '在下', '', '人家',
'本小姐', '老夫', '老子', '', '本尊', '', '拙者', '', '', '自分', '吾輩', '我輩', '',
'己等', '俺等', '此方', '', '', '劳资', '本宝宝', '', '本喵', 'watashi', 'i', 'myself',
'self', 'oneself'
]
# 剔除命令前缀
for i in _cmd_aliases[game_type]:
if match(rf'(?i){i}', message):
message = sub(rf'(?i){i}', '', message)
message = message.strip()
break
if message == '' or message.isspace():
return None, ('用户名为空', None)
if message.startswith('[CQ:at,qq='):
try:
user = int(str(message).split('[CQ:at,qq=')[1].split(']')[0])
except ValueError:
return None, ('QQ号码不合法', None)
else:
return 'AT', (None, user)
elif message in _me:
# 会不会有人叫本姑娘 本大爷这种或许可以成为id的名字呢
# TODO: 在判断是否可能是查自己的情况的时候 也去判断是否能成立为一个UserName
return 'ME', (None, None)
else:
return await check_name(message, game_type)
async def handle_rank_message(message: str) -> str:
_cmd_aliases = ['io段位', 'iorank']
# 剔除命令前缀
for i in _cmd_aliases:
if match(rf'(?i){i}', message):
message = sub(rf'(?i){i}', '', message)
message = message.strip()
break
else:
raise ValueError('预期外行为, 请上报GitHub')
return message
async def check_name(name: str, game_type: str) -> tuple[str | None, tuple]:
'''返回值为tuple[gameType, tuple[message, user]]'''
if game_type == 'IO':
if match(r'^[a-f0-9]{24}$', name):
return 'ID', (None, name)
if match(r'^[a-zA-Z0-9_-]{3,16}$', name):
return 'Name', (None, name.lower())
return None, ('用户名不合法', None)
if game_type == 'TOP':
if match(r'^[a-zA-Z0-9_]{1,16}$', name):
return 'Name', (None, name)
return None, ('用户名不合法', None)
if game_type == 'TOS':
if (match(r'^(?!\.)(?!com[0-9]$)(?!con$)(?!lpt[0-9]$)(?!nul$)(?!prn$)[^\-][^\+][^\|\*\?\\\s\!:<>/$"]*[^\.\|\*\?\\\s\!:<>/$"]+$', name)
and name.isdigit() is False
and 2 <= len(name) <= 18):
# 虽然我也不想这么长 但是似乎确实得这么长
# TODO 简化正则表达式
return 'Name', (None, name)
if name.isdigit() is True:
return 'QQ', (None, name)
return None, ('用户名不合法', None)
return None, ('游戏类型错误', None)

View File

@@ -0,0 +1,322 @@
from typing import overload
from .typing import Number
class TetrisMetricsBaseWithPPS:
def __init__(self, pps: Number, precision: int) -> None:
self._pps = pps
self.precision = precision
@property
def _lpm(self) -> Number:
return self._pps * 24
@property
def lpm(self) -> Number:
return round(self._lpm, self.precision)
@property
def pps(self) -> Number:
return round(self._pps, self.precision)
class TetrisMetricsBaseWithLPM:
def __init__(self, lpm: Number, precision: int) -> None:
self._lpm = lpm
self.precision = precision
@property
def lpm(self) -> Number:
return round(self._lpm, self.precision)
@property
def _pps(self) -> Number:
return self._lpm / 24
@property
def pps(self) -> Number:
return round(self._pps, self.precision)
class TetrisMetricsBaseWithVS:
def __init__(self, vs: Number, precision: int) -> None:
self._vs = vs
self.precision = precision
@property
def vs(self) -> Number:
return round(self._vs, self.precision)
@property
def _adpm(self) -> Number:
return self._vs * 0.6
@property
def adpm(self) -> Number:
return round(self._adpm, self.precision)
class TetrisMetricsBaseWithADPM:
def __init__(self, adpm: Number, precision: int) -> None:
self._adpm = adpm
self.precision = precision
@property
def _vs(self) -> Number:
return self._adpm / 0.6
@property
def vs(self) -> Number:
return round(self._vs, self.precision)
@property
def adpm(self) -> Number:
return round(self._adpm, self.precision)
class TetrisMetricsBasicWithPPS(TetrisMetricsBaseWithPPS):
def __init__(self, pps: Number, apm: Number, precision: int) -> None:
super().__init__(pps=pps, precision=precision)
self._apm = apm
@property
def apm(self) -> Number:
return round(self._apm, self.precision)
@property
def apl(self) -> Number:
return round(self._apm / self._lpm, self.precision)
class TetrisMetricsBasicWithLPM(TetrisMetricsBaseWithLPM):
def __init__(self, lpm: Number, apm: Number, precision: int):
super().__init__(lpm=lpm, precision=precision)
self._apm = apm
@property
def apm(self) -> Number:
return round(self._apm, self.precision)
@property
def apl(self) -> Number:
return round(self._apm / self._lpm, self.precision)
class TetrisMetricsProWithPPSVS(TetrisMetricsBasicWithPPS, TetrisMetricsBaseWithVS):
def __init__(self, pps: Number, apm: Number, vs: Number, precision: int) -> None:
super().__init__(pps=pps, apm=apm, precision=precision)
super(TetrisMetricsBaseWithPPS, self).__init__(vs=vs, precision=precision)
@property
def adpl(self) -> Number:
return round(self._adpm / self._lpm, self.precision)
class TetrisMetricsProWithPPSADPM(TetrisMetricsBasicWithPPS, TetrisMetricsBaseWithADPM):
def __init__(self, pps: Number, apm: Number, adpm: Number, precision: int) -> None:
super().__init__(pps=pps, apm=apm, precision=precision)
super(TetrisMetricsBaseWithPPS, self).__init__(adpm=adpm, precision=precision)
@property
def adpl(self) -> Number:
return round(self._adpm / self._lpm, self.precision)
class TetrisMetricsProWithLPMVS(TetrisMetricsBasicWithLPM, TetrisMetricsBaseWithVS):
def __init__(self, lpm: Number, apm: Number, vs: Number, precision: int) -> None:
super().__init__(lpm=lpm, apm=apm, precision=precision)
super(TetrisMetricsBaseWithLPM, self).__init__(vs=vs, precision=precision)
@property
def adpl(self) -> Number:
return round(self._adpm / self._lpm, self.precision)
class TetrisMetricsProWithLPMADPM(TetrisMetricsBasicWithLPM, TetrisMetricsBaseWithADPM):
def __init__(self, lpm: Number, apm: Number, adpm: Number, precision: int) -> None:
super().__init__(lpm=lpm, apm=apm, precision=precision)
super(TetrisMetricsBaseWithLPM, self).__init__(adpm=adpm, precision=precision)
@property
def adpl(self) -> Number:
return round(self._adpm / self._lpm, self.precision)
@overload
def get_metrics( # noqa: PLR0913
*,
pps: Number,
lpm: None = None,
apm: None = None,
vs: None = None,
adpm: None = None,
precision: int = 2,
) -> TetrisMetricsBaseWithPPS:
...
@overload
def get_metrics( # noqa: PLR0913
*,
pps: None = None,
lpm: Number,
apm: None = None,
vs: None = None,
adpm: None = None,
precision: int = 2,
) -> TetrisMetricsBaseWithLPM:
...
@overload
def get_metrics( # noqa: PLR0913
*,
pps: None = None,
lpm: None = None,
apm: None = None,
vs: Number,
adpm: None = None,
precision: int = 2,
) -> TetrisMetricsBaseWithVS:
...
@overload
def get_metrics( # noqa: PLR0913
*,
pps: None = None,
lpm: None = None,
apm: None = None,
vs: None = None,
adpm: Number,
precision: int = 2,
) -> TetrisMetricsBaseWithADPM:
...
@overload
def get_metrics( # noqa: PLR0913
*,
pps: Number,
lpm: None = None,
apm: Number,
vs: None = None,
adpm: None = None,
precision: int = 2,
) -> TetrisMetricsBasicWithPPS:
...
@overload
def get_metrics( # noqa: PLR0913
*,
pps: None = None,
lpm: Number,
apm: Number,
vs: None = None,
adpm: None = None,
precision: int = 2,
) -> TetrisMetricsBasicWithLPM:
...
@overload
def get_metrics( # noqa: PLR0913
*,
pps: Number,
lpm: None = None,
apm: Number,
vs: Number,
adpm: None = None,
precision: int = 2,
) -> TetrisMetricsProWithPPSVS:
...
@overload
def get_metrics( # noqa: PLR0913
*,
pps: Number,
lpm: None = None,
apm: Number,
vs: None = None,
adpm: Number,
precision: int = 2,
) -> TetrisMetricsProWithPPSADPM:
...
@overload
def get_metrics( # noqa: PLR0913
*,
pps: None = None,
lpm: Number,
apm: Number,
vs: Number,
adpm: None = None,
precision: int = 2,
) -> TetrisMetricsProWithLPMVS:
...
@overload
def get_metrics( # noqa: PLR0913
*,
pps: None = None,
lpm: Number,
apm: Number,
vs: None = None,
adpm: Number,
precision: int = 2,
) -> TetrisMetricsProWithLPMADPM:
...
def get_metrics( # noqa: PLR0911, PLR0912, PLR0913
*,
pps: Number | None = None,
lpm: Number | None = None,
apm: Number | None = None,
vs: Number | None = None,
adpm: Number | None = None,
precision: int = 2,
) -> (
TetrisMetricsBaseWithPPS
| TetrisMetricsBaseWithLPM
| TetrisMetricsBaseWithVS
| TetrisMetricsBaseWithADPM
| TetrisMetricsBasicWithPPS
| TetrisMetricsBasicWithLPM
| TetrisMetricsProWithPPSVS
| TetrisMetricsProWithLPMVS
| TetrisMetricsProWithPPSADPM
| TetrisMetricsProWithLPMADPM
):
if apm is None:
if pps is not None:
return TetrisMetricsBaseWithPPS(pps, precision=precision)
if lpm is not None:
return TetrisMetricsBaseWithLPM(lpm, precision=precision)
if vs is not None:
return TetrisMetricsBaseWithVS(vs, precision=precision)
if adpm is not None:
return TetrisMetricsBaseWithADPM(adpm, precision=precision)
elif vs is None and adpm is None:
if pps is not None:
return TetrisMetricsBasicWithPPS(pps, apm, precision=precision)
if lpm is not None:
return TetrisMetricsBasicWithLPM(lpm, apm, precision=precision)
else:
if vs is not None:
if pps is not None:
return TetrisMetricsProWithPPSVS(pps, apm, vs, precision=precision)
if lpm is not None:
return TetrisMetricsProWithLPMVS(lpm, apm, vs, precision=precision)
if adpm is not None:
if pps is not None:
return TetrisMetricsProWithPPSADPM(pps, apm, adpm, precision=precision)
if lpm is not None:
return TetrisMetricsProWithLPMADPM(lpm, apm, adpm, precision=precision)
raise TypeError

View File

@@ -0,0 +1,19 @@
from nonebot.adapters import Bot
def get_platform(bot: Bot) -> str:
try:
from nonebot.adapters.onebot.v12 import Bot as OB12Bot
if isinstance(bot, OB12Bot):
return bot.platform
except ImportError:
pass
try:
from nonebot.adapters.satori import Bot as SaBot
if isinstance(bot, SaBot):
return bot.platform
except ImportError:
pass
return bot.type

View File

@@ -0,0 +1,79 @@
from datetime import UTC, datetime
from typing import ClassVar
from nonebot import get_driver, get_plugin
from nonebot.adapters import Bot, Event
from nonebot.matcher import Matcher
from nonebot.message import run_postprocessor, run_preprocessor
from nonebot_plugin_orm import get_session
from ..db.models import HistoricalData
driver = get_driver()
class Recorder:
matchers: ClassVar[set[type[Matcher]]] = set()
historical_data: ClassVar[dict[int, tuple[HistoricalData, bool]]] = {}
@classmethod
def create_historical_data(cls, event_id: int, historical_data: HistoricalData) -> None:
cls.historical_data[event_id] = (historical_data, False)
@classmethod
def update_historical_data(cls, event_id: int, historical_data: HistoricalData) -> None:
if event_id not in cls.historical_data:
raise KeyError
cls.historical_data[event_id] = (historical_data, True)
@classmethod
def get_historical_data(cls, event_id: int) -> HistoricalData:
return cls.historical_data[event_id][0]
@classmethod
async def save_historical_data(cls, event_id: int) -> None:
if event_id not in cls.historical_data:
raise KeyError
historical_data, completed = cls.historical_data.pop(event_id)
if completed:
async with get_session() as session:
session.add(historical_data)
await session.commit()
@classmethod
def del_historical_data(cls, event_id: int) -> None:
cls.historical_data.pop(event_id)
@driver.on_startup
def _():
plugin = get_plugin('nonebot_plugin_tetris_stats')
if plugin is not None:
Recorder.matchers = plugin.matcher
else:
raise RuntimeError('获取不到自身插件对象')
@run_preprocessor
def _(bot: Bot, event: Event, matcher: Matcher):
if isinstance(matcher, tuple(Recorder.matchers)):
Recorder.create_historical_data(
event_id=id(event),
historical_data=HistoricalData(
trigger_time=datetime.now(tz=UTC),
bot_platform=bot.type,
bot_account=bot.self_id,
source_type=event.get_type(),
source_account=event.get_session_id(),
message=event.get_message(),
),
)
@run_postprocessor
async def _(event: Event, matcher: Matcher, exception: Exception | None):
if isinstance(matcher, tuple(Recorder.matchers)):
if exception is not None:
Recorder.del_historical_data(id(event))
else:
await Recorder.save_historical_data(id(event))

View File

@@ -0,0 +1,128 @@
from urllib.parse import urljoin, urlparse
from aiofiles import open
from httpx import AsyncClient, HTTPError
from nonebot import get_driver
from nonebot.log import logger
from playwright.async_api import Response
from ujson import JSONDecodeError, dumps, loads
from ..config.config import CACHE_PATH
from .browser import BrowserManager
from .exception import RequestError
driver = get_driver()
@driver.on_startup
async def _():
await Request._init_cache()
await Request._read_cache()
@driver.on_shutdown
async def _():
await Request._write_cache()
def splice_url(url_list: list[str]) -> str:
url = ''
if len(url_list):
url = url_list.pop(0)
for i in url_list:
url = urljoin(url, i)
return url
class Request:
"""网络请求相关类"""
_CACHE_FILE = CACHE_PATH.joinpath('cloudflare_cache.json')
_headers: dict | None = None
_cookies: dict | None = None
@classmethod
async def _anti_cloudflare(cls, url: str) -> bytes:
"""用firefox硬穿五秒盾"""
browser = await BrowserManager.get_browser()
context = await browser.new_context()
page = await context.new_page()
response = await page.goto(url)
attempts = 0
while attempts < 60: # noqa: PLR2004
attempts += 1
text = await page.locator('body').text_content()
if text is None:
await page.wait_for_timeout(1000)
continue
if await page.title() == 'Please Wait... | Cloudflare':
logger.warning('疑似触发了 Cloudflare 的验证码')
break
try:
loads(text)
except JSONDecodeError:
await page.wait_for_timeout(1000)
else:
if not isinstance(response, Response):
raise RequestError('api请求失败')
cls._headers = await response.request.all_headers()
try:
cls._cookies = {i['name']: i['value'] for i in await context.cookies()}
except KeyError:
cls._cookies = None
await page.close()
await context.close()
return await response.body()
await page.close()
await context.close()
raise RequestError('绕过五秒盾失败')
@classmethod
async def _init_cache(cls) -> None:
"""初始化缓存文件"""
if not cls._CACHE_FILE.exists():
async with open(file=cls._CACHE_FILE, mode='w', encoding='UTF-8') as file:
await file.write(dumps({'headers': cls._headers, 'cookies': cls._cookies}))
@classmethod
async def _read_cache(cls) -> None:
"""读取缓存文件"""
try:
async with open(file=cls._CACHE_FILE, mode='r', encoding='UTF-8') as file:
json = loads(await file.read())
except FileNotFoundError:
await cls._init_cache()
except (PermissionError, JSONDecodeError):
cls._CACHE_FILE.unlink()
await cls._init_cache()
else:
cls._headers = json['headers']
cls._cookies = json['cookies']
@classmethod
async def _write_cache(cls) -> None:
"""写入缓存文件"""
try:
async with open(file=cls._CACHE_FILE, mode='r+', encoding='UTF-8') as file:
await file.write(dumps({'headers': cls._headers, 'cookies': cls._cookies}))
except FileNotFoundError:
await cls._init_cache()
except (PermissionError, JSONDecodeError):
cls._CACHE_FILE.unlink()
await cls._init_cache()
@classmethod
async def request(cls, url: str, *, is_json: bool = True) -> bytes:
"""请求api"""
try:
async with AsyncClient(cookies=cls._cookies) as session:
response = await session.get(url, headers=cls._headers)
if is_json:
loads(response.content)
return response.content
except HTTPError as e:
raise RequestError(f'请求错误\n{e!r}') from e
except JSONDecodeError:
if urlparse(url).netloc.lower().endswith('tetr.io'):
return await cls._anti_cloudflare(url)
raise

View File

@@ -0,0 +1,61 @@
from collections.abc import Awaitable, Callable
from typing import Any, Literal
Number = int | float
GameType = Literal['IO', 'TOP', 'TOS']
CommandType = Literal['bind', 'query']
AsyncCallable = Callable[..., Awaitable[Any]]
Me = Literal[
'',
'自己',
'我等',
'卑人',
'',
'老身',
'',
'老娘',
'本姑娘',
'本大爷',
'鄙人',
'寡人',
'小生',
'贫僧',
'本人',
'',
'',
'',
'',
'',
'me',
'洒家',
'在下',
'',
'人家',
'本小姐',
'老夫',
'老子',
'',
'本尊',
'',
'拙者',
'',
'',
'自分',
'吾輩',
'我輩',
'',
'己等',
'俺等',
'此方',
'',
'',
'劳资',
'本宝宝',
'',
'本喵',
'watashi',
'i',
'myself',
'self',
'oneself',
]