mirror of
https://github.com/AstrBotDevs/AstrBot.git
synced 2026-08-31 01:40:25 +08:00
feat: plugin changelogs and update system
This commit is contained in:
@@ -1657,7 +1657,9 @@ class PluginManager:
|
||||
is_reserved=plugin.reserved,
|
||||
)
|
||||
|
||||
async def update_plugin(self, plugin_name: str, proxy="") -> None:
|
||||
async def update_plugin(
|
||||
self, plugin_name: str, proxy="", download_url: str = ""
|
||||
) -> None:
|
||||
"""升级一个插件"""
|
||||
plugin = self.context.get_registered_star(plugin_name)
|
||||
if not plugin:
|
||||
@@ -1665,7 +1667,7 @@ class PluginManager:
|
||||
if plugin.reserved:
|
||||
raise Exception("该插件是 AstrBot 保留插件,无法更新。")
|
||||
|
||||
await self.updator.update(plugin, proxy=proxy)
|
||||
await self.updator.update(plugin, proxy=proxy, download_url=download_url)
|
||||
if plugin.root_dir_name:
|
||||
plugin_dir_path = os.path.join(self.plugin_store_path, plugin.root_dir_name)
|
||||
await self._ensure_plugin_requirements(
|
||||
|
||||
@@ -31,11 +31,15 @@ class PluginUpdator(RepoZipUpdator):
|
||||
|
||||
return plugin_path
|
||||
|
||||
async def update(self, plugin: StarMetadata, proxy="") -> str:
|
||||
async def update(
|
||||
self, plugin: StarMetadata, proxy="", download_url: str = ""
|
||||
) -> str:
|
||||
repo_url = plugin.repo
|
||||
|
||||
if not repo_url:
|
||||
raise Exception(f"Plugin {plugin.name} does not specify a repository URL.")
|
||||
if not repo_url and not download_url:
|
||||
raise Exception(
|
||||
f"Plugin {plugin.name} does not specify a repository URL or download URL."
|
||||
)
|
||||
|
||||
if not plugin.root_dir_name:
|
||||
raise Exception(
|
||||
@@ -47,7 +51,13 @@ class PluginUpdator(RepoZipUpdator):
|
||||
logger.info(
|
||||
f"Updating plugin at path: {plugin_path}, repository URL: {repo_url}",
|
||||
)
|
||||
await self.download_from_repo_url(plugin_path, repo_url, proxy=proxy)
|
||||
if download_url:
|
||||
logger.info(
|
||||
f"Downloading plugin update archive for {plugin.name}: {download_url}"
|
||||
)
|
||||
await self._download_file(download_url, plugin_path + ".zip")
|
||||
else:
|
||||
await self.download_from_repo_url(plugin_path, repo_url, proxy=proxy)
|
||||
|
||||
try:
|
||||
remove_dir(plugin_path)
|
||||
|
||||
@@ -1708,9 +1708,12 @@ class PluginRoute(Route):
|
||||
post_data = await request.get_json()
|
||||
plugin_name = post_data["name"]
|
||||
proxy: str = post_data.get("proxy", None)
|
||||
download_url = str(post_data.get("download_url") or "").strip()
|
||||
try:
|
||||
logger.info(f"正在更新插件 {plugin_name}")
|
||||
await self.plugin_manager.update_plugin(plugin_name, proxy)
|
||||
await self.plugin_manager.update_plugin(
|
||||
plugin_name, proxy, download_url=download_url
|
||||
)
|
||||
# self.core_lifecycle.restart()
|
||||
await self.plugin_manager.reload(plugin_name)
|
||||
await self._sync_skills_after_plugin_change()
|
||||
@@ -1731,9 +1734,12 @@ class PluginRoute(Route):
|
||||
post_data = await request.get_json()
|
||||
plugin_names: list[str] = post_data.get("names") or []
|
||||
proxy: str = post_data.get("proxy", "")
|
||||
download_urls: dict[str, str] = post_data.get("download_urls") or {}
|
||||
|
||||
if not isinstance(plugin_names, list) or not plugin_names:
|
||||
return Response().error("插件列表不能为空").__dict__
|
||||
if not isinstance(download_urls, dict):
|
||||
download_urls = {}
|
||||
|
||||
results = []
|
||||
sem = asyncio.Semaphore(PLUGIN_UPDATE_CONCURRENCY)
|
||||
@@ -1742,7 +1748,10 @@ class PluginRoute(Route):
|
||||
async with sem:
|
||||
try:
|
||||
logger.info(f"批量更新插件 {name}")
|
||||
await self.plugin_manager.update_plugin(name, proxy)
|
||||
download_url = str(download_urls.get(name) or "").strip()
|
||||
await self.plugin_manager.update_plugin(
|
||||
name, proxy, download_url=download_url
|
||||
)
|
||||
return {"name": name, "status": "ok", "message": "更新成功"}
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
|
||||
@@ -104,6 +104,8 @@
|
||||
"notFound": "Plugin not found",
|
||||
"docsTitle": "Documentation",
|
||||
"docsEmpty": "No documentation",
|
||||
"changelogTitle": "Changelog",
|
||||
"changelogEmpty": "No changelog",
|
||||
"handlerGroups": {
|
||||
"page": "Pages",
|
||||
"skill": "Skills",
|
||||
@@ -213,6 +215,12 @@
|
||||
"downloadSource": "The plugin package will be installed from:",
|
||||
"githubSecurityWarning": "AstrBot cannot guarantee the safety of plugins downloaded from GitHub. Install only if you trust the plugin source."
|
||||
},
|
||||
"update": {
|
||||
"title": "Update Extension",
|
||||
"sectionTitle": "Update",
|
||||
"downloadSource": "The update package will be installed from:",
|
||||
"confirm": "Confirm Update"
|
||||
},
|
||||
"danger_warning": {
|
||||
"title": "Dangerous Plugin Warning",
|
||||
"message": "This plugin has been flagged as containing security risks, including unsafe code or functionalities that may cause system malfunctions or data loss. Do you wish to proceed with the installation?",
|
||||
|
||||
@@ -104,6 +104,8 @@
|
||||
"notFound": "Плагин не найден",
|
||||
"docsTitle": "Документация",
|
||||
"docsEmpty": "Документация отсутствует",
|
||||
"changelogTitle": "Журнал изменений",
|
||||
"changelogEmpty": "Журнал изменений отсутствует",
|
||||
"handlerGroups": {
|
||||
"page": "Pages",
|
||||
"skill": "Skills",
|
||||
@@ -212,6 +214,12 @@
|
||||
"downloadSource": "Пакет плагина будет установлен из:",
|
||||
"githubSecurityWarning": "AstrBot не может гарантировать безопасность плагинов, загруженных с GitHub. Устанавливайте только если доверяете источнику плагина."
|
||||
},
|
||||
"update": {
|
||||
"title": "Обновление плагина",
|
||||
"sectionTitle": "Обновление",
|
||||
"downloadSource": "Пакет обновления будет установлен из:",
|
||||
"confirm": "Подтвердить обновление"
|
||||
},
|
||||
"danger_warning": {
|
||||
"title": "Внимание!",
|
||||
"message": "Этот плагин может содержать небезопасный код или функции, которые могут привести к нестабильности системы или потере данных. Вы уверены, что хотите продолжить установку?",
|
||||
|
||||
@@ -104,6 +104,8 @@
|
||||
"notFound": "未找到该插件",
|
||||
"docsTitle": "文档",
|
||||
"docsEmpty": "暂无文档",
|
||||
"changelogTitle": "更新日志",
|
||||
"changelogEmpty": "暂无更新日志",
|
||||
"handlerGroups": {
|
||||
"page": "页面",
|
||||
"skill": "技能",
|
||||
@@ -213,6 +215,12 @@
|
||||
"downloadSource": "将从以下位置安装插件包体:",
|
||||
"githubSecurityWarning": "AstrBot 不能保证从 GitHub 上下载的插件的安全性。请确认你信任该插件来源后再安装。"
|
||||
},
|
||||
"update": {
|
||||
"title": "更新插件",
|
||||
"sectionTitle": "更新",
|
||||
"downloadSource": "将从以下位置安装更新包体:",
|
||||
"confirm": "确认更新"
|
||||
},
|
||||
"danger_warning": {
|
||||
"title": "警告",
|
||||
"message": "该插件可能包含不安全的代码或功能,可能导致系统异常或数据损失等。请确认是否继续安装?",
|
||||
|
||||
@@ -49,6 +49,7 @@ const {
|
||||
updatingAll,
|
||||
readmeDialog,
|
||||
forceUpdateDialog,
|
||||
updateConfirmDialog,
|
||||
updateAllConfirmDialog,
|
||||
changelogDialog,
|
||||
getInitialListViewMode,
|
||||
@@ -111,6 +112,8 @@ const {
|
||||
uninstallExtension,
|
||||
handleUninstallConfirm,
|
||||
updateExtension,
|
||||
closeUpdateConfirmDialog,
|
||||
confirmUpdatePlugin,
|
||||
showUpdateAllConfirm,
|
||||
confirmUpdateAll,
|
||||
cancelUpdateAll,
|
||||
@@ -152,6 +155,11 @@ const {
|
||||
selectedInstallDownloadUrl,
|
||||
selectedInstallSourceUrl,
|
||||
installUsesGithubSource,
|
||||
selectedUpdateExtension,
|
||||
selectedUpdateMarketPlugin,
|
||||
selectedUpdateDownloadUrl,
|
||||
selectedUpdateSourceUrl,
|
||||
updateUsesGithubSource,
|
||||
checkInstallCompatibility,
|
||||
refreshPluginMarket,
|
||||
handleLocaleChange,
|
||||
@@ -221,6 +229,28 @@ const installDialogPluginLogo = computed(() => {
|
||||
const logo = selectedInstallPlugin.value?.logo;
|
||||
return typeof logo === "string" && logo.trim() ? logo : defaultPluginIcon;
|
||||
});
|
||||
|
||||
const updateDialogPlugin = computed(
|
||||
() => selectedUpdateMarketPlugin.value || selectedUpdateExtension.value,
|
||||
);
|
||||
|
||||
const updateDialogPluginName = computed(() =>
|
||||
updateDialogPlugin.value ? pluginName(updateDialogPlugin.value) : "",
|
||||
);
|
||||
|
||||
const updateDialogCurrentVersion = computed(() =>
|
||||
String(selectedUpdateExtension.value?.version || "").trim(),
|
||||
);
|
||||
|
||||
const updateDialogTargetVersion = computed(() =>
|
||||
String(selectedUpdateMarketPlugin.value?.version || "").trim(),
|
||||
);
|
||||
|
||||
const updateDialogPluginLogo = computed(() => {
|
||||
const logo =
|
||||
selectedUpdateMarketPlugin.value?.logo || selectedUpdateExtension.value?.logo;
|
||||
return typeof logo === "string" && logo.trim() ? logo : defaultPluginIcon;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -1052,6 +1082,84 @@ const installDialogPluginLogo = computed(() => {
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Update plugin confirmation dialog -->
|
||||
<v-dialog v-model="updateConfirmDialog.show" width="500">
|
||||
<v-card class="rounded-lg">
|
||||
<v-card-title class="text-h3 pa-4 pb-0 pl-6">
|
||||
{{ tm("dialogs.update.title") }}
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<div class="market-install-confirm">
|
||||
<div class="market-install-confirm__header">
|
||||
<img
|
||||
:src="updateDialogPluginLogo"
|
||||
:alt="updateDialogPluginName"
|
||||
class="market-install-confirm__logo"
|
||||
/>
|
||||
<div class="market-install-confirm__meta">
|
||||
<div class="market-install-confirm__name">
|
||||
{{ updateDialogPluginName }}
|
||||
</div>
|
||||
<div
|
||||
v-if="updateDialogCurrentVersion || updateDialogTargetVersion"
|
||||
class="market-install-confirm__author"
|
||||
>
|
||||
<template v-if="updateDialogTargetVersion">
|
||||
{{ updateDialogCurrentVersion || tm("status.unknown") }}
|
||||
<v-icon icon="mdi-arrow-right" size="14" class="mx-1" />
|
||||
{{ updateDialogTargetVersion }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ tm("detail.info.version") }}:
|
||||
{{ updateDialogCurrentVersion || tm("status.unknown") }}
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<v-divider class="my-4" />
|
||||
|
||||
<div
|
||||
v-if="selectedUpdateSourceUrl"
|
||||
class="market-install-confirm__section-title"
|
||||
>
|
||||
{{ tm("dialogs.update.sectionTitle") }}
|
||||
</div>
|
||||
<div
|
||||
v-if="selectedUpdateSourceUrl"
|
||||
class="market-install-source text-caption text-medium-emphasis mb-3"
|
||||
>
|
||||
<div>{{ tm("dialogs.update.downloadSource") }}</div>
|
||||
<div class="market-install-source__url">
|
||||
{{ selectedUpdateSourceUrl }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<v-alert
|
||||
v-if="updateUsesGithubSource"
|
||||
type="warning"
|
||||
variant="tonal"
|
||||
density="comfortable"
|
||||
class="market-install-alert mt-4 mb-4"
|
||||
>
|
||||
{{ tm("dialogs.install.githubSecurityWarning") }}
|
||||
</v-alert>
|
||||
|
||||
<ProxySelector v-if="!selectedUpdateDownloadUrl" class="mt-4" />
|
||||
</div>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="grey" variant="text" @click="closeUpdateConfirmDialog">
|
||||
{{ tm("buttons.cancel") }}
|
||||
</v-btn>
|
||||
<v-btn color="primary" variant="flat" @click="confirmUpdatePlugin">
|
||||
{{ tm("dialogs.update.confirm") }}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- 强制更新确认对话框 -->
|
||||
<v-dialog v-model="forceUpdateDialog.show" max-width="420">
|
||||
<v-card class="rounded-lg">
|
||||
|
||||
@@ -84,6 +84,10 @@ const readmeLoading = ref(false);
|
||||
const readmeError = ref("");
|
||||
const readmeEmpty = ref(false);
|
||||
const renderedReadme = ref("");
|
||||
const changelogLoading = ref(false);
|
||||
const changelogError = ref("");
|
||||
const changelogEmpty = ref(false);
|
||||
const renderedChangelog = ref("");
|
||||
const expandedCommandGroups = ref(new Set());
|
||||
const logoLoadFailed = ref(false);
|
||||
const detailPageRef = ref(null);
|
||||
@@ -562,6 +566,20 @@ const fetchPluginDetail = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const getDocumentUrl = (fieldName) => {
|
||||
const plugin = pluginData.value || {};
|
||||
const marketPlugin = props.marketPlugin || {};
|
||||
return String(plugin[fieldName] || marketPlugin[fieldName] || "").trim();
|
||||
};
|
||||
|
||||
const fetchRemoteMarkdown = async (url) => {
|
||||
const res = await axios.get(url, {
|
||||
responseType: "text",
|
||||
transformResponse: [(data) => data],
|
||||
});
|
||||
return typeof res.data === "string" ? res.data : String(res.data || "");
|
||||
};
|
||||
|
||||
const fetchReadme = async () => {
|
||||
const plugin = pluginData.value || {};
|
||||
if (!plugin?.name) return;
|
||||
@@ -572,7 +590,25 @@ const fetchReadme = async () => {
|
||||
renderedReadme.value = "";
|
||||
|
||||
if (isMarketDetail.value) {
|
||||
readmeLoading.value = false;
|
||||
const readmeUrl = getDocumentUrl("readme_url");
|
||||
if (!readmeUrl) {
|
||||
readmeEmpty.value = true;
|
||||
readmeLoading.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const content = await fetchRemoteMarkdown(readmeUrl);
|
||||
if (!content.trim()) {
|
||||
readmeEmpty.value = true;
|
||||
return;
|
||||
}
|
||||
renderedReadme.value = renderMarkdown(content);
|
||||
} catch (err) {
|
||||
readmeError.value = err?.message || String(err);
|
||||
} finally {
|
||||
readmeLoading.value = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -617,14 +653,83 @@ const fetchReadme = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const showDocsSection = computed(() => !isMarketDetail.value);
|
||||
const fetchChangelog = async () => {
|
||||
const plugin = pluginData.value || {};
|
||||
if (!plugin?.name) return;
|
||||
|
||||
changelogLoading.value = true;
|
||||
changelogError.value = "";
|
||||
changelogEmpty.value = false;
|
||||
renderedChangelog.value = "";
|
||||
|
||||
if (isMarketDetail.value) {
|
||||
const changelogUrl = getDocumentUrl("changelog_url");
|
||||
if (!changelogUrl) {
|
||||
changelogEmpty.value = true;
|
||||
changelogLoading.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const content = await fetchRemoteMarkdown(changelogUrl);
|
||||
if (!content.trim()) {
|
||||
changelogEmpty.value = true;
|
||||
return;
|
||||
}
|
||||
renderedChangelog.value = renderMarkdown(content);
|
||||
} catch (err) {
|
||||
changelogError.value = err?.message || String(err);
|
||||
} finally {
|
||||
changelogLoading.value = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await axios.get("/api/plugin/changelog", {
|
||||
params: { name: plugin.name },
|
||||
});
|
||||
|
||||
if (res.data.status !== "ok") {
|
||||
changelogError.value =
|
||||
res.data.message || tm("messages.operationFailed");
|
||||
return;
|
||||
}
|
||||
|
||||
const content = res.data.data?.content || "";
|
||||
if (!content) {
|
||||
changelogEmpty.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
renderedChangelog.value = renderMarkdown(content);
|
||||
} catch (err) {
|
||||
changelogError.value = err?.message || String(err);
|
||||
} finally {
|
||||
changelogLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const showDocsSection = computed(
|
||||
() => !isMarketDetail.value || !!getDocumentUrl("readme_url"),
|
||||
);
|
||||
|
||||
const showChangelogSection = computed(
|
||||
() => !isMarketDetail.value || !!getDocumentUrl("changelog_url"),
|
||||
);
|
||||
|
||||
watch(
|
||||
() => [props.plugin?.name, props.sourceTab],
|
||||
() => [
|
||||
props.plugin?.name,
|
||||
props.sourceTab,
|
||||
props.marketPlugin?.readme_url,
|
||||
props.marketPlugin?.changelog_url,
|
||||
],
|
||||
async () => {
|
||||
logoLoadFailed.value = false;
|
||||
await fetchPluginDetail();
|
||||
fetchReadme();
|
||||
fetchChangelog();
|
||||
scrollToHashTarget();
|
||||
},
|
||||
{ immediate: true },
|
||||
@@ -889,6 +994,26 @@ onBeforeUnmount(() => {
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</section>
|
||||
|
||||
<section v-if="showChangelogSection" class="detail-section">
|
||||
<h3 class="detail-section__title">
|
||||
{{ tm("detail.changelogTitle") }}
|
||||
</h3>
|
||||
<v-card class="rounded-lg docs-card" variant="outlined">
|
||||
<v-card-text>
|
||||
<div v-if="changelogLoading" class="docs-state">
|
||||
<v-progress-circular indeterminate color="primary" />
|
||||
</div>
|
||||
<v-alert v-else-if="changelogError" type="error" variant="tonal">
|
||||
{{ changelogError }}
|
||||
</v-alert>
|
||||
<div v-else-if="changelogEmpty" class="text-medium-emphasis">
|
||||
{{ tm("detail.changelogEmpty") }}
|
||||
</div>
|
||||
<div v-else class="docs-markdown" v-html="renderedChangelog"></div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -117,6 +117,12 @@ export const useExtensionPage = () => {
|
||||
extensionName: "",
|
||||
});
|
||||
|
||||
const updateConfirmDialog = reactive({
|
||||
show: false,
|
||||
extensionName: "",
|
||||
forceUpdate: false,
|
||||
});
|
||||
|
||||
// 更新全部插件确认对话框
|
||||
const updateAllConfirmDialog = reactive({
|
||||
show: false,
|
||||
@@ -596,6 +602,38 @@ export const useExtensionPage = () => {
|
||||
uninstall({ kind: "failed", id: dirName }, { skipConfirm: false });
|
||||
};
|
||||
|
||||
const normalizeInstallUrl = (value) =>
|
||||
String(value || "")
|
||||
.trim()
|
||||
.replace(/\/+$/, "");
|
||||
|
||||
const isGithubRepoUrl = (value) =>
|
||||
/^https:\/\/github\.com\/[^/\s]+\/[^/\s]+(?:\.git)?(?:\/tree\/[^/\s]+)?$/i.test(
|
||||
normalizeInstallUrl(value),
|
||||
);
|
||||
|
||||
const getInstalledExtensionByName = (extensionName) => {
|
||||
const data = Array.isArray(extension_data?.data) ? extension_data.data : [];
|
||||
return data.find((extension) => extension.name === extensionName) || null;
|
||||
};
|
||||
|
||||
const findMarketPluginForExtension = (extension) => {
|
||||
if (!extension) return null;
|
||||
const repo = normalizeInstallUrl(extension.repo).toLowerCase();
|
||||
return (
|
||||
pluginMarketData.value.find(
|
||||
(plugin) =>
|
||||
repo &&
|
||||
normalizeInstallUrl(plugin?.repo).toLowerCase() === repo,
|
||||
) ||
|
||||
pluginMarketData.value.find((plugin) => plugin.name === extension.name) ||
|
||||
null
|
||||
);
|
||||
};
|
||||
|
||||
const getUpdateDownloadUrl = (extension) =>
|
||||
String(findMarketPluginForExtension(extension)?.download_url || "").trim();
|
||||
|
||||
const checkUpdate = () => {
|
||||
const onlinePluginsMap = new Map();
|
||||
const onlinePluginsNameMap = new Map();
|
||||
@@ -657,10 +695,20 @@ export const useExtensionPage = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const openUpdateConfirmDialog = (extensionName, forceUpdate = false) => {
|
||||
updateConfirmDialog.extensionName = extensionName;
|
||||
updateConfirmDialog.forceUpdate = forceUpdate;
|
||||
updateConfirmDialog.show = true;
|
||||
};
|
||||
|
||||
const closeUpdateConfirmDialog = () => {
|
||||
updateConfirmDialog.show = false;
|
||||
updateConfirmDialog.extensionName = "";
|
||||
updateConfirmDialog.forceUpdate = false;
|
||||
};
|
||||
|
||||
const updateExtension = async (extension_name, forceUpdate = false) => {
|
||||
// 查找插件信息
|
||||
const data = Array.isArray(extension_data?.data) ? extension_data.data : [];
|
||||
const ext = data.find((e) => e.name === extension_name);
|
||||
const ext = getInstalledExtensionByName(extension_name);
|
||||
|
||||
// 如果没有检测到更新且不是强制更新,则弹窗确认
|
||||
if (!ext?.has_update && !forceUpdate) {
|
||||
@@ -669,12 +717,28 @@ export const useExtensionPage = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
openUpdateConfirmDialog(extension_name, forceUpdate);
|
||||
};
|
||||
|
||||
const confirmUpdatePlugin = async () => {
|
||||
const extensionName = updateConfirmDialog.extensionName;
|
||||
const ext = getInstalledExtensionByName(extensionName);
|
||||
if (!extensionName || !ext) {
|
||||
closeUpdateConfirmDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
const downloadUrl = getUpdateDownloadUrl(ext);
|
||||
closeUpdateConfirmDialog();
|
||||
loadingDialog.title = tm("status.loading");
|
||||
loadingDialog.statusCode = 0;
|
||||
loadingDialog.result = "";
|
||||
loadingDialog.show = true;
|
||||
try {
|
||||
const res = await axios.post("/api/plugin/update", {
|
||||
name: extension_name,
|
||||
proxy: getSelectedGitHubProxy(),
|
||||
name: extensionName,
|
||||
download_url: downloadUrl,
|
||||
proxy: downloadUrl ? "" : getSelectedGitHubProxy(),
|
||||
});
|
||||
|
||||
if (res.data.status === "error") {
|
||||
@@ -692,7 +756,7 @@ export const useExtensionPage = () => {
|
||||
|
||||
// 更新完成后弹出更新日志
|
||||
viewChangelog({
|
||||
name: extension_name,
|
||||
name: extensionName,
|
||||
repo: ext?.repo || null,
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -731,7 +795,7 @@ export const useExtensionPage = () => {
|
||||
const name = forceUpdateDialog.extensionName;
|
||||
forceUpdateDialog.show = false;
|
||||
forceUpdateDialog.extensionName = "";
|
||||
updateExtension(name, true);
|
||||
openUpdateConfirmDialog(name, true);
|
||||
};
|
||||
|
||||
const updateAllExtensions = async () => {
|
||||
@@ -747,9 +811,15 @@ export const useExtensionPage = () => {
|
||||
loadingDialog.show = true;
|
||||
|
||||
const targets = updatableExtensions.value.map((ext) => ext.name);
|
||||
const downloadUrls = Object.fromEntries(
|
||||
updatableExtensions.value
|
||||
.map((ext) => [ext.name, getUpdateDownloadUrl(ext)])
|
||||
.filter(([, downloadUrl]) => downloadUrl),
|
||||
);
|
||||
try {
|
||||
const res = await axios.post("/api/plugin/update-all", {
|
||||
names: targets,
|
||||
download_urls: downloadUrls,
|
||||
proxy: getSelectedGitHubProxy(),
|
||||
});
|
||||
|
||||
@@ -921,16 +991,6 @@ export const useExtensionPage = () => {
|
||||
resetInstallDialogState();
|
||||
};
|
||||
|
||||
const normalizeInstallUrl = (value) =>
|
||||
String(value || "")
|
||||
.trim()
|
||||
.replace(/\/+$/, "");
|
||||
|
||||
const isGithubRepoUrl = (value) =>
|
||||
/^https:\/\/github\.com\/[^/\s]+\/[^/\s]+(?:\.git)?(?:\/tree\/[^/\s]+)?$/i.test(
|
||||
normalizeInstallUrl(value),
|
||||
);
|
||||
|
||||
const selectedInstallDownloadUrl = computed(() => {
|
||||
const plugin = selectedInstallPlugin.value;
|
||||
const downloadUrl = String(plugin?.download_url || "").trim();
|
||||
@@ -1482,6 +1542,30 @@ export const useExtensionPage = () => {
|
||||
|
||||
const selectedInstallPlugin = computed(() => resolveSelectedInstallPlugin());
|
||||
|
||||
const selectedUpdateExtension = computed(() =>
|
||||
getInstalledExtensionByName(updateConfirmDialog.extensionName),
|
||||
);
|
||||
|
||||
const selectedUpdateMarketPlugin = computed(() =>
|
||||
findMarketPluginForExtension(selectedUpdateExtension.value),
|
||||
);
|
||||
|
||||
const selectedUpdateDownloadUrl = computed(() =>
|
||||
String(selectedUpdateMarketPlugin.value?.download_url || "").trim(),
|
||||
);
|
||||
|
||||
const selectedUpdateSourceUrl = computed(
|
||||
() =>
|
||||
selectedUpdateDownloadUrl.value ||
|
||||
String(selectedUpdateExtension.value?.repo || "").trim(),
|
||||
);
|
||||
|
||||
const updateUsesGithubSource = computed(
|
||||
() =>
|
||||
!selectedUpdateDownloadUrl.value &&
|
||||
isGithubRepoUrl(selectedUpdateSourceUrl.value),
|
||||
);
|
||||
|
||||
const checkInstallCompatibility = async () => {
|
||||
installCompat.checked = false;
|
||||
installCompat.compatible = true;
|
||||
@@ -1677,6 +1761,7 @@ export const useExtensionPage = () => {
|
||||
updatingAll,
|
||||
readmeDialog,
|
||||
forceUpdateDialog,
|
||||
updateConfirmDialog,
|
||||
updateAllConfirmDialog,
|
||||
changelogDialog,
|
||||
pluginSearch,
|
||||
@@ -1742,6 +1827,8 @@ export const useExtensionPage = () => {
|
||||
requestUninstallFailedPlugin,
|
||||
handleUninstallConfirm,
|
||||
updateExtension,
|
||||
closeUpdateConfirmDialog,
|
||||
confirmUpdatePlugin,
|
||||
showUpdateAllConfirm,
|
||||
confirmUpdateAll,
|
||||
cancelUpdateAll,
|
||||
@@ -1783,6 +1870,11 @@ export const useExtensionPage = () => {
|
||||
selectedInstallDownloadUrl,
|
||||
selectedInstallSourceUrl,
|
||||
installUsesGithubSource,
|
||||
selectedUpdateExtension,
|
||||
selectedUpdateMarketPlugin,
|
||||
selectedUpdateDownloadUrl,
|
||||
selectedUpdateSourceUrl,
|
||||
updateUsesGithubSource,
|
||||
checkInstallCompatibility,
|
||||
refreshPluginMarket,
|
||||
handleLocaleChange,
|
||||
|
||||
Reference in New Issue
Block a user