为Javdb上评分高的影片增加热力图背景色,同时支持根据热力排序。
当前为
// ==UserScript==
// @name Javdb 优质影片高亮 (排序与优化版)
// @namespace http://tampermonkey.net/
// @version 1.0.0
// @description 为Javdb上评分高的影片增加热力图背景色,同时支持根据热力排序。
// @author JHT,黄页大嫖客(Modified by Gemini)
// @match https://javdb.com/*
// @grant GM_addStyle
// @license GPLv3
// @homeURL https://sleazyfork.org/zh-CN/scripts/539525-javdb-%E4%BC%98%E8%B4%A8%E5%BD%B1%E7%89%87%E9%AB%98%E4%BA%AE-%E6%8E%92%E5%BA%8F%E4%B8%8E%E4%BC%98%E5%8C%96%E7%89%88
// @supportURL https://sleazyfork.org/zh-CN/scripts/539525-javdb-%E4%BC%98%E8%B4%A8%E5%BD%B1%E7%89%87%E9%AB%98%E4%BA%AE-%E6%8E%92%E5%BA%8F%E4%B8%8E%E4%BC%98%E5%8C%96%E7%89%88/feedback
// ==/UserScript==
(function() {
'use strict';
// --- 配置项 ---
const SCORE_THRESHOLD = 3.75; // 评分阈值,只有高于此分数的影片才会被着色
const MAX_RATED_BY = 200; // 用于归一化的最大评价人数,超过此值视为满热度贡献
/**
* 根据综合热度值计算热力图颜色 (蓝 -> 绿 -> 红)。
* @param {number} heat - 一个 0 到 1 之间的值,代表热度。
* @returns {string} CSS rgba 颜色字符串,透明度固定为 50%。
*/
function getHeatmapColor(heat) {
let r = 0, g = 0, b = 0;
const h = Math.min(Math.max(heat, 0), 1);
if (h < 0.5) {
const t = h * 2;
g = Math.round(255 * t);
b = Math.round(255 * (1 - t));
} else {
const t = (h - 0.5) * 2;
r = Math.round(255 * t);
g = Math.round(255 * (1 - t));
}
return `rgba(${r}, ${g}, ${b}, 0.5)`;
}
/**
* 根据影片评分和评价人数计算综合热度值。
* @param {number} score - 影片评分。
* @param {number} ratedBy - 评价人数。
* @returns {number} 一个 0 到 1 之间的热度值。
*/
function calculateHeat(score, ratedBy) {
const scoreNormalized = score / 5;
const scoreTransformed = Math.pow(scoreNormalized, 2);
const ratedByNormalized = Math.min(ratedBy / MAX_RATED_BY, 1);
return scoreTransformed * ratedByNormalized;
}
const scoreRegex = /([\d.]+)分, 由(\d+)人評價/;
/**
* 计算热度并将其存储在元素上,然后应用高亮。
* @param {HTMLElement} item - 影片的 item 元素。
*/
function processAndHighlightItem(item) {
if (item.dataset.highlighted) return;
const scoreElement = item.querySelector('.score .value');
const anchorElement = item.querySelector('a');
item.dataset.heat = '0';
if (scoreElement && anchorElement) {
const scoreMatch = scoreElement.textContent.trim().match(scoreRegex);
if (scoreMatch) {
const score = parseFloat(scoreMatch[1]);
if (score >= SCORE_THRESHOLD) {
const ratedBy = parseInt(scoreMatch[2], 10);
const heat = calculateHeat(score, ratedBy);
item.dataset.heat = heat;
anchorElement.style.backgroundColor = getHeatmapColor(heat);
}
}
}
item.dataset.highlighted = 'true';
}
/**
* 在指定的根元素下查找并处理所有影片项。
* @param {HTMLElement} rootElement - 开始搜索 .item 的根元素。
*/
function processItemsIn(rootElement) {
const lists = rootElement.matches('.movie-list') ? [rootElement] : rootElement.querySelectorAll('.movie-list');
lists.forEach(list => {
list.querySelectorAll('.item').forEach(processAndHighlightItem);
});
}
/**
* 创建一个独立的、浮动的排序按钮。
*/
function createIndependentSortButton() {
if (!document.querySelector('.movie-list') || document.getElementById('sort-by-heat-btn-container')) {
return;
}
const container = document.createElement('div');
container.id = 'sort-by-heat-btn-container';
const sortButton = document.createElement('a');
sortButton.textContent = '排序';
sortButton.className = 'button';
sortButton.addEventListener('click', (event) => {
event.preventDefault();
sortItemsByHeat();
});
container.appendChild(sortButton);
document.body.appendChild(container);
GM_addStyle(`
#sort-by-heat-btn-container {
position: fixed;
bottom: 27.2px;
right: 0px;
z-index: 9998;
}
#sort-by-heat-btn-container .button {
width: 39.3px;
height: 27.2px;
font-size: 0.8rem;
background-color: #fa6699;
color: white;
border: none;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
`);
}
/**
* 对页面上所有 movie-list 中的项目进行统一排序。
*/
function sortItemsByHeat() {
const containers = document.querySelectorAll('.movie-list');
if (containers.length === 0) return;
let allItems = [];
containers.forEach(container => {
allItems = allItems.concat(Array.from(container.querySelectorAll('.item')));
});
allItems.sort((a, b) => {
const heatA = parseFloat(a.dataset.heat || 0);
const heatB = parseFloat(b.dataset.heat || 0);
return heatB - heatA;
});
const primaryContainer = containers[0];
const fragment = document.createDocumentFragment();
allItems.forEach(item => fragment.appendChild(item));
primaryContainer.innerHTML = '';
primaryContainer.appendChild(fragment);
for (let i = 1; i < containers.length; i++) {
containers[i].remove();
}
// 动态获取顶部导航栏高度,以实现精确滚动,增强脚本对网站改版的适应性
const navBar = document.querySelector('.navbar.is-fixed-top');
const offset = navBar ? navBar.getBoundingClientRect().height : 53.45; // 若找不到则使用备用值
const elementTopPosition = primaryContainer.getBoundingClientRect().top + window.pageYOffset;
const targetScrollPosition = elementTopPosition - offset;
window.scrollTo({
top: targetScrollPosition,
behavior: 'smooth'
});
}
/**
* 脚本主函数/入口
*/
function main() {
try {
// 1. 处理页面初次加载时已存在的项目
processItemsIn(document.body);
// 2. 创建排序按钮
createIndependentSortButton();
// 3. 设置观察器,以处理后续动态加载的内容(如自动翻页)
const observer = new MutationObserver((mutationsList) => {
for (const mutation of mutationsList) {
if (mutation.type === 'childList' && mutation.addedNodes.length) {
// 每次有新内容加载时,都尝试创建按钮(如果尚不存在)并处理新项目
createIndependentSortButton();
mutation.addedNodes.forEach(node => {
if (node.nodeType === 1) { // 确保是元素节点
processItemsIn(node);
}
});
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
} catch (error) {
console.error("Javdb 高亮脚本出现错误:", error);
}
}
// --- 脚本执行 ---
// 等待DOM加载完毕后执行主函数,确保所有需要操作的元素都已就绪
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', main);
} else {
main();
}
})();