Extension available on chrome web store: https://chromewebstore.google.com
Update the GitHub project https://github.com/Jerrysmd/weibo-content-filter. Add popup page and allow for custom configurations
Extension now available on chrome web store: https://chromewebstore.google.com
Extension 1 step: Config manifest.json
- content_scripts: Injects scripts into web pages that match specified URLs, runs at document start.
- permissions: Defines the APIs the extension needs access to, like browser storage.
- action: Specifies the popup displayed when the user clicks the extension icon.
- background: Runs scripts in the background, even when the popup is not open.
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
| {
"name": "大眼夹(新版微博)",
"short_name": "大眼夹",
"version": "0.1",
"manifest_version": 3,
"description": "新版微博非官方插件,去除时间线上的推荐、推广和广告微博。",
"icons": {
"48": "images/weiboFilter.png",
"128": "images/weiboFilter.large.png"
},
"content_scripts": [
{
"matches": [
"https://weibo.com/*",
"https://www.weibo.com/*",
"https://d.weibo.com/*",
"http://d.weibo.com/*",
"http://weibo.com/*",
"http://www.weibo.com/*"
],
"js": [
"main.js"
],
"run_at": "document_start"
}
],
"permissions": [
"storage"
],
"action": {
"default_popup": "popup.html"
},
"background": {
"service_worker": "background.js"
}
}
|
Extension 2 step: Setup popup HTML and JS
- The
DOMContentLoaded
event ensures the code runs after the popup’s HTML is loaded. - The
addEventListener
attaches handlers to the checkboxes, updating the browser’s storage when their state changes.
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
| document.addEventListener('DOMContentLoaded', function () {
chrome.storage.sync.get(['hotPush', 'recommend', 'mediaRecommend', 'ad', 'fansTop'], function (items) {
document.getElementById('hotPush').checked = items.hotPush || false;
document.getElementById('recommend').checked = items.recommend || false;
document.getElementById('mediaRecommend').checked = items.mediaRecommend || false;
document.getElementById('ad').checked = items.ad || false;
document.getElementById('fansTop').checked = items.fansTop || false;
});
document.getElementById('hotPush').addEventListener('change', function () {
chrome.storage.sync.set({ 'hotPush': this.checked });
});
document.getElementById('recommend').addEventListener('change', function () {
chrome.storage.sync.set({ 'recommend': this.checked });
});
document.getElementById('mediaRecommend').addEventListener('change', function () {
chrome.storage.sync.set({ 'mediaRecommend': this.checked });
});
document.getElementById('ad').addEventListener('change', function () {
chrome.storage.sync.set({ 'ad': this.checked });
});
document.getElementById('fansTop').addEventListener('change', function () {
chrome.storage.sync.set({ 'fansTop': this.checked });
});
});
|
Extension 3 step: Setup background.js
The background.js
file separates storage logic from the content script (main.js
). This allows for:
- Cleaner separation of concerns
- Asynchronous communication between scripts
- Centralized, more efficient storage management
- Easier maintenance and flexibility
1
2
3
4
5
6
7
8
| chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === 'getSettings') {
chrome.storage.sync.get(['hotPush', 'recommend', 'mediaRecommend', 'ad', 'fansTop'], function (items) {
sendResponse(items);
});
return true;
}
});
|
Extension 4 step: Make main.js
- Callback and Async:
- The
callback
function is asynchronous and uses the await
keyword to call the getSettings()
function. - This allows the code to wait for the settings to be retrieved from the background script before processing the DOM mutations.
- MutationObserver:
- The
MutationObserver
is used to monitor changes to the DOM structure of the web page. - When changes are detected, the
callback
function is called to process the mutations.
- Config:
- The
config
object defines the types of mutations the MutationObserver
should watch for. - In this case, it’s set to watch for changes to the child nodes (
childList: true
) and recursively observe the entire subtree (subtree: 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
| const getSettings = () => {
return new Promise((resolve, reject) => {
chrome.runtime.sendMessage({ action: 'getSettings' }, (response) => {
if (chrome.runtime.lastError) {
reject(chrome.runtime.lastError);
} else {
resolve(response);
}
});
});
};
const callback = async (mutationsList, observer) => {
try {
const items = await getSettings();
mutationsList.forEach(mutation => {
if (mutation.type === 'childList') {
const divs = document.querySelectorAll('#scroller > div.vue-recycle-scroller__item-wrapper > div');
divs.forEach(function (div) {
const targetDiv = div.querySelector('div.wbpro-tag.wbpro-tag-c2.head-info_tag_3iMJw > div');
const targetContent = targetDiv ? targetDiv.textContent.trim() : '';
const isPromotedPost =
(items.hotPush && targetContent.match(/^(热推)$/))
|| (items.recommend && targetContent.match(/^(推荐)$/))
|| (items.mediaRecommend && targetContent.match(/^(媒体推荐)$/))
|| (items.ad && div.querySelector('div[mark*="reallog_mark_ad"]') && !div.querySelector('div[mark*="999_reallog_mark_ad"]') && !targetContent.match(/^(热推)$/))
|| (items.fansTop && (targetContent.match(/^(粉丝头条)$/) || div.querySelector('div[mark*="FansTop"]')));
const hasRepost = !!div.querySelector("div.Feed_retweet_JqZJb");
const displayValue = isPromotedPost ? 'none' : '';
if (hasRepost) {
div.querySelectorAll("div.wbpro-feed-content, div.Feed_retweet_JqZJb, footer").forEach(el => {
el.style.display = displayValue;
});
} else {
div.querySelectorAll("div.wbpro-feed-content, footer").forEach(el => {
el.style.display = displayValue;
});
}
});
}
});
} catch (error) {
console.error('Failed to get settings:', error);
}
};
const observer = new MutationObserver(callback);
const config = {
childList: true,
subtree: true
};
observer.observe(document, config);
console.log('Observer started');
|