在 M-Team 页面提取番号并显示封面图,支持尺寸切换
// ==UserScript==
// @name 9kgCoverPeek
// @namespace http://tampermonkey.net/
// @version 0.3
// @description 在 M-Team 页面提取番号并显示封面图,支持尺寸切换
// @author opoa
// @match https://*.m-team.cc/browse/adult*
// @match https://*.m-team.cc/showcaseDetail*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_addStyle
// @icon https://res.cfopoa.top/icon/9k-512.webp
// @license MIT
// ==/UserScript==
(function () {
'use strict';
// 尺寸配置
const SIZE_CONFIG = {
small: { maxWidth: '150px', label: '小'},
medium: { maxWidth: '280px', label: '中'},
large: { maxWidth: '450px', label: '大'},
};
const STORAGE_KEY = 'nkg_cover_size';
const POSITION_KEY = 'nkg_panel_position';
const ICON_URL = 'https://res.cfopoa.top/icon/9k-512.webp';
function getCurrentSize() {
return GM_getValue(STORAGE_KEY, 'medium');
}
function setCurrentSize(size) {
GM_setValue(STORAGE_KEY, size);
}
// 提取番号,支持大小写
function extractAvCode(text) {
const match = text.match(/([A-Za-z]{2,5})-(\d{2,5})/);
return match ? match[0] : null;
}
// 创建封面图元素(含骨架屏容器)
function createCoverImage(avCode) {
const lowerCode = avCode.toLowerCase();
const wrapper = document.createElement('span');
wrapper.className = 'nkg-cover-wrapper';
const img = document.createElement('img');
img.src = `https://fourhoi.com/${lowerCode}/cover-n.jpg`;
img.className = 'nkg-cover-img';
img.loading = 'lazy';
img.alt = avCode;
img.addEventListener('load', () => wrapper.classList.add('nkg-loaded'), { once: true });
img.addEventListener('error', () => {
img.remove();
wrapper.classList.add('nkg-error');
wrapper.textContent = avCode;
}, { once: true });
wrapper.appendChild(img);
return wrapper;
}
// 防重复处理
function processSpan(span) {
if (span.dataset.nkgProcessed) return;
span.dataset.nkgProcessed = 'true';
const avCode = extractAvCode(span.textContent);
if (!avCode) return;
const img = createCoverImage(avCode);
span.parentNode.insertBefore(img, span);
}
// 切换所有封面图尺寸(通过 CSS 变量)
function updateAllImages(size) {
setCurrentSize(size);
document.documentElement.style.setProperty('--nkg-cover-size', SIZE_CONFIG[size].maxWidth);
updateSizeIndicator(size);
}
// 更新浮动按钮的激活状态
function updateSizeIndicator(size) {
const panel = document.getElementById('nkg-size-panel');
if (!panel) return;
panel.querySelectorAll('.nkg-btn').forEach(btn => {
const isActive = btn.dataset.size === size;
btn.classList.toggle('active', isActive);
btn.setAttribute('aria-checked', isActive);
});
}
// 展开/收起时自动调整位置,确保面板不超出视口
function adjustPanelPosition(wrapper) {
const isExpanded = wrapper.classList.contains('expanded');
if (isExpanded) {
wrapper._nkgOrigPos = {
left: wrapper.style.left,
top: wrapper.style.top,
right: wrapper.style.right,
bottom: wrapper.style.bottom
};
requestAnimationFrame(() => {
const rect = wrapper.getBoundingClientRect();
const fullW = wrapper.scrollWidth;
const fullH = wrapper.scrollHeight;
const vw = window.innerWidth;
const vh = window.innerHeight;
const pad = 8;
let left = rect.left;
let top = rect.top;
let adjusted = false;
if (left + fullW > vw - pad) { left = vw - fullW - pad; adjusted = true; }
if (left < pad) { left = pad; adjusted = true; }
if (top + fullH > vh - pad) { top = vh - fullH - pad; adjusted = true; }
if (top < pad) { top = pad; adjusted = true; }
if (adjusted) {
wrapper.classList.add('nkg-adjusting');
wrapper.style.left = left + 'px';
wrapper.style.top = top + 'px';
wrapper.style.right = 'auto';
wrapper.style.bottom = 'auto';
wrapper._nkgAdjusted = true;
}
});
} else if (wrapper._nkgAdjusted && wrapper._nkgOrigPos) {
const pos = wrapper._nkgOrigPos;
wrapper.style.left = pos.left;
wrapper.style.top = pos.top;
wrapper.style.right = pos.right;
wrapper.style.bottom = pos.bottom;
wrapper._nkgAdjusted = false;
setTimeout(() => wrapper.classList.remove('nkg-adjusting'), 380);
}
}
// 使元素可拖动,拖动距离小于阈值视为点击(Pointer Events 支持触屏)
function makeDraggable(wrapper, handle) {
let startX, startY, origX, origY, dragging = false;
handle.addEventListener('pointerdown', e => {
e.preventDefault();
handle.setPointerCapture(e.pointerId);
startX = e.clientX;
startY = e.clientY;
const rect = wrapper.getBoundingClientRect();
origX = rect.left;
origY = rect.top;
dragging = false;
function onMove(e) {
const dx = e.clientX - startX;
const dy = e.clientY - startY;
if (!dragging && Math.abs(dx) + Math.abs(dy) > 4) {
dragging = true;
wrapper.classList.remove('nkg-adjusting');
}
if (dragging) {
wrapper.style.left = origX + dx + 'px';
wrapper.style.top = origY + dy + 'px';
wrapper.style.right = 'auto';
wrapper.style.bottom = 'auto';
}
}
function onUp() {
handle.removeEventListener('pointermove', onMove);
handle.removeEventListener('pointerup', onUp);
if (!dragging) {
wrapper.classList.toggle('expanded');
handle.setAttribute('aria-expanded', wrapper.classList.contains('expanded'));
adjustPanelPosition(wrapper);
} else {
wrapper._nkgAdjusted = false;
GM_setValue(POSITION_KEY, JSON.stringify({
left: wrapper.style.left,
top: wrapper.style.top
}));
}
}
handle.addEventListener('pointermove', onMove);
handle.addEventListener('pointerup', onUp);
});
}
// 创建浮动尺寸切换面板(点击触发按钮展开/收起,可拖动)
function createSizeToggle() {
const wrapper = document.createElement('div');
wrapper.id = 'nkg-size-toggle';
// 头部:图标 + 标题同行
const header = document.createElement('div');
header.className = 'nkg-header';
const trigger = document.createElement('img');
trigger.id = 'nkg-trigger-btn';
trigger.src = ICON_URL;
trigger.alt = '9kgCoverPeek';
trigger.setAttribute('role', 'button');
trigger.setAttribute('aria-label', '切换封面图尺寸面板');
trigger.setAttribute('aria-expanded', 'false');
header.appendChild(trigger);
const title = document.createElement('div');
title.className = 'nkg-panel-title';
title.textContent = 'Cover Peek 👀';
header.appendChild(title);
wrapper.appendChild(header);
const panel = document.createElement('div');
panel.id = 'nkg-size-panel';
panel.setAttribute('role', 'group');
panel.setAttribute('aria-label', '封面尺寸选项');
const currentSize = getCurrentSize();
// 封面尺寸选项行
const row = document.createElement('div');
row.className = 'nkg-option-row';
const label = document.createElement('span');
label.className = 'nkg-option-label';
label.id = 'nkg-size-label';
label.textContent = '封面尺寸:';
row.appendChild(label);
const btns = document.createElement('div');
btns.className = 'nkg-option-btns';
btns.setAttribute('role', 'radiogroup');
btns.setAttribute('aria-labelledby', 'nkg-size-label');
Object.entries(SIZE_CONFIG).forEach(([key, config]) => {
const btn = document.createElement('button');
btn.textContent = config.label;
btn.dataset.size = key;
btn.className = key === currentSize ? 'nkg-btn active' : 'nkg-btn';
btn.setAttribute('role', 'radio');
btn.setAttribute('aria-checked', key === currentSize);
btn.addEventListener('click', () => updateAllImages(key));
btns.appendChild(btn);
});
row.appendChild(btns);
panel.appendChild(row);
wrapper.appendChild(panel);
document.body.appendChild(wrapper);
// 恢复拖拽位置(校验视口边界)
try {
const pos = JSON.parse(GM_getValue(POSITION_KEY, 'null'));
if (pos && pos.left && pos.top) {
const left = parseInt(pos.left, 10);
const top = parseInt(pos.top, 10);
if (left >= 0 && left < window.innerWidth - 20 && top >= 0 && top < window.innerHeight - 20) {
wrapper.style.left = pos.left;
wrapper.style.top = pos.top;
wrapper.style.right = 'auto';
wrapper.style.bottom = 'auto';
}
}
} catch (_) {}
makeDraggable(wrapper, trigger);
}
// 注入样式
function addStyles() {
GM_addStyle(`
/* 封面图容器(骨架屏) */
.nkg-cover-wrapper {
display: inline-block;
vertical-align: middle;
margin-right: 5px;
min-width: 40px;
min-height: 40px;
border-radius: 4px;
background: linear-gradient(90deg, #eee 25%, #ddd 50%, #eee 75%);
background-size: 200% 100%;
animation: nkg-shimmer 1.5s ease infinite;
}
.nkg-cover-wrapper.nkg-loaded {
min-width: unset;
min-height: unset;
background: none;
animation: none;
}
.nkg-cover-wrapper.nkg-error {
display: inline-flex;
align-items: center;
min-width: unset;
min-height: unset;
padding: 2px 6px;
background: #f8f8f8;
border: 1px dashed #ccc;
font-size: 11px;
color: #999;
animation: none;
}
@keyframes nkg-shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
/* 封面图 */
.nkg-cover-img {
max-width: var(--nkg-cover-size, 280px);
vertical-align: middle;
border-radius: 4px;
opacity: 0;
transition: max-width 0.3s ease, opacity 0.3s ease;
}
.nkg-loaded .nkg-cover-img {
opacity: 1;
}
/* 浮动面板 */
#nkg-size-toggle {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 9999;
padding: 6px;
border-radius: 16px;
background: transparent;
box-shadow: none;
overflow: hidden;
max-width: 40px;
max-height: 40px;
transition: max-width 0.35s cubic-bezier(0.25, 0.8, 0.25, 1),
max-height 0.35s cubic-bezier(0.25, 0.8, 0.25, 1),
background 0.3s ease,
box-shadow 0.3s ease,
border-radius 0.3s ease;
}
#nkg-size-toggle.expanded {
max-width: 400px;
max-height: 200px;
background: #fff;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.12);
}
#nkg-size-toggle.nkg-adjusting {
transition: max-width 0.35s cubic-bezier(0.25, 0.8, 0.25, 1),
max-height 0.35s cubic-bezier(0.25, 0.8, 0.25, 1),
background 0.3s ease,
box-shadow 0.3s ease,
border-radius 0.3s ease,
left 0.35s cubic-bezier(0.25, 0.8, 0.25, 1),
top 0.35s cubic-bezier(0.25, 0.8, 0.25, 1);
}
#nkg-trigger-btn {
width: 28px;
height: 28px;
min-width: 28px;
border-radius: 50%;
cursor: grab;
transition: transform 0.2s;
user-select: none;
-webkit-user-drag: none;
}
#nkg-trigger-btn:hover {
transform: scale(1.1);
}
.nkg-header {
display: flex;
align-items: center;
gap: 8px;
white-space: nowrap;
}
#nkg-size-panel {
margin-top: 6px;
padding: 4px 8px 2px;
opacity: 0;
transition: opacity 0.3s ease;
}
#nkg-size-toggle.expanded #nkg-size-panel {
opacity: 1;
}
.nkg-panel-title {
font-size: 16px;
font-weight: 700;
color: #222;
white-space: nowrap;
flex: 1;
word-spacing: 0.4em;
opacity: 0;
transition: opacity 0.3s ease;
}
#nkg-size-toggle.expanded .nkg-panel-title {
opacity: 1;
}
.nkg-option-row {
display: flex;
align-items: center;
gap: 6px;
white-space: nowrap;
}
.nkg-option-label {
font-size: 13px;
color: #333;
font-weight: 500;
}
.nkg-option-btns {
display: flex;
gap: 4px;
}
.nkg-btn {
border: 1px solid #e0e0e0;
padding: 4px 14px;
border-radius: 10px;
cursor: pointer;
background: #fafafa;
color: #555;
font-size: 13px;
transition: color 0.2s, background 0.2s, border-color 0.2s, transform 0.15s;
}
.nkg-btn:hover {
color: #333;
background: #f0f0f0;
border-color: #ccc;
}
.nkg-btn:active {
transform: scale(0.92);
}
.nkg-btn.active {
background: #4a6cf7;
color: #fff;
border-color: #4a6cf7;
}
`);
}
// 初始化
function init() {
addStyles();
document.documentElement.style.setProperty('--nkg-cover-size', SIZE_CONFIG[getCurrentSize()].maxWidth);
createSizeToggle();
document.querySelectorAll('span[aria-describedby]').forEach(processSpan);
new MutationObserver(mutations => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.matches('span[aria-describedby]')) processSpan(node);
node.querySelectorAll('span[aria-describedby]').forEach(processSpan);
}
}
}
}).observe(document.body, { childList: true, subtree: true });
}
if (document.readyState === 'complete') init();
else window.addEventListener('load', init);
})();