-
+
- 编辑弹幕
+
+
+ !val && (uiStore.uiConfig.medalInfoPanelSortMode = false)"
+ />
+ 编辑名单
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ !val && (uiStore.uiConfig.medalInfoPanelSortMode = false)"
+ />
+ 编辑名单
+
+
+
+
+
+
+
+
+
+
+
+ 编辑弹幕
+
+
+
+
+
+
+
+ !val && (uiStore.uiConfig.medalInfoPanelSortMode = false)"
+ />
+ 编辑名单
+
+
+
+
+
+
+
-
-
-
- 分钟 / 直播间
@@ -202,18 +291,20 @@ function handleRowClick(row: MedalInfoRow) {
!val && (uiStore.uiConfig.medalInfoPanelSortMode = false)"
/>
- 编辑名单
+
+
直播任务相关信息可在
@@ -227,10 +318,11 @@ function handleRowClick(row: MedalInfoRow) {
查看。
-
+
+
@@ -252,7 +344,13 @@ function handleRowClick(row: MedalInfoRow) {
新增弹幕
-
+
+
+
!val && nextTick(() => initSelection(medalInfoTableData))"
+ @change="(val) => !val && nextTick(() => refreshSelection(medalInfoTableData))"
/>
diff --git a/src/library/help-info/index.ts b/src/library/help-info/index.ts
index 8c32920b..7ef6e393 100644
--- a/src/library/help-info/index.ts
+++ b/src/library/help-info/index.ts
@@ -35,10 +35,7 @@ const helpInfo: HelpInfo = {
list: {
title: '黑白名单 / 排序模式',
message: h('p', [
- h(
- 'div',
- '为了更精细地控制为哪些粉丝勋章执行点亮熄灭勋章和观看直播任务,你可以使用黑名单或白名单模式。',
- ),
+ h('div', '每个直播任务都有自己独立的黑名单或白名单,互不影响。'),
h('ul', [
h('li', [
h('span', '黑名单:仅为'),
@@ -56,7 +53,7 @@ const helpInfo: HelpInfo = {
'div',
'使用白名单模式时,点击【编辑粉丝勋章名单】窗口右下角的开关即可在【常规模式】和【排序模式】之间切换。',
),
- h('div', '在排序模式下,你可以调整脚本执行观看任务的粉丝勋章顺序。'),
+ h('div', '在排序模式下,你可以调整脚本执行任务的粉丝勋章顺序。'),
h('ul', [
h('li', '使用鼠标拖拽表格中的行来调整顺序。'),
h('li', '拖拽行至表格的顶部和底部可以触发滚动。'),
@@ -115,6 +112,38 @@ const helpInfo: HelpInfo = {
]),
]),
},
+ like: {
+ title: '粉丝团点赞',
+ message: h('p', [
+ h('div', '为已点亮的粉丝勋章完成粉丝团每日点赞任务。'),
+ h('ul', [
+ h('li', '只有主播开播时点赞才会计入任务进度,因此只会给开播中的直播间点赞。'),
+ h(
+ 'li',
+ '脚本会根据 B 站返回的每日上限自动决定点赞次数,每个等级的勋章上限不同(如 2/2、10/10)。',
+ ),
+ h('li', '每个直播间分多轮点赞,每轮约 30 次,轮间间隔约 30 秒。'),
+ ]),
+ ]),
+ },
+ danmu: {
+ title: '粉丝团发弹幕',
+ message: h('p', [
+ h('div', '为已点亮的粉丝勋章完成粉丝团每日发弹幕任务。'),
+ h('ul', [
+ h('li', '脚本会根据 B 站返回的每日上限自动决定发送条数,每个等级的勋章上限不同。'),
+ h(
+ 'li',
+ '点击【编辑弹幕】按钮编辑要发送的弹幕,脚本会从中按顺序循环抽取弹幕发送。建议修改默认的弹幕。',
+ ),
+ h(
+ 'li',
+ '若担心直播中刷屏被禁言,可开启【仅在未开播的直播间发弹幕】,此时会跳过当前正在直播的直播间。',
+ ),
+ h('li', '弹幕被屏蔽词拦截或失败会自动小幅补发。'),
+ ]),
+ ]),
+ },
watch: {
title: '观看直播',
message: h('p', [
@@ -122,8 +151,10 @@ const helpInfo: HelpInfo = {
h('ul', [
h('li', '部分直播间因为没有设置直播分区导致任务无法完成。'),
h('li', '主播当前是否开播不会影响该任务的完成。'),
- h('li', '根据目前规则,观看15分钟可点亮粉丝勋章,再观看25分钟即可获得全部30亲密度。'),
- h('li', '由于计时存在误差,可以在目标基础上适当增加一些观看时长。'),
+ h(
+ 'li',
+ '脚本会根据 B 站返回的每日上限自动计算观看时长,每观看 15 分钟领取一档亲密度,并附加约 1 分钟缓冲以补偿计时误差。',
+ ),
]),
h('div', [
h('strong', '注意:'),
diff --git a/src/library/storage/defaultValues.ts b/src/library/storage/defaultValues.ts
index 8ee6ad74..8fb11ffa 100644
--- a/src/library/storage/defaultValues.ts
+++ b/src/library/storage/defaultValues.ts
@@ -56,17 +56,48 @@ const defaultValues: DefaultValues = {
'←◡←',
`(●'◡'●)ノ♥`,
],
+ isWhiteList: false,
+ roomidList: [],
+ _lastCompleteTime: 0,
+ },
+ like: {
+ enabled: false,
+ isWhiteList: false,
+ roomidList: [],
+ _lastCompleteTime: 0,
+ },
+ danmu: {
+ enabled: false,
+ danmuList: [
+ '(⌒▽⌒)',
+ '( ̄▽ ̄)',
+ '(=・ω・=)',
+ '(`・ω・´)',
+ '(〜 ̄△ ̄)〜',
+ '(・∀・)',
+ '(°∀°)ノ',
+ '╮( ̄▽ ̄)╭',
+ '_(:3」∠)_',
+ '(^・ω・^ )',
+ '(● ̄(エ) ̄●)',
+ 'ε=ε=(ノ≧∇≦)ノ',
+ '⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄',
+ '←◡←',
+ `(●'◡'●)ノ♥`,
+ ],
+ onlyWhenNotLiving: false,
+ isWhiteList: false,
+ roomidList: [],
_lastCompleteTime: 0,
},
watch: {
enabled: false,
- time: 25,
+ isWhiteList: false,
+ roomidList: [],
_watchingProgress: {},
_lastWatchTime: 0,
_lastCompleteTime: 0,
},
- isWhiteList: false,
- roomidList: [],
},
},
OtherTasks: {
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/MedalModule.ts b/src/modules/dailyTasks/liveTasks/medalTasks/MedalModule.ts
index d61fb645..247d4a9b 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/MedalModule.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/MedalModule.ts
@@ -2,25 +2,37 @@ import BaseModule from '@/modules/BaseModule'
import { storeToRefs } from 'pinia'
import { useBiliStore, useModuleStore } from '@/stores'
import { watch } from 'vue'
-import type { PublicMedalFilters } from './types'
-import { arrayToMap } from '@/library/utils'
+import type { MedalTaskSharedConfig, PublicMedalFilters } from './types'
+import { arrayToMap, sleep } from '@/library/utils'
import type { LiveData } from '@/library/bili-api/data'
+import BAPI from '@/library/bili-api'
+import { isTimestampToday } from '@/library/luxon'
+import _ from 'lodash'
+
+type JumpType = 'like' | 'sendDanmu' | 'watchLive' | 'feedLight' | 'sendGift'
class MedalModule extends BaseModule {
medalTasksConfig = useModuleStore().moduleConfig.DailyTasks.LiveTasks.medalTasks
+ /**
+ * 子类需要重写此 getter,返回该任务对应的 `isWhiteList` 与 `roomidList`
+ */
+ protected get taskConfig(): MedalTaskSharedConfig {
+ throw new Error('Method not implemented.')
+ }
+
protected PUBLIC_MEDAL_FILTERS: PublicMedalFilters = {
// 包含在白名单中或不包含在黑名单中返回true,否则返回false
whiteBlackList: (m) =>
- this.medalTasksConfig.isWhiteList
- ? this.medalTasksConfig.roomidList.includes(m.room_info.room_id)
- : !this.medalTasksConfig.roomidList.includes(m.room_info.room_id),
+ this.taskConfig.isWhiteList
+ ? this.taskConfig.roomidList.includes(m.room_info.room_id)
+ : !this.taskConfig.roomidList.includes(m.room_info.room_id),
// 等级小于120返回true,否则返回false
levelLt120: (medal) => medal.medal.level < 120,
}
protected sortMedals(medals: LiveData.FansMedalPanel.List[]): LiveData.FansMedalPanel.List[] {
- const orderMap = arrayToMap(this.medalTasksConfig.roomidList)
+ const orderMap = arrayToMap(this.taskConfig.roomidList)
return medals.sort(
(a, b) => orderMap.get(a.room_info.room_id)! - orderMap.get(b.room_info.room_id)!,
)
@@ -49,6 +61,120 @@ class MedalModule extends BaseModule {
}
})
}
+
+ /**
+ * 解析每日任务进度文案 "每日上限 X/Y"
+ *
+ * @param sub_title API 返回的 sub_title
+ * @returns 解析成功返回 `{ current, limit }`,否则返回 `null`
+ */
+ protected static parseDailyLimit(
+ sub_title: string | undefined,
+ ): { current: number; limit: number } | null {
+ if (!sub_title) return null
+ const match = sub_title.match(/(\d+)\s*\/\s*(\d+)/)
+ if (!match) return null
+ return { current: Number(match[1]), limit: Number(match[2]) }
+ }
+
+ /**
+ * 按 jump_type 在 task_info 中查找任务项
+ */
+ protected static findTaskInfo(
+ task_info: LiveData.GetActivatedMedalInfo.TaskInfo[] | null | undefined,
+ jump_type: JumpType,
+ ): LiveData.GetActivatedMedalInfo.TaskInfo | undefined {
+ return task_info?.find((t) => t.jump_type === jump_type)
+ }
+
+ /**
+ * 从任务 title 中解析数字(如 "点赞30次" → 30,"发弹幕10次" → 10)
+ *
+ * @returns 解析成功返回数字,否则返回 null
+ */
+ protected static parseTitleCount(title: string | undefined): number | null {
+ if (!title) return null
+ const match = title.match(/\d+/)
+ if (!match) return null
+ return Number(match[0])
+ }
+
+ /** 任务内缓存:target_id -> task_info(缓存 Promise 避免并发重复请求) */
+ private taskInfoCache = new Map<
+ number,
+ Promise
+ >()
+
+ /** 在 `run()` 入口调用以清空缓存 */
+ protected resetTaskInfoCache(): void {
+ this.taskInfoCache.clear()
+ }
+
+ /**
+ * 获取指定粉丝勋章的 task_info。调用前会先 `sleep` 一段时间避免风控。
+ *
+ * 同一 target_id 的并发请求会共享同一个 Promise。
+ *
+ * @param target_id 主播 uid
+ * @returns 成功返回 task_info 数组,失败返回 null
+ */
+ protected fetchTaskInfo(
+ target_id: number,
+ ): Promise {
+ const cached = this.taskInfoCache.get(target_id)
+ if (cached) return cached
+
+ const promise = (async () => {
+ await sleep(_.random(300, 500))
+ try {
+ const response = await BAPI.live.getActivatedMedalInfo(target_id)
+ this.logger.log(`BAPI.live.getActivatedMedalInfo(${target_id}) response`, response)
+ if (response.code === 0) {
+ return response.data.task_info
+ } else {
+ this.logger.error(`BAPI.live.getActivatedMedalInfo(${target_id}) 失败`, response.message)
+ return null
+ }
+ } catch (error) {
+ this.logger.error(`BAPI.live.getActivatedMedalInfo(${target_id}) 出错`, error)
+ return null
+ }
+ })()
+
+ this.taskInfoCache.set(target_id, promise)
+ return promise
+ }
+
+ /**
+ * 若点亮熄灭勋章任务已启用且今天未完成,等待其结束后再继续。
+ *
+ * 用于让 like/danmu/watch 在 light 任务完成后再执行,避免与点亮逻辑产生竞争。
+ */
+ protected async waitForLightTask(): Promise {
+ const lightConfig = this.medalTasksConfig.light
+ if (!lightConfig.enabled) return
+ if (isTimestampToday(lightConfig._lastCompleteTime)) return
+
+ const moduleStore = useModuleStore()
+ const currentStatus = moduleStore.moduleStatus.DailyTasks.LiveTasks.medalTasks.light
+ if (currentStatus === 'done' || currentStatus === 'error') return
+
+ this.logger.log('等待点亮熄灭勋章任务完成后再执行')
+
+ return new Promise((resolve) => {
+ const stopWatch = watch(
+ () => moduleStore.moduleStatus.DailyTasks.LiveTasks.medalTasks.light,
+ (newStatus) => {
+ if (newStatus === 'done' || newStatus === 'error') {
+ stopWatch()
+ // 重新获取粉丝勋章(主要是为了获取最新的点亮状态、是否正在直播状态)
+ moduleStore.rerunModule('Default_FansMedals')
+ resolve()
+ }
+ },
+ )
+ })
+ }
}
export default MedalModule
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/danmuTask.ts b/src/modules/dailyTasks/liveTasks/medalTasks/danmuTask.ts
new file mode 100644
index 00000000..615bd634
--- /dev/null
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/danmuTask.ts
@@ -0,0 +1,153 @@
+import { delayToNextMoment, isNowIn, isTimestampToday, tsm } from '@/library/luxon'
+import BAPI from '@/library/bili-api'
+import { useBiliStore, useModuleStore } from '@/stores'
+import { sleep } from '@/library/utils'
+import type { ModuleStatusTypes } from '@/types'
+import _ from 'lodash'
+import MedalModule from '@/modules/dailyTasks/liveTasks/medalTasks/MedalModule'
+import type { LiveData } from '@/library/bili-api/data'
+
+class DanmuTask extends MedalModule {
+ config = this.medalTasksConfig.danmu
+
+ protected get taskConfig() {
+ return this.config
+ }
+
+ set status(s: ModuleStatusTypes) {
+ useModuleStore().moduleStatus.DailyTasks.LiveTasks.medalTasks.danmu = s
+ }
+
+ /**
+ * 获取已点亮的粉丝勋章;开启 `onlyWhenNotLiving` 时排除开播中的
+ */
+ private getMedals(): LiveData.FansMedalPanel.List[] {
+ const fansMedals = useBiliStore().filteredFansMedals
+ const result = fansMedals.filter(
+ (medal) =>
+ this.PUBLIC_MEDAL_FILTERS.whiteBlackList(medal) &&
+ this.PUBLIC_MEDAL_FILTERS.levelLt120(medal) &&
+ medal.medal.is_lighted === 1 &&
+ (!this.config.onlyWhenNotLiving || medal.room_info.living_status !== 1),
+ )
+
+ if (this.taskConfig.isWhiteList) {
+ this.sortMedals(result)
+ }
+
+ return result
+ }
+
+ /**
+ * 发弹幕
+ * @returns 是否发送成功
+ */
+ private async sendDanmu(medal: LiveData.FansMedalPanel.List, danmu: string): Promise {
+ const room_id = medal.room_info.room_id
+ const target_id = medal.medal.target_id
+ const nick_name = medal.anchor_info.nick_name
+ const medal_name = medal.medal.medal_name
+ const logMessage = `粉丝勋章【${medal_name}】 在主播【${nick_name}】(UID:${target_id})的直播间(${room_id})发送弹幕 ${danmu}`
+
+ try {
+ const response = await BAPI.live.sendMsg(danmu, room_id)
+ this.logger.log(`BAPI.live.sendMsg(${danmu}, ${room_id})`, response)
+ if (response.code === 0) {
+ if (response.msg === 'k') {
+ this.logger.warn(`粉丝团发弹幕 ${logMessage} 异常,弹幕可能包含屏蔽词`)
+ } else {
+ this.logger.log(`粉丝团发弹幕 ${logMessage} 成功`)
+ return true
+ }
+ } else {
+ this.logger.error(`粉丝团发弹幕 ${logMessage} 失败`, response.message)
+ }
+ } catch (error) {
+ this.logger.error(`粉丝团发弹幕 ${logMessage} 出错`, error)
+ }
+
+ return false
+ }
+
+ public async run(): Promise {
+ this.logger.log('粉丝团发弹幕模块开始运行')
+
+ if (!isTimestampToday(this.config._lastCompleteTime)) {
+ await this.waitForLightTask()
+
+ if (!(await this.waitForFansMedals())) {
+ this.logger.error('粉丝勋章数据不存在,不执行粉丝团发弹幕任务')
+ this.status = 'error'
+ return
+ }
+
+ this.status = 'running'
+ this.resetTaskInfoCache()
+ const fansMedals = this.getMedals()
+ let allCompleted = true
+ let danmuIndex = 0
+
+ outer: for (let i = 0; i < fansMedals.length; i++) {
+ if (isNowIn(23, 55, 0, 5)) {
+ this.logger.log('即将或刚刚发生跨天,提早结束本轮粉丝团发弹幕任务')
+ allCompleted = false
+ break
+ }
+
+ const medal = fansMedals[i]
+ const taskInfo = await this.fetchTaskInfo(medal.medal.target_id)
+ if (!taskInfo) continue
+
+ const item = MedalModule.findTaskInfo(taskInfo, 'sendDanmu')
+ if (!item || item.is_done) continue
+
+ const parsed = MedalModule.parseDailyLimit(item.sub_title)
+ if (!parsed || parsed.current >= parsed.limit) continue
+
+ const remaining = parsed.limit - parsed.current
+ // 失败补偿:最多额外发 3 条
+ let target = remaining
+ const maxTarget = remaining + 3
+
+ for (let j = 0; j < target; j++) {
+ if (isNowIn(23, 55, 0, 5)) {
+ this.logger.log('即将或刚刚发生跨天,提早结束本轮粉丝团发弹幕任务')
+ allCompleted = false
+ break outer
+ }
+
+ const danmuText = this.config.danmuList[danmuIndex++ % this.config.danmuList.length]
+ const ok = await this.sendDanmu(medal, danmuText)
+ if (!ok) {
+ target = Math.min(target + 1, maxTarget)
+ }
+
+ if (j < target - 1 || i < fansMedals.length - 1) {
+ await sleep(_.random(6000, 8000))
+ }
+ }
+ }
+
+ if (allCompleted) {
+ this.config._lastCompleteTime = tsm()
+ this.status = 'done'
+ this.logger.log('粉丝团发弹幕任务已完成')
+ } else {
+ this.status = ''
+ }
+ } else {
+ if (isNowIn(0, 0, 0, 5)) {
+ this.logger.log('昨天的粉丝团发弹幕任务已经完成过了,等到今天的00:05再执行')
+ } else {
+ this.logger.log('今天已经完成过粉丝团发弹幕任务了')
+ this.status = 'done'
+ }
+ }
+
+ const diff = delayToNextMoment()
+ this.nextRunTimer = setTimeout(() => this.run(), diff.ms)
+ this.logger.log('距离粉丝团发弹幕模块下次运行时间:', diff.str)
+ }
+}
+
+export default DanmuTask
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/index.ts b/src/modules/dailyTasks/liveTasks/medalTasks/index.ts
index cf5cbb87..c19d3d30 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/index.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/index.ts
@@ -1,2 +1,4 @@
export { default as DailyTask_LiveTask_LightTask } from './lightTask'
+export { default as DailyTask_LiveTask_LikeTask } from './likeTask'
+export { default as DailyTask_LiveTask_DanmuTask } from './danmuTask'
export { default as DailyTask_LiveTask_WatchTask } from './watchTask'
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts b/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts
index 04213093..f3331489 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts
@@ -11,6 +11,10 @@ import type { LiveData } from '@/library/bili-api/data'
class LightTask extends MedalModule {
config = this.medalTasksConfig.light
+ protected get taskConfig() {
+ return this.config
+ }
+
set status(s: ModuleStatusTypes) {
useModuleStore().moduleStatus.DailyTasks.LiveTasks.medalTasks.light = s
}
@@ -44,7 +48,7 @@ class LightTask extends MedalModule {
result[livingStatus].push(medal)
})
- if (this.medalTasksConfig.isWhiteList) {
+ if (this.taskConfig.isWhiteList) {
// 白名单排序
this.sortMedals(result.on)
this.sortMedals(result.off)
@@ -118,7 +122,11 @@ class LightTask extends MedalModule {
private async likeTask(medals: LiveData.FansMedalPanel.List[]) {
for (let i = 0; i < medals.length; i++) {
const medal = medals[i]
- await this.like(medal, _.random(30, 35))
+ const taskInfo = await this.fetchTaskInfo(medal.medal.target_id)
+ const item = MedalModule.findTaskInfo(taskInfo, 'like')
+ // 从 title 解析点亮所需点赞次数(如 "点赞30次" → 30),失败时 fallback 到 30
+ const baseCount = MedalModule.parseTitleCount(item?.title) ?? 30
+ await this.like(medal, _.random(baseCount, baseCount + 5))
if (i < medals.length - 1) {
await sleep(_.random(30000, 35000))
@@ -136,14 +144,18 @@ class LightTask extends MedalModule {
for (let i = 0; i < medals.length; i++) {
const medal = medals[i]
- let target = 10
+ const taskInfo = await this.fetchTaskInfo(medal.medal.target_id)
+ const item = MedalModule.findTaskInfo(taskInfo, 'sendDanmu')
+ // 从 title 解析点亮所需弹幕条数(如 "发弹幕10次" → 10),失败时 fallback 到 10
+ const baseTarget = MedalModule.parseTitleCount(item?.title) ?? 10
+ let target = baseTarget
+ const maxTarget = baseTarget + 3 // 失败补偿,最多多发 3 条
for (let j = 0; j < target; j++) {
const danmuText = this.config.danmuList[danmuIndex++ % this.config.danmuList.length]
if (!(await this.sendDanmu(medal, danmuText))) {
- // 弹幕发送失败,多尝试一次,每个直播间最多发13条
- target = Math.min(target + 1, 13)
+ target = Math.min(target + 1, maxTarget)
}
if (i < medals.length - 1 || j < target - 1) {
@@ -164,6 +176,7 @@ class LightTask extends MedalModule {
}
this.status = 'running'
+ this.resetTaskInfoCache()
const fansMedals = this.getMedals()
await Promise.allSettled([this.likeTask(fansMedals.on), this.sendDanmuTask(fansMedals.off)])
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/likeTask.ts b/src/modules/dailyTasks/liveTasks/medalTasks/likeTask.ts
new file mode 100644
index 00000000..7b36da33
--- /dev/null
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/likeTask.ts
@@ -0,0 +1,138 @@
+import { delayToNextMoment, isNowIn, isTimestampToday, tsm } from '@/library/luxon'
+import BAPI from '@/library/bili-api'
+import { useBiliStore, useModuleStore } from '@/stores'
+import { sleep } from '@/library/utils'
+import type { ModuleStatusTypes } from '@/types'
+import _ from 'lodash'
+import MedalModule from '@/modules/dailyTasks/liveTasks/medalTasks/MedalModule'
+import type { LiveData } from '@/library/bili-api/data'
+
+class LikeTask extends MedalModule {
+ config = this.medalTasksConfig.like
+
+ protected get taskConfig() {
+ return this.config
+ }
+
+ set status(s: ModuleStatusTypes) {
+ useModuleStore().moduleStatus.DailyTasks.LiveTasks.medalTasks.like = s
+ }
+
+ /**
+ * 获取已点亮且主播开播中的粉丝勋章
+ */
+ private getMedals(): LiveData.FansMedalPanel.List[] {
+ const fansMedals = useBiliStore().filteredFansMedals
+ const result = fansMedals.filter(
+ (medal) =>
+ this.PUBLIC_MEDAL_FILTERS.whiteBlackList(medal) &&
+ this.PUBLIC_MEDAL_FILTERS.levelLt120(medal) &&
+ medal.medal.is_lighted === 1 &&
+ medal.room_info.living_status === 1,
+ )
+
+ if (this.taskConfig.isWhiteList) {
+ this.sortMedals(result)
+ }
+
+ return result
+ }
+
+ /**
+ * 点赞
+ */
+ private async like(medal: LiveData.FansMedalPanel.List, click_time: number): Promise {
+ const room_id = medal.room_info.room_id
+ const target_id = medal.medal.target_id
+ const nick_name = medal.anchor_info.nick_name
+ const medal_name = medal.medal.medal_name
+ const logMessage = `粉丝勋章【${medal_name}】 给主播【${nick_name}】(UID:${target_id})的直播间(${room_id})点赞 ${click_time} 次`
+
+ try {
+ const response = await BAPI.live.likeReport(room_id, target_id, click_time)
+ this.logger.log(`BAPI.live.likeReport(${room_id}, ${target_id}, ${click_time})`, response)
+ if (response.code === 0) {
+ this.logger.log(`粉丝团点赞 ${logMessage} 成功`)
+ } else {
+ this.logger.error(`粉丝团点赞 ${logMessage} 失败`, response.message)
+ }
+ } catch (error) {
+ this.logger.error(`粉丝团点赞 ${logMessage} 出错`, error)
+ }
+ }
+
+ public async run(): Promise {
+ this.logger.log('粉丝团点赞模块开始运行')
+
+ if (!isTimestampToday(this.config._lastCompleteTime)) {
+ await this.waitForLightTask()
+
+ if (!(await this.waitForFansMedals())) {
+ this.logger.error('粉丝勋章数据不存在,不执行粉丝团点赞任务')
+ this.status = 'error'
+ return
+ }
+
+ this.status = 'running'
+ this.resetTaskInfoCache()
+ const fansMedals = this.getMedals()
+ let allCompleted = true
+
+ outer: for (let i = 0; i < fansMedals.length; i++) {
+ if (isNowIn(23, 55, 0, 5)) {
+ this.logger.log('即将或刚刚发生跨天,提早结束本轮粉丝团点赞任务')
+ allCompleted = false
+ break
+ }
+
+ const medal = fansMedals[i]
+ const taskInfo = await this.fetchTaskInfo(medal.medal.target_id)
+ if (!taskInfo) continue
+
+ const item = MedalModule.findTaskInfo(taskInfo, 'like')
+ if (!item || item.is_done) continue
+
+ const parsed = MedalModule.parseDailyLimit(item.sub_title)
+ if (!parsed || parsed.current >= parsed.limit) continue
+
+ const times = MedalModule.parseTitleCount(item.title) ?? 30
+
+ const remaining = parsed.limit - parsed.current
+ for (let j = 0; j < remaining; j++) {
+ if (isNowIn(23, 55, 0, 5)) {
+ this.logger.log('即将或刚刚发生跨天,提早结束本轮粉丝团点赞任务')
+ allCompleted = false
+ break outer
+ }
+
+ await this.like(medal, _.random(times, times + 5))
+
+ if (j < remaining - 1 || i < fansMedals.length - 1) {
+ await sleep(_.random(30000, 35000))
+ }
+ }
+ }
+
+ if (allCompleted) {
+ this.config._lastCompleteTime = tsm()
+ this.status = 'done'
+ this.logger.log('粉丝团点赞任务已完成')
+ } else {
+ this.status = ''
+ }
+ } else {
+ if (isNowIn(0, 0, 0, 5)) {
+ this.logger.log('昨天的粉丝团点赞任务已经完成过了,等到今天的00:05再执行')
+ } else {
+ this.logger.log('今天已经完成过粉丝团点赞任务了')
+ this.status = 'done'
+ }
+ }
+
+ const diff = delayToNextMoment()
+ this.nextRunTimer = setTimeout(() => this.run(), diff.ms)
+ this.logger.log('距离粉丝团点赞模块下次运行时间:', diff.str)
+ }
+}
+
+export default LikeTask
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/types.d.ts b/src/modules/dailyTasks/liveTasks/medalTasks/types.d.ts
index d2906188..79f133fb 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/types.d.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/types.d.ts
@@ -2,6 +2,11 @@ import type { LiveData } from '@/library/bili-api/data'
type LiveStatus = 'on' | 'off'
+interface MedalTaskSharedConfig {
+ isWhiteList: boolean
+ roomidList: number[]
+}
+
interface PublicMedalFilters {
whiteBlackList: (medal: LiveData.FansMedalPanel.List) => boolean
levelLt120: (medal: LiveData.FansMedalPanel.List) => boolean
@@ -14,4 +19,4 @@ interface LightTaskMedalFilters {
type MedalsByLivingStatus = Record
-export { PublicMedalFilters, LightTaskMedalFilters, MedalsByLivingStatus }
+export { MedalTaskSharedConfig, PublicMedalFilters, LightTaskMedalFilters, MedalsByLivingStatus }
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/watchTask.ts b/src/modules/dailyTasks/liveTasks/medalTasks/watchTask.ts
index 35897a4c..b938d906 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/watchTask.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/watchTask.ts
@@ -4,7 +4,7 @@ import { useBiliStore, useModuleStore, usePlayerStore } from '@/stores'
import Logger from '@/library/logger'
import CryptoJS from 'crypto-js'
import { sleep, uuid } from '@/library/utils'
-import type { ModuleConfig, ModuleStatusTypes, RunAtMoment } from '@/types'
+import type { ModuleStatusTypes, RunAtMoment } from '@/types'
import _ from 'lodash'
import MedalModule from '@/modules/dailyTasks/liveTasks/medalTasks/MedalModule'
import type { LiveData } from '@/library/bili-api/data'
@@ -26,20 +26,18 @@ class RoomHeart {
parentID: number,
ruid: number,
watchedSeconds: number,
+ targetSeconds: number,
) {
this.roomID = roomID
this.areaID = areaID
this.parentID = parentID
this.ruid = ruid
this.watchedSeconds = watchedSeconds
-
- this.config = useModuleStore().moduleConfig.DailyTasks.LiveTasks.medalTasks.watch
+ this.targetSeconds = targetSeconds
}
private logger = new Logger('RoomHeart')
- private config: ModuleConfig['DailyTasks']['LiveTasks']['medalTasks']['watch']
-
set status(s: ModuleStatusTypes) {
useModuleStore().moduleStatus.DailyTasks.LiveTasks.medalTasks.watch = s
}
@@ -47,6 +45,9 @@ class RoomHeart {
/** 今日当前直播间已观看时间(秒) */
private watchedSeconds: number
+ /** 当前直播间观看目标秒数 */
+ private readonly targetSeconds: number
+
private readonly areaID: number
private readonly parentID: number
private readonly roomID: number
@@ -166,8 +167,8 @@ class RoomHeart {
if (response.code === 0) {
this.seq += 1
this.updateProgress()
- if (this.watchedSeconds >= this.config.time * 60) {
- // 达到设置的观看时间,结束
+ if (this.watchedSeconds >= this.targetSeconds) {
+ // 达到目标观看时间,结束
return
}
;({
@@ -249,6 +250,10 @@ class WatchTask extends MedalModule {
config = this.medalTasksConfig.watch
+ protected get taskConfig() {
+ return this.config
+ }
+
set status(s: ModuleStatusTypes) {
useModuleStore().moduleStatus.DailyTasks.LiveTasks.medalTasks.watch = s
}
@@ -256,8 +261,8 @@ class WatchTask extends MedalModule {
private playerStore = usePlayerStore()
/**
- * 获取粉丝勋章
- * @returns 根据直播状态划分、经过排序和过滤的粉丝勋章
+ * 获取已点亮的粉丝勋章
+ * @returns 经过排序和过滤的粉丝勋章
*/
private getMedals(): LiveData.FansMedalPanel.List[] {
const fansMedals = useBiliStore().filteredFansMedals
@@ -265,10 +270,11 @@ class WatchTask extends MedalModule {
const result = fansMedals.filter(
(medal) =>
this.PUBLIC_MEDAL_FILTERS.whiteBlackList(medal) &&
- this.PUBLIC_MEDAL_FILTERS.levelLt120(medal),
+ this.PUBLIC_MEDAL_FILTERS.levelLt120(medal) &&
+ medal.medal.is_lighted === 1,
)
- if (this.medalTasksConfig.isWhiteList) {
+ if (this.taskConfig.isWhiteList) {
this.sortMedals(result)
}
@@ -327,6 +333,8 @@ class WatchTask extends MedalModule {
})
if (!isTimestampToday(this.config._lastCompleteTime)) {
+ await this.waitForLightTask()
+
if (!(await this.waitForFansMedals())) {
this.logger.error('粉丝勋章数据不存在,不执行观看直播任务')
this.status = 'error'
@@ -334,6 +342,7 @@ class WatchTask extends MedalModule {
}
this.status = 'running'
+ this.resetTaskInfoCache()
if (!isTimestampToday(this.config._lastWatchTime, 0, 0)) {
// 如果上次观看(不是完成任务)的时间戳不在今天,将今天已观看的秒数置为0
@@ -360,27 +369,43 @@ class WatchTask extends MedalModule {
const medal = fansMedals[i]
const roomid = medal.room_info.room_id
const uid = medal.medal.target_id
+
+ const taskInfo = await this.fetchTaskInfo(uid)
+ if (!taskInfo) continue
+
+ const item = MedalModule.findTaskInfo(taskInfo, 'watchLive')
+ if (!item) continue
+
+ const parsed = MedalModule.parseDailyLimit(item.sub_title)
+ if (!parsed) continue
+ if (item.is_done || parsed.current >= parsed.limit) continue
+
+ const minutes = MedalModule.parseTitleCount(item.title) ?? 15
+
+ // 目标秒数 = 每日上限 × minutes 分钟 + 60s 缓冲(补偿心跳计时误差)
+ const targetSeconds = parsed.limit * minutes * 60 + 60
+
+ if ((this.config._watchingProgress[roomid] ?? 0) >= targetSeconds) {
+ // 今日观看时间已达到目标值,跳过
+ continue
+ }
+
const [area_id, parent_area_id] = await this.getAreaInfo(medal.room_info.url, roomid)
if (area_id > 0 && parent_area_id > 0) {
// area_id 和 parent_area_id 都大于 0,说明直播间设置了分区,心跳有效
- if (
- !this.config._watchingProgress[roomid] ||
- this.config._watchingProgress[roomid] < this.config.time * 60
- ) {
- // 今日观看时间未达到设置值,开始心跳
- this.logger.log(
- `粉丝勋章【${medal.medal.medal_name}】 开始直播间 ${roomid}(主播【${medal.anchor_info.nick_name}】,UID:${uid})的观看直播任务`,
- )
-
- await new RoomHeart(
- roomid,
- area_id,
- parent_area_id,
- uid,
- this.config._watchingProgress[roomid] ?? 0,
- ).start()
- }
+ this.logger.log(
+ `粉丝勋章【${medal.medal.medal_name}】 开始直播间 ${roomid}(主播【${medal.anchor_info.nick_name}】,UID:${uid})的观看直播任务,目标时长 ${targetSeconds} 秒`,
+ )
+
+ await new RoomHeart(
+ roomid,
+ area_id,
+ parent_area_id,
+ uid,
+ this.config._watchingProgress[roomid] ?? 0,
+ targetSeconds,
+ ).start()
}
}
diff --git a/src/modules/default/fansMedals.ts b/src/modules/default/fansMedals.ts
index 43da4315..598c534c 100644
--- a/src/modules/default/fansMedals.ts
+++ b/src/modules/default/fansMedals.ts
@@ -53,7 +53,7 @@ class FansMedals extends BaseModule {
const biliStore = useBiliStore()
const medalTasks = useModuleStore().moduleConfig.DailyTasks.LiveTasks.medalTasks
- const taskValues = [medalTasks.light, medalTasks.watch]
+ const taskValues = [medalTasks.light, medalTasks.like, medalTasks.danmu, medalTasks.watch]
if (
(force ||
diff --git a/src/stores/useModuleStore.ts b/src/stores/useModuleStore.ts
index aaeb3560..dd6476a9 100644
--- a/src/stores/useModuleStore.ts
+++ b/src/stores/useModuleStore.ts
@@ -26,6 +26,8 @@ const defaultModuleStatus: ModuleStatus = {
LiveTasks: {
medalTasks: {
light: '',
+ like: '',
+ danmu: '',
watch: '',
},
},
@@ -98,6 +100,20 @@ export const useModuleStore = defineStore('module', () => {
rerunModule('Default_FansMedals', true)
rerunModule('DailyTask_LiveTask_LightTask')
},
+ like: () => {
+ moduleStatus.value.DailyTasks.LiveTasks.medalTasks.like = ''
+ moduleConfig.value.DailyTasks.LiveTasks.medalTasks.like._lastCompleteTime = 0
+
+ rerunModule('Default_FansMedals', true)
+ rerunModule('DailyTask_LiveTask_LikeTask')
+ },
+ danmu: () => {
+ moduleStatus.value.DailyTasks.LiveTasks.medalTasks.danmu = ''
+ moduleConfig.value.DailyTasks.LiveTasks.medalTasks.danmu._lastCompleteTime = 0
+
+ rerunModule('Default_FansMedals', true)
+ rerunModule('DailyTask_LiveTask_DanmuTask')
+ },
watch: () => {
moduleStatus.value.DailyTasks.LiveTasks.medalTasks.watch = ''
moduleConfig.value.DailyTasks.LiveTasks.medalTasks.watch._lastCompleteTime = 0
diff --git a/src/types/helpInfo.d.ts b/src/types/helpInfo.d.ts
index 028f9229..b202f84b 100644
--- a/src/types/helpInfo.d.ts
+++ b/src/types/helpInfo.d.ts
@@ -17,6 +17,8 @@ interface HelpInfo {
medalTasks: {
list: HelpInfoItem
light: HelpInfoItem
+ like: HelpInfoItem
+ danmu: HelpInfoItem
watch: HelpInfoItem
}
}
diff --git a/src/types/module.d.ts b/src/types/module.d.ts
index a5595263..e722c2e2 100644
--- a/src/types/module.d.ts
+++ b/src/types/module.d.ts
@@ -20,6 +20,8 @@ interface ModulesNeedStatus {
LiveTasks: {
medalTasks: {
light: T
+ like: T
+ danmu: T
watch: T
}
}
diff --git a/src/types/storage.d.ts b/src/types/storage.d.ts
index d05d3ded..47c7ca6b 100644
--- a/src/types/storage.d.ts
+++ b/src/types/storage.d.ts
@@ -24,17 +24,32 @@ interface ModuleConfig {
light: {
enabled: boolean
danmuList: string[]
+ isWhiteList: boolean
+ roomidList: number[]
+ _lastCompleteTime: number
+ }
+ like: {
+ enabled: boolean
+ isWhiteList: boolean
+ roomidList: number[]
+ _lastCompleteTime: number
+ }
+ danmu: {
+ enabled: boolean
+ danmuList: string[]
+ onlyWhenNotLiving: boolean
+ isWhiteList: boolean
+ roomidList: number[]
_lastCompleteTime: number
}
watch: {
enabled: boolean
- time: number
+ isWhiteList: boolean
+ roomidList: number[]
_watchingProgress: Record
_lastWatchTime: number
_lastCompleteTime: number
}
- isWhiteList: boolean
- roomidList: number[]
}
}
OtherTasks: {
From f65bae94ce32c18410d577f4b341fd8dcee9a807 Mon Sep 17 00:00:00 2001
From: andywang425 <56548456+andywang425@users.noreply.github.com>
Date: Wed, 20 May 2026 22:11:16 +0800
Subject: [PATCH 10/27] =?UTF-8?q?=F0=9F=90=B3=20chore:=20UI=E7=9B=B8?=
=?UTF-8?q?=E5=85=B3=E4=BC=98=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 4种粉丝团任务的黑白名单弹窗有独立的“是否为排序模式”的UiConfig
- InfoIcon弹窗加宽
- 内置文档和日志文本优化
- 点亮熄灭勋章danmuList默认值调整
---
src/components/LiveTasks.vue | 89 ++++++++++---------
src/components/icons/InfoIcon.vue | 1 +
src/library/help-info/index.ts | 51 ++++++-----
src/library/storage/defaultValues.ts | 37 ++++----
.../liveTasks/medalTasks/danmuTask.ts | 24 ++---
.../liveTasks/medalTasks/likeTask.ts | 22 ++---
src/types/storage.d.ts | 7 +-
7 files changed, 128 insertions(+), 103 deletions(-)
diff --git a/src/components/LiveTasks.vue b/src/components/LiveTasks.vue
index e88700fb..8dd6bc75 100644
--- a/src/components/LiveTasks.vue
+++ b/src/components/LiveTasks.vue
@@ -1,8 +1,8 @@
@@ -215,20 +214,21 @@ function handleRowClick(row: MedalInfoRow) {
v-model="config.medalTasks.light.isWhiteList"
active-text="白名单"
inactive-text="黑名单"
- @change="(val) => !val && (uiStore.uiConfig.medalInfoPanelSortMode = false)"
+ @change="(val) => !val && (uiStore.uiConfig.medalInfoPanelIsSortMode.light = false)"
/>
编辑名单
+
-
+
-
+
@@ -239,24 +239,21 @@ function handleRowClick(row: MedalInfoRow) {
v-model="config.medalTasks.like.isWhiteList"
active-text="白名单"
inactive-text="黑名单"
- @change="(val) => !val && (uiStore.uiConfig.medalInfoPanelSortMode = false)"
+ @change="(val) => !val && (uiStore.uiConfig.medalInfoPanelIsSortMode.like = false)"
/>
编辑名单
+
-
+
-
-
+
编辑弹幕
@@ -264,17 +261,27 @@ function handleRowClick(row: MedalInfoRow) {
+
+
+
+
+
+
!val && (uiStore.uiConfig.medalInfoPanelSortMode = false)"
+ @change="(val) => !val && (uiStore.uiConfig.medalInfoPanelIsSortMode.danmu = false)"
/>
编辑名单
+
@@ -294,7 +301,7 @@ function handleRowClick(row: MedalInfoRow) {
v-model="config.medalTasks.watch.isWhiteList"
active-text="白名单"
inactive-text="黑名单"
- @change="(val) => !val && (uiStore.uiConfig.medalInfoPanelSortMode = false)"
+ @change="(val) => !val && (uiStore.uiConfig.medalInfoPanelIsSortMode.watch = false)"
/>
编辑名单
@@ -354,7 +361,7 @@ function handleRowClick(row: MedalInfoRow) {
-
+
@@ -418,7 +425,7 @@ function handleRowClick(row: MedalInfoRow) {
{
lockScroll: false,
autofocus: true,
confirmButtonText: 'OK',
+ customStyle: { '--el-messagebox-width': '28vw' },
}).catch(() => {})
}
diff --git a/src/library/help-info/index.ts b/src/library/help-info/index.ts
index 7ef6e393..f9119b09 100644
--- a/src/library/help-info/index.ts
+++ b/src/library/help-info/index.ts
@@ -35,7 +35,7 @@ const helpInfo: HelpInfo = {
list: {
title: '黑白名单 / 排序模式',
message: h('p', [
- h('div', '每个直播任务都有自己独立的黑名单或白名单,互不影响。'),
+ h('div', '每个直播任务都有自己独立的黑名单或白名单。'),
h('ul', [
h('li', [
h('span', '黑名单:仅为'),
@@ -70,18 +70,19 @@ const helpInfo: HelpInfo = {
'li',
h('p', [
h('strong', '点赞'),
- h('div', '对于正在直播的房间,给直播间点赞约30次。'),
- h('ul', [
- h('li', [h('span', '点赞次数为略微超过任务要求的随机值。')]),
- h('li', [h('span', '部分直播间无法完成该任务,原因未知。')]),
- ]),
+ h('div', '对于正在直播的房间,给直播间点赞。'),
+ h('div', '脚本会根据 B 站返回的任务要求自动决定点赞次数。'),
]),
),
h(
'li',
h('p', [
h('strong', '发送弹幕'),
- h('div', '对于未开播的房间,发送十条弹幕。'),
+ h('div', '对于未开播的房间,发送数条弹幕。'),
+ h(
+ 'div',
+ '脚本会根据 B 站返回的任务要求自动决定发送条数。发送失败时会自动小幅补发。',
+ ),
h('div', [
h(
'div',
@@ -110,28 +111,37 @@ const helpInfo: HelpInfo = {
]),
),
]),
+ h('div', [
+ h('strong', '注意:'),
+ h(
+ 'span',
+ '如果你启用了本功能,点赞、发弹幕和观看直播任务都会等点亮熄灭勋章任务完成后再执行。',
+ ),
+ ]),
]),
},
like: {
- title: '粉丝团点赞',
+ title: '点赞',
message: h('p', [
- h('div', '为已点亮的粉丝勋章完成粉丝团每日点赞任务。'),
+ h('div', '为已点亮的粉丝勋章完成粉丝团升级任务中的点赞任务。'),
h('ul', [
h('li', '只有主播开播时点赞才会计入任务进度,因此只会给开播中的直播间点赞。'),
h(
'li',
- '脚本会根据 B 站返回的每日上限自动决定点赞次数,每个等级的勋章上限不同(如 2/2、10/10)。',
+ '脚本会根据 B 站返回的任务要求自动决定点赞次数。高等级的粉丝勋章拥有更高的任务上限,因此能获得更多的亲密度。',
),
- h('li', '每个直播间分多轮点赞,每轮约 30 次,轮间间隔约 30 秒。'),
]),
]),
},
danmu: {
- title: '粉丝团发弹幕',
+ title: '发弹幕',
message: h('p', [
- h('div', '为已点亮的粉丝勋章完成粉丝团每日发弹幕任务。'),
+ h('div', '为已点亮的粉丝勋章完成粉丝团升级任务中的发弹幕任务。'),
h('ul', [
- h('li', '脚本会根据 B 站返回的每日上限自动决定发送条数,每个等级的勋章上限不同。'),
+ h(
+ 'li',
+ '脚本会根据 B 站返回的任务要求自动决定发送条数。高等级的粉丝勋章拥有更高的任务上限,因此能获得更多的亲密度。',
+ ),
h(
'li',
'点击【编辑弹幕】按钮编辑要发送的弹幕,脚本会从中按顺序循环抽取弹幕发送。建议修改默认的弹幕。',
@@ -140,21 +150,18 @@ const helpInfo: HelpInfo = {
'li',
'若担心直播中刷屏被禁言,可开启【仅在未开播的直播间发弹幕】,此时会跳过当前正在直播的直播间。',
),
- h('li', '弹幕被屏蔽词拦截或失败会自动小幅补发。'),
+ h('li', '弹幕发送失败(比如被屏蔽词拦截)会自动小幅补发。'),
]),
]),
},
watch: {
title: '观看直播',
message: h('p', [
- h('div', '完成粉丝勋章的观看直播任务。'),
+ h('div', '为已点亮的粉丝勋章完成完成粉丝团升级任务中的观看直播任务。'),
h('ul', [
h('li', '部分直播间因为没有设置直播分区导致任务无法完成。'),
h('li', '主播当前是否开播不会影响该任务的完成。'),
- h(
- 'li',
- '脚本会根据 B 站返回的每日上限自动计算观看时长,每观看 15 分钟领取一档亲密度,并附加约 1 分钟缓冲以补偿计时误差。',
- ),
+ h('li', '脚本会根据 B 站返回的任务要求自动计算观看时长。'),
]),
h('div', [
h('strong', '注意:'),
@@ -172,14 +179,14 @@ const helpInfo: HelpInfo = {
title: '银瓜子换硬币',
message: h('p', [
h('div', '把银瓜子兑换为硬币。'),
- h('div', '具体兑换规则请点击直播间页面的“立即充值→银瓜子商店”查看。'),
+ h('div', '具体兑换规则请点击直播间页面右下角的“电池图标→银瓜子商店”查看。'),
]),
},
coinToSilver: {
title: '硬币换银瓜子',
message: h('p', [
h('div', '把硬币兑换为银瓜子。'),
- h('div', '具体兑换规则请点击直播间页面的“立即充值→银瓜子商店”查看。'),
+ h('div', '具体兑换规则请点击直播间页面右下角的“电池图标→银瓜子商店”查看。'),
]),
},
getYearVipPrivilege: {
diff --git a/src/library/storage/defaultValues.ts b/src/library/storage/defaultValues.ts
index 8fb11ffa..873710f0 100644
--- a/src/library/storage/defaultValues.ts
+++ b/src/library/storage/defaultValues.ts
@@ -12,7 +12,12 @@ const defaultValues: DefaultValues = {
isShowPanel: true,
activeMenuIndex: 'MainSiteTasks',
panelWidthPercent: 45,
- medalInfoPanelSortMode: false,
+ medalInfoPanelIsSortMode: {
+ light: false,
+ like: false,
+ danmu: false,
+ watch: false,
+ },
},
modules: {
DailyTasks: {
@@ -40,21 +45,21 @@ const defaultValues: DefaultValues = {
light: {
enabled: false,
danmuList: [
- '(⌒▽⌒)',
- '( ̄▽ ̄)',
- '(=・ω・=)',
- '(`・ω・´)',
- '(〜 ̄△ ̄)〜',
- '(・∀・)',
- '(°∀°)ノ',
- '╮( ̄▽ ̄)╭',
- '_(:3」∠)_',
- '(^・ω・^ )',
- '(● ̄(エ) ̄●)',
- 'ε=ε=(ノ≧∇≦)ノ',
- '⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄',
- '←◡←',
- `(●'◡'●)ノ♥`,
+ '[dog]',
+ '[花]',
+ '[妙]',
+ '[哇]',
+ '[爱]',
+ '[比心]',
+ '[笑哭]',
+ '[捂脸]',
+ '[喝彩]',
+ '[大笑]',
+ '[惊喜]',
+ '[OK]',
+ '[哈欠]',
+ '[墨镜]',
+ `[牛]`,
],
isWhiteList: false,
roomidList: [],
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/danmuTask.ts b/src/modules/dailyTasks/liveTasks/medalTasks/danmuTask.ts
index 615bd634..f9b22b86 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/danmuTask.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/danmuTask.ts
@@ -54,29 +54,29 @@ class DanmuTask extends MedalModule {
this.logger.log(`BAPI.live.sendMsg(${danmu}, ${room_id})`, response)
if (response.code === 0) {
if (response.msg === 'k') {
- this.logger.warn(`粉丝团发弹幕 ${logMessage} 异常,弹幕可能包含屏蔽词`)
+ this.logger.warn(`发弹幕 ${logMessage} 异常,弹幕可能包含屏蔽词`)
} else {
- this.logger.log(`粉丝团发弹幕 ${logMessage} 成功`)
+ this.logger.log(`发弹幕 ${logMessage} 成功`)
return true
}
} else {
- this.logger.error(`粉丝团发弹幕 ${logMessage} 失败`, response.message)
+ this.logger.error(`发弹幕 ${logMessage} 失败`, response.message)
}
} catch (error) {
- this.logger.error(`粉丝团发弹幕 ${logMessage} 出错`, error)
+ this.logger.error(`发弹幕 ${logMessage} 出错`, error)
}
return false
}
public async run(): Promise {
- this.logger.log('粉丝团发弹幕模块开始运行')
+ this.logger.log('发弹幕模块开始运行')
if (!isTimestampToday(this.config._lastCompleteTime)) {
await this.waitForLightTask()
if (!(await this.waitForFansMedals())) {
- this.logger.error('粉丝勋章数据不存在,不执行粉丝团发弹幕任务')
+ this.logger.error('粉丝勋章数据不存在,不执行发弹幕任务')
this.status = 'error'
return
}
@@ -89,7 +89,7 @@ class DanmuTask extends MedalModule {
outer: for (let i = 0; i < fansMedals.length; i++) {
if (isNowIn(23, 55, 0, 5)) {
- this.logger.log('即将或刚刚发生跨天,提早结束本轮粉丝团发弹幕任务')
+ this.logger.log('即将或刚刚发生跨天,提早结束本轮发弹幕任务')
allCompleted = false
break
}
@@ -111,7 +111,7 @@ class DanmuTask extends MedalModule {
for (let j = 0; j < target; j++) {
if (isNowIn(23, 55, 0, 5)) {
- this.logger.log('即将或刚刚发生跨天,提早结束本轮粉丝团发弹幕任务')
+ this.logger.log('即将或刚刚发生跨天,提早结束本轮发弹幕任务')
allCompleted = false
break outer
}
@@ -131,22 +131,22 @@ class DanmuTask extends MedalModule {
if (allCompleted) {
this.config._lastCompleteTime = tsm()
this.status = 'done'
- this.logger.log('粉丝团发弹幕任务已完成')
+ this.logger.log('发弹幕任务已完成')
} else {
this.status = ''
}
} else {
if (isNowIn(0, 0, 0, 5)) {
- this.logger.log('昨天的粉丝团发弹幕任务已经完成过了,等到今天的00:05再执行')
+ this.logger.log('昨天的发弹幕任务已经完成过了,等到今天的00:05再执行')
} else {
- this.logger.log('今天已经完成过粉丝团发弹幕任务了')
+ this.logger.log('今天已经完成过发弹幕任务了')
this.status = 'done'
}
}
const diff = delayToNextMoment()
this.nextRunTimer = setTimeout(() => this.run(), diff.ms)
- this.logger.log('距离粉丝团发弹幕模块下次运行时间:', diff.str)
+ this.logger.log('距离发弹幕模块下次运行时间:', diff.str)
}
}
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/likeTask.ts b/src/modules/dailyTasks/liveTasks/medalTasks/likeTask.ts
index 7b36da33..b1a32b52 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/likeTask.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/likeTask.ts
@@ -52,23 +52,23 @@ class LikeTask extends MedalModule {
const response = await BAPI.live.likeReport(room_id, target_id, click_time)
this.logger.log(`BAPI.live.likeReport(${room_id}, ${target_id}, ${click_time})`, response)
if (response.code === 0) {
- this.logger.log(`粉丝团点赞 ${logMessage} 成功`)
+ this.logger.log(`点赞 ${logMessage} 成功`)
} else {
- this.logger.error(`粉丝团点赞 ${logMessage} 失败`, response.message)
+ this.logger.error(`点赞 ${logMessage} 失败`, response.message)
}
} catch (error) {
- this.logger.error(`粉丝团点赞 ${logMessage} 出错`, error)
+ this.logger.error(`点赞 ${logMessage} 出错`, error)
}
}
public async run(): Promise {
- this.logger.log('粉丝团点赞模块开始运行')
+ this.logger.log('点赞模块开始运行')
if (!isTimestampToday(this.config._lastCompleteTime)) {
await this.waitForLightTask()
if (!(await this.waitForFansMedals())) {
- this.logger.error('粉丝勋章数据不存在,不执行粉丝团点赞任务')
+ this.logger.error('粉丝勋章数据不存在,不执行点赞任务')
this.status = 'error'
return
}
@@ -80,7 +80,7 @@ class LikeTask extends MedalModule {
outer: for (let i = 0; i < fansMedals.length; i++) {
if (isNowIn(23, 55, 0, 5)) {
- this.logger.log('即将或刚刚发生跨天,提早结束本轮粉丝团点赞任务')
+ this.logger.log('即将或刚刚发生跨天,提早结束本轮点赞任务')
allCompleted = false
break
}
@@ -100,7 +100,7 @@ class LikeTask extends MedalModule {
const remaining = parsed.limit - parsed.current
for (let j = 0; j < remaining; j++) {
if (isNowIn(23, 55, 0, 5)) {
- this.logger.log('即将或刚刚发生跨天,提早结束本轮粉丝团点赞任务')
+ this.logger.log('即将或刚刚发生跨天,提早结束本轮点赞任务')
allCompleted = false
break outer
}
@@ -116,22 +116,22 @@ class LikeTask extends MedalModule {
if (allCompleted) {
this.config._lastCompleteTime = tsm()
this.status = 'done'
- this.logger.log('粉丝团点赞任务已完成')
+ this.logger.log('点赞任务已完成')
} else {
this.status = ''
}
} else {
if (isNowIn(0, 0, 0, 5)) {
- this.logger.log('昨天的粉丝团点赞任务已经完成过了,等到今天的00:05再执行')
+ this.logger.log('昨天的点赞任务已经完成过了,等到今天的00:05再执行')
} else {
- this.logger.log('今天已经完成过粉丝团点赞任务了')
+ this.logger.log('今天已经完成过点赞任务了')
this.status = 'done'
}
}
const diff = delayToNextMoment()
this.nextRunTimer = setTimeout(() => this.run(), diff.ms)
- this.logger.log('距离粉丝团点赞模块下次运行时间:', diff.str)
+ this.logger.log('距离点赞模块下次运行时间:', diff.str)
}
}
diff --git a/src/types/storage.d.ts b/src/types/storage.d.ts
index 47c7ca6b..c1482ecb 100644
--- a/src/types/storage.d.ts
+++ b/src/types/storage.d.ts
@@ -133,7 +133,12 @@ interface UiConfig {
isShowPanel: boolean
activeMenuIndex: MenuIndex
panelWidthPercent: number
- medalInfoPanelSortMode: boolean
+ medalInfoPanelIsSortMode: {
+ light: boolean
+ like: boolean
+ danmu: boolean
+ watch: boolean
+ }
}
interface Cache {
From 5ceb33109344d8757e1424ec69ccbba91c1fdcb7 Mon Sep 17 00:00:00 2001
From: andywang425 <56548456+andywang425@users.noreply.github.com>
Date: Thu, 21 May 2026 15:36:01 +0800
Subject: [PATCH 11/27] =?UTF-8?q?=F0=9F=8E=88=20perf:=20=E7=B2=89=E4=B8=9D?=
=?UTF-8?q?=E5=8B=8B=E7=AB=A0=E4=BB=BB=E5=8A=A1=E4=BB=A3=E7=A0=81=E4=BC=98?=
=?UTF-8?q?=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 去除taskConfig抽象
- 提取公共粉丝勋章过滤器SHARED_MEDAL_FILTERS
---
.../liveTasks/medalTasks/MedalModule.ts | 30 ++++++-------
.../liveTasks/medalTasks/danmuTask.ts | 14 +++---
.../liveTasks/medalTasks/lightTask.ts | 45 ++++++++-----------
.../liveTasks/medalTasks/likeTask.ts | 14 +++---
.../liveTasks/medalTasks/types.d.ts | 25 +++++------
.../liveTasks/medalTasks/watchTask.ts | 13 ++----
6 files changed, 58 insertions(+), 83 deletions(-)
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/MedalModule.ts b/src/modules/dailyTasks/liveTasks/medalTasks/MedalModule.ts
index 247d4a9b..0cc102b5 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/MedalModule.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/MedalModule.ts
@@ -2,7 +2,7 @@ import BaseModule from '@/modules/BaseModule'
import { storeToRefs } from 'pinia'
import { useBiliStore, useModuleStore } from '@/stores'
import { watch } from 'vue'
-import type { MedalTaskSharedConfig, PublicMedalFilters } from './types'
+import type { MedalTaskSharedConfig, SharedMedalFilters } from './types'
import { arrayToMap, sleep } from '@/library/utils'
import type { LiveData } from '@/library/bili-api/data'
import BAPI from '@/library/bili-api'
@@ -14,25 +14,25 @@ type JumpType = 'like' | 'sendDanmu' | 'watchLive' | 'feedLight' | 'sendGift'
class MedalModule extends BaseModule {
medalTasksConfig = useModuleStore().moduleConfig.DailyTasks.LiveTasks.medalTasks
- /**
- * 子类需要重写此 getter,返回该任务对应的 `isWhiteList` 与 `roomidList`
- */
- protected get taskConfig(): MedalTaskSharedConfig {
- throw new Error('Method not implemented.')
- }
+ /** 所有粉丝勋章任务的公共配置 */
+ declare protected config: MedalTaskSharedConfig
- protected PUBLIC_MEDAL_FILTERS: PublicMedalFilters = {
+ protected SHARED_MEDAL_FILTERS: SharedMedalFilters = {
// 包含在白名单中或不包含在黑名单中返回true,否则返回false
- whiteBlackList: (m) =>
- this.taskConfig.isWhiteList
- ? this.taskConfig.roomidList.includes(m.room_info.room_id)
- : !this.taskConfig.roomidList.includes(m.room_info.room_id),
+ meetWhiteOrBlackList: (m) =>
+ this.config.isWhiteList
+ ? this.config.roomidList.includes(m.room_info.room_id)
+ : !this.config.roomidList.includes(m.room_info.room_id),
// 等级小于120返回true,否则返回false
levelLt120: (medal) => medal.medal.level < 120,
+ // 点亮返回true,否则返回false
+ isLighted: (medal) => medal.medal.is_lighted === 1,
+ // 直播中返回true,否则返回false
+ isLiving: (medal) => medal.room_info.living_status === 1,
}
protected sortMedals(medals: LiveData.FansMedalPanel.List[]): LiveData.FansMedalPanel.List[] {
- const orderMap = arrayToMap(this.taskConfig.roomidList)
+ const orderMap = arrayToMap(this.config.roomidList)
return medals.sort(
(a, b) => orderMap.get(a.room_info.room_id)! - orderMap.get(b.room_info.room_id)!,
)
@@ -146,9 +146,9 @@ class MedalModule extends BaseModule {
}
/**
- * 若点亮熄灭勋章任务已启用且今天未完成,等待其结束后再继续。
+ * 若点亮熄灭勋章任务已启用且今天未完成,等待其结束后再继续
*
- * 用于让 like/danmu/watch 在 light 任务完成后再执行,避免与点亮逻辑产生竞争。
+ * 用于让 点赞/发弹幕/观看直播 在 点亮熄灭勋章 任务完成后再执行
*/
protected async waitForLightTask(): Promise {
const lightConfig = this.medalTasksConfig.light
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/danmuTask.ts b/src/modules/dailyTasks/liveTasks/medalTasks/danmuTask.ts
index f9b22b86..ef17c447 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/danmuTask.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/danmuTask.ts
@@ -10,10 +10,6 @@ import type { LiveData } from '@/library/bili-api/data'
class DanmuTask extends MedalModule {
config = this.medalTasksConfig.danmu
- protected get taskConfig() {
- return this.config
- }
-
set status(s: ModuleStatusTypes) {
useModuleStore().moduleStatus.DailyTasks.LiveTasks.medalTasks.danmu = s
}
@@ -25,13 +21,13 @@ class DanmuTask extends MedalModule {
const fansMedals = useBiliStore().filteredFansMedals
const result = fansMedals.filter(
(medal) =>
- this.PUBLIC_MEDAL_FILTERS.whiteBlackList(medal) &&
- this.PUBLIC_MEDAL_FILTERS.levelLt120(medal) &&
- medal.medal.is_lighted === 1 &&
- (!this.config.onlyWhenNotLiving || medal.room_info.living_status !== 1),
+ this.SHARED_MEDAL_FILTERS.meetWhiteOrBlackList(medal) &&
+ this.SHARED_MEDAL_FILTERS.levelLt120(medal) &&
+ this.SHARED_MEDAL_FILTERS.isLighted(medal) &&
+ (!this.config.onlyWhenNotLiving || !this.SHARED_MEDAL_FILTERS.isLiving(medal)),
)
- if (this.taskConfig.isWhiteList) {
+ if (this.config.isWhiteList) {
this.sortMedals(result)
}
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts b/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts
index f3331489..0851d98f 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts
@@ -5,53 +5,44 @@ import { sleep } from '@/library/utils'
import type { ModuleStatusTypes } from '@/types'
import _ from 'lodash'
import MedalModule from '@/modules/dailyTasks/liveTasks/medalTasks/MedalModule'
-import type { LightTaskMedalFilters, MedalsByLivingStatus } from './types'
import type { LiveData } from '@/library/bili-api/data'
+type MedalsByLiving = [LiveData.FansMedalPanel.List[], LiveData.FansMedalPanel.List[]]
+
class LightTask extends MedalModule {
config = this.medalTasksConfig.light
- protected get taskConfig() {
- return this.config
- }
-
set status(s: ModuleStatusTypes) {
useModuleStore().moduleStatus.DailyTasks.LiveTasks.medalTasks.light = s
}
- private MEDAL_FILTERS: LightTaskMedalFilters = {
- // 点亮返回true,否则返回false
- isLighted: (medal) => medal.medal.is_lighted === 1,
- // 直播中返回on,否则返回off
- livingStatus: (medal) => (medal.room_info.living_status === 1 ? 'on' : 'off'),
- }
-
/**
- * 获取粉丝勋章
- * @returns 根据直播状态划分、经过排序和过滤的粉丝勋章
+ * 获取未点亮的粉丝勋章
+ *
+ * @returns [[主播未开播的粉丝勋章], [主播开播中的粉丝勋章]]
*/
- private getMedals(): MedalsByLivingStatus {
+ private getMedals(): MedalsByLiving {
const fansMedals = useBiliStore().filteredFansMedals
-
- const result: MedalsByLivingStatus = {
- on: [],
- off: [],
- }
+ const result: MedalsByLiving = [[], []]
fansMedals.forEach((medal) => {
- if (!this.PUBLIC_MEDAL_FILTERS.whiteBlackList(medal) || this.MEDAL_FILTERS.isLighted(medal)) {
+ if (
+ !this.SHARED_MEDAL_FILTERS.meetWhiteOrBlackList(medal) ||
+ this.SHARED_MEDAL_FILTERS.isLighted(medal)
+ ) {
// 跳过被黑白名单过滤的和已经点亮的粉丝勋章
return
}
- const livingStatus = this.MEDAL_FILTERS.livingStatus(medal)
- result[livingStatus].push(medal)
+ // 根据直播状态划分
+ const index = this.SHARED_MEDAL_FILTERS.isLiving(medal) ? 1 : 0
+ result[index].push(medal)
})
- if (this.taskConfig.isWhiteList) {
+ if (this.config.isWhiteList) {
// 白名单排序
- this.sortMedals(result.on)
- this.sortMedals(result.off)
+ this.sortMedals(result[0])
+ this.sortMedals(result[1])
}
return result
@@ -179,7 +170,7 @@ class LightTask extends MedalModule {
this.resetTaskInfoCache()
const fansMedals = this.getMedals()
- await Promise.allSettled([this.likeTask(fansMedals.on), this.sendDanmuTask(fansMedals.off)])
+ await Promise.allSettled([this.sendDanmuTask(fansMedals[0]), this.likeTask(fansMedals[1])])
this.config._lastCompleteTime = tsm()
this.status = 'done'
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/likeTask.ts b/src/modules/dailyTasks/liveTasks/medalTasks/likeTask.ts
index b1a32b52..c3b0769d 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/likeTask.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/likeTask.ts
@@ -10,10 +10,6 @@ import type { LiveData } from '@/library/bili-api/data'
class LikeTask extends MedalModule {
config = this.medalTasksConfig.like
- protected get taskConfig() {
- return this.config
- }
-
set status(s: ModuleStatusTypes) {
useModuleStore().moduleStatus.DailyTasks.LiveTasks.medalTasks.like = s
}
@@ -25,13 +21,13 @@ class LikeTask extends MedalModule {
const fansMedals = useBiliStore().filteredFansMedals
const result = fansMedals.filter(
(medal) =>
- this.PUBLIC_MEDAL_FILTERS.whiteBlackList(medal) &&
- this.PUBLIC_MEDAL_FILTERS.levelLt120(medal) &&
- medal.medal.is_lighted === 1 &&
- medal.room_info.living_status === 1,
+ this.SHARED_MEDAL_FILTERS.meetWhiteOrBlackList(medal) &&
+ this.SHARED_MEDAL_FILTERS.levelLt120(medal) &&
+ this.SHARED_MEDAL_FILTERS.isLighted(medal) &&
+ this.SHARED_MEDAL_FILTERS.isLiving(medal),
)
- if (this.taskConfig.isWhiteList) {
+ if (this.config.isWhiteList) {
this.sortMedals(result)
}
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/types.d.ts b/src/modules/dailyTasks/liveTasks/medalTasks/types.d.ts
index 79f133fb..95b3d92f 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/types.d.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/types.d.ts
@@ -1,22 +1,19 @@
import type { LiveData } from '@/library/bili-api/data'
+import type { ModuleConfig } from '@/types'
-type LiveStatus = 'on' | 'off'
+type _MedalTasks = ModuleConfig['DailyTasks']['LiveTasks']['medalTasks']
-interface MedalTaskSharedConfig {
- isWhiteList: boolean
- roomidList: number[]
-}
+type MedalTaskSharedConfig =
+ | _MedalTasks['light']
+ | _MedalTasks['like']
+ | _MedalTasks['danmu']
+ | _MedalTasks['watch']
-interface PublicMedalFilters {
- whiteBlackList: (medal: LiveData.FansMedalPanel.List) => boolean
+interface SharedMedalFilters {
+ meetWhiteOrBlackList: (medal: LiveData.FansMedalPanel.List) => boolean
levelLt120: (medal: LiveData.FansMedalPanel.List) => boolean
-}
-
-interface LightTaskMedalFilters {
isLighted: (medal: LiveData.FansMedalPanel.List) => boolean
- livingStatus: (medal: LiveData.FansMedalPanel.List) => LiveStatus
+ isLiving: (medal: LiveData.FansMedalPanel.List) => boolean
}
-type MedalsByLivingStatus = Record
-
-export { MedalTaskSharedConfig, PublicMedalFilters, LightTaskMedalFilters, MedalsByLivingStatus }
+export { MedalTaskSharedConfig, SharedMedalFilters }
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/watchTask.ts b/src/modules/dailyTasks/liveTasks/medalTasks/watchTask.ts
index b938d906..21ce77ba 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/watchTask.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/watchTask.ts
@@ -250,10 +250,6 @@ class WatchTask extends MedalModule {
config = this.medalTasksConfig.watch
- protected get taskConfig() {
- return this.config
- }
-
set status(s: ModuleStatusTypes) {
useModuleStore().moduleStatus.DailyTasks.LiveTasks.medalTasks.watch = s
}
@@ -262,19 +258,18 @@ class WatchTask extends MedalModule {
/**
* 获取已点亮的粉丝勋章
- * @returns 经过排序和过滤的粉丝勋章
*/
private getMedals(): LiveData.FansMedalPanel.List[] {
const fansMedals = useBiliStore().filteredFansMedals
const result = fansMedals.filter(
(medal) =>
- this.PUBLIC_MEDAL_FILTERS.whiteBlackList(medal) &&
- this.PUBLIC_MEDAL_FILTERS.levelLt120(medal) &&
- medal.medal.is_lighted === 1,
+ this.SHARED_MEDAL_FILTERS.meetWhiteOrBlackList(medal) &&
+ this.SHARED_MEDAL_FILTERS.levelLt120(medal) &&
+ this.SHARED_MEDAL_FILTERS.isLighted(medal),
)
- if (this.taskConfig.isWhiteList) {
+ if (this.config.isWhiteList) {
this.sortMedals(result)
}
From 616a1e8d3ff1db0048b65f6c098e66a7df228a31 Mon Sep 17 00:00:00 2001
From: andywang425 <56548456+andywang425@users.noreply.github.com>
Date: Thu, 21 May 2026 19:38:35 +0800
Subject: [PATCH 12/27] =?UTF-8?q?=F0=9F=90=9E=20fix:=20isNowIn=E5=87=BD?=
=?UTF-8?q?=E6=95=B0=E5=8F=82=E6=95=B0=E4=B8=BA=E8=B7=A8=E5=A4=A9=E6=97=B6?=
=?UTF-8?q?=E9=97=B4=E6=AE=B5=E6=97=B6=E5=88=A4=E6=96=AD=E6=9C=89=E8=AF=AF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 使用isNowBefore和isNowAfter替代
---
src/library/luxon/index.ts | 39 +++++++++----------
.../liveTasks/medalTasks/danmuTask.ts | 8 ++--
.../liveTasks/medalTasks/lightTask.ts | 4 +-
.../liveTasks/medalTasks/likeTask.ts | 8 ++--
.../liveTasks/medalTasks/watchTask.ts | 8 ++--
.../dailyTasks/mainSiteTasks/coinTask.ts | 4 +-
.../dailyTasks/mainSiteTasks/loginTask.ts | 4 +-
.../dailyTasks/mainSiteTasks/shareTask.ts | 4 +-
.../dailyTasks/mainSiteTasks/watchTask.ts | 4 +-
.../dailyTasks/otherTasks/coinToSilverTask.ts | 4 +-
.../dailyTasks/otherTasks/silverToCoinTask.ts | 4 +-
11 files changed, 44 insertions(+), 47 deletions(-)
diff --git a/src/library/luxon/index.ts b/src/library/luxon/index.ts
index 20d62cbe..e41894cd 100644
--- a/src/library/luxon/index.ts
+++ b/src/library/luxon/index.ts
@@ -75,30 +75,27 @@ function delayToNextMoment(hour: number = 0, minute: number = 5): Duration {
}
/**
- * 判断现在是不是处在目标时间范围内
+ * 判断现在是不是在目标时间之前
*
- * 如果开始时间晚于结束时间,则将结束时间视为第二天的时间
- *
- * @param startHour 开始小时
- * @param startMinute 开始分钟
- * @param endHour 结束小时
- * @param endMinute 结束分钟
+ * @param hour 小时(0-23)
+ * @param minute 分钟(0-59)
*/
-function isNowIn(
- startHour: number,
- startMinute: number,
- endHour: number,
- endMinute: number,
-): boolean {
+function isNowBefore(hour: number, minute: number): boolean {
const now = DateTime.now()
- const start = DateTime.local(now.year, now.month, now.day, startHour, startMinute)
- let end = DateTime.local(now.year, now.month, now.day, endHour, endMinute)
-
- if (start > end) {
- end = end.plus({ days: 1 })
- }
+ const target = DateTime.local(now.year, now.month, now.day, hour, minute, 0, 0)
+ return now < target
+}
- return now >= start && now < end
+/**
+ * 判断现在是不是在目标时间之后
+ *
+ * @param hour 小时(0-23)
+ * @param minute 分钟(0-59)
+ */
+function isNowAfter(hour: number, minute: number): boolean {
+ const now = DateTime.now()
+ const target = DateTime.local(now.year, now.month, now.day, hour, minute, 0, 0)
+ return now > target
}
/**
@@ -115,4 +112,4 @@ function tsm(): number {
return DateTime.now().toMillis()
}
-export { isTimestampToday, delayToNextMoment, isNowIn, ts, tsm }
+export { isTimestampToday, delayToNextMoment, isNowBefore, isNowAfter, ts, tsm }
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/danmuTask.ts b/src/modules/dailyTasks/liveTasks/medalTasks/danmuTask.ts
index ef17c447..813bdec1 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/danmuTask.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/danmuTask.ts
@@ -1,4 +1,4 @@
-import { delayToNextMoment, isNowIn, isTimestampToday, tsm } from '@/library/luxon'
+import { delayToNextMoment, isNowAfter, isNowBefore, isTimestampToday, tsm } from '@/library/luxon'
import BAPI from '@/library/bili-api'
import { useBiliStore, useModuleStore } from '@/stores'
import { sleep } from '@/library/utils'
@@ -84,7 +84,7 @@ class DanmuTask extends MedalModule {
let danmuIndex = 0
outer: for (let i = 0; i < fansMedals.length; i++) {
- if (isNowIn(23, 55, 0, 5)) {
+ if (isNowAfter(23, 55) || isNowBefore(0, 5)) {
this.logger.log('即将或刚刚发生跨天,提早结束本轮发弹幕任务')
allCompleted = false
break
@@ -106,7 +106,7 @@ class DanmuTask extends MedalModule {
const maxTarget = remaining + 3
for (let j = 0; j < target; j++) {
- if (isNowIn(23, 55, 0, 5)) {
+ if (isNowAfter(23, 55) || isNowBefore(0, 5)) {
this.logger.log('即将或刚刚发生跨天,提早结束本轮发弹幕任务')
allCompleted = false
break outer
@@ -132,7 +132,7 @@ class DanmuTask extends MedalModule {
this.status = ''
}
} else {
- if (isNowIn(0, 0, 0, 5)) {
+ if (isNowBefore(0, 5)) {
this.logger.log('昨天的发弹幕任务已经完成过了,等到今天的00:05再执行')
} else {
this.logger.log('今天已经完成过发弹幕任务了')
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts b/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts
index 0851d98f..043f779c 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts
@@ -1,4 +1,4 @@
-import { delayToNextMoment, isNowIn, isTimestampToday, tsm } from '@/library/luxon'
+import { delayToNextMoment, isNowBefore, isTimestampToday, tsm } from '@/library/luxon'
import BAPI from '@/library/bili-api'
import { useBiliStore, useModuleStore } from '@/stores'
import { sleep } from '@/library/utils'
@@ -176,7 +176,7 @@ class LightTask extends MedalModule {
this.status = 'done'
this.logger.log('点亮熄灭勋章任务已完成')
} else {
- if (isNowIn(0, 0, 0, 5)) {
+ if (isNowBefore(0, 5)) {
this.logger.log('昨天的给点亮熄灭勋章任务已经完成过了,等到今天的00:05再执行')
} else {
this.logger.log('今天已经完成过点亮熄灭勋章任务了')
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/likeTask.ts b/src/modules/dailyTasks/liveTasks/medalTasks/likeTask.ts
index c3b0769d..695885ca 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/likeTask.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/likeTask.ts
@@ -1,4 +1,4 @@
-import { delayToNextMoment, isNowIn, isTimestampToday, tsm } from '@/library/luxon'
+import { delayToNextMoment, isNowAfter, isNowBefore, isTimestampToday, tsm } from '@/library/luxon'
import BAPI from '@/library/bili-api'
import { useBiliStore, useModuleStore } from '@/stores'
import { sleep } from '@/library/utils'
@@ -75,7 +75,7 @@ class LikeTask extends MedalModule {
let allCompleted = true
outer: for (let i = 0; i < fansMedals.length; i++) {
- if (isNowIn(23, 55, 0, 5)) {
+ if (isNowAfter(23, 55) || isNowBefore(0, 5)) {
this.logger.log('即将或刚刚发生跨天,提早结束本轮点赞任务')
allCompleted = false
break
@@ -95,7 +95,7 @@ class LikeTask extends MedalModule {
const remaining = parsed.limit - parsed.current
for (let j = 0; j < remaining; j++) {
- if (isNowIn(23, 55, 0, 5)) {
+ if (isNowAfter(23, 55) || isNowBefore(0, 5)) {
this.logger.log('即将或刚刚发生跨天,提早结束本轮点赞任务')
allCompleted = false
break outer
@@ -117,7 +117,7 @@ class LikeTask extends MedalModule {
this.status = ''
}
} else {
- if (isNowIn(0, 0, 0, 5)) {
+ if (isNowBefore(0, 5)) {
this.logger.log('昨天的点赞任务已经完成过了,等到今天的00:05再执行')
} else {
this.logger.log('今天已经完成过点赞任务了')
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/watchTask.ts b/src/modules/dailyTasks/liveTasks/medalTasks/watchTask.ts
index 21ce77ba..fa84667d 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/watchTask.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/watchTask.ts
@@ -1,4 +1,4 @@
-import { delayToNextMoment, isNowIn, isTimestampToday, tsm } from '@/library/luxon'
+import { delayToNextMoment, isNowAfter, isNowBefore, isTimestampToday, tsm } from '@/library/luxon'
import BAPI from '@/library/bili-api'
import { useBiliStore, useModuleStore, usePlayerStore } from '@/stores'
import Logger from '@/library/logger'
@@ -132,7 +132,7 @@ class RoomHeart {
* X心跳,E心跳过后都是X心跳
*/
private async X(): Promise {
- if (isNowIn(23, 59, 0, 5)) {
+ if (isNowAfter(23, 58) || isNowBefore(0, 5)) {
this.logger.log(`即将或刚刚发生跨天,停止直播间 ${this.roomID} 的X心跳`)
return
}
@@ -356,7 +356,7 @@ class WatchTask extends MedalModule {
let i: number
for (i = 0; i < fansMedals.length; i++) {
- if (isNowIn(23, 55, 0, 5)) {
+ if (isNowAfter(23, 55) || isNowBefore(0, 5)) {
this.logger.log('即将或刚刚发生跨天,提早结束本轮观看直播任务')
break
}
@@ -417,7 +417,7 @@ class WatchTask extends MedalModule {
this.config._lastCompleteTime = tsm()
}
} else {
- if (isNowIn(0, 0, 0, 5)) {
+ if (isNowBefore(0, 5)) {
this.logger.log('昨天的观看直播任务已经完成过了,等到今天的00:05再执行')
} else {
this.logger.log('今天已经完成过观看直播任务了')
diff --git a/src/modules/dailyTasks/mainSiteTasks/coinTask.ts b/src/modules/dailyTasks/mainSiteTasks/coinTask.ts
index b1b08c8e..787302cb 100644
--- a/src/modules/dailyTasks/mainSiteTasks/coinTask.ts
+++ b/src/modules/dailyTasks/mainSiteTasks/coinTask.ts
@@ -1,5 +1,5 @@
import BaseModule from '@/modules/BaseModule'
-import { isTimestampToday, delayToNextMoment, tsm, isNowIn } from '@/library/luxon'
+import { isTimestampToday, delayToNextMoment, tsm, isNowBefore } from '@/library/luxon'
import { useBiliStore, useModuleStore } from '@/stores'
import BAPI from '@/library/bili-api'
import type { ModuleStatusTypes } from '@/types'
@@ -169,7 +169,7 @@ class CoinTask extends BaseModule {
} else {
// 为了更加准确的语言描述和任务状态图标显示,需要判断当前所处的时间段
// 下面文字中的今天、昨天是指真实的今天、昨天而非在 isTimestampToday 函数中重新定义的
- if (isNowIn(0, 0, 0, 5)) {
+ if (isNowBefore(0, 5)) {
// 在半夜00:00 ~ 00:05
this.logger.log('昨天的每日投币任务已经完成过了,等到今天的00:05再执行')
} else {
diff --git a/src/modules/dailyTasks/mainSiteTasks/loginTask.ts b/src/modules/dailyTasks/mainSiteTasks/loginTask.ts
index 1c62aa6f..45fcdfe7 100644
--- a/src/modules/dailyTasks/mainSiteTasks/loginTask.ts
+++ b/src/modules/dailyTasks/mainSiteTasks/loginTask.ts
@@ -1,5 +1,5 @@
import BaseModule from '@/modules/BaseModule'
-import { isTimestampToday, delayToNextMoment, tsm, isNowIn } from '@/library/luxon'
+import { isTimestampToday, delayToNextMoment, tsm, isNowBefore } from '@/library/luxon'
import { useBiliStore, useModuleStore } from '@/stores'
import type { ModuleStatusTypes } from '@/types'
@@ -46,7 +46,7 @@ class LoginTask extends BaseModule {
this.status = 'done'
}
} else {
- if (isNowIn(0, 0, 0, 5)) {
+ if (isNowBefore(0, 5)) {
this.logger.log('昨天的每日登录任务已经完成过了,等到今天的00:05再执行')
} else {
this.logger.log('今天已经完成过每日登录任务了')
diff --git a/src/modules/dailyTasks/mainSiteTasks/shareTask.ts b/src/modules/dailyTasks/mainSiteTasks/shareTask.ts
index c687b9b4..6de7b445 100644
--- a/src/modules/dailyTasks/mainSiteTasks/shareTask.ts
+++ b/src/modules/dailyTasks/mainSiteTasks/shareTask.ts
@@ -1,5 +1,5 @@
import BaseModule from '@/modules/BaseModule'
-import { isTimestampToday, delayToNextMoment, tsm, isNowIn } from '@/library/luxon'
+import { isTimestampToday, delayToNextMoment, tsm, isNowBefore } from '@/library/luxon'
import { useBiliStore, useModuleStore } from '@/stores'
import BAPI from '@/library/bili-api'
import type { ModuleStatusTypes } from '@/types'
@@ -73,7 +73,7 @@ class ShareTask extends BaseModule {
this.logger.log('每日分享视频任务已完成')
}
} else {
- if (isNowIn(0, 0, 0, 5)) {
+ if (isNowBefore(0, 5)) {
this.logger.log('昨天的每日分享任务已经完成过了,等到今天的00:05再执行')
} else {
this.logger.log('今天已经完成过每日分享任务了')
diff --git a/src/modules/dailyTasks/mainSiteTasks/watchTask.ts b/src/modules/dailyTasks/mainSiteTasks/watchTask.ts
index 8498174d..4674716d 100644
--- a/src/modules/dailyTasks/mainSiteTasks/watchTask.ts
+++ b/src/modules/dailyTasks/mainSiteTasks/watchTask.ts
@@ -1,5 +1,5 @@
import BaseModule from '@/modules/BaseModule'
-import { isTimestampToday, delayToNextMoment, tsm, isNowIn } from '@/library/luxon'
+import { isTimestampToday, delayToNextMoment, tsm, isNowBefore } from '@/library/luxon'
import { useBiliStore, useModuleStore } from '@/stores'
import BAPI from '@/library/bili-api'
import type { ModuleStatusTypes } from '@/types'
@@ -74,7 +74,7 @@ class WatchTask extends BaseModule {
this.logger.log('每日观看视频任务已完成')
}
} else {
- if (isNowIn(0, 0, 0, 5)) {
+ if (isNowBefore(0, 5)) {
this.logger.log('昨天的每日观看视频任务已经完成过了,等到今天的00:05再执行')
} else {
this.logger.log('今天已经完成过每日观看视频任务了')
diff --git a/src/modules/dailyTasks/otherTasks/coinToSilverTask.ts b/src/modules/dailyTasks/otherTasks/coinToSilverTask.ts
index c99e674a..d904e284 100644
--- a/src/modules/dailyTasks/otherTasks/coinToSilverTask.ts
+++ b/src/modules/dailyTasks/otherTasks/coinToSilverTask.ts
@@ -1,5 +1,5 @@
import BaseModule from '@/modules/BaseModule'
-import { isTimestampToday, delayToNextMoment, tsm, isNowIn } from '@/library/luxon'
+import { isTimestampToday, delayToNextMoment, tsm, isNowAfter } from '@/library/luxon'
import BAPI from '@/library/bili-api'
import type { ModuleStatusTypes } from '@/types'
import { useModuleStore } from '@/stores'
@@ -36,7 +36,7 @@ class CoinToSilverTask extends BaseModule {
this.status = 'running'
await this.exchange()
} else {
- if (!isNowIn(0, 0, 0, 5)) {
+ if (isNowAfter(0, 5)) {
this.logger.log('今天已经完成过硬币换银瓜子任务了')
this.status = 'done'
} else {
diff --git a/src/modules/dailyTasks/otherTasks/silverToCoinTask.ts b/src/modules/dailyTasks/otherTasks/silverToCoinTask.ts
index f1f46bff..52d5ad4a 100644
--- a/src/modules/dailyTasks/otherTasks/silverToCoinTask.ts
+++ b/src/modules/dailyTasks/otherTasks/silverToCoinTask.ts
@@ -1,5 +1,5 @@
import BaseModule from '@/modules/BaseModule'
-import { isTimestampToday, delayToNextMoment, tsm, isNowIn } from '@/library/luxon'
+import { isTimestampToday, delayToNextMoment, tsm, isNowBefore } from '@/library/luxon'
import BAPI from '@/library/bili-api'
import type { ModuleStatusTypes } from '@/types'
import { useModuleStore } from '@/stores'
@@ -41,7 +41,7 @@ class SilverToCoinTask extends BaseModule {
this.status = 'running'
this.exchange()
} else {
- if (isNowIn(0, 0, 0, 5)) {
+ if (isNowBefore(0, 5)) {
this.logger.log('昨天的银瓜子换硬币任务已经完成过了,等到今天的00:05再执行')
} else {
this.logger.log('今天已经完成过银瓜子换硬币任务了')
From 6316f2f1347682912de4965398323cf47c176cc5 Mon Sep 17 00:00:00 2001
From: andywang425 <56548456+andywang425@users.noreply.github.com>
Date: Thu, 21 May 2026 20:25:58 +0800
Subject: [PATCH 13/27] =?UTF-8?q?=F0=9F=90=9E=20fix:=20=E4=BF=AE=E5=A4=8DD?=
=?UTF-8?q?efault=5FFansMedals=E6=A8=A1=E5=9D=97=E7=9F=AD=E6=97=B6?=
=?UTF-8?q?=E9=97=B4=E5=86=85=E5=A4=9A=E6=AC=A1rerun=E4=BC=9A=E9=87=8D?=
=?UTF-8?q?=E5=A4=8D=E8=BF=90=E8=A1=8C=E7=9A=84bug?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../liveTasks/medalTasks/MedalModule.ts | 1 +
src/modules/default/fansMedals.ts | 50 ++++++++++++-------
2 files changed, 33 insertions(+), 18 deletions(-)
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/MedalModule.ts b/src/modules/dailyTasks/liveTasks/medalTasks/MedalModule.ts
index 0cc102b5..ae605f70 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/MedalModule.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/MedalModule.ts
@@ -168,6 +168,7 @@ class MedalModule extends BaseModule {
if (newStatus === 'done' || newStatus === 'error') {
stopWatch()
// 重新获取粉丝勋章(主要是为了获取最新的点亮状态、是否正在直播状态)
+ // FansMedals 模块内部做了防重入,因此无需担心会重复获取
moduleStore.rerunModule('Default_FansMedals')
resolve()
}
diff --git a/src/modules/default/fansMedals.ts b/src/modules/default/fansMedals.ts
index 598c534c..9e369479 100644
--- a/src/modules/default/fansMedals.ts
+++ b/src/modules/default/fansMedals.ts
@@ -8,6 +8,11 @@ import ModuleError from '@/library/error/ModuleError'
import _ from 'lodash'
class FansMedals extends BaseModule {
+ /**
+ * 是否正在运行
+ */
+ private isRunning = false
+
/**
* 获取粉丝勋章
*
@@ -50,27 +55,36 @@ class FansMedals extends BaseModule {
}
public async run(force = false): Promise {
- const biliStore = useBiliStore()
+ if (this.isRunning) {
+ return
+ }
+ this.isRunning = true
- const medalTasks = useModuleStore().moduleConfig.DailyTasks.LiveTasks.medalTasks
- const taskValues = [medalTasks.light, medalTasks.like, medalTasks.danmu, medalTasks.watch]
+ try {
+ const biliStore = useBiliStore()
- if (
- (force ||
- // 开启了点亮熄灭勋章或观看直播功能且今天没完成过
- taskValues.some((t) => t.enabled && !isTimestampToday(t._lastCompleteTime, 0, 4))) &&
- // 如果正在获取粉丝勋章,不重复获取
- biliStore.fansMedalsStatus !== 'loading'
- ) {
- biliStore.fansMedalsStatus = 'loading'
- biliStore.fansMedals = await this.getFansMedals()
- biliStore.fansMedalsStatus = 'loaded'
- }
+ const medalTasks = useModuleStore().moduleConfig.DailyTasks.LiveTasks.medalTasks
+ const taskValues = [medalTasks.light, medalTasks.like, medalTasks.danmu, medalTasks.watch]
+
+ if (
+ (force ||
+ // 开启了任意粉丝勋章相关功能且今天没完成过
+ taskValues.some((t) => t.enabled && !isTimestampToday(t._lastCompleteTime, 0, 4))) &&
+ // 如果正在获取粉丝勋章,不重复获取
+ biliStore.fansMedalsStatus !== 'loading'
+ ) {
+ biliStore.fansMedalsStatus = 'loading'
+ biliStore.fansMedals = await this.getFansMedals()
+ biliStore.fansMedalsStatus = 'loaded'
+ }
- this.nextRunTimer = setTimeout(
- () => this.run().catch((reason) => this.logger.error(reason)),
- delayToNextMoment(0, 4).ms,
- )
+ this.nextRunTimer = setTimeout(
+ () => this.run().catch((reason) => this.logger.error(reason)),
+ delayToNextMoment(0, 4).ms,
+ )
+ } finally {
+ this.isRunning = false
+ }
}
}
From 6bde3d99ea57d5a759144e43dc39fe964336a17a Mon Sep 17 00:00:00 2001
From: andywang425 <56548456+andywang425@users.noreply.github.com>
Date: Fri, 22 May 2026 00:00:37 +0800
Subject: [PATCH 14/27] =?UTF-8?q?=F0=9F=90=9E=20fix:=20=E4=BF=AE=E5=A4=8D?=
=?UTF-8?q?=E7=BC=96=E8=BE=91=E5=90=8D=E5=8D=95=E5=BC=B9=E7=AA=97=E8=A1=A8?=
=?UTF-8?q?=E6=A0=BCbug?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- watch相关改动和修复
- 不再删除不存在的roomid
- 没加载出数据也停止显示加载中动画
---
src/components/LiveTasks.vue | 39 +++++++++++++++++++-----------------
1 file changed, 21 insertions(+), 18 deletions(-)
diff --git a/src/components/LiveTasks.vue b/src/components/LiveTasks.vue
index 8dd6bc75..0b2e4353 100644
--- a/src/components/LiveTasks.vue
+++ b/src/components/LiveTasks.vue
@@ -132,23 +132,23 @@ const medalInfoTableData = computed({
})
/** 是否显示加载中图标 */
const medalInfoLoading = ref(false)
+/** 取消监听 biliStore.fansMedals */
+let unwatchFansMedals: () => void = () => {}
/**
* 编辑名单
*/
const handleEditList = async (key: MedalTaskKey) => {
currentEditingTask.value = key
medalInfoPanelVisible.value = true
- await nextTick()
+
// 如果没有粉丝勋章信息,先获取
if (!biliStore.fansMedals) {
medalInfoLoading.value = true
- watch(
- medalInfoTableData,
- (newData) => {
- nextTick(() => {
- refreshSelection(newData)
- medalInfoLoading.value = false
- })
+ unwatchFansMedals = watch(
+ () => biliStore.fansMedals,
+ async () => {
+ await nextTick()
+ refreshSelection()
},
{ once: true },
)
@@ -156,27 +156,29 @@ const handleEditList = async (key: MedalTaskKey) => {
moduleStore.rerunModule('Default_FansMedals', true)
}
} else {
- refreshSelection(medalInfoTableData.value)
+ await nextTick()
+ refreshSelection()
}
}
/** 用来管理多选框状态的表格Ref */
const medalInfoTableRef = ref()
/** 根据当前任务的 roomidList 刷新多选框选择状态 */
-const refreshSelection = (rows?: MedalInfoRow[]) => {
+const refreshSelection = () => {
+ medalInfoLoading.value = false
// 排序模式下不更新多选框选择状态
- if (!rows || currentTaskIsSortMode.value) return
+ if (medalInfoTableData.value.length === 0 || currentTaskIsSortMode.value) return
medalInfoTableRef.value?.clearSelection()
const taskConf = currentTaskConfig.value
- taskConf.roomidList.forEach((roomid, index) => {
- const row = rows.find((row) => row.roomid === roomid)
+ taskConf.roomidList.forEach((roomid) => {
+ // 此处有可能无法找到直播间号为 roomid 的粉丝勋章(row 为 undefined)
+ // 比如用户曾经拥有某个粉丝勋章,并且在编辑名单弹窗里勾选过该勋章,但他后来把这个粉丝勋章删了
+ // 这些找不到的 roomid 不影响脚本运行
+ // 当用户修改名单时,会自动更新 roomidList,所以这里无需处理
+ const row = medalInfoTableData.value.find((row) => row.roomid === roomid)
if (row) {
medalInfoTableRef.value?.toggleRowSelection(row, true)
- } else {
- // 没有找到直播间号为 roomid 的粉丝勋章,可能是因为该粉丝勋章已被删除或是过滤
- // 从名单中去掉这个 roomid
- taskConf.roomidList.splice(index, 1)
}
})
}
@@ -357,6 +359,7 @@ function handleRowClick(row: MedalInfoRow) {
v-model="medalInfoPanelVisible"
:title="`编辑粉丝勋章名单 - ${currentTaskLabel}`"
:lock-scroll="false"
+ @close="unwatchFansMedals"
>
!val && nextTick(() => refreshSelection(medalInfoTableData))"
+ @change="(val) => !val && nextTick(() => refreshSelection())"
/>
From b9f93dffff90b08e896a68326444c31c9eb2ad82 Mon Sep 17 00:00:00 2001
From: andywang425 <56548456+andywang425@users.noreply.github.com>
Date: Fri, 22 May 2026 00:53:11 +0800
Subject: [PATCH 15/27] =?UTF-8?q?=E2=9C=A8=20feat(MedalModule):=20?=
=?UTF-8?q?=E4=BC=98=E5=8C=96=E8=8E=B7=E5=8F=96=E7=B2=89=E4=B8=9D=E5=8B=8B?=
=?UTF-8?q?=E7=AB=A0=E4=BB=BB=E5=8A=A1=E4=BF=A1=E6=81=AF=E7=9A=84=E7=BC=93?=
=?UTF-8?q?=E5=AD=98=E5=92=8C=E9=98=B2=E9=A3=8E=E6=8E=A7=E9=80=BB=E8=BE=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 使用简易队列,确保API请求之间有一个稳定的间隔
- 把缓存提升为所有粉丝勋章任务相关模块(MedalModule子类)共享
---
.../liveTasks/medalTasks/MedalModule.ts | 70 ++++++++++++++-----
.../liveTasks/medalTasks/danmuTask.ts | 2 +-
.../liveTasks/medalTasks/lightTask.ts | 2 +-
.../liveTasks/medalTasks/likeTask.ts | 2 +-
.../liveTasks/medalTasks/watchTask.ts | 2 +-
5 files changed, 55 insertions(+), 23 deletions(-)
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/MedalModule.ts b/src/modules/dailyTasks/liveTasks/medalTasks/MedalModule.ts
index ae605f70..a03e222f 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/MedalModule.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/MedalModule.ts
@@ -12,6 +12,23 @@ import _ from 'lodash'
type JumpType = 'like' | 'sendDanmu' | 'watchLive' | 'feedLight' | 'sendGift'
class MedalModule extends BaseModule {
+ /**
+ * 任务信息共享缓存
+ *
+ * target_id -> task_info Promise
+ */
+ private static taskInfoCache = new Map<
+ number,
+ Promise
+ >()
+
+ /**
+ * 简单限流队列
+ *
+ * 串行执行任务信息获取请求
+ */
+ private static taskInfoRequestQueue: Promise = Promise.resolve()
+
medalTasksConfig = useModuleStore().moduleConfig.DailyTasks.LiveTasks.medalTasks
/** 所有粉丝勋章任务的公共配置 */
@@ -81,7 +98,7 @@ class MedalModule extends BaseModule {
* 按 jump_type 在 task_info 中查找任务项
*/
protected static findTaskInfo(
- task_info: LiveData.GetActivatedMedalInfo.TaskInfo[] | null | undefined,
+ task_info: LiveData.GetActivatedMedalInfo.TaskInfo[] | null,
jump_type: JumpType,
): LiveData.GetActivatedMedalInfo.TaskInfo | undefined {
return task_info?.find((t) => t.jump_type === jump_type)
@@ -99,33 +116,46 @@ class MedalModule extends BaseModule {
return Number(match[0])
}
- /** 任务内缓存:target_id -> task_info(缓存 Promise 避免并发重复请求) */
- private taskInfoCache = new Map<
- number,
- Promise
- >()
+ /**
+ * 清空任务信息共享缓存
+ */
+ protected static clearTaskInfoCache(): void {
+ MedalModule.taskInfoCache.clear()
+ }
+
+ /** 将请求加入全局串行队列,并在真正发请求前等待一小段随机时间 */
+ private static enqueueTaskInfoRequest(requester: () => Promise): Promise {
+ const task = MedalModule.taskInfoRequestQueue
+ .catch(() => undefined)
+ .then(async () => {
+ await sleep(_.random(300, 500))
+ return requester()
+ })
+
+ MedalModule.taskInfoRequestQueue = task.then(
+ () => undefined,
+ () => undefined,
+ )
- /** 在 `run()` 入口调用以清空缓存 */
- protected resetTaskInfoCache(): void {
- this.taskInfoCache.clear()
+ return task
}
/**
- * 获取指定粉丝勋章的 task_info。调用前会先 `sleep` 一段时间避免风控。
+ * 获取指定粉丝勋章的任务信息
*
- * 同一 target_id 的并发请求会共享同一个 Promise。
+ * 同一 target_id 的并发请求会共享同一个 Promise;
+ * 不同 target_id 的请求会进入全局串行队列,避免短时间内并发触发过多请求
*
* @param target_id 主播 uid
- * @returns 成功返回 task_info 数组,失败返回 null
+ * @returns 成功返回任务信息数组,失败返回 null
*/
protected fetchTaskInfo(
target_id: number,
): Promise {
- const cached = this.taskInfoCache.get(target_id)
+ const cached = MedalModule.taskInfoCache.get(target_id)
if (cached) return cached
- const promise = (async () => {
- await sleep(_.random(300, 500))
+ const promise = MedalModule.enqueueTaskInfoRequest(async () => {
try {
const response = await BAPI.live.getActivatedMedalInfo(target_id)
this.logger.log(`BAPI.live.getActivatedMedalInfo(${target_id}) response`, response)
@@ -133,15 +163,17 @@ class MedalModule extends BaseModule {
return response.data.task_info
} else {
this.logger.error(`BAPI.live.getActivatedMedalInfo(${target_id}) 失败`, response.message)
+ MedalModule.taskInfoCache.delete(target_id)
return null
}
} catch (error) {
this.logger.error(`BAPI.live.getActivatedMedalInfo(${target_id}) 出错`, error)
+ MedalModule.taskInfoCache.delete(target_id)
return null
}
- })()
+ })
- this.taskInfoCache.set(target_id, promise)
+ MedalModule.taskInfoCache.set(target_id, promise)
return promise
}
@@ -162,11 +194,11 @@ class MedalModule extends BaseModule {
this.logger.log('等待点亮熄灭勋章任务完成后再执行')
return new Promise((resolve) => {
- const stopWatch = watch(
+ const unwatch = watch(
() => moduleStore.moduleStatus.DailyTasks.LiveTasks.medalTasks.light,
(newStatus) => {
if (newStatus === 'done' || newStatus === 'error') {
- stopWatch()
+ unwatch()
// 重新获取粉丝勋章(主要是为了获取最新的点亮状态、是否正在直播状态)
// FansMedals 模块内部做了防重入,因此无需担心会重复获取
moduleStore.rerunModule('Default_FansMedals')
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/danmuTask.ts b/src/modules/dailyTasks/liveTasks/medalTasks/danmuTask.ts
index 813bdec1..286651e8 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/danmuTask.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/danmuTask.ts
@@ -78,7 +78,7 @@ class DanmuTask extends MedalModule {
}
this.status = 'running'
- this.resetTaskInfoCache()
+ MedalModule.clearTaskInfoCache()
const fansMedals = this.getMedals()
let allCompleted = true
let danmuIndex = 0
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts b/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts
index 043f779c..6efa49d7 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts
@@ -167,7 +167,7 @@ class LightTask extends MedalModule {
}
this.status = 'running'
- this.resetTaskInfoCache()
+ MedalModule.clearTaskInfoCache()
const fansMedals = this.getMedals()
await Promise.allSettled([this.sendDanmuTask(fansMedals[0]), this.likeTask(fansMedals[1])])
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/likeTask.ts b/src/modules/dailyTasks/liveTasks/medalTasks/likeTask.ts
index 695885ca..c9b13c4f 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/likeTask.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/likeTask.ts
@@ -70,7 +70,7 @@ class LikeTask extends MedalModule {
}
this.status = 'running'
- this.resetTaskInfoCache()
+ MedalModule.clearTaskInfoCache()
const fansMedals = this.getMedals()
let allCompleted = true
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/watchTask.ts b/src/modules/dailyTasks/liveTasks/medalTasks/watchTask.ts
index fa84667d..6e8f6bfd 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/watchTask.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/watchTask.ts
@@ -337,7 +337,7 @@ class WatchTask extends MedalModule {
}
this.status = 'running'
- this.resetTaskInfoCache()
+ MedalModule.clearTaskInfoCache()
if (!isTimestampToday(this.config._lastWatchTime, 0, 0)) {
// 如果上次观看(不是完成任务)的时间戳不在今天,将今天已观看的秒数置为0
From 6da8523ae337eb19028ccaac02e91054537805af Mon Sep 17 00:00:00 2001
From: andywang425 <56548456+andywang425@users.noreply.github.com>
Date: Fri, 22 May 2026 01:25:20 +0800
Subject: [PATCH 16/27] =?UTF-8?q?=F0=9F=93=83=20docs:=20README?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/README.md b/README.md
index 6da67ca1..f62dfad6 100644
--- a/README.md
+++ b/README.md
@@ -37,10 +37,10 @@
- 一个旋转的加载中图标:该任务正在执行中。
- 没有图标:该功能不显示任务状态、功能未开启或该任务正在等待即将到来的下一次运行。
- 打开某一项功能后,如果你希望它立刻执行,需要刷新页面。
- 但是刷新页面会打断目前正在执行的任务,下次运行时能否根据上次的进度继续执行取决于功能模块的具体实现(目前大部分都不行)。
+ 但是刷新页面会打断目前正在执行的任务,下次运行时能否根据上次的进度继续执行取决于功能模块的具体实现(目前仅观看直播模块支持)。
建议首次使用时仔细阅读各项功能的说明,然后一次完成所有功能的设置。
- 打开多个直播间页面时,每日任务中的功能仅会在第一个直播间页面生效以防止任务被重复执行。体验优化、移除元素中的功能总是会生效。
-- 每日任务中大部分任务的任务周期是从每天半夜00:05到下一个半夜00:05,即只要过了每天的00:05就会自动执行下一轮。有个别任务的周期是每天早上08:05到下一个早上08:05。
+- 每日任务中大部分任务的任务周期是每天半夜00:05到下一个半夜00:05,即只要过了每天的00:05就会自动执行下一轮。
- 控制台日志:如果想查看控制台日志,请打开浏览器的开发者工具(快捷键`ctrl+shift+i`或`F12`)并切换到控制台(`Console`)。开头为`BLTH`的日志即为脚本日志。
部分日志可能只有高级用户才看得懂,大家各取所需看自己想看的即可。善用过滤(`Filter`)和搜索(快捷键`ctrl+f`)功能。
@@ -48,11 +48,11 @@
[BLTH](https://github.com/andywang425/BLTH)在设计上希望与[Bilibili Evolved](https://github.com/the1812/Bilibili-Evolved)和[bilibili 直播净化](https://greasyfork.org/zh-CN/scripts/21416-bilibili%E7%9B%B4%E6%92%AD%E5%87%80%E5%8C%96)兼容,即三个脚本能同时正常运行。
-如果你发现存在兼容性问题可以[新建一个空白issue](https://github.com/andywang425/BLTH/issues/new)来反馈。
+如果你发现存在兼容性问题可以[创建一个issue](https://github.com/andywang425/BLTH/issues/new?template=bug_report.yaml)来反馈。
# 反馈
-如果想反馈 Bug 或者提出建议,请点击[new issue](https://github.com/andywang425/BLTH/issues/new/choose)然后选择对应的 issue 模板。
+如果想反馈 Bug 或者提出建议,请在[Issues](https://github.com/andywang425/BLTH/issues)页面点击[New issue](https://github.com/andywang425/BLTH/issues/new/choose),然后选择对应的 issue 模板。
为了方便管理issue,请在 GreasyFork 安装脚本的用户尽量到 Github 反馈。
@@ -108,8 +108,8 @@
- [Github](https://github.com/lzghzr/TampermonkeyJS/blob/master/BiLiveNoVIP/BiLiveNoVIP.user.js)
- [GreasyFork](https://greasyfork.org/zh-CN/scripts/21416-bilibili%E7%9B%B4%E6%92%AD%E5%87%80%E5%8C%96)
-## 新 B 站粉丝牌助手
+## 新 B 站粉丝牌助手(持续更新版)
-作者:[一心向晚](https://github.com/XiaoMiku01)
+作者:[飞小RAN](https://github.com/xiaofeiTM233) (原作者:[一心向晚](https://github.com/XiaoMiku01))
-- [Github](https://github.com/XiaoMiku01/fansMedalHelper)
+- [Github](https://github.com/xiaofeiTM233/fansMedalHelper)([原仓库](https://github.com/XiaoMiku01/fansMedalHelper)已归档)
From 6549543eb3afa63faff7a4879492dd650105a5a6 Mon Sep 17 00:00:00 2001
From: andywang425 <56548456+andywang425@users.noreply.github.com>
Date: Fri, 22 May 2026 20:08:50 +0800
Subject: [PATCH 17/27] =?UTF-8?q?=F0=9F=90=B3=20chore(medalTasks):=20?=
=?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=97=A5=E5=BF=97=EF=BC=8C=E6=96=87=E6=9C=AC?=
=?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=8C=E5=87=8F=E7=82=B9=E8=B5=9EAPI?=
=?UTF-8?q?=E9=97=B4=E9=9A=94?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/components/LiveTasks.vue | 2 +-
.../liveTasks/medalTasks/danmuTask.ts | 25 ++++++++++++++--
.../liveTasks/medalTasks/lightTask.ts | 2 +-
.../liveTasks/medalTasks/likeTask.ts | 30 +++++++++++++++----
.../liveTasks/medalTasks/watchTask.ts | 29 ++++++++++++++----
5 files changed, 72 insertions(+), 16 deletions(-)
diff --git a/src/components/LiveTasks.vue b/src/components/LiveTasks.vue
index 0b2e4353..4c924bce 100644
--- a/src/components/LiveTasks.vue
+++ b/src/components/LiveTasks.vue
@@ -316,7 +316,7 @@ function handleRowClick(row: MedalInfoRow) {
- 直播任务相关信息可在
+ 粉丝团任务相关信息可在
= parsed.limit) continue
+ if (!parsed) {
+ this.logger.error(
+ `无法解析主播【${medal.anchor_info.nick_name}】(UID:${medal.medal.target_id})的发弹幕任务的每日上限信息,跳过发弹幕任务`,
+ )
+ continue
+ }
+
+ if (parsed.current >= parsed.limit) continue
const remaining = parsed.limit - parsed.current
// 失败补偿:最多额外发 3 条
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts b/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts
index 6efa49d7..e3e78072 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts
@@ -120,7 +120,7 @@ class LightTask extends MedalModule {
await this.like(medal, _.random(baseCount, baseCount + 5))
if (i < medals.length - 1) {
- await sleep(_.random(30000, 35000))
+ await sleep(_.random(15000, 20000))
}
}
}
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/likeTask.ts b/src/modules/dailyTasks/liveTasks/medalTasks/likeTask.ts
index c9b13c4f..60df241b 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/likeTask.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/likeTask.ts
@@ -83,16 +83,36 @@ class LikeTask extends MedalModule {
const medal = fansMedals[i]
const taskInfo = await this.fetchTaskInfo(medal.medal.target_id)
- if (!taskInfo) continue
+ if (!taskInfo) {
+ this.logger.error(
+ `无法获取主播【${medal.anchor_info.nick_name}】(UID:${medal.medal.target_id})的粉丝团升级任务信息,跳过点赞任务`,
+ )
+ continue
+ }
const item = MedalModule.findTaskInfo(taskInfo, 'like')
- if (!item || item.is_done) continue
+ if (!item) {
+ this.logger.error(
+ `无法在主播【${medal.anchor_info.nick_name}】(UID:${medal.medal.target_id})的粉丝团升级任务信息中找到点赞任务,跳过点赞任务`,
+ )
+ continue
+ }
+
+ if (item.is_done) continue
const parsed = MedalModule.parseDailyLimit(item.sub_title)
- if (!parsed || parsed.current >= parsed.limit) continue
+ if (!parsed) {
+ this.logger.error(
+ `无法解析主播【${medal.anchor_info.nick_name}】(UID:${medal.medal.target_id})的点赞任务的每日上限信息,跳过点赞任务`,
+ )
+ continue
+ }
- const times = MedalModule.parseTitleCount(item.title) ?? 30
+ if (parsed.current >= parsed.limit) continue
+ // 每轮点赞次数
+ const times = MedalModule.parseTitleCount(item.title) ?? 30
+ // 剩余点赞轮数
const remaining = parsed.limit - parsed.current
for (let j = 0; j < remaining; j++) {
if (isNowAfter(23, 55) || isNowBefore(0, 5)) {
@@ -104,7 +124,7 @@ class LikeTask extends MedalModule {
await this.like(medal, _.random(times, times + 5))
if (j < remaining - 1 || i < fansMedals.length - 1) {
- await sleep(_.random(30000, 35000))
+ await sleep(_.random(15000, 20000))
}
}
}
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/watchTask.ts b/src/modules/dailyTasks/liveTasks/medalTasks/watchTask.ts
index 6e8f6bfd..75d4c120 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/watchTask.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/watchTask.ts
@@ -366,18 +366,35 @@ class WatchTask extends MedalModule {
const uid = medal.medal.target_id
const taskInfo = await this.fetchTaskInfo(uid)
- if (!taskInfo) continue
+ if (!taskInfo) {
+ this.logger.error(
+ `无法获取主播【${medal.anchor_info.nick_name}】(UID:${uid})的粉丝团升级任务信息,跳过观看直播任务`,
+ )
+ continue
+ }
const item = MedalModule.findTaskInfo(taskInfo, 'watchLive')
- if (!item) continue
+ if (!item) {
+ this.logger.error(
+ `无法在主播【${medal.anchor_info.nick_name}】(UID:${uid})的粉丝团升级任务信息中找到观看直播任务,跳过观看直播任务`,
+ )
+ continue
+ }
+
+ if (item.is_done) continue
const parsed = MedalModule.parseDailyLimit(item.sub_title)
- if (!parsed) continue
- if (item.is_done || parsed.current >= parsed.limit) continue
+ if (!parsed) {
+ this.logger.error(
+ `无法解析主播【${medal.anchor_info.nick_name}】(UID:${uid})的观看直播任务的每日上限信息,跳过观看直播任务`,
+ )
+ continue
+ }
+ if (parsed.current >= parsed.limit) continue
const minutes = MedalModule.parseTitleCount(item.title) ?? 15
- // 目标秒数 = 每日上限 × minutes 分钟 + 60s 缓冲(补偿心跳计时误差)
+ // 目标秒数 = 每日上限 × minutes × 60s + 60s 缓冲(补偿心跳计时误差)
const targetSeconds = parsed.limit * minutes * 60 + 60
if ((this.config._watchingProgress[roomid] ?? 0) >= targetSeconds) {
@@ -390,7 +407,7 @@ class WatchTask extends MedalModule {
if (area_id > 0 && parent_area_id > 0) {
// area_id 和 parent_area_id 都大于 0,说明直播间设置了分区,心跳有效
this.logger.log(
- `粉丝勋章【${medal.medal.medal_name}】 开始直播间 ${roomid}(主播【${medal.anchor_info.nick_name}】,UID:${uid})的观看直播任务,目标时长 ${targetSeconds} 秒`,
+ `粉丝勋章【${medal.medal.medal_name}】 开始直播间 ${roomid}(主播【${medal.anchor_info.nick_name}】,UID:${uid})的观看直播任务,目标时长 ${targetSeconds / 60} 分钟)`,
)
await new RoomHeart(
From 0ea7a9b974466c34e1b9a51213f8ec85fb661c7f Mon Sep 17 00:00:00 2001
From: andywang425 <56548456+andywang425@users.noreply.github.com>
Date: Fri, 22 May 2026 23:36:37 +0800
Subject: [PATCH 18/27] =?UTF-8?q?=F0=9F=8E=88=20perf:=20=E9=81=BF=E5=85=8D?=
=?UTF-8?q?=E7=B2=89=E4=B8=9D=E5=9B=A2=E4=BB=BB=E5=8A=A1=E6=A8=A1=E5=9D=97?=
=?UTF-8?q?=E6=97=A0=E6=95=88=E9=87=8D=E5=A4=8D=E8=8E=B7=E5=8F=96=E7=B2=89?=
=?UTF-8?q?=E4=B8=9D=E5=8B=8B=E7=AB=A0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 如果【点亮熄灭勋章】模块实际上没有进行点亮操作,结束后不再重复获取粉丝勋章
---
src/library/storage/defaultValues.ts | 1 +
.../dailyTasks/liveTasks/medalTasks/MedalModule.ts | 14 +++++++++-----
.../dailyTasks/liveTasks/medalTasks/lightTask.ts | 14 ++++++++++----
src/stores/useModuleStore.ts | 1 +
src/types/storage.d.ts | 1 +
5 files changed, 22 insertions(+), 9 deletions(-)
diff --git a/src/library/storage/defaultValues.ts b/src/library/storage/defaultValues.ts
index 873710f0..fdadf4b8 100644
--- a/src/library/storage/defaultValues.ts
+++ b/src/library/storage/defaultValues.ts
@@ -63,6 +63,7 @@ const defaultValues: DefaultValues = {
],
isWhiteList: false,
roomidList: [],
+ _lastEffectiveCompleteTime: 0,
_lastCompleteTime: 0,
},
like: {
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/MedalModule.ts b/src/modules/dailyTasks/liveTasks/medalTasks/MedalModule.ts
index a03e222f..3301fb44 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/MedalModule.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/MedalModule.ts
@@ -188,8 +188,8 @@ class MedalModule extends BaseModule {
if (isTimestampToday(lightConfig._lastCompleteTime)) return
const moduleStore = useModuleStore()
- const currentStatus = moduleStore.moduleStatus.DailyTasks.LiveTasks.medalTasks.light
- if (currentStatus === 'done' || currentStatus === 'error') return
+ const lightStatus = moduleStore.moduleStatus.DailyTasks.LiveTasks.medalTasks.light
+ if (lightStatus === 'done' || lightStatus === 'error') return
this.logger.log('等待点亮熄灭勋章任务完成后再执行')
@@ -199,9 +199,13 @@ class MedalModule extends BaseModule {
(newStatus) => {
if (newStatus === 'done' || newStatus === 'error') {
unwatch()
- // 重新获取粉丝勋章(主要是为了获取最新的点亮状态、是否正在直播状态)
- // FansMedals 模块内部做了防重入,因此无需担心会重复获取
- moduleStore.rerunModule('Default_FansMedals')
+
+ if (lightConfig._lastEffectiveCompleteTime === lightConfig._lastCompleteTime) {
+ // 如果点亮熄灭勋章模块确实进行了点亮操作
+ // 重新获取粉丝勋章(主要是为了获取最新的点亮状态和直播状态)
+ // FansMedals 模块内部做了防重入,因此无需担心会重复获取
+ moduleStore.rerunModule('Default_FansMedals')
+ }
resolve()
}
},
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts b/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts
index e3e78072..3a01f592 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts
@@ -110,7 +110,7 @@ class LightTask extends MedalModule {
* @param medals
* @private
*/
- private async likeTask(medals: LiveData.FansMedalPanel.List[]) {
+ private async likeTask(medals: LiveData.FansMedalPanel.List[]): Promise {
for (let i = 0; i < medals.length; i++) {
const medal = medals[i]
const taskInfo = await this.fetchTaskInfo(medal.medal.target_id)
@@ -130,7 +130,7 @@ class LightTask extends MedalModule {
* @param medals
* @private
*/
- private async sendDanmuTask(medals: LiveData.FansMedalPanel.List[]) {
+ private async sendDanmuTask(medals: LiveData.FansMedalPanel.List[]): Promise {
let danmuIndex = 0
for (let i = 0; i < medals.length; i++) {
@@ -169,10 +169,16 @@ class LightTask extends MedalModule {
this.status = 'running'
MedalModule.clearTaskInfoCache()
const fansMedals = this.getMedals()
-
- await Promise.allSettled([this.sendDanmuTask(fansMedals[0]), this.likeTask(fansMedals[1])])
+ // 是否有需要点亮的粉丝勋章(是不是一次有效运行?)
+ const isEffectiveRun = fansMedals.some((medals) => medals.length > 0)
+ if (isEffectiveRun) {
+ await Promise.allSettled([this.sendDanmuTask(fansMedals[0]), this.likeTask(fansMedals[1])])
+ }
this.config._lastCompleteTime = tsm()
+ if (isEffectiveRun) {
+ this.config._lastEffectiveCompleteTime = this.config._lastCompleteTime
+ }
this.status = 'done'
this.logger.log('点亮熄灭勋章任务已完成')
} else {
diff --git a/src/stores/useModuleStore.ts b/src/stores/useModuleStore.ts
index dd6476a9..2370d3eb 100644
--- a/src/stores/useModuleStore.ts
+++ b/src/stores/useModuleStore.ts
@@ -95,6 +95,7 @@ export const useModuleStore = defineStore('module', () => {
medalTasks: {
light: () => {
moduleStatus.value.DailyTasks.LiveTasks.medalTasks.light = ''
+ moduleConfig.value.DailyTasks.LiveTasks.medalTasks.light._lastEffectiveCompleteTime = 0
moduleConfig.value.DailyTasks.LiveTasks.medalTasks.light._lastCompleteTime = 0
rerunModule('Default_FansMedals', true)
diff --git a/src/types/storage.d.ts b/src/types/storage.d.ts
index c1482ecb..25017d64 100644
--- a/src/types/storage.d.ts
+++ b/src/types/storage.d.ts
@@ -26,6 +26,7 @@ interface ModuleConfig {
danmuList: string[]
isWhiteList: boolean
roomidList: number[]
+ _lastEffectiveCompleteTime: number
_lastCompleteTime: number
}
like: {
From 1b7aadf766a983e51a4bc4ae1b7dc0bc41b36423 Mon Sep 17 00:00:00 2001
From: andywang425 <56548456+andywang425@users.noreply.github.com>
Date: Sat, 23 May 2026 00:19:17 +0800
Subject: [PATCH 19/27] =?UTF-8?q?=E2=9C=A8=20feat:=20=E9=A2=86=E5=8F=96?=
=?UTF-8?q?=E5=B9=B4=E5=BA=A6=E5=A4=A7=E4=BC=9A=E5=91=98=E6=9D=83=E7=9B=8A?=
=?UTF-8?q?=E6=94=AF=E6=8C=81=E9=A2=86=E5=8F=96=E8=B6=85=E7=BA=A7=E5=A4=A7?=
=?UTF-8?q?=E4=BC=9A=E5=91=98=E4=B8=93=E5=B1=9E=E6=9D=83=E7=9B=8A?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../otherTasks/getYearVipPrivilegeTask.ts | 168 +++++++++++-------
1 file changed, 102 insertions(+), 66 deletions(-)
diff --git a/src/modules/dailyTasks/otherTasks/getYearVipPrivilegeTask.ts b/src/modules/dailyTasks/otherTasks/getYearVipPrivilegeTask.ts
index a692fc5a..f86df1b4 100644
--- a/src/modules/dailyTasks/otherTasks/getYearVipPrivilegeTask.ts
+++ b/src/modules/dailyTasks/otherTasks/getYearVipPrivilegeTask.ts
@@ -15,7 +15,7 @@ class GetYearVipPrivilegeTask extends BaseModule {
useModuleStore().moduleStatus.DailyTasks.OtherTasks.getYearVipPrivilege = s
}
- private type2Name: Record = {
+ private type2Name: Record = {
1: '年度专享B币赠送',
2: '年度专享会员购优惠券',
3: '年度专享漫画礼包 - 漫画福利券',
@@ -27,6 +27,7 @@ class GetYearVipPrivilegeTask extends BaseModule {
15: '年度专享会员购星光宝盒88折券',
16: '大会员专享会员购10魔晶',
17: '大会员专享游戏优惠券',
+ 200: '超级大会员专享观影券',
}
/**
@@ -39,9 +40,15 @@ class GetYearVipPrivilegeTask extends BaseModule {
* 20: 尝试领取后提示:饿了么领取活动已经过期~;
* 21: 尝试领取后提示:超大会员身份状态异常
* 24,25,26,27: 尝试领取后提示:请求错误
- * 200: 尝试领取后提示:当前不符合领取条件
*/
- private blackList: number[] = [8, 14, 18, 19, 20, 21, 24, 25, 26, 27, 200]
+ private blackList = new Set([8, 14, 18, 19, 20, 21, 24, 25, 26, 27])
+
+ /**
+ * 根据权益类型获取权益名称
+ */
+ private getPrivilegeName(type: number): string {
+ return this.type2Name[type] ?? '未知'
+ }
/**
* 获取会员权益
@@ -72,18 +79,16 @@ class GetYearVipPrivilegeTask extends BaseModule {
const response = await BAPI.main.vip.receivePrivilege(type)
this.logger.log(`BAPI.main.vip.receivePrivilege(${type}) response`, response)
if (response.code === 0) {
- this.logger.log(
- `领取年度大会员权益(type = ${type}, ${this.type2Name[type] ?? '未知'})成功`,
- )
+ this.logger.log(`领取年度大会员权益(type = ${type}, ${this.getPrivilegeName(type)})成功`)
} else {
this.logger.error(
- `领取年度大会员权益(type = ${type}, ${this.type2Name[type] ?? '未知'})失败`,
+ `领取年度大会员权益(type = ${type}, ${this.getPrivilegeName(type)})失败`,
response.message,
)
}
} catch (error) {
this.logger.error(
- `领取年度大会员权益(type = ${type}, ${this.type2Name[type] ?? '未知'})出错`,
+ `领取年度大会员权益(type = ${type}, ${this.getPrivilegeName(type)})出错`,
error,
)
}
@@ -113,78 +118,109 @@ class GetYearVipPrivilegeTask extends BaseModule {
* 判断当前账号是否是年度大会员
*/
private isYearVip(): boolean {
- const biliStore = useBiliStore()
- const userInfo = biliStore.userInfo
- if (userInfo!.vip.status === 1 && userInfo!.vip.type === 2) {
- return true
- } else {
- this.logger.log('当前账号不是年度大会员,不领取权益')
+ const vip = useBiliStore().userInfo!.vip
+ return vip.status === 1 && (vip.type === 2 || vip.type === 0)
+ }
+
+ /**
+ * 判断当前账号是否是超级大会员(年度大会员的升级版)
+ */
+ private isSuperVip(): boolean {
+ const vip = useBiliStore().userInfo!.vip
+ return vip.status === 1 && vip.super_vip.is_super_vip // Or vip.type === 0
+ }
+
+ /**
+ * 判断当前账号是否满足权益领取条件
+ */
+ private canReceivePrivilege(
+ privilege: MainData.Vip.MyPrivilege.List,
+ isSuperVip: boolean,
+ ): boolean {
+ if (this.blackList.has(privilege.type)) {
+ return false
+ }
+
+ if (privilege.vip_type === 0 && !isSuperVip) {
+ this.logger.log(
+ `该权益(type = ${privilege.type}, ${this.getPrivilegeName(privilege.type)})需要超级大会员,当前账号不满足领取条件,跳过领取`,
+ )
return false
}
+
+ return true
}
public async run() {
this.logger.log('领取年度大会员权益模块开始运行')
- if (this.isYearVip()) {
- if (ts() >= this.config._nextReceiveTime) {
- // 当前时间已经超过了上次记录的下次领取时间,领取权益
- this.status = 'running'
- const list = await this.myPrivilege()
- if (list) {
- for (const i of list) {
- if (this.blackList.includes(i.type)) {
- // 不领取黑名单中的权益
- continue
+ if (!this.isYearVip()) {
+ this.logger.log('当前账号不是年度大会员,不领取权益')
+ return
+ }
+
+ if (ts() >= this.config._nextReceiveTime) {
+ // 当前时间已经超过了上次记录的下次领取时间,领取权益
+ this.status = 'running'
+ const list = await this.myPrivilege()
+ if (list) {
+ const isSuperVip = this.isSuperVip()
+
+ for (const privilege of list) {
+ if (!this.canReceivePrivilege(privilege, isSuperVip)) {
+ continue
+ }
+
+ if (privilege.state === 0) {
+ if (privilege.type === 9) {
+ // 专属等级加速包
+ await this.addExperience()
+ } else {
+ await this.receivePrivilege(privilege.type)
}
- if (i.state === 0) {
- if (i.type === 9) {
- // 专属等级加速包
- await this.addExperience()
+ } else if (privilege.state === 1) {
+ this.logger.log(
+ `该权益(type = ${privilege.type}, ${this.getPrivilegeName(privilege.type)})已经领取过了`,
+ )
+ } else {
+ // 需要完成任务才能领取
+ if (privilege.type === 9) {
+ const watchTaskConfig = useModuleStore().moduleConfig.DailyTasks.MainSiteTasks.watch
+ if (watchTaskConfig.enabled) {
+ this.logger.log('等待观看视频任务完成后再领取专属等级加速包(10主站经验)...')
+ watch(
+ () => watchTaskConfig._lastCompleteTime,
+ () => sleep(3000).then(() => this.addExperience()),
+ { once: true },
+ )
} else {
- await this.receivePrivilege(i.type)
- }
- } else if (i.state === 1) {
- this.logger.log(
- `该权益(type = ${i.type}, ${this.type2Name[i.type] ?? '未知'})已经领取过了`,
- )
- } else {
- // 需要完成任务才能领取
- if (i.type === 9) {
- const watchTaskConfig = useModuleStore().moduleConfig.DailyTasks.MainSiteTasks.watch
- if (watchTaskConfig.enabled) {
- this.logger.log('等待观看视频任务完成后再领取专属等级加速包(10主站经验)...')
- watch(
- () => watchTaskConfig._lastCompleteTime,
- () => sleep(3000).then(() => this.addExperience()),
- { once: true },
- )
- } else {
- this.logger.warn(
- '领取专属等级加速包(10主站经验)前需要观看任意一个视频,请打开【主站任务】中的【每日观看视频】,或是在运行脚本前手动观看',
- )
- }
+ this.logger.warn(
+ '领取专属等级加速包(10主站经验)前需要观看任意一个视频,请打开【主站任务】中的【每日观看视频】,或是在运行脚本前手动观看',
+ )
}
}
- await sleep(500)
}
- this.status = 'done'
- this.config._nextReceiveTime = Math.min(
- ...list.map((i) => i.period_end_unix).filter((unix) => unix > ts()),
- )
+ await sleep(500)
+ }
+ this.status = 'done'
+ const nextReceiveTimes = list.map((i) => i.period_end_unix).filter((unix) => unix > ts())
+ if (nextReceiveTimes.length > 0) {
+ this.config._nextReceiveTime = Math.min(...nextReceiveTimes)
+ } else {
+ this.config._nextReceiveTime = ts() + 86400
}
}
- // 等待下次运行或什么都不做
- const diff = this.config._nextReceiveTime - ts() + 3e5 // 增加5分钟延时
- if (diff < 86400) {
- this.logger.log(
- '领取年度大会员权益模块下次运行时间:',
- DateTime.fromSeconds(this.config._nextReceiveTime).toJSDate(),
- )
- this.nextRunTimer = setTimeout(() => this.run(), diff * 1000)
- } else {
- this.logger.log('距离下次领取年度大会员权益的时间超过一天,不计划下次运行')
- }
+ }
+
+ const diff = this.config._nextReceiveTime - ts() + 3e5 // 增加5分钟延时
+ if (diff < 86400) {
+ this.logger.log(
+ '领取年度大会员权益模块下次运行时间:',
+ DateTime.fromSeconds(this.config._nextReceiveTime).toJSDate(),
+ )
+ this.nextRunTimer = setTimeout(() => this.run(), diff * 1000)
+ } else {
+ this.logger.log('距离下次领取年度大会员权益的时间超过一天,不计划下次运行')
}
}
}
From 0f226f1be8edd5cf5332324b42fbd81a46d4e3f5 Mon Sep 17 00:00:00 2001
From: andywang425 <56548456+andywang425@users.noreply.github.com>
Date: Sat, 23 May 2026 00:27:17 +0800
Subject: [PATCH 20/27] =?UTF-8?q?=F0=9F=93=83=20docs:=20=E8=A1=A5=E5=85=85?=
=?UTF-8?q?=E7=82=B9=E4=BA=AE=E7=86=84=E7=81=AD=E5=8B=8B=E7=AB=A0=E5=86=85?=
=?UTF-8?q?=E7=BD=AE=E8=AF=B4=E6=98=8E?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/library/help-info/index.ts | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/src/library/help-info/index.ts b/src/library/help-info/index.ts
index f9119b09..e90877b0 100644
--- a/src/library/help-info/index.ts
+++ b/src/library/help-info/index.ts
@@ -113,10 +113,13 @@ const helpInfo: HelpInfo = {
]),
h('div', [
h('strong', '注意:'),
- h(
- 'span',
- '如果你启用了本功能,点赞、发弹幕和观看直播任务都会等点亮熄灭勋章任务完成后再执行。',
- ),
+ h('ul', [
+ h('li', '点赞、发弹幕和观看直播任务都会等点亮熄灭勋章任务完成后再执行。'),
+ h(
+ 'li',
+ '熄灭的勋章被点亮后,通过完成任务获得的亲密度会被储蓄。需要投喂一个粉丝灯牌才能领取。',
+ ),
+ ]),
]),
]),
},
From 598149da8247f163153c50eceeeb82b414080cdd Mon Sep 17 00:00:00 2001
From: andywang425 <56548456+andywang425@users.noreply.github.com>
Date: Sat, 23 May 2026 01:39:25 +0800
Subject: [PATCH 21/27] =?UTF-8?q?=F0=9F=8E=88=20perf:=20=E4=BD=BF=E7=94=A8?=
=?UTF-8?q?cookieStore=E9=87=8D=E6=9E=84Cookie=E5=B7=A5=E5=85=B7=E7=B1=BB?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/library/cookie/index.ts | 96 ++++++++++++++++++++--------------
src/modules/default/cookies.ts | 2 +-
2 files changed, 58 insertions(+), 40 deletions(-)
diff --git a/src/library/cookie/index.ts b/src/library/cookie/index.ts
index 3fd5f6cc..a623f330 100644
--- a/src/library/cookie/index.ts
+++ b/src/library/cookie/index.ts
@@ -2,15 +2,14 @@ class Cookie {
/**
* 获取所有 cookies
*/
- public static getAll(): Record {
- if (document.cookie === '') return {}
-
- const cookies = document.cookie.split('; ')
+ public static async getAll(): Promise> {
+ const cookies = await cookieStore.getAll()
const result: Record = {}
for (const cookie of cookies) {
- const [name, value] = cookie.split('=', 2)
- result[decodeURIComponent(name)] = decodeURIComponent(value ?? '')
+ if (cookie.name) {
+ result[cookie.name] = cookie.value ?? ''
+ }
}
return result
@@ -21,77 +20,96 @@ class Cookie {
* @param names cookie 名称数组
* @param defaultValue 当 cookie 不存在时使用的默认值,默认 undefined
*/
- public static get(names: string[], defaultValue?: string): Record
+ public static async get(
+ names: string[],
+ defaultValue?: string,
+ ): Promise>
/**
* 获取指定名称的一个 cookie
* @param name cookie 名称
* @param defaultValue 当 cookie 不存在时使用的默认值,默认 undefined
*/
- public static get(name: string, defaultValue?: string): string | undefined
+ public static async get(name: string, defaultValue?: string): Promise
/**
* 获取指定名称的一个或多个 cookies
* @param names cookie 名称或 cookie 名称数组
* @param defaultValue 当 cookie 不存在时使用的默认值,默认 undefined
*/
- public static get(names: string[] | string, defaultValue?: string) {
- const cookies = this.getAll()
-
+ public static async get(names: string[] | string, defaultValue?: string) {
if (Array.isArray(names)) {
const result: Record = {}
+ const cookies = await Promise.all(names.map((name) => cookieStore.get(name)))
- for (const name of names) {
- result[name] = cookies[name] ? cookies[name] : defaultValue
+ for (let i = 0; i < names.length; i++) {
+ result[names[i]] = cookies[i]?.value ?? defaultValue
}
return result
} else {
- return cookies[names] ? cookies[names] : defaultValue
+ const cookie = await cookieStore.get(names)
+ return cookie?.value ?? defaultValue
}
}
/**
- * 获取一组 cookies,如果有 cookie 未获取到,会反复获取直到超时为止
+ * 获取一组 cookies
*
- * TODO: 等 cookieStore 普及后使用监听取代轮询
+ * 如果有 cookie 未获取到,会通过监听 cookieStore 的 change 事件等待,直到全部获取或超时
*
* @param names 要获取的 cookie 名称数组
- * @param interval 获取间隔,默认 300 毫秒
* @param timeout 超时时间,若留空则永不超时
*/
public static getAsync(
names: T[],
- interval: number = 300,
timeout?: number,
): Promise> {
return new Promise((resolve, reject) => {
- let remainCookieNames = [...names]
- const cookies: Record = this.get(remainCookieNames)
+ const cookies = {} as Record
+ const nameSet = new Set(names)
+ let timeoutTimer: number | undefined
+
+ const cleanup = () => {
+ if (timeoutTimer !== undefined) clearTimeout(timeoutTimer)
+ cookieStore.removeEventListener('change', onChange)
+ }
- remainCookieNames = remainCookieNames.filter((r) => !cookies[r])
- if (remainCookieNames.length === 0) {
- resolve(>cookies)
- return
+ const checkDone = (): boolean => {
+ if (names.every((n) => n in cookies)) {
+ cleanup()
+ resolve(cookies)
+ return true
+ }
+ return false
}
- let timeoutTimer: number
+ const onChange = (event: CookieChangeEvent) => {
+ for (const c of event.changed) {
+ if (c.name && c.value && nameSet.has(c.name) && !(c.name in cookies)) {
+ cookies[c.name as T] = c.value
+ }
+ }
+ checkDone()
+ }
- const timer = setInterval(() => {
- Object.assign(cookies, this.get(remainCookieNames))
- remainCookieNames = remainCookieNames.filter((r) => !cookies[r])
+ cookieStore.addEventListener('change', onChange)
- if (remainCookieNames.length === 0) {
- if (timeout) clearTimeout(timeoutTimer)
- clearInterval(timer)
- resolve(>cookies)
+ Promise.all(names.map((name) => cookieStore.get(name))).then((initialCookies) => {
+ for (let i = 0; i < names.length; i++) {
+ const c = initialCookies[i]
+ if (c && c.value && !(names[i] in cookies)) {
+ cookies[names[i]] = c.value
+ }
}
- }, interval)
+ if (checkDone()) return
- if (timeout) {
- timeoutTimer = setTimeout(() => {
- clearInterval(timer)
- reject(new Error(`获取以下 cookie 超时:${remainCookieNames}`))
- }, timeout)
- }
+ if (timeout) {
+ timeoutTimer = setTimeout(() => {
+ const remain = names.filter((n) => !(n in cookies))
+ cleanup()
+ reject(new Error(`获取以下 cookie 超时:${remain}`))
+ }, timeout)
+ }
+ })
})
}
}
diff --git a/src/modules/default/cookies.ts b/src/modules/default/cookies.ts
index 6226dce3..dd718c38 100644
--- a/src/modules/default/cookies.ts
+++ b/src/modules/default/cookies.ts
@@ -13,7 +13,7 @@ class Cookies extends BaseModule {
* buvid3: 作为参数 buvid 在请求中出现,目前仅在主站 API 中使用
*/
private getCookies(): Promise {
- return Cookie.getAsync(['bili_jct', 'LIVE_BUVID', 'buvid3'], 300, 12e3)
+ return Cookie.getAsync(['bili_jct', 'LIVE_BUVID', 'buvid3'], 12e3)
}
public async run(): Promise {
From 27cc8e89d4f34c9c8c42fc0191d27c3233a7d95d Mon Sep 17 00:00:00 2001
From: andywang425 <56548456+andywang425@users.noreply.github.com>
Date: Sat, 23 May 2026 01:59:37 +0800
Subject: [PATCH 22/27] =?UTF-8?q?=F0=9F=93=83=20docs:=20=E8=A1=A5CHANGELOG?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
CHANGELOG.md | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 03150a3f..be27564c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,9 +2,21 @@
## [Unreleased]
+### 新增
+
+- 适配新版粉丝团任务,新增【点赞】和【发弹幕】任务
+- 【领取年度大会员权益】支持领取超级大会员专属权益
+
### 修复
- 修复【移除直播画面上方杂项】失效的bug
+- 修复跨天时间段判断错误导致部分任务在午夜附近表现异常的bug
+
+### 调整
+
+- 粉丝团任务【点亮熄灭勋章】、【点赞】、【发弹幕】和【观看直播】均拥有自己的黑白名单,互不影响
+- 【点亮熄灭勋章】和【发弹幕】分别拥有自己的弹幕配置
+- 💻 Cookie 工具类使用 `cookieStore` 重构
### 删除
From c2096b5f0ce6f9b519183050a6061cabe3add243 Mon Sep 17 00:00:00 2001
From: andywang425 <56548456+andywang425@users.noreply.github.com>
Date: Sat, 23 May 2026 15:46:53 +0800
Subject: [PATCH 23/27] =?UTF-8?q?=F0=9F=90=B3=20chore:=20=E8=A7=82?=
=?UTF-8?q?=E7=9C=8B=E7=9B=B4=E6=92=AD=E6=A8=A1=E5=9D=97=E4=B8=8D=E5=86=8D?=
=?UTF-8?q?=E8=AF=95=E5=9B=BE=E4=BB=8Eurl=E4=B8=AD=E6=8F=90=E5=8F=96?=
=?UTF-8?q?=E5=88=86=E5=8C=BA=E4=BF=A1=E6=81=AF=EF=BC=88=E6=80=BB=E6=98=AF?=
=?UTF-8?q?=E7=94=A8API=E8=8E=B7=E5=8F=96=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../liveTasks/medalTasks/MedalModule.ts | 6 ++++--
.../liveTasks/medalTasks/watchTask.ts | 18 ++----------------
2 files changed, 6 insertions(+), 18 deletions(-)
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/MedalModule.ts b/src/modules/dailyTasks/liveTasks/medalTasks/MedalModule.ts
index 3301fb44..c21e4379 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/MedalModule.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/MedalModule.ts
@@ -25,7 +25,7 @@ class MedalModule extends BaseModule {
/**
* 简单限流队列
*
- * 串行执行任务信息获取请求
+ * 串行执行获取任务信息请求
*/
private static taskInfoRequestQueue: Promise = Promise.resolve()
@@ -123,7 +123,9 @@ class MedalModule extends BaseModule {
MedalModule.taskInfoCache.clear()
}
- /** 将请求加入全局串行队列,并在真正发请求前等待一小段随机时间 */
+ /**
+ * 将获取任务信息请求加入全局串行队列
+ */
private static enqueueTaskInfoRequest(requester: () => Promise): Promise {
const task = MedalModule.taskInfoRequestQueue
.catch(() => undefined)
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/watchTask.ts b/src/modules/dailyTasks/liveTasks/medalTasks/watchTask.ts
index 75d4c120..1304a3fa 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/watchTask.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/watchTask.ts
@@ -284,22 +284,8 @@ class WatchTask extends MedalModule {
* @param roomid 房间号
* @returns [area_id, parent_area_id]
*/
- private async getAreaInfo(url: string, roomid: number): Promise<[number, number]> {
+ private async getAreaInfo(roomid: number): Promise<[number, number]> {
try {
- // 先尝试从 url 中提取 area_id 和 parent_area_id
- const urlObj = new URL(url)
- const area_id = Number(urlObj.searchParams.get('area_id'))
- const parent_area_id = Number(urlObj.searchParams.get('parent_area_id'))
-
- if (area_id > 0 && parent_area_id > 0) {
- this.logger.log(
- `已从直播间链接中(roomid = ${roomid})获取到分区数据(area_id = ${area_id},parent_area_id = ${parent_area_id})`,
- urlObj,
- )
- return [area_id, parent_area_id]
- }
-
- // 如果无法从 url 中提取到分区数据,调用 API 获取
const response = await BAPI.live.getInfoByRoom(roomid)
this.logger.log(`BAPI.live.getInfoByRoom(${roomid}) response`, response)
@@ -402,7 +388,7 @@ class WatchTask extends MedalModule {
continue
}
- const [area_id, parent_area_id] = await this.getAreaInfo(medal.room_info.url, roomid)
+ const [area_id, parent_area_id] = await this.getAreaInfo(roomid)
if (area_id > 0 && parent_area_id > 0) {
// area_id 和 parent_area_id 都大于 0,说明直播间设置了分区,心跳有效
From 7bc2666749a1f8ca79fa81d5b4d9d85df10ae401 Mon Sep 17 00:00:00 2001
From: andywang425 <56548456+andywang425@users.noreply.github.com>
Date: Sat, 23 May 2026 16:53:58 +0800
Subject: [PATCH 24/27] =?UTF-8?q?=F0=9F=90=9E=20fix:=20=E4=BF=AE=E6=94=B9?=
=?UTF-8?q?=E5=BC=B9=E5=B9=95=E5=8F=91=E9=80=81=E7=BB=93=E6=9E=9C=E5=88=A4?=
=?UTF-8?q?=E6=96=AD=E6=9D=A1=E4=BB=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- msg为空字符串才是发送成功
- 调整了一些容易发送失败的预设弹幕
---
CHANGELOG.md | 1 +
src/library/storage/defaultValues.ts | 8 ++++----
.../dailyTasks/liveTasks/medalTasks/danmuTask.ts | 10 +++++++---
.../dailyTasks/liveTasks/medalTasks/lightTask.ts | 12 ++++++++----
.../dailyTasks/otherTasks/coinToSilverTask.ts | 2 +-
5 files changed, 21 insertions(+), 12 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index be27564c..f48b0712 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@
- 修复【移除直播画面上方杂项】失效的bug
- 修复跨天时间段判断错误导致部分任务在午夜附近表现异常的bug
+- 修复弹幕发送结果判定不正确的bug
### 调整
diff --git a/src/library/storage/defaultValues.ts b/src/library/storage/defaultValues.ts
index fdadf4b8..8b265e4f 100644
--- a/src/library/storage/defaultValues.ts
+++ b/src/library/storage/defaultValues.ts
@@ -57,7 +57,7 @@ const defaultValues: DefaultValues = {
'[大笑]',
'[惊喜]',
'[OK]',
- '[哈欠]',
+ '[汤圆]',
'[墨镜]',
`[牛]`,
],
@@ -77,16 +77,16 @@ const defaultValues: DefaultValues = {
danmuList: [
'(⌒▽⌒)',
'( ̄▽ ̄)',
- '(=・ω・=)',
+ '(=・v・=)',
'(`・ω・´)',
- '(〜 ̄△ ̄)〜',
+ '(〜 ̄△ ̄)~',
'(・∀・)',
'(°∀°)ノ',
'╮( ̄▽ ̄)╭',
'_(:3」∠)_',
'(^・ω・^ )',
'(● ̄(エ) ̄●)',
- 'ε=ε=(ノ≧∇≦)ノ',
+ '(ノ≧∇≦)ノ',
'⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄',
'←◡←',
`(●'◡'●)ノ♥`,
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/danmuTask.ts b/src/modules/dailyTasks/liveTasks/medalTasks/danmuTask.ts
index f7859ac9..bad52b0b 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/danmuTask.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/danmuTask.ts
@@ -49,11 +49,15 @@ class DanmuTask extends MedalModule {
const response = await BAPI.live.sendMsg(danmu, room_id)
this.logger.log(`BAPI.live.sendMsg(${danmu}, ${room_id})`, response)
if (response.code === 0) {
- if (response.msg === 'k') {
- this.logger.warn(`发弹幕 ${logMessage} 异常,弹幕可能包含屏蔽词`)
- } else {
+ if (response.msg === '') {
this.logger.log(`发弹幕 ${logMessage} 成功`)
return true
+ } else if (response.msg === 'k') {
+ this.logger.warn(`发弹幕 ${logMessage} 异常,弹幕可能包含屏蔽词`)
+ } else if (response.msg === 'f') {
+ this.logger.warn(`发弹幕 ${logMessage} 异常,弹幕被过滤`)
+ } else {
+ this.logger.warn(`发弹幕 ${logMessage} 异常,未知错误:${response.msg}`)
}
} else {
this.logger.error(`发弹幕 ${logMessage} 失败`, response.message)
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts b/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts
index 3a01f592..8697384d 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts
@@ -89,11 +89,15 @@ class LightTask extends MedalModule {
const response = await BAPI.live.sendMsg(danmu, room_id)
this.logger.log(`BAPI.live.sendMsg(${danmu}, ${room_id})`, response)
if (response.code === 0) {
- if (response.msg === 'k') {
- this.logger.warn(`点亮熄灭勋章-发送弹幕 ${logMessage} 异常,弹幕可能包含屏蔽词`)
- } else {
- this.logger.log(`点亮熄灭勋章-发送弹幕 ${logMessage} 成功`)
+ if (response.msg === '') {
+ this.logger.log(`发弹幕 ${logMessage} 成功`)
return true
+ } else if (response.msg === 'k') {
+ this.logger.warn(`发弹幕 ${logMessage} 异常,弹幕可能包含屏蔽词`)
+ } else if (response.msg === 'f') {
+ this.logger.warn(`发弹幕 ${logMessage} 异常,弹幕被过滤`)
+ } else {
+ this.logger.warn(`发弹幕 ${logMessage} 异常,未知错误:${response.msg}`)
}
} else {
this.logger.error(`点亮熄灭勋章-发送弹幕 ${logMessage} 失败`, response.message)
diff --git a/src/modules/dailyTasks/otherTasks/coinToSilverTask.ts b/src/modules/dailyTasks/otherTasks/coinToSilverTask.ts
index d904e284..f30f5157 100644
--- a/src/modules/dailyTasks/otherTasks/coinToSilverTask.ts
+++ b/src/modules/dailyTasks/otherTasks/coinToSilverTask.ts
@@ -14,7 +14,7 @@ class CoinToSilverTask extends BaseModule {
private async exchange() {
try {
const response = await BAPI.live.coin2silver(this.config.num)
- this.logger.log(`BAPI.live.coin2silver{${this.config.num}} response`, response)
+ this.logger.log(`BAPI.live.coin2silver(${this.config.num}) response`, response)
if (response.code === 0) {
this.logger.log('硬币换银瓜子已完成,获得银瓜子:', response.data.silver)
this.config._lastCompleteTime = tsm()
From 44de610b133b25d5b03e9b78056c6cf51c2e3f39 Mon Sep 17 00:00:00 2001
From: andywang425 <56548456+andywang425@users.noreply.github.com>
Date: Sun, 24 May 2026 16:34:16 +0800
Subject: [PATCH 25/27] =?UTF-8?q?=F0=9F=8E=88=20perf:=20=E7=B2=89=E4=B8=9D?=
=?UTF-8?q?=E5=8B=8B=E7=AB=A0=E7=9B=B8=E5=85=B3=E4=BB=A3=E7=A0=81=E4=BC=98?=
=?UTF-8?q?=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../liveTasks/medalTasks/MedalModule.ts | 37 +++++++++++--------
.../liveTasks/medalTasks/lightTask.ts | 8 ++--
.../liveTasks/medalTasks/watchTask.ts | 4 --
src/modules/default/fansMedals.ts | 8 ++--
4 files changed, 28 insertions(+), 29 deletions(-)
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/MedalModule.ts b/src/modules/dailyTasks/liveTasks/medalTasks/MedalModule.ts
index c21e4379..6c5a8e82 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/MedalModule.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/MedalModule.ts
@@ -1,5 +1,4 @@
import BaseModule from '@/modules/BaseModule'
-import { storeToRefs } from 'pinia'
import { useBiliStore, useModuleStore } from '@/stores'
import { watch } from 'vue'
import type { MedalTaskSharedConfig, SharedMedalFilters } from './types'
@@ -48,11 +47,14 @@ class MedalModule extends BaseModule {
isLiving: (medal) => medal.room_info.living_status === 1,
}
- protected sortMedals(medals: LiveData.FansMedalPanel.List[]): LiveData.FansMedalPanel.List[] {
+ /**
+ * 对粉丝勋章列表进行排序
+ *
+ * 注意:该方法会修改传入的 medals 数组
+ */
+ protected sortMedals(medals: LiveData.FansMedalPanel.List[]): void {
const orderMap = arrayToMap(this.config.roomidList)
- return medals.sort(
- (a, b) => orderMap.get(a.room_info.room_id)! - orderMap.get(b.room_info.room_id)!,
- )
+ medals.sort((a, b) => orderMap.get(a.room_info.room_id)! - orderMap.get(b.room_info.room_id)!)
}
/**
@@ -62,19 +64,22 @@ class MedalModule extends BaseModule {
*/
protected waitForFansMedals(): Promise {
return new Promise((resolve) => {
- const { fansMedalsStatus } = storeToRefs(useBiliStore())
- if (fansMedalsStatus.value === 'loaded') {
+ const biliStore = useBiliStore()
+ if (biliStore.fansMedalsStatus === 'loaded') {
resolve(true)
} else {
- const unwatch = watch(fansMedalsStatus, (newValue) => {
- if (newValue === 'loaded') {
- unwatch()
- resolve(true)
- } else if (newValue === 'error') {
- unwatch()
- resolve(false)
- }
- })
+ const unwatch = watch(
+ () => biliStore.fansMedalsStatus,
+ (newValue) => {
+ if (newValue === 'loaded') {
+ unwatch()
+ resolve(true)
+ } else if (newValue === 'error') {
+ unwatch()
+ resolve(false)
+ }
+ },
+ )
}
})
}
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts b/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts
index 8697384d..3e6dc219 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts
@@ -90,14 +90,14 @@ class LightTask extends MedalModule {
this.logger.log(`BAPI.live.sendMsg(${danmu}, ${room_id})`, response)
if (response.code === 0) {
if (response.msg === '') {
- this.logger.log(`发弹幕 ${logMessage} 成功`)
+ this.logger.log(`点亮熄灭勋章-发弹幕 ${logMessage} 成功`)
return true
} else if (response.msg === 'k') {
- this.logger.warn(`发弹幕 ${logMessage} 异常,弹幕可能包含屏蔽词`)
+ this.logger.warn(`点亮熄灭勋章-发弹幕 ${logMessage} 异常,弹幕可能包含屏蔽词`)
} else if (response.msg === 'f') {
- this.logger.warn(`发弹幕 ${logMessage} 异常,弹幕被过滤`)
+ this.logger.warn(`点亮熄灭勋章-发弹幕 ${logMessage} 异常,弹幕被过滤`)
} else {
- this.logger.warn(`发弹幕 ${logMessage} 异常,未知错误:${response.msg}`)
+ this.logger.warn(`点亮熄灭勋章-发弹幕 ${logMessage} 异常,未知错误:${response.msg}`)
}
} else {
this.logger.error(`点亮熄灭勋章-发送弹幕 ${logMessage} 失败`, response.message)
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/watchTask.ts b/src/modules/dailyTasks/liveTasks/medalTasks/watchTask.ts
index 1304a3fa..dd5d648e 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/watchTask.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/watchTask.ts
@@ -38,10 +38,6 @@ class RoomHeart {
private logger = new Logger('RoomHeart')
- set status(s: ModuleStatusTypes) {
- useModuleStore().moduleStatus.DailyTasks.LiveTasks.medalTasks.watch = s
- }
-
/** 今日当前直播间已观看时间(秒) */
private watchedSeconds: number
diff --git a/src/modules/default/fansMedals.ts b/src/modules/default/fansMedals.ts
index 9e369479..85fc4f4f 100644
--- a/src/modules/default/fansMedals.ts
+++ b/src/modules/default/fansMedals.ts
@@ -67,11 +67,9 @@ class FansMedals extends BaseModule {
const taskValues = [medalTasks.light, medalTasks.like, medalTasks.danmu, medalTasks.watch]
if (
- (force ||
- // 开启了任意粉丝勋章相关功能且今天没完成过
- taskValues.some((t) => t.enabled && !isTimestampToday(t._lastCompleteTime, 0, 4))) &&
- // 如果正在获取粉丝勋章,不重复获取
- biliStore.fansMedalsStatus !== 'loading'
+ force ||
+ // 开启了任意粉丝勋章相关功能且今天没完成过
+ taskValues.some((t) => t.enabled && !isTimestampToday(t._lastCompleteTime, 0, 4))
) {
biliStore.fansMedalsStatus = 'loading'
biliStore.fansMedals = await this.getFansMedals()
From 85af210fb97b3997e0dea42e6858a6e0f7034054 Mon Sep 17 00:00:00 2001
From: andywang425 <56548456+andywang425@users.noreply.github.com>
Date: Sun, 24 May 2026 17:45:46 +0800
Subject: [PATCH 26/27] =?UTF-8?q?=E2=9C=A8=20feat:=20=E4=BC=98=E5=8C=96?=
=?UTF-8?q?=E5=BC=B9=E5=B9=95=E8=A1=A5=E5=8F=91=E6=9C=BA=E5=88=B6=EF=BC=8C?=
=?UTF-8?q?=E8=BE=BE=E5=88=B0=E4=B8=80=E5=AE=9A=E5=A4=B1=E8=B4=A5=E6=AC=A1?=
=?UTF-8?q?=E6=95=B0=E8=B7=B3=E8=BF=87=E5=BD=93=E5=89=8D=E5=8B=8B=E7=AB=A0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../liveTasks/medalTasks/MedalModule.ts | 3 +++
.../dailyTasks/liveTasks/medalTasks/danmuTask.ts | 16 +++++++++-------
.../dailyTasks/liveTasks/medalTasks/lightTask.ts | 12 ++++++++----
3 files changed, 20 insertions(+), 11 deletions(-)
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/MedalModule.ts b/src/modules/dailyTasks/liveTasks/medalTasks/MedalModule.ts
index 6c5a8e82..0adf075b 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/MedalModule.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/MedalModule.ts
@@ -11,6 +11,9 @@ import _ from 'lodash'
type JumpType = 'like' | 'sendDanmu' | 'watchLive' | 'feedLight' | 'sendGift'
class MedalModule extends BaseModule {
+ /** 最大弹幕补发次数 */
+ protected static readonly DANMU_RETRY_LIMIT = 3
+
/**
* 任务信息共享缓存
*
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/danmuTask.ts b/src/modules/dailyTasks/liveTasks/medalTasks/danmuTask.ts
index bad52b0b..b83dc0dc 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/danmuTask.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/danmuTask.ts
@@ -123,10 +123,8 @@ class DanmuTask extends MedalModule {
if (parsed.current >= parsed.limit) continue
- const remaining = parsed.limit - parsed.current
- // 失败补偿:最多额外发 3 条
- let target = remaining
- const maxTarget = remaining + 3
+ let target = parsed.limit - parsed.current
+ let failedCount = 0
for (let j = 0; j < target; j++) {
if (isNowAfter(23, 55) || isNowBefore(0, 5)) {
@@ -136,9 +134,13 @@ class DanmuTask extends MedalModule {
}
const danmuText = this.config.danmuList[danmuIndex++ % this.config.danmuList.length]
- const ok = await this.sendDanmu(medal, danmuText)
- if (!ok) {
- target = Math.min(target + 1, maxTarget)
+ if (!(await this.sendDanmu(medal, danmuText))) {
+ if (++failedCount > MedalModule.DANMU_RETRY_LIMIT) {
+ this.logger.warn(`当前直播间(${medal.room_info.room_id})弹幕发送失败次数过多,跳过`)
+ await sleep(_.random(6000, 8000))
+ break
+ }
+ target++
}
if (j < target - 1 || i < fansMedals.length - 1) {
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts b/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts
index 3e6dc219..a00b5e12 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts
@@ -142,15 +142,19 @@ class LightTask extends MedalModule {
const taskInfo = await this.fetchTaskInfo(medal.medal.target_id)
const item = MedalModule.findTaskInfo(taskInfo, 'sendDanmu')
// 从 title 解析点亮所需弹幕条数(如 "发弹幕10次" → 10),失败时 fallback 到 10
- const baseTarget = MedalModule.parseTitleCount(item?.title) ?? 10
- let target = baseTarget
- const maxTarget = baseTarget + 3 // 失败补偿,最多多发 3 条
+ let target = MedalModule.parseTitleCount(item?.title) ?? 10
+ let failedCount = 0
for (let j = 0; j < target; j++) {
const danmuText = this.config.danmuList[danmuIndex++ % this.config.danmuList.length]
if (!(await this.sendDanmu(medal, danmuText))) {
- target = Math.min(target + 1, maxTarget)
+ if (++failedCount > MedalModule.DANMU_RETRY_LIMIT) {
+ this.logger.warn(`当前直播间(${medal.room_info.room_id})弹幕发送失败次数过多,跳过`)
+ await sleep(_.random(6000, 8000))
+ break
+ }
+ target++
}
if (i < medals.length - 1 || j < target - 1) {
From 3f80a04d6fb4f31e09828aa2652054d564f771e8 Mon Sep 17 00:00:00 2001
From: andywang425 <56548456+andywang425@users.noreply.github.com>
Date: Sun, 24 May 2026 19:41:16 +0800
Subject: [PATCH 27/27] =?UTF-8?q?=F0=9F=90=B3=20chore:=20=E6=9B=B4?=
=?UTF-8?q?=E6=96=B0=E5=89=8D=E5=87=86=E5=A4=87?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
CHANGELOG.md | 2 ++
package-lock.json | 4 ++--
package.json | 2 +-
3 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f48b0712..3525b88c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,8 @@
## [Unreleased]
+## [7.2.9] - 2026-5-24
+
### 新增
- 适配新版粉丝团任务,新增【点赞】和【发弹幕】任务
diff --git a/package-lock.json b/package-lock.json
index 9550d308..c38115dd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "bilibili-live-tasks-helper",
- "version": "7.2.8",
+ "version": "7.2.9",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "bilibili-live-tasks-helper",
- "version": "7.2.8",
+ "version": "7.2.9",
"dependencies": {
"ajax-hook": "^3.0.3",
"crypto-js": "^4.2.0",
diff --git a/package.json b/package.json
index ad41eb57..ab87caa0 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "bilibili-live-tasks-helper",
"private": true,
- "version": "7.2.8",
+ "version": "7.2.9",
"type": "module",
"scripts": {
"dev": "vite",