// ==UserScript==
// @name Twitch Link Hover‑Preview
// @namespace https://chat.openai.com/
// @version 2025‑04‑24f
// @description Shows 320×180 Twitch thumbnails on hover
// @author You
// @match *://8chan.moe/*
// @match *://8chan.se/*
// @run-at document-idle
// @grant GM_xmlhttpRequest
// ==/UserScript==
(() => {
"use strict";
const RE = /^(?:https?:\/\/)?(?:www\.)?twitch\.tv\/([A-Za-z0-9_]+)(?:\/|$)/i;
const URL = s=>`https://static-cdn.jtvnw.net/previews-ttv/live_user_${s}-320x180.jpg`;
const URL4 ="https://static-cdn.jtvnw.net/ttv-static/404_preview-320x180.jpg";
/* fetch url → data:image/jpeg;base64,... */
const toB64 = ab=>btoa(String.fromCharCode(...new Uint8Array(ab)));
const fetchB64=u=>new Promise(r=>{
GM_xmlhttpRequest({method:"GET",url:u,responseType:"arraybuffer",
onload:x=>x.status===200?r(`data:image/jpeg;base64,${toB64(x.response)}`):r(null),
onerror:()=>r(null),ontimeout:()=>r(null)});
});
const cache=new Map(); // streamer → dataURL
async function getImg(s){
if(cache.has(s)) return cache.get(s);
let d=await fetchB64(URL(s)); if(!d) d=await fetchB64(URL4);
cache.set(s,d||""); return d;
}
/* floating img */
const img=document.createElement("img");
img.style.cssText="position:fixed;width:320px;height:180px;object-fit:cover;pointer-events:none;border-radius:4px;box-shadow:0 2px 10px rgba(0,0,0,.4);z-index:2147483647;display:none";
document.body.appendChild(img);
const pos=e=>{
const pad=16,W=320,H=180,{clientX:x,clientY:y}=e;
let l=x+pad,t=y+pad; if(l+W>innerWidth)l=x-W-pad; if(t+H>innerHeight)t=y-H-pad;
img.style.left=l+"px";img.style.top=t+"px";
};
let active=null;
document.addEventListener("mouseover",async e=>{
const a=e.target.closest("a[href]");
const match=a?.href.match(RE); // null if not a twitch link
if(!match){ // ↳ mouse is now over non‑Twitch content
if(active){ active=null; img.style.display="none"; } // hide if needed
return;
}
if(a===active) return; // still on same link
active=a; pos(e); img.style.display="block";
const data=await getImg(match[1].toLowerCase());
if(active===a && data) img.src=data;
});
document.addEventListener("mousemove",e=>{ if(active) pos(e); });
})();