diff --git a/astrbot/core/star/star_manager.py b/astrbot/core/star/star_manager.py index 38bdc61d71..bd31d5df2a 100644 --- a/astrbot/core/star/star_manager.py +++ b/astrbot/core/star/star_manager.py @@ -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 @@ -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 + @staticmethod def _normalize_plugin_dir_name(plugin_name: str) -> str: return plugin_name.strip() @@ -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: diff --git a/tests/test_plugin_manager.py b/tests/test_plugin_manager.py index e51f9f35a1..d6bf22ee19 100644 --- a/tests/test_plugin_manager.py +++ b/tests/test_plugin_manager.py @@ -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" + } + + +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"): + PluginManager._load_plugin_config_schema(str(schema_path)) + + class MockStar: def __init__(self): self.root_dir_name = TEST_PLUGIN_DIR @@ -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"}}),