// ==UserScript== // @name UseNet Enhanced // @version 6.29 // @date 12.07.25 // @description Userscript pour transformer la liste de releases sur un indexeur privé en galerie d'affiches responsive // @author Aerya | https://upandclear.org // @match https://unfr.pw/* // @updateURL https://tig.unfr.pw/Aerya/Mode-Affiches/raw/branch/main/mode_affiches.js // @downloadURL https://tig.unfr.pw/Aerya/Mode-Affiches/raw/branch/main/mode_affiches.js // @grant none // ==/UserScript== (function () { 'use strict'; const TMDB_CACHE_TTL = 24 * 60 * 60 * 1000; // 1 jour const CATEGORIES = [ { key: 'HOME', label: 'Accueil' }, { key: 'MOVIE', label: 'Films' }, { key: 'TV', label: 'Séries' } ]; const STORAGE_KEY = 'afficheModeSections'; const STORAGE_MINWIDTH_KEY = 'afficheMinWidth'; const STORAGE_FONT_SIZE_KEY = 'afficheRlzFontSize'; const STORAGE_SHOW_TMDB_KEY = 'afficheShowTmdb'; const TMDB_CACHE_KEY = 'afficheTmdbCache'; function getStoredConfig() { try { return JSON.parse(localStorage.getItem(STORAGE_KEY)) || []; } catch (e) { return []; } } function saveConfig(arr) { localStorage.setItem(STORAGE_KEY, JSON.stringify(arr)); } function getMinWidth() { return parseInt(localStorage.getItem(STORAGE_MINWIDTH_KEY) || '260'); } function setMinWidth(val) { localStorage.setItem(STORAGE_MINWIDTH_KEY, val); } function getFontSize() { return parseInt(localStorage.getItem(STORAGE_FONT_SIZE_KEY) || '22'); } function setFontSize(val) { localStorage.setItem(STORAGE_FONT_SIZE_KEY, val); } function getShowTmdb() { return localStorage.getItem(STORAGE_SHOW_TMDB_KEY) !== '0'; } function setShowTmdb(val) { localStorage.setItem(STORAGE_SHOW_TMDB_KEY, val ? '1' : '0'); } function getTmdbCache() { try { return JSON.parse(localStorage.getItem(TMDB_CACHE_KEY)) || {}; } catch (e) { return {}; } } function saveTmdbCache(cache) { localStorage.setItem(TMDB_CACHE_KEY, JSON.stringify(cache)); } function fetchTmdb(type, tmdbId) { if (!tmdbId) return Promise.resolve(null); const cache = getTmdbCache(); const key = `${type}_${tmdbId}`; const now = Date.now(); if (cache[key] && now - cache[key].fetched < TMDB_CACHE_TTL) { return Promise.resolve(cache[key].data); } let url = ''; if (type === 'movie') url = `https://unfr.pw/proxy_tmdb?type=movie&id=${tmdbId}`; else if (type === 'tv') url = `https://unfr.pw/proxy_tmdb?type=tv&id=${tmdbId}`; return fetch(url) .then(r => r.json()) .then(data => { cache[key] = { data, fetched: now }; saveTmdbCache(cache); return data; }).catch(() => null); } function extractDateAndSize(card) { let date = '', size = ''; card.querySelectorAll('.badge').forEach(badge => { const txt = badge.textContent.trim(); if (!size && /([0-9]+(\.[0-9]+)?)( ?(GB|MB|G|M|Go|Mo))$/i.test(txt)) size = txt; if (!date && /\d{2}\/\d{2}\/\d{2}/.test(txt)) date = txt.match(/\d{2}\/\d{2}\/\d{2}/)?.[0] || ''; }); return { date, size }; } function getSection() { const url = new URL(window.location.href); const section = url.searchParams.get('section'); const vm = url.searchParams.get('vm'); if (!section && (vm === '1' || !vm)) return 'HOME'; return section?.toUpperCase() || ''; } function transformAffiches() { const section = getSection(); const config = getStoredConfig(); const minwidth = getMinWidth(); const rlzFontSize = getFontSize(); const showTmdb = getShowTmdb(); const show = config.includes(section); if (!CATEGORIES.some(c => c.key === section)) return; if (!show) return; const cards = Array.from(document.querySelectorAll('.containert.article .card.affichet')); const containers = document.querySelectorAll('.containert.article'); if (!cards.length || !containers.length) return; // Group releases par film/série via TMDB ID const grouped = new Map(); cards.forEach(card => { const link = card.querySelector('a[href*="?d=fiche"]'); const href = link?.getAttribute('href') || ''; let tmdbId = ''; let movieType = 'movie'; let match = href.match(/movieid=(\d+)/i); if (match) { tmdbId = match[1]; movieType = 'movie'; } else { match = href.match(/tvid=(\d+)/i); if (match) { tmdbId = match[1]; movieType = 'tv'; } } if (!tmdbId) { const inp = card.querySelector('input#tmdb_id'); if (inp) tmdbId = inp.value; } const idKey = movieType + '_' + tmdbId; if (!tmdbId) return; if (!grouped.has(idKey)) grouped.set(idKey, { cards: [], tmdbId, movieType }); grouped.get(idKey).cards.push(card); }); // Création galerie const gallery = document.createElement('div'); gallery.className = 'd-flex flex-wrap'; gallery.style.justifyContent = 'center'; gallery.style.marginTop = '20px'; gallery.style.gap = '8px'; gallery.style.padding = '0 12px'; gallery.style.width = '100%'; gallery.style.marginLeft = 'auto'; gallery.style.marginRight = 'auto'; grouped.forEach((group, groupKey) => { const card = group.cards[0]; const img = card.querySelector('img.card-img-top'); const minHeight = img?.height || 330; if (!img) return; // Affiche = vignette const containerCard = document.createElement('div'); containerCard.style.flex = `0 0 ${minwidth}px`; containerCard.style.maxWidth = `${minwidth}px`; containerCard.style.position = 'relative'; containerCard.style.display = 'block'; // Badge notes TMDB/IMDB (vertical, propre, pas de badge vide) if (showTmdb) { fetchTmdb(group.movieType, group.tmdbId).then(data => { if (!data) return; const vote = data.vote_average ? Number(data.vote_average).toFixed(1) : '?'; const votes = data.vote_count ? ` (${data.vote_count})` : ''; const tmdbSvg = ``; const voteImdb = data.note_imdb ? Number(data.note_imdb).toFixed(1) : '?'; const votesImdb = data.vote_imdb ? ` (${data.vote_imdb})` : ''; const imdbSvg = ``; let tmdbUrl = ''; if (group.movieType === 'movie') tmdbUrl = `https://www.themoviedb.org/movie/${group.tmdbId}`; else if (group.movieType === 'tv') tmdbUrl = `https://www.themoviedb.org/tv/${group.tmdbId}`; // Conteneur vertical des badges const badgeWrapper = document.createElement('div'); badgeWrapper.style.position = 'absolute'; badgeWrapper.style.top = '7px'; badgeWrapper.style.left = '8px'; badgeWrapper.style.display = 'flex'; badgeWrapper.style.flexDirection = 'column'; badgeWrapper.style.gap = '3px'; badgeWrapper.style.zIndex = 15; badgeWrapper.style.pointerEvents = 'none'; // TMDB badge if ( typeof data.vote_average !== 'undefined' && data.vote_average !== null && data.vote_average !== 0 ) { const badgeTmdb = document.createElement('a'); badgeTmdb.href = tmdbUrl; badgeTmdb.target = '_blank'; badgeTmdb.rel = 'noopener noreferrer'; badgeTmdb.title = "Voir sur TMDB"; badgeTmdb.style.background = '#032541de'; badgeTmdb.style.color = '#ffd04e'; badgeTmdb.style.fontWeight = 'bold'; badgeTmdb.style.borderRadius = '8px'; badgeTmdb.style.padding = '2px 11px 2px 3px'; badgeTmdb.style.fontSize = '18px'; badgeTmdb.style.boxShadow = '0 2px 8px #222c'; badgeTmdb.style.display = 'flex'; badgeTmdb.style.alignItems = 'center'; badgeTmdb.style.gap = '6px'; badgeTmdb.style.pointerEvents = 'auto'; badgeTmdb.innerHTML = `${tmdbSvg}${vote}${votes}`; badgeWrapper.appendChild(badgeTmdb); } // IMDB badge if ( typeof data.note_imdb !== 'undefined' && data.note_imdb !== null && data.note_imdb !== 0 ) { const badgeImdb = document.createElement('span'); badgeImdb.style.background = '#032541de'; badgeImdb.style.color = '#ffd04e'; badgeImdb.style.fontWeight = 'bold'; badgeImdb.style.borderRadius = '8px'; badgeImdb.style.padding = '2px 11px 2px 3px'; badgeImdb.style.fontSize = '18px'; badgeImdb.style.boxShadow = '0 2px 8px #222c'; badgeImdb.style.display = 'flex'; badgeImdb.style.alignItems = 'center'; badgeImdb.style.gap = '6px'; badgeImdb.innerHTML = `${imdbSvg}${voteImdb}${votesImdb}`; badgeImdb.style.pointerEvents = 'auto'; badgeWrapper.appendChild(badgeImdb); } // Ajoute les badges seulement s'il y en a if (badgeWrapper.children.length > 0) { containerCard.appendChild(badgeWrapper); } }); } // Clone affiche const cloneImg = img.cloneNode(true); cloneImg.style.width = `${minwidth}px`; cloneImg.style.cursor = 'pointer'; containerCard.appendChild(cloneImg); // Overlay const tooltip = document.createElement('div'); tooltip.className = 'affiche-tooltip'; tooltip.style.position = 'absolute'; tooltip.style.top = '0'; tooltip.style.left = '0'; tooltip.style.background = 'rgba(10,10,10,0.98)'; tooltip.style.color = '#fff'; tooltip.style.padding = '22px 36px 26px 36px'; tooltip.style.borderRadius = '10px'; tooltip.style.fontSize = rlzFontSize + 'px'; tooltip.style.fontWeight = '400'; tooltip.style.width = Math.min(window.innerWidth - 40, 1150) + 'px'; tooltip.style.maxWidth = '99vw'; tooltip.style.minHeight = `${minHeight}px`; tooltip.style.zIndex = '1010'; tooltip.style.boxShadow = '0 0 14px 6px rgba(0,0,0,0.7)'; tooltip.style.whiteSpace = 'normal'; tooltip.style.display = 'none'; tooltip.style.pointerEvents = 'auto'; function adjustOverlayWidth() { tooltip.style.width = Math.min(window.innerWidth - 40, 1150) + 'px'; } window.addEventListener('resize', adjustOverlayWidth); let typeLabel = 'film', typeGender = 'le'; if (group.movieType === 'tv') { typeLabel = 'série'; typeGender = 'la'; } tooltip.innerHTML = `
`; let tooltipHTML = ''; group.cards.forEach((subcard, idx) => { const cardHeader = subcard.querySelector('.card-header'); const cardBody = subcard.querySelector('.card-body'); const title = cardHeader?.textContent.trim() || ''; const { date, size } = extractDateAndSize(subcard); let cardBodyHTML = cardBody ? cardBody.innerHTML : ''; let nfoHTML = ''; if (cardBody && cardBodyHTML) { let tmp = document.createElement('div'); tmp.innerHTML = cardBodyHTML; let spans = tmp.querySelectorAll('span.mx-1'); for (let i = 0; i < spans.length; i++) { let a = spans[i].querySelector('a[data-target="#NFO"]'); if (a) { a.addEventListener('click', function (e) { e.stopPropagation(); document.querySelectorAll('.affiche-tooltip').forEach(div => div.style.display = 'none'); }); nfoHTML = spans[i].outerHTML; spans[i].remove(); break; } } for (let i = 4; i < spans.length; i++) spans[i].remove(); cardBodyHTML = Array.from(tmp.childNodes).map(x => x.outerHTML).join(''); } tooltipHTML += `