您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Permet de blacklister des images (émojis, stickers, PP). Mettez votre souris sur l'image à blacklister et cliquez sur le ⛔ qui apparaît pour l'utiliser.
// ==UserScript== // @name JVC Image Blacklist // @namespace http://tampermonkey.net/ // @version 1.1 // @description Permet de blacklister des images (émojis, stickers, PP). Mettez votre souris sur l'image à blacklister et cliquez sur le ⛔ qui apparaît pour l'utiliser. // @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']; const DEFAULT_AVATAR = 'https://image.jeuxvideo.com/avatar-md/default.jpg'; // ---------- util ---------- function basenameOf(url) { if (!url) return ''; try { const u = new URL(url, location.href); return (u.pathname.split('/').pop() || '').split('?')[0]; } catch { const p = String(url).split('/'); return (p[p.length - 1] || '').split('?')[0]; } } function normalizeName(name) { if (!name) return ''; let s = decodeURIComponent(String(name)).toLowerCase(); s = s.replace(/(?:thumb|mini(?:s)?|small|med|mask|preview|vignette)[\._-]?/g, ''); s = s.replace(/-\d+x\d+(?:\.\w+)?/g, ''); s = s.replace(/[_\-.]+/g, '-').replace(/^-+|-+$/g, ''); return s; } function loadBlacklist() { try { const data = JSON.parse(localStorage.getItem(STORAGE_KEY)); if (!data) return []; if (typeof data[0] === 'string') return data.map((u) => ({ original: u, display: u, basename: basenameOf(u) })); return data; } catch { return []; } } function saveBlacklist(list) { localStorage.setItem(STORAGE_KEY, JSON.stringify(list)); updateCSS(list); updateToggle(list); } function 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); } function removeFromBlacklist(bn) { saveBlacklist(loadBlacklist().filter((it) => it.basename !== bn)); } // ---------- CSS ---------- function createStyle() { if (!document.getElementById(STYLE_ID)) { const s = document.createElement('style'); s.id = STYLE_ID; document.head.appendChild(s); } // base rule for avatars replaced by attribute (ensures persistence even if src is rewritten) const baseCSS = `.conteneur-message img[data-jvc-avatar-replaced="1"]{ content: url("${DEFAULT_AVATAR}") !important; }`; document.getElementById(STYLE_ID).textContent = baseCSS; } function updateCSS(list) { // we keep the base avatar rule in createStyle; here we append selectors to hide non-avatar images const styleEl = document.getElementById(STYLE_ID); if (!styleEl) return; const base = `.conteneur-message img:not([data-blacklist-preview="true"])`; const selectors = []; for (const it of list) { const bn = normalizeName(basenameOf(it.basename || it.display || it.original || '')); const disp = it.display || ''; const isAvatar = (disp && disp.includes('/avatar')) || (it.basename && it.basename.includes('avatar')); if (!isAvatar && bn) { // we match on data-src, src, alt, risibank-original-src (robust) selectors.push(`${base}[src*="${bn}"]`); selectors.push(`${base}[data-src*="${bn}"]`); selectors.push(`${base}[alt*="${bn}"]`); selectors.push(`${base}[risibank-original-src*="${bn}"]`); } } // keep existing avatar rule + add hide selectors const avatarRule = `.conteneur-message img[data-jvc-avatar-replaced="1"]{ content: url("${DEFAULT_AVATAR}") !important; }`; styleEl.textContent = avatarRule + (selectors.length ? '\n' + selectors.join(',') + ' { display:none !important; pointer-events:none !important; }' : ''); } // ---------- UI ---------- 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', 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) return; t.style.display = list && 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; } for(const it of list){ 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 || ''; Object.assign(img.style,{ maxWidth:'100px', maxHeight:'90px', objectFit:'contain', border:'1px solid #444', background:'#111', padding:'4px', boxSizing:'border-box', pointerEvents:'none' }); 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); } } // ---------- core processing ---------- function getDisp(img){ return img.dataset.src||img.currentSrc||img.getAttribute('data-src')||img.src||''; } function getOrig(img){ return img.dataset.src||img.getAttribute('risibank-original-src')||img.getAttribute('data-src')||img.alt||img.src||''; } function isAvatarCandidate(display, original, img) { return ((display && display.includes('/avatar')) || (original && original.includes('/avatar')) || img.classList.contains('avatar') || img.classList.contains('user-avatar-msg')); } function wrapImage(img) { if (img.closest('.smileys, .smileys__modal, .smileys__table')) return img.parentElement; if (img.parentElement && img.parentElement.dataset && img.parentElement.dataset.jvcWrapper === '1') return img.parentElement; const wrapper = document.createElement('span'); wrapper.style.display='inline-block'; wrapper.style.position='relative'; wrapper.dataset.jvcWrapper='1'; img.parentNode.insertBefore(wrapper, img); wrapper.appendChild(img); return wrapper; } // attach button safely (idempotent) function attachButtonTo(img) { if (img.dataset.jvcBtnAttached === '1') return; 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'; }); btn.addEventListener('click', (e)=>{ e.preventDefault(); e.stopImmediatePropagation(); e.stopPropagation(); const link = img.closest('a'); if(link){ try{ link.removeAttribute('href'); link.onclick = (ev)=>{ ev.preventDefault(); ev.stopImmediatePropagation(); return false; }; }catch(e){} } const display = getDisp(img), original = getOrig(img); const bn = normalizeName(basenameOf(original)) || normalizeName(basenameOf(display)) || ''; addToBlacklist({ original, display, basename: bn }); // immediate visual apply: if (isAvatarCandidate(display, original, img)) { try{ img.dataset.jvcAvatarReplaced = '1'; img.src = DEFAULT_AVATAR; }catch(e){} img.style.pointerEvents = 'none'; } else { img.style.display = 'none'; img.style.pointerEvents = 'none'; } renderPanel(); return false; }, { capture: true }); wrapper.appendChild(btn); img.dataset.jvcBtnAttached = '1'; } // process single image: check blacklist and apply replace/hide or attach button function processImage(img, list) { if (!img || img.closest('.smileys, .smileys__modal, .smileys__table')) return; // do NOT permanently block reprocessing; only avoid rapid double-processing via temp flag if (img.dataset.jvcProcessing === '1') return; img.dataset.jvcProcessing = '1'; setTimeout(()=>{ try{ delete img.dataset.jvcProcessing; }catch(e){} }, 300); const display = getDisp(img) || ''; const original = getOrig(img) || ''; if (EXCEPTIONS.includes(display) || EXCEPTIONS.includes(original)) { return; } // robust check using normalized names const banned = (function(){ const bnDisp = normalizeName(basenameOf(display)); const bnOrig = normalizeName(basenameOf(original)); const set = new Set(list.map(it => normalizeName(it.basename||''))); if (set.has(bnDisp) || set.has(bnOrig)) return true; for (const it of list) { const stored = normalizeName(it.basename || it.display || it.original || ''); if (!stored) continue; if (stored.includes(bnDisp) || stored.includes(bnOrig) || bnDisp.includes(stored) || bnOrig.includes(stored)) return true; } return false; })(); if (banned) { // apply replacement/hide **and mark** so CSS rule can persist display for avatars const isAvatar = isAvatarCandidate(display, original, img); if (isAvatar) { try { img.dataset.jvcAvatarReplaced = '1'; img.src = DEFAULT_AVATAR; } catch(e){} img.style.pointerEvents = 'none'; // remove any button if present try { if (img.parentElement && img.parentElement.querySelector('button')) img.parentElement.querySelectorAll('button').forEach(b=>b.remove()); } catch(e){} } else { img.style.display = 'none'; img.style.pointerEvents = 'none'; try { if (img.parentElement && img.parentElement.querySelector('button')) img.parentElement.querySelectorAll('button').forEach(b=>b.remove()); } catch(e){} } return; } // Not banned -> attach button (idempotent) attachButtonTo(img); } // scan all images in posts function enforceAllOnce() { const list = loadBlacklist(); document.querySelectorAll('.conteneur-message img').forEach(img => processImage(img, list)); } // start observer with guards function startObserver() { const obs = new MutationObserver((mutations) => { const list = loadBlacklist(); for (const m of mutations) { if (m.type === 'childList') { for (const n of m.addedNodes) { if (n.nodeType !== 1) continue; if (n.tagName === 'IMG' && n.closest('.conteneur-message')) processImage(n, list); if (n.querySelectorAll) n.querySelectorAll('.conteneur-message img').forEach(i => processImage(i, list)); } } else if (m.type === 'attributes') { const t = m.target; if (t && t.tagName === 'IMG' && t.closest('.conteneur-message')) processImage(t, list); } } }); obs.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['src','data-src','alt'] }); } // repeated enforcement for first N seconds to catch late lazy loaders / third-party scripts function startPeriodicEnforcement() { let runs = 0; const maxRuns = 12; // e.g., ~12 * 500ms = 6s const id = setInterval(()=>{ try { enforceAllOnce(); } catch(e){} runs++; if (runs >= maxRuns) clearInterval(id); }, 500); } // ---------- bootstrap ---------- function init() { createStyle(); createPanel(); createToggle(); updateCSS(loadBlacklist()); updateToggle(loadBlacklist()); enforceAllOnce(); startObserver(); startPeriodicEnforcement(); } init(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址