您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Multi-step auto-snap (Cam, Picture, Send, Final). Drag/upload profiles. Delay applies between every step. X = emergency stop.
// ==UserScript== // @name SnapScore Automater // @namespace http://tampermonkey.net/ // @version 3.0 // @description Multi-step auto-snap (Cam, Picture, Send, Final). Drag/upload profiles. Delay applies between every step. X = emergency stop. // @match https://www.snapchat.com/web* // @match https://web.snapchat.com/* // @grant none // @license Unlicense // ==/UserScript== (function(){ 'use strict'; /* ---------- UI ---------- */ const overlay = document.createElement('div'); Object.assign(overlay.style, { position:'fixed', top:'12px', right:'12px', zIndex:2147483647, background:'rgba(0,0,0,0.80)', color:'#fff', padding:'10px', borderRadius:'8px', fontFamily:'Inter, Arial, sans-serif', fontSize:'13px', minWidth:'460px', boxShadow:'0 6px 24px rgba(0,0,0,0.5)' }); overlay.innerHTML = ` <div style="display:flex;justify-content:space-between;align-items:center"> <strong>Snap AutoClick</strong> <div style="display:flex;gap:6px;align-items:center"> <button id="ac-close" title="Remove overlay" style="background:transparent;border:0;color:#fff;cursor:pointer">✕</button> </div> </div> <div style="margin-top:8px;display:flex;gap:6px;flex-wrap:wrap"> <button class="ac-set-btn" data-type="cam">Cam ON</button> <button class="ac-set-btn" data-type="picture">Picture</button> <button class="ac-set-btn" data-type="send">Send</button> <button class="ac-set-btn" data-type="final">Final Send</button> </div> <div style="margin-top:8px;display:flex;gap:8px;align-items:center"> <label style="font-size:12px">Delay (s):</label> <input id="ac-delay" type="text" value="0.2" style="width:70px;padding:4px;border-radius:4px;border:none"/> <button id="ac-start">Start</button> <button id="ac-stop">Stop</button> <button id="ac-clear">Clear All</button> <button id="ac-upload">Upload</button> </div> <div style="margin-top:8px;font-size:12px;color:#ddd">Profiles (drag images here or use Upload):</div> <div id="ac-drop" style="border:1px dashed #888;min-height:80px;padding:6px;margin-top:6px;display:flex;gap:6px;flex-wrap:wrap;align-items:flex-start;overflow:auto;max-height:160px;"></div> <div id="ac-preview" style="margin-top:8px;font-size:12px;color:#9f9;max-height:80px;overflow:auto;border:1px solid #555;padding:6px">Preview: <i>none</i></div> <div id="ac-status" style="margin-top:8px;font-size:12px;color:#9f9">Status: idle</div> <div style="margin-top:8px;font-size:11px;opacity:0.85"> Notes: X = emergency stop. Click a Set button then click the page element to save it. Script ignores left 1/3 of screen. </div> `; document.documentElement.appendChild(overlay); /* ---------- elements ---------- */ const dropEl = document.getElementById('ac-drop'); const startBtn = document.getElementById('ac-start'); const stopBtn = document.getElementById('ac-stop'); const clearBtn = document.getElementById('ac-clear'); const uploadBtn = document.getElementById('ac-upload'); const delayInput = document.getElementById('ac-delay'); const statusEl = document.getElementById('ac-status'); const previewEl = document.getElementById('ac-preview'); /* ---------- state ---------- */ // setButtons: each is { sel: string|null, el: Element|null } const setButtons = { cam:{sel:null,el:null}, picture:{sel:null,el:null}, send:{sel:null,el:null}, final:{sel:null,el:null} }; let captureMode = null; let running = false; let emergency = false; // profiles let profileList = []; // {id, type:'data'|'url', src, name, w?, h?, enabled:true} let profileIdCounter = 1; /* ---------- utilities & safety fixes ---------- */ function buildSelector(el){ if(!el || el.nodeType !== 1) return null; if(el.id) return `#${CSS.escape(el.id)}`; const parts = []; let node = el; while(node && node.nodeType === 1 && node.tagName.toLowerCase() !== 'html'){ let part = node.tagName.toLowerCase(); if(node.className && typeof node.className === 'string'){ const cls = node.className.split(/\s+/).filter(Boolean)[0]; if(cls) part += `.${CSS.escape(cls)}`; } const parent = node.parentElement; if(parent){ const siblings = Array.from(parent.children).filter(ch => ch.tagName === node.tagName); if(siblings.length > 1) part += `:nth-child(${Array.from(parent.children).indexOf(node)+1})`; } parts.unshift(part); node = node.parentElement; if(parts.length > 8) break; } return parts.length ? parts.join(' > ') : null; } function safeQuery(sel){ try { return sel ? document.querySelector(sel) : null; } catch(e){ return null; } } function updateStatus(msg){ if(msg) statusEl.textContent = msg; else statusEl.textContent = running ? `Running (delay ${delayInput.value}s)` : 'Status: idle'; highlightSetButtons(); } function highlightSetButtons(){ document.querySelectorAll('.ac-set-btn').forEach(btn=>{ const t = btn.dataset.type; const s = setButtons[t]; if(s && (s.sel || s.el)){ btn.style.border = '2px solid #0f0'; btn.style.boxShadow = '0 0 6px rgba(0,255,0,0.12)'; } else { btn.style.border = '1px solid #888'; btn.style.boxShadow = 'none'; } }); } function updatePreview(){ previewEl.innerHTML = profileList.length ? ('Profiles: ' + profileList.filter(p=>p.enabled).map(p=>p.name).join(', ') + (profileList.filter(p=>!p.enabled).length? ' <span style="color:#f88">(some disabled)</span>':'') ) : 'Preview: <i>none</i>'; } function minDelayMs(){ return 20; } // minimum 20ms for fast but realistic timing function getDelayMs(){ let d = parseFloat(delayInput.value); if(isNaN(d) || d < 0) d = 0.02; const ms = Math.round(d * 1000); return Math.max(ms, minDelayMs()); } /* ---------- profile UI and drag/drop ---------- */ function makeProfileCard(profile){ const div = document.createElement('div'); Object.assign(div.style, {display:'flex', flexDirection:'column', alignItems:'center', gap:'4px', width:'96px', padding:'6px', background:'rgba(255,255,255,0.03)', borderRadius:'6px'}); const img = document.createElement('img'); img.src = profile.src; img.style.width='48px'; img.style.height='48px'; img.style.objectFit='cover'; img.style.borderRadius='6px'; const label = document.createElement('div'); label.textContent = profile.name; label.style.fontSize='11px'; label.style.width='100%'; label.style.whiteSpace='nowrap'; label.style.overflow='hidden'; label.style.textOverflow='ellipsis'; label.style.color='#fff'; const row = document.createElement('div'); row.style.display='flex'; row.style.gap='6px'; row.style.alignItems='center'; const checkbox = document.createElement('input'); checkbox.type='checkbox'; checkbox.checked = !!profile.enabled; checkbox.addEventListener('change', ()=>{ profile.enabled = checkbox.checked; updatePreview(); }); const del = document.createElement('button'); del.textContent='X'; Object.assign(del.style,{background:'#900', color:'#fff', border:'0', padding:'2px 6px', borderRadius:'6px', cursor:'pointer'}); del.addEventListener('click', ()=>{ profileList = profileList.filter(p=>p.id!==profile.id); renderProfiles(); updatePreview(); }); row.appendChild(checkbox); row.appendChild(del); div.appendChild(img); div.appendChild(label); div.appendChild(row); return div; } function renderProfiles(){ dropEl.innerHTML = ''; profileList.forEach(p => dropEl.appendChild(makeProfileCard(p))); } // drag/drop handlers dropEl.addEventListener('dragover', e => { e.preventDefault(); dropEl.style.borderColor='#fff'; }); dropEl.addEventListener('dragleave', e => { dropEl.style.borderColor='#888'; }); dropEl.addEventListener('drop', e => { e.preventDefault(); dropEl.style.borderColor='#888'; // files if(e.dataTransfer.files && e.dataTransfer.files.length > 0){ Array.from(e.dataTransfer.files).forEach(file => { if(!file.type.startsWith('image/')) return; const reader = new FileReader(); reader.onload = ev => { const src = ev.target.result; const id = profileIdCounter++; const profile = { id, type:'data', src, name: file.name, enabled:true }; const img = new Image(); img.onload = () => { profile.w = img.naturalWidth; profile.h = img.naturalHeight; profileList.push(profile); renderProfiles(); updatePreview(); }; img.src = src; }; reader.readAsDataURL(file); }); } // dragged-from-page URL fallback const url = e.dataTransfer.getData('text/uri-list') || e.dataTransfer.getData('text/plain'); if(url){ const first = url.split('\n')[0].trim(); if(first){ const id = profileIdCounter++; const profile = { id, type:'url', src:first, name: first.split('/').pop()||'dragged-image', enabled:true }; const img = new Image(); img.crossOrigin='anonymous'; img.onload = () => { profile.w = img.naturalWidth; profile.h = img.naturalHeight; profileList.push(profile); renderProfiles(); updatePreview(); }; img.onerror = () => { profileList.push(profile); renderProfiles(); updatePreview(); }; img.src = first; } } }); // explicit Upload button (prevents accidental file opens) uploadBtn.addEventListener('click', () => { const inp = document.createElement('input'); inp.type = 'file'; inp.accept = 'image/*'; inp.multiple = true; inp.onchange = () => { Array.from(inp.files || []).forEach(file => { if(!file.type.startsWith('image/')) return; const reader = new FileReader(); reader.onload = ev => { const src = ev.target.result; const id = profileIdCounter++; const profile = { id, type:'data', src, name:file.name, enabled:true }; const img = new Image(); img.onload = () => { profile.w = img.naturalWidth; profile.h = img.naturalHeight; profileList.push(profile); renderProfiles(); updatePreview(); }; img.src = src; }; reader.readAsDataURL(file); }); }; inp.click(); }); /* ---------- click helpers: ignore left 1/3, find smallest clickable ---------- */ function inRightArea(rect){ return rect.left >= (window.innerWidth / 3); } function findSmallestClickableWithin(el){ if(!el) return null; const candidates = [el].concat(Array.from(el.querySelectorAll('button, a, [role="button"], [onclick], img'))); let best = null; let bestArea = Infinity; candidates.forEach(c => { try{ const r = c.getBoundingClientRect(); if(r.width <= 0 || r.height <= 0) return; if(!inRightArea(r)) return; const area = r.width * r.height; if(area > 0 && area < bestArea) { bestArea = area; best = c; } }catch(e){} }); return best; } function clickElement(el){ if(!el) return false; const target = findSmallestClickableWithin(el) || el; if(!target) return false; const rect = target.getBoundingClientRect(); if(!inRightArea(rect)) return false; const props = { bubbles:true, cancelable:true, view:window, clientX: Math.round(rect.left + rect.width/2), clientY: Math.round(rect.top + rect.height/2) }; try{ target.dispatchEvent(new PointerEvent('pointerdown', props)); target.dispatchEvent(new PointerEvent('pointerup', props)); target.dispatchEvent(new MouseEvent('click', props)); // flash highlight (safe restore) const prev = target.style.boxShadow; try{ target.style.boxShadow = '0 0 0 4px rgba(0,255,0,0.25)'; }catch(e){} setTimeout(()=>{ try{ target.style.boxShadow = prev; }catch(e){} }, 120); return true; }catch(e){ console.warn('click failed', e); return false; } } /* ---------- profile matching ---------- */ function lastSegment(url){ try{ return (new URL(url)).pathname.split('/').filter(Boolean).pop() || url; }catch(e){ return url.split('/').pop() || url; } } function findMatchingThumbnail(profile){ const allImgs = Array.from(document.querySelectorAll('img')); const candidates = allImgs.filter(img=>{ try{ const r = img.getBoundingClientRect(); if(r.width <= 0 || r.height <= 0) return false; if(!inRightArea(r)) return false; if(r.width <= 48 && r.height <= 48) return true; return false; }catch(e){ return false; } }); if(candidates.length === 0) return null; let best = null; let bestScore = Infinity; candidates.forEach(img=>{ try{ const src = (img.currentSrc || img.src || '').toString(); const r = img.getBoundingClientRect(); const area = r.width * r.height; let score = 1000; if(profile.src && src === profile.src) score = 0; else if(profile.src && lastSegment(src) && lastSegment(profile.src) && lastSegment(src) === lastSegment(profile.src)) score = 10; if(profile.w && profile.h){ const w = img.naturalWidth || img.width || r.width; const h = img.naturalHeight || img.height || r.height; score += Math.abs((w||0) - profile.w) + Math.abs((h||0) - profile.h); } score += area / 100; if(profile.type === 'data' && src.startsWith('data:')) score -= 5; if(score < bestScore){ bestScore = score; best = img; } }catch(e){} }); return best; } /* ---------- capture logic for set buttons ---------- */ document.querySelectorAll('.ac-set-btn').forEach(btn => { btn.addEventListener('click', () => { captureMode = btn.dataset.type; updateStatus(`Capture: click page element for "${captureMode}"`); const handler = e => { if(e.composedPath && e.composedPath().includes(overlay)) return; e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); const el = e.target; const sel = buildSelector(el); setButtons[captureMode] = { sel, el }; captureMode = null; document.removeEventListener('click', handler, true); updateStatus(); }; document.addEventListener('click', handler, true); }); }); /* ---------- runner (async, respects delay for every step) ---------- */ async function runSequenceOnce(delayMs){ // 1) main buttons for(const k of ['cam','picture','send']){ if(emergency) return false; const s = setButtons[k]; const el = s && (s.sel ? safeQuery(s.sel) : s.el) || null; if(el) clickElement(el); await sleep(delayMs); } // 2) profiles in order for(const p of profileList){ if(emergency) return false; if(!p.enabled) continue; const match = findMatchingThumbnail(p); if(match) clickElement(match); await sleep(delayMs); } // 3) final if(setButtons.final && (setButtons.final.sel || setButtons.final.el)){ const sf = setButtons.final; const fe = sf.sel ? safeQuery(sf.sel) : sf.el; if(fe) clickElement(fe); await sleep(delayMs); } return true; } function sleep(ms){ return new Promise(res => setTimeout(res, ms)); } async function startRunner(){ if(running) return; running = true; emergency = false; updateStatus(); const delayMs = getDelayMs(); // loop until stopped or emergency while(running && !emergency){ const ok = await runSequenceOnce(delayMs); if(!ok) break; // short micro-yield between sequences to avoid lock, but main pacing is delayMs inside await sleep(2); } running = false; updateStatus(); } function stopRunner(){ running = false; } /* ---------- UI wiring ---------- */ startBtn.addEventListener('click', () => { const anyBtn = Object.values(setButtons).some(s => s && (s.sel || s.el)); if(!anyBtn && profileList.length===0){ updateStatus('Set a button or add profiles first'); setTimeout(()=>updateStatus(), 1200); return; } startRunner(); }); stopBtn.addEventListener('click', ()=>{ emergency = true; stopRunner(); updateStatus('Stopped'); setTimeout(()=>updateStatus(), 400); }); clearBtn.addEventListener('click', ()=>{ Object.keys(setButtons).forEach(k => setButtons[k] = { sel:null, el:null }); profileList = []; profileIdCounter = 1; renderProfiles(); updatePreview(); updateStatus('Cleared'); setTimeout(()=>updateStatus(), 800); }); document.getElementById('ac-close').addEventListener('click', ()=>{ emergency=true; stopRunner(); overlay.remove(); }); document.addEventListener('keydown', e => { if(e.key && e.key.toLowerCase() === 'x'){ emergency = true; stopRunner(); updateStatus('EMERGENCY STOP'); setTimeout(()=>{ emergency=false; updateStatus(); }, 700); } }); /* ---------- init ---------- */ renderProfiles(); updatePreview(); updateStatus(); // debugging handle window.__SnapAutoClick = { startRunner, stopRunner, profileList, setButtons }; })();