mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-06 18:36:55 +08:00
feat: 新增消息队列设置功能,优化自动操作体验
- 在 ClientAutoAction.vue 中新增消息队列设置标签页,允许用户配置弹幕和私信发送间隔。 - 更新多个组件以支持新的设置选项,提升用户交互体验。 - 在 useBiliFunction 中实现队列处理逻辑,确保消息按设定间隔发送。 - 优化 CheckInSettings 组件,整合 AutoActionEditor 以简化配置管理。
This commit is contained in:
@@ -3,6 +3,7 @@ import { AutoActionItem, TriggerType, useAutoAction } from '@/client/store/useAu
|
|||||||
import { useDanmakuClient } from '@/store/useDanmakuClient';
|
import { useDanmakuClient } from '@/store/useDanmakuClient';
|
||||||
import { useBiliCookie } from '@/client/store/useBiliCookie';
|
import { useBiliCookie } from '@/client/store/useBiliCookie';
|
||||||
import { useWebFetcher } from '@/store/useWebFetcher';
|
import { useWebFetcher } from '@/store/useWebFetcher';
|
||||||
|
import { useBiliFunction } from '@/client/store/useBiliFunction';
|
||||||
import {
|
import {
|
||||||
NAlert,
|
NAlert,
|
||||||
NButton,
|
NButton,
|
||||||
@@ -24,7 +25,8 @@ import {
|
|||||||
NIcon,
|
NIcon,
|
||||||
NText,
|
NText,
|
||||||
NTooltip,
|
NTooltip,
|
||||||
NInput
|
NInput,
|
||||||
|
NInputNumber
|
||||||
} from 'naive-ui';
|
} from 'naive-ui';
|
||||||
import { computed, h, onMounted, onUnmounted, ref, watch, reactive } from 'vue';
|
import { computed, h, onMounted, onUnmounted, ref, watch, reactive } from 'vue';
|
||||||
import { ArrowUp24Regular, ArrowDown24Regular, Target24Filled, Edit16Regular } from '@vicons/fluent';
|
import { ArrowUp24Regular, ArrowDown24Regular, Target24Filled, Edit16Regular } from '@vicons/fluent';
|
||||||
@@ -40,6 +42,7 @@ const message = useMessage();
|
|||||||
const danmakuClient = useDanmakuClient();
|
const danmakuClient = useDanmakuClient();
|
||||||
const biliCookieStore = useBiliCookie();
|
const biliCookieStore = useBiliCookie();
|
||||||
const webFetcherStore = useWebFetcher();
|
const webFetcherStore = useWebFetcher();
|
||||||
|
const biliFunc = useBiliFunction();
|
||||||
|
|
||||||
// 从 store 获取 enabledTriggerTypes
|
// 从 store 获取 enabledTriggerTypes
|
||||||
const enabledTriggerTypes = computed(() => autoActionStore.enabledTriggerTypes);
|
const enabledTriggerTypes = computed(() => autoActionStore.enabledTriggerTypes);
|
||||||
@@ -451,10 +454,6 @@ function confirmTest() {
|
|||||||
}
|
}
|
||||||
showTestModal.value = false;
|
showTestModal.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
autoActionStore.init();
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -723,6 +722,51 @@ onMounted(() => {
|
|||||||
>
|
>
|
||||||
<DataManager />
|
<DataManager />
|
||||||
</NTabPane>
|
</NTabPane>
|
||||||
|
|
||||||
|
<!-- 新增:消息队列设置标签页 -->
|
||||||
|
<NTabPane
|
||||||
|
name="queue-settings"
|
||||||
|
tab="消息队列设置"
|
||||||
|
>
|
||||||
|
<NCard
|
||||||
|
title="全局消息队列设置"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<NSpace
|
||||||
|
vertical
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
|
<NSpace align="center">
|
||||||
|
<span>弹幕队列间隔(毫秒):</span>
|
||||||
|
<NInputNumber
|
||||||
|
v-model:value="biliFunc.danmakuInterval"
|
||||||
|
style="width: 120px"
|
||||||
|
@update:value="v => biliFunc.setDanmakuInterval(Number(v))"
|
||||||
|
/>
|
||||||
|
<NText
|
||||||
|
depth="3"
|
||||||
|
style="margin-left: 8px;"
|
||||||
|
>
|
||||||
|
每{{ biliFunc.danmakuInterval }}ms 发送一条弹幕
|
||||||
|
</NText>
|
||||||
|
</NSpace>
|
||||||
|
<NSpace align="center">
|
||||||
|
<span>私信队列间隔(毫秒):</span>
|
||||||
|
<NInputNumber
|
||||||
|
v-model:value="biliFunc.pmInterval"
|
||||||
|
style="width: 120px"
|
||||||
|
@update:value="v => biliFunc.setPmInterval(Number(v))"
|
||||||
|
/>
|
||||||
|
<NText
|
||||||
|
depth="3"
|
||||||
|
style="margin-left: 8px;"
|
||||||
|
>
|
||||||
|
每{{ biliFunc.pmInterval }}ms 发送一条私信
|
||||||
|
</NText>
|
||||||
|
</NSpace>
|
||||||
|
</NSpace>
|
||||||
|
</NCard>
|
||||||
|
</NTabPane>
|
||||||
</NTabs>
|
</NTabs>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</NCard>
|
</NCard>
|
||||||
|
|||||||
@@ -18,6 +18,14 @@ const props = defineProps({
|
|||||||
action: {
|
action: {
|
||||||
type: Object as () => AutoActionItem,
|
type: Object as () => AutoActionItem,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
hideName: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
hideEnabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -53,7 +61,11 @@ const TriggerSettings = getTriggerSettings();
|
|||||||
<TemplateSettings :action="action" />
|
<TemplateSettings :action="action" />
|
||||||
|
|
||||||
<!-- 基本设置 -->
|
<!-- 基本设置 -->
|
||||||
<BasicSettings :action="action" />
|
<BasicSettings
|
||||||
|
:action="action"
|
||||||
|
:hide-name="hideName"
|
||||||
|
:hide-enabled="hideEnabled"
|
||||||
|
/>
|
||||||
<!-- 高级选项 - 所有高级设置放在一个折叠面板中 -->
|
<!-- 高级选项 - 所有高级设置放在一个折叠面板中 -->
|
||||||
<NCollapse class="settings-collapse">
|
<NCollapse class="settings-collapse">
|
||||||
<template #default>
|
<template #default>
|
||||||
|
|||||||
@@ -398,7 +398,6 @@ function insertExample(template: string) {
|
|||||||
<!-- 模板示例 -->
|
<!-- 模板示例 -->
|
||||||
<NCollapse
|
<NCollapse
|
||||||
class="template-examples"
|
class="template-examples"
|
||||||
:default-expanded-names="['examples']"
|
|
||||||
>
|
>
|
||||||
<NCollapseItem
|
<NCollapseItem
|
||||||
name="examples"
|
name="examples"
|
||||||
|
|||||||
@@ -6,6 +6,14 @@ const props = defineProps({
|
|||||||
action: {
|
action: {
|
||||||
type: Object as () => AutoActionItem,
|
type: Object as () => AutoActionItem,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
hideName: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
hideEnabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -44,6 +52,7 @@ const priorityOptions = [
|
|||||||
class="basic-settings"
|
class="basic-settings"
|
||||||
>
|
>
|
||||||
<NSpace
|
<NSpace
|
||||||
|
v-if="!hideName"
|
||||||
key="name"
|
key="name"
|
||||||
align="center"
|
align="center"
|
||||||
justify="space-between"
|
justify="space-between"
|
||||||
@@ -58,6 +67,7 @@ const priorityOptions = [
|
|||||||
</NSpace>
|
</NSpace>
|
||||||
|
|
||||||
<NSpace
|
<NSpace
|
||||||
|
v-if="!hideEnabled"
|
||||||
key="enabled"
|
key="enabled"
|
||||||
align="center"
|
align="center"
|
||||||
justify="space-between"
|
justify="space-between"
|
||||||
|
|||||||
@@ -60,7 +60,7 @@
|
|||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
/>
|
/>
|
||||||
<template #feedback>
|
<template #feedback>
|
||||||
每个用户在指定秒数内只能签到一次。
|
每个用户在指定秒数内签到命令只会响应一次
|
||||||
</template>
|
</template>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
|
|
||||||
@@ -90,15 +90,16 @@
|
|||||||
</NAlert>
|
</NAlert>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<TemplateEditor
|
<!-- 使用 AutoActionEditor 编辑 action 配置 -->
|
||||||
v-model:template="config.successAction"
|
<AutoActionEditor
|
||||||
title="签到成功回复模板"
|
:action="config.successAction"
|
||||||
:custom-test-context="checkInTestContext"
|
:hide-name="true"
|
||||||
|
:hide-enabled="true"
|
||||||
/>
|
/>
|
||||||
<TemplateEditor
|
<AutoActionEditor
|
||||||
v-model:template="config.cooldownAction"
|
:action="config.cooldownAction"
|
||||||
title="冷却中回复模板"
|
:hide-name="true"
|
||||||
:custom-test-context="checkInTestContext"
|
:hide-enabled="true"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<NDivider title-placement="left">
|
<NDivider title-placement="left">
|
||||||
@@ -133,11 +134,11 @@
|
|||||||
<template #feedback>
|
<template #feedback>
|
||||||
成功触发早鸟签到的用户额外获得的积分。
|
成功触发早鸟签到的用户额外获得的积分。
|
||||||
</template>
|
</template>
|
||||||
</NFormItem> <TemplateEditor
|
</NFormItem>
|
||||||
v-model:template="config.earlyBird.successAction"
|
<AutoActionEditor
|
||||||
title="早鸟成功回复模板"
|
:action="config.earlyBird.successAction"
|
||||||
description="用户成功触发早鸟奖励时发送的回复消息,可用变量: {{user.name}}, {{checkin.bonusPoints}}, {{checkin.totalPoints}}, {{checkin.userPoints}}"
|
:hide-name="true"
|
||||||
:custom-test-context="checkInTestContext"
|
:hide-enabled="true"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
@@ -248,6 +249,7 @@ import { EventModel, EventDataTypes } from '@/api/api-models';
|
|||||||
import { Info24Filled } from '@vicons/fluent';
|
import { Info24Filled } from '@vicons/fluent';
|
||||||
import { computed, h, ref } from 'vue';
|
import { computed, h, ref } from 'vue';
|
||||||
import type { UserCheckInData } from '@/client/store/autoAction/modules/checkin';
|
import type { UserCheckInData } from '@/client/store/autoAction/modules/checkin';
|
||||||
|
import AutoActionEditor from '../AutoActionEditor.vue';
|
||||||
|
|
||||||
const autoActionStore = useAutoAction();
|
const autoActionStore = useAutoAction();
|
||||||
const config = autoActionStore.checkInModule.checkInConfig;
|
const config = autoActionStore.checkInModule.checkInConfig;
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { relaunch } from '@tauri-apps/plugin-process';
|
|||||||
import { useDanmakuWindow } from "../store/useDanmakuWindow";
|
import { useDanmakuWindow } from "../store/useDanmakuWindow";
|
||||||
import { getAllWebviewWindows } from "@tauri-apps/api/webviewWindow";
|
import { getAllWebviewWindows } from "@tauri-apps/api/webviewWindow";
|
||||||
import { useAutoAction } from "../store/useAutoAction";
|
import { useAutoAction } from "../store/useAutoAction";
|
||||||
|
import { useBiliFunction } from "../store/useBiliFunction";
|
||||||
|
|
||||||
const accountInfo = useAccount();
|
const accountInfo = useAccount();
|
||||||
|
|
||||||
@@ -147,6 +148,7 @@ export async function initAll(isOnBoot: boolean) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useAutoAction().init();
|
useAutoAction().init();
|
||||||
|
useBiliFunction().init();
|
||||||
|
|
||||||
clientInited.value = true;
|
clientInited.value = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ import { ref, Ref, computed } from 'vue';
|
|||||||
import { EventModel, EventDataTypes } from '@/api/api-models';
|
import { EventModel, EventDataTypes } from '@/api/api-models';
|
||||||
import { ActionType, AutoActionItem, RuntimeState, TriggerType, Priority, KeywordMatchType } from '../types';
|
import { ActionType, AutoActionItem, RuntimeState, TriggerType, Priority, KeywordMatchType } from '../types';
|
||||||
import { usePointStore } from '@/store/usePointStore';
|
import { usePointStore } from '@/store/usePointStore';
|
||||||
import { processTemplate } from '../actionUtils';
|
import { processTemplate, executeActions } from '../actionUtils';
|
||||||
import { buildExecutionContext } from '../utils';
|
import { buildExecutionContext } from '../utils';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { useIDBKeyval } from '@vueuse/integrations/useIDBKeyval';
|
import { useIDBKeyval } from '@vueuse/integrations/useIDBKeyval';
|
||||||
import { GuidUtils } from '@/Utils';
|
import { GuidUtils } from '@/Utils';
|
||||||
|
import { createDefaultAutoAction } from '../utils';
|
||||||
|
|
||||||
// 签到配置接口
|
// 签到配置接口
|
||||||
export interface CheckInConfig {
|
export interface CheckInConfig {
|
||||||
@@ -27,59 +28,33 @@ export interface CheckInConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 创建默认配置
|
// 创建默认配置
|
||||||
function createDefaultCheckInConfig(): CheckInConfig { return {
|
function createDefaultCheckInConfig(): CheckInConfig {
|
||||||
|
const successAction = createDefaultAutoAction(TriggerType.DANMAKU);
|
||||||
|
successAction.name = '签到成功回复';
|
||||||
|
successAction.template = '@{{user.name}} 签到成功,获得 {{checkin.totalPoints}} 积分。';
|
||||||
|
|
||||||
|
const cooldownAction = createDefaultAutoAction(TriggerType.DANMAKU);
|
||||||
|
cooldownAction.name = '签到冷却回复';
|
||||||
|
cooldownAction.template = '{{user.name}} 你今天已经签到过了,明天再来吧~';
|
||||||
|
|
||||||
|
const earlyBirdAction = createDefaultAutoAction(TriggerType.DANMAKU);
|
||||||
|
earlyBirdAction.name = '早鸟签到回复';
|
||||||
|
earlyBirdAction.template = '恭喜 {{user.name}} 完成早鸟签到!额外获得 {{bonusPoints}} 积分,共获得 {{totalPoints}} 积分!';
|
||||||
|
|
||||||
|
return {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
command: '签到',
|
command: '签到',
|
||||||
points: 10,
|
points: 10,
|
||||||
cooldownSeconds: 3600, // 1小时
|
cooldownSeconds: 3600, // 1小时
|
||||||
onlyDuringLive: true, // 默认仅在直播时可签到
|
onlyDuringLive: true, // 默认仅在直播时可签到
|
||||||
sendReply: true, // 默认发送回复消息
|
sendReply: true, // 默认发送回复消息
|
||||||
successAction: {
|
successAction,
|
||||||
id: uuidv4(),
|
cooldownAction,
|
||||||
name: '签到成功回复',
|
|
||||||
enabled: true,
|
|
||||||
triggerType: TriggerType.DANMAKU,
|
|
||||||
actionType: ActionType.SEND_DANMAKU,
|
|
||||||
template: '@{{user.name}} 签到成功,获得 {{checkin.totalPoints}} 积分。',
|
|
||||||
priority: Priority.NORMAL,
|
|
||||||
logicalExpression: '',
|
|
||||||
ignoreCooldown: false,
|
|
||||||
executeCommand: '',
|
|
||||||
triggerConfig: {},
|
|
||||||
actionConfig: {}
|
|
||||||
},
|
|
||||||
cooldownAction: {
|
|
||||||
id: uuidv4(),
|
|
||||||
name: '签到冷却回复',
|
|
||||||
enabled: true,
|
|
||||||
triggerType: TriggerType.DANMAKU,
|
|
||||||
actionType: ActionType.SEND_DANMAKU,
|
|
||||||
template: '{{user.name}} 你今天已经签到过了,明天再来吧~',
|
|
||||||
priority: Priority.NORMAL,
|
|
||||||
logicalExpression: '',
|
|
||||||
ignoreCooldown: false,
|
|
||||||
executeCommand: '',
|
|
||||||
triggerConfig: {},
|
|
||||||
actionConfig: {}
|
|
||||||
},
|
|
||||||
earlyBird: {
|
earlyBird: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
windowMinutes: 30,
|
windowMinutes: 30,
|
||||||
bonusPoints: 5,
|
bonusPoints: 5,
|
||||||
successAction: {
|
successAction: earlyBirdAction
|
||||||
id: uuidv4(),
|
|
||||||
name: '早鸟签到回复',
|
|
||||||
enabled: true,
|
|
||||||
triggerType: TriggerType.DANMAKU,
|
|
||||||
actionType: ActionType.SEND_DANMAKU,
|
|
||||||
template: '恭喜 {{user.name}} 完成早鸟签到!额外获得 {{bonusPoints}} 积分,共获得 {{totalPoints}} 积分!',
|
|
||||||
priority: Priority.NORMAL,
|
|
||||||
logicalExpression: '',
|
|
||||||
ignoreCooldown: false,
|
|
||||||
executeCommand: '',
|
|
||||||
triggerConfig: {},
|
|
||||||
actionConfig: {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -186,18 +161,22 @@ export function useCheckIn(
|
|||||||
if (lastCheckInTime > 0 && isSameDay) {
|
if (lastCheckInTime > 0 && isSameDay) {
|
||||||
// 用户今天已经签到过,发送提示
|
// 用户今天已经签到过,发送提示
|
||||||
if (checkInConfig.value.sendReply) {
|
if (checkInConfig.value.sendReply) {
|
||||||
// 使用buildExecutionContext构建上下文
|
// 构建上下文
|
||||||
const cooldownContext = buildExecutionContext(event, roomId.value, TriggerType.DANMAKU, {
|
const cooldownContext = buildExecutionContext(event, roomId.value, TriggerType.DANMAKU, {
|
||||||
user: { name: username, uid: userId }
|
user: { name: username, uid: userId }
|
||||||
});
|
});
|
||||||
|
// 统一用 executeActions
|
||||||
const message = processTemplate(checkInConfig.value.cooldownAction, cooldownContext);
|
executeActions(
|
||||||
|
[checkInConfig.value.cooldownAction],
|
||||||
if (roomId.value && message) {
|
event,
|
||||||
sendDanmaku(roomId.value, message).catch(err =>
|
TriggerType.DANMAKU,
|
||||||
console.error('[CheckIn] 发送已签到提示失败:', err)
|
roomId.value,
|
||||||
);
|
runtimeState,
|
||||||
}
|
{ sendLiveDanmaku: sendDanmaku },
|
||||||
|
{
|
||||||
|
customContextBuilder: () => cooldownContext
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
window.$notification.info({
|
window.$notification.info({
|
||||||
title: '签到提示',
|
title: '签到提示',
|
||||||
@@ -227,21 +206,15 @@ export function useCheckIn(
|
|||||||
// 更新用户积分
|
// 更新用户积分
|
||||||
try {
|
try {
|
||||||
// 调用积分系统添加积分
|
// 调用积分系统添加积分
|
||||||
const point = await pointStore.addPoints(userId, pointsEarned, `签到奖励 (${format(new Date(), 'yyyy-MM-dd')})`, `${username} 完成签到`); // 更新签到记录
|
const point = await pointStore.addPoints(userId, pointsEarned, `签到奖励 (${format(new Date(), 'yyyy-MM-dd')})`, `${username} 完成签到`);
|
||||||
if (checkInStorage.value) {
|
if (checkInStorage.value) {
|
||||||
// 确保 lastCheckIn 对象存在
|
|
||||||
if (!checkInStorage.value.lastCheckIn) {
|
if (!checkInStorage.value.lastCheckIn) {
|
||||||
checkInStorage.value.lastCheckIn = {};
|
checkInStorage.value.lastCheckIn = {};
|
||||||
}
|
}
|
||||||
// 确保 users 对象存在
|
|
||||||
if (!checkInStorage.value.users) {
|
if (!checkInStorage.value.users) {
|
||||||
checkInStorage.value.users = {};
|
checkInStorage.value.users = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取用户当前的签到数据
|
|
||||||
let userData = checkInStorage.value.users[userId];
|
let userData = checkInStorage.value.users[userId];
|
||||||
|
|
||||||
// 如果是新用户,创建用户数据
|
|
||||||
if (!userData) {
|
if (!userData) {
|
||||||
userData = {
|
userData = {
|
||||||
ouid: userId,
|
ouid: userId,
|
||||||
@@ -253,54 +226,31 @@ export function useCheckIn(
|
|||||||
firstCheckInTime: currentTime
|
firstCheckInTime: currentTime
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算连续签到天数
|
|
||||||
const lastCheckInDate = new Date(userData.lastCheckInTime);
|
const lastCheckInDate = new Date(userData.lastCheckInTime);
|
||||||
const currentDate = new Date(currentTime);
|
const currentDate = new Date(currentTime);
|
||||||
|
|
||||||
// 如果上次签到不是昨天(隔了一天以上),则重置连续签到天数
|
|
||||||
const isYesterday =
|
const isYesterday =
|
||||||
lastCheckInDate.getFullYear() === currentDate.getFullYear() &&
|
lastCheckInDate.getFullYear() === currentDate.getFullYear() &&
|
||||||
lastCheckInDate.getMonth() === currentDate.getMonth() &&
|
lastCheckInDate.getMonth() === currentDate.getMonth() &&
|
||||||
lastCheckInDate.getDate() === currentDate.getDate() - 1;
|
lastCheckInDate.getDate() === currentDate.getDate() - 1;
|
||||||
|
|
||||||
// 如果上次签到不是今天(防止重复计算)
|
|
||||||
if (!isSameDay) {
|
if (!isSameDay) {
|
||||||
// 更新连续签到天数
|
|
||||||
if (isYesterday) {
|
if (isYesterday) {
|
||||||
// 昨天签到过,增加连续签到天数
|
|
||||||
userData.streakDays += 1;
|
userData.streakDays += 1;
|
||||||
} else if (userData.lastCheckInTime > 0) {
|
} else if (userData.lastCheckInTime > 0) {
|
||||||
// 不是昨天签到且不是首次签到,重置连续签到天数为1
|
|
||||||
userData.streakDays = 1;
|
userData.streakDays = 1;
|
||||||
} else {
|
} else {
|
||||||
// 首次签到
|
|
||||||
userData.streakDays = 1;
|
userData.streakDays = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新累计签到次数
|
|
||||||
userData.totalCheckins += 1;
|
userData.totalCheckins += 1;
|
||||||
|
|
||||||
// 更新早鸟签到次数
|
|
||||||
if (isEarlyBird) {
|
if (isEarlyBird) {
|
||||||
userData.earlyBirdCount += 1;
|
userData.earlyBirdCount += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新最后签到时间
|
|
||||||
userData.lastCheckInTime = currentTime;
|
userData.lastCheckInTime = currentTime;
|
||||||
// 更新用户名(以防用户改名)
|
|
||||||
userData.username = username;
|
userData.username = username;
|
||||||
|
|
||||||
// 保存用户数据
|
|
||||||
checkInStorage.value.users[userId] = userData;
|
checkInStorage.value.users[userId] = userData;
|
||||||
// 更新lastCheckIn记录
|
|
||||||
checkInStorage.value.lastCheckIn[userId] = currentTime;
|
checkInStorage.value.lastCheckIn[userId] = currentTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送成功消息
|
|
||||||
if (roomId.value) {
|
if (roomId.value) {
|
||||||
// 构建签到上下文数据
|
|
||||||
const checkInData = {
|
const checkInData = {
|
||||||
checkin: {
|
checkin: {
|
||||||
points: checkInConfig.value.points,
|
points: checkInConfig.value.points,
|
||||||
@@ -312,28 +262,21 @@ export function useCheckIn(
|
|||||||
cooldownSeconds: checkInConfig.value.cooldownSeconds
|
cooldownSeconds: checkInConfig.value.cooldownSeconds
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 根据配置决定是否发送回复消息
|
|
||||||
if (checkInConfig.value.sendReply) {
|
if (checkInConfig.value.sendReply) {
|
||||||
// 使用buildExecutionContext构建完整上下文
|
|
||||||
const successContext = buildExecutionContext(event, roomId.value, TriggerType.DANMAKU, checkInData);
|
const successContext = buildExecutionContext(event, roomId.value, TriggerType.DANMAKU, checkInData);
|
||||||
|
const action = isEarlyBird ? checkInConfig.value.earlyBird.successAction : checkInConfig.value.successAction;
|
||||||
let message;
|
executeActions(
|
||||||
if (isEarlyBird) {
|
[action],
|
||||||
// 使用早鸟签到模板
|
event,
|
||||||
message = processTemplate(checkInConfig.value.earlyBird.successAction, successContext);
|
TriggerType.DANMAKU,
|
||||||
} else {
|
roomId.value,
|
||||||
// 使用普通签到模板
|
runtimeState,
|
||||||
message = processTemplate(checkInConfig.value.successAction, successContext);
|
{ sendLiveDanmaku: sendDanmaku },
|
||||||
}
|
{
|
||||||
|
customContextBuilder: () => successContext
|
||||||
if (message) {
|
}
|
||||||
sendDanmaku(roomId.value, message).catch(err =>
|
);
|
||||||
console.error('[CheckIn] 发送签到成功消息失败:', err)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.$notification.success({
|
window.$notification.success({
|
||||||
title: '签到成功',
|
title: '签到成功',
|
||||||
description: `${username} 完成签到, 获得 ${pointsEarned} 积分, 累计签到 ${checkInStorage.value.users[userId].totalCheckins} 次`,
|
description: `${username} 完成签到, 获得 ${pointsEarned} 积分, 累计签到 ${checkInStorage.value.users[userId].totalCheckins} 次`,
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ import { useAccount } from "@/api/account";
|
|||||||
import { useBiliCookie } from "./useBiliCookie";
|
import { useBiliCookie } from "./useBiliCookie";
|
||||||
import { fetch as tauriFetch } from "@tauri-apps/plugin-http"; // 引入 Body
|
import { fetch as tauriFetch } from "@tauri-apps/plugin-http"; // 引入 Body
|
||||||
import { defineStore, acceptHMRUpdate } from 'pinia';
|
import { defineStore, acceptHMRUpdate } from 'pinia';
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref, onUnmounted } from 'vue';
|
||||||
import md5 from 'md5';
|
import md5 from 'md5';
|
||||||
import { QueryBiliAPI } from "../data/utils";
|
import { QueryBiliAPI } from "../data/utils";
|
||||||
import { onSendPrivateMessageFailed } from "../data/notification";
|
import { onSendPrivateMessageFailed } from "../data/notification";
|
||||||
|
import { useSettings } from "./useSettings";
|
||||||
|
|
||||||
// WBI 混合密钥编码表
|
// WBI 混合密钥编码表
|
||||||
const mixinKeyEncTab = [
|
const mixinKeyEncTab = [
|
||||||
@@ -79,27 +80,105 @@ async function getWbiKeys(cookie: string): Promise<{ img_key: string, sub_key: s
|
|||||||
export const useBiliFunction = defineStore('biliFunction', () => {
|
export const useBiliFunction = defineStore('biliFunction', () => {
|
||||||
const biliCookieStore = useBiliCookie();
|
const biliCookieStore = useBiliCookie();
|
||||||
const account = useAccount();
|
const account = useAccount();
|
||||||
|
const settingsStore = useSettings();
|
||||||
const cookie = computed(() => biliCookieStore.cookie);
|
const cookie = computed(() => biliCookieStore.cookie);
|
||||||
const uid = computed(() => account.value.biliId);
|
const uid = computed(() => account.value.biliId);
|
||||||
// 存储WBI密钥
|
// 存储WBI密钥
|
||||||
const wbiKeys = ref<{ img_key: string, sub_key: string } | null>(null);
|
const wbiKeys = ref<{ img_key: string, sub_key: string } | null>(null);
|
||||||
|
|
||||||
|
// 队列相关状态
|
||||||
|
const danmakuQueue = ref<{ roomId: number, message: string, color?: string, fontsize?: number, mode?: number }[]>([]);
|
||||||
|
const pmQueue = ref<{ receiverId: number, message: string }[]>([]);
|
||||||
|
const isDanmakuProcessing = ref(false);
|
||||||
|
const isPmProcessing = ref(false);
|
||||||
|
const danmakuTimer = ref<ReturnType<typeof setTimeout> | null>(null);
|
||||||
|
const pmTimer = ref<ReturnType<typeof setTimeout> | null>(null);
|
||||||
|
|
||||||
|
// 使用computed获取设置中的间隔值
|
||||||
|
const danmakuInterval = computed(() => settingsStore.settings.danmakuInterval);
|
||||||
|
const pmInterval = computed(() => settingsStore.settings.pmInterval);
|
||||||
|
|
||||||
const csrf = computed(() => {
|
const csrf = computed(() => {
|
||||||
if (!cookie.value) return null;
|
if (!cookie.value) return null;
|
||||||
const match = cookie.value.match(/bili_jct=([^;]+)/);
|
const match = cookie.value.match(/bili_jct=([^;]+)/);
|
||||||
return match ? match[1] : null;
|
return match ? match[1] : null;
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
// 设置间隔的方法
|
||||||
* 发送直播弹幕
|
async function setDanmakuInterval(interval: number) {
|
||||||
* @param roomId 直播间 ID
|
settingsStore.settings.danmakuInterval = interval;
|
||||||
* @param message 弹幕内容
|
await settingsStore.save();
|
||||||
* @param color 弹幕颜色 (十六进制, 如 FFFFFF)
|
}
|
||||||
* @param fontsize 字体大小 (默认 25)
|
|
||||||
* @param mode 弹幕模式 (1: 滚动, 4: 底部, 5: 顶部)
|
async function setPmInterval(interval: number) {
|
||||||
* @returns Promise<boolean> 是否发送成功 (基于API响应码)
|
settingsStore.settings.pmInterval = interval;
|
||||||
*/
|
await settingsStore.save();
|
||||||
async function sendLiveDanmaku(roomId: number, message: string, color: string = 'ffffff', fontsize: number = 25, mode: number = 1): Promise<boolean> {
|
}
|
||||||
|
|
||||||
|
// 处理弹幕队列
|
||||||
|
async function processDanmakuQueue() {
|
||||||
|
if (isDanmakuProcessing.value || danmakuQueue.value.length === 0) return;
|
||||||
|
isDanmakuProcessing.value = true;
|
||||||
|
console.log('[BiliFunction] 处理弹幕队列', danmakuQueue.value);
|
||||||
|
try {
|
||||||
|
const item = danmakuQueue.value[0];
|
||||||
|
await _sendLiveDanmaku(item.roomId, item.message, item.color, item.fontsize, item.mode);
|
||||||
|
danmakuQueue.value.shift();
|
||||||
|
} finally {
|
||||||
|
isDanmakuProcessing.value = false;
|
||||||
|
if (danmakuQueue.value.length > 0) {
|
||||||
|
danmakuTimer.value = setTimeout(() => processDanmakuQueue(), danmakuInterval.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理私信队列
|
||||||
|
async function processPmQueue() {
|
||||||
|
if (isPmProcessing.value || pmQueue.value.length === 0) return;
|
||||||
|
isPmProcessing.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const item = pmQueue.value[0];
|
||||||
|
await _sendPrivateMessage(item.receiverId, item.message);
|
||||||
|
pmQueue.value.shift();
|
||||||
|
} finally {
|
||||||
|
isPmProcessing.value = false;
|
||||||
|
if (pmQueue.value.length > 0) {
|
||||||
|
pmTimer.value = setTimeout(() => processPmQueue(), pmInterval.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理定时器
|
||||||
|
function clearTimers() {
|
||||||
|
if (danmakuTimer.value) {
|
||||||
|
clearTimeout(danmakuTimer.value);
|
||||||
|
danmakuTimer.value = null;
|
||||||
|
}
|
||||||
|
if (pmTimer.value) {
|
||||||
|
clearTimeout(pmTimer.value);
|
||||||
|
pmTimer.value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化函数
|
||||||
|
async function init() {
|
||||||
|
// 确保设置已经初始化
|
||||||
|
if (!settingsStore.settings.danmakuInterval) {
|
||||||
|
await settingsStore.init();
|
||||||
|
}
|
||||||
|
// 启动队列处理
|
||||||
|
processDanmakuQueue();
|
||||||
|
processPmQueue();
|
||||||
|
console.log('[BiliFunction] 队列初始化完成');
|
||||||
|
// 清理定时器
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearTimers();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 原始发送弹幕方法(重命名为_sendLiveDanmaku)
|
||||||
|
async function _sendLiveDanmaku(roomId: number, message: string, color: string = 'ffffff', fontsize: number = 25, mode: number = 1): Promise<boolean> {
|
||||||
if (!csrf.value || !cookie.value) {
|
if (!csrf.value || !cookie.value) {
|
||||||
console.error("发送弹幕失败:缺少 cookie 或 csrf token");
|
console.error("发送弹幕失败:缺少 cookie 或 csrf token");
|
||||||
return false;
|
return false;
|
||||||
@@ -124,7 +203,6 @@ export const useBiliFunction = defineStore('biliFunction', () => {
|
|||||||
};
|
};
|
||||||
const params = new URLSearchParams(data)
|
const params = new URLSearchParams(data)
|
||||||
try {
|
try {
|
||||||
// 注意: B站网页版发送弹幕是用 application/x-www-form-urlencoded
|
|
||||||
const response = await tauriFetch(url, {
|
const response = await tauriFetch(url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -133,7 +211,7 @@ export const useBiliFunction = defineStore('biliFunction', () => {
|
|||||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.0.0 Safari/537.36",
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.0.0 Safari/537.36",
|
||||||
"Referer": `https://live.bilibili.com/${roomId}`
|
"Referer": `https://live.bilibili.com/${roomId}`
|
||||||
},
|
},
|
||||||
body: params, // 发送 JSON 数据
|
body: params,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -141,7 +219,6 @@ export const useBiliFunction = defineStore('biliFunction', () => {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
// B站成功码通常是 0
|
|
||||||
if (json.code !== 0) {
|
if (json.code !== 0) {
|
||||||
window.$notification.error({
|
window.$notification.error({
|
||||||
title: '发送弹幕失败',
|
title: '发送弹幕失败',
|
||||||
@@ -168,65 +245,8 @@ export const useBiliFunction = defineStore('biliFunction', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// 原始发送私信方法(重命名为_sendPrivateMessage)
|
||||||
* 封禁直播间用户 (需要主播或房管权限)
|
async function _sendPrivateMessage(receiverId: number, message: string): Promise<boolean> {
|
||||||
* @param roomId 直播间 ID
|
|
||||||
* @param userId 要封禁的用户 UID
|
|
||||||
* @param hours 封禁时长 (小时, 1-720)
|
|
||||||
*/
|
|
||||||
async function banLiveUser(roomId: number, userId: number, hours: number = 1) {
|
|
||||||
// 使用 csrf.value
|
|
||||||
if (!csrf.value || !cookie.value) {
|
|
||||||
console.error("封禁用户失败:缺少 cookie 或 csrf token");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 确保 hours 在 1 到 720 之间
|
|
||||||
const validHours = Math.max(1, Math.min(hours, 720));
|
|
||||||
const url = "https://api.live.bilibili.com/banned_service/v2/Silent/add_user";
|
|
||||||
const data = {
|
|
||||||
room_id: roomId.toString(),
|
|
||||||
block_uid: userId.toString(),
|
|
||||||
hour: validHours.toString(),
|
|
||||||
csrf: csrf.value, // 使用计算属性的值
|
|
||||||
csrf_token: csrf.value, // 使用计算属性的值
|
|
||||||
visit_id: "", // 通常可以为空
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const params = new URLSearchParams(data)
|
|
||||||
const response = await tauriFetch(url, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
"Cookie": cookie.value, // 使用计算属性的值
|
|
||||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.0.0 Safari/537.36",
|
|
||||||
"Referer": `https://live.bilibili.com/p/html/live-room-setting/#/room-manager/black-list?room_id=${roomId}` // 模拟来源
|
|
||||||
},
|
|
||||||
body: params, // 发送 URLSearchParams 数据
|
|
||||||
});
|
|
||||||
if (!response.ok) {
|
|
||||||
console.error("封禁用户失败:", response.status, await response.text());
|
|
||||||
return response.statusText;
|
|
||||||
}
|
|
||||||
const json = await response.json();
|
|
||||||
if (json.code !== 0) {
|
|
||||||
console.error("封禁用户API失败:", json.code, json.message || json.msg);
|
|
||||||
return json.data;
|
|
||||||
}
|
|
||||||
console.log("封禁用户成功:", json.data);
|
|
||||||
return json.data;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("封禁用户时发生错误:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送私信
|
|
||||||
* @param receiverId 接收者 UID
|
|
||||||
* @param message 私信内容
|
|
||||||
* @returns Promise<boolean> 是否发送成功 (基于API响应码)
|
|
||||||
*/
|
|
||||||
async function sendPrivateMessage(receiverId: number, message: string): Promise<boolean> {
|
|
||||||
if (!csrf.value || !cookie.value || !uid.value) {
|
if (!csrf.value || !cookie.value || !uid.value) {
|
||||||
const error = "发送私信失败:缺少 cookie, csrf token 或 uid";
|
const error = "发送私信失败:缺少 cookie, csrf token 或 uid";
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -338,12 +358,82 @@ export const useBiliFunction = defineStore('biliFunction', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 新的队列发送方法
|
||||||
|
async function sendLiveDanmaku(roomId: number, message: string, color: string = 'ffffff', fontsize: number = 25, mode: number = 1): Promise<boolean> {
|
||||||
|
danmakuQueue.value.push({ roomId, message, color, fontsize, mode });
|
||||||
|
processDanmakuQueue();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendPrivateMessage(receiverId: number, message: string): Promise<boolean> {
|
||||||
|
pmQueue.value.push({ receiverId, message });
|
||||||
|
processPmQueue();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 封禁直播间用户 (需要主播或房管权限)
|
||||||
|
* @param roomId 直播间 ID
|
||||||
|
* @param userId 要封禁的用户 UID
|
||||||
|
* @param hours 封禁时长 (小时, 1-720)
|
||||||
|
*/
|
||||||
|
async function banLiveUser(roomId: number, userId: number, hours: number = 1) {
|
||||||
|
// 使用 csrf.value
|
||||||
|
if (!csrf.value || !cookie.value) {
|
||||||
|
console.error("封禁用户失败:缺少 cookie 或 csrf token");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 确保 hours 在 1 到 720 之间
|
||||||
|
const validHours = Math.max(1, Math.min(hours, 720));
|
||||||
|
const url = "https://api.live.bilibili.com/banned_service/v2/Silent/add_user";
|
||||||
|
const data = {
|
||||||
|
room_id: roomId.toString(),
|
||||||
|
block_uid: userId.toString(),
|
||||||
|
hour: validHours.toString(),
|
||||||
|
csrf: csrf.value, // 使用计算属性的值
|
||||||
|
csrf_token: csrf.value, // 使用计算属性的值
|
||||||
|
visit_id: "", // 通常可以为空
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const params = new URLSearchParams(data)
|
||||||
|
const response = await tauriFetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
"Cookie": cookie.value, // 使用计算属性的值
|
||||||
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.0.0 Safari/537.36",
|
||||||
|
"Referer": `https://live.bilibili.com/p/html/live-room-setting/#/room-manager/black-list?room_id=${roomId}` // 模拟来源
|
||||||
|
},
|
||||||
|
body: params, // 发送 URLSearchParams 数据
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error("封禁用户失败:", response.status, await response.text());
|
||||||
|
return response.statusText;
|
||||||
|
}
|
||||||
|
const json = await response.json();
|
||||||
|
if (json.code !== 0) {
|
||||||
|
console.error("封禁用户API失败:", json.code, json.message || json.msg);
|
||||||
|
return json.data;
|
||||||
|
}
|
||||||
|
console.log("封禁用户成功:", json.data);
|
||||||
|
return json.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("封禁用户时发生错误:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
init,
|
||||||
sendLiveDanmaku,
|
sendLiveDanmaku,
|
||||||
banLiveUser,
|
banLiveUser,
|
||||||
sendPrivateMessage,
|
sendPrivateMessage,
|
||||||
csrf,
|
csrf,
|
||||||
uid,
|
uid,
|
||||||
|
danmakuInterval,
|
||||||
|
pmInterval,
|
||||||
|
setDanmakuInterval,
|
||||||
|
setPmInterval,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ export type VTsuruClientSettings = {
|
|||||||
enableNotification: boolean;
|
enableNotification: boolean;
|
||||||
notificationSettings: NotificationSettings;
|
notificationSettings: NotificationSettings;
|
||||||
|
|
||||||
|
// 消息队列间隔设置
|
||||||
|
danmakuInterval: number;
|
||||||
|
pmInterval: number;
|
||||||
|
|
||||||
dev_disableDanmakuClient: boolean;
|
dev_disableDanmakuClient: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -32,6 +36,10 @@ export const useSettings = defineStore('settings', () => {
|
|||||||
enableTypes: ['question-box', 'danmaku', 'message-failed'],
|
enableTypes: ['question-box', 'danmaku', 'message-failed'],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 默认间隔为2秒
|
||||||
|
danmakuInterval: 2000,
|
||||||
|
pmInterval: 2000,
|
||||||
|
|
||||||
dev_disableDanmakuClient: false,
|
dev_disableDanmakuClient: false,
|
||||||
};
|
};
|
||||||
const settings = ref<VTsuruClientSettings>(Object.assign({}, defaultSettings));
|
const settings = ref<VTsuruClientSettings>(Object.assign({}, defaultSettings));
|
||||||
@@ -40,6 +48,9 @@ export const useSettings = defineStore('settings', () => {
|
|||||||
settings.value = (await store.get()) || Object.assign({}, defaultSettings);
|
settings.value = (await store.get()) || Object.assign({}, defaultSettings);
|
||||||
settings.value.notificationSettings ??= defaultSettings.notificationSettings;
|
settings.value.notificationSettings ??= defaultSettings.notificationSettings;
|
||||||
settings.value.notificationSettings.enableTypes ??= [ 'question-box', 'danmaku', 'message-failed' ];
|
settings.value.notificationSettings.enableTypes ??= [ 'question-box', 'danmaku', 'message-failed' ];
|
||||||
|
// 初始化消息队列间隔设置
|
||||||
|
settings.value.danmakuInterval ??= defaultSettings.danmakuInterval;
|
||||||
|
settings.value.pmInterval ??= defaultSettings.pmInterval;
|
||||||
}
|
}
|
||||||
async function save() {
|
async function save() {
|
||||||
await store.set(settings.value);
|
await store.set(settings.value);
|
||||||
|
|||||||
@@ -4,6 +4,28 @@ import { VNode } from "vue";
|
|||||||
import { FETCH_API } from "./constants";
|
import { FETCH_API } from "./constants";
|
||||||
|
|
||||||
export const updateNotes: updateNoteType[] = [
|
export const updateNotes: updateNoteType[] = [
|
||||||
|
{
|
||||||
|
ver: 6,
|
||||||
|
date: '2025.4.26',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: 'new',
|
||||||
|
title: '自动操作新增弹幕签到功能',
|
||||||
|
content: [
|
||||||
|
[
|
||||||
|
'客户端新增弹幕签到功能,支持观众通过发送特定指令获得积分 (需扫码登录或者使用CookieCloud才能发送回复',
|
||||||
|
() => h(NImage, { src: 'https://pan.suki.club/d/vtsuru/imgur/0e784480a3016b748af2579b2c492a3b.png', width: 300 }),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'客户端安装方式:',
|
||||||
|
() => h(NButton, {
|
||||||
|
text: true, tag: 'a', href: 'https://www.wolai.com/carN6qvUm3FErze9Xo53ii', target: '_blank', type: 'info'
|
||||||
|
}, () => '查看介绍'),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
ver: 5,
|
ver: 5,
|
||||||
date: '2025.4.24',
|
date: '2025.4.24',
|
||||||
|
|||||||
Reference in New Issue
Block a user