// ==UserScript== // @name Chub Character Link Scraper // @namespace https://github.com/GentleBurr/chub-charlink-scraper // @version 1.2.0 // @description Mass-scrape Chub character links automatically // @author GentleBurr // @match *://*.chub.ai/* // @icon data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20100%20100%22%3E%3Crect%20width%3D%22100%22%20height%3D%22100%22%20rx%3D%2220%22%20fill%3D%22%232d004d%22%2F%3E%3Ctext%20x%3D%2250%22%20y%3D%2250%22%20font-size%3D%2250%22%20text-anchor%3D%22middle%22%20dominant-baseline%3D%22middle%22%3E%F0%9F%A7%B2%3C%2Ftext%3E%3C%2Fsvg%3E // @updateURL https://raw.githubusercontent.com/GentleBurr/chub-charlink-scraper/main/scraper.user.js // @downloadURL https://raw.githubusercontent.com/GentleBurr/chub-charlink-scraper/main/scraper.user.js // @grant none // ==/UserScript== (function(){ // Setup core variables and shortcuts var d = document, S = sessionStorage, J = JSON, g = function(i){ return d.getElementById(i); }, c = function(t){ return d.createElement(t); }, o = function(k){ return J.parse(S.getItem(k) || '[]'); }, w = function(k,v){ S.setItem(k, J.stringify(v)); }; // Prevent multiple instances from injecting if(g('c-scr')) return; // Create the main floating UI container var b = c('div'); b.id = 'c-scr'; // CSS Styling with Flexbox armor to prevent overflow and text highlighting var css = ` #c-scr { position:fixed; right:20px; width:auto; box-sizing:border-box; background:#012; color:#eee; padding:12px; border-radius:8px; z-index:99999; box-shadow:0 4px 6px #000; font-family:sans-serif; text-align:center; border:2px solid #70f; touch-action:none; display:flex; flex-direction:column; } .b { padding:8px; border-radius:4px; margin-bottom:8px; font-weight:700; color:#eee; cursor:pointer; transition:all 0.6s ease; -webkit-user-select:none; user-select:none; } .b1 { background:#222; border:1px solid #888; } .b2 { background:#70f; border:none; } .b3 { background:#b22; border:none; } .t { color:#888; font-size:10px; } #c-m { pointer-events:none; -webkit-user-select:none; user-select:none; } .w { position:fixed; top:10%; left:10%; width:80%; height:80%; z-index:99999; background:#012; border:2px solid #70f; padding:10px; border-radius:8px; display:flex; flex-direction:column; box-sizing:border-box; } .ta { flex:1; background:#222; color:#eee; border:1px solid #888; padding:10px; border-radius:4px; font-size:10px; margin-bottom:10px; } .bx { display:flex; gap:5px; } .cr { color:#555; font-size:9px; display:block; margin:-2px 0 -5px; cursor:pointer; text-align:left; } `; var sty = c('style'); sty.innerHTML = css; d.head.appendChild(sty); // Inject HTML structure (Header, Scrollable Body, Symmetrical Controls, Stacked Footer) b.innerHTML = `
Scraper Ready!
`; d.body.appendChild(b); // Guest Detector: Checks if user is logged out to adjust bottom padding dynamically var getB = function() { if (window.innerWidth > window.innerHeight) return '10px'; var links = d.getElementsByTagName('a'); for (var i = 0; i < links.length; i++) { var txt = links[i].innerText.trim().toLowerCase(); if (txt === 'login' || txt === 'sign in') return '20px'; } return '70px'; // Logged-in user nav bar height }; // Dynamic Height calculation to prevent breaking through mobile UI bars var adjH = function(){ b.style.maxHeight = (window.innerHeight - (window.innerWidth > window.innerHeight ? 20 : 80)) + 'px'; }; adjH(); b.style.bottom = getB(); // Drag and Drop State Variables var hd = g('c-hd'), act = false, iX = 0, iY = 0, sX = 0, sY = 0, isD = false, mL = null, mT = null, E = function(e){ return e.touches ? e.touches[0] : e; }; // Drag Start (Records initial tap position) var st = function(e){ if(e.target.id === 'c-tog') return; e.preventDefault(); var v = E(e), R = b.getBoundingClientRect(); iX = v.clientX - R.left; iY = v.clientY - R.top; sX = v.clientX; sY = v.clientY; isD = false; act = true; }; var end = function(){ act = false; }; // Drag Move (Includes 5px deadzone to prevent accidental anchor stripping) var mv = function(e){ if(!act) return; var v = E(e); if(!isD){ if(Math.abs(v.clientX - sX) > 5 || Math.abs(v.clientY - sY) > 5){ isD = true; mL = null; mT = null; } else return; } if(e.cancelable) e.preventDefault(); var nx = v.clientX - iX, ny = v.clientY - iY, iw = window.innerWidth, ih = window.innerHeight, bw = b.offsetWidth, bh = b.offsetHeight; // Window boundaries forcefield if(nx < 4) nx = 4; if(ny < 4) ny = 4; if(nx + bw > iw - 4) nx = iw - bw - 4; if(ny + bh > ih - 4) ny = ih - bh - 4; b.style.left = nx + 'px'; b.style.top = ny + 'px'; b.style.right = 'auto'; b.style.bottom = 'auto'; }; // Attach Event Listeners hd.addEventListener('touchstart', st, {passive:false}); hd.addEventListener('mousedown', st); d.addEventListener('touchend', end); d.addEventListener('mouseup', end); d.addEventListener('touchmove', mv, {passive:false}); d.addEventListener('mousemove', mv); // Window Resize Handler (Catches mobile virtual keyboard) window.addEventListener('resize', function(){ adjH(); if(!b.style.left || b.style.left === 'auto') return; var iw = window.innerWidth, ih = window.innerHeight, R = b.getBoundingClientRect(); if(R.right > iw - 4) b.style.left = Math.max(4, iw - R.width - 4) + 'px'; if(R.bottom > ih - 4) b.style.top = Math.max(4, ih - R.height - 4) + 'px'; if(R.left < 4) b.style.left = '4px'; if(R.top < 4) b.style.top = '4px'; b.style.right = 'auto'; b.style.bottom = 'auto'; mL = null; mT = null; }); // Device Rotation Handler (Snaps UI to correct orientation settings) window.addEventListener('orientationchange', function(){ setTimeout(function(){ adjH(); b.style.left = 'auto'; b.style.top = 'auto'; b.style.bottom = getB(); b.style.right = '20px'; mL = null; mT = null; var R = b.getBoundingClientRect(); if(R.top < 10){ b.style.bottom = 'auto'; b.style.top = '10px'; } // Auto-scroll peek pattern setTimeout(function(){ var x = g('c-bod'), s = g('c-s'); if(x && s) x.scrollTop = Math.max(0, x.scrollHeight - x.clientHeight - s.offsetHeight - 8); }, 50); }, 300); }); // Expand/Collapse Toggle Logic var tog = g('c-tog'), wrap = g('c-wrap'); tog.onclick = function(){ var isE = (wrap.style.display === 'none'), oR = b.getBoundingClientRect(); if(isE){ if(b.style.left && b.style.left !== 'auto'){ mL = b.style.left; mT = b.style.top; } wrap.style.display = 'flex'; tog.innerText = '➖'; b.style.width = '222px'; setTimeout(function(){ var x = g('c-bod'), s = g('c-s'); if(x && s) x.scrollTop = Math.max(0, x.scrollHeight - x.clientHeight - s.offsetHeight - 8); }, 50); } else { wrap.style.display = 'none'; tog.innerText = '➕'; b.style.width = 'auto'; } var nR = b.getBoundingClientRect(), iw = window.innerWidth, ih = window.innerHeight; // Complex anchor recovery system if(b.style.left && b.style.left !== 'auto'){ if(isE){ b.style.left = (parseFloat(b.style.left) + (oR.width - nR.width)) + 'px'; var fR = b.getBoundingClientRect(); if(fR.left < 4) b.style.left = '4px'; else if(fR.right > iw - 4) b.style.left = (iw - fR.width - 4) + 'px'; if(fR.top < 4) b.style.top = '4px'; else if(fR.bottom > ih - 4) b.style.top = (ih - fR.height - 4) + 'px'; } else { if(mL !== null && mT !== null){ b.style.left = mL; b.style.top = mT; } else { b.style.left = (parseFloat(b.style.left) + (oR.width - nR.width)) + 'px'; } } b.style.right = 'auto'; b.style.bottom = 'auto'; } else { if(isE){ if(nR.top < 4){ b.style.bottom = 'auto'; b.style.top = '4px'; } } else { b.style.bottom = getB(); b.style.top = 'auto'; b.style.left = 'auto'; b.style.right = '20px'; } } }; // UI Updater (Fires whenever a creator is added/scraped) var A = g('c-a'), u = function(){ var L = o('c_l'), K = o('c_c'); g('c-t').innerText = L.length; // Dynamically render interactive creator tags var cl = g('c-l'); cl.innerHTML = ''; if(K.length){ for(var i=0; i×'; s.lastChild.onclick = function(){ if(confirm("Remove '" + cr + "' from the active creator list?")){ var nK = o('c_c'), idx = nK.indexOf(cr); if(idx > -1){ nK.splice(idx, 1); w('c_c', nK); u(); } } }; cl.appendChild(s); })(K[i]); } } else { cl.innerText = 'None'; } A.style.background = K.length ? '#222' : '#555'; A.style.border = K.length ? '1px solid #70f' : '1px solid #777'; A.style.opacity = K.length ? '1' : '0.6'; if(b.style.bottom === 'auto'){ var R = b.getBoundingClientRect(), ih = window.innerHeight; if(R.bottom > ih - 4) b.style.top = Math.max(4, ih - R.height - 4) + 'px'; } setTimeout(function(){ var x = g('c-bod'), s = g('c-s'); if(x && s) x.scrollTop = Math.max(0, x.scrollHeight - x.clientHeight - s.offsetHeight - 8); }, 50); }; u(); // Manual 'Add Creator' Button Logic g('c-n').onclick = function(){ var cr = P(); if(!cr) return g('c-m').innerText = "Not a profile!"; var K = o('c_c'); if(K.indexOf(cr) < 0){ K.push(cr); w('c_c', K); g('c-m').innerText = "Added!"; u(); } else { g('c-m').innerText = "Already added!"; } }; // Extract creator name from current URL var P = function(){ var m = location.pathname.match(/\/(users|characters)\/([^\/]+)/i); return m ? m[2].toLowerCase() : null; }; // Mass-Scrape Button Logic (With built-in Tag Engine) A.onclick = function(){ var K = o('c_c'); if(!K.length) return g('c-m').innerText = "Add a creator!"; // Retrieve tag filters from sessionStorage var incStr = (S.getItem('c_ft_inc')||'').toLowerCase().split(',').map(function(s){return s.trim()}).filter(function(s){return s}); var excStr = (S.getItem('c_ft_exc')||'').toLowerCase().split(',').map(function(s){return s.trim()}).filter(function(s){return s}); var L = o('c_l'), a = 0, lk = d.getElementsByTagName('a'); // Scraping execution function var D = function(f, node){ var x = f.toLowerCase(), matchesCreator = false; for(var j=0; j 0 || excStr.length > 0)){ // Rip raw HTML tags from the DOM node var tags = Array.from(node.querySelectorAll('.ant-tag')).map(function(t){ return t.innerText.trim().toLowerCase(); }); // OR Logic: Fails if NONE of the 'Should Include' tags match if(incStr.length > 0){ var hasAny = false; for(var k=0; k 0){ var hasExc = false; for(var k=0; k×'; s.lastChild.onclick = function(){ var curTags = (S.getItem(key)||'').split(',').map(function(x){return x.trim()}).filter(function(x){return x}); var idx = curTags.indexOf(tag); if(idx > -1) { curTags.splice(idx, 1); S.setItem(key, curTags.join(',')); renderTags(container, key); } }; container.appendChild(s); })(tags[i]); } }; // Helper to construct a complete filter section var buildSection = function(lbl, key) { var sec = c('div'); sec.style.marginBottom = '15px'; var l = c('div'); l.style.cssText = "font-size:11px; text-align:left; margin-bottom:4px; color:#ccc;"; l.innerText = lbl; var row = c('div'); row.style.cssText = "display:flex; gap:5px; margin-bottom:5px;"; var i = c('input'); i.type = "text"; i.style.cssText = "flex:1; background:#222; color:#eee; border:1px solid #888; padding:5px; border-radius:4px; font-size:12px;"; var addBtn = c('button'); addBtn.innerText = "➕"; addBtn.className = "b"; addBtn.style.cssText = "margin:0; padding:0 12px; flex-shrink:0; display:flex; align-items:center; justify-content:center; background:#222; border:1px solid #777;"; row.appendChild(i); row.appendChild(addBtn); var tagBox = c('div'); tagBox.style.cssText = "text-align:left; min-height:20px;"; addBtn.onclick = function() { var newTags = i.value.split(',').map(function(x){return x.trim()}).filter(function(x){return x}); if(newTags.length > 0) { var curTags = (S.getItem(key)||'').split(',').map(function(x){return x.trim()}).filter(function(x){return x}); for(var j=0; j