屏蔽含關鍵詞評論,支援添加與管理關鍵詞,依評分設定生效範圍
// ==UserScript==
// @name Kmoe屏蔽器
// @author yaulei
// @version 1.5
// @description 屏蔽含關鍵詞評論,支援添加與管理關鍵詞,依評分設定生效範圍
// @match *://mox.moe/*
// @match *://kox.moe/*
// @match *://koz.moe/*
// @exclude *://mox.moe/c/10001.htm
// @exclude *://kox.moe/c/10001.htm
// @exclude *://koz.moe/c/10001.htm
// @grant none
// @license MIT
// @namespace https://greasyfork.org/users/1379965
// ==/UserScript==
(function() {
'use strict';
const excludedUrls = [
"https://mox.moe/c/10001.htm",
"https://kox.moe/c/10001.htm"
];
const currentUrl = window.location.href;
if (excludedUrls.includes(currentUrl)) return;
const STORAGE_KEY = 'kox_block_keywords';
const RATING_FILTER_KEY = 'kox_rating_filters';
// ===== UI 元素創建 =====
// 外層容器(固定在左下角)
const uiWrapper = document.createElement('div');
uiWrapper.style.position = 'fixed';
uiWrapper.style.bottom = '20px';
uiWrapper.style.left = '20px';
uiWrapper.style.zIndex = '9999';
uiWrapper.style.fontSize = '14px';
uiWrapper.style.maxWidth = '260px';
// 主按鈕:打開設定面板
const toggleButton = document.createElement('button');
toggleButton.textContent = '屏蔽器設定';
toggleButton.style.width = '100%';
toggleButton.style.marginBottom = '6px';
// 面板容器(初始隱藏)
const panel = document.createElement('div');
panel.style.display = 'none';
panel.style.background = 'white';
panel.style.border = '1px solid #ccc';
panel.style.borderRadius = '8px';
panel.style.padding = '8px';
panel.style.boxShadow = '0 2px 10px rgba(0,0,0,0.2)';
panel.style.marginTop = '6px';
// 按鈕切換面板顯示
toggleButton.onclick = () => {
panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
};
// ===== 添加屏蔽詞輸入欄 =====
const input = document.createElement('input');
input.type = 'text';
input.placeholder = '輸入詞語(多個詞使用空格分隔)';
input.style.width = '100%';
input.style.marginTop = '4px';
const addButton = document.createElement('button');
addButton.textContent = '添加';
addButton.style.marginTop = '4px';
addButton.style.width = '100%';
panel.appendChild(input);
panel.appendChild(addButton);
// ===== 屏蔽範圍選擇(折疊區塊) =====
const ratingSection = document.createElement('details');
const ratingSummary = document.createElement('summary');
ratingSummary.textContent = '屏蔽範圍';
ratingSection.appendChild(ratingSummary);
const ratingOptions = ["未评分", "1", "2", "3", "4", "5"];
const selectedRatings = new Set(JSON.parse(localStorage.getItem(RATING_FILTER_KEY)) || ratingOptions);
ratingOptions.forEach(label => {
const btn = document.createElement('button');
btn.textContent = label;
btn.style.margin = '2px';
btn.style.padding = '2px 5px';
btn.style.border = '1px solid #888';
btn.style.borderRadius = '4px';
btn.style.background = selectedRatings.has(label) ? '#ccc' : '#fff';
btn.onclick = () => {
if (selectedRatings.has(label)) {
selectedRatings.delete(label);
btn.style.background = '#fff';
} else {
selectedRatings.add(label);
btn.style.background = '#ccc';
}
localStorage.setItem(RATING_FILTER_KEY, JSON.stringify(Array.from(selectedRatings)));
hideComments();
};
ratingSection.appendChild(btn);
});
panel.appendChild(ratingSection);
// ===== 屏蔽詞管理區塊(折疊) =====
const section = document.createElement('details');
const summary = document.createElement('summary');
summary.textContent = '屏蔽詞管理';
section.appendChild(summary);
const keywordList = document.createElement('div');
keywordList.style.display = 'flex';
keywordList.style.flexWrap = 'wrap';
keywordList.style.gap = '6px';
keywordList.style.marginTop = '6px';
section.appendChild(keywordList);
panel.appendChild(section);
// ===== 關鍵詞管理邏輯 =====
function getKeywords() {
return JSON.parse(localStorage.getItem(STORAGE_KEY)) || [];
}
function saveKeywords(list) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(list));
}
function updateKeywordList() {
keywordList.innerHTML = '';
getKeywords().forEach((kw, i) => {
const item = document.createElement('span');
item.textContent = kw;
item.style.border = '1px solid #ccc';
item.style.padding = '2px 6px';
item.style.borderRadius = '4px';
item.style.background = '#f5f5f5';
item.style.display = 'inline-flex';
item.style.alignItems = 'center';
const del = document.createElement('button');
del.textContent = 'x';
del.style.marginLeft = '4px';
del.onclick = () => {
const updated = getKeywords().filter((_, idx) => idx !== i);
saveKeywords(updated);
updateKeywordList();
hideComments();
};
item.appendChild(del);
keywordList.appendChild(item);
});
}
function addKeywords() {
const newWords = input.value.trim().split(/\s+/);
if (!newWords.length || newWords[0] === '') return;
const current = getKeywords();
const updated = [...new Set([...current, ...newWords])];
saveKeywords(updated);
input.value = '';
updateKeywordList();
hideComments();
}
addButton.onclick = addKeywords;
input.addEventListener('keydown', e => {
if (e.key === 'Enter') {
e.preventDefault();
addKeywords();
}
});
// ===== 評論過濾邏輯 =====
function hideComments() {
const keywords = getKeywords();
const ratingSet = new Set(JSON.parse(localStorage.getItem(RATING_FILTER_KEY)) || ratingOptions);
const commentElements = document.querySelectorAll('td[id^="comm_cont_"]');
commentElements.forEach(comment => {
const text = comment.innerText || comment.textContent;
const ratingMatch = text.match(/對本書評價\s*:\s*(\d)\s*星/);
const rating = ratingMatch ? ratingMatch[1] : "未评分";
const match = keywords.some(kw => text.includes(kw));
comment.style.display = match && ratingSet.has(rating) ? 'none' : '';
});
}
// 點擊非 UI 區域自動折疊設定面板
document.addEventListener('click', function(e) {
if (!uiWrapper.contains(e.target)) {
panel.style.display = 'none';
}
});
// 初始化 UI
uiWrapper.appendChild(toggleButton);
uiWrapper.appendChild(panel);
document.body.appendChild(uiWrapper);
updateKeywordList();
hideComments();
// 監控 DOM 動態變更,自動重新篩選
const observer = new MutationObserver(hideComments);
observer.observe(document.body, { childList: true, subtree: true });
})();