On-hover Steam thumbnail & description for 1337x torrent titles (cleans up names for better matches)
当前为
// ==UserScript==
// @name 1337x – Steam Hover Preview
// @namespace https://greasyfork.org/users/youruserid
// @version 1.1
// @description On-hover Steam thumbnail & description for 1337x torrent titles (cleans up names for better matches)
// @author DeonHolo
// @license MIT
// @match *://1337x.to/*
// @match *://1337x.st/*
// @match *://1337x.ws/*
// @match *://1337x.eu/*
// @match *://1337x.se/*
// @match *://1337x.is/*
// @match *://1337x.gd/*
// @grant GM_xmlhttpRequest
// @connect store.steampowered.com
// @run-at document-idle
// ==/UserScript==
;(function(){
'use strict';
// create tooltip
const tip = document.createElement('div');
Object.assign(tip.style, {
position : 'fixed',
padding : '8px',
background : 'rgba(255,255,255,0.95)',
border : '1px solid #444',
borderRadius : '4px',
boxShadow : '0 2px 6px rgba(0,0,0,0.2)',
zIndex : 2147483647,
maxWidth : '300px',
fontSize : '12px',
lineHeight : '1.3',
display : 'none',
pointerEvents : 'none'
});
document.body.appendChild(tip);
let hoverCounter = 0;
const cache = new Map();
function gmFetch(url){
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method : 'GET',
url,
responseType : 'json',
onload : r => resolve(r.response),
onerror : e => reject(e)
});
});
}
async function fetchSteam(name){
if (cache.has(name)) return cache.get(name);
let search;
try {
search = await gmFetch(
`https://store.steampowered.com/api/storesearch/?cc=us&l=en&term=${encodeURIComponent(name)}`
);
} catch {
return null;
}
const id = search.items?.[0]?.id;
if (!id) return null;
let details;
try {
details = await gmFetch(
`https://store.steampowered.com/api/appdetails?appids=${id}&cc=us&l=en`
);
} catch {
return null;
}
const data = details[id]?.data;
cache.set(name, data);
return data;
}
function cleanName(raw){
let name = raw.trim();
// split at dash, slash, bracket, 'Update' or 'Edition'
name = name.split(/(?:[-\/\(\[]|Update|Edition)/i)[0].trim();
// strip off version suffix
name = name.replace(/ v[\d.].*$/i, '').trim();
return name;
}
function positionTip(e){
let x = e.clientX + 12;
let y = e.clientY + 12;
const w = tip.offsetWidth, h = tip.offsetHeight;
if (x + w > window.innerWidth) x = window.innerWidth - w - 8;
if (y + h > window.innerHeight) y = window.innerHeight - h - 8;
tip.style.left = x + 'px';
tip.style.top = y + 'px';
}
async function showTip(e){
const thisHover = ++hoverCounter;
const raw = e.target.textContent;
const name = cleanName(raw);
tip.innerHTML = `<p>Loading <strong>${name}</strong>…</p>`;
tip.style.display = 'block';
positionTip(e);
// smaller debounce for snappier feel
await new Promise(r => setTimeout(r, 100));
if (thisHover !== hoverCounter) return;
const data = await fetchSteam(name);
if (thisHover !== hoverCounter) return;
if (!data) {
tip.innerHTML = `<p>No Steam info for<br><strong>${name}</strong>.</p>`;
return;
}
tip.innerHTML = `
<img src="${data.header_image}" style="width:100%;margin-bottom:6px">
<p>${data.short_description}</p>
`;
}
function hideTip(){
hoverCounter++;
tip.style.display = 'none';
}
const SEL = 'td.coll-1 a[href^="/torrent/"]';
document.addEventListener('mouseover', e => {
if (e.target.matches(SEL)) showTip(e);
});
document.addEventListener('mousemove', e => {
if (e.target.matches(SEL) && tip.style.display === 'block') positionTip(e);
});
document.addEventListener('mouseout', e => {
if (e.target.matches(SEL)) hideTip(e);
});
})();