Mode-Affiches/mode_affiches.js

496 lines
19 KiB
JavaScript
Raw Normal View History

2025-07-12 08:46:54 +00:00
// ==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 = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 32" width="70" height="42" style="vertical-align:middle; margin-right:4px;"><rect width="110" height="32" rx="8" fill="#01d277"/><text x="55" y="22" text-anchor="middle" font-size="19" font-family="Arial" fill="#fff" font-weight="bold">TMDB</text></svg>`;
// 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}<span style="font-size:19px;font-weight:bold;">${vote}</span><span style="font-size:13px;color:#ffd04e;">${votes}</span>`;
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 à louverture 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 = `
<div style="display:flex;align-items:center;justify-content:flex-start;margin-bottom:18px;width:100%;">
<span style="font-size:${rlzFontSize}px; font-weight:600; color:#45e3ee; margin-right:24px;display:flex;align-items:center;">
<span style="font-size:${rlzFontSize + 2}px;vertical-align:middle;">&#8595;</span>
Les dernières releases
<span style="font-size:${rlzFontSize + 2}px;vertical-align:middle;">&#8595;</span>
</span>
<span style="flex:0 0 36px; margin:0 22px; display:flex;justify-content:center;align-items:center; color:#aaa;font-size:${rlzFontSize - 1}px;font-weight:400;">|</span>
<a href="${card.querySelector('a[href*="?d=fiche"]')?.href || '#'}"
style="color:#ffd04e; font-size:${rlzFontSize - 1}px; font-weight:600; text-decoration:none;">
Voir toutes les releases pour ${typeGender} ${typeLabel}
</a>
</div>
`;
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 += `
<div style="margin-bottom:12px;display:flex;align-items:center;gap:12px;">
<span style="flex:3 1 70%;font-size:${rlzFontSize}px;font-weight:400;color:#cde5fc;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${title}">
${title}
</span>
<span style="margin-left:12px;font-size:${rlzFontSize-2}px;font-weight:400;color:#b5dbff;white-space:nowrap;">
${size ? size : ''}${size && date ? ' • ' : ''}${date ? date : ''}
</span>
<div style="display:inline-flex;gap:10px;margin-left:12px;vertical-align:middle;align-items:center;">
${cardBodyHTML}
${nfoHTML}
</div>
</div>
`;
});
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);
})();