Compare commits

..

133 Commits

Author SHA1 Message Date
bc37a015d6 🔖 1.0.0 2024-05-02 01:24:29 +08:00
呵呵です
fd85140c99 绑定使用图片回复 #61 (#305)
* 🚧 查数据图初版测试

Co-authored-by: C1ystal <m17687496044@163.com>
Co-authored-by: C29H25N3O5 <michaelgu495@gmail.com>

* 🙈 添加一些 ignore 文件

* 🎨 格式化代码

* 🐛 修复格式化导致的样式爆炸

* 💄 优化曲线图观感

* 💄 将雷达图的指示器名称旋转显示

* 💄 查数据图第二版

Co-authored-by: C29H25N3O5 <michaelgu495@gmail.com>

* ✏️ 修复 typo

* 💄 把用户头像文件的引用放到 html 里

* 💄 账户绑定图第一版

Co-authored-by: C1ystal <m17687496044@163.com>
Co-authored-by: C29H25N3O5 <michaelgu495@gmail.com>

* 🚧 模板化测试

*  添加依赖 fastapi

*  通过 FastAPI 提供静态文件

*  添加依赖 jinja2

* 💄 更新数据图模板 (#291)

* feat(template): show actual value

* feat(template): add user avatar

* feat(template): fix radar

* feat(style): fix name container width fixed caused display misplacement

* feat(style): fix vs value wrap display

* feat(template): make check data length in template

* feat(template): update radar data

* feat(jinja): update data

* fix(template): fix typo

* feat(style): prevent sign too long

* feat(template): turn off echarts animation

* chore(deps): add identicon.js

* fix(template): fix typo

* 🙈 更新.gitignore

* 🏗️ 大部分重构为 flex 布局

---------

Co-authored-by: shoucandanghehe <wallfjjd@gmail.com>
Co-authored-by: 呵呵です <51957264+shoucandanghehe@users.noreply.github.com>

*  添加依赖 nonebot_plugin_userinfo

*  通过 FastAPI 托管渲染后的模板

*  新增头像 api 使用 playwright 生成

*  修正模板资源文件引用路径
被托管后的正确路径

* 💄 将绑定图模板化

* 💄 重命名变量

* 🚚 重命名资源

*  使用 jinja2 渲染模板

*  使用 playwright 渲染网页

* 🩹 渲染模板时对 IO 进行一些额外处理

*  添加依赖 pillow

* 🚚 修改托管页面的路由路径

* 💬 优化绑定图文案

*  新增获取自身网络位置的方法

* 🍱 更新 unknown.svg

*  新增获取用户头像的方法

*  绑定消息使用图片回复

*  为 identicon api 添加缓存

* 🔥 删除旧文件

* 🚚 重命名模板

* 📄 添加字体 License

* 🙈 更新.gitignore

---------

Co-authored-by: C1ystal <m17687496044@163.com>
Co-authored-by: C29H25N3O5 <michaelgu495@gmail.com>
Co-authored-by: 渣渣120 <WOSHIZHAZHA120@qq.com>
2024-05-02 01:22:33 +08:00
dependabot[bot]
80f4316564 ⬆️ Bump nonebot2 from 2.2.1 to 2.3.0 (#304)
Bumps [nonebot2](https://github.com/nonebot/nonebot2) from 2.2.1 to 2.3.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.2.1...v2.3.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>
2024-05-02 00:31:44 +08:00
dependabot[bot]
3b9c0c89b1 ⬆️ Bump nonebot-adapter-satori from 0.11.3 to 0.11.4 (#302)
Bumps [nonebot-adapter-satori](https://github.com/nonebot/adapter-satori) from 0.11.3 to 0.11.4.
- [Release notes](https://github.com/nonebot/adapter-satori/releases)
- [Commits](https://github.com/nonebot/adapter-satori/compare/v0.11.3...v0.11.4)

---
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-04-30 23:25:33 +08:00
c02fdfc47f 🔖 1.0.0.a17 2024-04-30 02:27:31 +08:00
93b169fa40 🐛 修复对 Pydantic V1 的适配 2024-04-30 02:27:08 +08:00
5cb428ed71 🔖 1.0.0.a16 2024-04-30 02:09:34 +08:00
呵呵です
ec392ee384 支持茶服多个api地址的故障转移 (#301)
*  RequestError 新增 status_code 参数

*  新增支持故障转移的请求方法

*  支持茶服多个api地址的故障转移
2024-04-30 01:52:41 +08:00
dependabot[bot]
d037cf6d44 ⬆️ Bump nonebot-adapter-satori from 0.11.2 to 0.11.3 (#300)
Bumps [nonebot-adapter-satori](https://github.com/nonebot/adapter-satori) from 0.11.2 to 0.11.3.
- [Release notes](https://github.com/nonebot/adapter-satori/releases)
- [Commits](https://github.com/nonebot/adapter-satori/compare/v0.11.2...v0.11.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-04-30 00:02:36 +08:00
dependabot[bot]
6964e9b655 ⬆️ Bump nonebot-plugin-orm from 0.7.1 to 0.7.2 (#298)
Bumps [nonebot-plugin-orm](https://github.com/nonebot/plugin-orm) from 0.7.1 to 0.7.2.
- [Release notes](https://github.com/nonebot/plugin-orm/releases)
- [Commits](https://github.com/nonebot/plugin-orm/compare/v0.7.1...v0.7.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>
2024-04-29 23:56:55 +08:00
dependabot[bot]
7a032bf947 ⬆️ Bump nonebot-adapter-kaiheila from 0.3.3 to 0.3.4 (#299)
Bumps [nonebot-adapter-kaiheila](https://github.com/Tian-que/nonebot-adapter-kaiheila) from 0.3.3 to 0.3.4.
- [Release notes](https://github.com/Tian-que/nonebot-adapter-kaiheila/releases)
- [Commits](https://github.com/Tian-que/nonebot-adapter-kaiheila/compare/v0.3.3...v0.3.4)

---
updated-dependencies:
- dependency-name: nonebot-adapter-kaiheila
  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-04-29 23:56:31 +08:00
dependabot[bot]
9a91e5ef5b ⬆️ Bump nonebot-plugin-alconna from 0.44.0 to 0.45.0 (#297)
Bumps [nonebot-plugin-alconna](https://github.com/nonebot/plugin-alconna) from 0.44.0 to 0.45.0.
- [Release notes](https://github.com/nonebot/plugin-alconna/releases)
- [Commits](https://github.com/nonebot/plugin-alconna/compare/v0.44.0...v0.45.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-04-29 23:56:20 +08:00
dependabot[bot]
5b58697fce ⬆️ Bump nonebot-adapter-satori from 0.11.0 to 0.11.2 (#296)
Bumps [nonebot-adapter-satori](https://github.com/nonebot/adapter-satori) from 0.11.0 to 0.11.2.
- [Release notes](https://github.com/nonebot/adapter-satori/releases)
- [Commits](https://github.com/nonebot/adapter-satori/compare/v0.11.0...v0.11.2)

---
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-04-28 15:38:57 +08:00
dependabot[bot]
b14cebe832 ⬆️ Bump ruff from 0.4.1 to 0.4.2 (#295)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.1 to 0.4.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/v0.4.1...v0.4.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-04-28 15:38:48 +08:00
dependabot[bot]
4306195ee5 ⬆️ Bump nonebot-plugin-alconna from 0.43.0 to 0.44.0 (#294)
Bumps [nonebot-plugin-alconna](https://github.com/nonebot/plugin-alconna) from 0.43.0 to 0.44.0.
- [Release notes](https://github.com/nonebot/plugin-alconna/releases)
- [Commits](https://github.com/nonebot/plugin-alconna/compare/v0.43.0...v0.44.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-04-28 15:38:39 +08:00
dependabot[bot]
ac9c6e79d9 ⬆️ Bump nonebot-adapter-satori from 0.10.5 to 0.11.0 (#293)
Bumps [nonebot-adapter-satori](https://github.com/nonebot/adapter-satori) from 0.10.5 to 0.11.0.
- [Release notes](https://github.com/nonebot/adapter-satori/releases)
- [Commits](https://github.com/nonebot/adapter-satori/compare/v0.10.5...v0.11.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-04-25 15:53:32 +08:00
dependabot[bot]
ed035c65c1 ⬆️ Bump mypy from 1.9.0 to 1.10.0 (#292)
Bumps [mypy](https://github.com/python/mypy) from 1.9.0 to 1.10.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/1.9.0...v1.10.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-04-25 02:29:20 +08:00
dc8bc9b306 🚨 添加一些type: ignore) 2024-04-24 17:56:24 +08:00
454dd57007 🐛 mode写错了 2024-04-24 17:55:17 +08:00
b396a6d450 存储历史IO Rank数据至本地 2024-04-24 17:28:40 +08:00
7f584a46eb 添加依赖 zstandard 2024-04-24 16:56:44 +08:00
27518c0408 适配 Pydantic V2 2024-04-24 16:49:01 +08:00
dependabot[bot]
d2a3801dac ⬆️ Bump nonebot-plugin-alconna from 0.42.5 to 0.43.0 (#290)
Bumps [nonebot-plugin-alconna](https://github.com/nonebot/plugin-alconna) from 0.42.5 to 0.43.0.
- [Release notes](https://github.com/nonebot/plugin-alconna/releases)
- [Commits](https://github.com/nonebot/plugin-alconna/compare/v0.42.5...v0.43.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-04-24 00:53:44 +08:00
563564ac8d 适配 Pydantic V2 2024-04-24 00:52:32 +08:00
87c87ad231 🗃️ 重命名字段 2024-04-24 00:52:31 +08:00
30515d1907 🚨 ruff auto fix 2024-04-23 22:52:04 +08:00
dependabot[bot]
bd0a8ea447 ⬆️ Bump nonebot-plugin-alconna from 0.42.4 to 0.42.5 (#289)
Bumps [nonebot-plugin-alconna](https://github.com/nonebot/plugin-alconna) from 0.42.4 to 0.42.5.
- [Release notes](https://github.com/nonebot/plugin-alconna/releases)
- [Commits](https://github.com/nonebot/plugin-alconna/compare/v0.42.4...v0.42.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-04-23 22:28:43 +08:00
dependabot[bot]
1db1e6dbba ⬆️ Bump nonebot-adapter-satori from 0.10.4 to 0.10.5 (#288)
Bumps [nonebot-adapter-satori](https://github.com/nonebot/adapter-satori) from 0.10.4 to 0.10.5.
- [Release notes](https://github.com/nonebot/adapter-satori/releases)
- [Commits](https://github.com/nonebot/adapter-satori/compare/v0.10.4...v0.10.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-04-23 22:28:21 +08:00
dependabot[bot]
9040aa9fba ⬆️ Bump ruff from 0.3.7 to 0.4.1 (#287)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.3.7 to 0.4.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.3.7...v0.4.1)

---
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-04-23 22:27:59 +08:00
呵呵です
3a5f1eb266 🔖 1.0.0.a15 2024-04-17 14:11:45 +08:00
MianSoft
43e927430a 👽️ 修改茶服api地址 (#286) 2024-04-17 14:09:59 +08:00
e1b0918a52 🔖 1.0.0.a14 2024-04-15 17:42:31 +08:00
c86b2eb31b ⬆️ 更新依赖 2024-04-15 17:41:39 +08:00
dependabot[bot]
47b3f3e881 ⬆️ Bump nonebot-plugin-alconna from 0.37.0 to 0.38.2 (#268)
Bumps [nonebot-plugin-alconna](https://github.com/nonebot/plugin-alconna) from 0.37.0 to 0.38.2.
- [Release notes](https://github.com/nonebot/plugin-alconna/releases)
- [Commits](https://github.com/nonebot/plugin-alconna/compare/v0.37.0...v0.38.2)

---
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-03-14 08:53:59 +08:00
dependabot[bot]
7caee587b4 ⬆️ Bump pandas from 2.2.0 to 2.2.1 (#266)
Bumps [pandas](https://github.com/pandas-dev/pandas) from 2.2.0 to 2.2.1.
- [Release notes](https://github.com/pandas-dev/pandas/releases)
- [Commits](https://github.com/pandas-dev/pandas/compare/v2.2.0...v2.2.1)

---
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-03-14 08:53:44 +08:00
dependabot[bot]
28ae564e59 ⬆️ Bump nonebot-plugin-orm from 0.6.3 to 0.7.1 (#264)
Bumps [nonebot-plugin-orm](https://github.com/nonebot/plugin-orm) from 0.6.3 to 0.7.1.
- [Release notes](https://github.com/nonebot/plugin-orm/releases)
- [Commits](https://github.com/nonebot/plugin-orm/compare/v0.6.3...v0.7.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>
2024-03-04 14:06:04 +08:00
dependabot[bot]
90dee8402d ⬆️ Bump httpx from 0.26.0 to 0.27.0 (#263)
Bumps [httpx](https://github.com/encode/httpx) from 0.26.0 to 0.27.0.
- [Release notes](https://github.com/encode/httpx/releases)
- [Changelog](https://github.com/encode/httpx/blob/master/CHANGELOG.md)
- [Commits](https://github.com/encode/httpx/compare/0.26.0...0.27.0)

---
updated-dependencies:
- dependency-name: httpx
  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-03-04 12:02:53 +08:00
dependabot[bot]
8b560e55cb ⬆️ Bump pandas-stubs from 2.1.4.231227 to 2.2.0.240218 (#259)
Bumps [pandas-stubs](https://github.com/pandas-dev/pandas-stubs) from 2.1.4.231227 to 2.2.0.240218.
- [Changelog](https://github.com/pandas-dev/pandas-stubs/blob/main/docs/release_procedure.md)
- [Commits](https://github.com/pandas-dev/pandas-stubs/compare/v2.1.4.231227...v2.2.0.240218)

---
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>
2024-03-04 11:53:08 +08:00
dependabot[bot]
3080531503 ⬆️ Bump nonebot-adapter-satori from 0.9.1 to 0.9.3 (#258)
Bumps [nonebot-adapter-satori](https://github.com/nonebot/adapter-satori) from 0.9.1 to 0.9.3.
- [Release notes](https://github.com/nonebot/adapter-satori/releases)
- [Commits](https://github.com/nonebot/adapter-satori/compare/v0.9.1...v0.9.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-03-04 11:52:54 +08:00
dependabot[bot]
fae0088533 ⬆️ Bump ruff from 0.2.1 to 0.3.0 (#265)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.2.1 to 0.3.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.2.1...v0.3.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-03-04 11:48:09 +08:00
dependabot[bot]
db9286a369 ⬆️ Bump nonebot-adapter-onebot from 2.4.0 to 2.4.1 (#257)
Bumps [nonebot-adapter-onebot](https://github.com/nonebot/adapter-onebot) from 2.4.0 to 2.4.1.
- [Release notes](https://github.com/nonebot/adapter-onebot/releases)
- [Commits](https://github.com/nonebot/adapter-onebot/compare/v2.4.0...v2.4.1)

---
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-02-17 22:14:59 +08:00
dependabot[bot]
420fb29318 ⬆️ Bump nonebot-adapter-kaiheila from 0.3.0 to 0.3.1 (#256)
Bumps [nonebot-adapter-kaiheila](https://github.com/Tian-que/nonebot-adapter-kaiheila) from 0.3.0 to 0.3.1.
- [Release notes](https://github.com/Tian-que/nonebot-adapter-kaiheila/releases)
- [Commits](https://github.com/Tian-que/nonebot-adapter-kaiheila/compare/v0.03...v0.3.1)

---
updated-dependencies:
- dependency-name: nonebot-adapter-kaiheila
  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-02-17 21:31:20 +08:00
dependabot[bot]
433a6edd3b ⬆️ Bump nonebot-adapter-satori from 0.9.0 to 0.9.1 (#255)
Bumps [nonebot-adapter-satori](https://github.com/nonebot/adapter-satori) from 0.9.0 to 0.9.1.
- [Release notes](https://github.com/nonebot/adapter-satori/releases)
- [Commits](https://github.com/nonebot/adapter-satori/compare/v0.9.0...v0.9.1)

---
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-02-17 20:56:27 +08:00
dependabot[bot]
fa81231f78 ⬆️ Bump nonebot-plugin-alconna from 0.36.2 to 0.37.0 (#253)
Bumps [nonebot-plugin-alconna](https://github.com/nonebot/plugin-alconna) from 0.36.2 to 0.37.0.
- [Release notes](https://github.com/nonebot/plugin-alconna/releases)
- [Commits](https://github.com/nonebot/plugin-alconna/compare/v0.36.2...v0.37.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-02-17 19:22:12 +08:00
dependabot[bot]
c474cf0af2 ⬆️ Bump nonebot-plugin-apscheduler from 0.3.0 to 0.4.0 (#252)
Bumps [nonebot-plugin-apscheduler](https://github.com/nonebot/plugin-apscheduler) from 0.3.0 to 0.4.0.
- [Release notes](https://github.com/nonebot/plugin-apscheduler/releases)
- [Commits](https://github.com/nonebot/plugin-apscheduler/compare/v0.3.0...v0.4.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-02-17 10:09:27 +08:00
dependabot[bot]
e38eb5cdff ⬆️ Bump types-lxml from 2023.10.21 to 2024.2.9 (#251)
Bumps [types-lxml](https://github.com/abelcheung/types-lxml) from 2023.10.21 to 2024.2.9.
- [Release notes](https://github.com/abelcheung/types-lxml/releases)
- [Commits](https://github.com/abelcheung/types-lxml/compare/2023.10.21...2024.02.09)

---
updated-dependencies:
- dependency-name: types-lxml
  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-02-14 16:39:41 +08:00
dependabot[bot]
7bacf89840 ⬆️ Bump nonebot-adapter-onebot from 2.3.1 to 2.4.0 (#254)
Bumps [nonebot-adapter-onebot](https://github.com/nonebot/adapter-onebot) from 2.3.1 to 2.4.0.
- [Release notes](https://github.com/nonebot/adapter-onebot/releases)
- [Commits](https://github.com/nonebot/adapter-onebot/compare/v2.3.1...v2.4.0)

---
updated-dependencies:
- dependency-name: nonebot-adapter-onebot
  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-02-14 14:44:14 +08:00
dependabot[bot]
4622e90995 ⬆️ Bump nonebot-adapter-satori from 0.8.1 to 0.9.0 (#250)
Bumps [nonebot-adapter-satori](https://github.com/nonebot/adapter-satori) from 0.8.1 to 0.9.0.
- [Release notes](https://github.com/nonebot/adapter-satori/releases)
- [Commits](https://github.com/nonebot/adapter-satori/compare/v0.8.1...v0.9.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-02-12 22:42:50 +08:00
dependabot[bot]
fa8c2b11e6 ⬆️ Bump nonebot-plugin-localstore from 0.5.1 to 0.6.0 (#249)
Bumps [nonebot-plugin-localstore](https://github.com/nonebot/plugin-localstore) from 0.5.1 to 0.6.0.
- [Release notes](https://github.com/nonebot/plugin-localstore/releases)
- [Commits](https://github.com/nonebot/plugin-localstore/compare/v0.5.1...v0.6.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-02-12 19:54:44 +08:00
dependabot[bot]
2123b747af ⬆️ Bump playwright from 1.41.1 to 1.41.2 (#246)
Bumps [playwright](https://github.com/Microsoft/playwright-python) from 1.41.1 to 1.41.2.
- [Release notes](https://github.com/Microsoft/playwright-python/releases)
- [Commits](https://github.com/Microsoft/playwright-python/compare/v1.41.1...v1.41.2)

---
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-02-12 08:38:07 +08:00
dependabot[bot]
e65233d09f ⬆️ Bump viztracer from 0.16.1 to 0.16.2 (#245)
Bumps [viztracer](https://github.com/gaogaotiantian/viztracer) from 0.16.1 to 0.16.2.
- [Release notes](https://github.com/gaogaotiantian/viztracer/releases)
- [Commits](https://github.com/gaogaotiantian/viztracer/compare/0.16.1...0.16.2)

---
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-02-12 08:03:30 +08:00
dependabot[bot]
7e81bf6b8b ⬆️ Bump ruff from 0.2.0 to 0.2.1 (#243)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.2.0 to 0.2.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.2.0...v0.2.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-02-12 07:41:37 +08:00
dependabot[bot]
c4614aa006 ⬆️ Bump nonebot2 from 2.1.3 to 2.2.0 (#248)
Bumps [nonebot2](https://github.com/nonebot/nonebot2) from 2.1.3 to 2.2.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.1.3...v2.2.0)

---
updated-dependencies:
- dependency-name: nonebot2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-12 07:30:30 +08:00
dependabot[bot]
79a657b9f5 ⬆️ Bump playwright from 1.40.0 to 1.41.1 (#237)
Bumps [playwright](https://github.com/Microsoft/playwright-python) from 1.40.0 to 1.41.1.
- [Release notes](https://github.com/Microsoft/playwright-python/releases)
- [Commits](https://github.com/Microsoft/playwright-python/compare/v1.40.0...v1.41.1)

---
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-02-06 07:07:21 +08:00
dependabot[bot]
0164f29c1e ⬆️ Bump pandas from 2.1.4 to 2.2.0 (#235)
Bumps [pandas](https://github.com/pandas-dev/pandas) from 2.1.4 to 2.2.0.
- [Release notes](https://github.com/pandas-dev/pandas/releases)
- [Commits](https://github.com/pandas-dev/pandas/compare/v2.1.4...v2.2.0)

---
updated-dependencies:
- dependency-name: pandas
  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-02-06 07:00:41 +08:00
dependabot[bot]
8db56366df ⬆️ Bump nonebot-plugin-alconna from 0.35.1 to 0.36.2 (#239)
Bumps [nonebot-plugin-alconna](https://github.com/nonebot/plugin-alconna) from 0.35.1 to 0.36.2.
- [Release notes](https://github.com/nonebot/plugin-alconna/releases)
- [Commits](https://github.com/nonebot/plugin-alconna/compare/v0.35.1...v0.36.2)

---
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-02-06 07:00:12 +08:00
dependabot[bot]
de0a1e4c73 ⬆️ Bump nonebot-plugin-orm from 0.6.2 to 0.6.3 (#233)
Bumps [nonebot-plugin-orm](https://github.com/nonebot/plugin-orm) from 0.6.2 to 0.6.3.
- [Release notes](https://github.com/nonebot/plugin-orm/releases)
- [Commits](https://github.com/nonebot/plugin-orm/compare/v0.6.2...v0.6.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>
2024-02-06 05:52:35 +08:00
dependabot[bot]
3670ce7221 ⬆️ Bump ruff from 0.1.13 to 0.2.0 (#240)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.1.13 to 0.2.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.1.13...v0.2.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-02-06 05:51:32 +08:00
dependabot[bot]
101ed737ab ⬆️ Bump fastapi from 0.103.0 to 0.109.1 (#241)
Bumps [fastapi](https://github.com/tiangolo/fastapi) from 0.103.0 to 0.109.1.
- [Release notes](https://github.com/tiangolo/fastapi/releases)
- [Commits](https://github.com/tiangolo/fastapi/compare/0.103.0...0.109.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-06 05:51:11 +08:00
呵呵です
1611bf47fa 🔖 1.0.0.a13 2024-01-15 18:51:38 +08:00
dependabot[bot]
e084cdb145 ⬆️ Bump lxml from 5.0.0 to 5.1.0 (#230)
Bumps [lxml](https://github.com/lxml/lxml) from 5.0.0 to 5.1.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.0.0...lxml-5.1.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-01-15 18:51:05 +08:00
呵呵です
27258ab744 👽️ 修改茶服api地址 2024-01-15 18:40:20 +08:00
dependabot[bot]
07324825e6 ⬆️ Bump ruff from 0.1.11 to 0.1.13 (#231)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.1.11 to 0.1.13.
- [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.11...v0.1.13)

---
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-01-15 18:35:27 +08:00
dependabot[bot]
472becdfe0 ⬆️ Bump types-aiofiles from 23.2.0.0 to 23.2.0.20240106 (#229)
Bumps [types-aiofiles](https://github.com/python/typeshed) from 23.2.0.0 to 23.2.0.20240106.
- [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-01-15 12:15:09 +08:00
dependabot[bot]
bc87e4b16d ⬆️ Bump ruff from 0.1.9 to 0.1.11 (#228)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.1.9 to 0.1.11.
- [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.9...v0.1.11)

---
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-01-04 06:21:11 +08:00
dependabot[bot]
28e2a46303 ⬆️ Bump nonebot-plugin-orm from 0.6.0 to 0.6.2 (#227)
Bumps [nonebot-plugin-orm](https://github.com/nonebot/plugin-orm) from 0.6.0 to 0.6.2.
- [Release notes](https://github.com/nonebot/plugin-orm/releases)
- [Commits](https://github.com/nonebot/plugin-orm/compare/v0.6.0...v0.6.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>
2024-01-04 06:20:58 +08:00
1324015d58 🔖 1.0.0.a12 2024-01-03 09:52:25 +08:00
e6eae023e7 🐛 修复茶服pb数据处理错误的bug 2024-01-03 09:51:56 +08:00
67cfb07246 🔖 1.0.0.a11 2024-01-03 09:37:36 +08:00
12145a614f ⬇️ 错误的使用了Python3.11的新特性 2024-01-03 09:37:10 +08:00
0b07882a16 🐛 修复事件没有正确结束的bug 2024-01-03 09:27:26 +08:00
呵呵です
9073bf5d0b 🔖 1.0.0.a10 2024-01-03 09:02:22 +08:00
dependabot[bot]
f4dd5fe76f ⬆️ Bump nonebot-plugin-alconna from 0.34.1 to 0.35.1 (#226) 2024-01-03 09:02:20 +08:00
dependabot[bot]
1f44fc9884 ⬆️ Bump nonebot2 from 2.1.2 to 2.1.3 (#225) 2024-01-03 09:02:18 +08:00
dependabot[bot]
44dee7f200 ⬆️ Bump types-ujson from 5.8.0.1 to 5.9.0.0 (#224)
Bumps [types-ujson](https://github.com/python/typeshed) from 5.8.0.1 to 5.9.0.0.
- [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-01-03 09:02:14 +08:00
dependabot[bot]
dc5ade6ffc ⬆️ Bump pandas from 2.1.3 to 2.1.4 (#223)
Bumps [pandas](https://github.com/pandas-dev/pandas) from 2.1.3 to 2.1.4.
- [Release notes](https://github.com/pandas-dev/pandas/releases)
- [Commits](https://github.com/pandas-dev/pandas/compare/v2.1.3...v2.1.4)

---
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-01-03 09:02:12 +08:00
dependabot[bot]
05ce329976 ⬆️ Bump pandas-stubs from 2.1.1.230928 to 2.1.4.231227 (#222)
Bumps [pandas-stubs](https://github.com/pandas-dev/pandas-stubs) from 2.1.1.230928 to 2.1.4.231227.
- [Changelog](https://github.com/pandas-dev/pandas-stubs/blob/main/docs/release_procedure.md)
- [Commits](https://github.com/pandas-dev/pandas-stubs/compare/v2.1.1.230928...v2.1.4.231227)

---
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-01-03 09:02:07 +08:00
dependabot[bot]
43cabf2135 ⬆️ Bump nonebot-adapter-discord from 0.1.2 to 0.1.3 (#218) 2024-01-03 09:02:06 +08:00
dependabot[bot]
6767136850 ⬆️ Bump lxml from 4.9.3 to 5.0.0 (#221) 2024-01-03 09:02:06 +08:00
dependabot[bot]
65999b4625 ⬆️ Bump nonebot-adapter-satori from 0.8.0 to 0.8.1 (#217) 2024-01-03 09:02:06 +08:00
dependabot[bot]
9fde62ac9e ⬆️ Bump ujson from 5.8.0 to 5.9.0 (#219) 2024-01-03 09:02:05 +08:00
dependabot[bot]
c74d8b70aa ⬆️ Bump ruff from 0.1.6 to 0.1.9 (#220)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.1.6 to 0.1.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.1.6...v0.1.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-01-03 09:02:05 +08:00
dependabot[bot]
0e29b38f9d ⬆️ Bump playwright from 1.39.0 to 1.40.0 (#205) 2024-01-03 09:01:45 +08:00
dependabot[bot]
d040c7dca2 ⬆️ Bump viztracer from 0.16.0 to 0.16.1 (#211) 2024-01-03 09:00:06 +08:00
dependabot[bot]
68ace3a715 ⬆️ Bump httpx from 0.25.1 to 0.26.0 (#214) 2024-01-03 09:00:00 +08:00
dependabot[bot]
e63ac69e0f ⬆️ Bump mypy from 1.7.0 to 1.8.0 (#215) 2024-01-03 08:59:18 +08:00
4afda62782 添加状态码检查 2023-12-30 06:52:45 +08:00
呵呵です
abf4410a00 👽️ 适配 茶服 新赛季 (#216)
* 👽️ 适配 茶服 新赛季

* ✏️ 少个-

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

*  适配 kook 茶服查target

* 🐛 修复 onebotv11 查自己 找不到用户的bug

* 🐛 修复 茶服 查绑定 找不到用户的bug

*  kook 茶服查target 添加后备方案

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

*  适配 discord 茶服查target
2023-12-30 06:43:06 +08:00
88c2915251 🐛 修复 pydantic model 不能被正确反序列化的bug 2023-11-29 11:43:00 +08:00
546369241a 添加冗余 platform 字段 2023-11-29 11:41:48 +08:00
d59bccbd4d 细化异常 2023-11-29 11:29:46 +08:00
75a6989a7f 使用上下文管理器管理页面 2023-11-29 11:00:55 +08:00
ad635bd37d 🎨 修改错误处理逻辑 2023-11-29 10:59:58 +08:00
呵呵です
b6d63c9e7f 🐛 修复 io record 解析错误的bug (#207) 2023-11-23 20:07:57 +08:00
805da8cd36 🔖 1.0.0.a9 2023-11-22 18:34:07 +08:00
4a13d7807a 🐛 修复计算时间时区不正确的bug 2023-11-22 18:33:42 +08:00
7bbdeacc5e 🔖 1.0.0.a8 2023-11-22 16:11:57 +08:00
dependabot[bot]
782792e455 ⬆️ Bump nonebot-plugin-orm from 0.5.1 to 0.6.0 (#203) 2023-11-22 08:11:15 +00:00
dependabot[bot]
bd10549b4c ⬆️ Bump ruff from 0.1.5 to 0.1.6 (#202) 2023-11-22 08:02:43 +00:00
dependabot[bot]
035e6d4782 ⬆️ Bump nonebot-plugin-alconna from 0.33.3 to 0.33.6 (#201) 2023-11-22 08:02:33 +00:00
003e6619d8 iorank 指令不再去尝试更新数据 2023-11-22 15:58:55 +08:00
c0fa92df30 🚨 fix Incompatible overrides 2023-11-22 15:57:04 +08:00
7cdb0f3547 为 IO Rank 添加重试机制 2023-11-22 15:49:33 +08:00
b773fb44a1 ️ 为 IO 添加缓存 2023-11-22 13:22:18 +08:00
c75c6b73bd 🙈 更新.gitignore 2023-11-22 13:02:17 +08:00
67782c3156 添加依赖 aiocache 2023-11-22 13:01:41 +08:00
1e02858913 💥 🗃️ 将 pydantic 模型序列化后再存数据库 2023-11-21 20:47:56 +08:00
60605d0dca 🐛 修复 IO Z段位 不显示glicko和rd的bug 2023-11-21 13:58:39 +08:00
0d589450bd 将处理过程中的 dataclass 换成 pydantic 2023-11-21 00:50:32 +08:00
dependabot[bot]
2f144acf0c ⬆️ Bump nonebot-adapter-satori from 0.7.0 to 0.8.0 (#200)
Bumps [nonebot-adapter-satori](https://github.com/nonebot/adapter-satori) from 0.7.0 to 0.8.0.
- [Release notes](https://github.com/nonebot/adapter-satori/releases)
- [Commits](https://github.com/nonebot/adapter-satori/compare/v0.7.0...v0.8.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>
2023-11-17 01:49:57 +08:00
87e6a544a2 🔖 1.0.0.a7 2023-11-16 21:34:41 +08:00
74db1931fd 🐛 修复在多个无效参数时 Alconna 自动回复的bug 2023-11-16 21:33:24 +08:00
1ca6d1f86a 使用 zoneinfo 处理时区,并优化数据库查询逻辑 2023-11-16 16:27:06 +08:00
dependabot[bot]
7361789245 ⬆️ Bump nonebot-plugin-orm from 0.5.0 to 0.5.1 (#199) 2023-11-15 15:44:10 +00:00
fe69d8d2fe 🔖 1.0.0.a6 2023-11-15 14:37:41 +08:00
2737119865 🐛 修复 茶服 命令参数设置错误的bug 2023-11-15 14:36:54 +08:00
34a654b5df 🐛 修复只输入主命令时不发送帮助提示的bug 2023-11-15 14:34:27 +08:00
f9f39618a1 💚 修复Release CI 2023-11-15 14:02:22 +08:00
81a3c9cb79 🔖 1.0.0.a5 2023-11-15 11:44:23 +08:00
4a15c45e0a 💚 修复Release CI 2023-11-15 11:44:23 +08:00
e90ad53ee6 🐛 修复在事件响应器异常退出后 Recorder 继续执行的bug 2023-11-15 11:37:12 +08:00
0c968be163 避免 Alconna 在 ParamsUnmatched 时自动回复
🎨 将通用 handle 封装一下
2023-11-15 11:04:09 +08:00
bfadac4f79 添加配置项 请求超时时间 2023-11-15 00:43:12 +08:00
89f09cd66c 🔥 删除配置项 db_url 2023-11-15 00:37:04 +08:00
777703362e 🎨 先断开连接再删文件 2023-11-15 00:28:54 +08:00
ea5308877c 🎨 🔥 去除一个没什么用的函数 2023-11-15 00:26:55 +08:00
3cc93925a6 🐛 修复迁移旧数据库时拿错config字段的bug 2023-11-15 00:14:03 +08:00
e0bd0a9252 🐛 修复 io user info 解析错误的bug 2023-11-15 00:07:05 +08:00
d31ce48a18 💚 修复Release CI 2023-11-14 13:15:51 +08:00
7da38e0346 🔖 Release 1.0.0.a4 2023-11-14 13:11:46 +08:00
呵呵です
84368a16c3 👷 更改 dependabot 推送分支 2023-11-14 13:44:58 +08:00
6a10ede5ba 👷 更新Release CI 2023-11-14 12:56:06 +08:00
4c205e516f 🔥 去除命令解析失败时发送提醒 2023-11-14 12:54:42 +08:00
91 changed files with 4660 additions and 1452 deletions

View File

@@ -7,6 +7,6 @@ version: 2
updates:
- package-ecosystem: "pip" # See documentation for possible values
directory: "/" # Location of package manifests
target-branch: "dev"
target-branch: "main"
schedule:
interval: "daily"

View File

@@ -8,18 +8,42 @@ on:
jobs:
release:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: write
steps:
- uses: actions/checkout@v3
- name: Install poetry
run: pipx install poetry
shell: bash
- uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install Poetry
cache: "poetry"
- run: poetry install
shell: bash
- name: Get Version
id: version
run: |
pip install poetry
- name: Build
shell: bash
run: |
poetry install
poetry env use python
poetry publish --build -u ${{ secrets.USERNAME }} -p ${{ secrets.PASSWORD }} -n
echo "VERSION=$(poetry version -s)" >> $GITHUB_OUTPUT
echo "TAG_VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
echo "TAG_NAME=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
- name: Check Version
if: steps.version.outputs.VERSION != steps.version.outputs.TAG_VERSION
run: exit 1
- name: Build Package
run: poetry build
- name: Publish Package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
- 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
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

13
.gitignore vendored
View File

@@ -5,6 +5,17 @@ Untitled*
*copy*
.vscode
*dev*
*cache*
*_cache*
*backup*
*.pyc
node_modules
.prettier*
package.json
pnpm-lock.yaml
*.drawio.svg
package-lock.json
*Zone.Identifier
.env*
bot.py
TODO
*.fish

View File

@@ -20,3 +20,4 @@ __plugin_meta__ = PluginMetadata(
)
from . import game_data_processor # noqa: F401, E402
from .utils import host # noqa: F401, E402

View File

@@ -11,4 +11,4 @@ CACHE_PATH: Path = get_cache_dir('nonebot_plugin_tetris_stats')
class Config(BaseModel):
"""配置类"""
db_url: str = 'sqlite://data/nonebot_plugin_tetris_stats/data.db'
tetris_req_timeout: float = 30.0

View File

@@ -0,0 +1,51 @@
"""Rename field
迁移 ID: 09d4bb60160d
父迁移: b9d65badc713
创建时间: 2024-04-23 23:42:04.541672
"""
from __future__ import annotations
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
revision: str = '09d4bb60160d'
down_revision: str | Sequence[str] | None = 'b9d65badc713'
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('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())
batch_op.drop_index('ix_nonebot_plugin_tetris_stats_iorank_create_time')
op.create_index(
batch_op.f('ix_nonebot_plugin_tetris_stats_iorank_update_time'),
'nonebot_plugin_tetris_stats_iorank',
['update_time'],
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('nonebot_plugin_tetris_stats_iorank', schema=None) as batch_op:
batch_op.alter_column('update_time', new_column_name='create_time')
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_iorank_update_time'))
op.create_index(
'ix_nonebot_plugin_tetris_stats_iorank_create_time',
'nonebot_plugin_tetris_stats_iorank',
['create_time'],
unique=False,
)
# ### end Alembic commands ###

View File

@@ -0,0 +1,43 @@
"""add field
迁移 ID: 0d50142b780f
父迁移: 09d4bb60160d
创建时间: 2024-04-24 14:55:08.064098
"""
from __future__ import annotations
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
revision: str = '0d50142b780f'
down_revision: str | Sequence[str] | None = '09d4bb60160d'
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('nonebot_plugin_tetris_stats_iorank', schema=None) as batch_op:
batch_op.add_column(sa.Column('file_hash', sa.String(length=128), nullable=True))
batch_op.create_index(
batch_op.f('ix_nonebot_plugin_tetris_stats_iorank_file_hash'), ['file_hash'], 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('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'))
batch_op.drop_column('file_hash')
# ### end Alembic commands ###

View File

@@ -0,0 +1,65 @@
"""Add redundant platform field
迁移 ID: 6c3206f90cc3
父迁移: 9f6582279ce2
创建时间: 2023-11-26 20:15:56.033892
"""
from __future__ import annotations
from collections.abc import Sequence
from alembic import op
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session
from ujson import dumps, loads
revision: str = '6c3206f90cc3'
down_revision: str | Sequence[str] | None = '9f6582279ce2'
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade(name: str = '') -> None:
if name:
return
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(connection) as session:
for row in session.query(HistoricalData):
platform = row.game_platform
game_user = loads(row.game_user)
processed_data = loads(row.processed_data)
game_user['platform'] = platform
processed_data['platform'] = platform
row.game_user = dumps(game_user)
row.processed_data = dumps(processed_data)
session.add(row)
session.commit()
def downgrade(name: str = '') -> None:
if name:
return
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(connection) as session:
for row in session.query(HistoricalData):
game_user = loads(row.game_user)
processed_data = loads(row.processed_data)
game_user.pop('platform', None)
processed_data.pop('platform', None)
row.game_user = dumps(game_user)
row.processed_data = dumps(processed_data)
session.add(row)
session.commit()

View File

@@ -64,7 +64,7 @@ def upgrade(name: str = '') -> None:
if name:
return
try:
db_path = Path(config.db_path)
db_path = Path(config.db_url)
except AttributeError:
db_path = Path('data/nonebot_plugin_tetris_stats/data.db')
if db_path.exists() is False:
@@ -84,7 +84,7 @@ def upgrade(name: str = '') -> None:
raise RuntimeError('nonebot_plugin_tetris_stats: 请先安装 0.4.4 版本完成迁移之后再升级')
logger.info('nonebot_plugin_tetris_stats: 发现来自老版本的数据, 正在迁移...')
migrate_old_data(connection)
db_path.unlink()
db_path.unlink()
def downgrade(name: str = '') -> None:

View File

@@ -0,0 +1,112 @@
"""Recreate HistoricalData
迁移 ID: 9f6582279ce2
父迁移: 9cd1647db502
创建时间: 2023-11-21 08:35:50.393246
"""
from __future__ import annotations
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import sqlite
from nonebot_plugin_tetris_stats.db.models import PydanticType
revision: str = '9f6582279ce2'
down_revision: str | Sequence[str] | None = '9cd1647db502'
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('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')
op.drop_table('nonebot_plugin_tetris_stats_historicaldata')
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', PydanticType(list), nullable=False),
sa.Column('processed_data', PydanticType(list), nullable=False),
sa.Column('finish_time', sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint('id', name=op.f('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(
batch_op.f('ix_nonebot_plugin_tetris_stats_historicaldata_command_type'), ['command_type'], unique=False
)
batch_op.create_index(
batch_op.f('ix_nonebot_plugin_tetris_stats_historicaldata_game_platform'), ['game_platform'], unique=False
)
batch_op.create_index(
batch_op.f('ix_nonebot_plugin_tetris_stats_historicaldata_source_account'), ['source_account'], unique=False
)
batch_op.create_index(
batch_op.f('ix_nonebot_plugin_tetris_stats_historicaldata_source_type'), ['source_type'], 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('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'))
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_historicaldata_source_account'))
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_historicaldata_game_platform'))
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_historicaldata_command_type'))
op.drop_table('nonebot_plugin_tetris_stats_historicaldata')
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.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_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
)
# ### end Alembic commands ###

View File

@@ -0,0 +1,38 @@
"""Del old TOS bind data
迁移 ID: b9d65badc713
父迁移: 6c3206f90cc3
创建时间: 2023-12-30 00:27:40.991704
"""
from __future__ import annotations
from collections.abc import Sequence
from alembic import op
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session
revision: str = 'b9d65badc713'
down_revision: str | Sequence[str] | None = '6c3206f90cc3'
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade(name: str = '') -> None:
if name:
return
Base = automap_base() # noqa: N806
connection = op.get_bind()
Base.prepare(autoload_with=connection)
Bind = Base.classes.nonebot_plugin_tetris_stats_bind # noqa: N806
with Session(connection) as session:
session.query(Bind).filter(Bind.game_platform == 'TOS').delete()
session.commit()
def downgrade(name: str = '') -> None:
if name:
return

View File

@@ -1,3 +1,5 @@
from enum import StrEnum, auto
from nonebot_plugin_orm import AsyncSession
from sqlalchemy import select
@@ -5,6 +7,11 @@ from ..utils.typing import GameType
from .models import Bind
class BindStatus(StrEnum):
SUCCESS = auto()
UPDATE = auto()
async def query_bind_info(
session: AsyncSession,
chat_platform: str,
@@ -27,7 +34,7 @@ async def create_or_update_bind(
chat_account: str,
game_platform: GameType,
game_account: str,
) -> str:
) -> BindStatus:
bind = await query_bind_info(
session=session,
chat_platform=chat_platform,
@@ -42,9 +49,9 @@ async def create_or_update_bind(
game_account=game_account,
)
session.add(bind)
message = '绑定成功'
message = BindStatus.SUCCESS
else:
bind.game_account = game_account
message = '更新绑定成功'
message = BindStatus.UPDATE
await session.commit()
return message

View File

@@ -1,14 +1,42 @@
from collections.abc import Callable, Sequence
from datetime import datetime
from typing import Any
from nonebot.adapters import Message
from nonebot.compat import type_validate_json
from nonebot_plugin_orm import Model
from sqlalchemy import JSON, DateTime, PickleType, String
from pydantic import BaseModel, ValidationError
from sqlalchemy import JSON, DateTime, Dialect, PickleType, String, TypeDecorator
from sqlalchemy.orm import Mapped, MappedAsDataclass, mapped_column
from ..game_data_processor import ProcessedData, User
from ..game_data_processor.schemas import BaseProcessedData, BaseUser
from ..utils.typing import CommandType, GameType
class PydanticType(TypeDecorator):
impl = JSON
def __init__(self, get_model: Callable[[], Sequence[type[BaseModel]]], *args: Any, **kwargs: Any): # noqa: ANN401
self.get_model = get_model
super().__init__(*args, **kwargs)
def process_bind_param(self, value: Any | None, dialect: Dialect) -> str: # noqa: ANN401
# 将 Pydantic 模型实例转换为 JSON
if isinstance(value, tuple(self.get_model())):
return value.json() # type: ignore[union-attr]
raise TypeError
def process_result_value(self, value: Any | None, dialect: Dialect) -> BaseModel: # noqa: ANN401
# 将 JSON 转换回 Pydantic 模型实例
if isinstance(value, str | bytes):
for i in self.get_model():
try:
return type_validate_json(i, value)
except ValidationError: # noqa: PERF203
...
raise TypeError
class Bind(MappedAsDataclass, Model):
id: Mapped[int] = mapped_column(init=False, primary_key=True)
chat_platform: Mapped[str] = mapped_column(String(32), index=True)
@@ -28,6 +56,8 @@ class HistoricalData(MappedAsDataclass, Model):
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[User] = mapped_column(PickleType, init=False)
processed_data: Mapped[ProcessedData] = mapped_column(PickleType, 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)

View File

@@ -1,26 +1,20 @@
from abc import ABC, abstractmethod
from dataclasses import dataclass
from datetime import UTC, datetime
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
@dataclass
class User:
"""游戏用户"""
@dataclass
class RawResponse:
"""原始请求数据"""
@dataclass
class ProcessedData:
"""处理/验证后的数据"""
from ..utils.recorder import Recorder # noqa: E402 避免循环导入
UTC = timezone.utc
class Processor(ABC):
@@ -49,7 +43,14 @@ class Processor(ABC):
raise NotImplementedError
@abstractmethod
async def handle_bind(self, platform: str, account: str) -> str:
async def handle_bind(
self,
platform: str,
account: str,
bot_info: UserInfo,
*args: Any, # noqa: ANN401
**kwargs: Any, # noqa: ANN401
) -> UniMessage:
"""处理绑定消息"""
raise NotImplementedError
@@ -65,6 +66,9 @@ class Processor(ABC):
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
@@ -75,6 +79,24 @@ class Processor(ABC):
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,

View File

@@ -1,23 +1,28 @@
from datetime import timedelta
from datetime import datetime, timedelta, timezone
from zoneinfo import ZoneInfo
from arclet.alconna import Alconna, Arg, ArgFlag, Args, CommandMeta, Option
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 AlcMatches, At, on_alconna
from nonebot_plugin_alconna import At, on_alconna
from nonebot_plugin_orm import get_session
from sqlalchemy import select
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 MessageFormatError, NeedCatchError
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, check_rank_data, identify_user_info
from .processor import Processor, User, identify_user_info
from .typing import Rank
UTC = timezone.utc
alc = on_alconna(
Alconna(
'io',
@@ -65,6 +70,7 @@ alc = on_alconna(
dest='rank',
help_text='查询 IO 段位信息',
),
Arg('other', AllParam, flags=[ArgFlag.HIDDEN, ArgFlag.OPTIONAL]),
meta=CommandMeta(
description='查询 TETR.IO 的信息',
example='io绑定scdhh\nio查我\niorankx',
@@ -81,16 +87,16 @@ alc.shortcut('fkosk', {'command': 'io查', 'args': ['我']})
@alc.assign('bind')
async def _(bot: Bot, event: Event, matcher: Matcher, account: User):
proc = Processor(
event_id=id(event),
user=account,
command_args=[],
)
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 matcher.finish(await proc.handle_bind(platform=get_platform(bot), account=event.get_user_id()))
await (
await proc.handle_bind(platform=get_platform(bot), account=event.get_user_id(), bot_info=bot_info)
).send()
except NeedCatchError as e:
await matcher.finish(str(e))
await matcher.send(str(e))
raise HandleNotFinishedError from e
await matcher.finish()
@alc.assign('query')
@@ -113,7 +119,8 @@ async def _(bot: Bot, event: Event, matcher: Matcher, target: At | Me):
try:
await matcher.finish(message + await proc.handle_query())
except NeedCatchError as e:
await matcher.finish(str(e))
await matcher.send(str(e))
raise HandleNotFinishedError from e
@alc.assign('query')
@@ -126,25 +133,37 @@ async def _(event: Event, matcher: Matcher, account: User):
try:
await matcher.finish(await proc.handle_query())
except NeedCatchError as e:
await matcher.finish(str(e))
await matcher.send(str(e))
raise HandleNotFinishedError from e
@alc.assign('rank')
async def _(event: Event, matcher: Matcher, rank: Rank):
async def _(matcher: Matcher, rank: Rank):
if rank == 'z':
await matcher.finish('暂不支持查询未知段位')
try:
await check_rank_data()
except NeedCatchError as e:
await matcher.finish(str(f'段位信息获取失败\n{e}'))
async with get_session() as session:
data = (
await session.scalars(select(IORank).where(IORank.rank == rank).order_by(IORank.id.desc()).limit(5))
).all()
latest_data = data[0]
message = f'{rank.upper()} 段 分数线 {latest_data.tr_line:.2f} TR, {latest_data.player_count} 名玩家\n'
if len(data) > 1:
message += f'对比 {(latest_data.create_time-data[-1].create_time).total_seconds()/3600:.2f} 小时前趋势: {f"{difference:.2f}" if (difference:=latest_data.tr_line-data[-1].tr_line) > 0 else f"{-difference:.2f}" if difference < 0 else ""}'
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)
@@ -169,19 +188,9 @@ async def _(event: Event, matcher: Matcher, rank: Rank):
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.create_time+timedelta(hours=8)).strftime("%Y-%m-%d %H:%M:%S")}'
f'数据更新时间: {latest_data.update_time.replace(tzinfo=UTC).astimezone(ZoneInfo("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")}'
)
await matcher.finish(message)
@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:
await matcher.finish(
f'{matches.error_info!r}\n' if matches.error_info is not None else '' + '输入"io --help"查看帮助'
)
add_default_handlers(alc)

View File

@@ -0,0 +1,30 @@
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,7 +1,8 @@
from ...utils.typing import GameType
from typing import Literal
from .typing import Rank
GAME_TYPE: GameType = 'IO'
GAME_TYPE: Literal['IO'] = 'IO'
BASE_URL = 'https://ch.tetr.io/api/'
RANK_PERCENTILE: dict[Rank, float] = {
'x': 1,

View File

@@ -1,4 +1,4 @@
from datetime import UTC, datetime
from datetime import datetime, timezone
from nonebot_plugin_orm import Model
from sqlalchemy import JSON, DateTime, String
@@ -6,6 +6,8 @@ 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)
@@ -21,9 +23,8 @@ class IORank(MappedAsDataclass, Model):
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)
create_time: Mapped[datetime] = mapped_column(
update_time: Mapped[datetime] = mapped_column(
DateTime,
default=lambda: datetime.now(tz=UTC),
index=True,
init=False,
)
file_hash: Mapped[str | None] = mapped_column(String(128), index=True)

View File

@@ -1,63 +1,55 @@
from collections import defaultdict
from collections.abc import Callable
from dataclasses import asdict, dataclass
from datetime import UTC, datetime, timedelta
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 pydantic import parse_raw_as
from nonebot_plugin_userinfo import UserInfo as NBUserInfo # type: ignore[import-untyped]
from sqlalchemy import select
from zstandard import ZstdCompressor
from ...db import create_or_update_bind
from ...db import BindStatus, create_or_update_bind
from ...utils.avatar import get_avatar
from ...utils.exception import MessageFormatError, RequestError, WhatTheFuckError
from ...utils.request import Request, splice_url
from ...utils.typing import GameType
from .. import ProcessedData as ProcessedDataMeta
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 .. import RawResponse as RawResponseMeta
from .. import User as UserMeta
from .cache import Cache
from .constant import BASE_URL, GAME_TYPE, RANK_PERCENTILE
from .model import IORank
from .schemas.league_all import FailedModel as LeagueAllFailed
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 FailedModel as InfoFailed
from .schemas.user_info import (
NeverPlayedLeague,
NeverRatedLeague,
UserInfo,
)
from .schemas.user_info import NeverPlayedLeague, NeverRatedLeague, UserInfo
from .schemas.user_info import SuccessModel as InfoSuccess
from .schemas.user_records import FailedModel as RecordsFailed
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()
@dataclass
class User(UserMeta):
ID: str | None = None
name: str | None = None
@dataclass
class RawResponse(RawResponseMeta):
user_info: bytes | None = None
user_records: bytes | None = None
@dataclass
class ProcessedData(ProcessedDataMeta):
user_info: InfoSuccess | None = None
user_records: RecordsSuccess | None = None
def identify_user_info(info: str) -> User | MessageFormatError:
if match(r'^[a-f0-9]{24}$', info):
return User(ID=info)
@@ -77,23 +69,46 @@ class Processor(ProcessorMeta):
self.processed_data = ProcessedData()
@property
def game_platform(self) -> GameType:
def game_platform(self) -> Literal['IO']:
return GAME_TYPE
async def handle_bind(self, platform: str, account: str) -> str:
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:
return await create_or_update_bind(
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
async def handle_query(self) -> str:
"""处理查询消息"""
@@ -113,10 +128,10 @@ class Processor(ProcessorMeta):
async def get_user_info(self) -> InfoSuccess:
"""获取用户数据"""
if self.processed_data.user_info is None:
self.raw_response.user_info = await Request.request(
self.raw_response.user_info = await Cache.get(
splice_url([BASE_URL, 'users/', f'{self.user.ID or self.user.name}'])
)
user_info: UserInfo = parse_raw_as(UserInfo, self.raw_response.user_info) # type: ignore[arg-type]
user_info: UserInfo = type_validate_json(UserInfo, self.raw_response.user_info) # type: ignore[arg-type]
if isinstance(user_info, InfoFailed):
raise RequestError(f'用户信息请求错误:\n{user_info.error}')
self.processed_data.user_info = user_info
@@ -125,20 +140,10 @@ class Processor(ProcessorMeta):
async def get_user_records(self) -> RecordsSuccess:
"""获取Solo数据"""
if self.processed_data.user_records is None:
self.raw_response.user_records = await Request.request(
splice_url(
[
BASE_URL,
'users/',
f'{self.user.ID or self.user.name}/',
'records',
]
)
)
user_records: UserRecords = parse_raw_as(
UserRecords, # type: ignore[arg-type]
self.raw_response.user_records,
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, RecordsFailed):
raise RequestError(f'用户Solo数据请求错误:\n{user_records.error}')
self.processed_data.user_records = user_records
@@ -155,12 +160,13 @@ class Processor(ProcessorMeta):
else:
if isinstance(league, NeverRatedLeague):
ret_message += f'用户 {user_name} 暂未完成定级赛, 最近十场的数据:'
elif 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})'
)
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 )"
@@ -185,13 +191,14 @@ class Processor(ProcessorMeta):
@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 = parse_raw_as(
league_all: LeagueAll = type_validate_json(
LeagueAll, # type: ignore[arg-type]
await Request.request(splice_url([BASE_URL, 'users/lists/league/all'])),
(data := await Cache.get(splice_url([BASE_URL, 'users/lists/league/all']))),
)
if isinstance(league_all, LeagueAllFailed):
raise RequestError(f'用户Solo数据请求错误:\n{league_all.error}')
raise RequestError(f'排行榜数据请求错误:\n{league_all.error}')
def pps(user: LeagueAllUser) -> float:
return user.league.pps
@@ -214,7 +221,11 @@ async def get_io_rank_data() -> None:
sort: Callable[[list[LeagueAllUser], Callable[[LeagueAllUser], float]], LeagueAllUser],
) -> tuple[dict[str, str], float]:
user = sort(users, field)
return asdict(User(ID=user.id, name=user.username)), field(user)
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)
@@ -239,6 +250,8 @@ async def get_io_rank_data() -> None:
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:
@@ -247,8 +260,8 @@ async def get_io_rank_data() -> None:
@driver.on_startup
async def check_rank_data() -> None:
async def _() -> None:
async with get_session() as session:
latest_time = await session.scalar(select(IORank.create_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()
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

@@ -28,20 +28,20 @@ class SuccessModel(BaseSuccessModel):
league: League
supporter: bool
verified: bool
country: str | None
country: str | None = None
class InvalidUser(BaseModel):
class League(BaseModel):
gamesplayed: int
gameswon: int
rating: float
glicko: float | None
rd: float | None
glicko: float | None = None
rd: float | None = None
rank: Rank
bestrank: Rank
apm: float | None
pps: float | None
vs: float | None
apm: float | None = None
pps: float | None = None
vs: float | None = None
decaying: bool
id: str = Field(..., alias='_id')

View File

@@ -0,0 +1,21 @@
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

@@ -0,0 +1,17 @@
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

@@ -14,7 +14,7 @@ class SuccessModel(BaseSuccessModel):
class Badge(BaseModel):
id: str
label: str
ts: datetime | None
ts: datetime | None = None
class NeverPlayedLeague(BaseModel):
gamesplayed: Literal[0]
@@ -29,9 +29,9 @@ class SuccessModel(BaseSuccessModel):
prev_at: Literal[-1]
percentile: Literal[-1]
percentile_rank: Literal['z']
apm: None
pps: None
vs: None
apm: None = Field(None)
pps: None = Field(None)
vs: None = Field(None)
decaying: bool
class NeverRatedLeague(BaseModel):
@@ -60,8 +60,8 @@ class SuccessModel(BaseSuccessModel):
bestrank: Rank
standing: int
standing_local: int
next_rank: Rank | None
prev_rank: Rank | None
next_rank: Rank | None = None
prev_rank: Rank | None = None
next_at: int
prev_at: int
percentile: float
@@ -70,7 +70,7 @@ class SuccessModel(BaseSuccessModel):
rd: float
apm: float
pps: float
vs: float | None
vs: float | None = None
decaying: bool
class Connections(BaseModel):
@@ -78,41 +78,41 @@ class SuccessModel(BaseSuccessModel):
id: str
username: str
discord: Discord | None
discord: Discord | None = None
class Distinguishment(BaseModel):
type: str # noqa: A003
type: str
id: str = Field(..., alias='_id')
username: str
role: Literal['anon', 'user', 'bot', 'halfmod', 'mod', 'admin', 'sysop', 'banned']
ts: datetime | None
botmaster: str | None
ts: datetime | None = None
botmaster: str | None = None
badges: list[Badge]
xp: float
gamesplayed: int
gameswon: int
gametime: float
country: str | None
badstanding: bool | None
supporter: bool | None # osk说是必有, 但实际上不是 fk osk
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
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
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
bio: str | None = None
connections: Connections
friend_count: int
distinguishment: Distinguishment | None
friend_count: int | None = None
distinguishment: Distinguishment | None = None
user: User

View File

@@ -12,14 +12,14 @@ class EndContext(BaseModel):
zero: bool
locked: bool
prev: int
frameoffset: int
frameoffset: int | None = None
class Clears(BaseModel):
singles: int
doubles: int
triples: int
quads: int
pentas: int | None
pentas: int | None = None
realtspins: int
minitspins: int
minitspinsingles: int
@@ -33,7 +33,7 @@ class EndContext(BaseModel):
class Garbage(BaseModel):
sent: int
received: int
attack: int | None
attack: int | None = None
cleared: int
class Finesse(BaseModel):
@@ -46,18 +46,18 @@ class EndContext(BaseModel):
level_lines: int
level_lines_needed: int
inputs: int
holds: int | None
holds: int | None = None
time: Time
score: int
zenlevel: int
zenprogress: int
zenlevel: int | None = None
zenprogress: int | None = None
level: int
combo: int
currentcombopower: int | None # WTF
currentcombopower: int | None = None # WTF
topcombo: int
btb: int
topbtb: int
currentbtbchainpower: int | None # WTF * 2
currentbtbchainpower: int | None = None # WTF * 2
tspins: int
piecesplaced: int
clears: Clears
@@ -79,7 +79,7 @@ class BaseModeRecord(BaseModel):
replayid: str
user: User
ts: datetime
ismulti: bool | None
ismulti: bool | None = None
endcontext: EndContext
class MultiRecord(BaseModel):
@@ -92,21 +92,19 @@ class BaseModeRecord(BaseModel):
replayid: str
user: User
ts: datetime
ismulti: bool | None
ismulti: bool | None = None
endcontext: list[EndContext]
record: SoloRecord | MultiRecord | None
rank: int | None
record: SoloRecord | MultiRecord | None = None
rank: int | None = None
class SuccessModel(BaseSuccessModel):
class Data(BaseModel):
class Records(BaseModel):
class Sprint(BaseModeRecord):
...
class Sprint(BaseModeRecord): ...
class Blitz(BaseModeRecord):
...
class Blitz(BaseModeRecord): ...
sprint: Sprint = Field(..., alias='40l')
blitz: Blitz

View File

@@ -0,0 +1,31 @@
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,13 +1,15 @@
from arclet.alconna import Alconna, Arg, ArgFlag, Args, CommandMeta, Option
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 AlcMatches, At, on_alconna
from nonebot_plugin_alconna import At, on_alconna
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 MessageFormatError, NeedCatchError
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
@@ -51,6 +53,7 @@ alc = on_alconna(
dest='query',
help_text='查询 TOP 游戏信息',
),
Arg('other', AllParam, flags=[ArgFlag.HIDDEN, ArgFlag.OPTIONAL]),
meta=CommandMeta(
description='查询 TetrisOnline波兰服 的信息',
example='top绑定scdhh\ntop查我',
@@ -65,16 +68,29 @@ alc = on_alconna(
@alc.assign('bind')
async def _(bot: Bot, event: Event, matcher: Matcher, account: User):
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 matcher.finish(await proc.handle_bind(platform=get_platform(bot), account=event.get_user_id()))
await (
await proc.handle_bind(
platform=get_platform(bot), account=event.get_user_id(), bot_info=bot_info, user_info=user_info
)
).send()
except NeedCatchError as e:
await matcher.finish(str(e))
await matcher.send(str(e))
raise HandleNotFinishedError from e
await matcher.finish()
@alc.assign('query')
@@ -97,7 +113,8 @@ async def _(bot: Bot, event: Event, matcher: Matcher, target: At | Me):
try:
await matcher.finish(message + await proc.handle_query())
except NeedCatchError as e:
await matcher.finish(str(e))
await matcher.send(str(e))
raise HandleNotFinishedError from e
@alc.assign('query')
@@ -110,17 +127,8 @@ async def _(event: Event, matcher: Matcher, account: User):
try:
await matcher.finish(await proc.handle_query())
except NeedCatchError as e:
await matcher.finish(str(e))
await matcher.send(str(e))
raise HandleNotFinishedError from e
@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:
await matcher.finish(
f'{matches.error_info!r}\n' if matches.error_info is not None else '' + '输入"top --help"查看帮助'
)
add_default_handlers(alc)

View File

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

View File

@@ -2,37 +2,36 @@ from contextlib import suppress
from dataclasses import dataclass
from io import StringIO
from re import match
from typing import NoReturn
from urllib.parse import urlencode
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 ...db import create_or_update_bind
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.typing import GameType
from .. import ProcessedData as ProcessedDataMeta
from ...utils.screenshot import screenshot
from .. import Processor as ProcessorMeta
from .. import RawResponse as RawResponseMeta
from .. import User as UserMeta
from ..schemas import BaseUser
from .constant import BASE_URL, GAME_TYPE
from .schemas.response import ProcessedData, RawResponse
@dataclass
class User(UserMeta):
class User(BaseUser):
platform: Literal['TOP'] = GAME_TYPE
name: str
@dataclass
class RawResponse(RawResponseMeta):
user_profile: bytes | None = None
@dataclass
class ProcessedData(ProcessedDataMeta):
user_profile: str | None = None
@property
def unique_identifier(self) -> str:
return self.name
@dataclass
@@ -64,21 +63,41 @@ class Processor(ProcessorMeta):
self.processed_data = ProcessedData()
@property
def game_platform(self) -> GameType:
def game_platform(self) -> Literal['TOP']:
return GAME_TYPE
async def handle_bind(self, platform: str, account: str) -> str:
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:
return await create_or_update_bind(
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
async def handle_query(self) -> str:
"""处理查询消息"""
@@ -94,10 +113,9 @@ class Processor(ProcessorMeta):
self.processed_data.user_profile = self.raw_response.user_profile.decode()
return self.processed_data.user_profile
async def check_user(self) -> None | NoReturn:
async def check_user(self) -> None:
if 'user not found!' in await self.get_user_profile():
raise RequestError('用户不存在!')
return None
async def get_user_name(self) -> str:
"""获取用户名"""

View File

@@ -0,0 +1,16 @@
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,13 +1,17 @@
from arclet.alconna import Alconna, Arg, ArgFlag, Args, CommandMeta, Option
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 AlcMatches, At, on_alconna
from nonebot_plugin_alconna import At, on_alconna
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 MessageFormatError, NeedCatchError
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
@@ -52,6 +56,7 @@ alc = on_alconna(
dest='query',
help_text='查询 茶服 游戏信息',
),
Arg('other', AllParam, flags=[ArgFlag.HIDDEN, ArgFlag.OPTIONAL]),
meta=CommandMeta(
description='查询 TetrisOnline茶服 的信息',
example='茶服查我',
@@ -64,42 +69,82 @@ alc = on_alconna(
aliases={'tos', 'TOS'},
)
try:
from nonebot.adapters.onebot.v11 import GROUP, MessageEvent
from nonebot.adapters.onebot.v11 import Bot as OB11Bot
@alc.assign('bind')
async def _(event: MessageEvent, matcher: Matcher):
await matcher.finish('QQ 平台无需绑定')
async def finish_special_query(matcher: Matcher, proc: Processor) -> NoReturn:
try:
await matcher.finish(await proc.handle_query())
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: MessageEvent, matcher: Matcher, target: At | Me):
if event.is_tome() and await GROUP(bot, event):
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=target.target if isinstance(target, At) else event.get_user_id()),
user=User(teaid=f'onebot-{target.target}' if isinstance(target, At) else f'onebot-{event.get_user_id()}'),
command_args=[],
)
try:
await matcher.finish(await proc.handle_query())
except NeedCatchError as e:
await matcher.finish(str(e))
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):
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 matcher.finish(await proc.handle_bind(platform=get_platform(bot), account=event.get_user_id()))
await (
await proc.handle_bind(platform=get_platform(bot), account=event.get_user_id(), bot_info=bot_info)
).send()
except NeedCatchError as e:
await matcher.finish(str(e))
await matcher.send(str(e))
raise HandleNotFinishedError from e
await matcher.finish()
@alc.assign('query')
@@ -116,13 +161,14 @@ async def _(bot: Bot, event: Event, matcher: Matcher, target: At | Me):
message = '* 由于无法验证绑定信息, 不能保证查询到的用户为本人\n'
proc = Processor(
event_id=id(event),
user=User(name=bind.game_account),
user=User(teaid=bind.game_account),
command_args=[],
)
try:
await matcher.finish(message + await proc.handle_query())
except NeedCatchError as e:
await matcher.finish(str(e))
await matcher.send(str(e))
raise HandleNotFinishedError from e
@alc.assign('query')
@@ -135,17 +181,8 @@ async def _(event: Event, matcher: Matcher, account: User):
try:
await matcher.finish(await proc.handle_query())
except NeedCatchError as e:
await matcher.finish(str(e))
await matcher.send(str(e))
raise HandleNotFinishedError from e
@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:
await matcher.finish(
f'{matches.error_info!r}\n' if matches.error_info is not None else '' + '输入"茶服 --help"查看帮助'
)
add_default_handlers(alc)

View File

@@ -1,4 +1,10 @@
from ...utils.typing import GameType
from typing import Literal
GAME_TYPE: GameType = 'TOS'
BASE_URL = 'https://teatube.cn:8888/'
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,41 +1,41 @@
from dataclasses import dataclass
from re import match
from typing import Any
from urllib.parse import urlencode
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 pydantic import parse_raw_as
from nonebot_plugin_userinfo import UserInfo as NBUserInfo # type: ignore[import-untyped]
from ...db import create_or_update_bind
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.typing import GameType
from .. import ProcessedData as ProcessedDataMeta
from ...utils.screenshot import screenshot
from .. import Processor as ProcessorMeta
from .. import RawResponse as RawResponseMeta
from .. import User as UserMeta
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
@dataclass
class User(UserMeta):
class User(BaseUser):
platform: Literal['TOS'] = GAME_TYPE
teaid: str | None = None
name: str | None = None
@dataclass
class RawResponse(RawResponseMeta):
user_profile: dict[frozenset[tuple[str, Any]], bytes]
user_info: bytes | None = None
@dataclass
class ProcessedData(ProcessedDataMeta):
user_profile: dict[frozenset[tuple[str, Any]], UserProfile]
user_info: InfoSuccess | None = None
@property
def unique_identifier(self) -> str:
if self.teaid is None:
raise ValueError('不完整的User!')
return self.teaid
@dataclass
@@ -60,7 +60,7 @@ def identify_user_info(info: str) -> User | MessageFormatError:
and 2 <= len(info) <= 18 # noqa: PLR2004
):
return User(name=info)
if info.isdigit():
if info.startswith(('onebot-', 'qqguild-', 'kook-', 'discord-')) and info.split('-', maxsplit=1)[1].isdigit():
return User(teaid=info)
return MessageFormatError('用户名/QQ号不合法')
@@ -76,23 +76,42 @@ class Processor(ProcessorMeta):
self.processed_data = ProcessedData(user_profile={})
@property
def game_platform(self) -> GameType:
def game_platform(self) -> Literal['TOS']:
return GAME_TYPE
async def handle_bind(self, platform: str, account: str) -> str:
async def handle_bind(self, platform: str, account: str, bot_info: NBUserInfo) -> UniMessage:
"""处理绑定消息"""
self.command_type = 'bind'
await self.get_user()
if self.user.name is None:
raise # FIXME: 不知道怎么才能把这类型给变过来了
async with get_session() as session:
return await create_or_update_bind(
bind_status = await create_or_update_bind(
session=session,
chat_platform=platform,
chat_account=account,
game_platform=GAME_TYPE,
game_account=self.user.name,
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
async def handle_query(self) -> str:
"""处理查询消息"""
@@ -113,45 +132,60 @@ class Processor(ProcessorMeta):
"""获取用户信息"""
if self.processed_data.user_info is None:
if self.user.teaid is not None:
url = splice_url(
[
BASE_URL,
'getTeaIdInfo',
f'?{urlencode({"teaId":self.user.teaid})}',
]
)
url = [
splice_url(
[
i,
'getTeaIdInfo',
f'?{urlencode({"teaId":self.user.teaid})}',
]
)
for i in BASE_URL
]
else:
url = splice_url(
[
BASE_URL,
'getUsernameInfo',
f'?{urlencode({"username":self.user.name})}',
]
)
self.raw_response.user_info = await Request.request(url)
user_info: UserInfo = parse_raw_as(UserInfo, self.raw_response.user_info) # type: ignore[arg-type]
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, Any] | None = None) -> UserProfile:
async def get_user_profile(self, other_parameter: dict[str, str | bytes] | None = None) -> UserProfile:
"""获取用户数据"""
if other_parameter is None:
other_parameter = {}
fset = frozenset(other_parameter.items())
if self.processed_data.user_profile.get(fset) is None:
self.raw_response.user_profile[fset] = await Request.request(
splice_url(
[
BASE_URL,
'getProfile',
f'?{urlencode({"id":self.user.teaid or self.user.name},**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[fset] = UserProfile.parse_raw(self.raw_response.user_profile[fset])
return self.processed_data.user_profile[fset]
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:
"""获取游戏数据"""
@@ -211,11 +245,7 @@ class Processor(ProcessorMeta):
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 # noqa: PLR2004
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 ''
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 message

View File

@@ -0,0 +1,20 @@
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

@@ -0,0 +1,3 @@
from pathlib import Path
path = Path(__file__).absolute().parent

View File

@@ -0,0 +1,43 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<link href="../../static/css/bind.css" rel="stylesheet" />
</head>
<body>
<div id="background">
<div id="main-content">
<div id="bind-subject">
<div id="bind-icons">
<img id="user-avatar" src="{{ user_avatar }}" />
<img id="state" src="../../static/static/bind/{{ state }}.svg" />
<img id="bot-avatar" src="{{ bot_avatar }}" />
</div>
<div id="command-result">
已将您在
<p id="game-type">{{ game_type }}</p>
上的账号
<br />
<p id="user-name">{{ user_name }}</p>
<br />
{% if state == 'success' %} 成功验证并绑定至
<p id="bot-name">{{ bot_name }}.</p>
{% elif state == 'unverified'%} 绑定至
<p id="bot-name">{{ bot_name }}</p>
, 但尚未通过验证. {% elif state == 'unknown' %} 绑定至
<p id="bot-name">{{ bot_name }}</p>
,<br />但是
<p id="bot-name">{{ bot_name }}</p>
暂时无法验证您的身份. {% elif state == 'unlink' %} 成功从
<p id="bot-name">{{ bot_name }}</p>
解绑. {% endif %}
</div>
</div>
<div id="extra-info">您可以输入 “{{ command }}” 命令来查找您在该平台上的统计数据.</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,118 @@
@font-face {
font-family: 'SourceHanSansSC-VF';
src: url('../static/fonts/SourceHanSans/SourceHanSansSC-VF.otf.woff2') format('woff2');
font-display: swap;
font-style: normal;
}
@font-face {
font-family: 'CabinetGrotesk-Variable';
src: url('../static/fonts/CabinetGrotesk/CabinetGrotesk-Variable.woff2') format('woff2');
font-display: swap;
font-style: normal;
}
* {
margin: 0;
padding: 0;
}
#background {
display: flex;
flex-direction: column;
align-items: flex-start;
padding: 30px;
gap: 10px;
width: 444px;
background: #f1f1f1;
font-family: 'CabinetGrotesk-Variable', 'SourceHanSansSC-VF';
}
#main-content {
display: flex;
flex-direction: column;
margin: 0 auto;
padding: 0px;
gap: 15px;
}
#bind-subject {
display: flex;
flex-direction: column;
align-items: center;
padding: 0px;
gap: 30px;
}
#bind-icons {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
padding: 0px;
gap: 32px;
}
#user-avatar {
width: 96px;
height: 96px;
filter: drop-shadow(0px 11px 23px rgba(0, 0, 0, 0.22));
border-radius: 20px;
}
#state {
width: 128px;
height: 56px;
}
#bot-avatar {
width: 96px;
height: 96px;
filter: drop-shadow(0px 11px 23px rgba(0, 0, 0, 0.22));
border-radius: 20px;
}
#command-result {
font-weight: 350;
font-size: 25px;
line-height: 36.2px;
text-align: center;
color: #000000;
}
#game-type {
display: inline;
font-weight: 800;
line-height: 31px;
}
#user-name {
display: inline;
font-weight: 800;
line-height: 31px;
white-space: nowrap;
text-overflow: ellipsis;
}
#bot-name {
display: inline;
font-weight: 400;
line-height: 31px;
}
#extra-info {
width: 324px;
margin: 0 auto;
font-weight: 400;
font-size: 16px;
line-height: 23px;
text-align: center;
color: #52525c;
}

View File

@@ -0,0 +1,351 @@
@font-face {
font-family: 'CabinetGrotesk-Variable';
src: url('../static/fonts/CabinetGrotesk/CabinetGrotesk-Variable.woff2') format('woff2');
}
@font-face {
font-family: '26FGalaxySans-ObliqueVF';
src: url('../static/fonts/26FGalaxySans/26FGalaxySans-ObliqueVF.woff2') format('woff2');
}
* {
margin: 0;
padding: 0;
}
.flex-gap {
flex: 1;
}
.big-title {
margin-left: 25px;
margin-top: 22px;
font-weight: 900;
font-size: 35px;
line-height: 43px;
color: #000000;
}
.box-shadow {
box-shadow: 0px 9px 25px rgba(0, 0, 0, 0.15);
}
.chart-shadow {
box-shadow: 0px 15px 30px rgba(0, 0, 0, 0.3);
}
.box-rounded-corners {
border-radius: 30px;
}
.small-data-box {
position: relative;
width: 275px;
height: 125px;
}
.big-data-value {
position: absolute;
left: 24px;
top: 52px;
font-weight: 500;
font-size: 45px;
line-height: 56px;
}
.small-data-value {
position: absolute;
top: 79px;
right: 25px;
font-weight: 500;
font-size: 15px;
line-height: 19px;
text-align: right;
}
#main-content {
display: flex;
flex-direction: column;
width: 625px;
background: #f1f1f1;
font-family: 'CabinetGrotesk-Variable';
}
#account-box {
display: flex;
flex-direction: column;
}
#info-box {
display: flex;
justify-content: space-between;
margin-top: 10px;
}
#user-info-box {
display: flex;
flex-direction: column;
align-items: center;
padding: 25px;
gap: 10px;
width: 275px;
height: 275px;
background: #fafafa;
box-sizing: border-box;
}
#user-avatar {
width: 125px;
height: 125px;
filter: drop-shadow(0px 11px 23px rgba(0, 0, 0, 0.22));
border-radius: 65px;
}
#user-name {
font-weight: 800;
font-size: 25px;
line-height: 31px;
color: #000000;
}
#user-sign {
width: 225px;
height: 66px;
font-weight: 400;
font-size: 18px;
line-height: 22px;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
color: #000000;
}
#game-info-box {
display: flex;
flex-direction: column;
align-items: flex-start;
padding: 25px;
gap: 10px;
width: 275px;
height: 275px;
background: #fafafa;
box-sizing: border-box;
}
#game-type-box {
display: flex;
flex-direction: column;
}
#game-logo {
width: 60px;
height: 60px;
border-radius: 10px;
}
#game-name {
font-weight: 800;
font-size: 30px;
line-height: 37px;
color: #000000;
}
#game-info-dividing-line {
width: 225px;
border: 1px solid #bababa;
transform: rotate(0.25deg);
}
#ranking-info-box {
display: flex;
flex-direction: column;
}
#ranking-title {
font-weight: 800;
font-size: 25px;
line-height: 31px;
color: #000000;
}
#ranking {
font-weight: 400;
font-size: 50px;
line-height: 120%;
color: #000000;
}
#rd {
margin-top: -16px;
font-weight: 300;
font-size: 30px;
line-height: 120%;
color: #000000;
}
#TR-curve-chart {
align-self: center;
margin-top: 25px;
width: 575px;
height: 275px;
background: linear-gradient(222.34deg, #525252 11.97%, #1d1916 89.73%);
}
#TR-title {
position: absolute;
margin-left: 24px;
margin-top: 19px;
font-weight: 800;
font-size: 25px;
line-height: 31px;
white-space: nowrap;
color: #fafafa;
}
#rank-icon {
position: absolute;
margin-left: 27px;
margin-top: 90px;
width: 50px;
height: 50px;
}
#TR {
position: absolute;
margin-left: 24px;
margin-top: 143px;
font-weight: 800;
font-size: 45px;
line-height: 120%;
color: #fafafa;
}
#multiplayer-box {
display: flex;
flex-direction: row;
justify-content: space-between;
margin-top: 14px;
}
#multiplayer-data-box {
display: flex;
flex-direction: column;
}
.multiplayer-data {
margin-top: 25px;
}
.multiplayer-data:first-child {
margin-top: 0px;
}
#lpm-box {
background-image: url('../static/data/LPM.svg');
}
#lpm-value {
color: #4d7d0f;
}
#pps-value {
color: #4d7d0f;
}
#apm-box {
background-image: url('../static/data/APM.svg');
}
#apm-value {
color: #b5530a;
}
#apl-value {
color: #b5530a;
}
#adpm-box {
background-image: url('../static/data/ADPM.svg');
}
#adpm-value {
color: #235db4;
}
#vs-value {
top: 62px;
color: #4779c6;
}
#adpl-value {
color: #4779c6;
}
#radar-chart {
width: 275px;
height: 275px;
background: linear-gradient(222.34deg, #525252 11.97%, #1d1916 89.73%),
linear-gradient(222.34deg, #4f9dff 11.97%, #2563ea 89.73%);
}
#singleplayer-box {
display: flex;
flex-direction: row;
align-content: space-between;
margin-top: 14px;
}
#sprint-box {
background-image: url('../static/data/40L.svg');
}
#blitz-box {
background-image: url('../static/data/Blitz.svg');
}
#sprint-value {
color: #b42323;
}
#blitz-value {
color: #8e23b4;
}
#footer {
display: flex;
justify-content: center;
margin-top: 20px;
margin-bottom: 20px;
font-family: '26FGalaxySans-ObliqueVF';
font-size: 32px;
font-weight: 257;
text-align: center;
}

View File

@@ -0,0 +1,352 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<link href="../../static/css/data.css" rel="stylesheet" />
</head>
<body>
<div id="main-content">
<span class="big-title">Account&Rankings</span>
<div id="account-box">
<div id="info-box">
<div class="flex-gap"></div>
<div class="box-shadow box-rounded-corners" id="user-info-box">
<img id="user-avatar" src="{{user_avatar}}" />
<div id="user-name">{{user_name}}</div>
<div id="user-sign">“{{user_sign}}”</div>
</div>
<div class="flex-gap"></div>
<div class="box-shadow box-rounded-corners" id="game-info-box">
<div id="game-type-box">
<img id="game-logo" src="../../static/static/logo/{{game_type}}.svg" />
<span id="game-name">{{game_type}}</span>
</div>
<div id="game-info-dividing-line"></div>
<div id="ranking-info-box">
<span id="ranking-title">Ranking</span>
<span id="ranking">{{ranking}}</span>
<span id="rd">±{{rd}}</span>
</div>
</div>
<div class="flex-gap"></div>
</div>
<div class="chart-shadow box-rounded-corners" id="TR-curve-chart">
<span id="TR-title">Tetra Rating (TR)</span>
<img id="rank-icon" src="../../static/static/rank/{{rank}}.svg" />
<span id="TR" style="display: flex; align-items: flex-end"
>{{TR}}&nbsp;
<p style="font-size: 30px; font-weight: 400; line-height: 47px">(#{{global_rank}})</p>
</span>
</div>
</div>
<span class="big-title">Multiplayer Stats</span>
<div id="multiplayer-box">
<div class="flex-gap"></div>
<div id="multiplayer-data-box">
<div class="multiplayer-data small-data-box box-shadow box-rounded-corners" id="lpm-box">
<span class="big-data-value" id="lpm-value">{{lpm}}</span>
<span class="small-data-value" id="pps-value">{{pps}} pps</span>
</div>
<div class="multiplayer-data small-data-box box-shadow box-rounded-corners" id="apm-box">
<span class="big-data-value" id="apm-value">{{apm}}</span>
<span class="small-data-value" id="apl-value">x{{apl}}</span>
</div>
<div class="multiplayer-data small-data-box box-shadow box-rounded-corners" id="adpm-box">
<span class="big-data-value" id="adpm-value">{{adpm}}</span>
<span class="small-data-value" id="adpl-value">x{{adpl}}</span>
<span class="small-data-value" id="vs-value">{{vs}} vs</span>
</div>
</div>
<div class="flex-gap"></div>
<div class="chart-shadow box-rounded-corners" id="radar-chart"></div>
<div class="flex-gap"></div>
</div>
<span class="big-title">Singleplayer Stats</span>
<div id="singleplayer-box">
<div class="flex-gap"></div>
<div class="small-data-box box-shadow box-rounded-corners" id="sprint-box">
<span class="big-data-value" id="sprint-value">{{sprint}}</span>
</div>
<div class="flex-gap"></div>
<div class="small-data-box box-shadow box-rounded-corners" id="blitz-box">
<span class="big-data-value" id="blitz-value">{{blitz}}</span>
</div>
<div class="flex-gap"></div>
</div>
<div id="footer">Powered by<br />Nonebot2 x nonebot-plugin-tetris-stats</div>
</div>
</body>
<script src="../../static/js/echarts.js"></script>
<script>
var data = {{data}}
// 曲线图
var lineChartDom = document.getElementById('TR-curve-chart');
var lineChart = echarts.init(lineChartDom, null, { renderer: 'svg' });
var option;
/** @type EChartsOption */
option = {
animation: false,
grid: {
left: '-5%',
bottom: '17%',
width: '90%',
height: '70%',
},
xAxis: {
type: 'time',
minInterval: 3600 * 48 * 1000,
axisTick: {
show: false,
},
axisLine: {
show: false,
},
axisLabel: {
formatter: function (value, index) {
var date = new Date(value);
var lst;
var ret;
function format_date() {
return new Intl.DateTimeFormat('en-US', {
month: '2-digit',
day: '2-digit',
})
.format(date)
.split('/');
}
switch (index) {
case 0:
case 6:
ret = '';
break;
default:
lst = format_date();
if (index === 5) {
ret = '{last_month|' + lst[0] + '}\n{last_day|' + lst[1] + '}';
break;
}
ret = '{month|' + lst[0] + '}\n{day|' + lst[1] + '}';
}
return ret;
},
rich: {
month: {
fontFamily: 'CabinetGrotesk-Variable',
fontSize: 13,
fontWeight: '400',
color: 'rgba(255, 255, 255, 0.6)',
},
day: {
fontFamily: 'CabinetGrotesk-Variable',
fontSize: 20,
fontWeight: '800',
color: 'rgba(255, 255, 255, 0.6)',
},
last_month: {
fontFamily: 'CabinetGrotesk-Variable',
fontSize: 13,
fontWeight: '400',
color: '#373533',
backgroundColor: '#FAFAFA',
borderRadius: 6,
padding: [-10, 0, 10, 0],
width: 36,
height: 37,
lineHeight: 32,
},
last_day: {
fontFamily: 'CabinetGrotesk-Variable',
fontSize: 20,
fontWeight: '800',
color: '#373533',
padding: [-18, 0, 0, 0],
lineHeight: 0,
},
},
},
zlevel: 1,
},
yAxis: {
type: 'value',
interval: {{split_value}},
position: 'right',
splitLine: {
show: false,
},
axisLine: {
show: false,
},
axisLabel: {
align: 'right',
formatter: function (value, index) {
return '{value|' + value.toLocaleString() + '}';
},
rich: {
value: {
fontFamily: 'CabinetGrotesk-Variable',
fontSize: 15,
fontWeight: '500',
color: 'rgba(255, 255, 255, 0.6)',
},
},
},
offset: 70,
max: {{value_max+offset}},
min: {{value_min-offset}},
},
series: [
{
// 10天的数据最后一天只要第一条 (时间戳最少要多1ms)
data: data,
type: 'line',
smooth: true,
symbol: function (value, params) {
if (params.dataIndex === data.length - 1) {
return 'image://../../static/static/data/point.svg';
}
return 'none';
},
symbolSize: 75,
symbolOffset: [0.79, 0],
lineStyle: {
color: '#FAFAFA99',
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: 'rgba(250, 250, 250, 0.3)',
},
{
offset: 1,
color: 'rgba(250, 250, 250, 0)',
},
],
global: false,
},
},
markLine: {
data: [
{
xAxis: 'max',
y: 300,
},
],
label: {
show: false,
},
lineStyle: {
color: '#FAFAFA',
width: 3,
type: 'dashed',
cap: 'round',
},
symbol: 'none',
animation: false,
},
z: 5,
},
],
};
option && lineChart.setOption(option);
</script>
<script>
// 雷达图
var radarChartDom = document.getElementById('radar-chart');
var radarChart = echarts.init(radarChartDom, null, { renderer: 'svg' });
var option;
option = {
animation: false,
radar: [
{
indicator: [
{ name: 'PPS' },
{ name: 'APP', nameRotate: 60 },
{ name: 'DSPP', nameRotate: -60 },
{ name: 'OR' },
{ name: 'CI', nameRotate: 60 },
{ name: 'GE', nameRotate: -60 },
],
center: ['50%', '50%'],
radius: '65%',
startAngle: 90,
splitNumber: 4,
shape: 'circle',
silent: true,
axisName: {
color: '#FAFAFA',
fontFamily: 'CabinetGrotesk-Variable',
fontSize: 15,
fontWeight: '800',
},
splitArea: {
show: false,
},
axisLine: {
lineStyle: {
color: 'rgba(250, 250, 250, 0.3)',
},
},
axisLabel: {
show: true,
rotate: 0,
margin: -1,
fontFamily: 'CabinetGrotesk-Variable',
fontSize: 7,
fontWeight: '800',
color: '#FFFFFF',
},
splitLine: {
lineStyle: {
color: 'rgba(250, 250, 250, 0.3)',
},
},
},
],
series: [
{
type: 'radar',
symbol: 'none',
label: {
show: true,
},
emphasis: {
disabled: true,
},
lineStyle: {
color: '#FAFAFA',
width: 2.5,
shadowBlur: 20,
shadowColor: 'rgba(250, 250, 250, 1)',
},
areaStyle: {
color: 'rgba(250, 250, 250, 0.45)',
},
data: [
{
value: [{{pps}}, {{app}}, {{dspp}}, {{OR}}, {{ci}}, {{ge}}],
},
],
},
],
};
option && radarChart.setOption(option);
</script>
</html>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,205 @@
/**
* Identicon.js 2.3.3
* http://github.com/stewartlord/identicon.js
*
* PNGLib required for PNG output
* http://www.xarg.org/download/pnglib.js
*
* Copyright 2018, Stewart Lord
* Released under the BSD license
* http://www.opensource.org/licenses/bsd-license.php
*/
(function() {
var PNGlib;
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
PNGlib = require('./pnglib');
} else {
PNGlib = window.PNGlib;
}
var Identicon = function(hash, options){
if (typeof(hash) !== 'string' || hash.length < 15) {
throw 'A hash of at least 15 characters is required.';
}
this.defaults = {
background: [240, 240, 240, 255],
margin: 0.08,
size: 64,
saturation: 0.7,
brightness: 0.5,
format: 'png'
};
this.options = typeof(options) === 'object' ? options : this.defaults;
// backward compatibility with old constructor (hash, size, margin)
if (typeof(arguments[1]) === 'number') { this.options.size = arguments[1]; }
if (arguments[2]) { this.options.margin = arguments[2]; }
this.hash = hash
this.background = this.options.background || this.defaults.background;
this.size = this.options.size || this.defaults.size;
this.format = this.options.format || this.defaults.format;
this.margin = this.options.margin !== undefined ? this.options.margin : this.defaults.margin;
// foreground defaults to last 7 chars as hue at 70% saturation, 50% brightness
var hue = parseInt(this.hash.substr(-7), 16) / 0xfffffff;
var saturation = this.options.saturation || this.defaults.saturation;
var brightness = this.options.brightness || this.defaults.brightness;
this.foreground = this.options.foreground || this.hsl2rgb(hue, saturation, brightness);
};
Identicon.prototype = {
background: null,
foreground: null,
hash: null,
margin: null,
size: null,
format: null,
image: function(){
return this.isSvg()
? new Svg(this.size, this.foreground, this.background)
: new PNGlib(this.size, this.size, 256);
},
render: function(){
var image = this.image(),
size = this.size,
baseMargin = Math.floor(size * this.margin),
cell = Math.floor((size - (baseMargin * 2)) / 5),
margin = Math.floor((size - cell * 5) / 2),
bg = image.color.apply(image, this.background),
fg = image.color.apply(image, this.foreground);
// the first 15 characters of the hash control the pixels (even/odd)
// they are drawn down the middle first, then mirrored outwards
var i, color;
for (i = 0; i < 15; i++) {
color = parseInt(this.hash.charAt(i), 16) % 2 ? bg : fg;
if (i < 5) {
this.rectangle(2 * cell + margin, i * cell + margin, cell, cell, color, image);
} else if (i < 10) {
this.rectangle(1 * cell + margin, (i - 5) * cell + margin, cell, cell, color, image);
this.rectangle(3 * cell + margin, (i - 5) * cell + margin, cell, cell, color, image);
} else if (i < 15) {
this.rectangle(0 * cell + margin, (i - 10) * cell + margin, cell, cell, color, image);
this.rectangle(4 * cell + margin, (i - 10) * cell + margin, cell, cell, color, image);
}
}
return image;
},
rectangle: function(x, y, w, h, color, image){
if (this.isSvg()) {
image.rectangles.push({x: x, y: y, w: w, h: h, color: color});
} else {
var i, j;
for (i = x; i < x + w; i++) {
for (j = y; j < y + h; j++) {
image.buffer[image.index(i, j)] = color;
}
}
}
},
// adapted from: https://gist.github.com/aemkei/1325937
hsl2rgb: function(h, s, b){
h *= 6;
s = [
b += s *= b < .5 ? b : 1 - b,
b - h % 1 * s * 2,
b -= s *= 2,
b,
b + h % 1 * s,
b + s
];
return[
s[ ~~h % 6 ] * 255, // red
s[ (h|16) % 6 ] * 255, // green
s[ (h|8) % 6 ] * 255 // blue
];
},
toString: function(raw){
// backward compatibility with old toString, default to base64
if (raw) {
return this.render().getDump();
} else {
return this.render().getBase64();
}
},
isSvg: function(){
return this.format.match(/svg/i)
}
};
var Svg = function(size, foreground, background){
this.size = size;
this.foreground = this.color.apply(this, foreground);
this.background = this.color.apply(this, background);
this.rectangles = [];
};
Svg.prototype = {
size: null,
foreground: null,
background: null,
rectangles: null,
color: function(r, g, b, a){
var values = [r, g, b].map(Math.round);
values.push((a >= 0) && (a <= 255) ? a/255 : 1);
return 'rgba(' + values.join(',') + ')';
},
getDump: function(){
var i,
xml,
rect,
fg = this.foreground,
bg = this.background,
stroke = this.size * 0.005;
xml = "<svg xmlns='http://www.w3.org/2000/svg'"
+ " width='" + this.size + "' height='" + this.size + "'"
+ " style='background-color:" + bg + ";'>"
+ "<g style='fill:" + fg + "; stroke:" + fg + "; stroke-width:" + stroke + ";'>";
for (i = 0; i < this.rectangles.length; i++) {
rect = this.rectangles[i];
if (rect.color == bg) continue;
xml += "<rect "
+ " x='" + rect.x + "'"
+ " y='" + rect.y + "'"
+ " width='" + rect.w + "'"
+ " height='" + rect.h + "'"
+ "/>";
}
xml += "</g></svg>"
return xml;
},
getBase64: function(){
if ('function' === typeof btoa) {
return btoa(this.getDump());
} else if (Buffer) {
return new Buffer(this.getDump(), 'binary').toString('base64');
} else {
throw 'Cannot generate base64 output';
}
}
};
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
module.exports = Identicon;
} else {
window.Identicon = Identicon;
}
})();

View File

@@ -0,0 +1,20 @@
<svg width="132" height="56" viewBox="0 0 132 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 10.5C1.17157 10.5 0.5 11.1716 0.5 12C0.5 12.8284 1.17157 13.5 2 13.5V10.5ZM131.061 13.0607C131.646 12.4749 131.646 11.5251 131.061 10.9393L121.515 1.3934C120.929 0.807611 119.979 0.807611 119.393 1.3934C118.808 1.97919 118.808 2.92893 119.393 3.51472L127.879 12L119.393 20.4853C118.808 21.0711 118.808 22.0208 119.393 22.6066C119.979 23.1924 120.929 23.1924 121.515 22.6066L131.061 13.0607ZM4.95385 13.5C5.78227 13.5 6.45385 12.8284 6.45385 12C6.45385 11.1716 5.78227 10.5 4.95385 10.5V13.5ZM11.8462 10.5C11.0177 10.5 10.3462 11.1716 10.3462 12C10.3462 12.8284 11.0177 13.5 11.8462 13.5V10.5ZM17.7538 13.5C18.5823 13.5 19.2538 12.8284 19.2538 12C19.2538 11.1716 18.5823 10.5 17.7538 10.5V13.5ZM24.6462 10.5C23.8177 10.5 23.1462 11.1716 23.1462 12C23.1462 12.8284 23.8177 13.5 24.6462 13.5V10.5ZM30.5538 13.5C31.3823 13.5 32.0538 12.8284 32.0538 12C32.0538 11.1716 31.3823 10.5 30.5538 10.5V13.5ZM37.4462 10.5C36.6177 10.5 35.9462 11.1716 35.9462 12C35.9462 12.8284 36.6177 13.5 37.4462 13.5V10.5ZM43.3538 13.5C44.1823 13.5 44.8538 12.8284 44.8538 12C44.8538 11.1716 44.1823 10.5 43.3538 10.5V13.5ZM50.2462 10.5C49.4177 10.5 48.7462 11.1716 48.7462 12C48.7462 12.8284 49.4177 13.5 50.2462 13.5V10.5ZM56.1538 13.5C56.9823 13.5 57.6538 12.8284 57.6538 12C57.6538 11.1716 56.9823 10.5 56.1538 10.5V13.5ZM63.0462 10.5C62.2177 10.5 61.5462 11.1716 61.5462 12C61.5462 12.8284 62.2177 13.5 63.0462 13.5V10.5ZM68.9538 13.5C69.7823 13.5 70.4538 12.8284 70.4538 12C70.4538 11.1716 69.7823 10.5 68.9538 10.5V13.5ZM75.8462 10.5C75.0177 10.5 74.3462 11.1716 74.3462 12C74.3462 12.8284 75.0177 13.5 75.8462 13.5V10.5ZM81.7539 13.5C82.5823 13.5 83.2539 12.8284 83.2539 12C83.2539 11.1716 82.5823 10.5 81.7539 10.5V13.5ZM88.6462 10.5C87.8177 10.5 87.1462 11.1716 87.1462 12C87.1462 12.8284 87.8177 13.5 88.6462 13.5V10.5ZM94.5539 13.5C95.3823 13.5 96.0539 12.8284 96.0539 12C96.0539 11.1716 95.3823 10.5 94.5539 10.5V13.5ZM101.446 10.5C100.618 10.5 99.9462 11.1716 99.9462 12C99.9462 12.8284 100.618 13.5 101.446 13.5V10.5ZM107.354 13.5C108.182 13.5 108.854 12.8284 108.854 12C108.854 11.1716 108.182 10.5 107.354 10.5V13.5ZM114.246 10.5C113.418 10.5 112.746 11.1716 112.746 12C112.746 12.8284 113.418 13.5 114.246 13.5V10.5ZM120.154 13.5C120.982 13.5 121.654 12.8284 121.654 12C121.654 11.1716 120.982 10.5 120.154 10.5V13.5ZM127.046 10.5C126.218 10.5 125.546 11.1716 125.546 12C125.546 12.8284 126.218 13.5 127.046 13.5V10.5ZM2 13.5H4.95385V10.5H2V13.5ZM11.8462 13.5H17.7538V10.5H11.8462V13.5ZM24.6462 13.5H30.5538V10.5H24.6462V13.5ZM37.4462 13.5H43.3538V10.5H37.4462V13.5ZM50.2462 13.5H56.1538V10.5H50.2462V13.5ZM63.0462 13.5H68.9538V10.5H63.0462V13.5ZM75.8462 13.5H81.7539V10.5H75.8462V13.5ZM88.6462 13.5H94.5539V10.5H88.6462V13.5ZM101.446 13.5H107.354V10.5H101.446V13.5ZM114.246 13.5H120.154V10.5H114.246V13.5ZM127.046 13.5H130V10.5H127.046V13.5Z" fill="#A1A1AB"/>
<path d="M130 45.5C130.828 45.5 131.5 44.8284 131.5 44C131.5 43.1716 130.828 42.5 130 42.5V45.5ZM0.939331 42.9393C0.353546 43.5251 0.353546 44.4749 0.939331 45.0607L10.4853 54.6066C11.0711 55.1924 12.0208 55.1924 12.6066 54.6066C13.1924 54.0208 13.1924 53.0711 12.6066 52.4853L4.12132 44L12.6066 35.5147C13.1924 34.9289 13.1924 33.9792 12.6066 33.3934C12.0208 32.8076 11.0711 32.8076 10.4853 33.3934L0.939331 42.9393ZM127.046 42.5C126.218 42.5 125.546 43.1716 125.546 44C125.546 44.8284 126.218 45.5 127.046 45.5V42.5ZM120.154 45.5C120.982 45.5 121.654 44.8284 121.654 44C121.654 43.1716 120.982 42.5 120.154 42.5V45.5ZM114.246 42.5C113.418 42.5 112.746 43.1716 112.746 44C112.746 44.8284 113.418 45.5 114.246 45.5V42.5ZM107.354 45.5C108.182 45.5 108.854 44.8284 108.854 44C108.854 43.1716 108.182 42.5 107.354 42.5V45.5ZM101.446 42.5C100.618 42.5 99.9462 43.1716 99.9462 44C99.9462 44.8284 100.618 45.5 101.446 45.5V42.5ZM94.5538 45.5C95.3823 45.5 96.0538 44.8284 96.0538 44C96.0538 43.1716 95.3823 42.5 94.5538 42.5V45.5ZM88.6462 42.5C87.8177 42.5 87.1462 43.1716 87.1462 44C87.1462 44.8284 87.8177 45.5 88.6462 45.5V42.5ZM81.7538 45.5C82.5823 45.5 83.2538 44.8284 83.2538 44C83.2538 43.1716 82.5823 42.5 81.7538 42.5V45.5ZM75.8462 42.5C75.0177 42.5 74.3462 43.1716 74.3462 44C74.3462 44.8284 75.0177 45.5 75.8462 45.5V42.5ZM68.9538 45.5C69.7823 45.5 70.4538 44.8284 70.4538 44C70.4538 43.1716 69.7823 42.5 68.9538 42.5V45.5ZM63.0462 42.5C62.2177 42.5 61.5462 43.1716 61.5462 44C61.5462 44.8284 62.2177 45.5 63.0462 45.5V42.5ZM56.1538 45.5C56.9823 45.5 57.6538 44.8284 57.6538 44C57.6538 43.1716 56.9823 42.5 56.1538 42.5V45.5ZM50.2461 42.5C49.4177 42.5 48.7461 43.1716 48.7461 44C48.7461 44.8284 49.4177 45.5 50.2461 45.5V42.5ZM43.3538 45.5C44.1823 45.5 44.8538 44.8284 44.8538 44C44.8538 43.1716 44.1823 42.5 43.3538 42.5V45.5ZM37.4461 42.5C36.6177 42.5 35.9461 43.1716 35.9461 44C35.9461 44.8284 36.6177 45.5 37.4461 45.5V42.5ZM30.5538 45.5C31.3823 45.5 32.0538 44.8284 32.0538 44C32.0538 43.1716 31.3823 42.5 30.5538 42.5V45.5ZM24.6461 42.5C23.8177 42.5 23.1461 43.1716 23.1461 44C23.1461 44.8284 23.8177 45.5 24.6461 45.5V42.5ZM17.7538 45.5C18.5823 45.5 19.2538 44.8284 19.2538 44C19.2538 43.1716 18.5823 42.5 17.7538 42.5V45.5ZM11.8461 42.5C11.0177 42.5 10.3461 43.1716 10.3461 44C10.3461 44.8284 11.0177 45.5 11.8461 45.5V42.5ZM4.95383 45.5C5.78225 45.5 6.45383 44.8284 6.45383 44C6.45383 43.1716 5.78225 42.5 4.95383 42.5V45.5ZM130 42.5H127.046V45.5H130V42.5ZM120.154 42.5H114.246V45.5H120.154V42.5ZM107.354 42.5H101.446V45.5H107.354V42.5ZM94.5538 42.5H88.6462V45.5H94.5538V42.5ZM81.7538 42.5H75.8462V45.5H81.7538V42.5ZM68.9538 42.5H63.0462V45.5H68.9538V42.5ZM56.1538 42.5H50.2461V45.5H56.1538V42.5ZM43.3538 42.5H37.4461V45.5H43.3538V42.5ZM30.5538 42.5H24.6461V45.5H30.5538V42.5ZM17.7538 42.5H11.8461V45.5H17.7538V42.5ZM4.95383 42.5H2V45.5H4.95383V42.5Z" fill="#A1A1AB"/>
<circle cx="66" cy="28" r="28" fill="#F1F1F1"/>
<g filter="url(#filter0_d_503_299)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M66 48C77.0457 48 86 39.0457 86 28C86 16.9543 77.0457 8 66 8C54.9543 8 46 16.9543 46 28C46 39.0457 54.9543 48 66 48ZM59.6667 37.0667L66.2667 30.4667L72.8667 37.0667C73.1778 37.3778 73.5444 37.5333 73.9667 37.5333C74.3889 37.5333 74.7556 37.3778 75.0667 37.0667C75.3778 36.7556 75.5333 36.3889 75.5333 35.9667C75.5333 35.5444 75.3778 35.1778 75.0667 34.8667L68.4667 28.2667L75.0667 21.6667C75.3778 21.3556 75.5333 20.9889 75.5333 20.5667C75.5333 20.1444 75.3778 19.7778 75.0667 19.4667C74.7556 19.1556 74.3889 19 73.9667 19C73.5444 19 73.1778 19.1556 72.8667 19.4667L66.2667 26.0667L59.6667 19.4667C59.3556 19.1556 58.9889 19 58.5667 19C58.1444 19 57.7778 19.1556 57.4667 19.4667C57.1556 19.7778 57 20.1444 57 20.5667C57 20.9889 57.1556 21.3556 57.4667 21.6667L64.0667 28.2667L57.4667 34.8667C57.1556 35.1778 57 35.5444 57 35.9667C57 36.3889 57.1556 36.7556 57.4667 37.0667C57.7778 37.3778 58.1444 37.5333 58.5667 37.5333C58.9889 37.5333 59.3556 37.3778 59.6667 37.0667Z" fill="#F04444"/>
</g>
<defs>
<filter id="filter0_d_503_299" x="42" y="7" width="48" height="48" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="3"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_503_299"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_503_299" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

@@ -0,0 +1,20 @@
<svg width="132" height="56" viewBox="0 0 132 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 10.5C1.17157 10.5 0.5 11.1716 0.5 12C0.5 12.8284 1.17157 13.5 2 13.5V10.5ZM131.061 13.0607C131.646 12.4749 131.646 11.5251 131.061 10.9393L121.515 1.3934C120.929 0.807611 119.979 0.807611 119.393 1.3934C118.808 1.97919 118.808 2.92893 119.393 3.51472L127.879 12L119.393 20.4853C118.808 21.0711 118.808 22.0208 119.393 22.6066C119.979 23.1924 120.929 23.1924 121.515 22.6066L131.061 13.0607ZM2 13.5H130V10.5H2V13.5Z" fill="#A1A1AB"/>
<path d="M130 45.5C130.828 45.5 131.5 44.8284 131.5 44C131.5 43.1716 130.828 42.5 130 42.5V45.5ZM0.939331 42.9393C0.353546 43.5251 0.353546 44.4749 0.939331 45.0607L10.4853 54.6066C11.0711 55.1924 12.0208 55.1924 12.6066 54.6066C13.1924 54.0208 13.1924 53.0711 12.6066 52.4853L4.12132 44L12.6066 35.5147C13.1924 34.9289 13.1924 33.9792 12.6066 33.3934C12.0208 32.8076 11.0711 32.8076 10.4853 33.3934L0.939331 42.9393ZM130 42.5L2 42.5V45.5L130 45.5V42.5Z" fill="#A1A1AB"/>
<circle cx="66" cy="28" r="28" fill="#F1F1F1"/>
<g filter="url(#filter0_d_503_333)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M66 48C77.0457 48 86 39.0457 86 28C86 16.9543 77.0457 8 66 8C54.9543 8 46 16.9543 46 28C46 39.0457 54.9543 48 66 48ZM62.0168 35.7834C62.2057 35.8611 62.4001 35.9 62.6001 35.9C62.8001 35.9 62.9945 35.8611 63.1834 35.7834C63.3723 35.7056 63.5334 35.5889 63.6668 35.4334L76.4334 22.6667C76.7445 22.3778 76.9001 22.0167 76.9001 21.5834C76.9001 21.15 76.7445 20.7778 76.4334 20.4667C76.1445 20.1556 75.789 20.0056 75.3668 20.0167C74.9445 20.0278 74.5779 20.1778 74.2668 20.4667L62.6001 32.1334L57.7334 27.2334C57.4223 26.9445 57.0501 26.8 56.6168 26.8C56.1834 26.8 55.8223 26.9445 55.5334 27.2334C55.2445 27.5222 55.1001 27.8889 55.1001 28.3334C55.1001 28.7778 55.2445 29.1445 55.5334 29.4334L61.5668 35.4334C61.6779 35.5889 61.8279 35.7056 62.0168 35.7834Z" fill="#23C55E"/>
</g>
<defs>
<filter id="filter0_d_503_333" x="42" y="7" width="48" height="48" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="3"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_503_333"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_503_333" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,20 @@
<svg width="132" height="56" viewBox="0 0 132 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 10.5C1.17157 10.5 0.5 11.1716 0.5 12C0.5 12.8284 1.17157 13.5 2 13.5V10.5ZM131.061 13.0607C131.646 12.4749 131.646 11.5251 131.061 10.9393L121.515 1.3934C120.929 0.807611 119.979 0.807611 119.393 1.3934C118.808 1.97919 118.808 2.92893 119.393 3.51472L127.879 12L119.393 20.4853C118.808 21.0711 118.808 22.0208 119.393 22.6066C119.979 23.1924 120.929 23.1924 121.515 22.6066L131.061 13.0607ZM2 13.5H130V10.5H2V13.5Z" fill="#A1A1AB"/>
<path d="M130 45.5C130.828 45.5 131.5 44.8284 131.5 44C131.5 43.1716 130.828 42.5 130 42.5V45.5ZM0.939331 42.9393C0.353546 43.5251 0.353546 44.4749 0.939331 45.0607L10.4853 54.6066C11.0711 55.1924 12.0208 55.1924 12.6066 54.6066C13.1924 54.0208 13.1924 53.0711 12.6066 52.4853L4.12132 44L12.6066 35.5147C13.1924 34.9289 13.1924 33.9792 12.6066 33.3934C12.0208 32.8076 11.0711 32.8076 10.4853 33.3934L0.939331 42.9393ZM130 42.5L2 42.5V45.5L130 45.5V42.5Z" fill="#A1A1AB"/>
<circle cx="66" cy="28" r="28" fill="#F1F1F1"/>
<g filter="url(#filter0_d_1756_38)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M66 47.9998C77.0457 47.9998 86 39.0455 86 27.9998C86 16.9541 77.0457 7.99976 66 7.99976C54.9543 7.99976 46 16.9541 46 27.9998C46 39.0455 54.9543 47.9998 66 47.9998ZM63.555 32.7332H67.3209V32.0227C67.3209 31.2885 67.4749 30.6608 67.7828 30.1398C68.1144 29.595 68.5288 29.1095 69.0262 28.6832C69.5473 28.2331 70.092 27.8068 70.6605 27.4042C71.2289 26.9779 71.7618 26.516 72.2592 26.0186C72.7802 25.5212 73.1947 24.9528 73.5026 24.3133C73.8342 23.6502 74 22.8686 74 21.9685C74 19.9317 73.325 18.4277 71.975 17.4566C70.6486 16.4855 68.8486 16 66.5748 16C64.7985 16 63.2353 16.3316 61.8853 16.9948C60.5589 17.6579 59.5405 18.5698 58.83 19.7303C58.1431 20.8909 57.8826 22.2291 58.0484 23.7449L61.4234 26.1607C61.2339 24.716 61.3287 23.5199 61.7076 22.5725C62.1103 21.6251 62.7142 20.9264 63.5195 20.4764C64.3248 20.0027 65.2485 19.7659 66.2906 19.7659C67.4749 19.7659 68.3275 20.0146 68.8486 20.5119C69.3933 20.9856 69.6657 21.6014 69.6657 22.3593C69.6657 22.9751 69.5117 23.508 69.2038 23.9581C68.9196 24.4081 68.5407 24.8225 68.067 25.2015C67.617 25.5805 67.1314 25.9713 66.6104 26.3739C66.0893 26.7765 65.5919 27.2147 65.1182 27.6884C64.6682 28.1621 64.2893 28.7305 63.9814 29.3937C63.6972 30.0332 63.555 30.8029 63.555 31.703V32.7332ZM63.1287 40.1229H67.6762V35.078H63.1287V40.1229Z" fill="#E9B308"/>
</g>
<defs>
<filter id="filter0_d_1756_38" x="42" y="6.99976" width="48" height="48" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="3"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1756_38"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1756_38" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -0,0 +1,20 @@
<svg width="132" height="56" viewBox="0 0 132 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 10.5C1.17157 10.5 0.5 11.1716 0.5 12C0.5 12.8284 1.17157 13.5 2 13.5V10.5ZM131.061 13.0607C131.646 12.4749 131.646 11.5251 131.061 10.9393L121.515 1.3934C120.929 0.807611 119.979 0.807611 119.393 1.3934C118.808 1.97919 118.808 2.92893 119.393 3.51472L127.879 12L119.393 20.4853C118.808 21.0711 118.808 22.0208 119.393 22.6066C119.979 23.1924 120.929 23.1924 121.515 22.6066L131.061 13.0607ZM4.95385 13.5C5.78227 13.5 6.45385 12.8284 6.45385 12C6.45385 11.1716 5.78227 10.5 4.95385 10.5V13.5ZM11.8462 10.5C11.0177 10.5 10.3462 11.1716 10.3462 12C10.3462 12.8284 11.0177 13.5 11.8462 13.5V10.5ZM17.7538 13.5C18.5823 13.5 19.2538 12.8284 19.2538 12C19.2538 11.1716 18.5823 10.5 17.7538 10.5V13.5ZM24.6462 10.5C23.8177 10.5 23.1462 11.1716 23.1462 12C23.1462 12.8284 23.8177 13.5 24.6462 13.5V10.5ZM30.5538 13.5C31.3823 13.5 32.0538 12.8284 32.0538 12C32.0538 11.1716 31.3823 10.5 30.5538 10.5V13.5ZM37.4462 10.5C36.6177 10.5 35.9462 11.1716 35.9462 12C35.9462 12.8284 36.6177 13.5 37.4462 13.5V10.5ZM43.3538 13.5C44.1823 13.5 44.8538 12.8284 44.8538 12C44.8538 11.1716 44.1823 10.5 43.3538 10.5V13.5ZM50.2462 10.5C49.4177 10.5 48.7462 11.1716 48.7462 12C48.7462 12.8284 49.4177 13.5 50.2462 13.5V10.5ZM56.1538 13.5C56.9823 13.5 57.6538 12.8284 57.6538 12C57.6538 11.1716 56.9823 10.5 56.1538 10.5V13.5ZM63.0462 10.5C62.2177 10.5 61.5462 11.1716 61.5462 12C61.5462 12.8284 62.2177 13.5 63.0462 13.5V10.5ZM68.9538 13.5C69.7823 13.5 70.4538 12.8284 70.4538 12C70.4538 11.1716 69.7823 10.5 68.9538 10.5V13.5ZM75.8462 10.5C75.0177 10.5 74.3462 11.1716 74.3462 12C74.3462 12.8284 75.0177 13.5 75.8462 13.5V10.5ZM81.7539 13.5C82.5823 13.5 83.2539 12.8284 83.2539 12C83.2539 11.1716 82.5823 10.5 81.7539 10.5V13.5ZM88.6462 10.5C87.8177 10.5 87.1462 11.1716 87.1462 12C87.1462 12.8284 87.8177 13.5 88.6462 13.5V10.5ZM94.5539 13.5C95.3823 13.5 96.0539 12.8284 96.0539 12C96.0539 11.1716 95.3823 10.5 94.5539 10.5V13.5ZM101.446 10.5C100.618 10.5 99.9462 11.1716 99.9462 12C99.9462 12.8284 100.618 13.5 101.446 13.5V10.5ZM107.354 13.5C108.182 13.5 108.854 12.8284 108.854 12C108.854 11.1716 108.182 10.5 107.354 10.5V13.5ZM114.246 10.5C113.418 10.5 112.746 11.1716 112.746 12C112.746 12.8284 113.418 13.5 114.246 13.5V10.5ZM120.154 13.5C120.982 13.5 121.654 12.8284 121.654 12C121.654 11.1716 120.982 10.5 120.154 10.5V13.5ZM127.046 10.5C126.218 10.5 125.546 11.1716 125.546 12C125.546 12.8284 126.218 13.5 127.046 13.5V10.5ZM2 13.5H4.95385V10.5H2V13.5ZM11.8462 13.5H17.7538V10.5H11.8462V13.5ZM24.6462 13.5H30.5538V10.5H24.6462V13.5ZM37.4462 13.5H43.3538V10.5H37.4462V13.5ZM50.2462 13.5H56.1538V10.5H50.2462V13.5ZM63.0462 13.5H68.9538V10.5H63.0462V13.5ZM75.8462 13.5H81.7539V10.5H75.8462V13.5ZM88.6462 13.5H94.5539V10.5H88.6462V13.5ZM101.446 13.5H107.354V10.5H101.446V13.5ZM114.246 13.5H120.154V10.5H114.246V13.5ZM127.046 13.5H130V10.5H127.046V13.5Z" fill="#A1A1AB"/>
<path d="M130 45.5C130.828 45.5 131.5 44.8284 131.5 44C131.5 43.1716 130.828 42.5 130 42.5V45.5ZM0.939331 42.9393C0.353546 43.5251 0.353546 44.4749 0.939331 45.0607L10.4853 54.6066C11.0711 55.1924 12.0208 55.1924 12.6066 54.6066C13.1924 54.0208 13.1924 53.0711 12.6066 52.4853L4.12132 44L12.6066 35.5147C13.1924 34.9289 13.1924 33.9792 12.6066 33.3934C12.0208 32.8076 11.0711 32.8076 10.4853 33.3934L0.939331 42.9393ZM127.046 42.5C126.218 42.5 125.546 43.1716 125.546 44C125.546 44.8284 126.218 45.5 127.046 45.5V42.5ZM120.154 45.5C120.982 45.5 121.654 44.8284 121.654 44C121.654 43.1716 120.982 42.5 120.154 42.5V45.5ZM114.246 42.5C113.418 42.5 112.746 43.1716 112.746 44C112.746 44.8284 113.418 45.5 114.246 45.5V42.5ZM107.354 45.5C108.182 45.5 108.854 44.8284 108.854 44C108.854 43.1716 108.182 42.5 107.354 42.5V45.5ZM101.446 42.5C100.618 42.5 99.9462 43.1716 99.9462 44C99.9462 44.8284 100.618 45.5 101.446 45.5V42.5ZM94.5538 45.5C95.3823 45.5 96.0538 44.8284 96.0538 44C96.0538 43.1716 95.3823 42.5 94.5538 42.5V45.5ZM88.6462 42.5C87.8177 42.5 87.1462 43.1716 87.1462 44C87.1462 44.8284 87.8177 45.5 88.6462 45.5V42.5ZM81.7538 45.5C82.5823 45.5 83.2538 44.8284 83.2538 44C83.2538 43.1716 82.5823 42.5 81.7538 42.5V45.5ZM75.8462 42.5C75.0177 42.5 74.3462 43.1716 74.3462 44C74.3462 44.8284 75.0177 45.5 75.8462 45.5V42.5ZM68.9538 45.5C69.7823 45.5 70.4538 44.8284 70.4538 44C70.4538 43.1716 69.7823 42.5 68.9538 42.5V45.5ZM63.0462 42.5C62.2177 42.5 61.5462 43.1716 61.5462 44C61.5462 44.8284 62.2177 45.5 63.0462 45.5V42.5ZM56.1538 45.5C56.9823 45.5 57.6538 44.8284 57.6538 44C57.6538 43.1716 56.9823 42.5 56.1538 42.5V45.5ZM50.2461 42.5C49.4177 42.5 48.7461 43.1716 48.7461 44C48.7461 44.8284 49.4177 45.5 50.2461 45.5V42.5ZM43.3538 45.5C44.1823 45.5 44.8538 44.8284 44.8538 44C44.8538 43.1716 44.1823 42.5 43.3538 42.5V45.5ZM37.4461 42.5C36.6177 42.5 35.9461 43.1716 35.9461 44C35.9461 44.8284 36.6177 45.5 37.4461 45.5V42.5ZM30.5538 45.5C31.3823 45.5 32.0538 44.8284 32.0538 44C32.0538 43.1716 31.3823 42.5 30.5538 42.5V45.5ZM24.6461 42.5C23.8177 42.5 23.1461 43.1716 23.1461 44C23.1461 44.8284 23.8177 45.5 24.6461 45.5V42.5ZM17.7538 45.5C18.5823 45.5 19.2538 44.8284 19.2538 44C19.2538 43.1716 18.5823 42.5 17.7538 42.5V45.5ZM11.8461 42.5C11.0177 42.5 10.3461 43.1716 10.3461 44C10.3461 44.8284 11.0177 45.5 11.8461 45.5V42.5ZM4.95383 45.5C5.78225 45.5 6.45383 44.8284 6.45383 44C6.45383 43.1716 5.78225 42.5 4.95383 42.5V45.5ZM130 42.5H127.046V45.5H130V42.5ZM120.154 42.5H114.246V45.5H120.154V42.5ZM107.354 42.5H101.446V45.5H107.354V42.5ZM94.5538 42.5H88.6462V45.5H94.5538V42.5ZM81.7538 42.5H75.8462V45.5H81.7538V42.5ZM68.9538 42.5H63.0462V45.5H68.9538V42.5ZM56.1538 42.5H50.2461V45.5H56.1538V42.5ZM43.3538 42.5H37.4461V45.5H43.3538V42.5ZM30.5538 42.5H24.6461V45.5H30.5538V42.5ZM17.7538 42.5H11.8461V45.5H17.7538V42.5ZM4.95383 42.5H2V45.5H4.95383V42.5Z" fill="#A1A1AB"/>
<circle cx="66" cy="28" r="28" fill="#F1F1F1"/>
<g filter="url(#filter0_d_503_316)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M66 48C77.0457 48 86 39.0457 86 28C86 16.9543 77.0457 8 66 8C54.9543 8 46 16.9543 46 28C46 39.0457 54.9543 48 66 48ZM67.4585 27.0375L69.4418 29.05C69.714 29.05 69.9522 28.9479 70.1564 28.7438C70.3605 28.5396 70.4626 28.3014 70.4626 28.0292C70.4626 27.757 70.3605 27.5236 70.1564 27.3292C69.9522 27.1348 69.714 27.0375 69.4418 27.0375H67.4585ZM72.1835 31.8209L74.3418 33.9209C75.4112 33.4542 76.3251 32.6959 77.0835 31.6459C77.8418 30.5959 78.221 29.4098 78.221 28.0875C78.221 26.2403 77.589 24.6799 76.3251 23.4063C75.0612 22.1327 73.496 21.4959 71.6293 21.4959H68.8293C68.4404 21.4959 68.1147 21.632 67.8522 21.9042C67.5897 22.1764 67.4585 22.507 67.4585 22.8959C67.4585 23.2848 67.5897 23.6104 67.8522 23.8729C68.1147 24.1354 68.4404 24.2667 68.8293 24.2667H71.6585C72.7474 24.2667 73.6515 24.6264 74.371 25.3459C75.0904 26.0653 75.4501 26.9792 75.4501 28.0875C75.4501 29.0014 75.1487 29.8084 74.546 30.5084C73.9432 31.2084 73.1557 31.6459 72.1835 31.8209ZM63.9863 29.05L74.546 39.5792C74.7599 39.8125 75.0078 39.9292 75.2897 39.9292C75.5717 39.9292 75.8196 39.8125 76.0335 39.5792C76.2474 39.3653 76.3543 39.1271 76.3543 38.8646C76.3543 38.6021 76.2474 38.3639 76.0335 38.15L55.821 17.9375C55.5876 17.7236 55.3349 17.6167 55.0626 17.6167C54.7904 17.6167 54.5474 17.7236 54.3335 17.9375C54.1001 18.1709 53.9883 18.4236 53.998 18.6959C54.0078 18.9681 54.1196 19.2111 54.3335 19.425L57.1687 22.2521C56.3256 22.7064 55.609 23.3294 55.0189 24.1209C54.1925 25.2292 53.7793 26.5223 53.7793 28C53.7793 29.8473 54.4112 31.4028 55.6751 32.6667C56.939 33.9306 58.5043 34.5625 60.371 34.5625H63.6085C63.9974 34.5625 64.3279 34.4313 64.6001 34.1688C64.8724 33.9063 65.0085 33.5806 65.0085 33.1917C65.0085 32.8028 64.8724 32.4771 64.6001 32.2146C64.3279 31.9521 63.9974 31.8209 63.6085 31.8209H60.371C59.2626 31.8209 58.3487 31.4611 57.6293 30.7417C56.9099 30.0223 56.5501 29.1084 56.5501 28C56.5501 26.8917 56.9099 25.9778 57.6293 25.2584C58.0882 24.7995 58.6262 24.4869 59.2433 24.3207L62.0223 27.0916C61.9526 27.1327 61.8883 27.1827 61.8293 27.2417C61.6349 27.4361 61.5376 27.6889 61.5376 28C61.5376 28.3111 61.6397 28.5639 61.8439 28.7584C62.048 28.9528 62.296 29.05 62.5876 29.05H63.9863Z" fill="#71717B"/>
</g>
<defs>
<filter id="filter0_d_503_316" x="42" y="7" width="48" height="48" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="3"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_503_316"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_503_316" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@@ -0,0 +1,20 @@
<svg width="132" height="56" viewBox="0 0 132 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 10.5C1.17157 10.5 0.5 11.1716 0.5 12C0.5 12.8284 1.17157 13.5 2 13.5V10.5ZM131.061 13.0607C131.646 12.4749 131.646 11.5251 131.061 10.9393L121.515 1.3934C120.929 0.807611 119.979 0.807611 119.393 1.3934C118.808 1.97919 118.808 2.92893 119.393 3.51472L127.879 12L119.393 20.4853C118.808 21.0711 118.808 22.0208 119.393 22.6066C119.979 23.1924 120.929 23.1924 121.515 22.6066L131.061 13.0607ZM2 13.5H130V10.5H2V13.5Z" fill="#A1A1AB"/>
<path d="M130 45.5C130.828 45.5 131.5 44.8284 131.5 44C131.5 43.1716 130.828 42.5 130 42.5V45.5ZM0.939331 42.9393C0.353546 43.5251 0.353546 44.4749 0.939331 45.0607L10.4853 54.6066C11.0711 55.1924 12.0208 55.1924 12.6066 54.6066C13.1924 54.0208 13.1924 53.0711 12.6066 52.4853L4.12132 44L12.6066 35.5147C13.1924 34.9289 13.1924 33.9792 12.6066 33.3934C12.0208 32.8076 11.0711 32.8076 10.4853 33.3934L0.939331 42.9393ZM130 42.5L2 42.5V45.5L130 45.5V42.5Z" fill="#A1A1AB"/>
<circle cx="66" cy="28" r="28" fill="#F1F1F1"/>
<g filter="url(#filter0_d_505_398)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M66 48C77.0457 48 86 39.0457 86 28C86 16.9543 77.0457 8 66 8C54.9543 8 46 16.9543 46 28C46 39.0457 54.9543 48 66 48ZM67.5 17.75C67.5 18.7165 66.7165 19.5 65.75 19.5C64.7835 19.5 64 18.7165 64 17.75C64 16.7835 64.7835 16 65.75 16C66.7165 16 67.5 16.7835 67.5 17.75ZM65.75 23C66.5784 23 67.25 23.6716 67.25 24.5V38.5C67.25 39.3284 66.5784 40 65.75 40C64.9216 40 64.25 39.3284 64.25 38.5V24.5C64.25 23.6716 64.9216 23 65.75 23Z" fill="#E9B308"/>
</g>
<defs>
<filter id="filter0_d_505_398" x="42" y="7" width="48" height="48" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="3"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_505_398"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_505_398" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -0,0 +1,27 @@
<svg width="275" height="125" viewBox="0 0 275 125" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_601_45)">
<rect width="275" height="125" rx="30" fill="url(#paint0_linear_601_45)"/>
<path d="M39.175 38.55H24.65V35.775L32.6 25.25H33.65V29.175H33.25L28.45 35.45H39.175V38.55ZM36.6 42H33.025V25.25H36.6V42ZM47.8797 42.225C46.2797 42.225 44.9297 41.8583 43.8297 41.125C42.7297 40.375 41.888 39.35 41.3047 38.05C40.738 36.75 40.4547 35.2667 40.4547 33.6C40.4547 31.9333 40.738 30.4583 41.3047 29.175C41.888 27.875 42.7297 26.8583 43.8297 26.125C44.9297 25.375 46.2797 25 47.8797 25C49.463 25 50.8047 25.375 51.9047 26.125C53.0214 26.8583 53.863 27.875 54.4297 29.175C54.9964 30.4583 55.2797 31.9333 55.2797 33.6C55.2797 35.2667 54.9964 36.75 54.4297 38.05C53.863 39.35 53.0214 40.375 51.9047 41.125C50.8047 41.8583 49.463 42.225 47.8797 42.225ZM47.8797 38.75C48.7464 38.75 49.4464 38.5417 49.9797 38.125C50.513 37.7083 50.9047 37.1083 51.1547 36.325C51.4047 35.5417 51.5297 34.6333 51.5297 33.6C51.5297 32.55 51.4047 31.6417 51.1547 30.875C50.9047 30.1083 50.513 29.5167 49.9797 29.1C49.4464 28.6667 48.7464 28.45 47.8797 28.45C47.013 28.45 46.313 28.6667 45.7797 29.1C45.2464 29.5167 44.8547 30.1083 44.6047 30.875C44.3547 31.6417 44.2297 32.55 44.2297 33.6C44.2297 34.6333 44.3547 35.5417 44.6047 36.325C44.8547 37.1083 45.2464 37.7083 45.7797 38.125C46.313 38.5417 47.013 38.75 47.8797 38.75ZM61.2248 42H57.6498V25.25H61.2248V42ZM69.5998 42H58.8998V38.825H69.5998V42Z" fill="#B42323"/>
<g style="mix-blend-mode:overlay" opacity="0.5">
<mask id="mask0_601_45" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="121" y="-51" width="226" height="226">
<rect x="121.584" y="-2.90686" width="183.584" height="183.584" transform="rotate(-15 121.584 -2.90686)" fill="url(#paint1_radial_601_45)"/>
</mask>
<g mask="url(#mask0_601_45)">
<path d="M237.048 132.458L230.578 78.757L204.717 85.6862C202.87 86.1812 201.598 85.9942 200.9 85.1251C200.208 84.2548 200.123 82.8256 200.646 80.8376L223.577 -6.47827L230.966 -8.45807L237.436 45.2426L263.296 38.3133C265.144 37.8184 266.413 38.0061 267.106 38.8764C267.803 39.7454 267.89 41.1739 267.367 43.1619L244.436 130.478L237.048 132.458Z" fill="#1C1B1F"/>
</g>
</g>
</g>
<defs>
<linearGradient id="paint0_linear_601_45" x1="50.5" y1="-12" x2="244" y2="144.5" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFC7C7"/>
<stop offset="1" stop-color="#FA9C9C"/>
</linearGradient>
<radialGradient id="paint1_radial_601_45" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(213.376 88.8852) rotate(90) scale(97.5291)">
<stop offset="0.208333" stop-color="white" stop-opacity="0.78"/>
<stop offset="1" stop-color="white" stop-opacity="0"/>
</radialGradient>
<clipPath id="clip0_601_45">
<rect width="275" height="125" rx="30" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -0,0 +1,33 @@
<svg width="275" height="125" viewBox="0 0 275 125" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_601_13)">
<rect width="275" height="125" rx="30" fill="url(#paint0_linear_601_13)"/>
<g style="mix-blend-mode:overlay" opacity="0.5">
<mask id="mask0_601_13" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="92" y="-32" width="209" height="208">
<rect x="92.9521" y="12.3839" width="169.169" height="169.169" transform="rotate(-15 92.9521 12.3839)" fill="url(#paint1_radial_601_13)"/>
</mask>
<g mask="url(#mask0_601_13)">
<rect x="201.286" y="49.032" width="42.2923" height="42.2923" transform="rotate(-15 201.286 49.032)" fill="black"/>
<rect x="115.499" y="72.0188" width="88.8138" height="42.2923" transform="rotate(-15 115.499 72.0188)" fill="url(#paint2_linear_601_13)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M204.462 106.925C216.305 116.473 229.328 121.132 243.531 120.903C255.945 114 264.896 103.454 270.383 89.2622C275.866 75.0756 276.667 60.738 272.785 46.2494L263.437 11.3625L212.882 6.51932L171.522 35.9911L175.681 51.5145L187.171 48.436L185.118 40.7764L216.138 18.6725L254.055 22.3049L261.295 49.328C264.386 60.8617 263.94 72.2191 259.958 83.4002C255.976 94.5814 249.427 103.079 240.313 108.892C229.512 108.415 219.593 104.33 210.553 96.6381C209.983 96.1531 209.425 95.6605 208.879 95.1602L195.757 98.6763C198.37 101.605 201.271 104.355 204.462 106.925Z" fill="black"/>
</g>
</g>
<path d="M27.925 42H24.125L30.4 25.25H35L41.275 42H37.35L33.65 31.65L32.65 28.1L31.65 31.65L27.925 42ZM37.125 38.275H27.625V35.25H37.125V38.275ZM48.8486 42H43.3236V38.825H48.5486C49.582 38.825 50.432 38.6333 51.0986 38.25C51.782 37.85 52.2903 37.2667 52.6236 36.5C52.957 35.7167 53.1236 34.7583 53.1236 33.625C53.1236 32.475 52.9486 31.5167 52.5986 30.75C52.2653 29.9833 51.757 29.4083 51.0736 29.025C50.3903 28.625 49.532 28.425 48.4986 28.425H43.3236V25.25H48.7986C50.4986 25.25 51.9486 25.6 53.1486 26.3C54.3653 27 55.2903 27.975 55.9236 29.225C56.557 30.475 56.8736 31.9417 56.8736 33.625C56.8736 35.2917 56.557 36.7583 55.9236 38.025C55.2903 39.275 54.3736 40.25 53.1736 40.95C51.9903 41.65 50.5486 42 48.8486 42ZM45.6486 42H42.0736V25.25H45.6486V42ZM65.9914 37.15H60.4414V33.975H65.8664C66.4831 33.975 67.0247 33.8833 67.4914 33.7C67.9747 33.5167 68.3497 33.225 68.6164 32.825C68.8997 32.425 69.0414 31.9 69.0414 31.25C69.0414 30.55 68.8997 30 68.6164 29.6C68.3497 29.1833 67.9747 28.8833 67.4914 28.7C67.0247 28.5167 66.4831 28.425 65.8664 28.425H60.4414V25.25H65.9914C67.2747 25.25 68.4247 25.4583 69.4414 25.875C70.4747 26.2917 71.2914 26.9417 71.8914 27.825C72.4914 28.6917 72.7914 29.8333 72.7914 31.25C72.7914 32.6333 72.4914 33.7583 71.8914 34.625C71.3081 35.4917 70.4997 36.1333 69.4664 36.55C68.4497 36.95 67.2914 37.15 65.9914 37.15ZM62.6164 42H59.0414V25.25H62.6164V42ZM78.1682 42H74.5932V25.25H79.3182L82.0432 31.475L83.6182 36.45L85.1932 31.475L87.9182 25.25H92.6432V42H89.0682V35.725L89.5682 28.975L87.5682 34.55L85.4932 39.1H81.7432L79.6682 34.55L77.6432 28.975L78.1682 35.725V42Z" fill="#235DB4"/>
</g>
<defs>
<linearGradient id="paint0_linear_601_13" x1="50.5" y1="-12" x2="244" y2="144.5" gradientUnits="userSpaceOnUse">
<stop stop-color="#C7DAFF"/>
<stop offset="1" stop-color="#9CBCFA"/>
</linearGradient>
<radialGradient id="paint1_radial_601_13" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(177.537 96.9684) rotate(90) scale(89.8711)">
<stop offset="0.208333" stop-color="white"/>
<stop offset="1" stop-color="white" stop-opacity="0"/>
</radialGradient>
<linearGradient id="paint2_linear_601_13" x1="115.499" y1="72.0188" x2="204.313" y2="72.0188" gradientUnits="userSpaceOnUse">
<stop stop-opacity="0"/>
<stop offset="1" stop-opacity="0.4"/>
</linearGradient>
<clipPath id="clip0_601_13">
<rect width="275" height="125" rx="30" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1,44 @@
<svg width="275" height="125" viewBox="0 0 275 125" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_601_29)">
<rect width="275" height="125" rx="30" fill="url(#paint0_linear_601_29)"/>
<path d="M27.925 42H24.125L30.4 25.25H35L41.275 42H37.35L33.65 31.65L32.65 28.1L31.65 31.65L27.925 42ZM37.125 38.275H27.625V35.25H37.125V38.275ZM49.0236 37.15H43.4736V33.975H48.8986C49.5153 33.975 50.057 33.8833 50.5236 33.7C51.007 33.5167 51.382 33.225 51.6486 32.825C51.932 32.425 52.0736 31.9 52.0736 31.25C52.0736 30.55 51.932 30 51.6486 29.6C51.382 29.1833 51.007 28.8833 50.5236 28.7C50.057 28.5167 49.5153 28.425 48.8986 28.425H43.4736V25.25H49.0236C50.307 25.25 51.457 25.4583 52.4736 25.875C53.507 26.2917 54.3236 26.9417 54.9236 27.825C55.5236 28.6917 55.8236 29.8333 55.8236 31.25C55.8236 32.6333 55.5236 33.7583 54.9236 34.625C54.3403 35.4917 53.532 36.1333 52.4986 36.55C51.482 36.95 50.3236 37.15 49.0236 37.15ZM45.6486 42H42.0736V25.25H45.6486V42ZM61.2004 42H57.6254V25.25H62.3504L65.0754 31.475L66.6504 36.45L68.2254 31.475L70.9504 25.25H75.6754V42H72.1004V35.725L72.6004 28.975L70.6004 34.55L68.5254 39.1H64.7754L62.7004 34.55L60.6754 28.975L61.2004 35.725V42Z" fill="#B5530A"/>
<g style="mix-blend-mode:overlay" opacity="0.8">
<mask id="mask0_601_29" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="122" y="-15" width="154" height="154">
<rect x="122" y="18" width="125" height="125" transform="rotate(-15 122 18)" fill="url(#paint1_radial_601_29)"/>
</mask>
<g mask="url(#mask0_601_29)">
<rect x="178.355" y="10.9877" width="31.25" height="31.25" transform="rotate(-15 178.355 10.9877)" fill="black"/>
<rect x="217.141" y="41.0355" width="31.25" height="31.25" transform="rotate(-15 217.141 41.0355)" fill="black"/>
<rect x="181.974" y="90.8991" width="31.25" height="31.25" transform="rotate(-15 181.974 90.8991)" fill="black"/>
<rect x="124.022" y="25.5463" width="56.25" height="31.25" transform="rotate(-15 124.022 25.5463)" fill="url(#paint2_linear_601_29)"/>
<rect x="144.242" y="101.009" width="39.0625" height="31.25" transform="rotate(-15 144.242 101.009)" fill="url(#paint3_linear_601_29)"/>
<rect x="134.132" y="63.2778" width="85.9375" height="31.25" transform="rotate(-15 134.132 63.2778)" fill="url(#paint4_linear_601_29)"/>
</g>
</g>
</g>
<defs>
<linearGradient id="paint0_linear_601_29" x1="50.5" y1="-12" x2="244" y2="144.5" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFEAC7"/>
<stop offset="1" stop-color="#FACF9C"/>
</linearGradient>
<radialGradient id="paint1_radial_601_29" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(184.5 80.5) rotate(90) scale(66.4062)">
<stop offset="0.208333" stop-color="white"/>
<stop offset="1" stop-color="white" stop-opacity="0"/>
</radialGradient>
<linearGradient id="paint2_linear_601_29" x1="124.022" y1="25.5463" x2="180.272" y2="25.5463" gradientUnits="userSpaceOnUse">
<stop stop-opacity="0"/>
<stop offset="1" stop-opacity="0.4"/>
</linearGradient>
<linearGradient id="paint3_linear_601_29" x1="144.242" y1="101.009" x2="183.305" y2="101.009" gradientUnits="userSpaceOnUse">
<stop stop-opacity="0"/>
<stop offset="1" stop-opacity="0.4"/>
</linearGradient>
<linearGradient id="paint4_linear_601_29" x1="134.132" y1="63.2778" x2="220.07" y2="63.2778" gradientUnits="userSpaceOnUse">
<stop stop-opacity="0"/>
<stop offset="1" stop-opacity="0.4"/>
</linearGradient>
<clipPath id="clip0_601_29">
<rect width="275" height="125" rx="30" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -0,0 +1,27 @@
<svg width="275" height="125" viewBox="0 0 275 125" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_601_51)">
<rect width="275" height="125" rx="30" fill="url(#paint0_linear_601_51)"/>
<g style="mix-blend-mode:overlay">
<mask id="mask0_601_51" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="127" y="-29" width="195" height="196">
<rect x="127.168" y="12.7571" width="158.811" height="158.811" transform="rotate(-15 127.168 12.7571)" fill="url(#paint1_radial_601_51)"/>
</mask>
<g mask="url(#mask0_601_51)">
<path d="M189.83 16.5183L186.405 3.73504L224.755 -6.54079L228.18 6.24251L189.83 16.5183ZM221.453 83.4012L234.236 79.976L223.96 41.6261L211.177 45.0513L221.453 83.4012ZM241.545 132.822C233.662 134.934 225.825 135.408 218.032 134.245C210.242 133.076 203.038 130.58 196.42 126.759C189.801 122.938 184.039 117.947 179.131 111.785C174.227 105.619 170.719 98.5937 168.607 90.7107C166.495 82.8277 166.02 74.9898 167.184 67.1972C168.353 59.4077 170.848 52.2037 174.669 45.5852C178.491 38.9667 183.482 33.206 189.645 28.303C195.811 23.3946 202.835 19.8843 210.718 17.7721C217.323 16.0024 223.946 15.3693 230.589 15.8728C237.232 16.3764 243.759 17.9385 250.17 20.5594L256.721 9.21339L268.067 15.764L261.516 27.11C266.992 31.3517 271.608 36.2232 275.366 41.7244C279.123 47.2255 281.887 53.2785 283.657 59.8832C285.769 67.7662 286.243 75.6041 285.079 83.3967C283.91 91.1862 281.415 98.3902 277.594 105.009C273.773 111.627 268.781 117.39 262.62 122.297C256.453 127.201 249.428 130.71 241.545 132.822ZM238.12 120.039C250.477 116.727 259.853 109.534 266.248 98.4581C272.643 87.3822 274.184 75.6657 270.873 63.3085C267.562 50.9513 260.369 41.5754 249.293 35.1807C238.217 28.7861 226.5 27.2443 214.143 30.5554C201.786 33.8665 192.41 41.06 186.015 52.1358C179.621 63.2117 178.079 74.9282 181.39 87.2854C184.701 99.6426 191.895 109.019 202.971 115.413C214.046 121.808 225.763 123.35 238.12 120.039Z" fill="#1C1B1F"/>
</g>
</g>
<path d="M33.475 42H26.95V38.825H32.95C33.9 38.825 34.5917 38.6667 35.025 38.35C35.4583 38.0333 35.675 37.5333 35.675 36.85C35.675 36.4 35.5667 36.0333 35.35 35.75C35.15 35.4667 34.85 35.2583 34.45 35.125C34.05 34.975 33.5667 34.9 33 34.9H26.95V32.025H32.675C33.1417 32.025 33.5417 31.9667 33.875 31.85C34.225 31.7333 34.4917 31.55 34.675 31.3C34.875 31.0333 34.975 30.6833 34.975 30.25C34.975 29.7833 34.875 29.425 34.675 29.175C34.4917 28.9083 34.225 28.7167 33.875 28.6C33.525 28.4833 33.1083 28.425 32.625 28.425H26.95V25.25H33.575C34.8083 25.25 35.8 25.4417 36.55 25.825C37.3167 26.1917 37.875 26.7 38.225 27.35C38.575 28 38.75 28.7167 38.75 29.5C38.75 30.2167 38.6 30.825 38.3 31.325C38.0167 31.8083 37.6333 32.2 37.15 32.5C36.6667 32.7833 36.1167 33 35.5 33.15C34.8833 33.2833 34.25 33.3667 33.6 33.4C35.0167 33.45 36.1417 33.6583 36.975 34.025C37.825 34.3917 38.4333 34.8917 38.8 35.525C39.1667 36.1417 39.35 36.8417 39.35 37.625C39.35 38.675 39.1083 39.5167 38.625 40.15C38.1417 40.7833 37.4583 41.25 36.575 41.55C35.6917 41.85 34.6583 42 33.475 42ZM28.925 42H25.35V25.25H28.925V42ZM44.9871 42H41.4121V25.25H44.9871V42ZM51.0906 28.3H47.5156V25.25H51.0906V28.3ZM51.0906 42H47.5156V29.75H51.0906V42ZM58.4691 42.25C57.5691 42.25 56.8191 42.0833 56.2191 41.75C55.6358 41.4167 55.2025 40.9583 54.9191 40.375C54.6358 39.7917 54.4941 39.125 54.4941 38.375V28.3L58.0691 26.425V37.725C58.0691 38.225 58.1775 38.575 58.3941 38.775C58.6275 38.975 58.9191 39.075 59.2691 39.075C59.6525 39.075 60.0025 38.95 60.3191 38.7C60.6525 38.4333 60.9108 38.125 61.0941 37.775L62.1191 40.7C61.8525 41.0333 61.4108 41.375 60.7941 41.725C60.1775 42.075 59.4025 42.25 58.4691 42.25ZM61.4691 32.475H52.6691V29.75H61.4691V32.475ZM73.9848 42H62.7848V39.375L67.0348 34.725L69.6598 32.475L66.0348 32.65H63.1348V29.75H73.7348V32.375L69.4098 36.8L66.6348 39.25L70.4098 39.1H73.9848V42Z" fill="#8E23B4"/>
</g>
<defs>
<linearGradient id="paint0_linear_601_51" x1="50.5" y1="-12" x2="244" y2="144.5" gradientUnits="userSpaceOnUse">
<stop stop-color="#EAC7FF"/>
<stop offset="1" stop-color="#D19CFA"/>
</linearGradient>
<radialGradient id="paint1_radial_601_51" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(206.573 92.1626) rotate(90) scale(84.3683)">
<stop offset="0.208333" stop-color="white" stop-opacity="0.78"/>
<stop offset="1" stop-color="white" stop-opacity="0"/>
</radialGradient>
<clipPath id="clip0_601_51">
<rect width="275" height="125" rx="30" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@@ -0,0 +1,36 @@
<svg width="275" height="125" viewBox="0 0 275 125" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_601_57)">
<rect width="275" height="125" rx="30" fill="url(#paint0_linear_601_57)"/>
<g style="mix-blend-mode:overlay">
<mask id="mask0_601_57" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="122" y="-15" width="154" height="154">
<rect x="122" y="18" width="125" height="125" transform="rotate(-15 122 18)" fill="url(#paint1_radial_601_57)"/>
</mask>
<g mask="url(#mask0_601_57)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M190.033 96.8278L146.264 108.556L154.352 138.741L213.213 122.969L209.169 107.876L194.077 111.92L190.033 96.8278ZM275.093 106.388L231.324 118.116L223.236 87.9309L267.005 76.2032L275.093 106.388Z" fill="black"/>
<rect x="138.176" y="78.3704" width="45.3125" height="31.25" transform="rotate(-15 138.176 78.3704)" fill="black"/>
<rect x="215.148" y="57.7457" width="45.3125" height="31.25" transform="rotate(-15 215.148 57.7457)" fill="black"/>
<rect x="167.278" y="5.86786" width="31.25" height="62.5" transform="rotate(-15 167.278 5.86786)" fill="url(#paint2_linear_601_57)"/>
<rect x="198.547" y="62.1942" width="15.625" height="31.25" transform="rotate(-15 198.547 62.1942)" fill="black"/>
<rect x="179.41" y="51.1456" width="15.625" height="31.25" transform="rotate(-15 179.41 51.1456)" fill="black"/>
</g>
</g>
<path d="M28.925 42H25.35V25.25H28.925V42ZM37.3 42H26.6V38.825H37.3V42ZM37.685 32.425L36.985 30.625C37.4516 30.6083 37.8433 30.525 38.16 30.375C38.4933 30.225 38.66 29.9167 38.66 29.45V29.125H36.935V25.25H40.735V29.225C40.735 30.1917 40.4766 30.9417 39.96 31.475C39.46 32.0083 38.7016 32.325 37.685 32.425ZM49.8781 37.15H44.3281V33.975H49.7531C50.3698 33.975 50.9115 33.8833 51.3781 33.7C51.8615 33.5167 52.2365 33.225 52.5031 32.825C52.7865 32.425 52.9281 31.9 52.9281 31.25C52.9281 30.55 52.7865 30 52.5031 29.6C52.2365 29.1833 51.8615 28.8833 51.3781 28.7C50.9115 28.5167 50.3698 28.425 49.7531 28.425H44.3281V25.25H49.8781C51.1615 25.25 52.3115 25.4583 53.3281 25.875C54.3615 26.2917 55.1781 26.9417 55.7781 27.825C56.3781 28.6917 56.6781 29.8333 56.6781 31.25C56.6781 32.6333 56.3781 33.7583 55.7781 34.625C55.1948 35.4917 54.3865 36.1333 53.3531 36.55C52.3365 36.95 51.1781 37.15 49.8781 37.15ZM46.5031 42H42.9281V25.25H46.5031V42ZM62.0549 42H58.4799V25.25H63.2049L65.9299 31.475L67.5049 36.45L69.0799 31.475L71.8049 25.25H76.5299V42H72.9549V35.725L73.4549 28.975L71.4549 34.55L69.3799 39.1H65.6299L63.5549 34.55L61.5299 28.975L62.0549 35.725V42Z" fill="#4D7D0F"/>
</g>
<defs>
<linearGradient id="paint0_linear_601_57" x1="50.5" y1="-12" x2="244" y2="144.5" gradientUnits="userSpaceOnUse">
<stop stop-color="#EBFFC7"/>
<stop offset="1" stop-color="#D8FA9C"/>
</linearGradient>
<radialGradient id="paint1_radial_601_57" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(184.5 80.5) rotate(90) scale(66.4062)">
<stop offset="0.208333" stop-color="white"/>
<stop offset="1" stop-color="white" stop-opacity="0"/>
</radialGradient>
<linearGradient id="paint2_linear_601_57" x1="182.903" y1="5.86786" x2="182.903" y2="68.3679" gradientUnits="userSpaceOnUse">
<stop stop-opacity="0"/>
<stop offset="1" stop-opacity="0.4"/>
</linearGradient>
<clipPath id="clip0_601_57">
<rect width="275" height="125" rx="30" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -0,0 +1,30 @@
<svg width="88" height="88" viewBox="0 0 88 88" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_ddd_936_155)">
<circle cx="44" cy="44" r="10" fill="#4F9DFF"/>
<circle cx="44" cy="44" r="8.5" stroke="#FAFAFA" stroke-width="3"/>
</g>
<defs>
<filter id="filter0_ddd_936_155" x="0" y="0" width="88" height="88" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="12"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.309804 0 0 0 0 0.615686 0 0 0 0 1 0 0 0 1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_936_155"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="7"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.309804 0 0 0 0 0.615686 0 0 0 0 1 0 0 0 1 0"/>
<feBlend mode="normal" in2="effect1_dropShadow_936_155" result="effect2_dropShadow_936_155"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="17"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.309804 0 0 0 0 0.615686 0 0 0 0 1 0 0 0 1 0"/>
<feBlend mode="normal" in2="effect2_dropShadow_936_155" result="effect3_dropShadow_936_155"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect3_dropShadow_936_155" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,92 @@
(C) Copyright 20192022 26F Studio.
This Font Software is licensed under the SIL Open Font License,
Version 1.1. This license is copied below, and is also available at
https://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@@ -0,0 +1,58 @@
Fontshare EULA
---—---------------------------------—------------------------------
Free Font - End User License Agreement (FF EULA)
---—---------------------------------—------------------------------
Notice to User
Indian Type Foundry designs, produces and distributes font software as digital fonts to end users worldwide. In addition to commercial fonts that are available for a fee, ITF also offers several fonts which can be used free of charge. The free fonts are distributed through a dedicated platform called www.fontshare.com (“Fontshare”) to end users worldwide. These free fonts are subject to this legally binding EULA between the Indian Type Foundry (“Indian Type Foundry” or “Licensor”) and you (“Licensee”). 
You acknowledge that the Font Software and designs embodied therein are protected by the copyright, other intellectual property rights and industrial property rights and by international treaties. They are and remain at all times the intellectual property of the Indian Type Foundry.
In addition to direct download, Fontshare also offers these free fonts via Fonthsare API using a code. In this case, the Font Software is delivered directly from the servers used by Indian Type Foundry to the Licensee's website, without the Licensee having to download the Font Software.
By downloading, accessing the API, installing, storing, copying or using one of any Font Software, you agree to the following terms. 
Definitions
“Font Software” refers to the set of computer files or programs released under this license that instructs your computer to display and/or print each letters, characters, typographic designs, ornament and so forth. Font Software includes all bitmap and vector representations of fonts and typographic representations and embellishments created by or derived from the Font Software. 
“Original Version” refers to the Font Software as distributed by the Indian Type Foundry as the copyright holder. 
“Derivative Work” refers to the pictorial representation of the font created by the Font Software, including typographic characters such as letters, numerals, ornaments, symbols, or punctuation and special characters.
01. Grant of License
You are hereby granted a non-exclusive, non-assignable, non-transferrable, terminable license to access, download and use the Font Software for your personal or commercial use for an unlimited period of time for free of charge. 
You may use the font Software in any media (including Print, Web, Mobile, Digital, Apps, ePub, Broadcasting and OEM) at any scale, at any location worldwide. 
You may use the Font Software to create logos and other graphic elements, images on any surface, vector files or other scalable drawings and static images. 
You may use the Font Software on any number of devices (computer, tablet, phone). The number of output devices (Printers) is not restricted. 
You may make only such reasonable number of back-up copies suitable to your permitted use. 
You may but are not required to identify Indian Type Foundry Fonts in your work credits. 
02. Limitations of usage
You may not modify, edit, adapt, translate, reverse engineer, decompile or disassemble, alter or otherwise copy the Font Software or the designs embodied therein in whole or in part, without the prior written consent of the Licensor. 
The Fonts may not - beyond the permitted copies and the uses defined herein - be distributed, duplicated, loaned, resold or licensed in any way, whether by lending, donating or give otherwise to a person or entity. This includes the distribution of the Fonts by e-mail, on USB sticks, CD-ROMs, or other media, uploading them in a public server or making the fonts available on peer-to-peer networks. A passing on to external designers or service providers (design agencies, repro studios, printers, etc.) is also not permitted. 
You are not allowed to transmit the Font Software over the Internet in font serving or for font replacement by means of technologies such as but not limited to EOT, Cufon, sIFR or similar technologies that may be developed in the future without the prior written consent of the Licensor. 
03. Embedding
You may embed the Font Software in PDF and other digital documents provided that is done in a secured, read-only mode. It must be ensured beyond doubt that the recipient cannot use the Font Software to edit or to create new documents. The design data (PDFs) created in this way and under these created design data (PDFs) may be distributed in any number. 
The extraction of the Font Software in whole or in part is prohibited. 
04. Third party use, Commercial print service provider
You may include the Font Software in a non-editable electronic document solely for printing and display purposes and provide that electronic document to the commercial print service provider for the purpose of printing. If the print service needs to install the fonts, they too need to download the Font Software from the Licensor's website.
05. Derivative Work
You are allowed to make derivative works as far as you use them for your personal or commercial use. However, you cannot modify, make changes or reverse engineer the original font software provided to you. Any derivative works are the exclusive property of the Licensor and shall be subject to the terms and conditions of this EULA. Derivative works may not be sub-licensed, sold, leased, rented, loaned, or given away without the express written permission of the Licensor. 
06. Warranty and Liability
BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, INDIAN TYPE FOUNDRY MAKES NO WARRANTIES, EXPRESS OR IMPLIED AS TO THE MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR OTHERWISE. THE FONT SOFTWARE WAS NOT MANUFACTURED FOR USE IN MANUFACTURING CONTROL DEVICES OR NAVIGATION DEVICES OR IN CIRCUMSTANCES THAT COULD RESULT IN ENVIRONMENTAL DAMAGE OR PERSONAL INJURY. WITHOUT LIMITING THE FOREGOING, INDIAN TYPE FOUNDRY SHALL IN NO EVENT BE LIABLE TO THE LICENSED USER OR ANY OTHER THIRD PARTY FOR ANY DIRECT, CONSEQUENTIAL OR INCIDENTAL DAMAGES, INCLUDING DAMAGES FROM LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION NOR FOR LOST PROFITS OR SAVINGS ARISING OUT OF THE USE OR INABILITY TO USE THE PRODUCT EVEN IF NOTIFIED IN ADVANCE, UNDER NO CIRCUMSTANCES SHALL INDIAN TYPE FOUNDRYS LIABILITY EXCEED THE REPLACEMENT COST OF THE SOFTWARE. 
IF LICENSEE CHOOSES TO ACCESS THE FONT SOFTWARE THROUGH A CODE (API), IT MAY HAVE A DIRECT IMPACT ON LICENSEE'S WEBSITE OR APPLICATIONS. INDIAN TYPE FOUNDRY IS NOT RESPONSIBLE OR LIABLE FOR ANY INTERRUPTION, MALFUNCTION, DOWNTIME OR OTHER FAILURE OF THE WEBSITE OR ITS API.
07. Updates, Maintenance and Support Services
Licensor will not provide you with any support services for the Software under this Agreement.
08. Termination 
Any breach of the terms of this agreement shall be a cause for termination, provided that such breach is notified in writing to the Licensee by the Licensor and the Licensee failed to rectify the breach within 30 days of the receipt of such notification. 
In the event of termination and without limitation of any remedies under law or equity, you must delete the Font Software and all copies thereof. Proof of this must be provided upon request of the Licensor.  
We reserve the right to claim damages for the violation of the conditions. 
09. Final Provisions
If individual provisions of this agreement are or become invalid, the validity of the remaining provisions shall remain unaffected. Invalid provisions shall be replaced by mutual agreement by such provisions that are suitable to achieve the desired economic purpose, taking into account the interests of both parties. The same shall apply mutatis mutandis to the filling of any gaps which may arise in this agreement.
This contract is subject to laws of the Republic of India. Place of performance and exclusive place of jurisdiction for all disputes between the parties arising out of or in connection with this contract is, as far as legally permissible, Ahmedabad, India.
- 
Last Updated on 22 March 2021
Copyright 2021 Indian Type Foundry. All rights reserved. 

View File

@@ -0,0 +1,96 @@
Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font
Name 'Source'. Source is a trademark of Adobe in the United States
and/or other countries.
This Font Software is licensed under the SIL Open Font License,
Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font
creation efforts of academic and linguistic communities, and to
provide a free and open framework in which fonts may be shared and
improved in partnership with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply to
any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software
components as distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to,
deleting, or substituting -- in part or in whole -- any of the
components of the Original Version, by changing formats or by porting
the Font Software to a new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed,
modify, redistribute, and sell modified and unmodified copies of the
Font Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components, in
Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the
corresponding Copyright Holder. This restriction only applies to the
primary font name as presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created using
the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 45 KiB

View File

@@ -0,0 +1,7 @@
<svg width="71" height="71" viewBox="0 0 71 71" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M53.1133 39.2642L42.3935 49.9446C41.8086 50.5274 41.5161 50.8188 41.1788 50.928C40.8822 51.024 40.5626 51.024 40.2659 50.928C39.9287 50.8188 39.6362 50.5274 39.0512 49.9446L30.2776 41.2032L21.5041 49.9446C20.9191 50.5274 20.6266 50.8188 20.2893 50.928C19.9927 51.024 19.6731 51.024 19.3764 50.928C19.0392 50.8188 18.7467 50.5274 18.1617 49.9446L16.1772 47.9673L30.2776 33.9187L39.2299 42.8381C40.0952 43.7002 41.498 43.7002 42.3633 42.8381L53.1133 32.1276V39.2642Z" fill="#EA5252"/>
<path d="M57.5446 34.8491L59.9407 32.4618C60.5256 31.879 60.8181 31.5876 60.9277 31.2516C61.0241 30.956 61.0241 30.6376 60.9277 30.342C60.8181 30.006 60.5256 29.7146 59.9407 29.1318L57.5439 26.7438C57.5444 26.7619 57.5446 26.78 57.5446 26.7982L57.5446 34.8491Z" fill="#EA5252"/>
<path d="M55.3835 24.5913C55.3653 24.5908 55.3472 24.5906 55.3289 24.5906L46.9514 24.5906L49.4959 22.0554C50.0809 21.4726 50.3734 21.1812 50.7107 21.072C51.0073 20.976 51.3269 20.976 51.6236 21.072C51.9608 21.1812 52.2533 21.4726 52.8383 22.0554L55.3835 24.5913Z" fill="#EA5252"/>
<path d="M42.5201 29.0057L40.7224 30.7968L31.9488 22.0554C31.3638 21.4726 31.0714 21.1812 30.7341 21.072C30.4374 20.976 30.1179 20.976 29.8212 21.072C29.4839 21.1812 29.1914 21.4726 28.6065 22.0554L11.0593 39.5382C10.4744 40.121 10.1819 40.4124 10.0723 40.7484C9.9759 41.044 9.9759 41.3624 10.0723 41.658C10.1819 41.994 10.4744 42.2854 11.0593 42.8682L13.0438 44.8454L28.7109 29.2358C29.5762 28.3737 30.9791 28.3737 31.8443 29.2358L40.7966 38.1552L49.9799 29.0057H42.5201Z" fill="#EA5252"/>
<circle cx="35.5" cy="35.5" r="29.5" stroke="#EA5252" stroke-width="2"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@@ -0,0 +1,3 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M50 160V200H0V0H120V20H85V50H50V108L120 95V115H180V80H200V200H150V142L50 160ZM135 100V65H100V35H135V0H165V35H200V65H165V100H135Z" fill="#D8FA9C"/>
</svg>

After

Width:  |  Height:  |  Size: 303 B

View File

@@ -0,0 +1,3 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M50 160V200H0V0H160L178 18H95V50H50V108L150 90V78H200V200H150V142L50 160ZM200 33V63H110V33H200Z" fill="#D8FA9C"/>
</svg>

After

Width:  |  Height:  |  Size: 270 B

View File

@@ -0,0 +1,3 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M50 160V200H0V0H160L200 40V200H150V142L50 160ZM150 50H50V108L150 90V50Z" fill="#D8FA9C"/>
</svg>

After

Width:  |  Height:  |  Size: 246 B

View File

@@ -0,0 +1,3 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M200 160V115L185 100L200 85V80H180V115H120V80H85V75H50V50H85V20H120V0H0V200H160L200 160ZM49.9998 125H150V150H49.9998V125ZM135 100V65H99.9998V35H135V1.03335e-05H165V35H200V65H165V100H135Z" fill="#B9E6FD"/>
</svg>

After

Width:  |  Height:  |  Size: 361 B

View File

@@ -0,0 +1,3 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M200 160V115L185 100L200 85V80H95V75H50V50H95V20H180L160 0H0V200H160L200 160ZM50 125H150V150H50V125ZM110 35H200V65H110V35Z" fill="#B9E6FD"/>
</svg>

After

Width:  |  Height:  |  Size: 297 B

View File

@@ -0,0 +1,3 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M200 160L160 200H0V0H160L200 40V85L185 100L200 115V160ZM50 50H150V75H50V50ZM50 125H150V150H50V125Z" fill="#B9E6FD"/>
</svg>

After

Width:  |  Height:  |  Size: 273 B

View File

@@ -0,0 +1,3 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M200 200V150H50V50H85V20H120V0H40L0 40V160L40 200H200ZM135 65V100H165V65H200V35H165V0H135V35H100V65H135ZM200 0V20H180V0H200Z" fill="#DDD6FF"/>
</svg>

After

Width:  |  Height:  |  Size: 299 B

View File

@@ -0,0 +1,3 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M200 200H40L0 160V40L40 0H200V20H95V50H50V150H200V200ZM200 35V65H110V35H200Z" fill="#DDD6FF"/>
</svg>

After

Width:  |  Height:  |  Size: 251 B

View File

@@ -0,0 +1,3 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M40 200H200V150H50V50H200V0H40L0 40V160L40 200Z" fill="#DDD6FF"/>
</svg>

After

Width:  |  Height:  |  Size: 182 B

View File

@@ -0,0 +1,3 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M200 80V160L160 200H0V0H120V20H85V50H50V150H150V115H180V80H200ZM135 100V65H100V35H135V0H165V35H200V65H165V100H135Z" fill="#F5CFFE"/>
</svg>

After

Width:  |  Height:  |  Size: 289 B

View File

@@ -0,0 +1,3 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M160 200H0V0H160L200 40V160L160 200ZM150 50H50V150H150V50Z" fill="#F5CFFE"/>
</svg>

After

Width:  |  Height:  |  Size: 233 B

View File

@@ -0,0 +1,3 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M200 160V115L180 95V115H120V80H85V75H50V50H85V20H120V0H40L0 40V85L40 125H150V150H0V200H160L200 160ZM135 65V100H165V65H200V35H165V0H135V35H100V65H135ZM200 0V20H180V0H200Z" fill="#FEF18B"/>
</svg>

After

Width:  |  Height:  |  Size: 344 B

View File

@@ -0,0 +1,3 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M200 160L160 200H0V150H150V125H40L0 85V40L40 0H200V18H95V50H50V75H95V78H163L200 115V160ZM200 33V63H110V33H200Z" fill="#FEF18B"/>
</svg>

After

Width:  |  Height:  |  Size: 285 B

View File

@@ -0,0 +1,3 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M160 200L200 160V115L160 75H50V50H200V0H40L0 40V85L40 125H150V150H0V200H160Z" fill="#FEF18B"/>
</svg>

After

Width:  |  Height:  |  Size: 211 B

View File

@@ -0,0 +1,3 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M100 200L140 160V115L100 75H50V50H140V0H40L0 40V85L40 125H90V150H0V200H100ZM160 200L200 160V115L160 75H121L155 109V166L121 200H160ZM200 0V50H155V0H200Z" fill="#FEF18B"/>
</svg>

After

Width:  |  Height:  |  Size: 326 B

View File

@@ -0,0 +1,3 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M40 200H160L200 160V0H150V150H50V0H0V160L40 200Z" fill="#FECBCA"/>
</svg>

After

Width:  |  Height:  |  Size: 183 B

View File

@@ -0,0 +1,3 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M139 200H200L62 0H0L139 200ZM61 200L91 157L44 90H34L55 120L0 200H61ZM166 109H156L109 42L138 0H200L145 79L166 109Z" fill="#F5CFFE"/>
</svg>

After

Width:  |  Height:  |  Size: 288 B

View File

@@ -0,0 +1,3 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M61 200H0L138 0H200L61 200Z" fill="#D7D3D0"/>
</svg>

After

Width:  |  Height:  |  Size: 162 B

View File

@@ -0,0 +1,81 @@
from base64 import b64decode, b64encode
from io import BytesIO
from typing import Literal, overload
from nonebot_plugin_userinfo import UserInfo # type: ignore[import-untyped]
from PIL import Image
from ..templates import path
from .browser import BrowserManager
@overload
async def get_avatar(user: UserInfo, scheme: Literal['Data URI'], default: str | None) -> str:
"""获取用户头像的指定格式
Args:
user (UserInfo): 要获取的用户
scheme (Literal[&#39;Data URI&#39;]): 格式
default (str | None): 获取不到时的默认值
Raises:
TypeError: Can't get avatar: 当获取不到头像并且没有设置默认值时抛出
TypeError: Can't get avatar format: 当获取到的头像无法识别格式时抛出
Returns:
str: Data URI 格式的头像
"""
@overload
async def get_avatar(user: UserInfo, scheme: Literal['bytes'], default: str | None) -> bytes:
"""获取用户头像的指定格式
Args:
user (UserInfo): 要获取的用户
scheme (Literal[&#39;bytes&#39;]): 格式
default (str | None): 获取不到时的默认值
Returns:
bytes: bytes 格式的头像
"""
async def get_avatar(user: UserInfo, scheme: Literal['Data URI', 'bytes'], default: str | None) -> str | bytes:
if user.user_avatar is None:
if default is None:
raise TypeError("Can't get avatar")
return default
bot_avatar = await user.user_avatar.get_image()
if scheme == 'Data URI':
avatar_format = Image.open(BytesIO(bot_avatar)).format
if avatar_format is None:
raise TypeError("Can't get avatar format")
return f'data:{Image.MIME[avatar_format]};base64,{b64encode(bot_avatar).decode()}'
return bot_avatar
async def generate_identicon(hash: str) -> bytes: # noqa: A002
"""使用 identicon 生成头像
Args:
hash (str): 提交给 identicon 的 hash 值
Returns:
bytes: identicon 生成的 svg 的二进制数据
"""
browser = await BrowserManager.get_browser()
async with await browser.new_page() as page:
await page.add_script_tag(path=path / 'js/identicon.js')
return b64decode(
await page.evaluate(rf"""
new Identicon('{hash}', {{
background: [0x08, 0x0a, 0x06, 255],
margin: 0.15,
size: 300,
brightness: 0.48,
saturation: 0.65,
format: 'svg',
}}).toString();
""")
)

View File

@@ -1,6 +1,7 @@
import sys
from os import environ
from platform import system
from re import sub
from nonebot import get_driver
from nonebot.log import logger
@@ -33,12 +34,12 @@ class BrowserManager:
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'])):
if 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'])):
if cls._call_playwright(['', 'install', 'firefox']):
logger.success('安装/更新 playwright 浏览器成功')
else:
logger.error('安装/更新 playwright 浏览器失败')
@@ -52,26 +53,20 @@ class BrowserManager:
logger.success('playwright 启动成功')
@classmethod
def _call_playwright(cls, argv: list[str]) -> BaseException:
def _call_playwright(cls, argv: list[str]) -> bool:
"""等价于调用 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
except SystemExit as e:
return e.code == 0
except BaseException: # noqa: BLE001
return False
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
return True
@classmethod
async def _start_browser(cls) -> Browser:

View File

@@ -15,21 +15,25 @@ class NeedCatchError(TetrisStatsError):
"""需要被捕获的异常基类"""
class DoNotCatchError(TetrisStatsError):
"""不应该被捕获的异常基类"""
class RequestError(NeedCatchError):
"""请求错误"""
def __init__(self, message: str = '', *, status_code: int | None = None):
super().__init__(message)
self.status_code = status_code
class MessageFormatError(NeedCatchError):
"""用户发送的消息格式不正确"""
class DatabaseVersionError(DoNotCatchError):
"""数据库版本错误"""
class DoNotCatchError(TetrisStatsError):
"""不应该被捕获的异常基类"""
class WhatTheFuckError(DoNotCatchError):
"""用于表示不应该出现的情况 ("""
class HandleNotFinishedError(DoNotCatchError):
"""任务没有正常完成处理的错误"""

View File

@@ -0,0 +1,82 @@
from hashlib import sha256
from ipaddress import IPv4Address, IPv6Address
from typing import ClassVar
from aiofiles import open
from fastapi import FastAPI, Query, Response, status
from fastapi.responses import FileResponse, HTMLResponse
from fastapi.staticfiles import StaticFiles
from nonebot import get_app, get_driver
from nonebot.log import logger
from nonebot_plugin_localstore import get_cache_dir # type: ignore[import-untyped]
from pydantic import IPvAnyAddress
from ..templates import path
from .avatar import generate_identicon
app = get_app()
driver = get_driver()
global_config = driver.config
cache_dir = get_cache_dir('nonebot_plugin_tetris_stats')
if not isinstance(app, FastAPI):
raise RuntimeError('本插件需要 FastAPI 驱动器才能运行') # noqa: TRY004
NOT_FOUND = HTMLResponse('404 Not Found', status_code=status.HTTP_404_NOT_FOUND)
class HostPage:
pages: ClassVar[dict[str, str]] = {}
def __init__(self, page: str) -> None:
self.page_hash = sha256(page.encode()).hexdigest()
self.pages[self.page_hash] = page
async def __aenter__(self) -> str:
return self.page_hash
async def __aexit__(self, exc_type, exc, tb) -> None: # noqa: ANN001
self.pages.pop(self.page_hash, None)
app.mount(
'/static',
StaticFiles(directory=path),
name='static',
)
@app.get('/host/page/{page_hash}.html', status_code=status.HTTP_200_OK)
async def _(page_hash: str) -> HTMLResponse:
if page_hash in HostPage.pages:
return HTMLResponse(HostPage.pages[page_hash])
return NOT_FOUND
@app.get('/identicon')
async def _(md5: str = Query(regex=r'^[a-fA-F0-9]{32}$')):
identicon_path = cache_dir / 'identicon' / f'{md5}.svg'
if identicon_path.exists() is False:
identicon_path.parent.mkdir(parents=True, exist_ok=True)
result = await generate_identicon(md5)
async with open(identicon_path, mode='xb') as file:
await file.write(result)
return Response(result, media_type='image/svg+xml')
logger.debug('Identicon Cache hit!')
return FileResponse(identicon_path, media_type='image/svg+xml')
def get_self_netloc() -> str:
host: IPv4Address | IPv6Address | IPvAnyAddress = global_config.host
if isinstance(host, IPv4Address):
if host == IPv4Address('0.0.0.0'): # noqa: S104
host = IPv4Address('127.0.0.1')
netloc = f'{host}:{global_config.port}'
else:
if host == IPv6Address('::'):
host = IPv6Address('::1')
netloc = f'[{host}]:{global_config.port}'
return netloc

View File

@@ -144,7 +144,7 @@ class TetrisMetricsProWithLPMADPM(TetrisMetricsBasicWithLPM, TetrisMetricsBaseWi
@overload
def get_metrics( # noqa: PLR0913
def get_metrics(
*,
pps: Number,
lpm: None = None,
@@ -157,7 +157,7 @@ def get_metrics( # noqa: PLR0913
@overload
def get_metrics( # noqa: PLR0913
def get_metrics(
*,
pps: None = None,
lpm: Number,
@@ -170,7 +170,7 @@ def get_metrics( # noqa: PLR0913
@overload
def get_metrics( # noqa: PLR0913
def get_metrics(
*,
pps: None = None,
lpm: None = None,
@@ -183,7 +183,7 @@ def get_metrics( # noqa: PLR0913
@overload
def get_metrics( # noqa: PLR0913
def get_metrics(
*,
pps: None = None,
lpm: None = None,
@@ -196,7 +196,7 @@ def get_metrics( # noqa: PLR0913
@overload
def get_metrics( # noqa: PLR0913
def get_metrics(
*,
pps: Number,
lpm: None = None,
@@ -209,7 +209,7 @@ def get_metrics( # noqa: PLR0913
@overload
def get_metrics( # noqa: PLR0913
def get_metrics(
*,
pps: None = None,
lpm: Number,
@@ -222,7 +222,7 @@ def get_metrics( # noqa: PLR0913
@overload
def get_metrics( # noqa: PLR0913
def get_metrics(
*,
pps: Number,
lpm: None = None,
@@ -235,7 +235,7 @@ def get_metrics( # noqa: PLR0913
@overload
def get_metrics( # noqa: PLR0913
def get_metrics(
*,
pps: Number,
lpm: None = None,
@@ -248,7 +248,7 @@ def get_metrics( # noqa: PLR0913
@overload
def get_metrics( # noqa: PLR0913
def get_metrics(
*,
pps: None = None,
lpm: Number,
@@ -261,7 +261,7 @@ def get_metrics( # noqa: PLR0913
@overload
def get_metrics( # noqa: PLR0913
def get_metrics(
*,
pps: None = None,
lpm: Number,

View File

@@ -1,4 +1,4 @@
from datetime import UTC, datetime
from datetime import datetime, timezone
from typing import ClassVar
from nonebot import get_driver, get_plugin
@@ -9,12 +9,15 @@ from nonebot_plugin_orm import get_session
from ..db.models import HistoricalData
UTC = timezone.utc
driver = get_driver()
class Recorder:
matchers: ClassVar[set[type[Matcher]]] = set()
historical_data: ClassVar[dict[int, tuple[HistoricalData, bool]]] = {}
error_event: ClassVar[set[int]] = set()
@classmethod
def create_historical_data(cls, event_id: int, historical_data: HistoricalData) -> None:
@@ -32,17 +35,27 @@ class Recorder:
@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)
historical_data, completed = cls.del_historical_data(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)
def del_historical_data(cls, event_id: int) -> tuple[HistoricalData, bool]:
return cls.historical_data.pop(event_id)
@classmethod
def add_error_event(cls, event_id: int) -> None:
cls.error_event.add(event_id)
@classmethod
def del_error_event(cls, event_id: int) -> None:
cls.error_event.remove(event_id)
@classmethod
def is_error_event(cls, event_id: int) -> bool:
return event_id in cls.error_event
@driver.on_startup
@@ -73,7 +86,9 @@ def _(bot: Bot, event: Event, matcher: Matcher):
@run_postprocessor
async def _(event: Event, matcher: Matcher, exception: Exception | None):
if isinstance(matcher, tuple(Recorder.matchers)):
event_id = id(event)
if exception is not None:
Recorder.del_historical_data(id(event))
Recorder.add_error_event(event_id)
Recorder.del_historical_data(event_id)
else:
await Recorder.save_historical_data(id(event))
await Recorder.save_historical_data(event_id)

View File

@@ -0,0 +1,69 @@
from typing import Any, Literal, overload
from jinja2 import Environment, FileSystemLoader
from ..game_data_processor.io_data_processor.typing import Rank
from ..templates import path
from .typing import GameType
Bind = Literal['bind.j2.html']
Data = Literal['data.j2.html']
env = Environment(
loader=FileSystemLoader(path), autoescape=True, trim_blocks=True, lstrip_blocks=True, enable_async=True
)
@overload
async def render(
template: Bind,
*,
user_avatar: str,
state: Literal['error', 'success', 'unknown', 'unlink', 'unverified'],
bot_avatar: str,
game_type: GameType,
user_name: str,
bot_name: str,
command: str,
) -> str: ...
@overload
async def render(
template: Data,
*,
user_avatar: str,
user_name: str,
user_sign: str,
game_type: Literal['TETR.IO'],
ranking: str | float,
rd: str | float,
rank: Rank,
TR: str | float, # noqa: N803
global_rank: str | int,
lpm: str | float,
pps: str | float,
apm: str | float,
apl: str | float,
adpm: str | float,
adpl: str | float,
vs: str | float,
sprint: str,
blitz: str,
data: list[list[int]],
split_value: int,
value_max: int,
value_min: int,
offset: int,
app: str | float,
dspp: str | float,
OR: str | float, # noqa: N803
ci: str | float,
ge: str | float,
) -> str: ...
async def render(template: Bind | Data, **kwargs: Any) -> str:
if kwargs['game_type'] == 'IO':
kwargs['game_type'] = 'TETR.IO'
return await env.get_template(template).render_async(**kwargs)

View File

@@ -1,17 +1,20 @@
from collections.abc import Sequence
from http import HTTPStatus
from urllib.parse import urljoin, urlparse
from aiofiles import open
from httpx import AsyncClient, HTTPError
from nonebot import get_driver
from nonebot import get_driver, get_plugin_config
from nonebot.log import logger
from playwright.async_api import Response
from ujson import JSONDecodeError, dumps, loads
from ..config.config import CACHE_PATH
from ..config.config import CACHE_PATH, Config
from .browser import BrowserManager
from .exception import RequestError
driver = get_driver()
config = get_plugin_config(Config)
@driver.on_startup
@@ -37,7 +40,7 @@ def splice_url(url_list: list[str]) -> str:
class Request:
"""网络请求相关类"""
_CACHE_FILE = CACHE_PATH.joinpath('cloudflare_cache.json')
_CACHE_FILE = CACHE_PATH / 'cloudflare_cache.json'
_headers: dict | None = None
_cookies: dict | None = None
@@ -45,36 +48,31 @@ class Request:
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()
async with await browser.new_context() as context, await context.new_page() as 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:
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()
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
return await response.body()
raise RequestError('绕过五秒盾失败')
@classmethod
@@ -115,14 +113,49 @@ class Request:
async def request(cls, url: str, *, is_json: bool = True) -> bytes:
"""请求api"""
try:
async with AsyncClient(cookies=cls._cookies) as session:
async with AsyncClient(cookies=cls._cookies, timeout=config.tetris_req_timeout) as session:
response = await session.get(url, headers=cls._headers)
if response.status_code != HTTPStatus.OK:
raise RequestError(
f'请求错误 code: {response.status_code} {HTTPStatus(response.status_code).phrase}\n{response.text}',
status_code=response.status_code,
)
if is_json:
loads(response.content)
return response.content
except HTTPError as e:
raise RequestError(f'请求错误\n{e!r}') from 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
@classmethod
async def failover_request(
cls,
urls: Sequence[str],
*,
failover_code: Sequence[int],
failover_exc: tuple[type[BaseException], ...],
is_json: bool = True,
) -> bytes:
error_list: list[RequestError] = []
for i in urls:
logger.debug(f'尝试请求 {i}')
try:
return await cls.request(i, is_json=is_json)
except RequestError as e:
if e.status_code in failover_code: # 如果状态码在 failover_code 中, 则继续尝试下一个URL
error_list.append(e)
continue
# 如果状态码不在故障转移列表中, 则查找异常栈, 如果异常栈内有 failover_exc 内的异常类型, 则继续尝试下一个URL
tb = e.__traceback__
while tb is not None:
if isinstance(tb.tb_frame.f_locals.get('exc_value'), failover_exc):
error_list.append(e)
break
tb = tb.tb_next
else:
raise
continue
raise RequestError(f'所有地址皆不可用\n{error_list!r}')

View File

@@ -0,0 +1,37 @@
from asyncio import sleep
from collections.abc import Awaitable, Callable
from datetime import timedelta
from functools import wraps
from typing import TypeVar, cast
from nonebot.log import logger
T = TypeVar('T')
def retry(
max_attempts: int = 3,
exception_type: type[BaseException] | tuple[type[BaseException], ...] = Exception,
delay: timedelta | None = None,
) -> Callable[[Callable[..., Awaitable[T]]], Callable[..., Awaitable[T]]]:
def decorator(func: Callable[..., Awaitable[T]]) -> Callable[..., Awaitable[T]]:
@wraps(func)
async def wrapper(*args, **kwargs) -> T: # noqa: ANN002, ANN003
attempts = 0
while attempts < max_attempts + 1:
try:
return await func(*args, **kwargs)
except exception_type as e: # noqa: PERF203
logger.exception(e)
attempts += 1
if attempts <= max_attempts:
if delay is not None:
await sleep(delay.total_seconds())
logger.debug(f'Retrying: {func.__name__} ({attempts}/{max_attempts})')
continue
raise
raise RuntimeError('Unexpectedly reached the end of the retry loop')
return cast(Callable[..., Awaitable[T]], wrapper)
return decorator

View File

@@ -0,0 +1,10 @@
from .browser import BrowserManager
async def screenshot(url: str) -> bytes:
browser = await BrowserManager.get_browser()
async with (
await browser.new_page(no_viewport=True, viewport={'width': 0, 'height': 0}) as page,
):
await page.goto(url)
return await page.screenshot(full_page=True, type='png')

2582
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = 'nonebot-plugin-tetris-stats'
version = '1.0.0.a3'
version = '1.0.0'
description = '一款基于 NoneBot2 的用于查询 Tetris 相关游戏数据的插件'
authors = ['scdhh <wallfjjd@gmail.com>']
readme = 'README.md'
@@ -10,33 +10,39 @@ license = 'AGPL-3.0'
[tool.poetry.dependencies]
python = '^3.10'
nonebot2 = '^2.0.0-beta.3'
lxml = '^4.9.1'
nonebot2 = { extras = ["fastapi"], version = "^2.3.0" }
lxml = '^5.1.0'
pandas = '>=1.4.3,<3.0.0'
playwright = '^1.24.1'
ujson = '^5.4.0'
playwright = '^1.41.2'
ujson = '^5.9.0'
aiofiles = "^23.2.1"
nonebot-plugin-orm = ">=0.1.1,<0.6.0"
nonebot-plugin-localstore = "^0.5.1"
httpx = "^0.25.0"
nonebot-plugin-alconna = ">=0.30,<0.34"
nonebot-plugin-apscheduler = "^0.3.0"
nonebot-plugin-orm = ">=0.1.1,<0.8.0"
nonebot-plugin-localstore = "^0.6.0"
httpx = "^0.27.0"
nonebot-plugin-alconna = ">=0.40"
nonebot-plugin-apscheduler = "^0.4.0"
aiocache = "^0.12.2"
zstandard = "^0.22.0"
jinja2 = "^3.1.3"
nonebot-plugin-userinfo = "^0.2.4"
pillow = "^10.3.0"
[tool.poetry.group.dev.dependencies]
mypy = '>=0.991,<1.8'
types-ujson = '^5.7.0'
mypy = '>=1.9'
types-ujson = '^5.9.0'
pandas-stubs = '>=1.5.2,<3.0.0'
ruff = '>=0.0.239,<0.1.6'
types-aiofiles = "^23.2.0.0"
nonebot2 = { extras = ["fastapi"], version = "^2.1.1" }
types-lxml = "^2023.3.28"
nonebot-plugin-orm = { extras = ["default"], version = ">=0.3,<0.6" }
nonebot-adapter-onebot = "^2.3.1"
nonebot-adapter-satori = "^0.7.0"
ruff = '>=0.3.0'
types-aiofiles = "^23.2.0.20240106"
types-lxml = "^2024.2.9"
nonebot-plugin-orm = { extras = ["default"], version = ">=0.3,<0.8" }
nonebot-adapter-onebot = "^2.4.1"
nonebot-adapter-satori = "^0.11.4"
nonebot-adapter-kaiheila = "^0.3.4"
nonebot-adapter-discord = "^0.1.3"
[tool.poetry.group.debug.dependencies]
objprint = '^0.2.2'
viztracer = "^0.16.0"
viztracer = "^0.16.2"
[build-system]
requires = ['poetry-core>=1.0.0']