Skip to content

Commit 372663f

Browse files
LIghtJUNctionCopilotCopilotSoulterDt8333
authored
同步主线 (#3298)
* Chore: Dockerfile (#3266) * fix: Dockerfile python main.py 改为uv run main.py * fix(dockerfile): 减少重复安装 * fix: 修复一些细节问题 * fix(.dockerignore): 需要git文件夹以获取astrbot版本(带git commit hash后缀) * fix(.dockerignore): uv run之前会uv sync * Replace insecure random with secrets module in cryptographic contexts (#3248) * Initial plan * Security fixes: Replace insecure random with secrets module and improve SSL context Co-authored-by: LIghtJUNction <[email protected]> * Address code review feedback: fix POST method and add named constants Co-authored-by: LIghtJUNction <[email protected]> * Improve documentation for random number generation constants Co-authored-by: LIghtJUNction <[email protected]> * Update astrbot/core/utils/io.py Co-authored-by: Copilot <[email protected]> * Update astrbot/core/platform/sources/wecom_ai_bot/WXBizJsonMsgCrypt.py Co-authored-by: Copilot <[email protected]> * Update tests/test_security_fixes.py Co-authored-by: Copilot <[email protected]> * Update astrbot/core/utils/io.py Co-authored-by: Copilot <[email protected]> * Update astrbot/core/utils/io.py Co-authored-by: Copilot <[email protected]> * Fix: Handle path parameter in SSL fallback for download_image_by_url Co-authored-by: LIghtJUNction <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: LIghtJUNction <[email protected]> Co-authored-by: LIghtJUNction <[email protected]> Co-authored-by: Copilot <[email protected]> * chore: nodejs in Dockerfile * fix: typing error (#3267) * fix: 修复一些小错误。 修复aiocqhttp和slack中部分逻辑缺失的await。修复discord中错误的异常捕获类型。 * fix(core.platform): 修复discord适配器中错误的message_chain赋值 * fix(aiocqhttp): 更新convert_message方法的返回类型为AstrBotMessage | None --------- Co-authored-by: Soulter <[email protected]> * feat: support options to delete plugins config and data (#3280) * - 为插件管理页面中,删除插件提供一致的二次确认(原本只有卡片视图有二次确认) - 二次确认时可选删除插件配置和持久化数据 - 添加对应的i18n支持 * ruff * 移除未使用的 const $confirm = inject('$confirm'); --------- Co-authored-by: Copilot <[email protected]> Co-authored-by: Copilot <[email protected]> Co-authored-by: Soulter <[email protected]> Co-authored-by: Dt8333 <[email protected]> Co-authored-by: Soulter <[email protected]> Co-authored-by: Misaka Mikoto <[email protected]>
1 parent a4967b3 commit 372663f

File tree

13 files changed

+277
-28
lines changed

13 files changed

+277
-28
lines changed

astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ async def send_by_session(
107107
)
108108
await super().send_by_session(session, message_chain)
109109

110-
async def convert_message(self, event: Event) -> AstrBotMessage:
110+
async def convert_message(self, event: Event) -> AstrBotMessage | None:
111111
logger.debug(f"[aiocqhttp] RawMessage {event}")
112112

113113
if event["post_type"] == "message":
@@ -222,7 +222,7 @@ async def _convert_handle_message_event(
222222
err = f"aiocqhttp: 无法识别的消息类型: {event.message!s},此条消息将被忽略。如果您在使用 go-cqhttp,请将其配置文件中的 message.post-format 更改为 array。"
223223
logger.critical(err)
224224
try:
225-
self.bot.send(event, err)
225+
await self.bot.send(event, err)
226226
except BaseException as e:
227227
logger.error(f"回复消息失败: {e}")
228228
return None

astrbot/core/platform/sources/discord/discord_platform_adapter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ async def send_by_session(
9090
)
9191
message_obj.self_id = self.client_self_id
9292
message_obj.session_id = session.session_id
93-
message_obj.message = message_chain
93+
message_obj.message = message_chain.chain
9494

9595
# 创建临时事件对象来发送消息
9696
temp_event = DiscordPlatformEvent(

astrbot/core/platform/sources/discord/discord_platform_event.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import asyncio
22
import base64
3+
import binascii
34
import sys
45
from io import BytesIO
56
from pathlib import Path
@@ -183,7 +184,7 @@ async def _parse_to_discord(
183184
BytesIO(img_bytes),
184185
filename=filename or "image.png",
185186
)
186-
except (ValueError, TypeError, base64.binascii.Error):
187+
except (ValueError, TypeError, binascii.Error):
187188
logger.debug(
188189
f"[Discord] 裸 Base64 解码失败,作为本地路径处理: {file_content}",
189190
)

astrbot/core/platform/sources/slack/slack_adapter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ async def send_by_session(
8282
session: MessageSesion,
8383
message_chain: MessageChain,
8484
):
85-
blocks, text = SlackMessageEvent._parse_slack_blocks(
85+
blocks, text = await SlackMessageEvent._parse_slack_blocks(
8686
message_chain=message_chain,
8787
web_client=self.web_client,
8888
)

astrbot/core/star/star_manager.py

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -680,11 +680,18 @@ async def install_plugin(self, repo_url: str, proxy=""):
680680

681681
return plugin_info
682682

683-
async def uninstall_plugin(self, plugin_name: str):
683+
async def uninstall_plugin(
684+
self,
685+
plugin_name: str,
686+
delete_config: bool = False,
687+
delete_data: bool = False,
688+
):
684689
"""卸载指定的插件。
685690
686691
Args:
687692
plugin_name (str): 要卸载的插件名称
693+
delete_config (bool): 是否删除插件配置文件,默认为 False
694+
delete_data (bool): 是否删除插件数据,默认为 False
688695
689696
Raises:
690697
Exception: 当插件不存在、是保留插件时,或删除插件文件夹失败时抛出异常
@@ -714,13 +721,59 @@ async def uninstall_plugin(self, plugin_name: str):
714721

715722
await self._unbind_plugin(plugin_name, plugin.module_path)
716723

724+
# 删除插件文件夹
717725
try:
718726
remove_dir(os.path.join(ppath, root_dir_name))
719727
except Exception as e:
720728
raise Exception(
721729
f"移除插件成功,但是删除插件文件夹失败: {e!s}。您可以手动删除该文件夹,位于 addons/plugins/ 下。",
722730
)
723731

732+
# 删除插件配置文件
733+
if delete_config and root_dir_name:
734+
config_file = os.path.join(
735+
self.plugin_config_path,
736+
f"{root_dir_name}_config.json",
737+
)
738+
if os.path.exists(config_file):
739+
try:
740+
os.remove(config_file)
741+
logger.info(f"已删除插件 {plugin_name} 的配置文件")
742+
except Exception as e:
743+
logger.warning(f"删除插件配置文件失败: {e!s}")
744+
745+
# 删除插件持久化数据
746+
# 注意:需要检查两个可能的目录名(plugin_data 和 plugins_data)
747+
# data/temp 目录可能被多个插件共享,不自动删除以防误删
748+
if delete_data and root_dir_name:
749+
data_base_dir = os.path.dirname(ppath) # data/
750+
751+
# 删除 data/plugin_data 下的插件持久化数据(单数形式,新版本)
752+
plugin_data_dir = os.path.join(
753+
data_base_dir, "plugin_data", root_dir_name
754+
)
755+
if os.path.exists(plugin_data_dir):
756+
try:
757+
remove_dir(plugin_data_dir)
758+
logger.info(
759+
f"已删除插件 {plugin_name} 的持久化数据 (plugin_data)"
760+
)
761+
except Exception as e:
762+
logger.warning(f"删除插件持久化数据失败 (plugin_data): {e!s}")
763+
764+
# 删除 data/plugins_data 下的插件持久化数据(复数形式,旧版本兼容)
765+
plugins_data_dir = os.path.join(
766+
data_base_dir, "plugins_data", root_dir_name
767+
)
768+
if os.path.exists(plugins_data_dir):
769+
try:
770+
remove_dir(plugins_data_dir)
771+
logger.info(
772+
f"已删除插件 {plugin_name} 的持久化数据 (plugins_data)"
773+
)
774+
except Exception as e:
775+
logger.warning(f"删除插件持久化数据失败 (plugins_data): {e!s}")
776+
724777
async def _unbind_plugin(self, plugin_name: str, plugin_module_path: str):
725778
"""解绑并移除一个插件。
726779

astrbot/dashboard/routes/plugin.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,9 +395,15 @@ async def uninstall_plugin(self):
395395

396396
post_data = await request.json
397397
plugin_name = post_data["name"]
398+
delete_config = post_data.get("delete_config", False)
399+
delete_data = post_data.get("delete_data", False)
398400
try:
399401
logger.info(f"正在卸载插件 {plugin_name}")
400-
await self.plugin_manager.uninstall_plugin(plugin_name)
402+
await self.plugin_manager.uninstall_plugin(
403+
plugin_name,
404+
delete_config=delete_config,
405+
delete_data=delete_data,
406+
)
401407
logger.info(f"卸载插件 {plugin_name} 成功")
402408
return Response().ok(None, "卸载成功").__dict__
403409
except Exception as e:

dashboard/src/components/shared/ExtensionCard.vue

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { ref, computed, inject } from 'vue';
33
import { useCustomizerStore } from "@/stores/customizer";
44
import { useModuleI18n } from '@/i18n/composables';
5+
import UninstallConfirmDialog from './UninstallConfirmDialog.vue';
56
67
const props = defineProps({
78
extension: {
@@ -31,6 +32,7 @@ const emit = defineEmits([
3132
]);
3233
3334
const reveal = ref(false);
35+
const showUninstallDialog = ref(false);
3436
3537
// 国际化
3638
const { tm } = useModuleI18n('features/extension');
@@ -55,19 +57,11 @@ const installExtension = async () => {
5557
};
5658
5759
const uninstallExtension = async () => {
58-
if (typeof $confirm !== "function") {
59-
console.error(tm("card.errors.confirmNotRegistered"));
60-
return;
61-
}
62-
63-
const confirmed = await $confirm({
64-
title: tm("dialogs.uninstall.title"),
65-
message: tm("dialogs.uninstall.message"),
66-
});
60+
showUninstallDialog.value = true;
61+
};
6762
68-
if (confirmed) {
69-
emit("uninstall", props.extension);
70-
}
63+
const handleUninstallConfirm = (options: { deleteConfig: boolean; deleteData: boolean }) => {
64+
emit("uninstall", props.extension, options);
7165
};
7266
7367
const toggleActivation = () => {
@@ -220,6 +214,12 @@ const viewReadme = () => {
220214
</v-card-actions>
221215
</v-card>
222216

217+
<!-- 卸载确认对话框 -->
218+
<UninstallConfirmDialog
219+
v-model="showUninstallDialog"
220+
@confirm="handleUninstallConfirm"
221+
/>
222+
223223
</template>
224224

225225
<style scoped>
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
<template>
2+
<v-dialog
3+
v-model="show"
4+
max-width="500"
5+
@click:outside="handleCancel"
6+
@keydown.esc="handleCancel"
7+
>
8+
<v-card>
9+
<v-card-title class="text-h5">
10+
{{ tm('dialogs.uninstall.title') }}
11+
</v-card-title>
12+
13+
<v-card-text>
14+
<div class="mb-4">
15+
{{ tm('dialogs.uninstall.message') }}
16+
</div>
17+
18+
<v-divider class="my-4"></v-divider>
19+
20+
<div class="text-subtitle-2 mb-3">{{ t('core.common.actions') }}:</div>
21+
22+
<v-checkbox
23+
v-model="deleteConfig"
24+
:label="tm('dialogs.uninstall.deleteConfig')"
25+
color="warning"
26+
hide-details
27+
class="mb-2"
28+
>
29+
<template v-slot:append>
30+
<v-tooltip location="top">
31+
<template v-slot:activator="{ props }">
32+
<v-icon v-bind="props" size="small" color="grey">mdi-information-outline</v-icon>
33+
</template>
34+
<span>{{ tm('dialogs.uninstall.configHint') }}</span>
35+
</v-tooltip>
36+
</template>
37+
</v-checkbox>
38+
39+
<v-checkbox
40+
v-model="deleteData"
41+
:label="tm('dialogs.uninstall.deleteData')"
42+
color="error"
43+
hide-details
44+
>
45+
<template v-slot:append>
46+
<v-tooltip location="top">
47+
<template v-slot:activator="{ props }">
48+
<v-icon v-bind="props" size="small" color="grey">mdi-information-outline</v-icon>
49+
</template>
50+
<span>{{ tm('dialogs.uninstall.dataHint') }}</span>
51+
</v-tooltip>
52+
</template>
53+
</v-checkbox>
54+
55+
<v-alert
56+
v-if="deleteConfig || deleteData"
57+
type="warning"
58+
variant="tonal"
59+
density="compact"
60+
class="mt-4"
61+
>
62+
<template v-slot:prepend>
63+
<v-icon>mdi-alert</v-icon>
64+
</template>
65+
{{ t('messages.validation.operation_cannot_be_undone') }}
66+
</v-alert>
67+
</v-card-text>
68+
69+
<v-card-actions>
70+
<v-spacer></v-spacer>
71+
<v-btn
72+
color="grey"
73+
variant="text"
74+
@click="handleCancel"
75+
>
76+
{{ t('core.common.cancel') }}
77+
</v-btn>
78+
<v-btn
79+
color="error"
80+
variant="elevated"
81+
@click="handleConfirm"
82+
>
83+
{{ t('core.common.confirm') }}
84+
</v-btn>
85+
</v-card-actions>
86+
</v-card>
87+
</v-dialog>
88+
</template>
89+
90+
<script setup lang="ts">
91+
import { ref, watch } from 'vue';
92+
import { useI18n, useModuleI18n } from '@/i18n/composables';
93+
94+
const props = defineProps({
95+
modelValue: {
96+
type: Boolean,
97+
default: false,
98+
},
99+
});
100+
101+
const emit = defineEmits(['update:modelValue', 'confirm', 'cancel']);
102+
103+
const { t } = useI18n();
104+
const { tm } = useModuleI18n('features/extension');
105+
106+
const show = ref(props.modelValue);
107+
const deleteConfig = ref(false);
108+
const deleteData = ref(false);
109+
110+
watch(() => props.modelValue, (val) => {
111+
show.value = val;
112+
if (val) {
113+
// 重置选项
114+
deleteConfig.value = false;
115+
deleteData.value = false;
116+
}
117+
});
118+
119+
watch(show, (val) => {
120+
emit('update:modelValue', val);
121+
});
122+
123+
const handleConfirm = () => {
124+
emit('confirm', {
125+
deleteConfig: deleteConfig.value,
126+
deleteData: deleteData.value,
127+
});
128+
show.value = false;
129+
};
130+
131+
const handleCancel = () => {
132+
emit('cancel');
133+
show.value = false;
134+
};
135+
</script>

dashboard/src/i18n/locales/en-US/features/extension.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,11 @@
9797
},
9898
"uninstall": {
9999
"title": "Confirm Deletion",
100-
"message": "Are you sure you want to delete this extension?"
100+
"message": "Are you sure you want to delete this extension?",
101+
"deleteConfig": "Also delete plugin configuration file",
102+
"deleteData": "Also delete plugin persistent data",
103+
"configHint": "Configuration file located in data/config directory",
104+
"dataHint": "Deletes data in data/plugin_data and data/plugins_data"
101105
},
102106
"install": {
103107
"title": "Install Extension",

dashboard/src/i18n/locales/en-US/messages/validation.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@
2020
"invalid_date": "Please enter a valid date",
2121
"date_range": "Invalid date range",
2222
"upload_failed": "File upload failed",
23-
"network_error": "Network connection error, please try again"
23+
"network_error": "Network connection error, please try again",
24+
"operation_cannot_be_undone": "⚠️ This operation cannot be undone, please choose carefully!"
2425
}

0 commit comments

Comments
 (0)