Skip to content

Feat: export and import plugins#8165

Open
M1LKT wants to merge 5 commits into
AstrBotDevs:masterfrom
M1LKT:feat/exportPlugin
Open

Feat: export and import plugins#8165
M1LKT wants to merge 5 commits into
AstrBotDevs:masterfrom
M1LKT:feat/exportPlugin

Conversation

@M1LKT
Copy link
Copy Markdown
Contributor

@M1LKT M1LKT commented May 12, 2026

Modifications / 改动点

核心功能:插件导入/导出功能

本次 PR 为 AstrBot Dashboard 新增了插件导入/导出功能,方便用户备份和分享已安装的插件配置。

修改的文件:

  1. src/views/extension/InstalledPluginsTab.vue

    • 新增三种导出模式:导出全部筛选插件、导出置顶插件、挑选插件导出
    • 新增导出插件选择对话框,支持全选/反选、显示插件 logo 和仓库链接
    • 导出时排除 astrbotbuiltin_commands 两个内置插件
    • 使用 LZString 压缩编码插件列表
  2. src/components/extension/PluginImportDialog.vue

    • 新增插件导入对话框,支持粘贴插件码
    • 解析后批量选择要导入的插件
    • 支持逐个顺序安装,实时显示安装状态(等待中/安装中/成功/失败)
    • 新增安全提示警告,提醒用户自行检查插件安全性
    • 仓库链接可点击跳转
  3. src/i18n/locales/zh-CN/features/extension.json

    • 新增 exportImport 分类,包含导出/导入相关的所有 i18n 文本
  4. src/i18n/locales/en-US/features/extension.json

    • 新增英文翻译
  5. dashboard/package.json

    • 新增 lz-string 依赖(用于压缩/解压插件码)

功能特点:

  • 导出:支持一键导出当前筛选结果、置顶插件列表或手动挑选

  • 导入:粘贴插件码 → 解析预览 → 选择安装 → 顺序下载

  • 压缩:使用 LZString 进行压缩,方便复制分享

  • 安全提示:导入时显示安全警告,提醒用户验证插件来源

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

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

导出插件码:
image
导出自选插件:
image
解析插件并导入下载:
image


Checklist / 检查清单

  • 😊 If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
    / 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。

  • 👀 My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
    / 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”

  • 🤓 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.
    / 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到 requirements.txtpyproject.toml 文件相应位置。

  • 😮 My changes do not introduce malicious code.
    / 我的更改没有引入恶意代码。

Summary by Sourcery

Add plugin export/import capabilities to the AstrBot Dashboard, enabling users to share and restore plugin configurations via compressed plugin codes.

New Features:

  • Add multiple plugin export modes from the installed plugins view, including filtered, pinned, and manually selected plugins, excluding built-in entries.
  • Introduce a plugin import dialog that parses compressed plugin codes, lets users select plugins, and installs them sequentially with status feedback.
  • Display generated plugin codes in the UI with copy-to-clipboard support for easy sharing.

Enhancements:

  • Add plugin repository links and icons in export/import selection lists for better plugin identification.
  • Extend the dashboard icon subset with additional MDI icons used by the new export/import UI.
  • Add localized i18n strings for export/import flows in both English and Chinese.

Build:

  • Add the lz-string dependency to support compression and decompression of plugin export/import codes.

@dosubot dosubot Bot added size:XL This PR changes 500-999 lines, ignoring generated files. area:webui The bug / feature is about webui(dashboard) of astrbot. feature:plugin The bug / feature is about AstrBot plugin system. labels May 12, 2026
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces plugin export and import functionality to the dashboard, allowing users to share plugin configurations via compressed strings. Key changes include the addition of the lz-string library, new Material Design Icons, and the implementation of a PluginImportDialog component. Feedback identifies a logic error in the import progress calculation and missing props/event handlers in the dialog's integration that are necessary for proxy support and UI refreshing.

Comment thread dashboard/src/components/extension/PluginImportDialog.vue
Comment thread dashboard/src/views/extension/InstalledPluginsTab.vue Outdated
Copy link
Copy Markdown
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 - I've found 2 issues, and left some high level feedback:

  • In PluginImportDialog.vue, installStatus mixes numeric keys for statuses and ${idx}_msg keys for error messages on the same object, which makes it harder to reason about; consider using a structured map like { [idx]: { status, message } } to keep per-plugin state cohesive and reduce key collisions.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `PluginImportDialog.vue`, `installStatus` mixes numeric keys for statuses and `${idx}_msg` keys for error messages on the same object, which makes it harder to reason about; consider using a structured map like `{ [idx]: { status, message } }` to keep per-plugin state cohesive and reduce key collisions.

## Individual Comments

### Comment 1
<location path="dashboard/src/views/extension/InstalledPluginsTab.vue" line_range="284-205" />
<code_context>
+  exportPlugin(picked);
+}
+
+const copyExportCode = async () => {
+  try {
+    await navigator.clipboard.writeText(exportCode.value);
+    toast(tm("exportImport.errors.copySuccess"), "success");
+  } catch (err) {
+    console.error("Copy failed", err);
+    toast(tm("exportImport.errors.copyFailed"), "error");
+  }
+}
+
</code_context>
<issue_to_address>
**suggestion:** Handle environments where `navigator.clipboard` is unavailable with a graceful fallback.

This relies on `navigator.clipboard.writeText` always being present and allowed. In environments where it’s missing or disallowed (older browsers, some WebViews, non-HTTPS), the copy flow will just fail. Since you already handle errors, consider first checking for `navigator.clipboard?.writeText` and, if unavailable, falling back to selecting the textarea via a ref (or at least surfacing a clearer “please copy manually” message).

Suggested implementation:

```
const copyExportCode = async () => {
  if (!exportCode.value) {
    toast(tm("exportImport.errors.nothingToCopy"), "warning");
    return;
  }

  const clipboard = typeof navigator !== "undefined" ? navigator.clipboard : undefined;
  const canUseClipboardApi =
    clipboard && typeof clipboard.writeText === "function";

  if (!canUseClipboardApi) {
    // Clipboard API not available or not allowed in this environment
    toast(tm("exportImport.errors.copyUnavailable"), "warning");
    return;
  }

  try {
    await clipboard.writeText(exportCode.value);
    toast(tm("exportImport.errors.copySuccess"), "success");
  } catch (err) {
    console.error("Copy failed", err);
    toast(tm("exportImport.errors.copyFailed"), "error");
  }
};

const showExportCode = ref(false);
const exportCode = ref("");

```

To provide a richer fallback (e.g. auto-selecting the export code for manual copy), you may also want to:
1. Add a `const exportCodeTextarea = ref(null);` in the script and bind it to the export textarea in the template via `ref="exportCodeTextarea"`.
2. In the `!canUseClipboardApi` branch, before the toast, call something like:
   ```js
   if (exportCodeTextarea.value) {
     exportCodeTextarea.value.focus();
     exportCodeTextarea.value.select();
   }
   toast(tm("exportImport.errors.copyUnavailable"), "warning");
   ```
3. Ensure the new i18n keys (`exportImport.errors.nothingToCopy`, `exportImport.errors.copyUnavailable`) are added to your locale files.
</issue_to_address>

### Comment 2
<location path="dashboard/src/components/extension/PluginImportDialog.vue" line_range="71-62" />
<code_context>
+  emit("update:modelValue", false);
+};
+
+const parseImportCode = () => {
+  importError.value = "";
+  importPlugins.value = [];
+  selected.value = [];
+  installStatus.value = {};
+  const code = (importCode.value || "").trim();
+  if (!code) {
+    importError.value = tm("exportImport.errors.needCode");
+    return;
+  }
+  try {
+    const jsonStr = LZString.decompressFromEncodedURIComponent(code);
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Avoid clearing existing parsed plugins before validating new import code.

`parseImportCode` clears `importPlugins`, `selected`, and `installStatus` before decompression/parsing. If parsing fails (e.g., invalid snippet), the previous valid state is lost. Consider parsing into local variables first, validating, and only then updating `importPlugins`/`selected` so failures leave the existing state intact and only surface an error.
</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.

@@ -202,6 +205,98 @@ const togglePinnedExtension = (extension) => {
}
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: Handle environments where navigator.clipboard is unavailable with a graceful fallback.

This relies on navigator.clipboard.writeText always being present and allowed. In environments where it’s missing or disallowed (older browsers, some WebViews, non-HTTPS), the copy flow will just fail. Since you already handle errors, consider first checking for navigator.clipboard?.writeText and, if unavailable, falling back to selecting the textarea via a ref (or at least surfacing a clearer “please copy manually” message).

Suggested implementation:

const copyExportCode = async () => {
  if (!exportCode.value) {
    toast(tm("exportImport.errors.nothingToCopy"), "warning");
    return;
  }

  const clipboard = typeof navigator !== "undefined" ? navigator.clipboard : undefined;
  const canUseClipboardApi =
    clipboard && typeof clipboard.writeText === "function";

  if (!canUseClipboardApi) {
    // Clipboard API not available or not allowed in this environment
    toast(tm("exportImport.errors.copyUnavailable"), "warning");
    return;
  }

  try {
    await clipboard.writeText(exportCode.value);
    toast(tm("exportImport.errors.copySuccess"), "success");
  } catch (err) {
    console.error("Copy failed", err);
    toast(tm("exportImport.errors.copyFailed"), "error");
  }
};

const showExportCode = ref(false);
const exportCode = ref("");

To provide a richer fallback (e.g. auto-selecting the export code for manual copy), you may also want to:

  1. Add a const exportCodeTextarea = ref(null); in the script and bind it to the export textarea in the template via ref="exportCodeTextarea".
  2. In the !canUseClipboardApi branch, before the toast, call something like:
    if (exportCodeTextarea.value) {
      exportCodeTextarea.value.focus();
      exportCodeTextarea.value.select();
    }
    toast(tm("exportImport.errors.copyUnavailable"), "warning");
  3. Ensure the new i18n keys (exportImport.errors.nothingToCopy, exportImport.errors.copyUnavailable) are added to your locale files.

selected.value = [];
installStatus.value = {};
installing.value = false;
}
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 (bug_risk): Avoid clearing existing parsed plugins before validating new import code.

parseImportCode clears importPlugins, selected, and installStatus before decompression/parsing. If parsing fails (e.g., invalid snippet), the previous valid state is lost. Consider parsing into local variables first, validating, and only then updating importPlugins/selected so failures leave the existing state intact and only surface an error.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:webui The bug / feature is about webui(dashboard) of astrbot. 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