nonebot-plugin-session-config 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nonebot_plugin_session_config-1.0.0/PKG-INFO +108 -0
- nonebot_plugin_session_config-1.0.0/README.md +92 -0
- nonebot_plugin_session_config-1.0.0/pyproject.toml +144 -0
- nonebot_plugin_session_config-1.0.0/src/nonebot_plugin_session_config/__init__.py +37 -0
- nonebot_plugin_session_config-1.0.0/src/nonebot_plugin_session_config/config.py +14 -0
- nonebot_plugin_session_config-1.0.0/src/nonebot_plugin_session_config/file.py +63 -0
- nonebot_plugin_session_config-1.0.0/src/nonebot_plugin_session_config/inject.py +50 -0
- nonebot_plugin_session_config-1.0.0/src/nonebot_plugin_session_config/param.py +46 -0
- nonebot_plugin_session_config-1.0.0/src/nonebot_plugin_session_config/rules.py +39 -0
- nonebot_plugin_session_config-1.0.0/src/nonebot_plugin_session_config/session_config.py +85 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: nonebot-plugin-session-config
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Nonebot2 会话级配置信息存储插件
|
|
5
|
+
Author: XeF2
|
|
6
|
+
Author-email: XeF2 <contact@xef2.top>
|
|
7
|
+
Requires-Dist: httpx>=0.28.1,<1.0.0
|
|
8
|
+
Requires-Dist: nonebot-plugin-localstore>=0.7.4,<1.0.0
|
|
9
|
+
Requires-Dist: nonebot-plugin-uninfo>=0.11.0,<1.0.0
|
|
10
|
+
Requires-Dist: nonebot2>=2.4.4,<3.0.0
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Project-URL: Homepage, https://github.com/USTC-XeF2/nonebot-plugin-session-config
|
|
13
|
+
Project-URL: Issues, https://github.com/USTC-XeF2/nonebot-plugin-session-config/issues
|
|
14
|
+
Project-URL: Repository, https://github.com/USTC-XeF2/nonebot-plugin-session-config.git
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
<div align="center">
|
|
18
|
+
<a href="https://v2.nonebot.dev/store">
|
|
19
|
+
<img src="https://raw.githubusercontent.com/fllesser/nonebot-plugin-template/refs/heads/resource/.docs/NoneBotPlugin.svg" width="310" alt="logo"></a>
|
|
20
|
+
|
|
21
|
+
## ✨ nonebot-plugin-session-config ✨
|
|
22
|
+
[](./LICENSE)
|
|
23
|
+
[](https://pypi.python.org/pypi/nonebot-plugin-session-config)
|
|
24
|
+
[](https://www.python.org)
|
|
25
|
+
[](https://github.com/astral-sh/uv)
|
|
26
|
+
<br/>
|
|
27
|
+
[](https://github.com/astral-sh/ruff)
|
|
28
|
+
[](https://results.pre-commit.ci/latest/github/USTC-XeF2/nonebot-plugin-session-config/master)
|
|
29
|
+
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
## 📖 介绍
|
|
33
|
+
|
|
34
|
+
本插件为每个会话(群聊、私聊等场景)提供了独立的持久化配置存储功能。
|
|
35
|
+
|
|
36
|
+
## 💿 安装
|
|
37
|
+
|
|
38
|
+
### 使用 nb-cli 安装
|
|
39
|
+
|
|
40
|
+
```shell
|
|
41
|
+
nb plugin install nonebot-plugin-session-config --upgrade
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 使用 uv 安装
|
|
45
|
+
|
|
46
|
+
```shell
|
|
47
|
+
uv add nonebot-plugin-session-config
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
安装仓库 master 分支
|
|
51
|
+
|
|
52
|
+
```shell
|
|
53
|
+
uv add git+https://github.com/USTC-XeF2/nonebot-plugin-session-config@master
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
打开 nonebot2 项目根目录下的 `pyproject.toml` 文件, 在 `[tool.nonebot.plugins]` 部分追加写入
|
|
57
|
+
|
|
58
|
+
```toml
|
|
59
|
+
"@local" = ["nonebot_plugin_session_config"]
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## ⚙️ 配置
|
|
63
|
+
|
|
64
|
+
所有配置项均以 `SESSION_CONFIG_` 为前缀(下文省略),且均为选填项。
|
|
65
|
+
|
|
66
|
+
| 配置项 | 默认值 | 说明 |
|
|
67
|
+
| :---: | :---: | :-: |
|
|
68
|
+
| BASE_DIR | None | 配置存储的根目录,值为 None 时使用 `localstore` 提供的配置文件目录,**一般不需要更改** |
|
|
69
|
+
| DIR_FORMAT | bot-{bot_id} | 各机器人所属目录的命名格式,不使用 `bot_id` 模板参数时所有机器人共用同一目录 |
|
|
70
|
+
| FILE_FORMAT | {scene_type}-{scene_id}.yaml | 配置文件的命名格式 |
|
|
71
|
+
| USE_GLOBAL | False | 是否尝试使用全局配置作为默认值,**开启此项时请确保会话配置与全局配置间没有意料外的重复键** |
|
|
72
|
+
| ENABLE_PARAM | False | 是否启用会话配置参数注入功能,启用后可直接指定会话配置类作为消息处理函数参数类型,**在插件冲突时可能会注入失败** |
|
|
73
|
+
|
|
74
|
+
## 🎉 使用
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from nonebot import on_message
|
|
78
|
+
|
|
79
|
+
from nonebot_plugin_session_config import (
|
|
80
|
+
BaseSessionConfig,
|
|
81
|
+
check_enabled,
|
|
82
|
+
get_session_config,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# 所有会话配置类均应继承自 BaseSessionConfig
|
|
87
|
+
class SessionConfig(BaseSessionConfig):
|
|
88
|
+
test_enabled: bool = False
|
|
89
|
+
test_key: int = 0
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
message_handler = on_message(
|
|
93
|
+
rule=check_enabled(SessionConfig, "test_enabled"),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@message_handler.handle()
|
|
98
|
+
async def _(session_config: SessionConfig = get_session_config(SessionConfig)):
|
|
99
|
+
await message_handler.finish(f"Test key value is: {session_config.test_key}")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# 使用自动参数注入(需在配置中启用 ENABLE_PARAM 选项)
|
|
103
|
+
@message_handler.handle()
|
|
104
|
+
async def _(session_config: SessionConfig):
|
|
105
|
+
await message_handler.finish(f"Test key value is: {session_config.test_key}")
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
本插件提供的配置不提供在程序中动态修改的接口,若需要修改请手动或自动编辑对应的文件,无需重启即可更新。
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<a href="https://v2.nonebot.dev/store">
|
|
3
|
+
<img src="https://raw.githubusercontent.com/fllesser/nonebot-plugin-template/refs/heads/resource/.docs/NoneBotPlugin.svg" width="310" alt="logo"></a>
|
|
4
|
+
|
|
5
|
+
## ✨ nonebot-plugin-session-config ✨
|
|
6
|
+
[](./LICENSE)
|
|
7
|
+
[](https://pypi.python.org/pypi/nonebot-plugin-session-config)
|
|
8
|
+
[](https://www.python.org)
|
|
9
|
+
[](https://github.com/astral-sh/uv)
|
|
10
|
+
<br/>
|
|
11
|
+
[](https://github.com/astral-sh/ruff)
|
|
12
|
+
[](https://results.pre-commit.ci/latest/github/USTC-XeF2/nonebot-plugin-session-config/master)
|
|
13
|
+
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
## 📖 介绍
|
|
17
|
+
|
|
18
|
+
本插件为每个会话(群聊、私聊等场景)提供了独立的持久化配置存储功能。
|
|
19
|
+
|
|
20
|
+
## 💿 安装
|
|
21
|
+
|
|
22
|
+
### 使用 nb-cli 安装
|
|
23
|
+
|
|
24
|
+
```shell
|
|
25
|
+
nb plugin install nonebot-plugin-session-config --upgrade
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### 使用 uv 安装
|
|
29
|
+
|
|
30
|
+
```shell
|
|
31
|
+
uv add nonebot-plugin-session-config
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
安装仓库 master 分支
|
|
35
|
+
|
|
36
|
+
```shell
|
|
37
|
+
uv add git+https://github.com/USTC-XeF2/nonebot-plugin-session-config@master
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
打开 nonebot2 项目根目录下的 `pyproject.toml` 文件, 在 `[tool.nonebot.plugins]` 部分追加写入
|
|
41
|
+
|
|
42
|
+
```toml
|
|
43
|
+
"@local" = ["nonebot_plugin_session_config"]
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## ⚙️ 配置
|
|
47
|
+
|
|
48
|
+
所有配置项均以 `SESSION_CONFIG_` 为前缀(下文省略),且均为选填项。
|
|
49
|
+
|
|
50
|
+
| 配置项 | 默认值 | 说明 |
|
|
51
|
+
| :---: | :---: | :-: |
|
|
52
|
+
| BASE_DIR | None | 配置存储的根目录,值为 None 时使用 `localstore` 提供的配置文件目录,**一般不需要更改** |
|
|
53
|
+
| DIR_FORMAT | bot-{bot_id} | 各机器人所属目录的命名格式,不使用 `bot_id` 模板参数时所有机器人共用同一目录 |
|
|
54
|
+
| FILE_FORMAT | {scene_type}-{scene_id}.yaml | 配置文件的命名格式 |
|
|
55
|
+
| USE_GLOBAL | False | 是否尝试使用全局配置作为默认值,**开启此项时请确保会话配置与全局配置间没有意料外的重复键** |
|
|
56
|
+
| ENABLE_PARAM | False | 是否启用会话配置参数注入功能,启用后可直接指定会话配置类作为消息处理函数参数类型,**在插件冲突时可能会注入失败** |
|
|
57
|
+
|
|
58
|
+
## 🎉 使用
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from nonebot import on_message
|
|
62
|
+
|
|
63
|
+
from nonebot_plugin_session_config import (
|
|
64
|
+
BaseSessionConfig,
|
|
65
|
+
check_enabled,
|
|
66
|
+
get_session_config,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# 所有会话配置类均应继承自 BaseSessionConfig
|
|
71
|
+
class SessionConfig(BaseSessionConfig):
|
|
72
|
+
test_enabled: bool = False
|
|
73
|
+
test_key: int = 0
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
message_handler = on_message(
|
|
77
|
+
rule=check_enabled(SessionConfig, "test_enabled"),
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@message_handler.handle()
|
|
82
|
+
async def _(session_config: SessionConfig = get_session_config(SessionConfig)):
|
|
83
|
+
await message_handler.finish(f"Test key value is: {session_config.test_key}")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# 使用自动参数注入(需在配置中启用 ENABLE_PARAM 选项)
|
|
87
|
+
@message_handler.handle()
|
|
88
|
+
async def _(session_config: SessionConfig):
|
|
89
|
+
await message_handler.finish(f"Test key value is: {session_config.test_key}")
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
本插件提供的配置不提供在程序中动态修改的接口,若需要修改请手动或自动编辑对应的文件,无需重启即可更新。
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "nonebot-plugin-session-config"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "Nonebot2 会话级配置信息存储插件"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
authors = [{ name = "XeF2", email = "contact@xef2.top" }]
|
|
8
|
+
dependencies = [
|
|
9
|
+
"httpx>=0.28.1,<1.0.0",
|
|
10
|
+
"nonebot-plugin-localstore>=0.7.4,<1.0.0",
|
|
11
|
+
"nonebot-plugin-uninfo>=0.11.0,<1.0.0",
|
|
12
|
+
"nonebot2>=2.4.4,<3.0.0",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[project.urls]
|
|
16
|
+
Homepage = "https://github.com/USTC-XeF2/nonebot-plugin-session-config"
|
|
17
|
+
Issues = "https://github.com/USTC-XeF2/nonebot-plugin-session-config/issues"
|
|
18
|
+
Repository = "https://github.com/USTC-XeF2/nonebot-plugin-session-config.git"
|
|
19
|
+
|
|
20
|
+
[dependency-groups]
|
|
21
|
+
dev = [
|
|
22
|
+
"bump-my-version>=1.3.0",
|
|
23
|
+
"nonebot2[fastapi]>=2.4.4,<3.0.0",
|
|
24
|
+
"poethepoet>=0.42.1",
|
|
25
|
+
"ruff>=0.15.8,<1.0.0",
|
|
26
|
+
{ include-group = "test" },
|
|
27
|
+
]
|
|
28
|
+
test = [
|
|
29
|
+
"nonebot-adapter-onebot>=2.4.6,<3.0.0",
|
|
30
|
+
"nonebot2[fastapi]>=2.4.4,<3.0.0",
|
|
31
|
+
"nonebug>=0.4.3,<1.0.0",
|
|
32
|
+
"poethepoet>=0.42.1",
|
|
33
|
+
"pytest-asyncio>=1.3.0,<1.4.0",
|
|
34
|
+
"pytest-cov>=7.1.0",
|
|
35
|
+
"pytest-xdist>=3.8.0,<4.0.0",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[build-system]
|
|
39
|
+
requires = ["uv_build>=0.9.2,<0.10.0"]
|
|
40
|
+
build-backend = "uv_build"
|
|
41
|
+
|
|
42
|
+
[tool.uv.sources]
|
|
43
|
+
nonebug = { git = "https://github.com/nonebot/nonebug" }
|
|
44
|
+
|
|
45
|
+
[tool.bumpversion]
|
|
46
|
+
current_version = "0.1.0"
|
|
47
|
+
commit = true
|
|
48
|
+
message = "release: bump vesion from {current_version} to {new_version}"
|
|
49
|
+
tag = true
|
|
50
|
+
|
|
51
|
+
[[tool.bumpversion.files]]
|
|
52
|
+
filename = "uv.lock"
|
|
53
|
+
search = "name = \"nonebot-plugin-session-config\"\nversion = \"{current_version}\""
|
|
54
|
+
replace = "name = \"nonebot-plugin-session-config\"\nversion = \"{new_version}\""
|
|
55
|
+
|
|
56
|
+
[tool.coverage.report]
|
|
57
|
+
exclude_lines = [
|
|
58
|
+
"raise NotImplementedError",
|
|
59
|
+
"if TYPE_CHECKING:",
|
|
60
|
+
"@overload",
|
|
61
|
+
"except ImportError:",
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
[tool.nonebot]
|
|
65
|
+
plugin_dirs = []
|
|
66
|
+
builtin_plugins = []
|
|
67
|
+
|
|
68
|
+
[tool.nonebot.plugins]
|
|
69
|
+
"@local" = ["nonebot_plugin_session_config"]
|
|
70
|
+
|
|
71
|
+
[tool.nonebot.adapters]
|
|
72
|
+
"@local" = []
|
|
73
|
+
|
|
74
|
+
[tool.poe.tasks]
|
|
75
|
+
test = "pytest --cov=src --cov-report xml --junitxml=./junit.xml -n auto"
|
|
76
|
+
bump = "bump-my-version bump"
|
|
77
|
+
show-bump = "bump-my-version show-bump"
|
|
78
|
+
|
|
79
|
+
[tool.pyright]
|
|
80
|
+
pythonVersion = "3.10"
|
|
81
|
+
pythonPlatform = "All"
|
|
82
|
+
defineConstant = { PYDANTIC_V2 = true }
|
|
83
|
+
executionEnvironments = [
|
|
84
|
+
{ root = "./tests", extraPaths = [
|
|
85
|
+
"./src",
|
|
86
|
+
] },
|
|
87
|
+
{ root = "./src" },
|
|
88
|
+
]
|
|
89
|
+
typeCheckingMode = "standard"
|
|
90
|
+
disableBytesTypePromotions = true
|
|
91
|
+
|
|
92
|
+
[tool.pytest]
|
|
93
|
+
addopts = [
|
|
94
|
+
"--import-mode=prepend", # 导入模式
|
|
95
|
+
"--strict-markers", # 严格标记模式
|
|
96
|
+
"--tb=short", # 简短的错误回溯
|
|
97
|
+
"-ra", # 显示所有测试结果摘要
|
|
98
|
+
"-s", # 显示打印信息
|
|
99
|
+
"-v", # 详细输出
|
|
100
|
+
]
|
|
101
|
+
pythonpath = ["src"]
|
|
102
|
+
asyncio_mode = "auto"
|
|
103
|
+
asyncio_default_fixture_loop_scope = "session"
|
|
104
|
+
|
|
105
|
+
[tool.ruff]
|
|
106
|
+
line-length = 88
|
|
107
|
+
|
|
108
|
+
[tool.ruff.format]
|
|
109
|
+
line-ending = "lf"
|
|
110
|
+
|
|
111
|
+
[tool.ruff.lint]
|
|
112
|
+
select = [
|
|
113
|
+
"F", # Pyflakes
|
|
114
|
+
"W", # pycodestyle warnings
|
|
115
|
+
"E", # pycodestyle errors
|
|
116
|
+
"I", # isort
|
|
117
|
+
"UP", # pyupgrade
|
|
118
|
+
"ASYNC", # flake8-async
|
|
119
|
+
"C4", # flake8-comprehensions
|
|
120
|
+
"T10", # flake8-debugger
|
|
121
|
+
"T20", # flake8-print
|
|
122
|
+
"PYI", # flake8-pyi
|
|
123
|
+
"PT", # flake8-pytest-style
|
|
124
|
+
"Q", # flake8-quotes
|
|
125
|
+
"TID", # flake8-tidy-imports
|
|
126
|
+
"RUF", # Ruff-specific rules
|
|
127
|
+
]
|
|
128
|
+
ignore = [
|
|
129
|
+
"E402", # module-import-not-at-top-of-file
|
|
130
|
+
"UP037", # quoted-annotation
|
|
131
|
+
"RUF001", # ambiguous-unicode-character-string
|
|
132
|
+
"RUF002", # ambiguous-unicode-character-docstring
|
|
133
|
+
"RUF003", # ambiguous-unicode-character-comment
|
|
134
|
+
"W191", # indentation contains tabs
|
|
135
|
+
"TID252", # relative-import
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
[tool.ruff.lint.isort]
|
|
139
|
+
length-sort = true
|
|
140
|
+
known-first-party = ["tests/*"]
|
|
141
|
+
extra-standard-library = ["typing_extensions"]
|
|
142
|
+
|
|
143
|
+
[tool.ruff.lint.pyupgrade]
|
|
144
|
+
keep-runtime-typing = true
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from nonebot import require
|
|
2
|
+
from nonebot.plugin import PluginMetadata, inherit_supported_adapters
|
|
3
|
+
|
|
4
|
+
require("nonebot_plugin_uninfo")
|
|
5
|
+
require("nonebot_plugin_localstore")
|
|
6
|
+
|
|
7
|
+
from .rules import check_enabled, check_condition
|
|
8
|
+
from .config import Config, plugin_config
|
|
9
|
+
from .session_config import (
|
|
10
|
+
BaseSessionConfig,
|
|
11
|
+
get_session_config,
|
|
12
|
+
traverse_session_configs,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"BaseSessionConfig",
|
|
17
|
+
"check_condition",
|
|
18
|
+
"check_enabled",
|
|
19
|
+
"get_session_config",
|
|
20
|
+
"traverse_session_configs",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
__plugin_meta__ = PluginMetadata(
|
|
24
|
+
name="会话配置",
|
|
25
|
+
description="会话级配置信息存储插件",
|
|
26
|
+
usage="session_config: YourConfig = get_session_config(YourConfig)",
|
|
27
|
+
type="library",
|
|
28
|
+
homepage="https://github.com/USTC-XeF2/nonebot-plugin-session-config",
|
|
29
|
+
config=Config,
|
|
30
|
+
supported_adapters=inherit_supported_adapters("nonebot_plugin_uninfo"),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
if plugin_config.session_config_enable_param:
|
|
34
|
+
from .param import SessionConfigParam
|
|
35
|
+
from .inject import inject_all
|
|
36
|
+
|
|
37
|
+
inject_all(SessionConfigParam)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from nonebot import get_driver, get_plugin_config
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Config(BaseModel):
|
|
6
|
+
session_config_base_dir: str | None = None
|
|
7
|
+
session_config_dir_format: str = "bot-{bot_id}"
|
|
8
|
+
session_config_file_format: str = "{scene_type}-{scene_id}.yaml"
|
|
9
|
+
session_config_use_global: bool = False
|
|
10
|
+
session_config_enable_param: bool = False
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
plugin_config = get_plugin_config(Config)
|
|
14
|
+
global_config = get_driver().config
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from threading import Lock
|
|
4
|
+
from collections import defaultdict
|
|
5
|
+
|
|
6
|
+
from nonebot_plugin_uninfo import Uninfo
|
|
7
|
+
from nonebot_plugin_localstore import get_plugin_config_dir
|
|
8
|
+
|
|
9
|
+
from .config import plugin_config
|
|
10
|
+
|
|
11
|
+
PLUGIN_CONFIG_DIR = get_plugin_config_dir()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _get_session_config_dir(bot_id: str):
|
|
15
|
+
if plugin_config.session_config_base_dir is None:
|
|
16
|
+
base_dir = PLUGIN_CONFIG_DIR
|
|
17
|
+
else:
|
|
18
|
+
base_dir = Path(plugin_config.session_config_base_dir)
|
|
19
|
+
return base_dir / plugin_config.session_config_dir_format.format(bot_id=bot_id)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _get_session_config_file(session: Uninfo):
|
|
23
|
+
return _get_session_config_dir(
|
|
24
|
+
session.self_id
|
|
25
|
+
) / plugin_config.session_config_file_format.format(
|
|
26
|
+
scene_type=session.scene.type.name.lower(),
|
|
27
|
+
scene_id=session.scene.id,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
_index_locks: dict[str, Lock] = defaultdict(Lock)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _update_index(session: Uninfo, config_path: Path):
|
|
35
|
+
with _index_locks[session.self_id]:
|
|
36
|
+
index_file = _get_session_config_dir(session.self_id) / "index.json"
|
|
37
|
+
if not index_file.parent.exists():
|
|
38
|
+
index_file.parent.mkdir(parents=True, exist_ok=True)
|
|
39
|
+
try:
|
|
40
|
+
if index_file.exists():
|
|
41
|
+
with index_file.open(encoding="utf-8") as rf:
|
|
42
|
+
data = json.load(rf)
|
|
43
|
+
else:
|
|
44
|
+
data = {}
|
|
45
|
+
except Exception:
|
|
46
|
+
data = {}
|
|
47
|
+
|
|
48
|
+
key = f"{session.scene.type.name.lower()}:{session.scene.id}"
|
|
49
|
+
data[key] = str(config_path)
|
|
50
|
+
|
|
51
|
+
with index_file.open("w", encoding="utf-8") as wf:
|
|
52
|
+
json.dump(data, wf, ensure_ascii=False, indent=2)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _get_index(bot_id: str) -> dict[tuple[str, str], Path]:
|
|
56
|
+
with _index_locks[bot_id]:
|
|
57
|
+
index_file = _get_session_config_dir(bot_id) / "index.json"
|
|
58
|
+
if index_file.exists():
|
|
59
|
+
with index_file.open(encoding="utf-8") as rf:
|
|
60
|
+
data = json.load(rf)
|
|
61
|
+
return {tuple(k.split(":")): Path(v) for k, v in data.items()}
|
|
62
|
+
else:
|
|
63
|
+
return {}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from nonebot.params import DependParam
|
|
4
|
+
from nonebot.dependencies import Param
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def inject_after(
|
|
8
|
+
obj: Any,
|
|
9
|
+
field_name: str,
|
|
10
|
+
param: type[Param],
|
|
11
|
+
after_param: type[Param] = DependParam,
|
|
12
|
+
):
|
|
13
|
+
original_params = getattr(obj, field_name)
|
|
14
|
+
|
|
15
|
+
if not (
|
|
16
|
+
isinstance(original_params, (list, tuple))
|
|
17
|
+
and all(issubclass(p, Param) for p in original_params)
|
|
18
|
+
):
|
|
19
|
+
raise TypeError(f"{field_name} is not a list or tuple of Param subclasses")
|
|
20
|
+
|
|
21
|
+
original_params_type = type(original_params)
|
|
22
|
+
original_params_list: list[type[Param]] = list(original_params)
|
|
23
|
+
|
|
24
|
+
for i, p in enumerate(original_params_list):
|
|
25
|
+
if p is after_param:
|
|
26
|
+
original_params_list.insert(i + 1, param)
|
|
27
|
+
break
|
|
28
|
+
else:
|
|
29
|
+
original_params_list.append(param)
|
|
30
|
+
|
|
31
|
+
setattr(obj, field_name, original_params_type(original_params_list))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def inject_all(param: type[Param], after_param: type[Param] = DependParam):
|
|
35
|
+
import nonebot.message
|
|
36
|
+
from nonebot.rule import Rule
|
|
37
|
+
from nonebot.matcher import Matcher
|
|
38
|
+
from nonebot.permission import Permission
|
|
39
|
+
|
|
40
|
+
inject_list = [
|
|
41
|
+
(nonebot.message, "EVENT_PCS_PARAMS"),
|
|
42
|
+
(nonebot.message, "RUN_PREPCS_PARAMS"),
|
|
43
|
+
(nonebot.message, "RUN_POSTPCS_PARAMS"),
|
|
44
|
+
(Rule, "HANDLER_PARAM_TYPES"),
|
|
45
|
+
(Matcher, "HANDLER_PARAM_TYPES"),
|
|
46
|
+
(Permission, "HANDLER_PARAM_TYPES"),
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
for obj, field_name in inject_list:
|
|
50
|
+
inject_after(obj, field_name, param, after_param=after_param)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
from typing import Any
|
|
3
|
+
from typing_extensions import override
|
|
4
|
+
|
|
5
|
+
from nonebot.utils import generic_check_issubclass
|
|
6
|
+
from nonebot.adapters import Bot, Event
|
|
7
|
+
from nonebot.dependencies import Param
|
|
8
|
+
from nonebot_plugin_uninfo import get_session
|
|
9
|
+
|
|
10
|
+
from .file import _get_session_config_file
|
|
11
|
+
from .session_config import BaseSessionConfig, _load_config
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SessionConfigParam(Param):
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
*args,
|
|
18
|
+
config_type: type[BaseSessionConfig] | None = None,
|
|
19
|
+
**kwargs: Any,
|
|
20
|
+
) -> None:
|
|
21
|
+
super().__init__(*args, **kwargs)
|
|
22
|
+
self.config_type = config_type
|
|
23
|
+
|
|
24
|
+
def __repr__(self) -> str:
|
|
25
|
+
return f"SessionConfigParam({self.config_type!r})"
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
@override
|
|
29
|
+
def _check_param(
|
|
30
|
+
cls, param: inspect.Parameter, allow_types: tuple[type[Param], ...]
|
|
31
|
+
):
|
|
32
|
+
if generic_check_issubclass(param.annotation, BaseSessionConfig):
|
|
33
|
+
return cls(config_type=param.annotation)
|
|
34
|
+
|
|
35
|
+
@override
|
|
36
|
+
async def _solve( # pyright: ignore[reportIncompatibleMethodOverride]
|
|
37
|
+
self, bot: Bot, event: Event, **kwargs: Any
|
|
38
|
+
):
|
|
39
|
+
session = await get_session(bot, event)
|
|
40
|
+
if session is None:
|
|
41
|
+
raise ValueError("No session available for SessionConfigParam")
|
|
42
|
+
if self.config_type is None:
|
|
43
|
+
raise ValueError("SessionConfigParam requires a specific config type")
|
|
44
|
+
|
|
45
|
+
config_path = _get_session_config_file(session)
|
|
46
|
+
return _load_config(config_path, self.config_type)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
|
|
3
|
+
from nonebot.rule import Rule
|
|
4
|
+
|
|
5
|
+
from .session_config import C, get_session_config
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def check_condition(config_type: type[C], checker: Callable[[C], bool]):
|
|
9
|
+
"""
|
|
10
|
+
创建一个基于会话配置的简单规则检查器。用于简化较为简单的条件判断,复杂条件请使用自定义依赖注入的规则函数。
|
|
11
|
+
|
|
12
|
+
用法:
|
|
13
|
+
```python
|
|
14
|
+
from nonebot_plugin_session_config import check_condition
|
|
15
|
+
|
|
16
|
+
message_handler = on_message(
|
|
17
|
+
...,
|
|
18
|
+
rule=check_condition(Config, lambda cfg: cfg.some_key > 0),
|
|
19
|
+
)
|
|
20
|
+
```
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def rule_checker(session_config: C = get_session_config(config_type)):
|
|
24
|
+
return checker(session_config)
|
|
25
|
+
|
|
26
|
+
return Rule(rule_checker)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def check_enabled(config_type: type[C], key: str):
|
|
30
|
+
"""
|
|
31
|
+
创建一个基于会话配置中布尔值的规则检查器,具体用法见 `check_condition`。
|
|
32
|
+
"""
|
|
33
|
+
if key not in config_type.model_fields:
|
|
34
|
+
raise ValueError(f"Key '{key}' not found in config '{config_type.__name__}'")
|
|
35
|
+
|
|
36
|
+
return check_condition(
|
|
37
|
+
config_type,
|
|
38
|
+
lambda session_config: getattr(session_config, key),
|
|
39
|
+
)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from typing import TypeVar
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import yaml
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
from nonebot.params import Depends
|
|
7
|
+
from nonebot_plugin_uninfo import Uninfo
|
|
8
|
+
|
|
9
|
+
from .file import _get_index, _update_index, _get_session_config_file
|
|
10
|
+
from .config import global_config, plugin_config
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BaseSessionConfig(BaseModel):
|
|
14
|
+
"""
|
|
15
|
+
会话配置基类
|
|
16
|
+
|
|
17
|
+
所有会话配置类都应该继承此类,以便使用依赖注入功能。
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
C = TypeVar("C", bound=BaseSessionConfig)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _load_config(config_path: Path, config_type: type[C]):
|
|
27
|
+
if not config_path.parent.exists():
|
|
28
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
29
|
+
config_path.touch()
|
|
30
|
+
|
|
31
|
+
with config_path.open(encoding="utf-8") as rf:
|
|
32
|
+
data = yaml.safe_load(rf)
|
|
33
|
+
if not isinstance(data, dict):
|
|
34
|
+
data = {}
|
|
35
|
+
|
|
36
|
+
if plugin_config.session_config_use_global:
|
|
37
|
+
for key, value in global_config.model_dump().items():
|
|
38
|
+
data.setdefault(key, value)
|
|
39
|
+
|
|
40
|
+
return config_type.model_validate(data)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_session_config(config_type: type[C]):
|
|
44
|
+
"""
|
|
45
|
+
获取会话配置依赖项。
|
|
46
|
+
|
|
47
|
+
用法:
|
|
48
|
+
```python
|
|
49
|
+
from nonebot_plugin_session_config import BaseSessionConfig, get_session_config
|
|
50
|
+
|
|
51
|
+
class SessionConfig(BaseSessionConfig):
|
|
52
|
+
some_key: int = 0
|
|
53
|
+
|
|
54
|
+
message_handler = on_message(...)
|
|
55
|
+
|
|
56
|
+
@message_handler.handle()
|
|
57
|
+
async def _(session_config: SessionConfig = get_session_config(SessionConfig)):
|
|
58
|
+
...
|
|
59
|
+
```
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def get_config(session: Uninfo):
|
|
63
|
+
config_path = _get_session_config_file(session)
|
|
64
|
+
config = _load_config(config_path, config_type)
|
|
65
|
+
try:
|
|
66
|
+
_update_index(session, config_path)
|
|
67
|
+
except Exception:
|
|
68
|
+
pass
|
|
69
|
+
return config
|
|
70
|
+
|
|
71
|
+
return Depends(get_config)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def traverse_session_configs(bot_id: str, config_type: type[C]):
|
|
75
|
+
"""
|
|
76
|
+
遍历指定机器人的所有会话配置。
|
|
77
|
+
|
|
78
|
+
返回一个字典,键为 `(scene_type, scene_id)` 元组,值为对应的会话配置实例。
|
|
79
|
+
"""
|
|
80
|
+
result: dict[tuple[str, str], C] = {}
|
|
81
|
+
index = _get_index(bot_id)
|
|
82
|
+
for (scene_type, scene_id), config_path in index.items():
|
|
83
|
+
config = _load_config(config_path, config_type)
|
|
84
|
+
result[(scene_type, scene_id)] = config
|
|
85
|
+
return result
|