// ==UserScript==
// @name Yande.re 大图预览 显示艺术家 打开原图
// @version 1.2
// @description Yande.re浏览增强:悬停大图预览,显示艺术家名称,点击跳转主页,右键名称标记艺术家,高亮显示不同颜色,支持双击/键盘翻页,自动显示隐藏图片,直接打开原图
// @description 原脚本已数年未更新,在原作者Joker(Streams)的基础上修改; konachan有限支持(需自行添加@match),部分功能可能无法使用
// @author Cylirix
// @author Joker
// @author Streams
// @match https://yande.re/*
// @icon https://yande.re/favicon.ico
// @grant GM_getValue
// @grant GM_setValue
// @namespace https://greasyfork.org/users/467741
// ==/UserScript==
jQuery.noConflict();
jQuery(function ($) {
// 全局唯一标识系统
var currentUUID = null;
var requestController = null;
var hoverTimer = null;
var currentHoverItem = null;
// 初始化艺术家状态(使用Tampermonkey存储系统)
var artistStates = {};
// 从Tampermonkey存储加载数据
try {
const savedStates = GM_getValue("artistStates", "{}");
artistStates = JSON.parse(savedStates);
} catch (e) {
console.error("Error loading artist states:", e);
}
// 右键菜单容器
const $contextMenu = $('<div id="artist-context-menu">').css({
position: 'fixed',
display: 'none',
background: 'rgba(0,0,0,0.7)',
borderRadius: '6px',
zIndex: 10000,
padding: '5px 5px'
}).appendTo('body');
// 状态配置 - 使用字母a、b、c作为ID
const stateConfig = [
{id: 'a', text: '已收藏', color: '#ffff00'},
{id: 'b', text: '已下载', color: '#00ff11'},
{id: 'c', text: 'ignore', color: '#999'},
{id: 'clear', text: '清除', color: '#fff'}
];
// 创建菜单项
stateConfig.forEach(option => {
$('<div class="menu-item">')
.text(option.text)
.css({
padding: '5px 15px',
cursor: 'pointer',
color: option.color,
fontSize: '12px'
})
.data('action', option.id)
.hover(
function() { $(this).css('background', '#444'); },
function() { $(this).css('background', 'transparent'); }
)
.appendTo($contextMenu);
});
// 预览框容器
var $zoombox = $('<div id="zoombox">').css({
position: 'fixed',
top: 0,
left: 0,
'z-index': 9999,
'pointer-events': 'none',
display: 'none',
'overflow': 'hidden',
'transition': 'width 0.3s, height 0.3s'
}).appendTo('body');
// 添加全局样式
function addGlobalStyle(css) {
$('<style></style>').html(css).appendTo('head');
}
// 动态生成状态样式
let stateStyles = '';
stateConfig.forEach(state => {
if (state.id !== 'clear') {
stateStyles += `
.artist-label.status-${state.id} {
color: ${state.color} !important;
}
`;
// 特殊状态样式
if (state.id === 'c') {
stateStyles += `
.artist-label.status-c {
text-decoration: line-through;
}
`;
}
}
});
addGlobalStyle(`
/* 主预览框样式 */
#zoombox {
max-width: 100%;
max-height: 100%;
border-radius: 4px;
}
/* 预览图内部样式 */
#zoombox img {
display: block;
pointer-events: none;
object-fit: contain;
transition: opacity 0.3s;
border-radius: 4px;
}
/* 图片容器 */
.inner {
position: relative;
}
/* 控制容器 - 右上角 */
.control-box {
position: absolute;
top: 0px;
right: 0px;
z-index: 1000;
pointer-events: none;
text-align: right;
max-width: 80%; /* 限制最大宽度 */
font-size: 11px; /* 统一字体大小 */
line-height: 1.2;
display: flex; /* 使用flex布局 */
flex-direction: column; /* 垂直排列元素 */
align-items: flex-end; /* 内容右对齐 */
}
/* 艺术家标签 - 多行显示 */
.artist-label {
background: rgba(0,0,0,0.3);
color: white; /* 默认文字颜色 */
padding: 2px 5px;
border-radius: 3px;
font-weight: bold;
white-space: normal; /* 允许多行显示 */
word-break: break-word; /* 长单词换行 */
pointer-events: auto;
margin-bottom: 2px; /* 与下方按钮间距 */
max-width: 100%; /* 宽度限制 */
text-align: left; /* 内部文本左对齐 */
cursor: context-menu; /* 显示右键菜单光标 */
}
/* unknown状态特殊样式 */
.artist-label.unknown {
color: #aaa !important; /* 更灰暗的颜色 */
font-style: italic; /* 添加斜体效果 */
}
/* 艺术家状态样式 */
${stateStyles}
/* 艺术家链接样式 */
.artist-label a {
color: inherit; /* 继承父元素颜色 */
text-decoration: none;
}
.artist-label a:hover {
text-decoration: underline;
}
/* 原图按钮 - 背景大小优化 */
.original-btn {
background: rgba(0,0,0,0.35);
color: white;
padding: 2px 5px;
border-radius: 3px;
text-decoration: none;
font-weight: bold;
pointer-events: auto;
white-space: nowrap; /* 禁止文字换行 */
display: none; /* 默认隐藏 */
}
/* 悬停时显示原图按钮 */
.inner:hover .original-btn {
display: block; /* 显示为块级元素,独占一行 */
}
/* 原图按钮悬停效果 */
.original-btn:hover {
background: rgba(200, 80, 80, 0.8);
}
/* 右键菜单样式 */
#artist-context-menu {
font-family: Arial, sans-serif;
}
.menu-item {
transition: background 0.5s;
}
/* === 默认显示分辨率 === */
#post-list-posts li a.directlink span.directlink-info {
display: none !important;
}
#post-list-posts li a.directlink span.directlink-res {
display: inline !important;
}
`);
// 翻页功能
function addKey() {
$(document).dblclick(function (e) {
var w = document.documentElement.offsetWidth || document.body.offsetWidth;
if (e.clientX > w / 2) nextPage();
else previousPage();
});
$(document).keydown(function (e) {
if (e.keyCode == 37) previousPage();
else if (e.keyCode == 39) nextPage();
});
function nextPage() {
var $nextBtn = $('a.next_page');
if ($nextBtn.length > 0) $nextBtn[0].click();
}
function previousPage() {
var $preBtn = $('a.previous_page');
if ($preBtn.length > 0) $preBtn[0].click();
}
}
// 生成唯一标识符
function generateUUID() {
return Date.now().toString(36) + Math.random().toString(36).substr(2);
}
function setupContextMenu() {
// 右键点击事件
$(document).on('contextmenu', '.artist-label', function(e) {
e.preventDefault();
const artistName = $(this).text().trim();
if (!artistName || artistName === 'unknown') return;
// === 直接使用视口坐标 ===
const x = e.clientX;
const y = e.clientY;
// 显示菜单
$contextMenu
.data('artist', artistName)
.css({
display: 'block',
left: x + 'px',
top: y + 'px'
});
// === 添加边界检查 ===
setTimeout(() => {
const menuRect = $contextMenu[0].getBoundingClientRect();
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
let adjustedX = x;
let adjustedY = y;
// 检查右边界
if (menuRect.right > windowWidth) {
adjustedX = windowWidth - menuRect.width - 10;
}
// 检查下边界
if (menuRect.bottom > windowHeight) {
adjustedY = windowHeight - menuRect.height - 10;
}
// 如果需要调整位置
if (adjustedX !== x || adjustedY !== y) {
$contextMenu.css({
left: adjustedX + 'px',
top: adjustedY + 'px'
});
}
}, 0);
});
// 菜单项点击事件
$contextMenu.on('click', '.menu-item', function() {
const action = $(this).data('action');
const artistName = $contextMenu.data('artist');
if (action === 'clear') {
// 清除标记
delete artistStates[artistName];
} else {
// 设置状态
artistStates[artistName] = action;
}
// 保存到Tampermonkey存储
GM_setValue("artistStates", JSON.stringify(artistStates));
// 更新所有该艺术家的标签
$(`.artist-label:contains('${artistName}')`).each(function() {
const $label = $(this);
// 清除所有状态类
stateConfig.forEach(state => {
if (state.id !== 'clear') {
$label.removeClass(`status-${state.id}`);
}
});
// 添加新状态类
if (action !== 'clear' && artistStates[artistName]) {
$label.addClass(`status-${artistStates[artistName]}`);
}
});
// 隐藏菜单
$contextMenu.hide();
});
// 点击其他地方关闭菜单
$(document).on('click', function(e) {
if (!$(e.target).closest('#artist-context-menu').length) {
$contextMenu.hide();
}
});
}
// 优化的预览逻辑
function addMouseZoomPreview() {
$("#post-list-posts > li, .pool-show .inner").each(function() {
const $item = $(this);
const $inner = $item.find('.inner');
if ($inner.length === 0) return;
let postId = $item.data('id');
if (!postId) {
// If not found, try to extract from thumb link
const $thumbLink = $inner.find('a.thumb');
if ($thumbLink.length) {
const href = $thumbLink.attr('href');
const postIdMatch = href && href.match(/\/post\/show\/(\d+)/);
if (postIdMatch && postIdMatch[1]) {
postId = postIdMatch[1];
}
}
}
// 创建控制容器(艺术家标签和原图按钮的父元素)
const $controlBox = $('<div class="control-box"></div>');
$inner.append($controlBox);
// 创建原图按钮
let originalUrl = null;
let sampleUrl = null;
const $thumbLink = $inner.find('a.thumb');
// === 获取当前域名 ===
const currentDomain = window.location.hostname;
const isKonachan = currentDomain.includes('konachan');
const isYandere = currentDomain.includes('yande.re');
// 方法1:从pool页面结构获取URL
if ($thumbLink.length) {
const href = $thumbLink.attr('href');
const postIdMatch = href && href.match(/\/post\/show\/(\d+)/);
if (postIdMatch && postIdMatch[1]) {
const postId = postIdMatch[1];
const thumbSrc = $thumbLink.find('img').attr('src');
if (thumbSrc) {
const md5 = thumbSrc.split('/').pop().split('.')[0];
if (md5) {
// === 根据域名动态生成URL ===
if (isYandere) {
originalUrl = `https://files.yande.re/image/${md5}/yande.re%20${postId}.jpg`;
sampleUrl = `https://files.yande.re/sample/${md5}/yande.re%20${postId}%20sample.jpg`;
} else if (isKonachan) {
originalUrl = `https://${currentDomain}/image/${md5}.jpg`;
sampleUrl = `https://${currentDomain}/sample/${md5}.jpg`;
}
}
}
}
}
// 方法2:从普通页面结构获取URL(仅当方法1失败时尝试)
if (!originalUrl || !sampleUrl) {
const $directLink = $item.find('a[class*="directlink"][href]');
if ($directLink.length) {
const url = $directLink.attr('href');
if (url) {
originalUrl = url;
// === 根据域名动态生成sample URL ===
if (isYandere) {
if (url.includes('/jpeg/')) {
originalUrl = url.replace('/jpeg/', '/image/');
}
if (url.includes('/image/')) {
sampleUrl = url.replace('/image/', '/sample/');
} else if (url.includes('/jpeg/')) {
sampleUrl = url.replace('/jpeg/', '/sample/');
} else {
sampleUrl = url;
}
} else if (isKonachan) {
// Konachan的URL格式不同
if (url.includes('/image/')) {
sampleUrl = url.replace('/image/', '/sample/');
} else {
// 尝试从URL中提取md5
const md5Match = url.match(/\/([a-f0-9]{32})\.jpg$/);
if (md5Match && md5Match[1]) {
sampleUrl = `https://${currentDomain}/sample/${md5Match[1]}.jpg`;
}
}
}
}
}
}
// 如果无法获取URL则跳过
if (!originalUrl || !sampleUrl) return;
// 创建并添加原图按钮 - 默认隐藏
const $originalBtn = $(`<a class="original-btn" href="${originalUrl}" target="_blank">打开原图</a>`);
$controlBox.append($originalBtn);
// 获取艺术家信息
if (postId) {
$.get(`/post.json?api_version=2&include_tags=1&tags=id:${postId}`, function(data) {
if (data.posts && data.posts.length > 0) {
const post = data.posts[0];
const tagTypes = data.tags || {};
const tags = post.tags.split(' ');
const artists = tags.filter(tag => tagTypes[tag] === 'artist');
let artistText = artists.length > 0 ? artists.join(', ') : 'unknown';
// 创建艺术家标签
const $artistLabel = $('<div class="artist-label"></div>');
// 检查是否为unknown状态
const isUnknown = artistText === 'unknown';
if (isUnknown) {
$artistLabel.addClass('unknown');
}
// 处理艺术家标签(单艺术家可点击,多艺术家只显示)
if (artists.length === 1) {
const artistName = artists[0];
// 创建艺术家链接
$artistLabel.append(
$(`<a href="/post?tags=${encodeURIComponent(artistName)}" target="_blank"></a>`)
.text(artistName)
);
// === 使用TM存储的状态 ===
if (artistStates[artistName]) {
$artistLabel.addClass(`status-${artistStates[artistName]}`);
}
} else {
$artistLabel.text(artistText);
}
// 添加到控制容器顶部
$controlBox.prepend($artistLabel);
}
});
}
// 获取缩略图元素
const $thumbImg = $item.find('img.preview');
if ($thumbImg.length === 0) return;
// 获取预设的图片宽高比
const thumbWidth = $thumbImg.width();
const thumbHeight = $thumbImg.height();
const aspectRatio = thumbWidth / thumbHeight;
// 悬停事件
$item.hover(
// 鼠标进入
function (e) {
// 保存当前悬停的元素
currentHoverItem = this;
// 清除之前的延时器
if (hoverTimer) {
clearTimeout(hoverTimer);
hoverTimer = null;
}
// 设置延时器(300毫秒)
hoverTimer = setTimeout(() => {
// 检查是否仍在同一个元素上
if (currentHoverItem !== this) return;
// 终止可能存在的旧请求
if (requestController) {
requestController.abort();
}
// 生成新UUID作为当前悬停标识
const thisUUID = generateUUID();
currentUUID = thisUUID;
requestController = new AbortController();
// 获取屏幕尺寸
const screenWidth = $(window).width();
const screenHeight = $(window).height();
// 判断横竖图
const isVertical = aspectRatio <= 1;
// 根据宽高比设置初始尺寸
let initWidth, initHeight;
if (isVertical) {
// 竖图:高度占满屏幕高度(无上下边距)
initHeight = screenHeight;
initWidth = initHeight * aspectRatio;
} else {
// 横图:宽度占屏幕宽度50%
initWidth = Math.min(screenWidth * 0.5, screenHeight * aspectRatio);
initHeight = initWidth / aspectRatio;
}
// 避开鼠标位置的逻辑
const mouseX = e.clientX;
let positionLeft = 'auto';
let positionRight = '10px';
if (mouseX < screenWidth / 2) {
// 鼠标在左侧,显示在右侧
positionRight = '10px';
positionLeft = 'auto';
} else {
// 鼠标在右侧,显示在左侧
positionLeft = '10px';
positionRight = 'auto';
}
// 应用位置
$zoombox.css({
top: '5px',
left: positionLeft,
right: positionRight,
width: initWidth + 'px',
height: initHeight + 'px'
});
// 创建加载容器
const $imgContainer = $('<div>').css({
position: 'relative',
overflow: 'hidden',
width: '100%',
height: '100%'
});
const $img = $('<img>').css({
opacity: 1,
width: '100%',
height: '100%'
});
$imgContainer.append($img);
$zoombox.empty().append($imgContainer).show();
// 绑定加载信号
requestController.signal.addEventListener('abort', () => {
if (currentUUID !== thisUUID) return;
$zoombox.hide().empty();
});
// 加载完成的回调
$img.on('load', function() {
if (currentUUID !== thisUUID) return;
// 获取实际图片信息
const naturalWidth = this.naturalWidth;
const naturalHeight = this.naturalHeight;
const actualAspectRatio = naturalWidth / naturalHeight;
// 判断实际图片横竖图
const isActualVertical = actualAspectRatio <= 1;
// 最终尺寸调整
if (isActualVertical) {
// 竖图:高度占满屏幕高度(无上下边距)
const finalHeight = screenHeight;
const finalWidth = finalHeight * actualAspectRatio;
$zoombox.css({
width: finalWidth + 'px',
height: finalHeight + 'px'
});
} else {
// 横图:宽度占屏幕宽度50%
const finalWidth = screenWidth * 0.5;
const finalHeight = finalWidth / actualAspectRatio;
$zoombox.css({
width: finalWidth + 'px',
height: finalHeight + 'px'
});
}
});
$img.on('error', function() {
if (currentUUID !== thisUUID) return;
$img.attr('src', originalUrl);
});
// 开始加载图片
$img.attr('src', sampleUrl);
}, 300); // 300毫秒延时
},
// 鼠标移出
function () {
// 清除延时器
if (hoverTimer) {
clearTimeout(hoverTimer);
hoverTimer = null;
}
// 重置当前悬停元素
currentHoverItem = null;
// 终止加载请求
if (requestController) {
requestController.abort();
requestController = null;
}
$zoombox.hide().empty();
}
);
});
}
// 显示隐藏图片
function showHiddenImage() {
$("#post-list-posts > li.javascript-hide").removeClass("javascript-hide");
}
// 独立函数:禁止图片气泡消息(移除title属性)
function disableImageTooltips() {
// 针对列表页的图片缩略图移除title属性
$("#post-list-posts img.preview[title]").removeAttr("title");
// 如果有其他可能的图片元素,可以在这里扩展选择器
}
// 初始化操作
$(document).ready(function() {
setTimeout(function() {
showHiddenImage();
addKey();
addMouseZoomPreview();
setupContextMenu(); // 初始化右键菜单功能
disableImageTooltips(); // 禁止气泡消息
}, 500);
});
});