Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 24 additions & 9 deletions astrbot/core/star/star_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,7 @@ def _load_plugin_i18n(plugin_path: str) -> dict[str, dict]:
continue

try:
with file_path.open(encoding="utf-8") as f:
with file_path.open(encoding="utf-8-sig") as f:
locale_data = json.load(f)
if isinstance(locale_data, dict):
translations[locale] = locale_data
Expand All @@ -575,6 +575,22 @@ def _load_plugin_i18n(plugin_path: str) -> dict[str, dict]:

return translations

@staticmethod
def _load_plugin_config_schema(schema_path: str) -> dict:
"""Load a plugin config schema, accepting an optional UTF-8 BOM."""
try:
with open(schema_path, encoding="utf-8-sig") as f:
return json.load(f)
except UnicodeDecodeError as exc:
raise ValueError(
f"插件配置 schema 必须使用 UTF-8 编码: {schema_path}"
) from exc
except json.JSONDecodeError as exc:
raise ValueError(
f"插件配置 schema 不是有效的 JSON: {schema_path} "
f"(line {exc.lineno}, column {exc.colno})"
) from exc
Comment on lines +578 to +592

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

为了提高代码的健壮性并提供更清晰的错误信息,建议在 _load_plugin_config_schema 中验证解析后的 JSON 根节点是否为字典(JSON 对象)。如果插件开发者提供了非字典格式的 schema(例如 JSON 数组或字符串),在后续解析时会抛出不直观的 AttributeError。提前进行类型检查并抛出明确的 ValueError 可以显著提升开发者的调试体验。

Suggested change
@staticmethod
def _load_plugin_config_schema(schema_path: str) -> dict:
"""Load a plugin config schema, accepting an optional UTF-8 BOM."""
try:
with open(schema_path, encoding="utf-8-sig") as f:
return json.load(f)
except UnicodeDecodeError as exc:
raise ValueError(
f"插件配置 schema 必须使用 UTF-8 编码: {schema_path}"
) from exc
except json.JSONDecodeError as exc:
raise ValueError(
f"插件配置 schema 不是有效的 JSON: {schema_path} "
f"(line {exc.lineno}, column {exc.colno})"
) from exc
@staticmethod
def _load_plugin_config_schema(schema_path: str) -> dict:
"""Load a plugin config schema, accepting an optional UTF-8 BOM."""
try:
with open(schema_path, encoding="utf-8-sig") as f:
schema = json.load(f)
if not isinstance(schema, dict):
raise ValueError(
f"插件配置 schema 根节点必须是一个 JSON 对象: {schema_path}"
)
return schema
except UnicodeDecodeError as exc:
raise ValueError(
f"插件配置 schema 必须使用 UTF-8 编码: {schema_path}"
) from exc
except json.JSONDecodeError as exc:
raise ValueError(
f"插件配置 schema 不是有效的 JSON: {schema_path} "
f"(line {exc.lineno}, column {exc.colno})"
) from exc


@staticmethod
def _normalize_plugin_dir_name(plugin_name: str) -> str:
return plugin_name.strip()
Expand Down Expand Up @@ -1111,14 +1127,13 @@ async def load(
)
if os.path.exists(plugin_schema_path):
# 加载插件配置
with open(plugin_schema_path, encoding="utf-8") as f:
plugin_config = AstrBotConfig(
config_path=os.path.join(
self.plugin_config_path,
f"{root_dir_name}_config.json",
),
schema=json.loads(f.read()),
)
plugin_config = AstrBotConfig(
config_path=os.path.join(
self.plugin_config_path,
f"{root_dir_name}_config.json",
),
schema=self._load_plugin_config_schema(plugin_schema_path),
)
logo_path = os.path.join(plugin_dir_path, self.logo_fname)

if path in star_map:
Expand Down
34 changes: 31 additions & 3 deletions tests/test_plugin_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,32 @@
TEST_PLUGIN_DIR = "helloworld"


def test_load_plugin_config_schema_accepts_utf8_bom(tmp_path: Path):
schema_path = tmp_path / "_conf_schema.json"
schema_path.write_bytes(b'\xef\xbb\xbf{"type": "object"}')

assert PluginManager._load_plugin_config_schema(str(schema_path)) == {
"type": "object"
Comment on lines +23 to +28

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Add a test for non-UTF-8 schemas to cover the UnicodeDecodeError branch in _load_plugin_config_schema.

Current tests cover UTF-8 (with/without BOM) and invalid JSON, but not files encoded with a different charset (e.g. GBK/ISO-8859-1). Since _load_plugin_config_schema has a specific UnicodeDecodeError branch that raises a ValueError about UTF-8, please add a test that writes the schema with a non-UTF-8 encoding and asserts that this ValueError (with the expected message) is raised, so that branch is covered.

}


def test_load_plugin_config_schema_accepts_utf8_without_bom(tmp_path: Path):
schema_path = tmp_path / "_conf_schema.json"
schema_path.write_text('{"type": "object"}', encoding="utf-8")

assert PluginManager._load_plugin_config_schema(str(schema_path)) == {
"type": "object"
}


def test_load_plugin_config_schema_reports_invalid_json(tmp_path: Path):
schema_path = tmp_path / "_conf_schema.json"
schema_path.write_text("{invalid", encoding="utf-8")

with pytest.raises(ValueError, match="不是有效的 JSON"):
Comment on lines +41 to +45

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Extend the invalid JSON schema test to assert that line and column information are included in the error message.

To make this behavior part of the contract, consider capturing the ValueError and asserting the presence of line/column info in the message, e.g.:

with pytest.raises(ValueError) as excinfo:
    PluginManager._load_plugin_config_schema(str(schema_path))

msg = str(excinfo.value)
assert "不是有效的 JSON" in msg
assert "line " in msg
assert "column " in msg

This will help prevent future refactors from dropping the improved diagnostics.

PluginManager._load_plugin_config_schema(str(schema_path))

Comment on lines +41 to +47

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

配合 _load_plugin_config_schema 中新增的 JSON 根节点类型校验,建议在此处添加对应的单元测试,以确保当 schema 不是有效的 JSON 对象(例如是一个 JSON 数组)时,能够正确抛出预期的 ValueError 并包含清晰的错误提示。

Suggested change
def test_load_plugin_config_schema_reports_invalid_json(tmp_path: Path):
schema_path = tmp_path / "_conf_schema.json"
schema_path.write_text("{invalid", encoding="utf-8")
with pytest.raises(ValueError, match="不是有效的 JSON"):
PluginManager._load_plugin_config_schema(str(schema_path))
def test_load_plugin_config_schema_reports_invalid_json(tmp_path: Path):
schema_path = tmp_path / "_conf_schema.json"
schema_path.write_text("{invalid", encoding="utf-8")
with pytest.raises(ValueError, match="不是有效的 JSON"):
PluginManager._load_plugin_config_schema(str(schema_path))
def test_load_plugin_config_schema_reports_non_dict(tmp_path: Path):
schema_path = tmp_path / "_conf_schema.json"
schema_path.write_text('["not", "a", "dict"]', encoding="utf-8")
with pytest.raises(ValueError, match="根节点必须是一个 JSON 对象"):
PluginManager._load_plugin_config_schema(str(schema_path))


class MockStar:
def __init__(self):
self.root_dir_name = TEST_PLUGIN_DIR
Expand Down Expand Up @@ -59,9 +85,11 @@ def test_load_plugin_i18n_reads_locale_files(tmp_path: Path):
plugin_path = tmp_path / "plugin"
i18n_path = plugin_path / ".astrbot-plugin" / "i18n"
i18n_path.mkdir(parents=True)
(i18n_path / "zh-CN.json").write_text(
json.dumps({"metadata": {"desc": "中文描述"}}, ensure_ascii=False),
encoding="utf-8",
(i18n_path / "zh-CN.json").write_bytes(
b"\xef\xbb\xbf"
+ json.dumps({"metadata": {"desc": "中文描述"}}, ensure_ascii=False).encode(
"utf-8"
),
)
(i18n_path / "en-US.json").write_text(
json.dumps({"metadata": {"desc": "English description"}}),
Expand Down
Loading