From 5fac5d79dd3f807ca95bf7e22a083a050d74698b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=9F=E9=B8=BFSeRazon?= Date: Tue, 9 Jun 2026 19:28:22 +0800 Subject: [PATCH 1/8] perf: optimize plugin list merge --- astrbot/cli/utils/plugin.py | 36 +++++++++---------- tests/test_cli_plugin_utils.py | 66 ++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 18 deletions(-) create mode 100644 tests/test_cli_plugin_utils.py diff --git a/astrbot/cli/utils/plugin.py b/astrbot/cli/utils/plugin.py index c06dda3500..5e9ab74d34 100644 --- a/astrbot/cli/utils/plugin.py +++ b/astrbot/cli/utils/plugin.py @@ -115,8 +115,9 @@ def build_plug_list(plugins_dir: Path) -> list: # Get local plugin info result = [] if plugins_dir.exists(): - for plugin_name in [d.name for d in plugins_dir.glob("*") if d.is_dir()]: - plugin_dir = plugins_dir / plugin_name + for plugin_dir in plugins_dir.iterdir(): + if not plugin_dir.is_dir(): + continue # Load metadata from metadata.yaml metadata = load_yaml_metadata(plugin_dir) @@ -163,28 +164,27 @@ def build_plug_list(plugins_dir: Path) -> list: click.echo(f"Failed to get online plugin list: {e}", err=True) # Compare with online plugins and update status - online_plugin_names = {plugin["name"] for plugin in online_plugins} + online_plugins_by_name = {plugin["name"]: plugin for plugin in online_plugins} + local_plugin_names = {plugin["name"] for plugin in result} for local_plugin in result: - if local_plugin["name"] in online_plugin_names: - # Find the corresponding online plugin - online_plugin = next( - p for p in online_plugins if p["name"] == local_plugin["name"] - ) - if ( - VersionComparator.compare_version( - local_plugin["version"], - online_plugin["version"], - ) - < 0 - ): - local_plugin["status"] = PluginStatus.NEED_UPDATE - else: + online_plugin = online_plugins_by_name.get(local_plugin["name"]) + if online_plugin is None: # Local plugin is not published online local_plugin["status"] = PluginStatus.NOT_PUBLISHED + continue + + if ( + VersionComparator.compare_version( + local_plugin["version"], + online_plugin["version"], + ) + < 0 + ): + local_plugin["status"] = PluginStatus.NEED_UPDATE # Add uninstalled online plugins for online_plugin in online_plugins: - if not any(plugin["name"] == online_plugin["name"] for plugin in result): + if online_plugin["name"] not in local_plugin_names: result.append(online_plugin) return result diff --git a/tests/test_cli_plugin_utils.py b/tests/test_cli_plugin_utils.py new file mode 100644 index 0000000000..e9eb4b80b9 --- /dev/null +++ b/tests/test_cli_plugin_utils.py @@ -0,0 +1,66 @@ +from pathlib import Path + +from astrbot.cli.utils.plugin import PluginStatus, build_plug_list + + +class FakeResponse: + def raise_for_status(self): + return None + + def json(self): + return { + "local-plugin": { + "desc": "remote description", + "version": "2.0.0", + "author": "remote-author", + "repo": "https://example.com/local-plugin", + }, + "remote-only": { + "desc": "remote only", + "version": "1.0.0", + "author": "remote-author", + "repo": "https://example.com/remote-only", + }, + } + + +class FakeClient: + def __enter__(self): + return self + + def __exit__(self, exc_type, exc, tb): + return False + + def get(self, url): + assert url == "https://api.soulter.top/astrbot/plugins" + return FakeResponse() + + +def write_metadata(plugin_dir: Path, name: str, version: str) -> None: + plugin_dir.mkdir() + plugin_dir.joinpath("metadata.yaml").write_text( + f""" +name: {name} +desc: local description +version: {version} +author: local-author +repo: https://example.com/{name} +""".strip(), + encoding="utf-8", + ) + + +def test_build_plug_list_merges_local_and_remote_plugins(monkeypatch, tmp_path): + write_metadata(tmp_path / "local-plugin", "local-plugin", "1.0.0") + write_metadata(tmp_path / "unpublished-plugin", "unpublished-plugin", "1.0.0") + tmp_path.joinpath("ignored-file").write_text("not a plugin", encoding="utf-8") + + monkeypatch.setattr("astrbot.cli.utils.plugin.httpx.Client", FakeClient) + + plugins = build_plug_list(tmp_path) + plugins_by_name = {plugin["name"]: plugin for plugin in plugins} + + assert plugins_by_name["local-plugin"]["status"] == PluginStatus.NEED_UPDATE + assert plugins_by_name["unpublished-plugin"]["status"] == PluginStatus.NOT_PUBLISHED + assert plugins_by_name["remote-only"]["status"] == PluginStatus.NOT_INSTALLED + assert len(plugins) == 3 From 6a66b9f58eb84de1e06744b9faddde188b28b054 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=9F=E9=B8=BFSeRazon?= Date: Tue, 9 Jun 2026 19:35:42 +0800 Subject: [PATCH 2/8] fix: guard plugin list directory iteration --- astrbot/cli/utils/plugin.py | 38 ++++++++-------- tests/test_cli_plugin_utils.py | 80 ++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 19 deletions(-) create mode 100644 tests/test_cli_plugin_utils.py diff --git a/astrbot/cli/utils/plugin.py b/astrbot/cli/utils/plugin.py index c06dda3500..768f4adbf6 100644 --- a/astrbot/cli/utils/plugin.py +++ b/astrbot/cli/utils/plugin.py @@ -114,9 +114,10 @@ def build_plug_list(plugins_dir: Path) -> list: """ # Get local plugin info result = [] - if plugins_dir.exists(): - for plugin_name in [d.name for d in plugins_dir.glob("*") if d.is_dir()]: - plugin_dir = plugins_dir / plugin_name + if plugins_dir.is_dir(): + for plugin_dir in plugins_dir.iterdir(): + if not plugin_dir.is_dir(): + continue # Load metadata from metadata.yaml metadata = load_yaml_metadata(plugin_dir) @@ -163,28 +164,27 @@ def build_plug_list(plugins_dir: Path) -> list: click.echo(f"Failed to get online plugin list: {e}", err=True) # Compare with online plugins and update status - online_plugin_names = {plugin["name"] for plugin in online_plugins} + online_plugins_by_name = {plugin["name"]: plugin for plugin in online_plugins} + local_plugin_names = {plugin["name"] for plugin in result} for local_plugin in result: - if local_plugin["name"] in online_plugin_names: - # Find the corresponding online plugin - online_plugin = next( - p for p in online_plugins if p["name"] == local_plugin["name"] - ) - if ( - VersionComparator.compare_version( - local_plugin["version"], - online_plugin["version"], - ) - < 0 - ): - local_plugin["status"] = PluginStatus.NEED_UPDATE - else: + online_plugin = online_plugins_by_name.get(local_plugin["name"]) + if online_plugin is None: # Local plugin is not published online local_plugin["status"] = PluginStatus.NOT_PUBLISHED + continue + + if ( + VersionComparator.compare_version( + local_plugin["version"], + online_plugin["version"], + ) + < 0 + ): + local_plugin["status"] = PluginStatus.NEED_UPDATE # Add uninstalled online plugins for online_plugin in online_plugins: - if not any(plugin["name"] == online_plugin["name"] for plugin in result): + if online_plugin["name"] not in local_plugin_names: result.append(online_plugin) return result diff --git a/tests/test_cli_plugin_utils.py b/tests/test_cli_plugin_utils.py new file mode 100644 index 0000000000..3e0090bd2e --- /dev/null +++ b/tests/test_cli_plugin_utils.py @@ -0,0 +1,80 @@ +from pathlib import Path + +from astrbot.cli.utils.plugin import PluginStatus, build_plug_list + + +class FakeResponse: + def raise_for_status(self): + return None + + def json(self): + return { + "local-plugin": { + "desc": "remote description", + "version": "2.0.0", + "author": "remote-author", + "repo": "https://example.com/local-plugin", + }, + "remote-only": { + "desc": "remote only", + "version": "1.0.0", + "author": "remote-author", + "repo": "https://example.com/remote-only", + }, + } + + +class FakeClient: + def __enter__(self): + return self + + def __exit__(self, exc_type, exc, tb): + return False + + def get(self, url): + assert url == "https://api.soulter.top/astrbot/plugins" + return FakeResponse() + + +def write_metadata(plugin_dir: Path, name: str, version: str) -> None: + plugin_dir.mkdir() + plugin_dir.joinpath("metadata.yaml").write_text( + f""" +name: {name} +desc: local description +version: {version} +author: local-author +repo: https://example.com/{name} +""".strip(), + encoding="utf-8", + ) + + +def test_build_plug_list_merges_local_and_remote_plugins(monkeypatch, tmp_path): + write_metadata(tmp_path / "local-plugin", "local-plugin", "1.0.0") + write_metadata(tmp_path / "unpublished-plugin", "unpublished-plugin", "1.0.0") + tmp_path.joinpath("ignored-file").write_text("not a plugin", encoding="utf-8") + + monkeypatch.setattr("astrbot.cli.utils.plugin.httpx.Client", FakeClient) + + plugins = build_plug_list(tmp_path) + plugins_by_name = {plugin["name"]: plugin for plugin in plugins} + + assert plugins_by_name["local-plugin"]["status"] == PluginStatus.NEED_UPDATE + assert plugins_by_name["unpublished-plugin"]["status"] == PluginStatus.NOT_PUBLISHED + assert plugins_by_name["remote-only"]["status"] == PluginStatus.NOT_INSTALLED + assert len(plugins) == 3 + + +def test_build_plug_list_treats_file_plugin_path_as_empty_local_set( + monkeypatch, tmp_path +): + plugins_file = tmp_path / "plugins" + plugins_file.write_text("not a directory", encoding="utf-8") + + monkeypatch.setattr("astrbot.cli.utils.plugin.httpx.Client", FakeClient) + + plugins = build_plug_list(plugins_file) + + assert [plugin["name"] for plugin in plugins] == ["local-plugin", "remote-only"] + assert all(plugin["status"] == PluginStatus.NOT_INSTALLED for plugin in plugins) From 043e2536f42f398888338b5e49e97a89851b8bcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=9F=E9=B8=BFSeRazon?= Date: Tue, 9 Jun 2026 19:39:40 +0800 Subject: [PATCH 3/8] test: move plugin utils tests to unit suite --- tests/{ => unit}/test_cli_plugin_utils.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{ => unit}/test_cli_plugin_utils.py (100%) diff --git a/tests/test_cli_plugin_utils.py b/tests/unit/test_cli_plugin_utils.py similarity index 100% rename from tests/test_cli_plugin_utils.py rename to tests/unit/test_cli_plugin_utils.py From db4ed63a4bc9466a028325e5cea48ee0f7f9ab8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=9F=E9=B8=BFSeRazon?= Date: Tue, 9 Jun 2026 19:44:46 +0800 Subject: [PATCH 4/8] fix: minimize plugin path guard change --- astrbot/cli/utils/plugin.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/astrbot/cli/utils/plugin.py b/astrbot/cli/utils/plugin.py index 768f4adbf6..234fee98b7 100644 --- a/astrbot/cli/utils/plugin.py +++ b/astrbot/cli/utils/plugin.py @@ -115,9 +115,8 @@ def build_plug_list(plugins_dir: Path) -> list: # Get local plugin info result = [] if plugins_dir.is_dir(): - for plugin_dir in plugins_dir.iterdir(): - if not plugin_dir.is_dir(): - continue + for plugin_name in [d.name for d in plugins_dir.glob("*") if d.is_dir()]: + plugin_dir = plugins_dir / plugin_name # Load metadata from metadata.yaml metadata = load_yaml_metadata(plugin_dir) @@ -164,27 +163,28 @@ def build_plug_list(plugins_dir: Path) -> list: click.echo(f"Failed to get online plugin list: {e}", err=True) # Compare with online plugins and update status - online_plugins_by_name = {plugin["name"]: plugin for plugin in online_plugins} - local_plugin_names = {plugin["name"] for plugin in result} + online_plugin_names = {plugin["name"] for plugin in online_plugins} for local_plugin in result: - online_plugin = online_plugins_by_name.get(local_plugin["name"]) - if online_plugin is None: + if local_plugin["name"] in online_plugin_names: + # Find the corresponding online plugin + online_plugin = next( + p for p in online_plugins if p["name"] == local_plugin["name"] + ) + if ( + VersionComparator.compare_version( + local_plugin["version"], + online_plugin["version"], + ) + < 0 + ): + local_plugin["status"] = PluginStatus.NEED_UPDATE + else: # Local plugin is not published online local_plugin["status"] = PluginStatus.NOT_PUBLISHED - continue - - if ( - VersionComparator.compare_version( - local_plugin["version"], - online_plugin["version"], - ) - < 0 - ): - local_plugin["status"] = PluginStatus.NEED_UPDATE # Add uninstalled online plugins for online_plugin in online_plugins: - if online_plugin["name"] not in local_plugin_names: + if not any(plugin["name"] == online_plugin["name"] for plugin in result): result.append(online_plugin) return result From 32bfefe5be3da467549b8b9ffdeceefab1ff3e5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=9F=E9=B8=BFSeRazon?= Date: Tue, 9 Jun 2026 19:50:40 +0800 Subject: [PATCH 5/8] test: cover missing plugin directory listing --- astrbot/cli/utils/plugin.py | 2 +- tests/unit/test_cli_plugin_utils.py | 93 +++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 tests/unit/test_cli_plugin_utils.py diff --git a/astrbot/cli/utils/plugin.py b/astrbot/cli/utils/plugin.py index c06dda3500..234fee98b7 100644 --- a/astrbot/cli/utils/plugin.py +++ b/astrbot/cli/utils/plugin.py @@ -114,7 +114,7 @@ def build_plug_list(plugins_dir: Path) -> list: """ # Get local plugin info result = [] - if plugins_dir.exists(): + if plugins_dir.is_dir(): for plugin_name in [d.name for d in plugins_dir.glob("*") if d.is_dir()]: plugin_dir = plugins_dir / plugin_name diff --git a/tests/unit/test_cli_plugin_utils.py b/tests/unit/test_cli_plugin_utils.py new file mode 100644 index 0000000000..d90c62c01f --- /dev/null +++ b/tests/unit/test_cli_plugin_utils.py @@ -0,0 +1,93 @@ +from pathlib import Path + +from astrbot.cli.utils.plugin import PluginStatus, build_plug_list + + +class FakeResponse: + def raise_for_status(self): + return None + + def json(self): + return { + "local-plugin": { + "desc": "remote description", + "version": "2.0.0", + "author": "remote-author", + "repo": "https://example.com/local-plugin", + }, + "remote-only": { + "desc": "remote only", + "version": "1.0.0", + "author": "remote-author", + "repo": "https://example.com/remote-only", + }, + } + + +class FakeClient: + def __enter__(self): + return self + + def __exit__(self, exc_type, exc, tb): + return False + + def get(self, url): + assert url == "https://api.soulter.top/astrbot/plugins" + return FakeResponse() + + +def write_metadata(plugin_dir: Path, name: str, version: str) -> None: + plugin_dir.mkdir() + plugin_dir.joinpath("metadata.yaml").write_text( + f""" +name: {name} +desc: local description +version: {version} +author: local-author +repo: https://example.com/{name} +""".strip(), + encoding="utf-8", + ) + + +def test_build_plug_list_merges_local_and_remote_plugins(monkeypatch, tmp_path): + write_metadata(tmp_path / "local-plugin", "local-plugin", "1.0.0") + write_metadata(tmp_path / "unpublished-plugin", "unpublished-plugin", "1.0.0") + tmp_path.joinpath("ignored-file").write_text("not a plugin", encoding="utf-8") + + monkeypatch.setattr("astrbot.cli.utils.plugin.httpx.Client", FakeClient) + + plugins = build_plug_list(tmp_path) + plugins_by_name = {plugin["name"]: plugin for plugin in plugins} + + assert plugins_by_name["local-plugin"]["status"] == PluginStatus.NEED_UPDATE + assert plugins_by_name["unpublished-plugin"]["status"] == PluginStatus.NOT_PUBLISHED + assert plugins_by_name["remote-only"]["status"] == PluginStatus.NOT_INSTALLED + assert len(plugins) == 3 + + +def test_build_plug_list_treats_file_plugin_path_as_empty_local_set( + monkeypatch, tmp_path +): + plugins_file = tmp_path / "plugins" + plugins_file.write_text("not a directory", encoding="utf-8") + + monkeypatch.setattr("astrbot.cli.utils.plugin.httpx.Client", FakeClient) + + plugins = build_plug_list(plugins_file) + + assert [plugin["name"] for plugin in plugins] == ["local-plugin", "remote-only"] + assert all(plugin["status"] == PluginStatus.NOT_INSTALLED for plugin in plugins) + + +def test_build_plug_list_treats_missing_plugin_path_as_empty_local_set( + monkeypatch, tmp_path +): + missing_plugins_dir = tmp_path / "missing-plugins" + + monkeypatch.setattr("astrbot.cli.utils.plugin.httpx.Client", FakeClient) + + plugins = build_plug_list(missing_plugins_dir) + + assert [plugin["name"] for plugin in plugins] == ["local-plugin", "remote-only"] + assert all(plugin["status"] == PluginStatus.NOT_INSTALLED for plugin in plugins) From 537b8aeee12f5d8ade83a22963c26ecf073356c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=9F=E9=B8=BFSeRazon?= Date: Thu, 11 Jun 2026 04:21:41 +0000 Subject: [PATCH 6/8] fix: address code review feedback on plugin utils and tests --- astrbot/cli/utils/plugin.py | 3 +- tests/test_cli_plugin_utils.py | 66 ----------------------------- tests/unit/test_cli_plugin_utils.py | 33 ++++++++++++++- 3 files changed, 34 insertions(+), 68 deletions(-) delete mode 100644 tests/test_cli_plugin_utils.py diff --git a/astrbot/cli/utils/plugin.py b/astrbot/cli/utils/plugin.py index 5e9ab74d34..30ebbe6386 100644 --- a/astrbot/cli/utils/plugin.py +++ b/astrbot/cli/utils/plugin.py @@ -114,7 +114,7 @@ def build_plug_list(plugins_dir: Path) -> list: """ # Get local plugin info result = [] - if plugins_dir.exists(): + if plugins_dir.is_dir(): for plugin_dir in plugins_dir.iterdir(): if not plugin_dir.is_dir(): continue @@ -186,6 +186,7 @@ def build_plug_list(plugins_dir: Path) -> list: for online_plugin in online_plugins: if online_plugin["name"] not in local_plugin_names: result.append(online_plugin) + local_plugin_names.add(online_plugin["name"]) return result diff --git a/tests/test_cli_plugin_utils.py b/tests/test_cli_plugin_utils.py deleted file mode 100644 index e9eb4b80b9..0000000000 --- a/tests/test_cli_plugin_utils.py +++ /dev/null @@ -1,66 +0,0 @@ -from pathlib import Path - -from astrbot.cli.utils.plugin import PluginStatus, build_plug_list - - -class FakeResponse: - def raise_for_status(self): - return None - - def json(self): - return { - "local-plugin": { - "desc": "remote description", - "version": "2.0.0", - "author": "remote-author", - "repo": "https://example.com/local-plugin", - }, - "remote-only": { - "desc": "remote only", - "version": "1.0.0", - "author": "remote-author", - "repo": "https://example.com/remote-only", - }, - } - - -class FakeClient: - def __enter__(self): - return self - - def __exit__(self, exc_type, exc, tb): - return False - - def get(self, url): - assert url == "https://api.soulter.top/astrbot/plugins" - return FakeResponse() - - -def write_metadata(plugin_dir: Path, name: str, version: str) -> None: - plugin_dir.mkdir() - plugin_dir.joinpath("metadata.yaml").write_text( - f""" -name: {name} -desc: local description -version: {version} -author: local-author -repo: https://example.com/{name} -""".strip(), - encoding="utf-8", - ) - - -def test_build_plug_list_merges_local_and_remote_plugins(monkeypatch, tmp_path): - write_metadata(tmp_path / "local-plugin", "local-plugin", "1.0.0") - write_metadata(tmp_path / "unpublished-plugin", "unpublished-plugin", "1.0.0") - tmp_path.joinpath("ignored-file").write_text("not a plugin", encoding="utf-8") - - monkeypatch.setattr("astrbot.cli.utils.plugin.httpx.Client", FakeClient) - - plugins = build_plug_list(tmp_path) - plugins_by_name = {plugin["name"]: plugin for plugin in plugins} - - assert plugins_by_name["local-plugin"]["status"] == PluginStatus.NEED_UPDATE - assert plugins_by_name["unpublished-plugin"]["status"] == PluginStatus.NOT_PUBLISHED - assert plugins_by_name["remote-only"]["status"] == PluginStatus.NOT_INSTALLED - assert len(plugins) == 3 diff --git a/tests/unit/test_cli_plugin_utils.py b/tests/unit/test_cli_plugin_utils.py index 3e0090bd2e..5d9007b876 100644 --- a/tests/unit/test_cli_plugin_utils.py +++ b/tests/unit/test_cli_plugin_utils.py @@ -37,7 +37,7 @@ def get(self, url): def write_metadata(plugin_dir: Path, name: str, version: str) -> None: - plugin_dir.mkdir() + plugin_dir.mkdir(parents=True, exist_ok=True) plugin_dir.joinpath("metadata.yaml").write_text( f""" name: {name} @@ -78,3 +78,34 @@ def test_build_plug_list_treats_file_plugin_path_as_empty_local_set( assert [plugin["name"] for plugin in plugins] == ["local-plugin", "remote-only"] assert all(plugin["status"] == PluginStatus.NOT_INSTALLED for plugin in plugins) + + +def test_build_plug_list_local_version_equal_or_newer(monkeypatch, tmp_path): + monkeypatch.setattr("astrbot.cli.utils.plugin.httpx.Client", FakeClient) + + # 1. test if local version == remote version + dir_equal = tmp_path / "dir_equal" + write_metadata(dir_equal / "local-plugin", "local-plugin", "2.0.0") + + plugins_equal = build_plug_list(dir_equal) + plugins_equal_by_name = {p["name"]: p for p in plugins_equal} + assert plugins_equal_by_name["local-plugin"]["status"] == PluginStatus.INSTALLED + + # 2. test if local version > remote version + dir_newer = tmp_path / "dir_newer" + write_metadata(dir_newer / "local-plugin", "local-plugin", "3.0.0") + + plugins_newer = build_plug_list(dir_newer) + plugins_newer_by_name = {p["name"]: p for p in plugins_newer} + assert plugins_newer_by_name["local-plugin"]["status"] == PluginStatus.INSTALLED + + +def test_build_plug_list_non_existent_path(monkeypatch, tmp_path): + non_existent_dir = tmp_path / "completely_non_existent_path" + + monkeypatch.setattr("astrbot.cli.utils.plugin.httpx.Client", FakeClient) + + plugins = build_plug_list(non_existent_dir) + + assert [plugin["name"] for plugin in plugins] == ["local-plugin", "remote-only"] + assert all(plugin["status"] == PluginStatus.NOT_INSTALLED for plugin in plugins) \ No newline at end of file From 017db6a3d878b64f35f1b21733354fa34953f2fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=9F=E9=B8=BFSeRazon?= Date: Thu, 11 Jun 2026 05:50:22 +0000 Subject: [PATCH 7/8] perf: optimize plugin comparison using dict pop method --- astrbot/cli/utils/plugin.py | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/astrbot/cli/utils/plugin.py b/astrbot/cli/utils/plugin.py index 30ebbe6386..ded6ebcfe2 100644 --- a/astrbot/cli/utils/plugin.py +++ b/astrbot/cli/utils/plugin.py @@ -142,32 +142,28 @@ def build_plug_list(plugins_dir: Path) -> list: ) # Get online plugin list - online_plugins = [] + online_plugins_dict = {} try: with httpx.Client() as client: resp = client.get("https://api.soulter.top/astrbot/plugins") resp.raise_for_status() data = resp.json() for plugin_id, plugin_info in data.items(): - online_plugins.append( - { - "name": str(plugin_id), - "desc": str(plugin_info.get("desc", "")), - "version": str(plugin_info.get("version", "")), - "author": str(plugin_info.get("author", "")), - "repo": str(plugin_info.get("repo", "")), - "status": PluginStatus.NOT_INSTALLED, - "local_path": None, - }, - ) + online_plugins_dict[str(plugin_id)] = { + "name": str(plugin_id), + "desc": str(plugin_info.get("desc", "")), + "version": str(plugin_info.get("version", "")), + "author": str(plugin_info.get("author", "")), + "repo": str(plugin_info.get("repo", "")), + "status": PluginStatus.NOT_INSTALLED, + "local_path": None, + } except Exception as e: click.echo(f"Failed to get online plugin list: {e}", err=True) # Compare with online plugins and update status - online_plugins_by_name = {plugin["name"]: plugin for plugin in online_plugins} - local_plugin_names = {plugin["name"] for plugin in result} for local_plugin in result: - online_plugin = online_plugins_by_name.get(local_plugin["name"]) + online_plugin = online_plugins_dict.pop(local_plugin["name"], None) if online_plugin is None: # Local plugin is not published online local_plugin["status"] = PluginStatus.NOT_PUBLISHED @@ -183,14 +179,10 @@ def build_plug_list(plugins_dir: Path) -> list: local_plugin["status"] = PluginStatus.NEED_UPDATE # Add uninstalled online plugins - for online_plugin in online_plugins: - if online_plugin["name"] not in local_plugin_names: - result.append(online_plugin) - local_plugin_names.add(online_plugin["name"]) + result.extend(online_plugins_dict.values()) return result - def manage_plugin( plugin: dict, plugins_dir: Path, From 53d3089e3087cc6e6562a196c58e6d32f1b5e4da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=9F=E9=B8=BFSeRazon?= Date: Thu, 11 Jun 2026 07:32:28 +0000 Subject: [PATCH 8/8] style: format plugin.py and retry CI --- astrbot/cli/utils/plugin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/astrbot/cli/utils/plugin.py b/astrbot/cli/utils/plugin.py index ded6ebcfe2..81d07e13ae 100644 --- a/astrbot/cli/utils/plugin.py +++ b/astrbot/cli/utils/plugin.py @@ -183,6 +183,7 @@ def build_plug_list(plugins_dir: Path) -> list: return result + def manage_plugin( plugin: dict, plugins_dir: Path,