diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..800eb98
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,8 @@
+root = true
+
+[*]
+charset = utf-8
+insert_final_newline = true
+indent_style = space
+indent_size = 2
+trim_trailing_whitespace = true
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..00c8fed
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,7 @@
+* text=auto
+
+*.bash text eol=lf
+*.bat text eol=crlf
+*.cmd text eol=crlf
+*.ps1 text eol=crlf
+*.sh text eol=lf
diff --git a/.prettierrc.json b/.prettierrc.json
index ecdf3e0..91b286a 100644
--- a/.prettierrc.json
+++ b/.prettierrc.json
@@ -4,5 +4,6 @@
"tabWidth": 2,
"singleQuote": true,
"printWidth": 100,
- "trailingComma": "none"
+ "trailingComma": "none",
+ "endOfLine": "auto"
}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e7e53f1..577cf89 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,31 @@
## [Unreleased]
+## [7.1.8] - 2024-8-28
+
+### 新增
+
+- 新页面和菜单项【设置】,可在此页面对控制面板宽度进行调整(日后会加入更多功能)
+- 适配B站实验室中的“屏幕适配”功能,确保开启时控制面板大小、位置依旧正确
+- 💻 支持wbi签名
+
+### 修复
+
+- 修正脚本内置说明中描述不准确的部分
+- 修复发送弹幕点亮勋章时无法正确识别因弹幕包含屏蔽词而失败的情况
+- 尝试修复会员专属等级加速包无法完成领取条件(看一个视频)的bug
+- 控制面板滚动条优化
+- 修复【移除元素】面板中帮助信息按钮点击后无效的问题
+
+### 删除
+
+- 移除【显示高能用户数量】(B站已有该功能)
+
+### 调整
+
+- 更新部分API,尽可能符合官方标准
+- 💻 配置prettier,editorconfig,gitattributes,解决换行符不一致导致的版本管理问题
+
## [7.1.7] - 2024-8-7
### 新增
@@ -58,7 +83,7 @@
### 调整
- 移除直播间幻星派对标志模块回归
-- Vue的VSCode官方拓展发生变化,修改相关文档和拓展推荐
+- 💻 Vue的VSCode官方拓展发生变化,修改相关文档和拓展推荐
- 暂时不领取type=14的大会员权益
## [7.1.3] - 2024-2-2
@@ -85,7 +110,7 @@
### 新增
-- 模块运行frame新增选项top
+- 💻 模块运行frame新增选项top
### 修复
@@ -104,8 +129,8 @@
### 新增
- 屏蔽挂机检测模块
-- 更多模块运行时机
-- 支持指定模块运行的frame,以及是否在默认模块运行完后运行
+- 💻 更多模块运行时机
+- 💻 支持指定模块运行的frame,以及是否在默认模块运行完后运行
### 调整
@@ -122,7 +147,7 @@
### 新增
- 移除元素板块,内含多个移除页面元素的功能
-- 模块运行时机概念,解决部分模块运行地太早导致的一些问题
+- 💻 模块运行时机概念,解决部分模块运行地太早导致的一些问题
### 调整
@@ -137,7 +162,7 @@
### 调整
-- 调整了Vue App注入时机和相关逻辑
+- 💻 调整了Vue App注入时机和相关逻辑
### 修复
@@ -168,7 +193,7 @@
### 新增
- 禁用P2P。
-- 新的工作流,自动编译master分支的脚本。
+- 💻 新的工作流,自动编译master分支的脚本。
### 调整
@@ -179,7 +204,7 @@
### 新增
- 体验优化板块,自动切换画质功能。
-- `Main BLTH`概念和模块`runMultiple`属性
+- 💻 `Main BLTH`概念和模块`runMultiple`属性
## [7.0.3] - 2023-7-31
diff --git a/README.md b/README.md
index 5d3350f..1b45b4b 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
-
+
-
Bilibili Live Tasks Helper
+
Bilibili Live Tasks Helper
# 环境要求
@@ -60,7 +60,6 @@
# 交流群
- QQ 频道:[点我加入 aw 的频道](https://qun.qq.com/qqweb/qunpro/share?_wv=3&_wwv=128&appChannel=share&inviteCode=1W7eVLs&businessType=9&from=181074&biz=ka&shareSource=5)。
-- 非官方电报群:[LaTiao01](https://t.me/LaTiao01)。
# 参与开发
diff --git a/package-lock.json b/package-lock.json
index d7291bf..2b256a8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "bilibili-live-tasks-helper",
- "version": "7.1.6",
+ "version": "7.1.8",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "bilibili-live-tasks-helper",
- "version": "7.1.6",
+ "version": "7.1.8",
"dependencies": {
"ajax-hook": "^3.0.3",
"crypto-js": "^4.2.0",
diff --git a/package.json b/package.json
index 0467430..32dfbc9 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "bilibili-live-tasks-helper",
"private": true,
- "version": "7.1.7",
+ "version": "7.1.8",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/scripts/minifyUserscript.js b/scripts/minifyUserscript.js
index b5518e3..6556d1f 100644
--- a/scripts/minifyUserscript.js
+++ b/scripts/minifyUserscript.js
@@ -1,11 +1,11 @@
/**
* @description 压缩编译后的用户脚本
*/
-import { promises as fs } from 'fs'
+import { readFile, writeFile } from 'node:fs/promises'
import { minify } from 'terser'
async function minifyUserscript(inputPath, outputPath) {
- const code = await fs.readFile(inputPath, 'utf-8')
+ const code = await readFile(inputPath, 'utf-8')
// 获取 Userscript metadata
const metadataMatch = code.match(/\/\/ ==UserScript==[\s\S]*?\/\/ ==\/UserScript==/)
@@ -21,7 +21,7 @@ async function minifyUserscript(inputPath, outputPath) {
// 加上 metadata
const result = `${metadata}\n${minified.code}`
- await fs.writeFile(outputPath, result, 'utf-8')
+ await writeFile(outputPath, result, 'utf-8')
console.log(`压缩完成: ${inputPath} -> ${outputPath}`)
}
diff --git a/src/App.vue b/src/App.vue
index 31f1d11..37ad70d 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -3,10 +3,11 @@ import { useUIStore } from './stores/useUIStore'
import PanelHeader from './components/PanelHeader.vue'
import PanelAside from './components/PanelAside.vue'
import PanelMain from './components/PanelMain.vue'
-import { dce, dq, waitForElement, isSelfTopFrame, topFrameDocuemntElement } from './library/dom'
+import { dce, dq, waitForElement, isSelfTopFrame, topFrameDocumentElement } from './library/dom'
import hotkeys from 'hotkeys-js'
import _ from 'lodash'
import Logger from './library/logger'
+import { unsafeWindow } from '$'
const uiStore = useUIStore()
@@ -22,15 +23,18 @@ let livePlayer: Element | null
// 显示/隐藏控制面板按钮
let button: HTMLButtonElement
/**
- * 设置控制面板的大小和位置
+ * 更新播放器的大小、位置和滚动条位置
*/
-function setPanelSize() {
+function updatePosition() {
const rect: DOMRect = livePlayer!.getBoundingClientRect()
- uiStore.baseStyleValue.top = rect.top + window.scrollY
- uiStore.baseStyleValue.left = rect.left + window.scrollX
- uiStore.baseStyleValue.height = rect.height
- uiStore.baseStyleValue.width = rect.width * 0.4
+ uiStore.livePlayerRect.top = rect.top
+ uiStore.livePlayerRect.left = rect.left
+ uiStore.livePlayerRect.height = rect.height
+ uiStore.livePlayerRect.width = rect.width
+ // 窗口滚动条位置需和播放器的大小、位置同步更新
+ uiStore.windowScrollPosition.x = unsafeWindow.scrollX
+ uiStore.windowScrollPosition.y = unsafeWindow.scrollY
}
/**
* 显示/隐藏控制面板按钮被点击
@@ -40,11 +44,11 @@ function buttonOnClick() {
button.innerText = uiStore.isShowPanelButtonText
}
// 节流,防止点击过快,减小渲染压力
-const throttleButtoOnClick = _.throttle(buttonOnClick, 300)
+const throttleButtonOnClick = _.throttle(buttonOnClick, 300)
// 播放器节点出现在最初的html中,可以直接获取
livePlayer = dq('#live-player-ctnr')
if (livePlayer) {
- setPanelSize()
+ updatePosition()
// 查找播放器上面的 header
// 节点#player-ctnr在初始html中出现
waitForElement(dq('#player-ctnr')!, '.left-ctnr.left-header-area', 10e3)
@@ -52,7 +56,7 @@ if (livePlayer) {
// 创建显示/隐藏控制面板按钮
button = dce('button')
button.setAttribute('class', 'blth_btn')
- button.onclick = throttleButtoOnClick
+ button.onclick = throttleButtonOnClick
button.innerText = uiStore.isShowPanelButtonText
playerHeaderLeft.append(button)
if (!isSelfTopFrame()) {
@@ -62,22 +66,22 @@ if (livePlayer) {
hotkeys(
'alt+b',
{
- element: topFrameDocuemntElement()
+ element: topFrameDocumentElement()
},
- throttleButtoOnClick
+ throttleButtonOnClick
)
}
- hotkeys('alt+b', throttleButtoOnClick)
+ hotkeys('alt+b', throttleButtonOnClick)
})
.catch((e: Error) => logger.error(e))
// 监听页面缩放,调整控制面板大小
// 因为这个操作频率不高就不节流或防抖了
- window.addEventListener('resize', () => setPanelSize())
+ window.addEventListener('resize', () => updatePosition())
// 监听 html 根节点和 body 节点
- // 主要是为了适配滚动条的显示/隐藏和实验室中的功能
- const observer = new MutationObserver(() => setPanelSize())
- observer.observe(document.documentElement, { attributes: true })
+ // 适配播放器网页模式
+ const observer = new MutationObserver(() => updatePosition())
observer.observe(document.body, { attributes: true })
+ observer.observe(document.documentElement, { attributes: true })
// 准备完毕,显示控制面板
if (isShowPanel) {
@@ -90,24 +94,24 @@ if (livePlayer) {
-
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
@@ -148,13 +152,8 @@ if (livePlayer) {
padding-bottom: var(--main-top-botton-padding);
}
-.fade-enter-active,
-.fade-leave-active {
- transition: opacity 0.1s ease;
-}
-
-.fade-enter-from,
-.fade-leave-to {
- opacity: 0;
+/* PanelMain切换时的动画效果 */
+.fade-enter-active {
+ animation: fade-in linear 0.2s;
}
diff --git a/src/assets/css/base.css b/src/assets/css/base.css
index f7e6868..c7d3dcd 100644
--- a/src/assets/css/base.css
+++ b/src/assets/css/base.css
@@ -33,3 +33,39 @@
.el-message-box li {
list-style: initial;
}
+
+/**
+ 适配实验室的“屏幕适配”功能
+ 原理:屏幕适配css中的zoom值为n,此处就设置为1/n
+ */
+html[lab-style*='adaptive'] {
+ @media screen and (min-width: 1930px) {
+ .base {
+ zoom: calc(15 / 16);
+ }
+ }
+
+ @media screen and (min-width: 2058px) {
+ .base {
+ zoom: calc(3 / 4);
+ }
+ }
+
+ @media screen and (min-width: 2570px) {
+ .base {
+ zoom: calc(2 / 3);
+ }
+ }
+
+ @media screen and (min-width: 3210px) {
+ .base {
+ zoom: calc(1 / 2);
+ }
+ }
+
+ @media screen and (min-width: 3850px) {
+ .base {
+ zoom: calc(15 / 32);
+ }
+ }
+}
diff --git a/src/components/EnhanceExperience.vue b/src/components/EnhanceExperience.vue
index 92b41d9..3dc356c 100644
--- a/src/components/EnhanceExperience.vue
+++ b/src/components/EnhanceExperience.vue
@@ -11,7 +11,7 @@ const qualityDescList = ['原画', '蓝光PRO', '蓝光', '超清PRO', '超清',
-
+
-
+
-
+
-
+
-
+
-
-
-
-
-
-
diff --git a/src/components/LiveTasks.vue b/src/components/LiveTasks.vue
index cc27b09..b164efe 100644
--- a/src/components/LiveTasks.vue
+++ b/src/components/LiveTasks.vue
@@ -136,19 +136,17 @@ function handleSelectionChange(selectedRows: MedalInfoRow[]) {
config.medalTasks.roomidList = selectedRows.map((row) => row.roomid)
}
-function handleRowClick(row: MedalInfoRow, _column: any, event: PointerEvent) {
- // 如果没点到链接,切换当前行的选择状态
- if (!(event.target as HTMLElement).className.startsWith('el-link')) {
- // @ts-expect-error
- medalInfoTableRef.value?.toggleRowSelection(row, undefined)
- }
+function handleRowClick(row: MedalInfoRow) {
+ // 切换当前行的选择状态
+ // @ts-expect-error
+ medalInfoTableRef.value?.toggleRowSelection(row, undefined)
}
-
+
@@ -157,7 +155,7 @@ function handleRowClick(row: MedalInfoRow, _column: any, event: PointerEvent) {
-
+
@@ -166,7 +164,7 @@ function handleRowClick(row: MedalInfoRow, _column: any, event: PointerEvent) {
-
+
@@ -175,7 +173,7 @@ function handleRowClick(row: MedalInfoRow, _column: any, event: PointerEvent) {
-
+
@@ -193,7 +191,7 @@ function handleRowClick(row: MedalInfoRow, _column: any, event: PointerEvent) {
-
+
@@ -204,7 +202,7 @@ function handleRowClick(row: MedalInfoRow, _column: any, event: PointerEvent) {
-
+
-
+
-
+
{{ scope.row.roomid }}
diff --git a/src/components/MainSiteTasks.vue b/src/components/MainSiteTasks.vue
index 8b6f183..add9c8d 100644
--- a/src/components/MainSiteTasks.vue
+++ b/src/components/MainSiteTasks.vue
@@ -12,21 +12,21 @@ const status = moduleStore.moduleStatus.DailyTasks.MainSiteTasks
-
+
-
+
-
+
@@ -37,7 +37,7 @@ const status = moduleStore.moduleStatus.DailyTasks.MainSiteTasks
-
+
diff --git a/src/components/OtherTasks.vue b/src/components/OtherTasks.vue
index ba595d7..664825d 100644
--- a/src/components/OtherTasks.vue
+++ b/src/components/OtherTasks.vue
@@ -26,7 +26,7 @@ const status = moduleStore.moduleStatus.DailyTasks.OtherTasks
-
+
花费硬币
@@ -38,7 +38,7 @@ const status = moduleStore.moduleStatus.DailyTasks.OtherTasks
-
+
diff --git a/src/components/PanelAside.vue b/src/components/PanelAside.vue
index 791768c..95de52d 100644
--- a/src/components/PanelAside.vue
+++ b/src/components/PanelAside.vue
@@ -44,6 +44,11 @@ const items: MenuItem[] = [
icon: 'Scissor',
title: '移除元素',
index: 'RemoveElement'
+ },
+ {
+ icon: 'Setting',
+ title: '设置',
+ index: 'ScriptSettings'
}
]
@@ -84,3 +89,9 @@ const items: MenuItem[] = [
+
+
diff --git a/src/components/PanelMain.vue b/src/components/PanelMain.vue
index be950b8..def90c9 100644
--- a/src/components/PanelMain.vue
+++ b/src/components/PanelMain.vue
@@ -4,6 +4,7 @@ import LiveTasks from './LiveTasks.vue'
import OtherTasks from './OtherTasks.vue'
import EnhanceExperience from './EnhanceExperience.vue'
import RemoveElement from './RemoveElement.vue'
+import ScriptSettings from './ScriptSettings.vue'
import { defineComponent } from 'vue'
// 注册对当前组件实例可用的组件
@@ -13,7 +14,8 @@ export default defineComponent({
LiveTasks,
OtherTasks,
EnhanceExperience,
- RemoveElement
+ RemoveElement,
+ ScriptSettings
}
})
diff --git a/src/components/RemoveElement.vue b/src/components/RemoveElement.vue
index 1737dd2..fff018b 100644
--- a/src/components/RemoveElement.vue
+++ b/src/components/RemoveElement.vue
@@ -1,5 +1,6 @@
+
+
+
+
+
+ 控制面板宽度
+
+
+
+
+
+
+
diff --git a/src/library/bili-api/api.d.ts b/src/library/bili-api/api.d.ts
index 5aa19d2..68b69ca 100644
--- a/src/library/bili-api/api.d.ts
+++ b/src/library/bili-api/api.d.ts
@@ -33,7 +33,7 @@ interface BapiMethods {
reply_mid?: number,
reply_attr?: number,
replay_dmid?: unknown,
- statistics?: { appId: number; platform: number }
+ statistics?: string
) => Promise
likeReport: (
room_id: number,
@@ -41,19 +41,11 @@ interface BapiMethods {
click_time?: number,
visit_id?: string
) => Promise
- getInfoByRoom: (room_id: number) => Promise
+ getInfoByRoom: (room_id: number, web_location?: string) => Promise
getUserTaskProgress: (target_id?: number) => Promise
userTaskReceiveRewards: (target_id?: number) => Promise
silver2coin: (visit_id?: string) => Promise
coin2silver: (num: number, platform?: string, visit_id?: string) => Promise
- queryContributionRank: (
- ruid: number,
- room_id: number,
- page: number,
- page_size: number,
- type?: string,
- _switch?: string
- ) => Promise
wearMedal: (medal_id: number, visit_id?: string) => Promise
}
liveTrace: {
@@ -81,29 +73,36 @@ interface BapiMethods {
nav: () => Promise
reward: () => Promise
dynamicAll: (
- type: string,
+ type?: string,
page?: number,
timezone_offset?: number,
- features?: string
+ platform?: string,
+ features?: string,
+ web_location?: string,
+ x_bili_device_req_json?: string,
+ x_bili_web_req_json?: string
) => Promise
videoHeartbeat: (
- aid: string,
- cid?: string,
+ aid: number,
+ cid?: number,
+ type?: number,
+ sub_type?: number,
+ dt?: number,
+ play_type?: number,
realtime?: number,
played_time?: number,
real_played_time?: number,
refer_url?: string,
quality?: number,
video_duration?: number,
- type?: number,
- sub_type?: number,
- play_type?: number,
- dt?: number,
last_play_progress_time?: number,
max_play_progress_time?: number,
+ outer?: number,
spmid?: string,
from_spmid?: string,
- extra?: string
+ session?: string,
+ extra?: string,
+ web_location?: number
) => Promise
share: (
aid: string,
@@ -124,7 +123,7 @@ interface BapiMethods {
) => Promise
videoRelation: (aid: string, bvid?: string) => Promise
vip: {
- myPrivilege: () => Promise
+ myPrivilege: (web_location?: string) => Promise
receivePrivilege: (type: number, platform?: string) => Promise
addExperience: () => Promise
}
diff --git a/src/library/bili-api/data.d.ts b/src/library/bili-api/data.d.ts
index 1666b97..977419a 100644
--- a/src/library/bili-api/data.d.ts
+++ b/src/library/bili-api/data.d.ts
@@ -888,83 +888,6 @@ declare namespace LiveData {
tid: string
}
}
-
- namespace QueryContributionRank {
- interface Data {
- count: number
- item: Item[]
- own_info: OwnInfo
- tips_text: string
- count_format: string
- desc_format: string
- config: Config
- }
-
- interface Item {
- uid: number
- name: string
- face: string
- rank: number
- score: number
- medal_info: MedalInfo
- guard_level: number
- wealth_level: number
- is_mystery: boolean
- uinfo: Uinfo
- }
- interface MedalInfo {
- guard_level: number
- medal_color_start: number
- medal_color_end: number
- medal_color_border: number
- medal_name: string
- level: number
- target_id: number
- is_light: number
- }
- interface Uinfo {
- uid: number
- base: Base
- }
- interface Base {
- name: string
- face: string
- name_color: number
- is_mystery: boolean
- risk_ctrl_info: RiskCtrlInfo
- origin_info: OriginInfo
- }
- interface RiskCtrlInfo {
- name: string
- face: string
- }
- interface OriginInfo {
- name: string
- face: string
- }
-
- interface OwnInfo {
- uid: number
- name: string
- face: string
- rank: number
- score: number
- rank_text: string
- need_score: number
- medal_info: MedalInfo
- guard_level: number
- wealth_level: number
- score_lead: number
- score_behind: number
- is_mystery: boolean
- uinfo: Uinfo
- }
-
- interface Config {
- deadline_ts: number
- value_text: string
- }
- }
}
declare namespace LiveTraceData {
@@ -1023,13 +946,14 @@ declare namespace MainData {
is_senior_member: number
wbi_img: WbiImg
is_jury: boolean
+ name_render: any
}
interface LevelInfo {
current_level: number
current_min: number
current_exp: number
- next_exp: string
+ next_exp: number | string
}
interface Official {
@@ -1051,6 +975,7 @@ declare namespace MainData {
expire: number
image_enhance: string
image_enhance_frame: string
+ n_pid: number
}
interface VipLabel {
@@ -1082,6 +1007,7 @@ declare namespace MainData {
tv_vip_status: number
tv_vip_pay_type: number
tv_due_date: number
+ avatar_icon: AvatarIcon
}
interface Label {
@@ -1099,6 +1025,12 @@ declare namespace MainData {
img_label_uri_hant_static: string
}
+ interface AvatarIcon {
+ icon_resource: IconResource
+ }
+
+ interface IconResource {}
+
interface Wallet {
mid: number
bcoin_balance: number
@@ -1159,14 +1091,14 @@ declare namespace MainData {
interface Modules {
module_author: ModuleAuthor
module_dynamic: ModuleDynamic
- module_interaction?: ModuleInteraction
module_more: ModuleMore
module_stat: ModuleStat
- module_fold?: ModuleFold
+ module_interaction?: ModuleInteraction
}
interface ModuleAuthor {
avatar: Avatar
+ decoration_card?: DecorationCard
face: string
face_nft: boolean
following: boolean
@@ -1182,14 +1114,12 @@ declare namespace MainData {
pub_ts: number
type: string
vip: Vip
- decorate?: Decorate
}
interface Avatar {
container_size: ContainerSize
fallback_layers: FallbackLayers
mid: string
- layers?: Layer2[]
}
interface ContainerSize {
@@ -1238,8 +1168,8 @@ declare namespace MainData {
interface Tags {
AVATAR_LAYER?: AvatarLayer
GENERAL_CFG?: GeneralCfg
- ICON_LAYER?: IconLayer
PENDENT_LAYER?: PendentLayer
+ ICON_LAYER?: IconLayer
}
interface AvatarLayer {}
@@ -1260,10 +1190,10 @@ declare namespace MainData {
boxSizing?: string
}
- interface IconLayer {}
-
interface PendentLayer {}
+ interface IconLayer {}
+
interface Resource {
res_image: ResImage
res_type: number
@@ -1285,107 +1215,33 @@ declare namespace MainData {
url: string
}
- interface Layer2 {
- is_critical_group?: boolean
- layers: Layer3[]
- }
-
- interface Layer3 {
- general_spec: GeneralSpec2
- layer_config: LayerConfig2
- resource: Resource2
- visible: boolean
- }
-
- interface GeneralSpec2 {
- pos_spec: PosSpec2
- render_spec: RenderSpec2
- size_spec: SizeSpec2
- }
-
- interface PosSpec2 {
- axis_x: number
- axis_y: number
- coordinate_pos: number
- }
-
- interface RenderSpec2 {
- opacity: number
- }
-
- interface SizeSpec2 {
- height: number
- width: number
- }
-
- interface LayerConfig2 {
- is_critical?: boolean
- tags: Tags2
- }
-
- interface Tags2 {
- AVATAR_LAYER?: AvatarLayer2
- GENERAL_CFG?: GeneralCfg2
- PENDENT_LAYER?: PendentLayer2
- ICON_LAYER?: IconLayer2
- }
-
- interface AvatarLayer2 {}
-
- interface GeneralCfg2 {
- config_type: number
- general_config: GeneralConfig2
- }
-
- interface GeneralConfig2 {
- web_css_style: WebCssStyle2
- }
-
- interface WebCssStyle2 {
- 'background-color'?: string
- border?: string
- borderRadius: string
- boxSizing?: string
- }
-
- interface PendentLayer2 {}
-
- interface IconLayer2 {}
-
- interface Resource2 {
- res_image?: ResImage2
- res_type: number
- res_animation?: ResAnimation
- }
-
- interface ResImage2 {
- image_src: ImageSrc2
- }
-
- interface ImageSrc2 {
- local?: number
- src_type: number
- placeholder?: number
- remote?: Remote2
- }
-
- interface Remote2 {
- bfs_style: string
- url: string
- }
-
- interface ResAnimation {
- webp_src: WebpSrc
+ interface DecorationCard {
+ big_card_url: string
+ card_type: number
+ card_type_name: string
+ card_url: string
+ fan: Fan
+ id: number
+ image_enhance: string
+ item_id: number
+ jump_url: string
+ name: string
}
- interface WebpSrc {
- remote: Remote3
- src_type: number
+ interface Fan {
+ color?: string
+ color_format?: ColorFormat
+ is_fan?: number
+ name?: string
+ num_desc?: string
+ number?: number
}
- interface Remote3 {
- bfs_style: string
- url: string
+ interface ColorFormat {
+ colors: string[]
+ end_point: string
+ gradients: number[]
+ start_point: string
}
interface OfficialVerify {
@@ -1398,6 +1254,7 @@ declare namespace MainData {
image: string
image_enhance: string
image_enhance_frame: string
+ n_pid: number
name: string
pid: number
}
@@ -1428,27 +1285,40 @@ declare namespace MainData {
use_img_label: boolean
}
- interface Decorate {
- card_url: string
- fan: Fan
- id: number
+ interface ModuleDynamic {
+ additional?: Additional
+ desc?: Desc
+ major: Major
+ topic?: Topic
+ }
+
+ interface Additional {
+ common: Common
+ type: string
+ }
+
+ interface Common {
+ button: Button
+ cover: string
+ desc1: string
+ desc2: string
+ head_text: string
+ id_str: string
jump_url: string
- name: string
- type: number
+ style: number
+ sub_type: string
+ title: string
}
- interface Fan {
- color: string
- is_fan: boolean
- num_str: string
- number: number
+ interface Button {
+ jump_style: JumpStyle
+ jump_url: string
+ type: number
}
- interface ModuleDynamic {
- additional: any
- desc?: Desc
- major: Major
- topic?: Topic
+ interface JumpStyle {
+ icon_url: string
+ text: string
}
interface Desc {
@@ -1460,15 +1330,6 @@ declare namespace MainData {
orig_text: string
text: string
type: string
- emoji?: Emoji
- jump_url?: string
- }
-
- interface Emoji {
- icon_url: string
- size: number
- text: string
- type: number
}
interface Major {
@@ -1508,35 +1369,6 @@ declare namespace MainData {
name: string
}
- interface ModuleInteraction {
- items: Item2[]
- }
-
- interface Item2 {
- desc: Desc2
- type: number
- }
-
- interface Desc2 {
- rich_text_nodes: RichTextNode2[]
- text: string
- }
-
- interface RichTextNode2 {
- orig_text: string
- rid?: string
- text: string
- type: string
- emoji?: Emoji2
- }
-
- interface Emoji2 {
- icon_url: string
- size: number
- text: string
- type: number
- }
-
interface ModuleMore {
three_point_items: ThreePointItem[]
}
@@ -1568,11 +1400,33 @@ declare namespace MainData {
status: boolean
}
- interface ModuleFold {
- ids: string[]
- statement: string
+ interface ModuleInteraction {
+ items: Item2[]
+ }
+
+ interface Item2 {
+ desc: Desc2
+ type: number
+ }
+
+ interface Desc2 {
+ rich_text_nodes: RichTextNode2[]
+ text: string
+ }
+
+ interface RichTextNode2 {
+ orig_text: string
+ rid?: string
+ text: string
+ type: string
+ emoji?: Emoji
+ }
+
+ interface Emoji {
+ icon_url: string
+ size: number
+ text: string
type: number
- users: any[]
}
}
@@ -1600,6 +1454,9 @@ declare namespace MainData {
is_senior_member: number
format060102: number
is_overdue_vip: boolean
+ vip_status: number
+ vip_type: number
+ keeptime_end: number
}
interface List {
@@ -1615,6 +1472,12 @@ declare namespace MainData {
app_describe: string
recive_state: number
salary_type: number
+ exp_params?: ExpParams
+ }
+
+ interface ExpParams {
+ exp_group_tag: string
+ hit_value: number
}
}
namespace AddExperience {
diff --git a/src/library/bili-api/index.ts b/src/library/bili-api/index.ts
index 4da3326..4b7fbdd 100644
--- a/src/library/bili-api/index.ts
+++ b/src/library/bili-api/index.ts
@@ -1,8 +1,7 @@
import Request from '../request'
import type { Requests, BapiMethods } from './api'
import { useBiliStore } from '@/stores/useBiliStore'
-import type { BiliCookies } from '@/types'
-import { packFormData } from '../utils'
+import { packFormData, uuid, wbiSign } from '../utils'
import { ts, tsm } from '../luxon'
const request: Requests = {
@@ -49,12 +48,13 @@ const BAPI: BapiMethods = {
reply_mid = 0,
reply_attr = 0,
replay_dmid = '',
- statistics = { appId: 100, platform: 5 }
+ statistics = '{"appId":100,"platform":5}'
) => {
const biliStore = useBiliStore()
- const bili_jct = (biliStore.cookies as BiliCookies).bili_jct
- return request.live.post('/msg/send', undefined, {
- data: packFormData({
+ const bili_jct = biliStore.cookies!.bili_jct
+ return request.live.post(
+ '/msg/send',
+ packFormData({
bubble,
msg,
color,
@@ -64,45 +64,45 @@ const BAPI: BapiMethods = {
reply_mid,
reply_attr,
replay_dmid,
- statistics: JSON.stringify(statistics),
+ statistics,
fontsize,
rnd: ts(),
roomid,
csrf: bili_jct,
csrf_token: bili_jct
- }),
- headers: {
- 'Content-Type': 'multipart/form-data'
- }
- })
+ })
+ )
},
likeReport: (room_id, anchor_id, click_time = 1, visit_id = '') => {
const biliStore = useBiliStore()
- const bili_jct = (biliStore.cookies as BiliCookies).bili_jct
- const uid = biliStore.BilibiliLive?.UID
+ const bili_jct = biliStore.cookies!.bili_jct
+ const uid = biliStore.BilibiliLive!.UID
return request.live.post('/xlive/app-ucenter/v1/like_info_v3/like/likeReportV3', {
click_time,
room_id,
- anchor_id,
uid,
- ts: ts(),
- csrf: bili_jct,
+ anchor_id,
csrf_token: bili_jct,
+ csrf: bili_jct,
visit_id
})
},
/**
* 该API只在带有多层iframe(背景很好看)的直播间中被使用,但参数填任意直播间均可
*/
- getInfoByRoom: (room_id) => {
- return request.live.get('/xlive/web-room/v1/index/getInfoByRoom', {
- room_id
- })
+ getInfoByRoom: (room_id, web_location = '444.8') => {
+ return request.live.get(
+ '/xlive/web-room/v1/index/getInfoByRoom',
+ wbiSign({
+ room_id,
+ web_location
+ })
+ )
},
getUserTaskProgress: (target_id = 11153765) => {
// 该 API 是 APP API,但也可以使用 web 的身份校验方式
const biliStore = useBiliStore()
- const bili_jct = (biliStore.cookies as BiliCookies).bili_jct
+ const bili_jct = biliStore.cookies!.bili_jct
return request.live.get('/xlive/app-ucenter/v1/userTask/GetUserTaskProgress', {
target_id,
csrf: bili_jct,
@@ -113,7 +113,7 @@ const BAPI: BapiMethods = {
// 该 API 是 APP API,但也可以使用 web 的身份校验方式,将 actionKey 设置为 csrf 即可
// 而且似乎不需要观看直播5分钟,只要发5条弹幕就行了
const biliStore = useBiliStore()
- const bili_jct = (biliStore.cookies as BiliCookies).bili_jct
+ const bili_jct = biliStore.cookies!.bili_jct
return request.live.post('/xlive/app-ucenter/v1/userTask/UserTaskReceiveRewards', {
actionKey: 'csrf',
target_id,
@@ -122,7 +122,7 @@ const BAPI: BapiMethods = {
})
},
silver2coin: (visit_id = '') => {
- const bili_jct = (useBiliStore().cookies as BiliCookies).bili_jct
+ const bili_jct = useBiliStore().cookies!.bili_jct
return request.live.post('/xlive/revenue/v1/wallet/silver2coin', {
csrf: bili_jct,
csrf_token: bili_jct,
@@ -130,7 +130,7 @@ const BAPI: BapiMethods = {
})
},
coin2silver: (num, platform = 'pc', visit_id = '') => {
- const bili_jct = (useBiliStore().cookies as BiliCookies).bili_jct
+ const bili_jct = useBiliStore().cookies!.bili_jct
return request.live.post('/xlive/revenue/v1/wallet/coin2silver', {
num,
csrf: bili_jct,
@@ -139,25 +139,8 @@ const BAPI: BapiMethods = {
visit_id
})
},
- queryContributionRank: (
- ruid,
- room_id,
- page,
- page_size,
- type = 'online_rank',
- _switch = 'contribution_rank'
- ) => {
- return request.live.get('/xlive/general-interface/v1/rank/queryContributionRank', {
- ruid,
- room_id,
- page,
- page_size,
- type,
- switch: _switch
- })
- },
wearMedal: (medal_id, visit_id = '') => {
- const bili_jct = (useBiliStore().cookies as BiliCookies).bili_jct
+ const bili_jct = useBiliStore().cookies!.bili_jct
return request.live.post('/xlive/web-room/v1/fansMedal/wear', {
medal_id,
csrf_token: bili_jct,
@@ -168,7 +151,7 @@ const BAPI: BapiMethods = {
},
liveTrace: {
E: (id, device, ruid, is_patch = 0, heart_beat = [], visit_id = '') => {
- const bili_jct = (useBiliStore().cookies as BiliCookies).bili_jct
+ const bili_jct = useBiliStore().cookies!.bili_jct
return request.liveTrace.post('/xlive/data-interface/v1/x25Kn/E', {
id: JSON.stringify(id),
device: JSON.stringify(device),
@@ -183,7 +166,7 @@ const BAPI: BapiMethods = {
})
},
X: (s, id, device, ruid, ets, benchmark, time, ts, visit_id = '') => {
- const bili_jct = (useBiliStore().cookies as BiliCookies).bili_jct
+ const bili_jct = useBiliStore().cookies!.bili_jct
return request.liveTrace.post('/xlive/data-interface/v1/x25Kn/X', {
s,
id: JSON.stringify(id),
@@ -207,60 +190,105 @@ const BAPI: BapiMethods = {
reward: () => {
return request.main.get('/x/member/web/exp/reward')
},
- dynamicAll: (type, page = 1, timezone_offset = -480, features = 'itemOpusStyle') => {
- return request.main.get('/x/polymer/web-dynamic/v1/feed/all', {
- timezone_offset,
- type,
- page,
- features
- })
+ dynamicAll: (
+ type = 'video',
+ page = 1,
+ timezone_offset = -480,
+ platform = 'web',
+ features = 'itemOpusStyle,listOnlyfans,opusBigCover,onlyfansVote,decorationCard,onlyfansAssetsV2,forwardListHidden,ugcDelete',
+ web_location = '333.1365',
+ x_bili_device_req_json = '{"platform":"web","device":"pc"}',
+ x_bili_web_req_json = '{"spm_id":"333.1365"}'
+ ) => {
+ return request.main.get(
+ '/x/polymer/web-dynamic/v1/feed/all',
+ {
+ timezone_offset,
+ type,
+ platform,
+ page,
+ features,
+ web_location,
+ x_bili_device_req_json,
+ x_bili_web_req_json
+ },
+ {
+ Origin: 'https://t.bilibili.com',
+ Referer: 'https://t.bilibili.com/'
+ }
+ )
},
videoHeartbeat: (
aid,
- cid = '',
- realtime = 0,
- played_time = 0,
- real_played_time = 0,
- refer_url = 'https://t.bilibili.com/?spm_id_from=444.3.0.0',
- quality = 116,
- video_duration = 100,
+ cid = 1000000000,
type = 3,
sub_type = 0,
- play_type = 0,
dt = 2,
- last_play_progress_time = 0,
- max_play_progress_time = 0,
- spmid = '333.488.0.0',
- from_spmid = '333.31.list.card_archive.click',
- extra = '{"player_version":"4.1.21-rc.1727.0"}'
+ play_type = 1,
+ realtime = 61,
+ played_time = 62,
+ real_played_time = 62,
+ refer_url = 'https://t.bilibili.com/?tab=video',
+ quality = 64,
+ video_duration = 180,
+ last_play_progress_time = 62,
+ max_play_progress_time = 62,
+ outer = 0,
+ spmid = '333.788.0.0',
+ from_spmid = '333.1365.list.card_archive.click',
+ session = uuid().replaceAll('-', ''),
+ extra = '{"player_version":"4.8.43"}',
+ web_location = 1315873
) => {
const biliStore = useBiliStore()
- return request.main.post('/x/click-interface/web/heartbeat', {
- start_ts: ts(),
- mid: useBiliStore().userInfo?.mid,
- aid,
- cid,
- type,
- sub_type,
- dt: dt,
- play_type,
- realtime,
- played_time,
- real_played_time,
- refer_url,
- quality,
- video_duration,
- last_play_progress_time,
- max_play_progress_time,
- spmid: spmid,
- from_spmid,
- extra,
- csrf: (biliStore.cookies as BiliCookies).bili_jct
- })
+ const start_ts = ts()
+ const mid = useBiliStore().userInfo!.mid
+
+ return request.main.post(
+ '/x/click-interface/web/heartbeat',
+ {
+ start_ts,
+ mid,
+ aid,
+ cid,
+ type,
+ sub_type,
+ dt,
+ play_type,
+ realtime,
+ played_time,
+ real_played_time,
+ refer_url,
+ quality,
+ video_duration,
+ last_play_progress_time,
+ max_play_progress_time,
+ outer,
+ spmid,
+ from_spmid,
+ session,
+ extra,
+ csrf: biliStore.cookies!.bili_jct
+ },
+ {
+ params: wbiSign({
+ w_start_ts: start_ts,
+ w_mid: mid,
+ w_aid: aid,
+ w_dt: dt,
+ w_realtime: realtime,
+ w_played_time: played_time,
+ w_real_played_time: real_played_time,
+ w_video_duration: video_duration,
+ w_last_play_progress_time: last_play_progress_time,
+ web_location
+ })
+ }
+ )
},
share: (aid, source = 'pc_client_normal', eab_x = 2, ramval = 0, ga = 1) => {
// source 不能用 web 端的值,改成 pc 客户端的才能完成任务
- const bili_jct = (useBiliStore().cookies as BiliCookies).bili_jct
+ const bili_jct = useBiliStore().cookies!.bili_jct
return request.main.post('/x/web-interface/share/add', {
aid,
eab_x,
@@ -280,16 +308,16 @@ const BAPI: BapiMethods = {
source = 'web_normal',
ga = 1
) => {
- const bili_jct = (useBiliStore().cookies as BiliCookies).bili_jct
+ const bili_jct = useBiliStore().cookies!.bili_jct
return request.main.post('/x/web-interface/coin/add ', {
- aid: aid,
+ aid,
multiply: num,
select_like: select_like,
cross_domain: cross_domain,
- eab_x: eab_x,
- ramval: ramval,
- source: source,
- ga: ga,
+ eab_x,
+ ramval,
+ source,
+ ga,
csrf: bili_jct
})
},
@@ -300,23 +328,22 @@ const BAPI: BapiMethods = {
})
},
vip: {
- myPrivilege: () => {
- const bili_jct = (useBiliStore().cookies as BiliCookies).bili_jct
+ myPrivilege: (web_location = '333.33') => {
return request.main.get(
'/x/vip/privilege/my',
{
- csrf: bili_jct
+ web_location
},
{
headers: {
Referer: 'https://account.bilibili.com/account/big/myPackage',
- Origin: 'https://account.bilibili.com/account/big/myPackage'
+ Origin: 'https://account.bilibili.com'
}
}
)
},
receivePrivilege: (type, platform = 'web') => {
- const bili_jct = (useBiliStore().cookies as BiliCookies).bili_jct
+ const bili_jct = useBiliStore().cookies!.bili_jct
return request.main.post(
'/x/vip/privilege/receive',
{
@@ -334,9 +361,9 @@ const BAPI: BapiMethods = {
},
addExperience: () => {
const biliStore = useBiliStore()
- const mid = biliStore.BilibiliLive?.UID
- const buvid = biliStore.cookies?.buvid3
- const bili_jct = biliStore.cookies?.bili_jct
+ const mid = biliStore.BilibiliLive!.UID
+ const buvid = biliStore.cookies!.buvid3
+ const bili_jct = biliStore.cookies!.bili_jct
return request.main.post(
'/x/vip/experience/add',
{
diff --git a/src/library/bili-api/response.d.ts b/src/library/bili-api/response.d.ts
index 74ca6ee..dc2a325 100644
--- a/src/library/bili-api/response.d.ts
+++ b/src/library/bili-api/response.d.ts
@@ -76,13 +76,6 @@ declare namespace Live {
message: string
}
- interface QueryContributionRank {
- code: number
- message: string
- ttl: number
- data: LiveData.QueryContributionRank.Data
- }
-
interface WearMedal {
code: number
message: string
@@ -111,8 +104,8 @@ declare namespace Main {
interface Nav {
code: number
message: string
- data: MainData.Nav.Data
ttl: number
+ data: MainData.Nav.Data
}
interface Reward {
diff --git a/src/library/cookie/index.ts b/src/library/cookie/index.ts
index 9fd4843..cd88129 100644
--- a/src/library/cookie/index.ts
+++ b/src/library/cookie/index.ts
@@ -1,77 +1,99 @@
-/**
- * 获取名称为 name 的 cookie
- * @param name 要获取的 cookie 名称
- */
-function getCookie(name: string): string | null {
- const value = `; ${document.cookie}`
- const parts = value.split(`; ${name}=`)
- if (parts.length === 2) return parts.pop()!.split(';').shift() as string
- return null
-}
+class Cookie {
+ /**
+ * 获取所有 cookies
+ */
+ public static getAll(): Record {
+ if (document.cookie === '') return {}
+
+ const cookies = document.cookie.split('; ')
+ const result: Record = {}
-/**
- * 获取名称在 names 数组中的 cookies
- * @param names 要获取的 cookie 名称数组
- */
-function getCookies(names: Iterable): Record {
- const cookies: Record = {} as Record
- const namesSet = new Set(names)
+ for (const cookie of cookies) {
+ const [name, value] = cookie.split('=', 2)
+ result[decodeURIComponent(name)] = decodeURIComponent(value)
+ }
- // 初始化所有 cookie 为 null
- for (const name of namesSet) {
- cookies[name] = null
+ return result
}
- for (const cookie of document.cookie.split('; ')) {
- const [cookieName, ...cookieValueParts] = cookie.split('=')
- const cookieValue = cookieValueParts.join('=')
+ /**
+ * 获取指定名称的一组 cookies
+ * @param names cookie 名称数组
+ * @param defaultValue 当 cookie 不存在时使用的默认值,默认 undefined
+ */
+ public static get(names: string[], defaultValue?: string): Record
+ /**
+ * 获取指定名称的一个 cookie
+ * @param name cookie 名称
+ * @param defaultValue 当 cookie 不存在时使用的默认值,默认 undefined
+ */
+ public static get(name: string, defaultValue?: string): string | undefined
+ /**
+ * 获取指定名称的一个或多个 cookies
+ * @param names cookie 名称或 cookie 名称数组
+ * @param defaultValue 当 cookie 不存在时使用的默认值,默认 undefined
+ */
+ public static get(names: string[] | string, defaultValue?: string) {
+ const cookies = this.getAll()
- if (namesSet.has(cookieName as T)) {
- cookies[cookieName as T] = decodeURIComponent(cookieValue)
- namesSet.delete(cookieName as T)
+ if (Array.isArray(names)) {
+ const result: Record = {}
+
+ for (const name of names) {
+ result[name] = cookies[name] ? cookies[name] : defaultValue
+ }
- // 所有 cookie 都已找到,跳出循环
- if (namesSet.size === 0) break
+ return result
+ } else {
+ return cookies[names] ? cookies[names] : defaultValue
}
}
- return cookies
-}
+ /**
+ * 获取一组 cookies,如果有 cookie 未获取到,会反复获取直到超时为止
+ *
+ * TODO: 等 cookieStore 普及后使用监听取代轮询
+ *
+ * @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)
-/**
- * 获取名称在 names 中的 cookies,如果有 cookie 未获取到,会反复获取直到超时为止
- *
- * @param names 要获取的 cookie 名称数组
- * @param interval 获取间隔
- * @param timeout 超时时间,若为 -1 则永不超时
- */
-function getCookiesAsync(
- names: T[],
- interval: number = 200,
- timeout: number = 10000
-): Promise> {
- return new Promise((resolve, reject) => {
- const startTime = Date.now()
- const remainNamesSet = new Set(names)
- const cookies: Record = {} as Record
+ remainCookieNames = remainCookieNames.filter((r) => !cookies[r])
+ if (remainCookieNames.length === 0) {
+ resolve(>cookies)
+ return
+ }
- const timer = setInterval(() => {
- Object.assign(cookies, getCookies(remainNamesSet))
+ let timeoutTimer: number
- // 删去 remainNamesSet 中已获取的 cookies
- for (const name in cookies) {
- if (cookies[name] !== null) remainNamesSet.delete(name)
- }
+ const timer = setInterval(() => {
+ Object.assign(cookies, this.get(remainCookieNames))
+ remainCookieNames = remainCookieNames.filter((r) => !cookies[r])
- if (remainNamesSet.size === 0) {
- clearInterval(timer)
- resolve(cookies)
- } else if (timeout !== -1 && Date.now() - startTime > timeout) {
- clearInterval(timer)
- reject('获取以下Cookies超时: ' + [...remainNamesSet])
+ if (remainCookieNames.length === 0) {
+ if (timeout) clearTimeout(timeoutTimer)
+ clearInterval(timer)
+ resolve(>cookies)
+ }
+ }, interval)
+
+ if (timeout) {
+ timeoutTimer = setTimeout(() => {
+ clearInterval(timer)
+ reject(`获取以下 cookie 超时:${remainCookieNames}`)
+ }, timeout)
}
- }, interval)
- })
+ })
+ }
}
-export { getCookie, getCookies, getCookiesAsync }
+export default Cookie
diff --git a/src/library/dom/index.ts b/src/library/dom/index.ts
index 261d159..22aca19 100644
--- a/src/library/dom/index.ts
+++ b/src/library/dom/index.ts
@@ -1,5 +1,3 @@
-import { unsafeWindow } from '$'
-
// 一些常用 DOM 方法的简写
const dq = document.querySelector.bind(document)
const dqa = document.querySelectorAll.bind(document)
@@ -54,23 +52,16 @@ function waitForElement(
* 特殊的直播间(背景很好看的那种,顶层 frame 被用来当背景板了)有三个 iframe,共四个 frame;
* 此时脚本会被注入到顶层 frame 和一个 iframe
*/
-const isTargetFrame = (): boolean => {
- if (document.head.innerHTML.includes('BilibiliLive')) {
- return true
- } else {
- return false
- }
-}
+const isTargetFrame = (): boolean => document.head.innerHTML.includes('BilibiliLive')
/**
* 判断是否在顶层 frame
*/
-const isSelfTopFrame = (): boolean => unsafeWindow.self === unsafeWindow.top
+const isSelfTopFrame = (): boolean => window.self === window.top
/**
* 获取顶层 frame 的 documentElement
*/
-const topFrameDocuemntElement = (): HTMLElement | undefined =>
- unsafeWindow.top?.document?.documentElement
+const topFrameDocumentElement = (): HTMLElement | undefined => window.top?.document.documentElement
-export { dq, dqa, dce, waitForElement, isTargetFrame, isSelfTopFrame, topFrameDocuemntElement }
+export { dq, dqa, dce, waitForElement, isTargetFrame, isSelfTopFrame, topFrameDocumentElement }
diff --git a/src/library/help-info/index.ts b/src/library/help-info/index.ts
index db7e0a2..a6f3b2e 100644
--- a/src/library/help-info/index.ts
+++ b/src/library/help-info/index.ts
@@ -121,7 +121,7 @@ const helpInfo: HelpInfo = {
h('strong', '注意:'),
h(
'span',
- '如果当前所处的直播间正在直播,可能无法获得任何亲密度。请在未开播的直播间使用本功能,后续会优化这一点。'
+ '使用本功能时不能以任何方式观看直播(网页、APP、电视),否则可能无法获得任何亲密度。'
)
])
])
@@ -221,12 +221,6 @@ const helpInfo: HelpInfo = {
invisibility: {
title: '隐身入场',
message: h('p', [h('div', '进入直播间时其他人不会收到提示,但还是会出现在高能用户榜单上。')])
- },
- showContributionUserNum: {
- title: '显示高能用户数量',
- message: h('p', [
- h('div', '在高能用户标签上显示当前直播间的高能用户数量,每分钟更新一次数据。')
- ])
}
},
RemoveElement: {
diff --git a/src/library/request/index.ts b/src/library/request/index.ts
index 32d9e55..ffb8701 100644
--- a/src/library/request/index.ts
+++ b/src/library/request/index.ts
@@ -1,13 +1,14 @@
import { GM_xmlhttpRequest, type GmXhrRequest } from '$'
import _ from 'lodash'
+import { addURLParams } from '../utils'
class Request {
/** 请求 URL 的前缀 */
- private url_prefix: string
+ private readonly url_prefix: string
/**
* 请求 Header 中 Origin 的值,为了方便同时也是 Referer 的值
*/
- private origin: string
+ private readonly origin: string
constructor(url_prefix?: string, orgin?: string) {
this.url_prefix = url_prefix ?? ''
@@ -19,13 +20,18 @@ class Request {
* @param url 请求 URL 除去前缀的部分
* @param params URL 参数
* @param otherDetails GM_xmlhttpRequest 的 details 参数
- * @returns Promise
*/
- public get(url: string, params?: Record, otherDetails?: any): Promise {
+ public get(
+ url: string,
+ params?: Record | string,
+ otherDetails?: Record
+ ): Promise {
+ url = addURLParams(this.url_prefix + url, params)
+
return new Promise((resolve, reject) => {
const defaultDetails: GmXhrRequest = {
method: 'GET',
- url: this.url_prefix + url + (params ? '?' + new URLSearchParams(params).toString() : ''),
+ url,
responseType: 'json',
headers: {
Accept: 'application/json, text/plain, */*',
@@ -40,6 +46,7 @@ class Request {
reject(err)
}
}
+
const details = _.defaultsDeep(otherDetails, defaultDetails)
GM_xmlhttpRequest(details)
})
@@ -48,24 +55,40 @@ class Request {
/**
* 发起一个 POST 请求
* @param url 请求 URL 除去前缀的部分
- * @param data application/x-www-form-urlencoded 其它类型的数据需要在 otherDetails 中定义
- * @param otherDetails GM_xmlhttpRequest 的 details 参数
- * @returns Promise
+ * @param data POST data
+ * @param otherDetails GM_xmlhttpRequest 的 details 参数(特别的,可以提供 params 属性作为 URL 参数)
*/
- public post(url: string, data?: Record, otherDetails?: any): Promise {
+ public post(
+ url: string,
+ data?: Record | string,
+ otherDetails?: Record
+ ): Promise {
+ const headers: Record = {
+ Accept: 'application/json, text/plain, */*',
+ Referer: this.origin,
+ Origin: this.origin,
+ 'Sec-Fetch-Site': 'same-site',
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ }
+
+ if (data instanceof FormData) {
+ // 如果要提交表单,删除 Content-Type 让浏览器自动生成
+ delete headers['Content-Type']
+ } else if (typeof data === 'object') {
+ // data 类型为 Record,转换为 string
+ data = new URLSearchParams(data).toString()
+ }
+
+ url = addURLParams(this.url_prefix + url, otherDetails?.params)
+ delete otherDetails?.params
+
return new Promise((resolve, reject) => {
const defaultDetails: GmXhrRequest = {
method: 'POST',
- url: this.url_prefix.concat(url),
- data: new URLSearchParams(data).toString(),
+ url,
+ data,
responseType: 'json',
- headers: {
- Accept: 'application/json, text/plain, */*',
- Referer: this.origin,
- Origin: this.origin,
- 'Sec-Fetch-Site': 'same-site',
- 'Content-Type': 'application/x-www-form-urlencoded'
- },
+ headers,
onload: function (response) {
resolve(response.response)
},
@@ -73,11 +96,8 @@ class Request {
reject(err)
}
}
+
const details = _.defaultsDeep(otherDetails, defaultDetails)
- if (details.headers['Content-Type'] === 'multipart/form-data') {
- // 如果要提交 multipart/form-data 格式的数据,删除该属性让浏览器自动生成 header
- delete details.headers['Content-Type']
- }
GM_xmlhttpRequest(details)
})
}
diff --git a/src/library/storage/defaultValues.ts b/src/library/storage/defaultValues.ts
index 3dbd584..8198dc3 100644
--- a/src/library/storage/defaultValues.ts
+++ b/src/library/storage/defaultValues.ts
@@ -10,7 +10,8 @@ const defaultValues: DefaultValues = {
ui: {
isCollapse: false,
isShowPanel: true,
- activeMenuIndex: 'MainSiteTasks'
+ activeMenuIndex: 'MainSiteTasks',
+ panelWidthPercent: 40
},
modules: {
DailyTasks: {
@@ -108,9 +109,6 @@ const defaultValues: DefaultValues = {
},
invisibility: {
enabled: false
- },
- showContributionUserNum: {
- enabled: false
}
},
RemoveElement: {
diff --git a/src/library/utils/index.ts b/src/library/utils/index.ts
index cf955c3..949738a 100644
--- a/src/library/utils/index.ts
+++ b/src/library/utils/index.ts
@@ -1,6 +1,8 @@
import _ from 'lodash'
-import { useModuleStore } from '@/stores/useModuleStore'
-import type { ModuleEmitterEvents, RunAtMoment } from '@/types'
+import CryptoJS from 'crypto-js'
+import type { RunAtMoment } from '@/types'
+import { ts } from '@/library/luxon'
+import { useBiliStore } from '@/stores/useBiliStore'
/**
* 生成一个 version 4 uuid
@@ -22,15 +24,51 @@ function sleep(miliseconds: number): Promise {
}
/**
- * 基于 Promise 和 mitt 的等待函数
- * @param type mitt 的 type 参数
- * @param timeout 超时时间
+ * 从 URL 中获取文件名
+ * @param url
*/
-function wait(type: keyof ModuleEmitterEvents, timeout: number = -1): Promise {
- return new Promise((resolve) => {
- useModuleStore().emitter.once(type, (event) => resolve(event))
- if (timeout !== -1) setTimeout(resolve, timeout)
- })
+function getFilenameFromUrl(url: string): string {
+ return url.substring(url.lastIndexOf('/') + 1).split('.')[0]
+}
+
+/**
+ * 为 URL 添加查询参数
+ * @param url URL
+ * @param params 查询参数
+ */
+function addURLParams(url: string, params?: Record | string): string {
+ if (!params) {
+ return url
+ }
+
+ if (typeof params === 'string') {
+ return url + '?' + params
+ } else {
+ return url + '?' + new URLSearchParams(params).toString()
+ }
+}
+
+/**
+ * 对请求参数进行 wbi 签名
+ * @param params 请求参数
+ */
+function wbiSign(params: Record): string {
+ // 添加 wts 字段(当前秒级时间戳)
+ params.wts = ts()
+ // 按照键对参数进行排序
+ const query = Object.keys(params)
+ .sort()
+ .map((key) => {
+ // 过滤 value 中的 !'()* 字符
+ const value = params[key].toString().replace(/[!'()*]/g, '')
+ // 注:空格需被编码为%20而不是+,因此不能使用URLSearchParams
+ return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`
+ })
+ .join('&')
+ // 计算 w_rid
+ const wbiSign = CryptoJS.MD5(query + useBiliStore().wbiSalt).toString()
+
+ return query + '&w_rid=' + wbiSign
}
/**
@@ -146,4 +184,14 @@ function waitForMoment(moment: RunAtMoment): Promise {
}
}
-export { uuid, sleep, wait, packFormData, deepestIterate, getUrlFromFetchInput, waitForMoment }
+export {
+ uuid,
+ sleep,
+ getFilenameFromUrl,
+ addURLParams,
+ wbiSign,
+ packFormData,
+ deepestIterate,
+ getUrlFromFetchInput,
+ waitForMoment
+}
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts b/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts
index 7764910..af5acce 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/lightTask.ts
@@ -73,7 +73,13 @@ class LightTask extends BaseModule {
const response = await BAPI.live.sendMsg(danmu, roomid)
this.logger.log(`BAPI.live.sendMsg(${danmu}, ${roomid})`, response)
if (response.code === 0) {
- this.logger.log(`点亮熄灭勋章-发送弹幕 在直播间 ${roomid} 发送弹幕 ${danmu} 成功`)
+ if (response.msg === 'k') {
+ this.logger.warn(
+ `点亮熄灭勋章-发送弹幕 在直播间 ${roomid} 发送弹幕 ${danmu} 异常,弹幕可能包含屏蔽词`
+ )
+ } else {
+ this.logger.log(`点亮熄灭勋章-发送弹幕 在直播间 ${roomid} 发送弹幕 ${danmu} 成功`)
+ }
} else {
this.logger.error(
`点亮熄灭勋章-发送弹幕 在直播间 ${roomid} 发送弹幕 ${danmu} 失败`,
diff --git a/src/modules/dailyTasks/liveTasks/medalTasks/watchTask.ts b/src/modules/dailyTasks/liveTasks/medalTasks/watchTask.ts
index dbb0b12..79cb180 100644
--- a/src/modules/dailyTasks/liveTasks/medalTasks/watchTask.ts
+++ b/src/modules/dailyTasks/liveTasks/medalTasks/watchTask.ts
@@ -8,7 +8,6 @@ import { uuid, sleep } from '@/library/utils'
import { useModuleStore } from '@/stores/useModuleStore'
import type { ModuleConfig } from '@/types'
import type { ModuleStatusTypes, RunAtMoment } from '@/types'
-import { getCookie } from '@/library/cookie'
import _ from 'lodash'
interface SpyderData {
@@ -75,7 +74,7 @@ class RoomHeart {
}
/** Cookie LIVE_BUVID */
- private buvid: string | null = useBiliStore().cookies?.LIVE_BUVID ?? getCookie('LIVE_BUVID')
+ private buvid?: string = useBiliStore().cookies!.LIVE_BUVID
private uuid = uuid()
diff --git a/src/modules/dailyTasks/mainSiteTasks/coinTask.ts b/src/modules/dailyTasks/mainSiteTasks/coinTask.ts
index 755f8ba..84ae9f2 100644
--- a/src/modules/dailyTasks/mainSiteTasks/coinTask.ts
+++ b/src/modules/dailyTasks/mainSiteTasks/coinTask.ts
@@ -1,7 +1,6 @@
import BaseModule from '../../BaseModule'
import { isTimestampToday, delayToNextMoment, tsm, isNowIn } from '@/library/luxon'
import { useBiliStore } from '@/stores/useBiliStore'
-import type { MainData } from '@/library/bili-api/data'
import BAPI from '@/library/bili-api'
import type { ModuleStatusTypes } from '@/types'
@@ -23,22 +22,16 @@ class CoinTask extends BaseModule {
/**
* 获取动态视频的 aid 和 bvid
*/
- private getDynamicVideoIds(): { aid: string; bvid: string }[] | null {
+ private getDynamicVideoIds(): { aid: string; bvid: string }[] {
const biliStore = useBiliStore()
- if (biliStore.dynamicVideos) {
- // 当 biliStore.dynamicVideos 不为 null 时,返回所有的 aid 和 bvid
- return biliStore.dynamicVideos.map((item) => {
- const archive = item.modules.module_dynamic.major.archive
- return {
- aid: archive.aid,
- bvid: archive.bvid
- }
- })
- } else {
- this.status = 'error'
- // 否则返回 null
- return null
- }
+ // 当 biliStore.dynamicVideos 不为 null 时,返回所有的 aid 和 bvid
+ return biliStore.dynamicVideos!.map((item) => {
+ const archive = item.modules.module_dynamic.major.archive
+ return {
+ aid: archive.aid,
+ bvid: archive.bvid
+ }
+ })
}
/**
@@ -126,28 +119,26 @@ class CoinTask extends BaseModule {
const biliStore = useBiliStore()
if (!isTimestampToday(this.config._lastCompleteTime)) {
this.status = 'running'
- if (biliStore.dailyRewardInfo) {
- // 今日已投币数量
- const total_coined_num = biliStore.dailyRewardInfo.coins / 10
- if (total_coined_num < this.config.num) {
- // 剩余要投的硬币数量
- const left_coin_num = this.config.num - total_coined_num
- const biliStore = useBiliStore()
- // 拥有的硬币数量
- const money = (biliStore.userInfo as MainData.Nav.Data).money ?? 5
- if (left_coin_num > money) {
- this.logger.log('硬币余额不足,不执行每日投币任务')
- this.status = 'done'
- } else {
- // 目前仅支持动态视频投币
- // TODO: 增加别的投币方式,比如给某UP的视频投币
- await this.coinDynamicVideos(left_coin_num)
- }
- } else {
- this.config._lastCompleteTime = tsm()
+ // 今日已投币数量
+ const total_coined_num = biliStore.dailyRewardInfo!.coins / 10
+ if (total_coined_num < this.config.num) {
+ // 剩余要投的硬币数量
+ const left_coin_num = this.config.num - total_coined_num
+ const biliStore = useBiliStore()
+ // 拥有的硬币数量
+ const money = biliStore.userInfo!.money ?? 5
+ if (left_coin_num > money) {
+ this.logger.log('硬币余额不足,不执行每日投币任务')
this.status = 'done'
- this.logger.log('每日投币任务已完成')
+ } else {
+ // 目前仅支持动态视频投币
+ // TODO: 增加别的投币方式,比如给某UP的视频投币
+ await this.coinDynamicVideos(left_coin_num)
}
+ } else {
+ this.config._lastCompleteTime = tsm()
+ this.status = 'done'
+ this.logger.log('每日投币任务已完成')
}
} else {
// 为了更加准确的语言描述和任务状态图标显示,需要判断当前所处的时间段
diff --git a/src/modules/dailyTasks/mainSiteTasks/loginTask.ts b/src/modules/dailyTasks/mainSiteTasks/loginTask.ts
index 2242b3e..f66d076 100644
--- a/src/modules/dailyTasks/mainSiteTasks/loginTask.ts
+++ b/src/modules/dailyTasks/mainSiteTasks/loginTask.ts
@@ -31,7 +31,7 @@ class LoginTask extends BaseModule {
if (!isTimestampToday(this.config._lastCompleteTime)) {
this.status = 'running'
// 每日登录任务未完成
- if (biliStore.dailyRewardInfo && !biliStore.dailyRewardInfo.login) {
+ if (!biliStore.dailyRewardInfo!.login) {
await this.login()
} else {
// 用户在运行脚本前已经完成了任务,也记录完成时间
diff --git a/src/modules/dailyTasks/mainSiteTasks/shareTask.ts b/src/modules/dailyTasks/mainSiteTasks/shareTask.ts
index d4a6597..1496b3f 100644
--- a/src/modules/dailyTasks/mainSiteTasks/shareTask.ts
+++ b/src/modules/dailyTasks/mainSiteTasks/shareTask.ts
@@ -13,13 +13,8 @@ class ShareTask extends BaseModule {
private getAid(): string {
const biliStore = useBiliStore()
- if (biliStore.dynamicVideos) {
- // 当 biliStore.dynamicVideos 不为 null 时,返回第一个视频的 aid
- return biliStore.dynamicVideos[0].modules.module_dynamic.major.archive.aid
- } else {
- // 否则返回 '2'
- return '2'
- }
+ // 返回第一个视频的 aid
+ return biliStore.dynamicVideos![0].modules.module_dynamic.major.archive.aid
}
private async share(aid: string): Promise {
@@ -47,7 +42,7 @@ class ShareTask extends BaseModule {
const biliStore = useBiliStore()
if (!isTimestampToday(this.config._lastCompleteTime)) {
this.status = 'running'
- if (biliStore.dailyRewardInfo && !biliStore.dailyRewardInfo.share) {
+ if (!biliStore.dailyRewardInfo!.share) {
const aid = this.getAid()
await this.share(aid)
} else {
diff --git a/src/modules/dailyTasks/mainSiteTasks/watchTask.ts b/src/modules/dailyTasks/mainSiteTasks/watchTask.ts
index b365d8f..139f254 100644
--- a/src/modules/dailyTasks/mainSiteTasks/watchTask.ts
+++ b/src/modules/dailyTasks/mainSiteTasks/watchTask.ts
@@ -1,10 +1,9 @@
import BaseModule from '../../BaseModule'
import { isTimestampToday, delayToNextMoment, tsm, isNowIn } from '@/library/luxon'
import { useBiliStore } from '@/stores/useBiliStore'
-import type { MainData } from '@/library/bili-api/data'
import BAPI from '@/library/bili-api'
-import _ from 'lodash'
import type { ModuleStatusTypes } from '@/types'
+import _ from 'lodash'
class WatchTask extends BaseModule {
config = this.moduleStore.moduleConfig.DailyTasks.MainSiteTasks.watch
@@ -13,22 +12,15 @@ class WatchTask extends BaseModule {
this.moduleStore.moduleStatus.DailyTasks.MainSiteTasks.watch = s
}
- private getAid(): string {
+ private getAid(): number {
const biliStore = useBiliStore()
- if (!_.isEmpty(biliStore.dynamicVideos)) {
- // 当 biliStore.dynamicVideos 不是 null 或 [] 时
- // 返回第一个视频的 aid
- return (biliStore.dynamicVideos as MainData.DynamicAll.Item[])[0].modules.module_dynamic.major
- .archive.aid
- } else {
- // 否则返回 '2'
- return '2'
- }
+ // 返回第一个视频的 aid
+ return Number(biliStore.dynamicVideos![0].modules.module_dynamic.major.archive.aid)
}
- private async watch(aid: string) {
+ private async watch(aid: number) {
try {
- const response = await BAPI.main.videoHeartbeat(aid)
+ const response = await BAPI.main.videoHeartbeat(aid, _.random(1000000000, 2000000000))
this.logger.log(`BAPI.main.videoHeartbeat(${aid}) response`, response)
if (response.code === 0) {
this.logger.log('每日观看视频任务已完成')
@@ -50,7 +42,7 @@ class WatchTask extends BaseModule {
const biliStore = useBiliStore()
if (!isTimestampToday(this.config._lastCompleteTime)) {
this.status = 'running'
- if (biliStore.dailyRewardInfo && !biliStore.dailyRewardInfo.watch) {
+ if (!biliStore.dailyRewardInfo!.watch) {
const aid = this.getAid()
await this.watch(aid)
} else {
diff --git a/src/modules/dailyTasks/otherTasks/getYearVipPrivilegeTask.ts b/src/modules/dailyTasks/otherTasks/getYearVipPrivilegeTask.ts
index 95d8a3a..b3f443e 100644
--- a/src/modules/dailyTasks/otherTasks/getYearVipPrivilegeTask.ts
+++ b/src/modules/dailyTasks/otherTasks/getYearVipPrivilegeTask.ts
@@ -92,7 +92,7 @@ class GetYearVipPrivilegeTask extends BaseModule {
private isYearVip(): boolean {
const biliStore = useBiliStore()
const userInfo = biliStore.userInfo
- if (userInfo && userInfo.vip.status === 1 && userInfo.vip.type === 2) {
+ if (userInfo!.vip.status === 1 && userInfo!.vip.type === 2) {
return true
} else {
this.logger.log('当前账号不是年度大会员,不领取权益')
@@ -130,9 +130,10 @@ class GetYearVipPrivilegeTask extends BaseModule {
const watchTaskConfig =
this.moduleStore.moduleConfig.DailyTasks.MainSiteTasks.watch
if (watchTaskConfig.enabled) {
+ // 等观看视频任务完成再领取
watch(
() => watchTaskConfig._lastCompleteTime,
- () => this.addExperience(),
+ () => sleep(3000).then(() => this.addExperience()),
{ once: true }
)
} else {
diff --git a/src/modules/default/cookies.ts b/src/modules/default/cookies.ts
index 6cebb42..2280799 100644
--- a/src/modules/default/cookies.ts
+++ b/src/modules/default/cookies.ts
@@ -1,5 +1,5 @@
import { useBiliStore } from '@/stores/useBiliStore'
-import { getCookiesAsync } from '@/library/cookie'
+import Cookie from '@/library/cookie'
import type { BiliCookies } from '@/types'
import BaseModule from '../BaseModule'
@@ -12,7 +12,7 @@ class Cookies extends BaseModule {
* buvid3: 作为参数 buvid 在请求中出现,目前仅在主站 API 中使用
*/
private getCookies(): Promise {
- return getCookiesAsync(['bili_jct', 'LIVE_BUVID', 'buvid3'])
+ return Cookie.getAsync(['bili_jct', 'LIVE_BUVID', 'buvid3'], 300, 10e3)
}
public async run(): Promise {
diff --git a/src/modules/default/dailyRewardInfo.ts b/src/modules/default/dailyRewardInfo.ts
index 0b08e8c..3215294 100644
--- a/src/modules/default/dailyRewardInfo.ts
+++ b/src/modules/default/dailyRewardInfo.ts
@@ -8,7 +8,7 @@ class DailyRewardInfo extends BaseModule {
/**
* 获取今日主站每日任务的完成情况
*/
- private async getDailyRewardInfo(): Promise {
+ private async getDailyRewardInfo(): Promise {
const mainSiteTasks = this.moduleStore.moduleConfig.DailyTasks.MainSiteTasks
// 开启了任意一项主站功能且该功能今天没完成过
if (
@@ -27,8 +27,6 @@ class DailyRewardInfo extends BaseModule {
this.logger.error('获取主站每日任务完成情况出错', error)
return Promise.reject(error)
}
- } else {
- return Promise.resolve(null)
}
}
diff --git a/src/modules/default/dynamicVideos.ts b/src/modules/default/dynamicVideos.ts
index 293098c..b9a07dc 100644
--- a/src/modules/default/dynamicVideos.ts
+++ b/src/modules/default/dynamicVideos.ts
@@ -10,7 +10,7 @@ class DynamicVideos extends BaseModule {
*
* 每日观看视频,每日分享视频,每日投币都会用到
*/
- private async getDynamicVideos(): Promise {
+ private async getDynamicVideos(): Promise {
const mainSiteTasks = this.moduleStore.moduleConfig.DailyTasks.MainSiteTasks
// 开启了观看视频、每日分享视频或每日投币功能且今天没完成过
if (
@@ -31,8 +31,6 @@ class DynamicVideos extends BaseModule {
this.logger.error('获取主站每日任务完成情况出错', error)
return Promise.reject(error)
}
- } else {
- return Promise.resolve(null)
}
}
diff --git a/src/modules/default/fansMetals.ts b/src/modules/default/fansMetals.ts
index e1c7743..036f925 100644
--- a/src/modules/default/fansMetals.ts
+++ b/src/modules/default/fansMetals.ts
@@ -16,7 +16,7 @@ class FansMetals extends BaseModule {
private async getFansMetals(
pages = Infinity,
force = false
- ): Promise {
+ ): Promise {
const medalTasks = this.moduleStore.moduleConfig.DailyTasks.LiveTasks.medalTasks
if (
@@ -61,8 +61,6 @@ class FansMetals extends BaseModule {
this.logger.error('获取粉丝勋章列表出错', error)
return Promise.reject(error)
}
- } else {
- return Promise.resolve(null)
}
}
diff --git a/src/modules/enhanceExperience/banP2P.ts b/src/modules/enhanceExperience/banP2P.ts
index b7c4390..e226e63 100644
--- a/src/modules/enhanceExperience/banP2P.ts
+++ b/src/modules/enhanceExperience/banP2P.ts
@@ -17,8 +17,8 @@ class BanP2P extends BaseModule {
'webkitRTCPeerConnection'
]
for (const i of RTClist) {
- // 判断属性是否存在并且是否可配置
- if (Object.prototype.hasOwnProperty.call(unsafeWindow, i)) {
+ // 判断属性是否存在
+ if (Object.hasOwn(unsafeWindow, i)) {
// 定义属性
Object.defineProperty(unsafeWindow, i, {
value: class {
diff --git a/src/modules/enhanceExperience/index.ts b/src/modules/enhanceExperience/index.ts
index 41e76d3..ccca35a 100644
--- a/src/modules/enhanceExperience/index.ts
+++ b/src/modules/enhanceExperience/index.ts
@@ -3,4 +3,3 @@ export { default as EnhanceExperience_BanP2P } from './banP2P'
export { default as EnhanceExperience_NoReport } from './noReport'
export { default as EnhanceExperience_NoSleep } from './noSleep'
export { default as EnhanceExperience_Invisibility } from './invisibility'
-export { default as EnhanceExperience_ShowContributionUserNum } from './showContributionUserNum'
diff --git a/src/modules/enhanceExperience/noReport.ts b/src/modules/enhanceExperience/noReport.ts
index cd4c119..20ad762 100644
--- a/src/modules/enhanceExperience/noReport.ts
+++ b/src/modules/enhanceExperience/noReport.ts
@@ -18,11 +18,7 @@ class NoReport extends BaseModule {
* @param url 需要判断的 URL
*/
private static isTargetURL(url: string) {
- if (url.includes('//data.bilibili.com') || url.includes('//data.bilivideo.com')) {
- return true
- } else {
- return false
- }
+ return url.includes('//data.bilibili.com') || url.includes('//data.bilivideo.com')
}
/**
diff --git a/src/modules/enhanceExperience/showContributionUserNum.ts b/src/modules/enhanceExperience/showContributionUserNum.ts
deleted file mode 100644
index 25117ce..0000000
--- a/src/modules/enhanceExperience/showContributionUserNum.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import BaseModule from '../BaseModule'
-import type { RunAtMoment } from '@/types'
-import BAPI from '@/library/bili-api'
-import { waitForElement } from '@/library/dom'
-import { useBiliStore } from '@/stores/useBiliStore'
-import _ from 'lodash'
-
-class ShowContributionUserNum extends BaseModule {
- static runOnMultiplePages: boolean = true
- static runAt: RunAtMoment = 'window-load'
-
- config = this.moduleStore.moduleConfig.EnhanceExperience.showContributionUserNum
-
- private async getContributionUserNum(
- anchor_uid: number,
- roomid: number,
- page: number = 1,
- page_size: number = 100
- ): Promise {
- return BAPI.live.queryContributionRank(anchor_uid, roomid, page, page_size).then((response) => {
- this.logger.log(
- `BAPI.live.queryContributionRank(${anchor_uid}, ${roomid}, ${page}, ${page_size})`,
- response
- )
- if (response.code === 0) {
- return response.data.count
- } else {
- this.logger.error('获取高能用户数量失败', response.message)
- return -1
- }
- })
- }
-
- /**
- * 更新显示的高能用户数量
- * @param element 高能用户的 DOM 元素
- * @param anchor_uid 主播uid
- * @param roomid 房间号
- */
- private async updateNumber(
- element: HTMLElement,
- anchor_uid: number,
- roomid: number
- ): Promise {
- const num = await this.getContributionUserNum(anchor_uid, roomid, 1, 100)
- if (num !== -1) {
- element.innerText = `高能用户(${num})`
- setTimeout(() => this.updateNumber(element, anchor_uid, roomid), _.random(50e3, 70e3))
- } else {
- // 如果获取高能用户人数失败,则不再获取
- element.innerText = '高能用户'
- }
- }
-
- public async run(): Promise {
- this.logger.log('显示高能用户数量模块开始运行')
- if (this.config.enabled) {
- const biliStore = useBiliStore()
- const anchor_uid = biliStore.BilibiliLive!.ANCHOR_UID
- const roomid = biliStore.BilibiliLive!.ROOMID
- const element = (await waitForElement(document.body, '#rank-list-ctnr-box .tab-list'))
- ?.firstChild as HTMLElement
- if (element) {
- this.updateNumber(element, anchor_uid, roomid)
- } else {
- this.logger.error('未找到高能用户标签')
- }
- }
- }
-}
-
-export default ShowContributionUserNum
diff --git a/src/modules/enhanceExperience/switchLiveStreamQuality.ts b/src/modules/enhanceExperience/switchLiveStreamQuality.ts
index 938f44b..51d6ed3 100644
--- a/src/modules/enhanceExperience/switchLiveStreamQuality.ts
+++ b/src/modules/enhanceExperience/switchLiveStreamQuality.ts
@@ -16,8 +16,8 @@ class SwitchLiveStreamQuality extends BaseModule {
const findPlayertimer = setInterval(() => {
if (
topWindow.livePlayer &&
- Object.prototype.hasOwnProperty.call(topWindow.livePlayer, 'switchQuality') &&
- Object.prototype.hasOwnProperty.call(topWindow.livePlayer, 'getPlayerInfo')
+ Object.hasOwn(topWindow.livePlayer, 'switchQuality') &&
+ Object.hasOwn(topWindow.livePlayer, 'getPlayerInfo')
) {
clearInterval(findPlayertimer)
clearTimeout(timeoutTimer)
diff --git a/src/stores/useBiliStore.ts b/src/stores/useBiliStore.ts
index 17549b1..7f41c4b 100644
--- a/src/stores/useBiliStore.ts
+++ b/src/stores/useBiliStore.ts
@@ -2,26 +2,47 @@ import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
import type { LiveData, MainData } from '@/library/bili-api/data'
import type { BiliCookies } from '@/types'
+import { getFilenameFromUrl } from '@/library/utils'
export const useBiliStore = defineStore('bili', () => {
// window.BilibiliLive 包含当前直播间的一些基本信息
- const BilibiliLive = ref(null)
+ const BilibiliLive = ref()
// 脚本要用到的 Cookies
- const cookies = ref(null)
+ const cookies = ref()
// 用户基本信息
- const userInfo = ref(null)
+ const userInfo = ref()
// 礼物配置信息
- const giftConfig = ref(null)
+ const giftConfig = ref()
// 主站每日任务完成情况
- const dailyRewardInfo = ref(null)
+ const dailyRewardInfo = ref()
// 动态中一页视频的信息
- const dynamicVideos = ref(null)
+ const dynamicVideos = ref()
// 粉丝勋章列表
- const fansMedals = ref(null)
+ const fansMedals = ref()
// 过滤了不存在直播间的粉丝勋章
- const filteredFansMedals = computed(
- () => fansMedals.value?.filter((m) => m.room_info.room_id !== 0) ?? null
+ const filteredFansMedals = computed(
+ () => fansMedals.value?.filter((m) => m.room_info.room_id !== 0) ?? []
)
+ // wbi 签名所需的盐值
+ const wbiSalt = computed(() => {
+ if (!userInfo.value) {
+ return ''
+ }
+
+ const imgKey = getFilenameFromUrl(userInfo.value.wbi_img.img_url)
+ const subKey = getFilenameFromUrl(userInfo.value.wbi_img.sub_url)
+ // 拼接 imgKey 和 subKey
+ const imgAndSubKey = imgKey + subKey
+ // 将 imgKey 和 subKey 中的字符按特定顺序重新排列,取前32位
+ return [
+ 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49, 33, 9, 42, 19, 29,
+ 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40, 61, 26, 17, 0, 1, 60, 51, 30, 4, 22,
+ 25, 54, 21, 56, 59, 6, 63, 57, 62, 11, 36, 20, 34, 44, 52
+ ]
+ .map((n) => imgAndSubKey[n])
+ .join('')
+ .slice(0, 32)
+ })
return {
BilibiliLive,
@@ -31,6 +52,7 @@ export const useBiliStore = defineStore('bili', () => {
dailyRewardInfo,
dynamicVideos,
fansMedals,
- filteredFansMedals
+ filteredFansMedals,
+ wbiSalt
}
})
diff --git a/src/stores/useUIStore.ts b/src/stores/useUIStore.ts
index 717bb65..8c83700 100644
--- a/src/stores/useUIStore.ts
+++ b/src/stores/useUIStore.ts
@@ -4,7 +4,7 @@ import Storage from '@/library/storage'
import _ from 'lodash'
import type { UiConfig, MenuIndex } from '@/types'
-interface BaseStyleValue {
+interface LivePlayerRect {
top: number
left: number
height: number
@@ -17,29 +17,33 @@ const index2name: Record = {
LiveTasks: '直播任务',
OtherTasks: '其它任务',
EnhanceExperience: '体验优化',
- RemoveElement: '移除元素'
+ RemoveElement: '移除元素',
+ ScriptSettings: '设置'
}
export const useUIStore = defineStore('ui', () => {
// 控制面板 UI 相关的设置
- const uiConfig: UiConfig = reactive(Storage.getUiConfig())
+ const uiConfig = reactive(Storage.getUiConfig())
// 被激活的菜单项的名称,用于在 Header 里显示子标题
const activeMenuName = computed(() => {
return index2name[uiConfig.activeMenuIndex]
})
- // 控制面板长、宽、位置信息
- const baseStyleValue: BaseStyleValue = reactive({
+ // 播放器长、宽、位置信息
+ const livePlayerRect = reactive({
top: 0,
left: 0,
height: 0,
width: 0
})
- // 把 number 转换为 css 的值
- const baseStyle = computed(() => ({
- top: baseStyleValue.top.toString() + 'px',
- left: baseStyleValue.left.toString() + 'px',
- height: baseStyleValue.height.toString() + 'px',
- width: baseStyleValue.width.toString() + 'px'
+ // 缓存的窗口滚动条位置
+ const windowScrollPosition = reactive({ x: 0, y: 0 })
+ // 控制面板 css(长、宽、位置信息)
+ const panelStyle = computed(() => ({
+ // 此处若使用最新的滚动条位置(window.scrollX/Y),用户在调整控制面板宽度时可能导致面板在垂直方向上错位
+ top: `${livePlayerRect.top + windowScrollPosition.y}px`,
+ left: `${livePlayerRect.left + windowScrollPosition.x}px`,
+ height: `${livePlayerRect.height}px`,
+ width: `${(livePlayerRect.width * uiConfig.panelWidthPercent) / 100}px`
}))
// 开关控制面板按钮的文字
const isShowPanelButtonText = computed(() => {
@@ -51,19 +55,22 @@ export const useUIStore = defineStore('ui', () => {
})
// 控制面板主体的滚动条窗口高度
// 因为 header 的高度是固定的 60px,所以用控制面板高度 - 60px
- const scrollBarHeight = computed(() => (baseStyleValue.height - 60).toString() + 'px')
+ const scrollBarHeight = computed(() => `${livePlayerRect.height - 60}px`)
+
/**
* 切换侧边栏的展开/收起状态
*/
function changeCollapse() {
uiConfig.isCollapse = !uiConfig.isCollapse
}
+
/**
* 切换控制面板的打开/关闭状态
*/
function changeShowPanel() {
uiConfig.isShowPanel = !uiConfig.isShowPanel
}
+
/**
*设置被激活菜单项的名称,配合 el-menu 的 `@select` 使用
* @param index 被激活菜单项
@@ -71,6 +78,7 @@ export const useUIStore = defineStore('ui', () => {
function setActiveMenuIndex(index: MenuIndex) {
uiConfig.activeMenuIndex = index
}
+
// 监听UI配置信息的变化,使用防抖降低油猴写配置信息频率
watch(
uiConfig,
@@ -80,8 +88,9 @@ export const useUIStore = defineStore('ui', () => {
return {
isShowPanelButtonText,
activeMenuName,
- baseStyleValue,
- baseStyle,
+ livePlayerRect,
+ windowScrollPosition,
+ panelStyle,
scrollBarHeight,
uiConfig,
changeCollapse,
diff --git a/src/types/cookies.d.ts b/src/types/cookies.d.ts
index c86986d..67ba4dd 100644
--- a/src/types/cookies.d.ts
+++ b/src/types/cookies.d.ts
@@ -1,5 +1,5 @@
type CookieNames = 'bili_jct' | 'LIVE_BUVID' | 'buvid3'
-type BiliCookies = Record
+type BiliCookies = Record
export { BiliCookies }
diff --git a/src/types/helpInfo.d.ts b/src/types/helpInfo.d.ts
index fbc19b8..ecd5b5b 100644
--- a/src/types/helpInfo.d.ts
+++ b/src/types/helpInfo.d.ts
@@ -38,7 +38,6 @@ interface HelpInfo {
noReport: HelpInfoItem
noSleep: HelpInfoItem
invisibility: HelpInfoItem
- showContributionUserNum: HelpInfoItem
}
RemoveElement: {
removePKBox: HelpInfoItem
diff --git a/src/types/storage.d.ts b/src/types/storage.d.ts
index d7e0f56..7caca27 100644
--- a/src/types/storage.d.ts
+++ b/src/types/storage.d.ts
@@ -79,9 +79,6 @@ interface ModuleConfig {
invisibility: {
enabled: boolean
}
- showContributionUserNum: {
- enabled: boolean
- }
}
RemoveElement: {
removePKBox: {
@@ -135,11 +132,13 @@ type MenuIndex =
| 'OtherTasks'
| 'EnhanceExperience'
| 'RemoveElement'
+ | 'ScriptSettings'
interface UiConfig {
isCollapse: boolean
isShowPanel: boolean
activeMenuIndex: MenuIndex
+ panelWidthPercent: number
}
interface Cache {