Files
Tampermonkey_scripts/Chaturbate/chaturbate-thumbnails-2x.user.js
2026-01-25 08:07:58 +08:00

234 lines
7.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ==UserScript==
// @name Chaturbate 缩略图放大 2 倍
// @namespace https://chaturbate.com/
// @version 0.1.1
// @description 将房间列表缩略图按宽高 2 倍放大
// @match https://chaturbate.com/*
// @match https://*.chaturbate.com/*
// @run-at document-start
// @grant none
// ==/UserScript==
(function () {
"use strict";
const SCALE = 2;
const SCALED_ATTR = "data-tm-scaled";
const BASE_W_ATTR = "data-tm-base-w";
const BASE_H_ATTR = "data-tm-base-h";
const DEBUG = true;
// 提前注入 CSS避免闪烁
const injectCSS = () => {
const style = document.createElement("style");
style.id = "tm-thumb-scale-style";
style.textContent = `
img.room_thumbnail:not([${SCALED_ATTR}="1"]) {
opacity: 0;
transition: opacity 0.1s;
}
img.room_thumbnail[${SCALED_ATTR}="1"] {
opacity: 1;
}
ul.list,
ul.list.endless_page_template {
display: flex !important;
flex-wrap: wrap !important;
gap: 0 !important;
}
ul.list li.roomCard,
ul.list.endless_page_template li.roomCard {
position: relative !important;
flex-shrink: 0 !important;
flex-grow: 0 !important;
margin: 0 !important;
padding: 0 !important;
}
`;
if (document.head) {
if (!document.getElementById("tm-thumb-scale-style")) {
document.head.appendChild(style);
}
} else {
document.addEventListener("DOMContentLoaded", () => {
if (!document.getElementById("tm-thumb-scale-style")) {
document.head.appendChild(style);
}
});
}
};
injectCSS();
const relaxBox = (el) => {
if (!el) return;
el.style.overflow = "visible";
el.style.maxWidth = "none";
};
const relaxAncestors = (el, depth = 6) => {
let current = el?.parentElement;
let steps = 0;
while (current && steps < depth && current !== document.body) {
relaxBox(current);
current = current.parentElement;
steps += 1;
}
};
const relaxKnownOuter = () => {
const selectors = [
"#roomlist_content_wrapper",
"#roomlist_root",
".BaseRoomContents",
"[data-testid='room-list']",
"ul.list",
"ul.list.endless_page_template",
];
selectors.forEach((selector) => {
document.querySelectorAll(selector).forEach((el) => {
relaxBox(el);
// 如果是列表容器,确保布局能容纳更大的卡片
if (el.matches?.("ul.list, ul.list.endless_page_template")) {
el.style.width = "100%";
el.style.maxWidth = "none";
el.style.boxSizing = "border-box";
el.style.display = "flex";
el.style.flexWrap = "wrap";
el.style.gap = "0";
}
});
});
};
const scale = () => {
if (DEBUG) console.log("[tm] running on", location.href);
relaxKnownOuter();
const items = document.querySelectorAll("a.room_thumbnail_container");
if (DEBUG) console.log("[tm] containers:", items.length);
items.forEach((container) => {
const img = container.querySelector("img.room_thumbnail");
if (DEBUG && !img) console.log("[tm] no img in container", container);
if (!img) return;
const attrW = parseFloat(img.getAttribute("width"));
const attrH = parseFloat(img.getAttribute("height"));
const baseW = parseFloat(img.getAttribute(BASE_W_ATTR)) || attrW;
const baseH = parseFloat(img.getAttribute(BASE_H_ATTR)) || attrH;
if (!baseW || !baseH) return;
if (!img.getAttribute(BASE_W_ATTR) || !img.getAttribute(BASE_H_ATTR)) {
img.setAttribute(BASE_W_ATTR, String(baseW));
img.setAttribute(BASE_H_ATTR, String(baseH));
}
const scaledW = baseW * SCALE;
const scaledH = baseH * SCALE;
if (DEBUG) console.log("[tm] img size:", baseW, baseH, img);
img.setAttribute("width", String(scaledW));
img.setAttribute("height", String(scaledH));
img.setAttribute(SCALED_ATTR, "1");
img.style.width = `${scaledW}px`;
img.style.height = `${scaledH}px`;
img.style.maxWidth = "none";
img.style.maxHeight = "none";
img.style.display = "block";
img.style.opacity = "1";
container.style.width = `${scaledW}px`;
container.style.height = `${scaledH}px`;
container.style.minWidth = `${scaledW}px`;
container.style.maxWidth = `${scaledW}px`;
container.style.minHeight = `${scaledH}px`;
container.style.maxHeight = "none";
container.style.overflow = "visible";
container.style.display = "block";
container.style.position = "relative";
container.style.boxSizing = "border-box";
container.style.flexShrink = "0";
container.style.margin = "0";
const card = container.closest("li.roomCard");
if (card) {
card.style.width = `${scaledW}px`;
card.style.minWidth = `${scaledW}px`;
card.style.maxWidth = `${scaledW}px`;
card.style.minHeight = `${scaledH}px`;
card.style.height = "auto";
card.style.overflow = "visible";
card.style.position = "relative";
card.style.flexShrink = "0";
card.style.flexGrow = "0";
card.style.flexBasis = `${scaledW}px`;
card.style.boxSizing = "border-box";
card.style.margin = "0";
card.style.padding = "0";
relaxAncestors(card);
}
relaxAncestors(container);
});
};
const init = () => {
if (DEBUG) {
const thumbs = document.querySelectorAll("img.room_thumbnail");
console.log("[tm] thumbnails:", thumbs.length, Array.from(thumbs));
}
scale();
};
// 立即执行(如果 DOM 已就绪)
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
// 延迟重试,处理动态加载
window.setTimeout(scale, 100);
window.setTimeout(scale, 300);
window.setTimeout(scale, 600);
window.setTimeout(scale, 1000);
window.setTimeout(scale, 2000);
// MutationObserver 监听动态添加的内容
const observer = new MutationObserver((mutations) => {
let shouldScale = false;
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === 1) {
if (
node.matches?.("a.room_thumbnail_container") ||
node.matches?.("li.roomCard") ||
node.querySelector?.("a.room_thumbnail_container")
) {
shouldScale = true;
}
}
});
});
if (shouldScale) {
if (DEBUG) console.log("[tm] new content detected, scaling...");
scale();
}
});
// 开始观察
const startObserving = () => {
const target = document.body || document.documentElement;
if (target) {
observer.observe(target, {
childList: true,
subtree: true,
});
if (DEBUG) console.log("[tm] MutationObserver started");
}
};
if (document.body) {
startObserving();
} else {
document.addEventListener("DOMContentLoaded", startObserving);
}
})();