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.
@@ -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](https://img.shields.io/github/license/USTC-XeF2/nonebot-plugin-session-config.svg)](./LICENSE)
23
+ [![pypi](https://img.shields.io/pypi/v/nonebot-plugin-session-config.svg)](https://pypi.python.org/pypi/nonebot-plugin-session-config)
24
+ [![python](https://img.shields.io/badge/python-3.10|3.11|3.12|3.13-blue.svg)](https://www.python.org)
25
+ [![uv](https://img.shields.io/badge/package%20manager-uv-black?style=flat-square&logo=uv)](https://github.com/astral-sh/uv)
26
+ <br/>
27
+ [![ruff](https://img.shields.io/badge/code%20style-ruff-black?style=flat-square&logo=ruff)](https://github.com/astral-sh/ruff)
28
+ [![pre-commit](https://results.pre-commit.ci/badge/github/USTC-XeF2/nonebot-plugin-session-config/master.svg)](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](https://img.shields.io/github/license/USTC-XeF2/nonebot-plugin-session-config.svg)](./LICENSE)
7
+ [![pypi](https://img.shields.io/pypi/v/nonebot-plugin-session-config.svg)](https://pypi.python.org/pypi/nonebot-plugin-session-config)
8
+ [![python](https://img.shields.io/badge/python-3.10|3.11|3.12|3.13-blue.svg)](https://www.python.org)
9
+ [![uv](https://img.shields.io/badge/package%20manager-uv-black?style=flat-square&logo=uv)](https://github.com/astral-sh/uv)
10
+ <br/>
11
+ [![ruff](https://img.shields.io/badge/code%20style-ruff-black?style=flat-square&logo=ruff)](https://github.com/astral-sh/ruff)
12
+ [![pre-commit](https://results.pre-commit.ci/badge/github/USTC-XeF2/nonebot-plugin-session-config/master.svg)](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