您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Skin uploader (URL/file), 512x512, KB cap, local mod-library + inject+save to agar.io account, editor upload button placed inside site editor modal (best-effort). No cheats.
// ==UserScript== // @name Dasgar YT 1.1 // @namespace http://tampermonkey.net/ // @version 1.0 // @description Skin uploader (URL/file), 512x512, KB cap, local mod-library + inject+save to agar.io account, editor upload button placed inside site editor modal (best-effort). No cheats. // @match *://agar.io/* // @match *://*.agar.io/* // @grant GM_addStyle // ==/UserScript== (function () { 'use strict'; /* ---------- Config ---------- */ const MAX_SIZE_BYTES = 512 * 1024; // 512 KB upload UI cap const FINAL_SIZE = 512; // final canvas size 512x512 const LIB_KEY = 'dasgar_mod_skins_v1'; const CACHED_KEY = 'dasgar_cached_skin_v1'; /* ---------- Styles ---------- */ GM_addStyle(` #dasgar_menu { position: fixed; top: 18px; right: 18px; width: 360px; z-index: 2147483647; background: rgba(12,12,14,0.94); color: #eaf8ff; padding: 12px; border-radius: 10px; font-family: Arial, sans-serif; font-size: 13px; box-shadow: 0 10px 40px rgba(0,0,0,0.6) } #dasgar_menu h3 { margin:0 0 8px 0; color:#7ef1c7; } #dasgar_preview { width:120px; height:120px; border-radius:50%; display:block; margin:8px auto; background:#222; object-fit:cover; border:6px solid #ff7700; } #dasgar_menu input[type="text"], #dasgar_menu input[type="number"], #dasgar_menu select { width:100%; padding:7px; border-radius:6px; border:1px solid rgba(255,255,255,0.06); background:rgba(255,255,255,0.02); color:#fff; } #dasgar_menu input[type="file"]{ width:100%; margin-top:6px; } #dasgar_menu button { width:100%; padding:8px; border-radius:8px; border:none; background:#2b8cff; color:#fff; font-weight:700; cursor:pointer; margin-top:8px; } .das-row { display:flex; gap:8px; align-items:center; margin-top:8px; } .das-small { padding:6px 8px; border-radius:8px; border:none; background:#00aaff; color:#fff; cursor:pointer; } .skin-entry { display:flex; gap:8px; align-items:center; padding:6px; margin-top:6px; background: rgba(255,255,255,0.03); border-radius:8px; } .skin-entry img { width:40px; height:40px; border-radius:50%; object-fit:cover; border:2px solid rgba(0,0,0,0.18); } #dasgar_editor_inject { position:absolute; right:18px; bottom:18px; z-index:2147483648; } #dasgar_status { margin-top:6px; color:#cfefff; font-size:12px; text-align:center; } `); /* ---------- UI Build ---------- */ const menu = document.createElement('div'); menu.id = 'dasgar_menu'; menu.innerHTML = ` <h3>Dasgar YT — Skins</h3> <label>Skin URL (direct image)</label> <input type="text" id="dasgar_url" placeholder="https://.../image.png"> <label>Upload image (max 512 KB)</label> <input type="file" id="dasgar_file" accept="image/*"> <canvas id="dasgar_preview" width="120" height="120"></canvas> <div class="das-row"> <input type="color" id="dasgar_bg" value="#ffcc00" style="width:58px; height:36px; border-radius:6px; border:none;"> <input type="color" id="dasgar_border" value="#ff7700" style="width:58px; height:36px; border-radius:6px; border:none;"> <input type="number" id="dasgar_border_w" value="6" min="0" max="40" style="width:70px; padding:6px; border-radius:6px;"> <select id="dasgar_size" style="flex:1; padding:6px; border-radius:6px;"> <option value="512">512 px (final)</option> <option value="256">256 px</option> <option value="128">128 px</option> </select> </div> <button id="dasgar_convert">Convert & Add to Library</button> <button id="dasgar_apply_site" style="background:#32cc6b">Upload → Save to agar.io</button> <h4 style="margin-top:10px">Saved (mod library)</h4> <div id="dasgar_library"></div> <div id="dasgar_status">Load via URL or Upload → Convert → Save to library or save into agar.io.</div> `; document.body.appendChild(menu); // preview canvas const preview = document.getElementById('dasgar_preview'); const pctx = preview.getContext('2d'); const urlInput = document.getElementById('dasgar_url'); const fileInput = document.getElementById('dasgar_file'); const bgInput = document.getElementById('dasgar_bg'); const borderInput = document.getElementById('dasgar_border'); const borderWInput = document.getElementById('dasgar_border_w'); const sizeSelect = document.getElementById('dasgar_size'); const convertBtn = document.getElementById('dasgar_convert'); const applySiteBtn = document.getElementById('dasgar_apply_site'); const libDiv = document.getElementById('dasgar_library'); const statusEl = document.getElementById('dasgar_status'); /* ---------- State ---------- */ let loadedImage = null; // HTMLImageElement or HTMLCanvasElement used for preview let cachedFinal = null; // canvas 512/256/... final result stored // helper: write status function setStatus(t){ statusEl.innerText = t; console.log('[DasgarSkin]', t); } /* ---------- Small helpers ---------- */ function clearPreview(){ pctx.clearRect(0,0,preview.width, preview.height); pctx.fillStyle = '#333'; pctx.fillRect(0,0,preview.width, preview.height); } clearPreview(); function drawPreviewFromImage(img){ const s = preview.width; pctx.clearRect(0,0,s,s); pctx.save(); pctx.beginPath(); pctx.arc(s/2,s/2,s/2,0,Math.PI*2); pctx.closePath(); pctx.clip(); const scale = Math.max(s/img.width, s/img.height); const iw = img.width * scale, ih = img.height * scale; pctx.drawImage(img, s/2 - iw/2, s/2 - ih/2, iw, ih); pctx.restore(); // border const bw = Math.max(0, Math.min(40, parseInt(borderWInput.value) || 0)); if (bw > 0){ pctx.beginPath(); pctx.arc(s/2,s/2, s/2 - bw/2,0,Math.PI*2); pctx.lineWidth = bw; pctx.strokeStyle = borderInput.value || '#ff7700'; pctx.stroke(); } preview.style.background = bgInput.value; } /* ---------- load image from URL helper (tries multiple ways) ---------- */ function loadImageFromURL(url){ return new Promise((resolve,reject) => { if (!url) return reject(new Error('No URL')); // 1) direct load const img = new Image(); img.onload = () => resolve(img); img.onerror = () => { // 2) try with crossOrigin anonymous const img2 = new Image(); img2.crossOrigin = 'anonymous'; img2.onload = () => resolve(img2); img2.onerror = () => { // 3) try fetch → blob (some servers permit) fetch(url).then(r => { if (!r.ok) throw new Error('fetch failed'); return r.blob(); }).then(blob => { const obj = URL.createObjectURL(blob); const img3 = new Image(); img3.onload = () => { URL.revokeObjectURL(obj); resolve(img3); }; img3.onerror = () => { URL.revokeObjectURL(obj); reject(new Error('blob load failed')); }; img3.src = obj; }).catch(err => { reject(err); }); }; img2.src = url; }; img.src = url; }); } /* ---------- file input handling ---------- */ fileInput.addEventListener('change', (e) => { const f = e.target.files && e.target.files[0]; if (!f) return; if (f.size > MAX_SIZE_BYTES){ setStatus('File too large (max ' + Math.round(MAX_SIZE_BYTES/1024) + ' KB).'); return; } setStatus('Loading file for preview...'); const obj = URL.createObjectURL(f); const img = new Image(); img.onload = () => { loadedImage = img; drawPreviewFromImage(img); setStatus('File ready for conversion.'); URL.revokeObjectURL(obj); }; img.onerror = () => { setStatus('File could not be loaded.'); URL.revokeObjectURL(obj); }; img.src = obj; }); /* ---------- URL input handling ---------- */ urlInput.addEventListener('change', async () => { const u = urlInput.value.trim(); if (!u) return; setStatus('Loading image from URL...'); try { const img = await loadImageFromURL(u); loadedImage = img; drawPreviewFromImage(img); setStatus('URL loaded for preview.'); } catch (err) { console.warn(err); setStatus('Failed to load URL (CORS or blocked). Try download & upload file instead.'); } }); /* ---------- convert to final canvas (512/256/128) ---------- */ function makeFinalCanvas(srcImg, size, borderW, borderColor, bgColor){ const c = document.createElement('canvas'); c.width = c.height = size; const ctx = c.getContext('2d'); // fill bg ctx.fillStyle = bgColor || '#fff'; ctx.fillRect(0,0,size,size); // circular clip and draw ctx.save(); ctx.beginPath(); ctx.arc(size/2, size/2, size/2 - 1, 0, Math.PI*2); ctx.closePath(); ctx.clip(); if (srcImg instanceof HTMLCanvasElement) ctx.drawImage(srcImg, 0, 0, size, size); else { const scale = Math.max(size / srcImg.width, size / srcImg.height); const iw = srcImg.width * scale, ih = srcImg.height * scale; ctx.drawImage(srcImg, size/2 - iw/2, size/2 - ih/2, iw, ih); } ctx.restore(); // stroke border inside circle const bw = Math.max(0, Math.min(40, borderW || 0)); if (bw > 0) { ctx.beginPath(); ctx.arc(size/2, size/2, size/2 - bw/2 - 1, 0, Math.PI*2); ctx.lineWidth = bw; ctx.strokeStyle = borderColor || '#ff7700'; ctx.stroke(); } return c; } /* ---------- library handling ---------- */ function saveToLibrary(name, dataURL){ const lib = JSON.parse(localStorage.getItem(LIB_KEY) || '[]'); lib.push({ name: name || ('Skin ' + (lib.length+1)), data: dataURL, ts: Date.now() }); try { localStorage.setItem(LIB_KEY, JSON.stringify(lib)); setStatus('Saved to mod library.'); renderLibrary(); } catch (e) { setStatus('Save failed (storage).'); } } function renderLibrary(){ libDiv.innerHTML = ''; const lib = JSON.parse(localStorage.getItem(LIB_KEY) || '[]'); if (!lib.length) { libDiv.innerHTML = '<div style="color:#bcd7ee;font-size:12px">No saved skins yet.</div>'; return; } lib.forEach((s, idx) => { const row = document.createElement('div'); row.className = 'skin-entry'; row.innerHTML = `<div style="display:flex;align-items:center;gap:8px"><img src="${s.data}"><div style="font-size:12px">${s.name}</div></div> <div style="display:flex;gap:6px"> <button data-idx="${idx}" class="das-row use">Use</button> <button data-idx="${idx}" class="das-row inject" style="background:#32cc6b">Upload→Agar</button> <button data-idx="${idx}" class="das-row del" style="background:#ff4d4d">Del</button> </div>`; libDiv.appendChild(row); }); // attach events libDiv.querySelectorAll('button.use').forEach(b => b.addEventListener('click', e => { const i = +e.currentTarget.dataset.idx; const lib = JSON.parse(localStorage.getItem(LIB_KEY) || '[]'); const item = lib[i]; if (item) { // set preview & cachedFinal loadedImage = new Image(); loadedImage.onload = () => { drawPreviewFromImage(loadedImage); setStatus('Loaded saved skin in preview.'); }; loadedImage.src = item.data; cachedFinal = null; } })); libDiv.querySelectorAll('button.del').forEach(b => b.addEventListener('click', e => { const i = +e.currentTarget.dataset.idx; const lib = JSON.parse(localStorage.getItem(LIB_KEY) || '[]'); lib.splice(i,1); localStorage.setItem(LIB_KEY, JSON.stringify(lib)); renderLibrary(); setStatus('Deleted saved skin.'); })); libDiv.querySelectorAll('button.inject').forEach(b => b.addEventListener('click', async (e) => { const i = +e.currentTarget.dataset.idx; const lib = JSON.parse(localStorage.getItem(LIB_KEY) || '[]'); const item = lib[i]; if (!item) return; // load image element try { const img = new Image(); img.onload = async () => { // create final canvas from saved data (should already be final, but ensure proper size) const finalSize = parseInt(sizeSelect.value,10) || FINAL_SIZE; const canvas = makeFinalCanvas(img, finalSize, parseInt(borderWInput.value,10) || 0, borderInput.value, bgInput.value); cachedFinal = canvas; setStatus('Prepared to inject into site editor. Attempting injection & Save...'); const ok = await injectIntoEditorAndSave(canvas); setStatus(ok ? 'Attempt to save on site done (check site).' : 'Failed to find site editor or Save button.'); }; img.src = item.data; } catch (err) { console.warn(err); setStatus('Error preparing injection.'); } })); } /* ---------- Convert button => convert current preview/loaded image and save to library ---------- */ convertBtn.addEventListener('click', () => { if (!loadedImage) { setStatus('No image to convert — upload or URL first.'); return; } const size = parseInt(sizeSelect.value,10) || FINAL_SIZE; const bw = parseInt(borderWInput.value,10) || 0; const bc = borderInput.value; const bgc = bgInput.value; const finalCanvas = makeFinalCanvas(loadedImage, size, bw, bc, bgc); cachedFinal = finalCanvas; // show final in preview (scaled) const tmp = new Image(); tmp.onload = () => { pctx.clearRect(0,0,preview.width, preview.height); pctx.drawImage(tmp,0,0,preview.width,preview.height); setStatus('Converted final skin (ready).'); }; tmp.src = finalCanvas.toDataURL('image/png'); // Save to library saveToLibrary('Skin ' + (new Date()).toLocaleTimeString(), finalCanvas.toDataURL('image/png')); // cache small saved (<=256) into local quick key try { if (size <= 256) localStorage.setItem(CACHED_KEY, finalCanvas.toDataURL('image/png')); } catch(e){} }); /* ---------- initial render library ---------- */ renderLibrary(); /* ---------- Editor injection: find the editor canvas and container ---------- */ function findEditorCanvas() { const canvases = Array.from(document.querySelectorAll('canvas')).filter(c => { try { const r = c.getBoundingClientRect(); return r.width > 160 && r.height > 160 && r.top > 0 && r.left > 0 && getComputedStyle(c).visibility !== 'hidden'; } catch (e) { return false; } }); if (!canvases.length) return null; // pick canvas closest to center const cx = innerWidth/2, cy = innerHeight/2; canvases.sort((a,b) => { const ra = a.getBoundingClientRect(), rb = b.getBoundingClientRect(); const da = Math.hypot((ra.left+ra.width/2)-cx, (ra.top+ra.height/2)-cy); const db = Math.hypot((rb.left+rb.width/2)-cx, (rb.top+rb.height/2)-cy); return da - db; }); return canvases[0]; } function findEditorContainer(candidateCanvas){ if (!candidateCanvas) return null; let anc = candidateCanvas.parentElement; for (let i=0;i<10 && anc; i++){ const style = getComputedStyle(anc); // heuristics for modal: white-ish background, large width and visible if ((/rgba?\(255,\s*255,\s*255/.test(style.backgroundColor) || style.backgroundColor === 'white' || style.backgroundColor.indexOf('rgb') >= 0) && anc.clientWidth > 300) { return anc; } // also check if ancestor contains a "Save" button text if (anc.querySelector && Array.from(anc.querySelectorAll('button,a,input')).some(el=>/save|upload|create/i.test((el.innerText||el.value||el.title||'').trim()))) { return anc; } anc = anc.parentElement; } // fallback: return the canvas parent return candidateCanvas.parentElement || document.body; } /* ---------- injecting our Upload UI into site editor (bottom-right of editor) ---------- */ let injectedEditorPanel = null; function injectUploadPanelIntoEditor(){ try { const canvas = findEditorCanvas(); if (!canvas) return null; const container = findEditorContainer(canvas); if (!container) return null; // if panel already exists and is inside same container, do nothing if (injectedEditorPanel && injectedEditorPanel.parentElement === container) return injectedEditorPanel; // remove previous if in different place if (injectedEditorPanel && injectedEditorPanel.parentElement) injectedEditorPanel.parentElement.removeChild(injectedEditorPanel); // create panel const panel = document.createElement('div'); panel.id = 'dasgar_editor_inject'; panel.style.position = 'absolute'; panel.style.right = '18px'; panel.style.bottom = '18px'; panel.style.background = 'rgba(0,0,0,0.45)'; panel.style.padding = '8px'; panel.style.borderRadius = '8px'; panel.style.color = '#fff'; panel.style.zIndex = 2147483648; panel.innerHTML = ` <input type="file" id="dasgar_editor_file" accept="image/*" style="width:140px"><br> <button id="dasgar_editor_convert" class="das-small" style="margin-top:6px;padding:6px 10px;background:#2b8cff">Convert & Inject</button> `; // append container.style.position = container.style.position || 'relative'; container.appendChild(panel); injectedEditorPanel = panel; // hookup file input and button const editorFile = panel.querySelector('#dasgar_editor_file'); const editorBtn = panel.querySelector('#dasgar_editor_convert'); editorFile.addEventListener('change', (ev) => { const f = ev.target.files && ev.target.files[0]; if (!f) return; if (f.size > MAX_SIZE_BYTES) { setStatus('Editor upload too large (max ' + Math.round(MAX_SIZE_BYTES/1024) + ' KB).'); return; } const obj = URL.createObjectURL(f); const img = new Image(); img.onload = () => { loadedImage = img; drawPreviewFromImage(img); setStatus('Editor file loaded. Click Convert & Inject to place into editor.'); URL.revokeObjectURL(obj); }; img.onerror = () => { setStatus('Editor file load failed.'); URL.revokeObjectURL(obj); }; img.src = obj; }); editorBtn.addEventListener('click', async () => { if (!loadedImage && !cachedFinal) { setStatus('No image to inject — upload or select from library.'); return; } // make final canvas from loadedImage or cachedFinal const src = loadedImage || (cachedFinal ? (()=>{ const tmp = document.createElement('canvas'); tmp.width = cachedFinal.width; tmp.height = cachedFinal.height; tmp.getContext('2d').drawImage(cachedFinal,0,0); return tmp; })() : null); if (!src) { setStatus('Nothing to inject.'); return; } const size = parseInt(sizeSelect.value,10) || FINAL_SIZE; const finalCanvas = makeFinalCanvas(src, size, parseInt(borderWInput.value,10)||0, borderInput.value, bgInput.value); cachedFinal = finalCanvas; // try injecting to editor canvas const ok = await injectIntoEditorAndSave(finalCanvas); setStatus(ok ? 'Injected & attempted Save on site (check site).' : 'Could not inject / find Save; try opening skin editor.'); }); return panel; } catch (err) { console.warn('injectEditor error', err); return null; } } // observe DOM for canvas/modal changes and inject panel when editor opens const domObserver = new MutationObserver(() => { try { injectUploadPanelIntoEditor(); } catch(e){} }); domObserver.observe(document.documentElement || document.body, { childList: true, subtree: true }); /* ---------- attempt to inject a final canvas into the editor canvas & click Save ---------- */ async function injectIntoEditorAndSave(finalCanvas){ try { const editorCanvas = findEditorCanvas(); if (!editorCanvas) { console.warn('No editor canvas'); return false; } // try to draw into editor canvas context try { const ectx = editorCanvas.getContext('2d'); ectx.save(); // compute destination square for draw const dst = Math.min(editorCanvas.width, editorCanvas.height); const dx = (editorCanvas.width - dst) / 2, dy = (editorCanvas.height - dst) / 2; // clip circle area if possible try { ectx.beginPath(); ectx.arc(editorCanvas.width/2, editorCanvas.height/2, dst/2 - 1, 0, Math.PI*2); ectx.closePath(); ectx.clip(); } catch(e){} ectx.drawImage(finalCanvas, dx, dy, dst, dst); ectx.restore(); setStatus('Injected into the editor canvas.'); } catch (err) { console.warn('draw into editor failed', err); return false; } // find a visible Save button (heuristic) const buttons = Array.from(document.querySelectorAll('button, a, input[type="button"], input[type="submit"]')).filter(el => { try { if (el.offsetParent === null) return false; // not visible const txt = (el.innerText || el.value || el.title || '').trim().toLowerCase(); return /save|upload|create|apply|convert/i.test(txt); } catch(e){ return false; } }); // pick the Save-looking one closest to editorCanvas let saveBtn = null; if (buttons.length) { // score by proximity to editor canvas const rc = editorCanvas.getBoundingClientRect(); let best = Infinity; buttons.forEach(b => { const rb = b.getBoundingClientRect(); const dist = Math.hypot((rb.left+rb.width/2)-(rc.left+rc.width/2), (rb.top+rb.height/2)-(rc.top+rc.height/2)); if (dist < best) { best = dist; saveBtn = b; } }); } if (saveBtn) { // click it programmatically (safest: dispatch MouseEvents) setStatus('Found Save button — clicking to save to account (you must be logged in).'); saveBtn.dispatchEvent(new MouseEvent('mouseover', { bubbles: true, cancelable: true })); await new Promise(r => setTimeout(r, 80)); saveBtn.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true })); await new Promise(r => setTimeout(r, 40)); saveBtn.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, cancelable: true })); saveBtn.click(); return true; } else { setStatus('No Save button found near editor — either site changed UI or editor is not open.'); return false; } } catch (e) { console.warn('inject+save error', e); setStatus('Injection failed: ' + (e.message||e)); return false; } } /* ---------- top-level Apply button tries to inject cachedFinal or ask user steps ---------- */ applySiteBtn.addEventListener('click', async () => { if (!cachedFinal) { if (!loadedImage) { setStatus('No skin prepared — use Upload or URL then Convert first.'); return; } // auto convert to final using chosen size & border const size = parseInt(sizeSelect.value,10) || FINAL_SIZE; cachedFinal = makeFinalCanvas(loadedImage, size, parseInt(borderWInput.value,10) || 0, borderInput.value, bgInput.value); } setStatus('Attempting to inject & save to agar.io (site editor must be open).'); const ok = await injectIntoEditorAndSave(cachedFinal); setStatus(ok ? 'Attempt done — check site/editor and Save dialog.' : 'Could not inject — open the site skin editor and try again.'); }); /* ---------- try restore last cached small skin for preview ---------- */ (function restoreCached(){ try { const s = localStorage.getItem(CACHED_KEY); if (s) { const img = new Image(); img.onload = ()=>{ loadedImage = img; drawPreviewFromImage(img); setStatus('Restored cached small skin.'); }; img.src = s; } } catch(e){} })(); /* ---------- wire color/border live preview (if loadedImage) ---------- */ bgInput.addEventListener('input', ()=>{ if (loadedImage) drawPreviewFromImage(loadedImage); }); borderInput.addEventListener('input', ()=>{ if (loadedImage) drawPreviewFromImage(loadedImage); }); borderWInput.addEventListener('input', ()=>{ if (loadedImage) drawPreviewFromImage(loadedImage); }); /* ---------- initial attempt to inject panel if editor already open ---------- */ setTimeout(injectUploadPanelIntoEditor, 900); // and periodically try (so when user opens editor UI we inject) setInterval(injectUploadPanelIntoEditor, 1500); /* ---------- final console message ---------- */ console.log('[Dasgar YT Skins v1.3] loaded — use URL/file → Convert & Add to Library → Upload→Save to agar.io (editor must be open & you must be signed in).'); })();