// ==UserScript== // @name UseNet Enhanced // @version 6.28 // @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://raw.githubusercontent.com/Aerya/Mode-Affiches/main/mode_affiches.js // @downloadURL https://raw.githubusercontent.com/Aerya/Mode-Affiches/main/mode_affiches.js // @grant none // ==/UserScript== (function () { 'use strict'; const TMDB_API_KEY = '1234'; // Mettez votre clé ici ! 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 (!TMDB_API_KEY || !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://api.themoviedb.org/3/movie/${tmdbId}?api_key=${TMDB_API_KEY}&language=fr-FR`; else if (type === 'tv') url = `https://api.themoviedb.org/3/tv/${tmdbId}?api_key=${TMDB_API_KEY}&language=fr-FR`; 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 note TMDB (optionnel) 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 = `TMDB`; // Détermine le lien TMDB (film ou série) 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}`; const badge = document.createElement('a'); badge.href = tmdbUrl; badge.target = '_blank'; badge.rel = 'noopener noreferrer'; badge.title = "Voir sur TMDB"; badge.style.position = 'absolute'; badge.style.top = '7px'; badge.style.left = '8px'; badge.style.background = '#032541de'; badge.style.color = '#ffd04e'; badge.style.fontWeight = 'bold'; badge.style.borderRadius = '8px'; badge.style.padding = '2px 11px 2px 3px'; badge.style.fontSize = '18px'; badge.style.boxShadow = '0 2px 8px #222c'; badge.style.zIndex = 15; badge.style.display = 'flex'; badge.style.alignItems = 'center'; badge.style.textDecoration = 'none'; badge.innerHTML = `${tmdbSvg}${vote}${votes}`; containerCard.appendChild(badge); }); } // 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'; // Responsive : recalcule la largeur à l’ouverture si resize function adjustOverlayWidth() { tooltip.style.width = Math.min(window.innerWidth - 40, 1150) + 'px'; } window.addEventListener('resize', adjustOverlayWidth); // Header overlay let typeLabel = 'film', typeGender = 'le'; if (group.movieType === 'tv') { typeLabel = 'série'; typeGender = 'la'; } tooltip.innerHTML = `
Les dernières releases | Voir toutes les releases pour ${typeGender} ${typeLabel}
`; 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 += `
${title} ${size ? size : ''}${size && date ? ' • ' : ''}${date ? date : ''}
${cardBodyHTML} ${nfoHTML}
`; }); tooltip.innerHTML += tooltipHTML; setTimeout(() => { tooltip.style.minHeight = ''; tooltip.style.height = ''; tooltip.style.maxHeight = 'none'; }, 10); // Toggle overlay au clic sur affiche let isOpen = false; cloneImg.addEventListener('click', (e) => { e.stopPropagation(); adjustOverlayWidth(); document.querySelectorAll('.affiche-tooltip').forEach(div => div.style.display = 'none'); tooltip.style.display = isOpen ? 'none' : 'block'; isOpen = !isOpen; }); document.addEventListener('click', (e) => { if (!containerCard.contains(e.target)) { tooltip.style.display = 'none'; isOpen = false; } }); containerCard.appendChild(tooltip); gallery.appendChild(containerCard); }); containers.forEach((container, idx) => { container.innerHTML = ''; if (idx === 0) container.appendChild(gallery); }); } function createConfigDropdown() { const container = document.createElement('div'); container.id = 'affiche-mode-menu-container'; container.style.position = 'fixed'; container.style.top = '12px'; container.style.right = '12px'; container.style.zIndex = '19999'; container.style.background = '#f5f5f5'; container.style.border = '1px solid #ccc'; container.style.borderRadius = '6px'; container.style.padding = '10px 14px'; container.style.fontSize = '14px'; container.style.boxShadow = '0 2px 6px rgba(0,0,0,0.2)'; container.style.color = '#000'; const toggle = document.createElement('button'); toggle.textContent = '🎬 Mode Affiches Alternatif'; toggle.style.cursor = 'pointer'; toggle.style.fontWeight = 'bold'; toggle.style.background = '#309d98'; toggle.style.color = '#fff'; toggle.style.border = 'none'; toggle.style.borderRadius = '5px'; toggle.style.padding = '6px 12px'; toggle.style.marginBottom = '8px'; toggle.style.zIndex = '19999'; toggle.addEventListener('click', () => { menu.style.display = menu.style.display === 'none' ? 'block' : 'none'; }); const menu = document.createElement('div'); menu.style.display = 'none'; menu.style.marginTop = '8px'; menu.style.zIndex = '20001'; // Choix sections const config = getStoredConfig(); CATEGORIES.forEach(cat => { const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.id = `chk_${cat.key}`; checkbox.checked = config.includes(cat.key); checkbox.addEventListener('change', () => { const newConf = getStoredConfig(); if (checkbox.checked) newConf.push(cat.key); else newConf.splice(newConf.indexOf(cat.key), 1); saveConfig(newConf); window.location.reload(); }); const label = document.createElement('label'); label.textContent = cat.label; label.setAttribute('for', checkbox.id); label.style.marginLeft = '4px'; const wrapper = document.createElement('div'); wrapper.appendChild(checkbox); wrapper.appendChild(label); menu.appendChild(wrapper); }); // Option badge TMDB const tmdbRow = document.createElement('div'); tmdbRow.style.marginTop = '14px'; const tmdbCheckbox = document.createElement('input'); tmdbCheckbox.type = 'checkbox'; tmdbCheckbox.id = 'chk_tmdb'; tmdbCheckbox.checked = getShowTmdb(); tmdbCheckbox.addEventListener('change', () => { setShowTmdb(tmdbCheckbox.checked); window.location.reload(); }); const tmdbLabel = document.createElement('label'); tmdbLabel.textContent = "Afficher la note TMDB sur l'affiche"; tmdbLabel.setAttribute('for', tmdbCheckbox.id); tmdbLabel.style.marginLeft = '4px'; tmdbRow.appendChild(tmdbCheckbox); tmdbRow.appendChild(tmdbLabel); menu.appendChild(tmdbRow); // Slider taille vignettes const sizeRow = document.createElement('div'); sizeRow.style.marginTop = '14px'; sizeRow.style.marginBottom = '2px'; sizeRow.textContent = 'Taille des affiches :'; menu.appendChild(sizeRow); const sizeSlider = document.createElement('input'); sizeSlider.type = 'range'; sizeSlider.min = '200'; sizeSlider.max = '360'; sizeSlider.step = '10'; sizeSlider.value = getMinWidth(); sizeSlider.style.width = '180px'; sizeSlider.style.verticalAlign = 'middle'; const sizeVal = document.createElement('span'); sizeVal.textContent = ` ${getMinWidth()}px`; sizeVal.style.marginLeft = '8px'; sizeSlider.addEventListener('input', () => { sizeVal.textContent = ` ${sizeSlider.value}px`; }); sizeSlider.addEventListener('change', () => { setMinWidth(sizeSlider.value); window.location.reload(); }); menu.appendChild(sizeSlider); menu.appendChild(sizeVal); // Slider taille police overlay const fontRow = document.createElement('div'); fontRow.style.marginTop = '16px'; fontRow.style.marginBottom = '2px'; fontRow.textContent = 'Taille du texte (overlay) :'; menu.appendChild(fontRow); const fontSlider = document.createElement('input'); fontSlider.type = 'range'; fontSlider.min = '14'; fontSlider.max = '28'; fontSlider.step = '2'; fontSlider.value = getFontSize(); fontSlider.style.width = '140px'; fontSlider.style.verticalAlign = 'middle'; const fontVal = document.createElement('span'); fontVal.textContent = ` ${getFontSize()}px`; fontVal.style.marginLeft = '8px'; fontSlider.addEventListener('input', () => { fontVal.textContent = ` ${fontSlider.value}px`; document.querySelectorAll('.affiche-tooltip').forEach(div => { div.style.fontSize = fontSlider.value + 'px'; }); }); fontSlider.addEventListener('change', () => { setFontSize(fontSlider.value); window.location.reload(); }); menu.appendChild(fontSlider); menu.appendChild(fontVal); container.appendChild(toggle); container.appendChild(menu); // Remonter haut de page if (!document.getElementById('remonter-haut-btn')) { const upBtn = document.createElement('button'); upBtn.id = 'remonter-haut-btn'; upBtn.textContent = '↑ Haut de page'; upBtn.style.position = 'fixed'; upBtn.style.bottom = '22px'; upBtn.style.right = '26px'; upBtn.style.background = '#309d98'; upBtn.style.color = '#fff'; upBtn.style.border = 'none'; upBtn.style.borderRadius = '6px'; upBtn.style.padding = '8px 20px'; upBtn.style.fontWeight = 'bold'; upBtn.style.fontSize = '16px'; upBtn.style.cursor = 'pointer'; upBtn.style.boxShadow = '0 3px 12px rgba(0,0,0,0.14)'; upBtn.style.zIndex = '99999'; upBtn.addEventListener('click', () => { window.scrollTo({ top: 0, behavior: 'smooth' }); }); document.body.appendChild(upBtn); } if (!document.getElementById('affiche-mode-menu-container')) document.body.appendChild(container); } function start() { createConfigDropdown(); transformAffiches(); } if (document.readyState !== 'loading') start(); else document.addEventListener('DOMContentLoaded', start); })();