// ==UserScript==
// @name JVC Image Blacklist
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Blacklist images sur JVC.
// @match https://www.jeuxvideo.com/*
// @grant none
// @license MIT
// ==/UserScript==
(function () {
'use strict';
const STORAGE_KEY = 'jvcImageBlacklist';
const PANEL_ID = 'jvc-image-blacklist-panel';
const TOGGLE_ID = 'jvc-image-blacklist-toggle';
const STYLE_ID = 'jvc-image-blacklist-style';
const EXCEPTIONS = ['https://risibank.fr/logo.png'];
// ---------- helpers ----------
const cssEscape = (s) => (window.CSS && CSS.escape ? CSS.escape(s) : String(s).replace(/(["\\])/g, '\\$1'));
const basenameOf = (url) => {
if (!url) return '';
try {
const u = new URL(url, location.href);
return (u.pathname.split('/').pop() || '').split('?')[0];
} catch {
// parfois alt contient une URL "brute"
const p = url.split('/'); return (p[p.length - 1] || '').split('?')[0];
}
};
const loadBlacklist = () => {
try {
const data = JSON.parse(localStorage.getItem(STORAGE_KEY));
if (!data) return [];
// rétrocompat
if (typeof data[0] === 'string') {
return data.map((url) => ({ original: url, display: url, basename: basenameOf(url) }));
}
return data;
} catch { return []; }
};
const saveBlacklist = (list) => {
localStorage.setItem(STORAGE_KEY, JSON.stringify(list));
updateCSS(list);
updateToggle(list);
};
const addToBlacklist = ({ original, display, basename }) => {
if (EXCEPTIONS.includes(display) || EXCEPTIONS.includes(original)) return;
const list = loadBlacklist();
if (list.some((it) => it.basename === basename)) return;
list.push({ original, display, basename });
saveBlacklist(list);
};
const removeFromBlacklist = (bn) => {
saveBlacklist(loadBlacklist().filter((it) => it.basename !== bn));
};
// ---------- CSS de masquage (limité aux posts) ----------
function updateCSS(list) {
const styleEl = document.getElementById(STYLE_ID);
if (!list.length) { if (styleEl) styleEl.textContent = ''; return; }
const base = `.conteneur-message img:not([data-blacklist-preview="true"])`;
const selectors = list.flatMap((it) => {
const bn = cssEscape(it.basename);
const disp = cssEscape(it.display);
return [
`${base}[src="${disp}"]`,
`${base}[src*="${bn}"]`,
`${base}[data-src="${disp}"]`,
`${base}[data-src*="${bn}"]`,
`${base}[alt*="${bn}"]`,
`${base}[risibank-original-src*="${bn}"]`,
];
}).join(',');
styleEl.textContent = `${selectors}{display:none !important; pointer-events:none !important;}`;
}
function createStyle() {
if (!document.getElementById(STYLE_ID)) {
const s = document.createElement('style');
s.id = STYLE_ID;
document.head.appendChild(s);
}
}
// ---------- Panneau ----------
function createPanel() {
if (document.getElementById(PANEL_ID)) return;
const panel = document.createElement('div');
panel.id = PANEL_ID;
Object.assign(panel.style, {
position: 'fixed',
top: '160px',
right: '12px',
backgroundColor: '#1e1e1e',
color: '#fff',
padding: '10px',
borderRadius: '8px',
border: '1px solid #333',
maxHeight: '380px',
overflowY: 'auto',
width: '360px',
zIndex: 999999,
fontSize: '13px',
display: 'none',
boxSizing: 'border-box',
});
const title = document.createElement('div');
title.textContent = 'Images blacklistées';
title.style.fontWeight = '700';
title.style.marginBottom = '8px';
const content = document.createElement('div');
content.id = PANEL_ID + '-content';
Object.assign(content.style, {
display: 'grid',
gridTemplateColumns: 'repeat(3, 1fr)',
gap: '10px',
justifyContent: 'flex-start',
boxSizing: 'border-box',
});
panel.appendChild(title);
panel.appendChild(content);
document.body.appendChild(panel);
}
function createToggle() {
if (document.getElementById(TOGGLE_ID)) return;
const btn = document.createElement('button');
btn.id = TOGGLE_ID;
btn.textContent = '🗑 Blacklist';
Object.assign(btn.style, {
position: 'fixed',
bottom: '18px',
right: '18px',
zIndex: 999999,
padding: '8px 12px',
backgroundColor: '#2d2d2d',
color: '#eee',
borderRadius: '8px',
border: '1px solid #444',
cursor: 'pointer',
display: 'none',
});
btn.onclick = () => {
const p = document.getElementById(PANEL_ID);
if (!p) return;
p.style.display = (p.style.display === 'block') ? 'none' : 'block';
renderPanel();
};
document.body.appendChild(btn);
}
function updateToggle(list) {
const t = document.getElementById(TOGGLE_ID);
if (t) t.style.display = list.length ? 'block' : 'none';
}
function renderPanel() {
const content = document.getElementById(PANEL_ID + '-content');
if (!content) return;
const list = loadBlacklist();
content.innerHTML = '';
if (!list.length) { content.textContent = 'Aucune image blacklistée.'; return; }
list.forEach((it) => {
const box = document.createElement('div');
Object.assign(box.style, {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
boxSizing: 'border-box',
});
const img = document.createElement('img');
img.dataset.blacklistPreview = 'true';
img.src = it.display || it.original;
if (img.src.includes('default.jpg') && it.original) img.src = it.original; // avatars
Object.assign(img.style, {
maxWidth: '100px',
maxHeight: '90px',
objectFit: 'contain',
border: '1px solid #444',
background: '#111',
padding: '4px',
boxSizing: 'border-box',
pointerEvents: 'none', // impossible à re-blacklister/bloquer depuis le panneau
});
const rm = document.createElement('button');
rm.textContent = 'Retirer';
Object.assign(rm.style, {
marginTop: '6px',
backgroundColor: '#8a2b2b',
color: '#fff',
border: 'none',
borderRadius: '4px',
padding: '4px 8px',
cursor: 'pointer',
fontSize: '12px',
});
rm.onclick = () => { removeFromBlacklist(it.basename); renderPanel(); };
box.appendChild(img);
box.appendChild(rm);
content.appendChild(box);
});
}
// ---------- extraction d'URL ----------
function getOrig(img) {
return img.dataset.src ||
img.getAttribute('risibank-original-src') ||
img.getAttribute('data-src') ||
img.alt ||
img.src;
}
function getDisp(img) {
return img.dataset.src || img.currentSrc || img.getAttribute('data-src') || img.src;
}
// ---------- bouton & attachement ----------
function wrapImage(img) {
if (img.closest('.smileys, .smileys__modal, .smileys__table')) return img.parentElement; // jamais wrapper le panneau d'émojis
const wrapper = document.createElement('span');
wrapper.style.display = 'inline-block';
wrapper.style.position = 'relative';
img.parentNode.insertBefore(wrapper, img);
wrapper.appendChild(img);
return wrapper;
}
function attachButtons() {
const list = loadBlacklist();
updateCSS(list);
// set de basenames pour lookup rapide
const banned = new Set(list.map((it) => it.basename));
document.querySelectorAll('.conteneur-message img').forEach((img) => {
if (img.dataset.jvcBL) return;
// ignorer images du panneau d'émojis et vignettes de panneau
if (img.closest('.smileys, .smileys__modal, .smileys__table') || img.dataset.blacklistPreview === 'true') {
img.dataset.jvcBL = '1';
return;
}
const display = getDisp(img) || '';
const original = getOrig(img) || '';
if (EXCEPTIONS.includes(display) || EXCEPTIONS.includes(original)) {
img.dataset.jvcBL = '1';
return;
}
const bnOrig = basenameOf(original);
const bnDisp = basenameOf(display);
// --- masquage immédiat (lazy Noelshack / data-src) ---
if (banned.has(bnOrig) || banned.has(bnDisp)) {
img.style.display = 'none'; // masque tout de suite
img.style.pointerEvents = 'none';
img.dataset.jvcBL = '1';
return;
}
// --- sinon on pose le bouton ---
const wrapper = wrapImage(img);
const btn = document.createElement('button');
btn.textContent = '⛔';
Object.assign(btn.style, {
position: 'absolute',
top: '4px',
right: '4px',
backgroundColor: 'rgba(0,0,0,0.55)',
color: '#fff',
border: 'none',
borderRadius: '4px',
fontSize: '12px',
padding: '2px 6px',
cursor: 'pointer',
display: 'none',
zIndex: 9999,
lineHeight: '1',
});
wrapper.addEventListener('mouseenter', () => { btn.style.display = 'block'; });
wrapper.addEventListener('mouseleave', () => { btn.style.display = 'none'; });
// clic sur ⛔ : empêcher toute ouverture + enregistrer
btn.addEventListener('click', (e) => {
e.preventDefault();
e.stopImmediatePropagation();
e.stopPropagation();
const link = img.closest('a');
if (link) {
link.removeAttribute('href');
link.onclick = (ev) => { ev.preventDefault(); return false; };
}
const bn = bnOrig || bnDisp || basenameOf(display || original);
addToBlacklist({ original, display, basename: bn });
renderPanel();
}, { capture: true });
wrapper.appendChild(btn);
img.dataset.jvcBL = '1';
});
}
// ---------- boot ----------
function init() {
createStyle();
createPanel();
createToggle();
updateCSS(loadBlacklist());
updateToggle(loadBlacklist());
attachButtons();
const obs = new MutationObserver(() => attachButtons());
obs.observe(document.body, { childList: true, subtree: true });
}
init();
})();