Skip to content

feat: add support for plugin astrbot-version and platform requirement checks#5235

Merged
Soulter merged 5 commits intomasterfrom
feat/plugin-astrbot-version-and-platform-checks
Feb 20, 2026
Merged

feat: add support for plugin astrbot-version and platform requirement checks#5235
Soulter merged 5 commits intomasterfrom
feat/plugin-astrbot-version-and-platform-checks

Conversation

@Soulter
Copy link
Member

@Soulter Soulter commented Feb 19, 2026

Modifications / 改动点

  • This is NOT a breaking change. / 这不是一个破坏性变更。

Screenshots or Test Results / 运行截图或测试结果


Checklist / 检查清单

  • 😊 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。/ If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
  • 👀 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”。/ My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
  • 🤓 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到了 requirements.txtpyproject.toml 文件相应位置。/ I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in requirements.txt and pyproject.toml.
  • 😮 我的更改没有引入恶意代码。/ My changes do not introduce malicious code.

Summary by Sourcery

为 AstrBot 的插件元数据添加版本要求和支持平台信息,在插件安装过程中强制进行兼容性检查,并在整个控制台界面中展示这些信息。

New Features:

  • 在插件元数据、API 响应以及控制台视图中暴露插件的 astrbot_version 要求和支持的平台信息。
  • 在后端和控制台两侧引入针对当前 AstrBot 版本的插件兼容性检查,包括用于预检查兼容性的专用 API。
  • 允许用户在通过 URL 或文件上传安装插件时绕过 AstrBot 版本不兼容警告。

Enhancements:

  • 在已安装插件卡片和插件市场卡片上显示支持平台和 AstrBot 版本要求,并提供本地化的平台名称和工具提示。
  • 改进插件市场搜索,支持按支持平台和 AstrBot 版本字段进行过滤。
  • 在本地存在已安装插件数据时,利用这些数据为插件市场条目回填缺失的 support_platformsastrbot_version 元数据。
  • 扩展平台适配器类型支持,新增对 wecom_ai_bot 适配器的支持。

Build:

  • 新增 packaging 作为运行时依赖,用于版本说明符的解析与比较。
Original summary in English

Summary by Sourcery

Add plugin metadata support for AstrBot version requirements and supported platforms, enforce compatibility checks during plugin installation, and surface this information throughout the dashboard UI.

New Features:

  • Expose plugin astrbot_version requirements and supported platforms across plugin metadata, API responses, and dashboard views.
  • Introduce backend and dashboard-side checks for plugin compatibility with the current AstrBot version, including a dedicated API for pre-checking compatibility.
  • Allow users to bypass AstrBot version incompatibility warnings when installing plugins via URL or file upload.

Enhancements:

  • Display supported platforms and AstrBot version requirements on installed plugin cards and marketplace plugin cards, including localized platform names and tooltips.
  • Improve plugin market search by allowing filtering by supported platforms and AstrBot version fields.
  • Backfill missing support_platforms and astrbot_version metadata for marketplace entries using locally installed plugin data when available.
  • Extend platform adapter type support to include the wecom_ai_bot adapter.

Build:

  • Add packaging as a runtime dependency for version specifier parsing and comparison.

@dosubot dosubot bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label Feb 19, 2026
@dosubot
Copy link

dosubot bot commented Feb 19, 2026

Related Documentation

Checked 1 published document(s) in 1 knowledge base(s). No updates required.

How did I do? Any feedback?  Join Discord

@dosubot dosubot bot added area:platform The bug / feature is about IM platform adapter, such as QQ, Lark, Telegram, WebChat and so on. feature:plugin The bug / feature is about AstrBot plugin system. labels Feb 19, 2026
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - 我发现了两个问题,并给出了一些整体性的反馈:

  • dashboard/routes/plugin.py 中你捕获了 PluginVersionIncompatibleError,但从未导入它,所以这个路由在运行时会抛出 NameError;请从 astrbot.core.star.star_manager 中添加相应的导入。
  • ExtensionPage.vueMarketPluginCard.vue 都定义了类似的 normalizePlatformList / 平台展示辅助函数;建议把这些逻辑提取到一个共享的工具模块中,以避免重复,并把平台展示相关逻辑集中到一个地方。
  • /plugin/check-compat 路由调用了私有方法 plugin_manager._validate_astrbot_version_specifier;更干净的做法是把它作为 PluginManager 上的公共辅助方法暴露出来(例如 validate_astrbot_version_specifier),而不是在外部依赖一个以下划线开头的私有方法。
面向 AI Agent 的提示词
Please address the comments from this code review:

## Overall Comments
- In `dashboard/routes/plugin.py` you catch `PluginVersionIncompatibleError` but never import it, so this route will raise a `NameError` at runtime; add the appropriate import from `astrbot.core.star.star_manager`.
- Both `ExtensionPage.vue` and `MarketPluginCard.vue` define similar `normalizePlatformList` / platform-display helpers; consider extracting these into a shared utility to avoid duplication and keep platform presentation logic in one place.
- The `/plugin/check-compat` route calls the private method `plugin_manager._validate_astrbot_version_specifier`; it would be cleaner to expose this as a public helper on `PluginManager` (e.g. `validate_astrbot_version_specifier`) instead of relying on a leading-underscore method from outside.

## Individual Comments

### Comment 1
<location> `dashboard/src/views/ExtensionPage.vue:1151-1160` </location>
<code_context>
+  );
+};
+
+const resolveSelectedInstallPlugin = () => {
+  if (
+    selectedMarketInstallPlugin.value &&
+    selectedMarketInstallPlugin.value.repo === extension_url.value
+  ) {
+    return selectedMarketInstallPlugin.value;
+  }
+  return pluginMarketData.value.find((plugin) => plugin.repo === extension_url.value) || null;
+};
+
</code_context>

<issue_to_address>
**suggestion:** 当通过 repo URL 解析选中的安装插件时,建议使用不区分大小写的比较。

当前的解析逻辑对 `plugin.repo === extension_url.value` 使用了严格相等比较,而在其他地方(例如 `checkAlreadyInstalled`)中,repo URL 会被归一化为小写。如果粘贴的 URL 大小写与市场数据中的不一致,`selectedInstallPlugin` 可能会是 `null`,从而导致兼容性检查和元数据无法展示。建议对两边都做归一化(例如 `plugin.repo?.toLowerCase()` 对比 `extension_url.value.toLowerCase()`),并处理 repo 可能为 `null` 的情况。

```suggestion
+const resolveSelectedInstallPlugin = () => {
+  const normalizedExtensionUrl = extension_url.value?.toLowerCase();
+
+  if (!normalizedExtensionUrl) {
+    return null;
+  }
+
+  if (
+    selectedMarketInstallPlugin.value &&
+    selectedMarketInstallPlugin.value.repo?.toLowerCase() === normalizedExtensionUrl
+  ) {
+    return selectedMarketInstallPlugin.value;
+  }
+
+  return (
+    pluginMarketData.value.find(
+      (plugin) => plugin.repo?.toLowerCase() === normalizedExtensionUrl,
+    ) || null
+  );
+};
+
```
</issue_to_address>

### Comment 2
<location> `astrbot/core/star/star_manager.py:297-306` </location>
<code_context>
         return metadata

+    @staticmethod
+    def _validate_astrbot_version_specifier(
+        version_spec: str | None,
+    ) -> tuple[bool, str | None]:
+        if not version_spec:
+            return True, None
+
+        normalized_spec = version_spec.strip()
+        if not normalized_spec:
+            return True, None
+
+        if "v" in normalized_spec.lower():
+            return (
+                False,
</code_context>

<issue_to_address>
**issue (bug_risk):** 这种对 `"v"` 的全量检查,会错误地拒绝合法的 PEP 440 版本说明符。

这个条件会拒绝任何包含 `v` 的说明符,包括像 `1.0.0.dev0` 或本地版本这样的合法版本,从而产生误报,即便 `packaging` 实际上可以正确解析它们。如果你只想阻止**** `v` 开头的版本(例如 `v4.16`),可以考虑改为检查 `normalized_spec.lstrip().lower().startswith('v')`,或者在解析前先剥掉单个前导 `v`。
</issue_to_address>

Sourcery 对开源项目是免费的——如果你觉得这些代码审查有帮助,欢迎分享 ✨
帮我变得更有用!请对每条评论点 👍 或 👎,我会基于这些反馈改进以后的审查。
Original comment in English

Hey - I've found 2 issues, and left some high level feedback:

  • In dashboard/routes/plugin.py you catch PluginVersionIncompatibleError but never import it, so this route will raise a NameError at runtime; add the appropriate import from astrbot.core.star.star_manager.
  • Both ExtensionPage.vue and MarketPluginCard.vue define similar normalizePlatformList / platform-display helpers; consider extracting these into a shared utility to avoid duplication and keep platform presentation logic in one place.
  • The /plugin/check-compat route calls the private method plugin_manager._validate_astrbot_version_specifier; it would be cleaner to expose this as a public helper on PluginManager (e.g. validate_astrbot_version_specifier) instead of relying on a leading-underscore method from outside.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `dashboard/routes/plugin.py` you catch `PluginVersionIncompatibleError` but never import it, so this route will raise a `NameError` at runtime; add the appropriate import from `astrbot.core.star.star_manager`.
- Both `ExtensionPage.vue` and `MarketPluginCard.vue` define similar `normalizePlatformList` / platform-display helpers; consider extracting these into a shared utility to avoid duplication and keep platform presentation logic in one place.
- The `/plugin/check-compat` route calls the private method `plugin_manager._validate_astrbot_version_specifier`; it would be cleaner to expose this as a public helper on `PluginManager` (e.g. `validate_astrbot_version_specifier`) instead of relying on a leading-underscore method from outside.

## Individual Comments

### Comment 1
<location> `dashboard/src/views/ExtensionPage.vue:1151-1160` </location>
<code_context>
+  );
+};
+
+const resolveSelectedInstallPlugin = () => {
+  if (
+    selectedMarketInstallPlugin.value &&
+    selectedMarketInstallPlugin.value.repo === extension_url.value
+  ) {
+    return selectedMarketInstallPlugin.value;
+  }
+  return pluginMarketData.value.find((plugin) => plugin.repo === extension_url.value) || null;
+};
+
</code_context>

<issue_to_address>
**suggestion:** Use case-insensitive comparison when resolving the selected install plugin by repo URL.

This resolver does a strict equality check on `plugin.repo === extension_url.value`, while elsewhere (e.g. `checkAlreadyInstalled`) repo URLs are normalized to lowercase. If the pasted URL’s casing differs from the market data, `selectedInstallPlugin` can be `null`, so compatibility checks and metadata won’t appear. Consider normalizing both sides (e.g. `plugin.repo?.toLowerCase()` vs `extension_url.value.toLowerCase()`) and handling a possible `null` repo.

```suggestion
+const resolveSelectedInstallPlugin = () => {
+  const normalizedExtensionUrl = extension_url.value?.toLowerCase();
+
+  if (!normalizedExtensionUrl) {
+    return null;
+  }
+
+  if (
+    selectedMarketInstallPlugin.value &&
+    selectedMarketInstallPlugin.value.repo?.toLowerCase() === normalizedExtensionUrl
+  ) {
+    return selectedMarketInstallPlugin.value;
+  }
+
+  return (
+    pluginMarketData.value.find(
+      (plugin) => plugin.repo?.toLowerCase() === normalizedExtensionUrl,
+    ) || null
+  );
+};
+
```
</issue_to_address>

### Comment 2
<location> `astrbot/core/star/star_manager.py:297-306` </location>
<code_context>
         return metadata

+    @staticmethod
+    def _validate_astrbot_version_specifier(
+        version_spec: str | None,
+    ) -> tuple[bool, str | None]:
+        if not version_spec:
+            return True, None
+
+        normalized_spec = version_spec.strip()
+        if not normalized_spec:
+            return True, None
+
+        if "v" in normalized_spec.lower():
+            return (
+                False,
</code_context>

<issue_to_address>
**issue (bug_risk):** The blanket `"v"` check can incorrectly reject valid PEP 440 version specifiers.

This condition rejects any specifier containing `v`, including valid ones like `1.0.0.dev0` or local versions, leading to false negatives even though `packaging` can parse them. If you only want to block a *leading* `v` (e.g. `v4.16`), consider checking `normalized_spec.lstrip().lower().startswith('v')` or stripping a single leading `v` before parsing instead.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +1151 to +1160
const resolveSelectedInstallPlugin = () => {
if (
selectedMarketInstallPlugin.value &&
selectedMarketInstallPlugin.value.repo === extension_url.value
) {
return selectedMarketInstallPlugin.value;
}
return pluginMarketData.value.find((plugin) => plugin.repo === extension_url.value) || null;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: 当通过 repo URL 解析选中的安装插件时,建议使用不区分大小写的比较。

当前的解析逻辑对 plugin.repo === extension_url.value 使用了严格相等比较,而在其他地方(例如 checkAlreadyInstalled)中,repo URL 会被归一化为小写。如果粘贴的 URL 大小写与市场数据中的不一致,selectedInstallPlugin 可能会是 null,从而导致兼容性检查和元数据无法展示。建议对两边都做归一化(例如 plugin.repo?.toLowerCase() 对比 extension_url.value.toLowerCase()),并处理 repo 可能为 null 的情况。

Suggested change
const resolveSelectedInstallPlugin = () => {
if (
selectedMarketInstallPlugin.value &&
selectedMarketInstallPlugin.value.repo === extension_url.value
) {
return selectedMarketInstallPlugin.value;
}
return pluginMarketData.value.find((plugin) => plugin.repo === extension_url.value) || null;
};
+const resolveSelectedInstallPlugin = () => {
+ const normalizedExtensionUrl = extension_url.value?.toLowerCase();
+
+ if (!normalizedExtensionUrl) {
+ return null;
+ }
+
+ if (
+ selectedMarketInstallPlugin.value &&
+ selectedMarketInstallPlugin.value.repo?.toLowerCase() === normalizedExtensionUrl
+ ) {
+ return selectedMarketInstallPlugin.value;
+ }
+
+ return (
+ pluginMarketData.value.find(
+ (plugin) => plugin.repo?.toLowerCase() === normalizedExtensionUrl,
+ ) || null
+ );
+};
+
Original comment in English

suggestion: Use case-insensitive comparison when resolving the selected install plugin by repo URL.

This resolver does a strict equality check on plugin.repo === extension_url.value, while elsewhere (e.g. checkAlreadyInstalled) repo URLs are normalized to lowercase. If the pasted URL’s casing differs from the market data, selectedInstallPlugin can be null, so compatibility checks and metadata won’t appear. Consider normalizing both sides (e.g. plugin.repo?.toLowerCase() vs extension_url.value.toLowerCase()) and handling a possible null repo.

Suggested change
const resolveSelectedInstallPlugin = () => {
if (
selectedMarketInstallPlugin.value &&
selectedMarketInstallPlugin.value.repo === extension_url.value
) {
return selectedMarketInstallPlugin.value;
}
return pluginMarketData.value.find((plugin) => plugin.repo === extension_url.value) || null;
};
+const resolveSelectedInstallPlugin = () => {
+ const normalizedExtensionUrl = extension_url.value?.toLowerCase();
+
+ if (!normalizedExtensionUrl) {
+ return null;
+ }
+
+ if (
+ selectedMarketInstallPlugin.value &&
+ selectedMarketInstallPlugin.value.repo?.toLowerCase() === normalizedExtensionUrl
+ ) {
+ return selectedMarketInstallPlugin.value;
+ }
+
+ return (
+ pluginMarketData.value.find(
+ (plugin) => plugin.repo?.toLowerCase() === normalizedExtensionUrl,
+ ) || null
+ );
+};
+

@Soulter Soulter merged commit 9a7a594 into master Feb 20, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:platform The bug / feature is about IM platform adapter, such as QQ, Lark, Telegram, WebChat and so on. feature:plugin The bug / feature is about AstrBot plugin system. size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments