JVC Image Blacklist

Blacklist images sur JVC.

// ==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();
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址