From b97081a87009965e3ac980c6343186b3cbdfc377 Mon Sep 17 00:00:00 2001 From: Megghy Date: Tue, 22 Apr 2025 19:08:31 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=9C=A8=20ClientAutoAction=20?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E4=B8=AD=E6=96=B0=E5=A2=9E=E6=93=8D=E4=BD=9C?= =?UTF-8?q?=E5=8E=86=E5=8F=B2=E6=A0=87=E7=AD=BE=E9=A1=B5=E5=92=8C=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 引入 ActionHistoryViewer 组件,展示执行历史。 - 更新主标签页逻辑,调整为操作管理和执行历史两个标签。 - 在自动操作逻辑中增加弹幕和私信发送历史记录功能,提升操作追踪能力。 --- src/client/ClientAutoAction.vue | 354 ++++++++++-------- .../autoaction/ActionHistoryViewer.vue | 278 ++++++++++++++ src/client/store/autoAction/actionUtils.ts | 88 ++++- .../store/autoAction/utils/historyLogger.ts | 145 +++++++ 4 files changed, 695 insertions(+), 170 deletions(-) create mode 100644 src/client/components/autoaction/ActionHistoryViewer.vue create mode 100644 src/client/store/autoAction/utils/historyLogger.ts diff --git a/src/client/ClientAutoAction.vue b/src/client/ClientAutoAction.vue index f197411..57d6a31 100644 --- a/src/client/ClientAutoAction.vue +++ b/src/client/ClientAutoAction.vue @@ -32,6 +32,7 @@ import AutoActionEditor from './components/autoaction/AutoActionEditor.vue'; import GlobalScheduledSettings from './components/autoaction/settings/GlobalScheduledSettings.vue'; import TimerCountdown from './components/autoaction/TimerCountdown.vue'; import DataManager from './components/autoaction/DataManager.vue'; +import ActionHistoryViewer from './components/autoaction/ActionHistoryViewer.vue'; const autoActionStore = useAutoAction(); const message = useMessage(); @@ -54,6 +55,7 @@ const typeMap = { }; const activeTab = ref(TriggerType.GIFT); +const activeMainTab = ref('action-management'); const showAddModal = ref(false); const selectedTriggerType = ref(TriggerType.GIFT); const editingActionId = ref(null); @@ -499,194 +501,214 @@ onUnmounted(() => { size="large" > + - - - - {{ enabledTriggerTypes[type] ? '启用' : '禁用' }}所有{{ label }} - - - - 当前连接模式 (OpenLive) 无法获取用户UID,因此无法执行【发送私信】操作。如需使用私信功能,请考虑切换至直连模式。 - - - - - - {{ `确认模拟一个 ${label} 事件来测试所有启用的 ${label} 操作吗?\n注意:这可能会发送真实的消息、执行操作,并可能触发B站风控限制。` }} - - - -
- -
- - - - - 下一个执行: - - {{ autoActionStore.nextScheduledAction?.name || '未命名操作' }} - - - - - 手动设置下一个要执行的操作 - - -
- - 定时发送类型已被禁用,所有相关操作不会执行。 - -
- - - - -
- - - - - - + 添加{{ typeMap[type as TriggerType] }} - -
- -
+ - - ← 返回列表 - + + {{ enabledTriggerTypes[type] ? '启用' : '禁用' }}所有{{ label }} + - -
+ + + - + + {{ `确认模拟一个 ${label} 事件来测试所有启用的 ${label} 操作吗?\n注意:这可能会发送真实的消息、执行操作,并可能触发B站风控限制。` }} + + + +
+ +
+ + + + + 下一个执行: + + {{ autoActionStore.nextScheduledAction?.name || '未命名操作' }} + + + + + 手动设置下一个要执行的操作 + +
- + + 定时发送类型已被禁用,所有相关操作不会执行。 + +
+ + + + +
+ + + + + + + 添加{{ typeMap[type as TriggerType] }} + +
+ +
+ + + ← 返回列表 + + + +
+ +
+
+
+
-
- +
+ - + + + + + + - diff --git a/src/client/components/autoaction/ActionHistoryViewer.vue b/src/client/components/autoaction/ActionHistoryViewer.vue new file mode 100644 index 0000000..3c6d95e --- /dev/null +++ b/src/client/components/autoaction/ActionHistoryViewer.vue @@ -0,0 +1,278 @@ + + + + + \ No newline at end of file diff --git a/src/client/store/autoAction/actionUtils.ts b/src/client/store/autoAction/actionUtils.ts index 5330d3b..9e51ae4 100644 --- a/src/client/store/autoAction/actionUtils.ts +++ b/src/client/store/autoAction/actionUtils.ts @@ -11,6 +11,7 @@ import { buildExecutionContext, getRandomTemplate } from './utils'; import { evaluateTemplateExpressions } from './expressionEvaluator'; import { evaluateExpression } from './utils'; import { useBiliCookie } from '../useBiliCookie'; +import { logDanmakuHistory, logPrivateMsgHistory, logCommandHistory } from './utils/historyLogger'; /** * 过滤有效的自动操作项 @@ -223,11 +224,57 @@ export function executeActions( if (action.actionConfig.delaySeconds && action.actionConfig.delaySeconds > 0) { setTimeout(() => { handlers.sendLiveDanmaku!(roomId, message) - .catch(err => console.error(`[AutoAction] 发送弹幕失败 (${action.name || action.id}):`, err)); + .then(success => { + // 记录弹幕发送历史 + logDanmakuHistory( + action.id, + action.name || '未命名操作', + message, + roomId, + success, + success ? undefined : '发送失败' + ).catch(err => console.error('记录弹幕历史失败:', err)); + return success; + }) + .catch(err => { + console.error(`[AutoAction] 发送弹幕失败 (${action.name || action.id}):`, err); + // 记录失败的发送 + logDanmakuHistory( + action.id, + action.name || '未命名操作', + message, + roomId, + false, + err.toString() + ).catch(e => console.error('记录弹幕历史失败:', e)); + }); }, action.actionConfig.delaySeconds * 1000); } else { handlers.sendLiveDanmaku(roomId, message) - .catch(err => console.error(`[AutoAction] 发送弹幕失败 (${action.name || action.id}):`, err)); + .then(success => { + // 记录弹幕发送历史 + logDanmakuHistory( + action.id, + action.name || '未命名操作', + message, + roomId, + success, + success ? undefined : '发送失败' + ).catch(err => console.error('记录弹幕历史失败:', err)); + return success; + }) + .catch(err => { + console.error(`[AutoAction] 发送弹幕失败 (${action.name || action.id}):`, err); + // 记录失败的发送 + logDanmakuHistory( + action.id, + action.name || '未命名操作', + message, + roomId, + false, + err.toString() + ).catch(e => console.error('记录弹幕历史失败:', e)); + }); } } } else { @@ -249,6 +296,16 @@ export function executeActions( const sendPmPromise = (uid: number, msg: string) => { return handlers.sendPrivateMessage!(uid, msg) .then(success => { + // 记录私信发送历史 + logPrivateMsgHistory( + action.id, + action.name || '未命名操作', + msg, + uid, + success, + success ? undefined : '发送失败' + ).catch(err => console.error('记录私信历史失败:', err)); + if (success && options?.onSuccess) { // 发送成功后调用 onSuccess 回调 options.onSuccess(action, context); @@ -257,6 +314,15 @@ export function executeActions( }) .catch(err => { console.error(`[AutoAction] 发送私信失败 (${action.name || action.id}):`, err); + // 记录失败的发送 + logPrivateMsgHistory( + action.id, + action.name || '未命名操作', + msg, + uid, + false, + err.toString() + ).catch(e => console.error('记录私信历史失败:', e)); return false; // 明确返回 false 表示失败 }); }; @@ -276,8 +342,22 @@ export function executeActions( break; case ActionType.EXECUTE_COMMAND: - // 执行自定义命令(未实现) - console.warn(`[AutoAction] 暂不支持执行自定义命令: ${action.name || action.id}`); + // 执行自定义命令 + const command = processTemplate(action, context); + if (command) { + // 更新冷却时间 + runtimeState.lastExecutionTime[action.id] = Date.now(); + + // 目前只记录执行历史,具体实现可在未来扩展 + logCommandHistory( + action.id, + action.name || '未命名操作', + command, + true + ).catch(err => console.error('记录命令执行历史失败:', err)); + + console.warn(`[AutoAction] 暂不支持执行自定义命令: ${action.name || action.id}`); + } break; default: diff --git a/src/client/store/autoAction/utils/historyLogger.ts b/src/client/store/autoAction/utils/historyLogger.ts new file mode 100644 index 0000000..756bb9c --- /dev/null +++ b/src/client/store/autoAction/utils/historyLogger.ts @@ -0,0 +1,145 @@ +import { get, set, del, update } from 'idb-keyval'; +import { ActionType } from '../types'; + +// 历史记录类型常量 +export enum HistoryType { + DANMAKU = 'danmaku', + PRIVATE_MSG = 'privateMsg', + COMMAND = 'command', +} + +// 历史记录项结构 +export interface HistoryItem { + id: string; // 唯一ID + actionId: string; // 操作ID + actionName: string; // 操作名称 + actionType: ActionType; // 操作类型 + timestamp: number; // 执行时间戳 + content: string; // 发送的内容 + target?: string; // 目标(如UID或房间ID) + success: boolean; // 是否成功 + error?: string; // 错误信息(如果有) +} + +// 每种类型的历史记录容量 +const HISTORY_CAPACITY = 1000; + +// 使用IDB存储的键名 +const HISTORY_KEYS = { + [HistoryType.DANMAKU]: 'autoAction_history_danmaku', + [HistoryType.PRIVATE_MSG]: 'autoAction_history_privateMsg', + [HistoryType.COMMAND]: 'autoAction_history_command', +}; + +// 环形队列添加记录 +async function addToCircularQueue(key: string, item: HistoryItem, capacity: number): Promise { + await update(key, (history = []) => { + // 添加到队列末尾 + history.push(item); + + // 如果超出容量,移除最旧的记录 + if (history.length > capacity) { + history.splice(0, history.length - capacity); + } + return history; + }); +} + +/** + * 记录弹幕发送历史 + */ +export async function logDanmakuHistory( + actionId: string, + actionName: string, + content: string, + roomId: number, + success: boolean, + error?: string +): Promise { + const historyItem: HistoryItem = { + id: `d_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`, + actionId, + actionName, + actionType: ActionType.SEND_DANMAKU, + timestamp: Date.now(), + content, + target: roomId.toString(), + success, + error + }; + + await addToCircularQueue(HISTORY_KEYS[HistoryType.DANMAKU], historyItem, HISTORY_CAPACITY); +} + +/** + * 记录私信发送历史 + */ +export async function logPrivateMsgHistory( + actionId: string, + actionName: string, + content: string, + userId: number, + success: boolean, + error?: string +): Promise { + const historyItem: HistoryItem = { + id: `p_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`, + actionId, + actionName, + actionType: ActionType.SEND_PRIVATE_MSG, + timestamp: Date.now(), + content, + target: userId.toString(), + success, + error + }; + + await addToCircularQueue(HISTORY_KEYS[HistoryType.PRIVATE_MSG], historyItem, HISTORY_CAPACITY); +} + +/** + * 记录命令执行历史 + */ +export async function logCommandHistory( + actionId: string, + actionName: string, + content: string, + success: boolean, + error?: string +): Promise { + const historyItem: HistoryItem = { + id: `c_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`, + actionId, + actionName, + actionType: ActionType.EXECUTE_COMMAND, + timestamp: Date.now(), + content, + success, + error + }; + + await addToCircularQueue(HISTORY_KEYS[HistoryType.COMMAND], historyItem, HISTORY_CAPACITY); +} + +/** + * 获取历史记录 + */ +export async function getHistoryByType(type: HistoryType): Promise { + return await get(HISTORY_KEYS[type]) || []; +} + +/** + * 清除历史记录 + */ +export async function clearHistory(type: HistoryType): Promise { + await del(HISTORY_KEYS[type]); +} + +/** + * 清除所有历史记录 + */ +export async function clearAllHistory(): Promise { + await Promise.all( + Object.values(HistoryType).map(type => clearHistory(type as HistoryType)) + ); +} \ No newline at end of file