// ==UserScript==
// @name IMDB info + .torrent from magnet (fixed 1337x + GM API)
// @version 4.1
// @description Show IMDB info on torrent sites and add .torrent download links for magnets
// @copyright 2025, quantavil (https://openuserjs.org/users/quantavil)
// @license MIT
// @namespace hossam6236-fixed
// @run-at document-idl
// Use @match (recommended) or @include per preference
// @match http*://*torrent*.*/*
// @match http*://*pirate*bay*.*/*
// @match http*://*tpb*.*/*
// @match http*://*isohunt*.*/*
// @match http*://*1337x*.*/*
// @match http*://*rarbg*.*/*
// @match http*://*zooqle*.*/*
// @match http*://*torlock*.*/*
// @match http*://*eztv*.*/*
// @match http*://*toorgle*.*/*
// @match http*://*demonoid*.*/*
// @match http*://*kickass*.*/*
// @match http*://*kat*.*/*
// @match http*://*.imdb.*/*
// Grants for cross-origin requests via GM APIs
// @grant GM_xmlhttpRequest
// @grant GM.xmlHttpRequest
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// Domains allowed for GM requests (Tampermonkey uses @connect)
// @connect omdbapi.com
// @connect m.media-amazon.com
// @connect ia.media-imdb.com
// @connect itorrents.org
// @connect torrage.info
// @connect btcache.me
// ==/UserScript==
(() => {
// ========== CONFIG ==========
// IMPORTANT: Replace with a valid personal OMDb API key.
// Get a key and use it via &apikey=KEY with t= and optional y= as per OMDb docs.
const OMDB_API_KEY = "YOUR_API_KEY";
const POSTER_PLACEHOLDER =
"https://ia.media-imdb.com/images/G/01/imdb/images/nopicture/large/film-184890147._CB379391879_.png";
// Prefer a resilient reference to the GM request function across TM variants
const gmXhr = (typeof GM_xmlhttpRequest === "function")
? GM_xmlhttpRequest
: (typeof GM !== "undefined" && typeof GM.xmlHttpRequest === "function" ? GM.xmlHttpRequest : null);
if (!gmXhr) {
console.error("[IMDB info] No GM request API available. Check @grant for GM_xmlhttpRequest or GM.xmlHttpRequest.");
return;
}
const CACHE_PREFIX = "imdbinfo-cache-v1:";
const CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
// Cross-platform storage wrappers (GM.* / GM_* / localStorage)
const gmGet = async (key, defVal = null) => {
try {
if (typeof GM !== "undefined" && typeof GM.getValue === "function") return await GM.getValue(key, defVal);
if (typeof GM_getValue === "function") return GM_getValue(key, defVal);
const raw = localStorage.getItem(key);
return raw == null ? defVal : raw;
} catch {
return defVal;
}
};
const gmSet = async (key, value) => {
try {
if (typeof GM !== "undefined" && typeof GM.setValue === "function") return await GM.setValue(key, value);
if (typeof GM_setValue === "function") return GM_setValue(key, value);
localStorage.setItem(key, value);
} catch {}
};
const gmDel = async (key) => {
try {
if (typeof GM !== "undefined" && typeof GM.deleteValue === "function") return await GM.deleteValue(key);
if (typeof GM_deleteValue === "function") return GM_deleteValue(key);
localStorage.removeItem(key);
} catch {}
};
const norm = (s) => (s || "").toString().trim().toLowerCase();
const cacheKeyFor = (title, year) => `${CACHE_PREFIX}${norm(title)}|${norm(year || "")}`;
async function getCachedMovie(title, year) {
const key = cacheKeyFor(title, year);
const raw = await gmGet(key, null);
if (!raw) return null;
let payload = null;
try {
payload = typeof raw === "string" ? JSON.parse(raw) : raw;
} catch {
return null;
}
if (!payload || !payload.data || !payload.ts) return null;
if (Date.now() - payload.ts > CACHE_TTL_MS) {
await gmDel(key); // expire
return null;
}
return payload.data;
}
async function setCachedMovie(title, year, data) {
// Only cache valid responses
if (!data || data.Response !== "True") return;
const key = cacheKeyFor(title, year);
const payload = JSON.stringify({ ts: Date.now(), data });
await gmSet(key, payload);
}
const STYLE = `
.imdb-download-link::before { content: '⇩'; }
.title_wrapper .imdb-download-link { font-size: .5em; }
a.movie-preview { display: inline-block !important; cursor: pointer; }
.movie-preview-starter {
display: inline-block; position: fixed; opacity: 0.85; top: 0; right: 0; z-index: 10000; text-align: center;
}
.movie-preview-starter--button {
display: inline-block; cursor: pointer; margin: 7px; padding: 7px; font-size: 12pt; font-family: Tahoma, Arial; border-radius: 5px;
}
.movie-preview-box {
position: fixed; z-index:9999; width:475px; height:283px; top: calc(50vh - 150px); left: 50vw;
display: flex; color: #000; background-color: white; border: 3px solid #222; border-radius: 5px; overflow: hidden;
opacity: 0; visibility: hidden; transition: all 0.5s ease-in-out;
}
.movie-preview-box.visible { opacity: 1; visibility: visible; }
.movie-preview-box *, .movie-preview-unique-list > * { font-size: 10pt; font-family: Tahoma, Arial; line-height: initial; }
.movie-preview-box.no-trailer .preview--info--trailer { display: none; }
.torrent-download-links { opacity: 0.8; font-size: 90%; position: absolute; display: none; }
.assisted-torrent-link:hover .torrent-download-links { display: inline-block; }
.movie-preview-unique-list {
width: 50%; max-width: 400px; max-height: 200px; margin: auto; overflow: auto; text-align: left; padding: 5px; line-height: 15px;
color: #000; background-color: white; border: 3px solid #222; border-radius: 5px;
}
.movie-preview-unique-list > * { margin: 2px; }
.movie-preview-unique-list a { border: 0; }
.movie-preview-unique-list a:hover { border: 0; text-decoration: underline; }
a.movie-preview.highlight { background-color: rgba(255, 231, 58, 0.59); }
.movie-preview-enhancement { display: inline-block !important; max-width: 30px; min-width: 30px; font-size: 85%; margin:0 4px 0 0; }
.movie-preview-enhancement.remarkable { font-weight: bold; }
.movie-preview-enhancement.starred-1::after { content: "★"; color: #DD0000; }
.movie-preview-enhancement.starred-2::after { content: "★"; color: #660000; }
.movie-preview-enhancement.starred-3::after { content: "★"; }
.movie-preview-enhancement.starred-4::after { content: "☆"; }
.preview--poster { flex-shrink: 0; width: 200px; height: 283px; }
.preview--poster--img { cursor: pointer; width: 100%; height: 100%; }
.preview--info { text-align:left; padding:3px; height:277px; overflow:auto; display:inline-block; }
.preview--info--title { text-align:center; font-size:125%; font-weight:bold; }
.preview--info--trailer { color: #369; cursor: pointer; display: inline-block; }
.preview--info--trailer:hover { text-decoration: underline; }
.preview--info--trailer::before { content: '('; }
.preview--info--trailer::after { content: '), '; }
.preview--info--imdb-rating, .preview--info--imdb-votes { font-weight: bold; }
`;
const appendStyle = (css) => {
const n = document.createElement("style");
n.type = "text/css";
n.textContent = css;
document.head.append(n);
};
const fetchSafe = (url) => new Promise((resolve, reject) => {
gmXhr({
url,
method: "GET",
onload: (res) => resolve(res.responseText),
onerror: reject
});
});
const setImgSrcBypassingAdBlock = (imageNode, src) => {
imageNode.src = src;
let blobUrl = null;
imageNode.onerror = () => {
if (!imageNode.src || !/^https?:/i.test(imageNode.src)) return;
gmXhr({
url: imageNode.src,
method: "GET",
responseType: "blob",
onload: (data) => {
const reader = new FileReader();
reader.onloadend = () => {
if (blobUrl) URL.revokeObjectURL(blobUrl);
blobUrl = reader.result;
imageNode.src = blobUrl;
const old = imageNode;
const clone = old.cloneNode();
clone.style = "";
old.replaceWith(clone);
};
reader.readAsDataURL(data.response);
},
onerror: () => {
imageNode.src = POSTER_PLACEHOLDER;
}
});
};
};
// Helpers
const getTorrentSearchURLFromMovieTitle = (title) =>
`https://thepiratebay.org/search/${encodeURIComponent(title)}/0/99/0`;
const getMovieHashFromTitleAndYear = (title, year = "") =>
`${title}_${year}`.trim().replace(/[^a-zA-Z0-9]+/g, "-");
const isHostnameIMDB = (h) => h.endsWith("imdb.com");
const isHostnamePirateBay = (h) => /.*(pirate.*bay|tpb).*/.test(h);
// Robust 1337x/document fallback: use document.title and try to carve out year if present
function getBestTitleYearFallback() {
const h1 = document.querySelector("h1");
let base = (h1 && h1.textContent) ? h1.textContent : document.title || "";
base = base.replace(/\s*[-|–]\s*1337x.*$/i, "").trim();
// Replace dots with spaces, strip common quality tags
base = base.replace(/\./g, " ").replace(/\b(720p|1080p|2160p|480p|webrip|web-dl|bluray|bdrip|hdrip|x264|x265|hevc|aac|dts|yts|mkv|mp4)\b/ig, " ");
// Extract year if present
const ym = base.match(/\b(19|20)\d{2}\b/);
if (ym) {
const year = ym[0];
const title = base.slice(0, ym.index).trim();
return { title, year };
}
return { title: base.trim(), year: "" };
}
// OMDb load by title/year (t & y)
async function loadMovie(title, year) {
// 1) Try cache first
const cached = await getCachedMovie(title, year);
if (cached) return cached;
// 2) Fetch from OMDb
const url = `https://www.omdbapi.com/?apikey=${encodeURIComponent(OMDB_API_KEY)}&t=${encodeURIComponent(title)}${year ? `&y=${encodeURIComponent(year)}` : ""}&plot=full&r=json`;
try {
const txt = await fetchSafe(url);
const obj = JSON.parse(txt);
// 3) Cache only successful responses
if (obj && obj.Response === "True") {
await setCachedMovie(title, year, obj);
return obj;
}
// No caching for failed or missing data — will retry next time
return { Error: obj && obj.Error ? obj.Error : "Not found", Title: title, Year: year || "" };
} catch (e) {
// No caching for errors — will retry next time
return { Error: e + "", Title: title, Year: year || "" };
}
}
function extractRankingMetrics(m) {
const awards = m.Awards || "";
const _cap = (text, re, idx = 1) => {
const r = text.match(re);
return r ? r[idx] : "";
};
const reg_wins = /([0-9]+) win(s|)/i;
const reg_noms = /([0-9]+) nomination(s|)/i;
const reg_wins_sig = /Won ([0-9]+) Oscar(s|)/i;
const reg_noms_sig = /Nominated for ([0-9]+) Oscar(s|)/i;
return {
rating: parseFloat(m.imdbRating) || 0,
votes: parseFloat((m.imdbVotes || "0").replace(/,/g, "")) || 0,
wins: parseInt(_cap(awards, reg_wins, 1)) || 0,
noms: parseInt(_cap(awards, reg_noms, 1)) || 0,
wins_sig: parseInt(_cap(awards, reg_wins_sig, 1)) || 0,
noms_sig: parseInt(_cap(awards, reg_noms_sig, 1)) || 0,
awards_text: awards.toLowerCase(),
};
}
function assessMovieRankings(m) {
const rm = extractRankingMetrics(m);
const { rating, votes, wins_sig, wins, noms_sig, noms } = rm;
const isRemarkable = rating >= 7.0 && votes > 50000;
let starredDegree;
if ((wins_sig >= 1 || noms_sig >= 2) && (wins >= 5 || noms >= 10)) starredDegree = 1;
else if (wins >= 10 || (noms_sig >= 1 && noms >= 5) || (rating > 8.0 && votes > 50000)) starredDegree = 2;
else if (wins >= 5 || noms >= 10 || noms_sig >= 1 || votes > 150000) starredDegree = 3;
else if (wins + noms > 1) starredDegree = 4;
let significancePercentage = 1.0;
if (rating <= 5.0 || votes <= 1000) {
significancePercentage = Math.max(0.15, Math.min(rating / 10, votes / 1000));
} else if (m.imdbRating == "N/A" || m.imdbVotes == "N/A") {
significancePercentage = 0.15;
}
return { isRemarkable, starredDegree, significancePercentage, rankingMetrics: rm };
}
function initPreviewNode() {
const previewNode = document.createElement("div");
previewNode.className = "movie-preview-box";
previewNode.insertAdjacentHTML("beforeend", `
<div class="preview--poster">
<img class="preview--poster--img" src="${POSTER_PLACEHOLDER}">
</div>
<div class="preview--info">
<div class="preview--info--title">
<a href="" target="_blank">
<span class="title">Title</span> (<span class="year">Year</span>)
</a>
</div>
<div class="preview--info--trailer" title="Play trailer" data-trailer-url="">▶</div>
<span class="preview--info--imdb-rating">-</span><span style="color:grey;">/10</span>
(<span class="preview--info--imdb-votes">-</span> votes),
<span class="preview--info--imdb-metascore">-</span> Metascore
<br /><u>Awards</u>: <span class="preview--info--awards">N/A</span>
<br /><u>Genre</u>: <span class="preview--info--genre">-</span>
<br /><u>Released</u>: <span class="preview--info--released">-</span>
<br /><u>Box Office</u>: <span class="preview--info--boxofficegross">N/A</span>
<br /><u>Rated</u>: <span class="preview--info--mpaa-rating">-</span>,
<u>Runtime</u>: <span class="preview--info--runtime">-</span>
<br /><u>Actors</u>: <span class="preview--info--actors">-</span>
<br /><u>Director</u>: <span class="preview--info--director">-</span>
<br /><u>Plot</u>: <span class="preview--info--plot">-</span>
</div>
`);
const posterImg = previewNode.querySelector(".preview--poster--img");
if (posterImg) {
posterImg.addEventListener("click", (e) => {
e.preventDefault();
const poster = posterImg.getAttribute("src") || "";
if (!poster || poster === POSTER_PLACEHOLDER || !/^https?:/i.test(poster)) return;
window.open(poster, "", "width=600,height=600");
});
}
const trailerBtn = previewNode.querySelector(".preview--info--trailer");
if (trailerBtn) {
trailerBtn.addEventListener("click", (e) => {
e.preventDefault();
const url = trailerBtn.getAttribute("data-trailer-url");
if (url) window.open(url, "", "width=900,height=500");
});
}
previewNode.hiding = 0;
previewNode.show = () => {
if (previewNode.hiding) clearTimeout(previewNode.hiding);
previewNode.hiding = 0;
previewNode.classList.add("visible");
};
previewNode.hide = () => {
if (previewNode.hiding) clearTimeout(previewNode.hiding);
previewNode.hiding = setTimeout(() => {
previewNode.classList.remove("visible");
previewNode.hiding = 0;
}, 800);
};
previewNode.setMovie = (m) => {
const tA = previewNode.querySelector(".preview--info--title > a");
if (tA) tA.setAttribute("href", m.imdbID ? `https://www.imdb.com/title/${m.imdbID}` : "#");
const tS = previewNode.querySelector(".preview--info--title .title");
if (tS) tS.textContent = m.Title || "-";
const yS = previewNode.querySelector(".preview--info--title .year");
if (yS) yS.textContent = m.Year || "-";
const pImg = previewNode.querySelector(".preview--poster--img");
if (pImg) setImgSrcBypassingAdBlock(pImg, m.Poster || POSTER_PLACEHOLDER);
if (!m.Trailer) previewNode.classList.add("no-trailer"); else previewNode.classList.remove("no-trailer");
const tr = previewNode.querySelector(".preview--info--trailer");
if (tr) tr.setAttribute("data-trailer-url", m.Trailer || "");
const r = previewNode.querySelector(".preview--info--imdb-rating");
if (r) r.textContent = m.imdbRating || "-";
const v = previewNode.querySelector(".preview--info--imdb-votes");
if (v) v.textContent = m.imdbVotes || "-";
const ms = previewNode.querySelector(".preview--info--imdb-metascore");
if (ms) ms.textContent = m.Metascore || "-";
const rel = previewNode.querySelector(".preview--info--released");
if (rel) rel.textContent = m.Released || "-";
const bo = previewNode.querySelector(".preview--info--boxofficegross");
if (bo) bo.textContent = m.BoxOffice || "N/A";
const g = previewNode.querySelector(".preview--info--genre");
if (g) g.textContent = m.Genre || "-";
const rr = previewNode.querySelector(".preview--info--mpaa-rating");
if (rr) rr.textContent = m.Rated || "-";
const rt = previewNode.querySelector(".preview--info--runtime");
if (rt) rt.textContent = m.Runtime || "-";
const aw = previewNode.querySelector(".preview--info--awards");
if (aw) aw.innerHTML = (m.Awards || "N/A")
.replace("Oscars.", "<b>Oscars</b>.")
.replace("Oscar.", "<b>Oscar</b>.")
.replace("Another ", "<br />Another ");
const ac = previewNode.querySelector(".preview--info--actors");
if (ac) ac.textContent = m.Actors || "-";
const dr = previewNode.querySelector(".preview--info--director");
if (dr) dr.textContent = m.Director || "-";
const pl = previewNode.querySelector(".preview--info--plot");
if (pl) pl.textContent = m.Plot || "-";
};
previewNode.addEventListener("mouseover", previewNode.show);
previewNode.addEventListener("mouseout", previewNode.hide);
return previewNode;
}
function updateLinkNodesWithMovieData(nodes, movie, onOver, onOut) {
nodes.forEach((linkNode) => {
if (!movie || movie.Error) return;
linkNode.addEventListener("mouseover", () => onOver(movie));
linkNode.addEventListener("mouseout", () => onOut(movie));
const { isRemarkable, starredDegree, significancePercentage, rankingMetrics: { awards_text } } = assessMovieRankings(movie);
const enh = document.createElement("a");
enh.classList.add("movie-preview-enhancement");
if (isRemarkable) enh.classList.add("remarkable");
if (starredDegree) enh.classList.add(`starred-${starredDegree}`);
enh.href = movie.imdbID ? `https://www.imdb.com/title/${movie.imdbID}` : "#";
enh.target = "_blank";
enh.title = `${movie.imdbVotes || "-"} votes - ${movie.Runtime || "-"} - Rated ${movie.Rated || "-"} - Awards: ${awards_text || "-"}`;
enh.textContent = movie.imdbRating || "-";
enh.style.opacity = String(significancePercentage);
if (linkNode.parentNode) {
linkNode.parentNode.insertBefore(enh, linkNode);
}
});
}
function applyImdbDomUpdate() {
const nodes = document.querySelectorAll(
"div.titleBar > div.title_wrapper > h1, td.titleColumn, div.lister-item-content .lister-item-header, div.title > a.title-grid, td.overview-top > h4 > a"
);
for (const n of nodes) {
if (n.hasAttribute("with-download-link")) continue;
n.setAttribute("with-download-link", "true");
let movieTitle = n.textContent || "";
movieTitle = movieTitle.replace(/\s+/g, " ").trim();
const a = document.createElement("a");
a.classList.add("imdb-download-link");
a.href = getTorrentSearchURLFromMovieTitle(movieTitle);
n.append(a);
}
}
function getMovieTitleAndYearFromLinkNode(linkNode) {
let text = (linkNode.textContent || "").toLowerCase();
// strip punctuation and common quality tags
text = text.replace(/[.,()]/g, " ")
.replace(/\b(1080p|720p|2160p|480p|webrip|web-dl|bluray|bdrip|hdrip|x264|x265|hevc|aac|dts|yts|mkv|mp4)\b/ig, " ");
const reYear = /\b(19|20)\d{2}\b/;
const reSeries = /S[0-9]{2}E[0-9]{2}|[0-9]{1}x[0-9]{2}/i;
const yM = text.match(reYear);
if (yM) {
const year = yM[0];
const title = text.slice(0, yM.index).trim();
if (title) return { title, year };
} else if (reSeries.test(text)) {
const idx = text.search(reSeries);
const title = text.slice(0, idx).trim();
if (title) return { title, year: "-" };
}
return { title: null, year: null };
}
function cleanupPorn(node) {
const s = (node.innerHTML || "").toLowerCase();
if (s.includes("xxx") || s.includes("porn")) node.outerHTML = "";
}
async function main() {
appendStyle(STYLE);
const hostname = window.location.hostname;
if (isHostnameIMDB(hostname)) {
applyImdbDomUpdate();
return;
}
// Starter button
const starter = document.createElement("form");
starter.className = "movie-preview-starter";
starter.insertAdjacentHTML("beforeend", `<button class="movie-preview-starter--button"> load IMDb info </button>`);
starter.addEventListener("submit", async (e) => {
e.preventDefault();
const preview = initPreviewNode();
const movies = new Map(); // key: hash, val: {title, year, hash, promise}
// Scan all anchors for titles and magnets
document.querySelectorAll("a").forEach((a) => {
const href = a.getAttribute("href") || "";
const hashMatch = /(^\/|^magnet\:\?xt\=urn\:btih\:)([a-zA-Z0-9]{40})/i.exec(href);
cleanupPorn(a);
if (hashMatch) {
const hash = hashMatch[2].toUpperCase();
const assist = document.createElement("div");
assist.className = "torrent-download-links";
assist.insertAdjacentHTML("beforeend", `
<a target="_blank" href="https://torrage.info/torrent.php?h=${hash}" style="display:inline-block;padding:0 5px;background-color:#748DAB;text-align:center;">t1</a>
<a target="_blank" href="https://www.btcache.me/torrent/${hash}" style="display:inline-block;padding:0 5px;background-color:#748DAB;text-align:center;">t2</a>
<a target="_blank" href="https://itorrents.org/torrent/${hash}.torrent" style="display:inline-block;padding:0 5px;background-color:#748DAB;text-align:center;">t3</a>
`);
const parent = a.parentNode;
if (parent && parent.nodeType === 1) {
parent.classList.add("assisted-torrent-link");
parent.append(assist);
}
}
const { title, year } = getMovieTitleAndYearFromLinkNode(a);
if (title && (year || year === "-")) {
const h = getMovieHashFromTitleAndYear(title, year);
a.classList.add("movie-preview");
a.dataset.movieHash = h;
if (!movies.has(h)) {
movies.set(h, { title, year, hash: h, promise: loadMovie(title, year === "-" ? "" : year) });
}
}
});
// 1337x/detail fallback: if nothing parsed from anchors, try document title/h1
if (movies.size === 0) {
const fb = getBestTitleYearFallback();
if (fb.title) {
const h = getMovieHashFromTitleAndYear(fb.title, fb.year || "");
movies.set(h, { title: fb.title, year: fb.year || "", hash: h, promise: loadMovie(fb.title, fb.year || "") });
}
}
const onOver = (m) => { preview.setMovie(m); preview.show(); };
const onOut = () => { preview.hide(); };
for (const mv of movies.values()) {
mv.promise.then((data) => {
const nodes = document.querySelectorAll(`.movie-preview[data-movie-hash="${mv.hash}"]`);
updateLinkNodesWithMovieData(Array.from(nodes), data, onOver, onOut);
}).catch((err) => console.error("[IMDB info] movie error:", mv.hash, err));
}
starter.remove();
// Unique list
const list = document.createElement("div");
list.className = "movie-preview-unique-list";
for (const mv of movies.values()) {
const row = document.createElement("div");
const l = document.createElement("a");
l.className = "movie-preview";
l.dataset.movieHash = mv.hash;
l.textContent = mv.hash;
l.addEventListener("click", () => {
document.querySelectorAll(".movie-preview").forEach(el => el.classList.remove("highlight"));
document.querySelectorAll(`.movie-preview[data-movie-hash="${mv.hash}"]`).forEach(el => el.classList.add("highlight"));
});
mv.promise.then((md) => {
if (md && md.Title) l.textContent = `${md.Title} (${md.Year || "-"})`;
}).catch(() => {});
row.append(l);
list.append(row);
}
document.body.prepend(list);
document.body.append(preview);
window.addEventListener("beforeunload", () => {
if (preview.hiding) clearTimeout(preview.hiding);
});
});
document.body.prepend(starter);
if (isHostnamePirateBay(hostname)) {
const mainContent = document.querySelector("#main-content");
if (mainContent) {
mainContent.style.marginLeft = "0";
mainContent.style.marginRight = "0";
}
}
}
main();
})();