diff --git a/CONTRIBUTING.en-US.md b/CONTRIBUTING.en-US.md new file mode 100644 index 0000000..945d2cd --- /dev/null +++ b/CONTRIBUTING.en-US.md @@ -0,0 +1,67 @@ +# How to Contribute? + +## Setting Up the Environment + +### For Developers with Basic Python Knowledge + +First, you need [Python **3.10**](https://www.python.org/) and [Poetry](https://python-poetry.org/). +Then, you need to clone this repository to your local machine using the `git clone` command, and install dependencies using `poetry install --sync`. + +### For Developers with Limited Python Knowledge + +Here are **my recommended** best practices: + +```bash +# Install uv +# Please refer to https://docs.astral.sh/uv/getting-started/installation/ + +# Set up the basic Python environment +uv python install 3.10 +uv tool install poetry + +# Clone the repository +git clone https://github.com/A-Minos/nonebot-plugin-tetris-stats.git +cd nonebot-plugin-tetris-stats + +# Install dependencies +uv run --no-project --python 3.10 poetry env use python +poetry install --sync +``` + +## Development + +### Code Development + +1. For static code analysis, use [ruff](https://docs.astral.sh/ruff/). You can install the corresponding plugin for your IDE or use the command line with `ruff check ./nonebot_plugin_tetris_stats/` to check the code. +2. For code formatting, use [ruff](https://docs.astral.sh/ruff/). You can install the corresponding plugin for your IDE or use the command line with `ruff format ./nonebot_plugin_tetris_stats/` to format the code. +3. For type checking, use both [basedpyright](https://docs.basedpyright.com/latest/) and [mypy](https://www.mypy-lang.org/). You can install the corresponding plugins for your IDE or use the following commands in the terminal to check the code: + +```bash +# basedpyright +basedpyright ./nonebot_plugin_tetris_stats/ + +# mypy +mypy ./nonebot_plugin_tetris_stats/ +``` + +### Internationalization + +This project uses [Tarina](https://github.com/ArcletProject/Tarina) for internationalization support. + +#### Adding a New Language + +1. Navigate to the `./nonebot_plugin_tetris_stats/i18n/` directory. +2. Run `tarina-lang create {language_code}` * Please note that the language code should preferably follow the [IETF language tag](https://en.wikipedia.org/wiki/IETF_language_tag) standard. +3. Edit the generated `./nonebot_plugin_tetris_stats/i18n/{language_code}.json` file. + +#### Updating an Existing Language + +1. Navigate to the `./nonebot_plugin_tetris_stats/i18n/` directory. +2. Edit the corresponding `./nonebot_plugin_tetris_stats/i18n/{language_code}.json` file. + +#### Adding New Entries + +1. Navigate to the `./nonebot_plugin_tetris_stats/i18n/` directory. +2. Edit the `.template.json` file. +3. Run `tarina-lang schema && tarina-lang model`. +4. Modify the language files, adding new entries at least to `en-US.json`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..8b277ca --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,68 @@ +# 我该如何参与开发? + +## 配置环境 + +### 对于有一定 Python 基础的开发者 + +首先你需要 [Python **3.10**](https://www.python.org/) 以及 [Poetry](https://python-poetry.org/)。 +然后你需要使用`git clone`命令将本仓库克隆到本地,然后使用`poetry install --sync`安装依赖。 + +### 对于基础不是很好的开发者 + +以下是**我认为的**最佳实践: + +```bash +# 安装 uv +# 请参考 https://docs.astral.sh/uv/getting-started/installation/ + +# 配置基础 Python 环境 +uv python install 3.10 +uv tool install poetry + +# 克隆仓库 +git clone https://github.com/A-Minos/nonebot-plugin-tetris-stats.git +cd nonebot-plugin-tetris-stats + +# 安装依赖 +uv run --no-project --python 3.10 poetry env use python +poetry install --sync +``` + +## 开发 + +### 代码开发 + +1. 代码静态检查使用 [ruff](https://docs.astral.sh/ruff/),你可以为你的ide安装对应插件来使用,也可以在命令行使用`ruff check ./nonebot_plugin_tetris_stats/`来检查代码。 +2. 代码格式化使用 [ruff](https://docs.astral.sh/ruff/),你可以为你的ide安装对应插件来使用,也可以在命令行使用`ruff format ./nonebot_plugin_tetris_stats/`来格式化代码。 +3. 类型检查同时使用 [basedpyright](https://docs.basedpyright.com/latest/) 和 [mypy](https://www.mypy-lang.org/),你可以为你的ide安装对应插件来使用。 +也可以在命令行使用下面的命令来检查代码: + +```bash +# basedpyright +basedpyright ./nonebot_plugin_tetris_stats/ + +# mypy +mypy ./nonebot_plugin_tetris_stats/ +``` + +### 国际化 + +本项目使用 [Tarina](https://github.com/ArcletProject/Tarina) 提供国际化支持。 + +#### 添加新的语言 + +1. 进入 `./nonebot_plugin_tetris_stats/i18n/` 目录。 +2. 运行 `tarina-lang create {语言代码}` * 请注意,语言代码最好符合 [IETF语言标签](https://zh.wikipedia.org/wiki/IETF%E8%AF%AD%E8%A8%80%E6%A0%87%E7%AD%BE) 的规范。 +3. 编辑生成的 `./nonebot_plugin_tetris_stats/i18n/{语言代码}.json` 文件。 + +#### 更新已有语言 + +1. 进入 `./nonebot_plugin_tetris_stats/i18n/` 目录。 +2. 编辑对应的 `./nonebot_plugin_tetris_stats/i18n/{语言代码}.json` 文件。 + +#### 添加新的条目 + +1. 进入 `./nonebot_plugin_tetris_stats/i18n/` 目录。 +2. 编辑 `.template.json` 文件。 +3. 运行 `tarina-lang schema && tarina-lang model`。 +4. 修改语言文件,至少为`en-US.json`添加新的条目。 diff --git a/nonebot_plugin_tetris_stats/games/__init__.py b/nonebot_plugin_tetris_stats/games/__init__.py index 773b856..f64cfad 100644 --- a/nonebot_plugin_tetris_stats/games/__init__.py +++ b/nonebot_plugin_tetris_stats/games/__init__.py @@ -7,6 +7,7 @@ from nonebot.typing import T_Handler from nonebot_plugin_alconna import AlcMatches, Alconna, At, CommandMeta, on_alconna from .. import ns +from ..i18n.model import Lang from ..utils.exception import MessageFormatError, NeedCatchError command: Alconna = Alconna( @@ -30,7 +31,7 @@ def add_block_handlers(handler: Callable[[T_Handler], T_Handler]) -> None: @handler async def _(bot: Bot, matcher: Matcher, target: At): if isinstance(target, At) and target.target == bot.self_id: - await matcher.finish('不能查询bot的信息') + await matcher.finish(Lang.interaction.wrong.query_bot()) from . import tetrio, top, tos # noqa: F401, E402 diff --git a/nonebot_plugin_tetris_stats/games/constant.py b/nonebot_plugin_tetris_stats/games/constant.py deleted file mode 100644 index fabe5ab..0000000 --- a/nonebot_plugin_tetris_stats/games/constant.py +++ /dev/null @@ -1 +0,0 @@ -CANT_VERIFY_MESSAGE = '* 由于无法验证绑定信息, 不能保证查询到的用户为本人\n' diff --git a/nonebot_plugin_tetris_stats/games/tetrio/query/__init__.py b/nonebot_plugin_tetris_stats/games/tetrio/query/__init__.py index 115ace1..e7a6963 100644 --- a/nonebot_plugin_tetris_stats/games/tetrio/query/__init__.py +++ b/nonebot_plugin_tetris_stats/games/tetrio/query/__init__.py @@ -14,10 +14,10 @@ from nonebot_plugin_user import get_user from sqlalchemy import select from ....db import query_bind_info, trigger +from ....i18n import Lang from ....utils.exception import FallbackError from ....utils.typing import Me from ... import add_block_handlers, alc -from ...constant import CANT_VERIFY_MESSAGE from .. import command, get_player from ..api import Player from ..constant import GAME_TYPE @@ -113,9 +113,10 @@ async def _( # noqa: PLR0913 ) if bind is None: await matcher.finish('未查询到绑定信息') - message = UniMessage(CANT_VERIFY_MESSAGE) player = Player(user_id=bind.game_account, trust=True) - await (message + await make_query_result(player, template or 'v1')).finish() + await ( + UniMessage.i18n(Lang.interaction.warning.unverified) + await make_query_result(player, template or 'v1') + ).finish() @alc.assign('TETRIO.query') diff --git a/nonebot_plugin_tetris_stats/games/tetrio/record/blitz.py b/nonebot_plugin_tetris_stats/games/tetrio/record/blitz.py index 7603a36..2974cbf 100644 --- a/nonebot_plugin_tetris_stats/games/tetrio/record/blitz.py +++ b/nonebot_plugin_tetris_stats/games/tetrio/record/blitz.py @@ -13,6 +13,7 @@ from nonebot_plugin_user import get_user from yarl import URL from ....db import query_bind_info, trigger +from ....i18n import Lang from ....utils.exception import RecordNotFoundError from ....utils.host import HostPage, get_self_netloc from ....utils.metrics import get_metrics @@ -22,7 +23,6 @@ from ....utils.render.schemas.tetrio.record.base import Finesse, Max, Mini, Tspi from ....utils.render.schemas.tetrio.record.blitz import Record, Statistic from ....utils.screenshot import screenshot from ....utils.typing import Me -from ...constant import CANT_VERIFY_MESSAGE from .. import alc from ..api.player import Player from ..constant import GAME_TYPE @@ -60,9 +60,10 @@ async def _( ) if bind is None: await matcher.finish('未查询到绑定信息') - message = UniMessage(CANT_VERIFY_MESSAGE) player = Player(user_id=bind.game_account, trust=True) - await (message + UniMessage.image(raw=await make_blitz_image(player))).finish() + await ( + UniMessage.i18n(Lang.interaction.warning.unverified) + UniMessage.image(raw=await make_blitz_image(player)) + ).finish() @alc.assign('TETRIO.record.blitz') diff --git a/nonebot_plugin_tetris_stats/games/tetrio/record/sprint.py b/nonebot_plugin_tetris_stats/games/tetrio/record/sprint.py index 8f8393d..f13cb07 100644 --- a/nonebot_plugin_tetris_stats/games/tetrio/record/sprint.py +++ b/nonebot_plugin_tetris_stats/games/tetrio/record/sprint.py @@ -13,6 +13,7 @@ from nonebot_plugin_user import get_user from yarl import URL from ....db import query_bind_info, trigger +from ....i18n import Lang from ....utils.exception import RecordNotFoundError from ....utils.host import HostPage, get_self_netloc from ....utils.metrics import get_metrics @@ -22,7 +23,6 @@ from ....utils.render.schemas.tetrio.record.base import Finesse, Max, Mini, Stat from ....utils.render.schemas.tetrio.record.sprint import Record from ....utils.screenshot import screenshot from ....utils.typing import Me -from ...constant import CANT_VERIFY_MESSAGE from .. import alc from ..api.player import Player from ..constant import GAME_TYPE @@ -60,9 +60,10 @@ async def _( ) if bind is None: await matcher.finish('未查询到绑定信息') - message = UniMessage(CANT_VERIFY_MESSAGE) player = Player(user_id=bind.game_account, trust=True) - await (message + UniMessage.image(raw=await make_sprint_image(player))).finish() + await ( + UniMessage.i18n(Lang.interaction.warning.unverified) + UniMessage.image(raw=await make_sprint_image(player)) + ).finish() @alc.assign('TETRIO.record.sprint') diff --git a/nonebot_plugin_tetris_stats/games/top/query.py b/nonebot_plugin_tetris_stats/games/top/query.py index 2c46d27..d8fb903 100644 --- a/nonebot_plugin_tetris_stats/games/top/query.py +++ b/nonebot_plugin_tetris_stats/games/top/query.py @@ -8,6 +8,7 @@ from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[im from nonebot_plugin_user import get_user from ...db import query_bind_info, trigger +from ...i18n import Lang from ...utils.exception import FallbackError from ...utils.host import HostPage, get_self_netloc from ...utils.metrics import TetrisMetricsBasicWithLPM, get_metrics @@ -18,7 +19,6 @@ from ...utils.render.schemas.top_info import Data as InfoData from ...utils.render.schemas.top_info import Info from ...utils.screenshot import screenshot from ...utils.typing import Me -from ..constant import CANT_VERIFY_MESSAGE from . import alc from .api import Player from .api.schemas.user_profile import Data, UserProfile @@ -44,7 +44,7 @@ async def _(event: Event, matcher: Matcher, target: At | Me, event_session: Even if bind is None: await matcher.finish('未查询到绑定信息') await ( - UniMessage(CANT_VERIFY_MESSAGE) + UniMessage.i18n(Lang.interaction.warning.unverified) + await make_query_result(await Player(user_name=bind.game_account, trust=True).get_profile()) ).finish() diff --git a/nonebot_plugin_tetris_stats/games/tos/query.py b/nonebot_plugin_tetris_stats/games/tos/query.py index da1ac08..c57e28c 100644 --- a/nonebot_plugin_tetris_stats/games/tos/query.py +++ b/nonebot_plugin_tetris_stats/games/tos/query.py @@ -14,6 +14,7 @@ from nonebot_plugin_user import get_user from nonebot_plugin_userinfo import EventUserInfo, UserInfo from ...db import query_bind_info, trigger +from ...i18n import Lang from ...utils.exception import RequestError from ...utils.host import HostPage, get_self_netloc from ...utils.image import get_avatar @@ -24,7 +25,6 @@ from ...utils.render.schemas.base import People, Ranking from ...utils.render.schemas.tos_info import Info, Multiplayer, Radar from ...utils.screenshot import screenshot from ...utils.typing import Me, Number -from ..constant import CANT_VERIFY_MESSAGE from . import alc from .api import Player from .api.schemas.user_info import UserInfoSuccess @@ -124,7 +124,7 @@ async def _( ) if bind is None: await matcher.finish('未查询到绑定信息') - message = CANT_VERIFY_MESSAGE + message = UniMessage.i18n(Lang.interaction.warning.unverified) player = Player(teaid=bind.game_account, trust=True) user_info, game_data = await gather(player.get_info(), get_game_data(player)) if game_data is not None: diff --git a/nonebot_plugin_tetris_stats/i18n/.config.json b/nonebot_plugin_tetris_stats/i18n/.config.json new file mode 100644 index 0000000..b556907 --- /dev/null +++ b/nonebot_plugin_tetris_stats/i18n/.config.json @@ -0,0 +1,5 @@ +{ + "default": "en-US", + "frozen": [], + "require": [] +} diff --git a/nonebot_plugin_tetris_stats/i18n/.lang.schema.json b/nonebot_plugin_tetris_stats/i18n/.lang.schema.json new file mode 100644 index 0000000..96bce4f --- /dev/null +++ b/nonebot_plugin_tetris_stats/i18n/.lang.schema.json @@ -0,0 +1,72 @@ +{ + "title": "Lang Schema", + "description": "Schema for lang file", + "type": "object", + "properties": { + "interaction": { + "title": "Interaction", + "description": "Scope 'interaction' of lang item", + "type": "object", + "additionalProperties": false, + "properties": { + "wrong": { + "title": "Wrong", + "description": "Scope 'wrong' of lang item", + "type": "object", + "additionalProperties": false, + "properties": { + "query_bot": { + "title": "query_bot", + "description": "value of lang item type 'query_bot'", + "type": "string" + } + } + }, + "warning": { + "title": "Warning", + "description": "Scope 'warning' of lang item", + "type": "object", + "additionalProperties": false, + "properties": { + "unverified": { + "title": "unverified", + "description": "value of lang item type 'unverified'", + "type": "string" + } + } + } + } + }, + "error": { + "title": "Error", + "description": "Scope 'error' of lang item", + "type": "object", + "additionalProperties": false, + "properties": { + "MessageFormatError": { + "title": "Messageformaterror", + "description": "Scope 'MessageFormatError' of lang item", + "type": "object", + "additionalProperties": false, + "properties": { + "TETR.IO": { + "title": "TETR.IO", + "description": "value of lang item type 'TETR.IO'", + "type": "string" + }, + "TOS": { + "title": "TOS", + "description": "value of lang item type 'TOS'", + "type": "string" + }, + "TOP": { + "title": "TOP", + "description": "value of lang item type 'TOP'", + "type": "string" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/nonebot_plugin_tetris_stats/i18n/.template.json b/nonebot_plugin_tetris_stats/i18n/.template.json new file mode 100644 index 0000000..d2730a1 --- /dev/null +++ b/nonebot_plugin_tetris_stats/i18n/.template.json @@ -0,0 +1,16 @@ +{ + "$schema": ".template.schema.json", + "scopes": [ + { + "scope": "interaction", + "types": [ + { "subtype": "wrong", "types": ["query_bot"] }, + { "subtype": "warning", "types": ["unverified"] } + ] + }, + { + "scope": "error", + "types": [{ "subtype": "MessageFormatError", "types": ["TETR.IO", "TOS", "TOP"] }] + } + ] +} diff --git a/nonebot_plugin_tetris_stats/i18n/.template.schema.json b/nonebot_plugin_tetris_stats/i18n/.template.schema.json new file mode 100644 index 0000000..87fbe89 --- /dev/null +++ b/nonebot_plugin_tetris_stats/i18n/.template.schema.json @@ -0,0 +1,54 @@ +{ + "title": "Template", + "description": "Template for lang items to generate schema for lang files", + "type": "object", + "properties": { + "scopes": { + "title": "Scopes", + "description": "All scopes of lang items", + "type": "array", + "uniqueItems": true, + "items": { + "title": "Scope", + "description": "First level of all lang items", + "type": "object", + "properties": { + "scope": { + "type": "string", + "description": "Scope name" + }, + "types": { + "type": "array", + "description": "All types of lang items", + "uniqueItems": true, + "items": { + "oneOf": [ + { + "type": "string", + "description": "Value of lang item" + }, + { + "type": "object", + "properties": { + "subtype": { + "type": "string", + "description": "Subtype name of lang item" + }, + "types": { + "type": "array", + "description": "All subtypes of lang items", + "uniqueItems": true, + "items": { + "$ref": "#/properties/scopes/items/properties/types/items" + } + } + } + } + ] + } + } + } + } + } + } +} diff --git a/nonebot_plugin_tetris_stats/i18n/__init__.py b/nonebot_plugin_tetris_stats/i18n/__init__.py new file mode 100644 index 0000000..f43d64d --- /dev/null +++ b/nonebot_plugin_tetris_stats/i18n/__init__.py @@ -0,0 +1,12 @@ +# This file is @generated by tarina.lang CLI tool +# It is not intended for manual editing. + +# ruff: noqa: E402, F401, PLC0414 + +from pathlib import Path + +from tarina.lang import lang # type: ignore[import-untyped] + +lang.load(Path(__file__).parent) + +from .model import Lang as Lang diff --git a/nonebot_plugin_tetris_stats/i18n/en-US.json b/nonebot_plugin_tetris_stats/i18n/en-US.json new file mode 100644 index 0000000..e9b203b --- /dev/null +++ b/nonebot_plugin_tetris_stats/i18n/en-US.json @@ -0,0 +1,16 @@ +{ + "$schema": ".lang.schema.json", + "interaction": { + "wrong": { "query_bot": "Can't query bot's information" }, + "warning": { + "unverified": "* Because I can't verify account linking information, I can't guarantee the info I found is yourself/themself." + } + }, + "error": { + "MessageFormatError": { + "TETR.IO": "Username/ID is invalid", + "TOS": "Username/ID is invalid", + "TOP": "Username is invalid" + } + } +} diff --git a/nonebot_plugin_tetris_stats/i18n/model.py b/nonebot_plugin_tetris_stats/i18n/model.py new file mode 100644 index 0000000..a32480b --- /dev/null +++ b/nonebot_plugin_tetris_stats/i18n/model.py @@ -0,0 +1,32 @@ +# This file is @generated by tarina.lang CLI tool +# It is not intended for manual editing. + +from tarina.lang.model import LangItem, LangModel # type: ignore[import-untyped] + + +class InteractionWrong: + query_bot: LangItem = LangItem('interaction', 'wrong.query_bot') + + +class InteractionWarning: + unverified: LangItem = LangItem('interaction', 'warning.unverified') + + +class Interaction: + wrong = InteractionWrong + warning = InteractionWarning + + +class ErrorMessageformaterror: + TETR_IO: LangItem = LangItem('error', 'MessageFormatError.TETR.IO') + TOS: LangItem = LangItem('error', 'MessageFormatError.TOS') + TOP: LangItem = LangItem('error', 'MessageFormatError.TOP') + + +class Error: + MessageFormatError = ErrorMessageformaterror + + +class Lang(LangModel): + interaction = Interaction + error = Error diff --git a/nonebot_plugin_tetris_stats/i18n/zh-CN.json b/nonebot_plugin_tetris_stats/i18n/zh-CN.json new file mode 100644 index 0000000..b16724e --- /dev/null +++ b/nonebot_plugin_tetris_stats/i18n/zh-CN.json @@ -0,0 +1,14 @@ +{ + "$schema": ".lang.schema.json", + "interaction": { + "wrong": { "query_bot": "不能查询bot的信息" }, + "warning": { "unverified": "* 由于无法验证绑定信息, 不能保证查询到的用户为本人" } + }, + "error": { + "MessageFormatError": { + "TETR.IO": "用户名/ID不合法", + "TOS": "用户名/ID不合法", + "TOP": "用户名不合法" + } + } +}