feat: 更新项目配置和组件,增强功能和用户体验

- 在 .gitignore 中添加了 .specstory 文件的忽略规则。
- 更新 tsconfig.json,修正了 vue-vine/types/macros 的引用路径。
- 在组件声明中新增了 NInput 组件的类型支持。
- 优化了 EventModel 接口,调整了 guard_level 的类型为 GuardLevel。
- 增加了 Follow 事件类型到 EventDataTypes 枚举中。
- 在 ClientAutoAction.vue 中引入了新的 store 和组件,增强了功能。
- 更新了多个设置组件,添加了关键词匹配类型和过滤模式的支持。
- 改进了模板编辑器和测试器的功能,支持更灵活的模板管理。
- 在弹幕客户端中新增了关注事件的处理逻辑,提升了事件响应能力。
This commit is contained in:
2025-04-22 02:30:09 +08:00
parent 2fc8f7fcf8
commit 77cf0c5edc
39 changed files with 3955 additions and 1959 deletions

View File

@@ -0,0 +1,287 @@
import { Ref } from 'vue';
import { EventModel } from '@/api/api-models';
import {
AutoActionItem,
TriggerType,
RuntimeState,
ActionType,
ExecutionContext
} from './types';
import { buildExecutionContext, getRandomTemplate } from './utils';
import { evaluateTemplateExpressions } from './expressionEvaluator';
import { evaluateExpression } from './utils';
import { useBiliCookie } from '../useBiliCookie';
/**
* 过滤有效的自动操作项
* @param actions 所有操作项列表
* @param triggerType 触发类型
* @param isLive 是否直播中
* @param isTianXuanActive 是否天选时刻激活
* @param options 额外过滤选项
* @returns 过滤后的操作项
*/
export function filterValidActions(
actions: AutoActionItem[],
triggerType: TriggerType,
isLive: Ref<boolean>,
isTianXuanActive?: Ref<boolean>,
options?: {
actionType?: ActionType; // 特定操作类型
customFilter?: (action: AutoActionItem) => boolean; // 自定义过滤器
}
): AutoActionItem[] {
return actions.filter(action => {
// 基本过滤条件
if (action.triggerType !== triggerType || !action.enabled) {
return false;
}
// 直播状态过滤
if (action.triggerConfig.onlyDuringLive && !isLive.value) {
return false;
}
// 天选时刻过滤
if (isTianXuanActive && action.triggerConfig.ignoreTianXuan && isTianXuanActive.value) {
return false;
}
// 操作类型过滤
if (options?.actionType && action.actionType !== options.actionType) {
return false;
}
// 自定义过滤器
if (options?.customFilter && !options.customFilter(action)) {
return false;
}
return true;
});
}
/**
* 检查用户是否满足过滤条件
* @param action 操作项
* @param event 事件数据
* @returns 是否满足条件
*/
export function checkUserFilters(action: AutoActionItem, event: EventModel): boolean {
if (!action.triggerConfig.userFilterEnabled) {
return true;
}
if (action.triggerConfig.requireMedal && !event.fans_medal_wearing_status) {
return false;
}
if (action.triggerConfig.requireCaptain && !event.guard_level) {
return false;
}
return true;
}
/**
* 检查冷却时间
* @param action 操作项
* @param runtimeState 运行时状态
* @returns 是否可以执行(已过冷却期)
*/
export function checkCooldown(action: AutoActionItem, runtimeState: RuntimeState): boolean {
if (action.ignoreCooldown) {
return true;
}
const now = Date.now();
const lastExecTime = runtimeState.lastExecutionTime[action.id] || 0;
const cooldownMs = (action.actionConfig.cooldownSeconds || 0) * 1000;
return now - lastExecTime >= cooldownMs;
}
/**
* 处理模板并返回格式化后的内容
* @param action 操作项
* @param context 执行上下文
* @param options 可选配置
* @returns 格式化后的内容如果没有有效模板则返回null
*/
export function processTemplate(
action: AutoActionItem,
context: any,
options?: {
useRandomTemplate?: boolean; // 是否随机选择模板默认true
defaultValue?: string; // 如果模板为空或格式化失败时的默认值
}
): string | null {
if (!action.template || action.template.trim() === '') {
console.warn(`跳过操作 "${action.name || '未命名'}":未设置有效模板`);
return options?.defaultValue || null;
}
try {
// 获取模板内容
let template: string;
if (options?.useRandomTemplate !== false) {
// 使用随机模板 (默认行为)
const randomTemplate = getRandomTemplate(action.template);
if (!randomTemplate) {
return options?.defaultValue || null;
}
template = randomTemplate;
} else {
// 使用整个模板字符串
template = action.template;
}
// 格式化模板
const formattedContent = evaluateTemplateExpressions(template, context);
return formattedContent;
} catch (error) {
console.error(`模板处理错误 (${action.name || action.id}):`, error);
return options?.defaultValue || null;
}
}
/**
* 执行操作的通用函数
* @param actions 过滤后的操作列表
* @param event 触发事件
* @param triggerType 触发类型
* @param roomId 房间ID
* @param runtimeState 运行时状态
* @param handlers 操作处理器
* @param options 额外选项
*/
export function executeActions(
actions: AutoActionItem[],
event: EventModel | null,
triggerType: TriggerType,
roomId: number,
runtimeState: RuntimeState,
handlers: {
sendLiveDanmaku?: (roomId: number, message: string) => Promise<boolean>;
sendPrivateMessage?: (userId: number, message: string) => Promise<boolean>;
// 可以扩展其他类型的发送处理器
},
options?: {
customContextBuilder?: (event: EventModel | null, roomId: number, triggerType: TriggerType) => ExecutionContext;
customFilters?: Array<(action: AutoActionItem, context: ExecutionContext) => boolean>;
skipUserFilters?: boolean;
skipCooldownCheck?: boolean;
onSuccess?: (action: AutoActionItem, context: ExecutionContext) => void;
}
) {
if (!roomId || actions.length === 0) return;
const biliCookie = useBiliCookie()
// 对每个操作进行处理
for (const action of actions) {
// 构建执行上下文
const context = options?.customContextBuilder
? options.customContextBuilder(event, roomId, triggerType)
: buildExecutionContext(event, roomId, triggerType);
// 应用自定义过滤器
if (options?.customFilters) {
const passesAllFilters = options.customFilters.every(filter => filter(action, context));
if (!passesAllFilters) continue;
}
// 检查用户过滤条件
if (!options?.skipUserFilters && event && !checkUserFilters(action, event)) {
continue;
}
// 检查逻辑表达式
if (action.logicalExpression && event) {
if (!evaluateExpression(action.logicalExpression, context)) {
continue;
}
}
// 检查冷却时间
if (!options?.skipCooldownCheck && !checkCooldown(action, runtimeState)) {
continue;
}
// 根据操作类型执行不同的处理逻辑
switch (action.actionType) {
case ActionType.SEND_DANMAKU:
if (!biliCookie.isCookieValid) {
continue; // 如果未登录,则跳过
}
if (handlers.sendLiveDanmaku) {
// 处理弹幕发送
const message = processTemplate(action, context);
if (message) {
// 更新冷却时间
runtimeState.lastExecutionTime[action.id] = Date.now();
// 延迟发送
if (action.actionConfig.delaySeconds && action.actionConfig.delaySeconds > 0) {
setTimeout(() => {
handlers.sendLiveDanmaku!(roomId, message)
.catch(err => console.error(`[AutoAction] 发送弹幕失败 (${action.name || action.id}):`, err));
}, action.actionConfig.delaySeconds * 1000);
} else {
handlers.sendLiveDanmaku(roomId, message)
.catch(err => console.error(`[AutoAction] 发送弹幕失败 (${action.name || action.id}):`, err));
}
}
} else {
console.warn(`[AutoAction] 未提供弹幕发送处理器,无法执行操作: ${action.name || action.id}`);
}
break;
case ActionType.SEND_PRIVATE_MSG:
if (!biliCookie.isCookieValid) {
continue; // 如果未登录,则跳过
}
if (handlers.sendPrivateMessage && event && event.uid) {
// 处理私信发送
const message = processTemplate(action, context);
if (message) {
// 更新冷却时间(私信也可以有冷却时间)
runtimeState.lastExecutionTime[action.id] = Date.now();
const sendPmPromise = (uid: number, msg: string) => {
return handlers.sendPrivateMessage!(uid, msg)
.then(success => {
if (success && options?.onSuccess) {
// 发送成功后调用 onSuccess 回调
options.onSuccess(action, context);
}
return success;
})
.catch(err => {
console.error(`[AutoAction] 发送私信失败 (${action.name || action.id}):`, err);
return false; // 明确返回 false 表示失败
});
};
// 私信通常不需要延迟,但我们也可以支持
if (action.actionConfig.delaySeconds && action.actionConfig.delaySeconds > 0) {
setTimeout(() => {
sendPmPromise(event.uid, message);
}, action.actionConfig.delaySeconds * 1000);
} else {
sendPmPromise(event.uid, message);
}
}
} else {
console.warn(`[AutoAction] 未提供私信发送处理器或事件缺少UID无法执行操作: ${action.name || action.id}`);
}
break;
case ActionType.EXECUTE_COMMAND:
// 执行自定义命令(未实现)
console.warn(`[AutoAction] 暂不支持执行自定义命令: ${action.name || action.id}`);
break;
default:
console.warn(`[AutoAction] 未知的操作类型: ${action.actionType}`);
}
}
}