-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathscript.js
172 lines (138 loc) · 4.35 KB
/
script.js
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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
if (typeof browser == 'undefined') {
globalThis.browser = chrome;
}
const AD_WORDS = [
'ads?', // en
'iklan', // id
// add more if you see in your language
];
const rgxAdd = new RegExp(`^(${AD_WORDS.join('|')})$`, 'i');
const DB_KEY_CONFIG = 'config';
const DB_KEY_BLOCKED_COUNT = 'blockedCount';
class Blocker {
static EVENT_POST_BLOCKED = 'BLOCKER_POST_BLOCKED';
static EVENT_CONFIG_CHANGED = 'BLOCKER_CONFIG_CHANGED';
#config;
#domObserver;
#eventBroker;
constructor({ config = defaultConfig, eventBroker = new EventTarget() }) {
this.#config = config;
this.#iniConfig();
this.#initEventBroker(eventBroker);
this.start();
}
#initEventBroker(eventBroker) {
this.#eventBroker = eventBroker;
this.#eventBroker?.addEventListener(Blocker.EVENT_CONFIG_CHANGED, async (event) => {
this.#log('blocker config changed');
// activate/deactivate blocker
if (this.#config?.isActive) {
this.start();
} else {
this.stop();
}
if (this.#config?.isRestartAfterConfigChanged) this.restart();
});
this.#eventBroker?.addEventListener(Blocker.EVENT_POST_BLOCKED, async (event) => {
this.#incBlockCount();
});
}
getConfig() {
return this.#config;
}
setConfig(config) {
this.#iniConfig(config);
this.#eventBroker?.dispatchEvent(
new CustomEvent(Blocker.EVENT_CONFIG_CHANGED, { detail: { config: this.#config } }),
);
}
#iniConfig(config) {
this.#config = { ...this.#config, ...config };
this.#config._rgxBlocked = new RegExp((this.#config?.blockedKeywords ?? []).join('|'), 'i');
}
restart() {
this.#log('blocker observer restart...');
this.stop();
this.start();
this.#log('blocker observer restarted');
}
start() {
if (this.#isRunning()) return;
this.#domObserver = new MutationObserver((mutations) => {
this.#mutationObserverCallback(mutations);
});
this.#domObserver?.observe(document.body, { childList: true, subtree: true });
this.#log('blocker observer started');
}
stop() {
if (!this.#isRunning()) return;
this.#domObserver?.disconnect();
this.#domObserver = null;
this.#log('blocker observer stopped');
}
#isRunning() {
return !!this.#domObserver;
}
#mutationObserverCallback(mutations) {
if (!this.#config?.isActive) return;
for (let mutation of mutations) {
if (mutation.type !== 'childList') continue;
for (let node of mutation.addedNodes) {
if (node.nodeType !== Node.ELEMENT_NODE) continue;
if (!this.#isPostBlocked(node)) continue;
// for now, set display to none
// last time, removing node break the website
node.style.display = 'none';
this.#eventBroker?.dispatchEvent(new CustomEvent(Blocker.EVENT_POST_BLOCKED));
}
}
}
#isPostBlocked(element) {
return this.#isPost(element) && this.#hasBlockedElement(element);
}
#isPost(element) {
return element.getAttribute('data-testid') === 'cellInnerDiv';
}
#hasBlockedElement({ textContent, children }) {
// ads
if (this.#config?.isBlockAd && rgxAdd.test(textContent)) {
this.#log('blocked ad:', textContent);
return true;
}
// blocked keywords
if (this.#config?.blockedKeywords?.length > 0 && this.#config?._rgxBlocked.test(textContent)) {
this.#log('blocked keyword:', textContent);
return true;
}
// find it inside the children
for (const child of children) {
if (!this.#hasBlockedElement(child)) continue;
return true;
}
return false;
}
async #incBlockCount() {
const db = await browser?.storage?.sync?.get(DB_KEY_BLOCKED_COUNT);
const count = (db?.[DB_KEY_BLOCKED_COUNT] ?? 0) + 1;
await browser?.storage?.sync?.set({ [DB_KEY_BLOCKED_COUNT]: count });
return count;
}
#log(...data) {
if (!this.#config?.verbose) return;
console.log(...data);
}
}
async function exec() {
const db = await browser?.storage?.sync?.get(DB_KEY_CONFIG);
const config = db?.[DB_KEY_CONFIG];
if (!config?.isActive) return;
const eventBroker = new EventTarget();
const blocker = new Blocker({ config, eventBroker });
browser?.storage?.onChanged?.addListener((changes, area) => {
if (area != 'sync') return;
const config = changes?.[DB_KEY_CONFIG]?.newValue;
if (!config) return;
blocker.setConfig(config);
});
}
exec();