1
2
3
4
5
6
7
8
9
10
11
case 68:
if (this.panoramaStore.shortcutPrior || this.danmakuStore.state.state)
return;
if (!this.keydownMap.get(_[i]).status) {
this.keydownMap.get(_[i]).status = !0;
var r = this.dmSettingStore.toggleDmSwitch() // 猜测dm是“弹幕”的意思
, l = r !== T.cF.Closed;
this.shortcutTooltip("shortcut", l ? r === T.cF.Concise ? "精简弹幕" : "开启弹幕" : "关闭弹幕"),
this.track.e("shortcut_dmswitch", +!!l)
}
break;
找 this.shortcutTooltip的定义.
1
2
3
4
5
6
7
8
9
i.shortcutTooltip = function(t, e) {
this.tooltipStore.remove(t),
this.tooltipStore.create({
name: t,
title: e,
target: this.nodes.videoArea,
position: p.Zj.CenterCenter
})
}
但是接下来找不到 tooltipStore.create的定义了.
我们可以借助控制台来查看。通过调试到特定位置,然后输入this.tooltipStore:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
*r {rootStore: n, rootPlayer: n, nodes: {…}, track: n, dp: {…}, …}*
1. **assetStore**: r {rootStore: n, rootPlayer: n, nodes: {…}, track: n, dp: {…}, …}
2. **auxStore**: r {rootStore: n, rootPlayer: n, nodes: {…}, track: n, dp: {…}, …}
3. **blockStore**: r {rootStore: n, rootPlayer: n, nodes: {…}, track: n, dp: {…}, …}
......
50. **storyStore**: r {rootStore: n, rootPlayer: n, nodes: {…}, track: n, dp: {…}, …}
51. **subtitleStore**: r {rootStore: n, rootPlayer: n, nodes: {…}, track: n, dp: {…}, …}
52. **toastStore**: r {rootStore: n, rootPlayer: n, nodes: {…}, track: n, dp: {…}, …}
53. **tooltipStore**: r {rootStore: n, rootPlayer: n, nodes: {…}, track: n, dp: {…}, …}
54. **track**: n {queues: Array(0), toBeDefined: 'TBD', getRealtimeReportInfo: *ƒ*, reportSender: *ƒ*}
55. **trackStore**: r {rootStore: n, rootPlayer: n, nodes: {…}, track: n, dp: {…}, …}
56. **translationStore**: r {rootStore: n, rootPlayer: n, nodes: {…}, track: n, dp: {…}, …}
57. **uiStore**: r {rootStore: n, rootPlayer: n, nodes: {…}, track: n, dp: {…}, …}
58. **upStore**: r {rootStore: n, rootPlayer: n, nodes: {…}, track: n, dp: {…}, …}
59. **userStore**: r {rootStore: n, rootPlayer: n, nodes: {…}, track: n, dp: {…}, …}
60. **viewTrack**: r {rootStore: n, rootPlayer: n, nodes: {…}, track: n, dp: {…}, …}
61. **wmpStore**: r {rootStore: n, rootPlayer: n, nodes: {…}, track: n, dp: {…}, …}
62. **Symbol(mobx administration)**: n {target\_: r, values\_: Map(1), name\_: 'ObservableObject', defaultAnnotation\_: {…}, keysAtom\_: n, …}
63. **Symbol(mobx-stored-annotations)**: {}
64. defaultState: (...)
65. log: (...)
66. get records: *ƒ ()*
67. set records: *ƒ (r)*
68. [[Prototype]]: r
1. **onDisconnect**: *ƒ ()*
2. **tryStartTimerClock**: *ƒ (n)*
3. **constructor**: *ƒ r(n)*
4. **create**: *ƒ u()*
5. **remove**: *ƒ u()*
6. **update**: *ƒ u()*
7. **Symbol(mobx-stored-annotations)**: {records: {…}, create: {…}, update: {…}, remove: {…}}
8. defaultState: (...)
9. log: (...)
10. [[Prototype]]: n
1. **changeState**: *ƒ (n,r)*
2. **onCommitted**: *ƒ ()*
3. **onDispose**: *ƒ (n)*
4. **onGraphFlatten**: *ƒ ()*
5. **onMount**: *ƒ ()*
6. **onMounted**: *ƒ ()*
7. **onNavigate**: *ƒ ()*
8. **onPrepared**: *ƒ ()*
9. **onStateCreate**: *ƒ ()*
10. **onStateCreated**: *ƒ ()*
11. **onStorageBuild**: *ƒ ()*
12. **setState**: *ƒ (n,r)*
13. **constructor**: *ƒ r(n)*
14. **defaultState**: (...)
15. log: (...)
16. get defaultState: *ƒ ()*
17. [[Prototype]]: Object
重要的是最后的prototype: r,可以看到这里方法中有create, remove, update等.
1
create : ƒ u() isMobxAction : true length : 0 name : "create" prototype : {} arguments : (...) caller : (...) [[FunctionLocation]] : VM41 core.8a3d87a9.js:1 [[Prototype]] : ƒ () [[Scopes]] : Scopes[6]
接着点击定位function location.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function ta(n, r, i, o) {
function u() {
return ts(n, i, r, o || this, arguments)
}
return void 0 === i && (i = !1),
u.isMobxAction = !0,
ti && (to.value = n,
T(u, "name", to)),
u
}
function ts(n, r, i, o, u) {
var l = tu(n, r, o, u);
try {
return i.apply(o, u)
} catch (n) {
throw l.error_ = n,
n
} finally {
tl(l)
}
}
混淆的代码,追踪到后面就很难手工分析了.
1
2
3
4
5
6
7
8
或者用控制台方法:
如果在控制台先输入一次`const ts = this.tooltipStore` (显示undefined正常,因为const赋值语句本来就会返回undefined), 但是下一次再输入ts会显示undefined, 是因为前面的ts作用域已经结束.
可以用 globalThis.ts = ..., 可以跨越多次命令求解.
输入ts.create,显示
ƒ u(){return ts(n,i,r,o||this,arguments)}
ta是MobX action wrapper,AI说里面比较重要的是records,
1
2
3
4
5
6
7
8
9
10
11
12
13
n {name_: 'ObservableMap', data_: Map(18), hasMap_: Map(18), keysAtom_: n, enhancer_: ƒ, …}
changeListeners_: undefined
data_: Map(18) {'ctrl:codec:0' => r, 'ctrl:codec:1' => r, 'ctrl:codec:2' => r, 'ctrl:codec:3' => r, 'ctrl:longpress-rate:smart' => r, …}
dehancer: undefined
enhancer_: ƒ em(n,r,i)
hasMap_: Map(18) {'ctrl:codec:0' => r, 'ctrl:codec:1' => r, 'ctrl:codec:2' => r, 'ctrl:codec:3' => r, 'ctrl:longpress-rate:smart' => r, …}
interceptors_: undefined
keysAtom_: n {name_: 'ObservableMap.keys()', isPendingUnobservation_: false, isBeingObserved_: true, observers_: Set(1), diffValue_: 0, …}
name_: "ObservableMap"
Symbol(mobx administration): {}
size: (...)
Symbol(Symbol.toStringTag): (...)
[[Prototype]]: Object
在create的时候按下step over, 然后再次检查records,data_部分多了一个”shortcut”,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
changeListeners_: undefined
data_: Map(19)
[[Entries]]
0: {"ctrl:codec:0" => r}
1: {"ctrl:codec:1" => r}
2: {"ctrl:codec:2" => r}
3: {"ctrl:codec:3" => r}
4: {"ctrl:longpress-rate:smart" => r}
5: {"ctrl:loundness:0" => r}
6: {"ctrl:loundness:1" => r}
7: {"ctrl:loundness:2" => r}
8: {"danmaku_switch" => r}
9: {"preventshade" => r}
10: {"special-more" => r}
11: {"feedback-btn" => r}
12: {"ctrl:pip" => r}
13: {"ctrl:fullscreen" => r}
14: {"ctrl:prev" => r}
15: {"ctrl:next" => r}
16: {"ctrl:widescreen" => r}
17: {"ctrl:webscreen" => r}
18: {"shortcut" => r}
size: 19
[[Prototype]]: Map
dehancer: undefined
enhancer_: ƒ em(n,r,i)
hasMap_: Map(19) {'ctrl:codec:0' => r, 'ctrl:codec:1' => r, 'ctrl:codec:2' => r, 'ctrl:codec:3' => r, 'ctrl:longpress-rate:smart' => r, …}
interceptors_: undefined
keysAtom_: n {name_: 'ObservableMap.keys()', isPendingUnobservation_: false, isBeingObserved_: true, observers_: Set(1), diffValue_: 0, …}
name_: "ObservableMap"
Symbol(mobx administration): {}
size: (...)
Symbol(Symbol.toStringTag): (...)
[[Prototype]]: Object
重点看这个shortcut, 控制台输入 this.tooltipStore.records.get(“shortcut”),
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{…}
delay: 300
duration: 3000
gap: 6
name: "shortcut"
offsetX: 0
offsetY: 0
position: 5
renderGap: 8
renderTarget: div.bpx-player-tooltip-area
target: div.bpx-player-video-area
timeStamp: 1788073261429
timerClock: r
title: "开启弹幕"
Symbol(mobx administration): n {target_: {…}, values_: Map(13), name_: 'ObservableMap.key', defaultAnnotation_: {…}, keysAtom_: n, …}
get delay: ƒ ()
set delay: ƒ (r)
get duration: ƒ ()
set duration: ƒ (r)
get gap: ƒ ()
set gap: ƒ (r)
get name: ƒ ()
set name: ƒ (r)
......
get title: ƒ ()
set title: ƒ (r)
[[Prototype]]: Object
现在开始着手屏蔽.
AI查找相关资料,从DOM层能确定最终屏蔽的对象是.bpx-player-tooltip-area (TODO)
1
2
3
playTooltipArea: '.bpx-player-tooltip-area'
playTooltipItem: '.bpx-player-tooltip-item'
playTooltipTitle: '.bpx-player-tooltip-title'
tempmonkey
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// ==UserScript==
// @name Bilibili 隐藏弹幕快捷键提示
// @match https://www.bilibili.com/video/*
// @run-at document-start
// @grant none
// ==/UserScript==
(() => {
'use strict';
const blockedTexts = new Set([
'开启弹幕',
'关闭弹幕',
'精简弹幕',
]);
const hideTooltip = (root) => {
// root 本身也可能就是 title
const titles = [];
if (root instanceof Element &&
root.matches('.bpx-player-tooltip-title')) {
titles.push(root);
}
if (root instanceof Element || root instanceof Document) {
titles.push(
...root.querySelectorAll('.bpx-player-tooltip-title')
);
}
for (const title of titles) {
if (!blockedTexts.has(title.textContent.trim())) {
continue;
}
const item = title.closest('.bpx-player-tooltip-item');
if (item) {
item.style.setProperty('display', 'none', 'important');
}
}
};
const observer = new MutationObserver(records => {
for (const record of records) {
for (const node of record.addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE) {
hideTooltip(node);
}
}
if (record.type === 'characterData') {
const parent = record.target.parentElement;
if (parent) {
hideTooltip(parent);
}
}
}
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
characterData: true,
});
})();
无效.
新版的也无效:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
// ==UserScript==
// @name Bilibili 隐藏弹幕快捷键提示
// @namespace http://tampermonkey.net/
// @description Don't show floated tips when using "d" in bilibili
// @version AngelBeats!
// @match https://www.bilibili.com/*
// @run-at document-start
// @grant none
// ==/UserScript==
(() => {
'use strict';
const blockedTexts = new Set([
'开启弹幕',
'关闭弹幕',
'精简弹幕',
]);
function checkTitle(title) {
if (!title?.matches?.('.bpx-player-tooltip-title')) {
return;
}
const item = title.closest('.bpx-player-tooltip-item');
if (!item) {
return;
}
const blocked = blockedTexts.has(title.textContent.trim());
if (blocked) {
item.style.setProperty('display', 'none', 'important');
} else {
// Bilibili 可能复用同一个 tooltip-item,
// 所以显示其他提示时要撤掉我们加的 display:none。
if (item.style.getPropertyPriority('display') === 'important' &&
item.style.display === 'none') {
item.style.removeProperty('display');
}
}
}
function checkNode(node) {
if (!(node instanceof Element)) {
return;
}
if (node.matches('.bpx-player-tooltip-title')) {
checkTitle(node);
}
for (const title of node.querySelectorAll('.bpx-player-tooltip-title')) {
checkTitle(title);
}
}
function start() {
// 先处理已经存在的节点
for (const title of document.querySelectorAll('.bpx-player-tooltip-title')) {
checkTitle(title);
}
const observer = new MutationObserver(records => {
for (const record of records) {
// 关键:
// textContent = "开启弹幕" 往往表现为
// title 元素内部 Text Node 被替换。
//
// 所以检查 record.target,而不仅仅是 addedNodes。
if (record.target instanceof Element) {
checkNode(record.target);
}
// 同时处理真正新增的 tooltip DOM
for (const node of record.addedNodes) {
if (node instanceof Element) {
checkNode(node);
}
}
}
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
characterData: true,
});
}
if (document.documentElement) {
start();
} else {
new MutationObserver((_, observer) => {
if (document.documentElement) {
observer.disconnect();
start();
}
}).observe(document, {
childList: true,
});
}
})();
ver2: 直接隐藏,也无效:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// ==UserScript==
// @name Bilibili 隐藏 D 键弹幕提示
// @namespace http://tampermonkey.net/
// @description Hide danmaku shortcut tooltip when pressing D
// @version 1.0
// @match https://www.bilibili.com/*
// @run-at document-start
// @grant none
// ==/UserScript==
(() => {
'use strict';
const style = document.createElement('style');
style.textContent = `
html.tm-hide-danmaku-tip
.bpx-player-tooltip-item[data-name="shortcut"] {
display: none !important;
visibility: hidden !important;
opacity: 0 !important;
}
`;
(document.head || document.documentElement).appendChild(style);
let timer = null;
window.addEventListener('keydown', e => {
if (
e.code !== 'KeyD' &&
e.key?.toLowerCase() !== 'd'
) {
return;
}
document.documentElement.classList.add(
'tm-hide-danmaku-tip'
);
clearTimeout(timer);
// Bilibili 的 shortcut tooltip 会存在一段时间,
// 在这期间直接通过 CSS 禁止显示。
timer = setTimeout(() => {
document.documentElement.classList.remove(
'tm-hide-danmaku-tip'
);
}, 2500);
}, true);
})();
最后发现script根本没有执行,原因是需要提前在chrome://extension -> tempmonkey -> detail中勾选allow user script.