Greasy Fork is available in English.
可自定义过滤条件的电影过滤器,支持设置最低评分和N/A处理,可配置统计窗口,增加显示/隐藏被过滤项目的按钮
当前为
// ==UserScript==
// @name UBits 智能电影过滤器
// @namespace http://tampermonkey.net/
// @version 1.4
// @description 可自定义过滤条件的电影过滤器,支持设置最低评分和N/A处理,可配置统计窗口,增加显示/隐藏被过滤项目的按钮
// @author Dost
// @match https://ubits.club/torrents.php*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// 默认配置
const DEFAULT_CONFIG = {
minIMDbRating: 6.1,
minDoubanRating: 6.5,
removeNA: false,
removeOnlyAllNA: true,
requireBothRatings: false,
showDebugInfo: true,
enabled: true,
showNotification: true,
notificationDuration: 8
};
// 加载保存的配置
let FILTER_CONFIG;
try {
FILTER_CONFIG = Object.assign({}, DEFAULT_CONFIG, JSON.parse(GM_getValue('UBitsFilterConfig', '{}')));
} catch (e) {
FILTER_CONFIG = Object.assign({}, DEFAULT_CONFIG);
console.error('Failed to load saved config, using defaults', e);
}
// 注册菜单命令
GM_registerMenuCommand('配置UBits电影过滤器', showConfigUI);
// 主函数
function main() {
if (!FILTER_CONFIG.enabled) {
console.log('UBits电影过滤器已禁用');
return;
}
addControlButtons();
applyFilters();
}
// 应用过滤器
function applyFilters() {
const stats = {
totalChecked: 0,
totalRemoved: 0,
removedByLowIMDb: 0,
removedByLowDouban: 0,
removedByNA: 0,
removedByMissingRating: 0
};
const rows = document.querySelectorAll('tr:has(> td > div[style*="display: flex; flex-direction: column"])');
rows.forEach(tr => {
stats.totalChecked++;
const ratingContainer = tr.querySelector('div[style*="display: flex; flex-direction: column"]');
if (!ratingContainer) return;
const ratingSpans = ratingContainer.querySelectorAll('span');
const imdbRating = parseRating(ratingSpans[0]?.textContent);
const doubanRating = ratingSpans[1] ? parseRating(ratingSpans[1]?.textContent) : null;
let shouldRemove = false;
const removeReasons = [];
// 检查双评分要求
if (FILTER_CONFIG.requireBothRatings && (!imdbRating.valid || !doubanRating?.valid)) {
shouldRemove = true;
removeReasons.push('缺少有效评分');
stats.removedByMissingRating++;
}
// 检查N/A
if (FILTER_CONFIG.removeNA) {
const imdbNA = imdbRating.isNA;
const doubanNA = doubanRating ? doubanRating.isNA : true;
if (FILTER_CONFIG.removeOnlyAllNA) {
// 仅当两个评分都是NA时才删除
if (imdbNA && doubanNA) {
shouldRemove = true;
removeReasons.push('双评分均为N/A');
stats.removedByNA++;
}
} else {
// 任意一个评分为NA就删除
if (imdbNA || doubanNA) {
shouldRemove = true;
removeReasons.push('存在N/A评分');
stats.removedByNA++;
}
}
}
// 检查IMDb评分
if (FILTER_CONFIG.minIMDbRating > 0 && imdbRating.valid && imdbRating.value < FILTER_CONFIG.minIMDbRating) {
shouldRemove = true;
removeReasons.push(`IMDb ${imdbRating.value} < ${FILTER_CONFIG.minIMDbRating}`);
stats.removedByLowIMDb++;
}
// 检查豆瓣评分
if (doubanRating && FILTER_CONFIG.minDoubanRating > 0 && doubanRating.valid && doubanRating.value < FILTER_CONFIG.minDoubanRating) {
shouldRemove = true;
removeReasons.push(`豆瓣 ${doubanRating.value} < ${FILTER_CONFIG.minDoubanRating}`);
stats.removedByLowDouban++;
}
if (shouldRemove) {
if (FILTER_CONFIG.showDebugInfo) {
console.log(`删除项目: ${removeReasons.join('; ')}`, tr);
}
tr.style.display = 'none';
tr.dataset.filtered = 'true';
stats.totalRemoved++;
}
});
showResults(stats);
}
function parseRating(ratingText) {
if (!ratingText) return { valid: false, isNA: true };
const text = ratingText.trim();
if (text === 'N/A' || text === '' || text === '-') {
return { valid: false, isNA: true };
}
const value = parseFloat(text);
if (isNaN(value)) {
return { valid: false, isNA: true };
}
return { valid: true, isNA: false, value: value };
}
function showResults(stats) {
const resultLines = [
`UBits电影过滤结果 (共检查 ${stats.totalChecked} 个项目)`,
`-------------------------------------`,
`隐藏总数: ${stats.totalRemoved}`,
...(stats.removedByLowIMDb > 0 ? [`- IMDb评分过低: ${stats.removedByLowIMDb}`] : []),
...(stats.removedByLowDouban > 0 ? [`- 豆瓣评分过低: ${stats.removedByLowDouban}`] : []),
...(stats.removedByNA > 0 ? [`- N/A评分: ${stats.removedByNA}`] : []),
...(stats.removedByMissingRating > 0 ? [`- 缺少有效评分: ${stats.removedByMissingRating}`] : []),
`-------------------------------------`,
`当前过滤条件:`,
`- 最低IMDb评分: ${FILTER_CONFIG.minIMDbRating > 0 ? FILTER_CONFIG.minIMDbRating : '不限制'}`,
`- 最低豆瓣评分: ${FILTER_CONFIG.minDoubanRating > 0 ? FILTER_CONFIG.minDoubanRating : '不限制'}`,
`- 删除N/A: ${FILTER_CONFIG.removeNA ? (FILTER_CONFIG.removeOnlyAllNA ? '仅双N/A' : '任意N/A') : '否'}`,
`- 要求双评分: ${FILTER_CONFIG.requireBothRatings ? '是' : '否'}`,
`- 过滤器状态: ${FILTER_CONFIG.enabled ? '启用' : '禁用'}`
];
const resultMsg = resultLines.join('\n');
console.log(resultMsg);
if (FILTER_CONFIG.showNotification) {
showNotification(resultMsg, FILTER_CONFIG.notificationDuration * 1000);
}
}
function showNotification(message, duration = 8000) {
const existing = document.getElementById('ubits-filter-notification');
if (existing) existing.remove();
const notification = document.createElement('div');
notification.id = 'ubits-filter-notification';
notification.style.cssText = `
position: fixed;
top: 10px;
right: 10px;
background-color: #f8f9fa;
color: #212529;
padding: 12px;
border-radius: 5px;
z-index: 9999;
box-shadow: 0 0 15px rgba(0,0,0,0.2);
max-width: 320px;
font-family: Arial, sans-serif;
font-size: 14px;
line-height: 1.5;
white-space: pre-line;
border-left: 4px solid #6c757d;
transform: translateX(120%);
transition: transform 0.3s ease-out;
`;
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.style.transform = 'translateX(0)';
}, 100);
let hideTimer;
const startHideTimer = () => {
hideTimer = setTimeout(() => {
notification.style.transition = 'opacity 1s';
notification.style.opacity = '0';
setTimeout(() => notification.remove(), 1000);
}, duration);
};
notification.addEventListener('mouseenter', () => {
clearTimeout(hideTimer);
notification.style.opacity = '1';
});
notification.addEventListener('mouseleave', startHideTimer);
startHideTimer();
}
function addControlButtons() {
// 移除旧按钮
const existing = document.querySelectorAll('#ubits-filter-config-btn, #ubits-filter-toggle-btn');
existing.forEach(btn => btn.remove());
// 创建配置按钮
const configBtn = document.createElement('button');
configBtn.id = 'ubits-filter-config-btn';
configBtn.textContent = '⚙️ 过滤器配置';
configBtn.style.cssText = `
position: fixed;
bottom: 70px;
right: 20px;
z-index: 9998;
padding: 8px 15px;
background: #6c757d;
color: white;
border: none;
border-radius: 20px;
cursor: pointer;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
font-family: Arial, sans-serif;
`;
configBtn.addEventListener('click', showConfigUI);
// 创建切换显示按钮
const toggleBtn = document.createElement('button');
toggleBtn.id = 'ubits-filter-toggle-btn';
toggleBtn.textContent = '👁️ 显示被过滤';
toggleBtn.style.cssText = `
position: fixed;
bottom: 20px;
right: 20px;
z-index: 9998;
padding: 8px 15px;
background: #17a2b8;
color: white;
border: none;
border-radius: 20px;
cursor: pointer;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
font-family: Arial, sans-serif;
`;
let showFiltered = false;
toggleBtn.addEventListener('click', () => {
showFiltered = !showFiltered;
toggleBtn.textContent = showFiltered ? '👁️ 隐藏被过滤' : '👁️ 显示被过滤';
toggleFilteredItems(showFiltered);
});
document.body.appendChild(configBtn);
document.body.appendChild(toggleBtn);
}
function toggleFilteredItems(show) {
const hiddenRows = document.querySelectorAll('tr[data-filtered="true"]');
hiddenRows.forEach(row => {
row.style.display = show ? '' : 'none';
});
}
function showConfigUI() {
const existing = document.getElementById('ubits-filter-config-dialog');
if (existing) existing.remove();
const dialog = document.createElement('div');
dialog.id = 'ubits-filter-config-dialog';
dialog.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
z-index: 10000;
box-shadow: 0 0 20px rgba(0,0,0,0.3);
border-radius: 8px;
width: 350px;
max-width: 90%;
font-family: Arial, sans-serif;
`;
dialog.innerHTML = `
<h3 style="margin-top:0;color:#495057">UBits电影过滤器配置</h3>
<div style="margin-bottom:15px">
<label style="display:flex;align-items:center">
<input type="checkbox" id="ubits-filter-enabled" ${FILTER_CONFIG.enabled ? 'checked' : ''} style="margin-right:8px">
启用过滤器
</label>
</div>
<div style="margin:20px 0;border-top:1px solid #eee;padding-top:15px">
<h4 style="margin:0 0 10px 0;color:#495057">过滤条件</h4>
<div style="margin-bottom:15px">
<label style="display:block;margin-bottom:5px;color:#495057">最低IMDb评分:</label>
<input type="number" id="ubits-filter-imdb" step="0.1" min="0" max="10" value="${FILTER_CONFIG.minIMDbRating}" style="width:100%;padding:8px;border:1px solid #ddd;border-radius:4px">
</div>
<div style="margin-bottom:15px">
<label style="display:block;margin-bottom:5px;color:#495057">最低豆瓣评分:</label>
<input type="number" id="ubits-filter-douban" step="0.1" min="0" max="10" value="${FILTER_CONFIG.minDoubanRating}" style="width:100%;padding:8px;border:1px solid #ddd;border-radius:4px">
</div>
<div style="margin-bottom:15px">
<label style="display:flex;align-items:center">
<input type="checkbox" id="ubits-filter-remove-na" ${FILTER_CONFIG.removeNA ? 'checked' : ''} style="margin-right:8px">
只要包含N/A的项目就隐藏(效果等同于第三个选项)
</label>
</div>
<div style="margin-bottom:15px">
<label style="display:flex;align-items:center">
<input type="checkbox" id="ubits-filter-remove-only-all-na" ${FILTER_CONFIG.removeOnlyAllNA ? 'checked' : ''} style="margin-right:8px" ${FILTER_CONFIG.removeNA ? '' : 'disabled'}>
仅当双评分均为N/A时隐藏(必须开启上一选项)
</label>
</div>
<div style="margin-bottom:15px">
<label style="display:flex;align-items:center">
<input type="checkbox" id="ubits-filter-require-both" ${FILTER_CONFIG.requireBothRatings ? 'checked' : ''} style="margin-right:8px">
必须同时包含IMDb和豆瓣评分(效果相当于前两个选项同时开启)
</label>
</div>
</div>
<div style="margin:20px 0;border-top:1px solid #eee;padding-top:15px">
<h4 style="margin:0 0 10px 0;color:#495057">通知设置</h4>
<div style="margin-bottom:15px">
<label style="display:flex;align-items:center">
<input type="checkbox" id="ubits-filter-show-notification" ${FILTER_CONFIG.showNotification ? 'checked' : ''} style="margin-right:8px">
显示统计窗口
</label>
</div>
<div style="margin-bottom:15px">
<label style="display:block;margin-bottom:5px;color:#495057">统计窗口显示时间(秒):</label>
<input type="number" id="ubits-filter-notification-duration" min="1" max="60" value="${FILTER_CONFIG.notificationDuration}" style="width:100%;padding:8px;border:1px solid #ddd;border-radius:4px">
</div>
</div>
<div style="display:flex;justify-content:space-between;margin-top:20px">
<button id="ubits-filter-save" style="padding:8px 15px;background:#28a745;color:white;border:none;border-radius:4px;cursor:pointer">保存</button>
<button id="ubits-filter-cancel" style="padding:8px 15px;background:#6c757d;color:white;border:none;border-radius:4px;cursor:pointer">取消</button>
</div>
`;
document.body.appendChild(dialog);
// 启用/禁用"仅当双评分均为N/A时删除"选项
document.getElementById('ubits-filter-remove-na').addEventListener('change', function() {
document.getElementById('ubits-filter-remove-only-all-na').disabled = !this.checked;
});
// 保存配置
document.getElementById('ubits-filter-save').addEventListener('click', () => {
FILTER_CONFIG.enabled = document.getElementById('ubits-filter-enabled').checked;
FILTER_CONFIG.minIMDbRating = parseFloat(document.getElementById('ubits-filter-imdb').value) || 0;
FILTER_CONFIG.minDoubanRating = parseFloat(document.getElementById('ubits-filter-douban').value) || 0;
FILTER_CONFIG.removeNA = document.getElementById('ubits-filter-remove-na').checked;
FILTER_CONFIG.removeOnlyAllNA = document.getElementById('ubits-filter-remove-only-all-na').checked;
FILTER_CONFIG.requireBothRatings = document.getElementById('ubits-filter-require-both').checked;
FILTER_CONFIG.showNotification = document.getElementById('ubits-filter-show-notification').checked;
FILTER_CONFIG.notificationDuration = parseInt(document.getElementById('ubits-filter-notification-duration').value) || 8;
GM_setValue('UBitsFilterConfig', JSON.stringify(FILTER_CONFIG));
dialog.remove();
resetFilters();
if (FILTER_CONFIG.enabled) {
applyFilters();
} else {
if (FILTER_CONFIG.showNotification) {
showNotification('UBits电影过滤器已禁用');
}
}
});
document.getElementById('ubits-filter-cancel').addEventListener('click', () => {
dialog.remove();
});
}
function resetFilters() {
const hiddenRows = document.querySelectorAll('tr[data-filtered="true"]');
hiddenRows.forEach(row => {
row.style.display = '';
row.removeAttribute('data-filtered');
});
}
window.addEventListener('load', main);
})();