diff --git a/mode_affiches.js b/mode_affiches.js
index 2e17702..2529439 100644
--- a/mode_affiches.js
+++ b/mode_affiches.js
@@ -1,536 +1,664 @@
// ==UserScript==
-// @name UseNet Enhanced
-// @version 6.29
+// @name UseNet Enhanced (Overseerr/Jellyseerr TV Fix + Nouvel Onglet)
+// @version 8.4.3
// @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
+// @description Galerie d'affiches, Radarr/Sonarr/Overseerr/Jellyseerr, badges TMDB/IMDB, options menu stables
+// @author Aerya
+// @match *://*/*
// @grant none
// ==/UserScript==
-
(function () {
'use strict';
- const TMDB_CACHE_TTL = 24 * 60 * 60 * 1000; // 1 jour
+ /* ==================================================================
+ * 1. AUTO‑UPDATE DISCRET MULTI-URL
+ * ==================================================================*/
+ const LOCAL_VERSION = '8.3.9'; // Mets ta version ici (à garder synchronisée)
+ const UPDATE_INTERVAL_MS = 12 * 60 * 60 * 1000;
+ const UPDATE_LS_KEY = 'afficheLastUpdateCheck';
- const CATEGORIES = [
- { key: 'HOME', label: 'Accueil' },
- { key: 'MOVIE', label: 'Films' },
- { key: 'TV', label: 'Séries' }
+ const UPDATE_URLS = [
+ `${location.protocol}//tig.${location.hostname}/Aerya/Mode-Affiches/raw/branch/main/mode_affiches.js`,
+ 'https://raw.githubusercontent.com/Aerya/Mode-Affiches/main/mode_affiches.js'
];
- 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 parseVersion(txt) {
+ const m = txt.match(/@version\s+([0-9]+(?:\.[0-9]+)*)/);
+ return m ? m[1] : null;
}
- 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);
+ function isNewer(remote, local) {
+ const R = remote.split('.').map(Number);
+ const L = local.split('.').map(Number);
+ for (let i = 0; i < Math.max(R.length, L.length); i++) {
+ const a = R[i] || 0, b = L[i] || 0;
+ if (a > b) return true;
+ if (a < b) return false;
}
- 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);
+ return false;
}
+ (function checkUpdateMulti() {
+ const last = parseInt(localStorage.getItem(UPDATE_LS_KEY) || '0', 10);
+ const now = Date.now();
+ if (now - last < UPDATE_INTERVAL_MS) return;
+
+ localStorage.setItem(UPDATE_LS_KEY, now.toString());
+
+ Promise.all(
+ UPDATE_URLS.map(url =>
+ fetch(`${url}?_=${now}`)
+ .then(r => r.text())
+ .then(txt => ({ url, version: parseVersion(txt) }))
+ .catch(() => null)
+ )
+ ).then(results => {
+ const valid = results.filter(x => x && x.version && isNewer(x.version, LOCAL_VERSION));
+ if (!valid.length) return;
+ valid.sort((a, b) => {
+ const aV = a.version.split('.').map(Number);
+ const bV = b.version.split('.').map(Number);
+ for (let i = 0; i < Math.max(aV.length, bV.length); i++) {
+ if ((aV[i] || 0) > (bV[i] || 0)) return -1;
+ if ((aV[i] || 0) < (bV[i] || 0)) return 1;
+ }
+ return 0;
+ });
+ window.open(valid[0].url, '_blank');
+ });
+ })();
+
+ /* ==================================================================
+ * 0. CONDITIONS D’ACTIVATION
+ * ==================================================================*/
+ if (!document.getElementById('m0de_afficheS_EnAbled')) return;
+
+ // === Toast notif
+ function toast(msg, type = 'info', ms = 3500) {
+ let color = type === 'error' ? '#c00' : type === 'success' ? '#15a92e' : '#368efc';
+ let bg = type === 'error' ? '#ffd5d5' : type === 'success' ? '#dbffe0' : '#e2eafc';
+ let box = document.createElement('div');
+ box.textContent = msg;
+ box.style = `position:fixed;top:26px;right:24px;z-index:99999;padding:14px 22px;font-size:17px;font-weight:bold;border-radius:9px;background:${bg};color:${color};box-shadow:0 4px 24px #0002;opacity:0.98;`;
+ document.body.appendChild(box);
+ setTimeout(() => box.remove(), ms);
+ }
+
+ // === Persistance config
+ const INSTANCES_KEY = 'afficheUsenetInstances';
+ const STORAGE_SECTIONS_KEY = 'afficheModeSections';
+ const STORAGE_MINWIDTH_KEY = 'afficheMinWidth';
+ const STORAGE_FONT_SIZE_KEY = 'afficheRlzFontSize';
+ const STORAGE_SHOW_TMDB_KEY = 'afficheShowTmdb';
+ const STORAGE_RELEASE_NEWTAB_KEY = 'afficheReleaseNewTab';
+ const TMDB_CACHE_KEY = 'afficheTmdbCache';
+
+ function getInstances() { try { return JSON.parse(localStorage.getItem(INSTANCES_KEY)) || {}; } catch { return {}; } }
+ function setInstances(obj) { localStorage.setItem(INSTANCES_KEY, JSON.stringify(obj)); }
+
+ function getSections() {
+ let arr = [];
+ try { arr = JSON.parse(localStorage.getItem(STORAGE_SECTIONS_KEY)) || []; }
+ catch { arr = []; }
+ return Array.isArray(arr) ? arr : [];
+ }
+ function setSections(arr) { localStorage.setItem(STORAGE_SECTIONS_KEY, JSON.stringify(arr)); }
+
+ function minW() { return parseInt(localStorage.getItem(STORAGE_MINWIDTH_KEY) || '260', 10); }
+ function fontSz() { return parseInt(localStorage.getItem(STORAGE_FONT_SIZE_KEY) || '22', 10); }
+ function showTmdb() { return localStorage.getItem(STORAGE_SHOW_TMDB_KEY) !== '0'; }
+ function openReleasesInNewTab() { return localStorage.getItem(STORAGE_RELEASE_NEWTAB_KEY) === '1'; }
+
+ // === Icons
+ function svg4kBadge(svg) {
+ return `${svg}
+ 4K
+ `;
+ }
+ function imgIcon(url, alt) {
+ return `
+
+ `;
+ }
+ function getIcon(type) {
+ if(type==='radarr') return imgIcon("https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/radarr.svg", "Radarr");
+ if(type==='radarr4k') return svg4kBadge(imgIcon("https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/radarr-4k.svg", "Radarr 4K"));
+ if(type==='sonarr') return imgIcon("https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/sonarr.svg", "Sonarr");
+ if(type==='sonarr4k') return svg4kBadge(imgIcon("https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/sonarr-4k.svg", "Sonarr 4K"));
+ if(type==='overseerr') return imgIcon("https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/overseerr.svg", "Overseerr");
+ if(type==='jellyseerr') return imgIcon("https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/jellyseerr.svg", "Jellyseerr");
+ if(type==='tmdb') return imgIcon("https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/tmdb.svg", "TMDB");
+ if(type==='imdb') return imgIcon("https://www.svgrepo.com/show/349409/imdb.svg", "IMDB");
+ return '';
+ }
+
+ // === API Info
+ const API_INFO = {
+ radarr: { name: "Radarr", api: "radarr", add: "movie", qual: "qualityProfile", root: "rootfolder", icon: "radarr", tmdb: true },
+ radarr4k: { name: "Radarr 4K", api: "radarr4k", add: "movie", qual: "qualityProfile", root: "rootfolder", icon: "radarr4k", tmdb: true },
+ sonarr: { name: "Sonarr", api: "sonarr", add: "series", qual: "qualityProfile", root: "rootfolder", icon: "sonarr", tmdb: false },
+ sonarr4k: { name: "Sonarr 4K", api: "sonarr4k", add: "series", qual: "qualityProfile", root: "rootfolder", icon: "sonarr4k", tmdb: false },
+ overseerr: { name: "Overseerr", api: "overseerr", add: "both", icon: "overseerr" },
+ jellyseerr: { name: "Jellyseerr", api: "jellyseerr", add: "both", icon: "jellyseerr" }
+ };
+
+ // === TMDB fetch/cache (similaire)
+ function fetchTmdb(type, id) {
+ if (!id) return Promise.resolve(null);
+ let cache = {};
+ try { cache = JSON.parse(localStorage.getItem(TMDB_CACHE_KEY)) || {}; } catch { cache = {}; }
+ const key = `${type}_${id}`;
+ const now = Date.now();
+ if (cache[key] && now - cache[key].fetched < 24 * 60 * 60 * 1000) return Promise.resolve(cache[key].data);
+ return fetch(`${location.origin}/proxy_tmdb?type=${type}&id=${id}`)
+ .then(r => r.json())
+ .then(d => { cache[key] = { data: d, fetched: now }; localStorage.setItem(TMDB_CACHE_KEY, JSON.stringify(cache)); return d; })
+ .catch(() => null);
+ }
+
+ // === Détection section
+ const CATEGORIES = [
+ { key: 'HOME', label: 'Accueil' },
+ { key: 'MOVIE', label: 'Films' },
+ { key: 'TV', label: 'Séries' }
+ ];
+ function getSection() {
+ const p = new URLSearchParams(location.search);
+ const s = p.get('section');
+ const vm = p.get('vm');
+ return (!s && (vm === '1' || !vm)) ? 'HOME' : (s || '').toUpperCase();
+ }
+
+ // === Extract date/size
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] || '';
+ card.querySelectorAll('.badge').forEach(b => {
+ const t = b.textContent.trim();
+ if (!size && /([0-9]+(?:\.[0-9]+)?)( ?(GB|MB|G|M|Go|Mo))$/i.test(t)) size = t;
+ if (!date && /\d{2}\/\d{2}\/\d{2}/.test(t)) date = t.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() || '';
+ // === Overlay (profil qualité)
+ function showProfileSelector(profiles, cb) {
+ document.querySelectorAll('.usenet-qprof-popover').forEach(e=>e.remove());
+ let pop = document.createElement('div');
+ pop.className = "usenet-qprof-popover";
+ pop.style = `position:fixed;z-index:99999;top:70px;right:32px;background:#23233a;color:#ffe388;
+ border:2px solid #ffe388;border-radius:12px;box-shadow:0 3px 16px #222a;
+ padding:18px 28px;min-width:260px;`;
+ pop.innerHTML = `Profil Qualité :
`;
+ profiles.forEach(prof => {
+ let btn = document.createElement('button');
+ btn.textContent = prof.name;
+ btn.style = `display:block;width:100%;margin:6px 0;padding:10px 0;font-size:17px;border:none;background:#f9d72c;color:#222;font-weight:bold;border-radius:8px;cursor:pointer;`;
+ btn.onclick = () => { pop.remove(); cb(prof.id); };
+ pop.appendChild(btn);
+ });
+ let cancel = document.createElement('button');
+ cancel.textContent = "Annuler";
+ cancel.style = `display:block;width:100%;margin:14px 0 0 0;padding:10px 0;font-size:15px;border:none;background:#ddd;color:#444;border-radius:8px;cursor:pointer;`;
+ cancel.onclick = ()=>pop.remove();
+ pop.appendChild(cancel);
+ document.body.appendChild(pop);
}
+ // === Affichage
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 section = getSection();
+ const conf = getSections();
+ const activate = conf.includes(section);
+ if (!CATEGORIES.some(c => c.key === section) || !activate) return;
- const cards = Array.from(document.querySelectorAll('.containert.article .card.affichet'));
+ 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();
+ const groups = 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 href = card.querySelector('a[href*="?d=fiche"]')?.getAttribute('href') || '';
+ let id = '', type = 'movie';
+ let m = href.match(/movieid=(\d+)/i);
+ if (m) { id = m[1]; }
+ else if ((m = href.match(/tvid=(\d+)/i))) { id = m[1]; type = 'tv'; }
+ else {
const inp = card.querySelector('input#tmdb_id');
- if (inp) tmdbId = inp.value;
+ if (inp) id = 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);
+ if (!id) return;
+ const key = `${type}_${id}`;
+ if (!groups.has(key)) groups.set(key, { type, id, cards: [] });
+ groups.get(key).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';
+ const inst = getInstances();
+ const hasRadarr = inst.radarr && inst.radarr.url && inst.radarr.apiKey;
+ const hasRadarr4k = inst.radarr4k && inst.radarr4k.url && inst.radarr4k.apiKey;
+ const hasSonarr = inst.sonarr && inst.sonarr.url && inst.sonarr.apiKey;
+ const hasSonarr4k = inst.sonarr4k && inst.sonarr4k.url && inst.sonarr4k.apiKey;
+ const hasOverseerr = inst.overseerr && inst.overseerr.url && inst.overseerr.apiKey;
+ const hasJellyseerr = inst.jellyseerr && inst.jellyseerr.url && inst.jellyseerr.apiKey;
- 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;
+ const gallery = Object.assign(document.createElement('div'), {
+ className: 'd-flex flex-wrap',
+ style: 'justify-content:center;margin-top:20px;gap:8px;padding:0 12px;width:100%;margin-left:auto;margin-right:auto;'
+ });
- // 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';
+ groups.forEach(group => {
+ const card0 = group.cards[0];
+ const img0 = card0.querySelector('img.card-img-top');
+ if (!img0) return;
+ const mH = img0.height || 330;
+ const cardDiv = Object.assign(document.createElement('div'), { style: `flex:0 0 ${minW()}px;max-width:${minW()}px;position:relative;display:block;` });
- // Badge notes TMDB/IMDB (vertical, propre, pas de badge vide)
- if (showTmdb) {
- fetchTmdb(group.movieType, group.tmdbId).then(data => {
+ // === Boutons
+ let btnIdx = 0;
+ const btnGen = (kind, instObj, apiData) => {
+ const btn = document.createElement('button');
+ btn.innerHTML = getIcon(kind);
+ btn.title = `Ajouter à ${apiData.name}`;
+ btn.className = "affiche-usenet-btn";
+ btn.style = `position:absolute; bottom:12px; right:${12 + btnIdx*(36+6)}px; z-index:22; width:36px; height:36px; background:rgba(255,255,255,0.78); border:2.5px solid #e6e6e6; border-radius:11px; padding:0; display:flex; align-items:center; justify-content:center; box-shadow:0 2px 10px #0002; cursor:pointer; transition:transform 0.11s; opacity:0.98;`;
+ btn.onmouseenter = () => btn.style.opacity = "1";
+ btn.onmouseleave = () => btn.style.opacity = "0.98";
+ btn.onclick = (e) => {
+ e.stopPropagation();
+ btn.disabled = true;
+ // Radarr/Sonarr
+ if (kind.startsWith("radarr") || kind.startsWith("sonarr")) {
+ toast("Chargement profils/dossiers...", "info", 1500);
+ Promise.all([
+ fetch(`${instObj.url}/api/v3/${apiData.qual}`, {headers: {'X-Api-Key': instObj.apiKey }}).then(r => r.json()),
+ fetch(`${instObj.url}/api/v3/${apiData.root}`, {headers: {'X-Api-Key': instObj.apiKey }}).then(r => r.json())
+ ]).then(([profs, roots]) => {
+ if (!Array.isArray(profs) || !profs.length) throw new Error('Aucun profil qualité');
+ if (!Array.isArray(roots) || !roots.length) throw new Error('Aucun dossier cible trouvé');
+ showProfileSelector(profs, function(qpid) {
+ const rootFolder = roots.find(r => r.path && !r.unmappedFolders?.length) || roots[0];
+ if (!rootFolder || !rootFolder.path) return toast("Aucun dossier cible valide trouvé", "error");
+ toast("Ajout en cours...", "info", 1200);
+ let body = {};
+ if(apiData.add === 'movie') {
+ fetchTmdb('movie', group.id).then(data => {
+ const tmdbId = group.id;
+ const year = data && data.release_date ? (data.release_date+'').substring(0,4) : '';
+ body = {
+ "title": data?.title || '',
+ "qualityProfileId": qpid,
+ "tmdbId": parseInt(tmdbId),
+ "year": year ? parseInt(year) : undefined,
+ "monitored": true,
+ "rootFolderPath": rootFolder.path,
+ "addOptions": { "searchForMovie": true }
+ };
+ fetch(`${instObj.url}/api/v3/movie`, {
+ method: 'POST',
+ headers: {'X-Api-Key': instObj.apiKey, 'Content-Type': 'application/json'},
+ body: JSON.stringify(body)
+ }).then(async resp => {
+ btn.disabled = false;
+ if (resp.status === 201) {
+ toast("✅ Ajouté à " + apiData.name + " !", "success");
+ } else if (resp.status === 400) {
+ let r = await resp.json();
+ if (r && r[0] && r[0].errorMessage && r[0].errorMessage.match(/already exists/i))
+ toast("Déjà présent dans " + apiData.name, "info");
+ else
+ toast("Erreur " + apiData.name + " : " + (r[0]?.errorMessage || resp.statusText), "error");
+ } else {
+ toast("Erreur " + apiData.name + " ("+resp.status+")", "error");
+ }
+ }).catch(e => {
+ btn.disabled = false;
+ toast("Erreur de connexion " + apiData.name, "error");
+ });
+ });
+ } else {
+ fetchTmdb('tv', group.id).then(data => {
+ const tvdbId = data?.external_ids?.tvdb_id || data?.tvdb_id;
+ body = {
+ "title": data?.name || '',
+ "qualityProfileId": qpid,
+ "tvdbId": parseInt(tvdbId),
+ "monitored": true,
+ "rootFolderPath": rootFolder.path,
+ "addOptions": { "searchForMissingEpisodes": true }
+ };
+ fetch(`${instObj.url}/api/v3/series`, {
+ method: 'POST',
+ headers: {'X-Api-Key': instObj.apiKey, 'Content-Type': 'application/json'},
+ body: JSON.stringify(body)
+ }).then(async resp => {
+ btn.disabled = false;
+ if (resp.status === 201) {
+ toast("✅ Ajouté à " + apiData.name + " !", "success");
+ } else if (resp.status === 400) {
+ let r = await resp.json();
+ if (r && r[0] && r[0].errorMessage && r[0].errorMessage.match(/already exists/i))
+ toast("Déjà présent dans " + apiData.name, "info");
+ else
+ toast("Erreur " + apiData.name + " : " + (r[0]?.errorMessage || resp.statusText), "error");
+ } else {
+ toast("Erreur " + apiData.name + " ("+resp.status+")", "error");
+ }
+ }).catch(e => {
+ btn.disabled = false;
+ toast("Erreur de connexion " + apiData.name, "error");
+ });
+ });
+ }
+ });
+ }).catch(e=>{
+ btn.disabled = false;
+ toast("Erreur de récupération profils/dossiers", "error");
+ });
+ }
+ // Overseerr / Jellyseerr
+ if (kind === "overseerr" || kind === "jellyseerr") {
+ if (group.type === "movie") {
+ fetch(`${instObj.url}/api/v1/request`, {
+ method: 'POST',
+ headers: {
+ 'X-Api-Key': instObj.apiKey,
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({ mediaType: "movie", mediaId: parseInt(group.id) })
+ }).then(async resp => {
+ btn.disabled = false;
+ if (resp.status === 201 || resp.status === 202) {
+ toast("✅ Ajouté à " + apiData.name + " !", "success");
+ } else {
+ let txt = await resp.text();
+ toast("Erreur " + apiData.name + " : " + txt, "error");
+ }
+ }).catch(() => {
+ btn.disabled = false;
+ toast("Erreur de connexion " + apiData.name, "error");
+ });
+ }
+ if (group.type === "tv") {
+ fetchTmdb('tv', group.id).then(data => {
+ let seasons = [];
+ if (data && data.seasons) {
+ seasons = data.seasons
+ .filter(s => s.season_number && s.season_number > 0)
+ .map(s => s.season_number);
+ }
+ if (!seasons.length) {
+ toast("Erreur : aucune saison trouvée pour cette série.", "error");
+ btn.disabled = false;
+ return;
+ }
+ fetch(`${instObj.url}/api/v1/request`, {
+ method: 'POST',
+ headers: {
+ 'X-Api-Key': instObj.apiKey,
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ mediaType: "tv",
+ mediaId: parseInt(group.id),
+ seasons: seasons
+ })
+ }).then(async resp => {
+ btn.disabled = false;
+ if (resp.status === 201 || resp.status === 202) {
+ toast("✅ Ajouté à " + apiData.name + " !", "success");
+ } else {
+ let txt = await resp.text();
+ toast("Erreur " + apiData.name + " : " + txt, "error");
+ }
+ }).catch(() => {
+ btn.disabled = false;
+ toast("Erreur de connexion " + apiData.name, "error");
+ });
+ });
+ }
+ }
+ };
+ cardDiv.appendChild(btn);
+ btnIdx++;
+ };
+
+ if (group.type === 'movie') {
+ if (hasRadarr) btnGen('radarr', inst.radarr, API_INFO.radarr);
+ if (hasRadarr4k) btnGen('radarr4k', inst.radarr4k, API_INFO.radarr4k);
+ if (hasOverseerr) btnGen('overseerr', inst.overseerr, API_INFO.overseerr);
+ if (hasJellyseerr) btnGen('jellyseerr', inst.jellyseerr, API_INFO.jellyseerr);
+ }
+ if (group.type === 'tv') {
+ if (hasSonarr) btnGen('sonarr', inst.sonarr, API_INFO.sonarr);
+ if (hasSonarr4k) btnGen('sonarr4k', inst.sonarr4k, API_INFO.sonarr4k);
+ if (hasOverseerr) btnGen('overseerr', inst.overseerr, API_INFO.overseerr);
+ if (hasJellyseerr) btnGen('jellyseerr', inst.jellyseerr, API_INFO.jellyseerr);
+ }
+
+ // === Badges TMDB/IMDB
+ if (showTmdb()) {
+ fetchTmdb(group.type, group.id).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);
+ const badgeWrap = Object.assign(document.createElement('div'), { style: 'position:absolute;top:7px;left:8px;display:flex;flex-direction:column;gap:3px;z-index:15;pointer-events:none;' });
+ function addBadge(icon, score, votes, url, type='tmdb') {
+ if (!score || score === '?') return;
+ const color = "#111";
+ const el = Object.assign(document.createElement(url ? 'a' : 'span'), {
+ innerHTML: `
+
+ ${getIcon(type)}
+ ${score}
+ ${votes}
+ `,
+ style: 'margin-bottom:5px;margin-right:3px;pointer-events:auto;text-decoration:none;'
+ });
+ if (url) { el.href = url; el.target = '_blank'; el.rel = 'noopener noreferrer'; el.title = 'Voir la fiche'; }
+ badgeWrap.appendChild(el);
}
+ addBadge('tmdb',
+ data.vote_average ? Number(data.vote_average).toFixed(1) : '?',
+ data.vote_count ? ` (${data.vote_count})` : '',
+ `https://www.themoviedb.org/${group.type === 'tv' ? 'tv' : 'movie'}/${group.id}`,
+ 'tmdb');
+ addBadge('imdb',
+ data.note_imdb ? Number(data.note_imdb).toFixed(1) : '?',
+ data.vote_imdb ? ` (${data.vote_imdb})` : '',
+ null,
+ 'imdb');
+ if (badgeWrap.childElementCount) cardDiv.appendChild(badgeWrap);
});
}
- // Clone affiche
- const cloneImg = img.cloneNode(true);
- cloneImg.style.width = `${minwidth}px`;
- cloneImg.style.cursor = 'pointer';
- containerCard.appendChild(cloneImg);
+ // === Image et overlay
+ const imgClone = img0.cloneNode(true);
+ imgClone.style.width = `${minW()}px`;
+ imgClone.style.cursor = 'pointer';
+ cardDiv.appendChild(imgClone);
- // 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 = `
-