您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Manage multiple sharable overlays (with import/export), render them behind tiles on wplace.live.
当前为
// ==UserScript== // @name Wplace Overlay Pro // @namespace http://tampermonkey.net/ // @version 2.0.0 // @description Manage multiple sharable overlays (with import/export), render them behind tiles on wplace.live. // @author shinkonet // @match https://wplace.live/* // @license MIT // @grant GM_setValue // @grant GM_getValue // @grant GM.setValue // @grant GM.getValue // @grant GM_xmlhttpRequest // @grant unsafeWindow // @connect * // @run-at document-start // ==/UserScript== (function () { 'use strict'; // ---------- Constants ---------- const TILE_SIZE = 1000; // ---------- GM helpers (support GM.* and GM_*) ---------- const gmGet = (key, def) => { try { if (typeof GM !== 'undefined' && typeof GM.getValue === 'function') return GM.getValue(key, def); if (typeof GM_getValue === 'function') return Promise.resolve(GM_getValue(key, def)); } catch {} return Promise.resolve(def); }; const gmSet = (key, value) => { try { if (typeof GM !== 'undefined' && typeof GM.setValue === 'function') return GM.setValue(key, value); if (typeof GM_setValue === 'function') return Promise.resolve(GM_setValue(key, value)); } catch {} return Promise.resolve(); }; // Cross-origin blob fetch via GM_xmlhttpRequest function gmFetchBlob(url) { return new Promise((resolve, reject) => { try { GM_xmlhttpRequest({ method: 'GET', url, responseType: 'blob', onload: (res) => { if (res.status >= 200 && res.status < 300 && res.response) { resolve(res.response); } else { reject(new Error(`GM_xhr failed: ${res.status} ${res.statusText}`)); } }, onerror: (e) => reject(new Error('GM_xhr network error')), ontimeout: () => reject(new Error('GM_xhr timeout')), }); } catch (e) { reject(e); } }); } function blobToDataURL(blob) { return new Promise((resolve, reject) => { const fr = new FileReader(); fr.onload = () => resolve(fr.result); fr.onerror = reject; fr.readAsDataURL(blob); }); } async function urlToDataURL(url) { const blob = await gmFetchBlob(url); if (!blob || !String(blob.type).startsWith('image/')) { throw new Error('URL did not return an image blob'); } return await blobToDataURL(blob); } const config = { overlays: [], activeOverlayId: null, overlayMode: 'overlay', isPanelCollapsed: false, autoCapturePixelUrl: false }; const CONFIG_KEYS = Object.keys(config); async function loadConfig() { try { await Promise.all(CONFIG_KEYS.map(async k => { config[k] = await gmGet(k, config[k]); })); } catch (e) { console.error("Overlay Pro: Failed to load config", e); } } async function saveConfig(keys = CONFIG_KEYS) { try { await Promise.all(keys.map(k => gmSet(k, config[k]))); } catch (e) { console.error("Overlay Pro: Failed to save config", e); } } const page = unsafeWindow; function uid() { return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`; } function createCanvas(w, h) { if (typeof OffscreenCanvas !== 'undefined') return new OffscreenCanvas(w, h); const c = document.createElement('canvas'); c.width = w; c.height = h; return c; } function canvasToBlob(canvas) { if (canvas.convertToBlob) return canvas.convertToBlob(); // OffscreenCanvas return new Promise((resolve, reject) => canvas.toBlob(b => b ? resolve(b) : reject(new Error("toBlob failed")), "image/png")); } async function blobToImage(blob) { if (typeof createImageBitmap === 'function') { try { return await createImageBitmap(blob); } catch {/* fallback below */} } return new Promise((resolve, reject) => { const url = URL.createObjectURL(blob); const img = new Image(); img.onload = () => { URL.revokeObjectURL(url); resolve(img); }; img.onerror = (e) => { URL.revokeObjectURL(url); reject(e); }; img.src = url; }); } function loadImage(src) { return new Promise((resolve, reject) => { const img = new Image(); img.crossOrigin = "anonymous"; img.onload = () => resolve(img); img.onerror = reject; img.src = src; }); } function extractPixelCoords(pixelUrl) { try { const u = new URL(pixelUrl); const parts = u.pathname.split('/'); const sp = new URLSearchParams(u.search); return { chunk1: parseInt(parts[3], 10), chunk2: parseInt(parts[4], 10), posX: parseInt(sp.get('x') || '0', 10), posY: parseInt(sp.get('y') || '0', 10), }; } catch { return { chunk1: 0, chunk2: 0, posX: 0, posY: 0 }; } } function matchTileUrl(urlStr) { try { const u = new URL(urlStr, location.href); if (u.hostname !== 'backend.wplace.live' || !u.pathname.startsWith('/files/')) return null; const m = u.pathname.match(/\/(\d+)\/(\d+)\.png$/i); if (!m) return null; return { chunk1: parseInt(m[1], 10), chunk2: parseInt(m[2], 10) }; } catch { return null; } } function matchPixelUrl(urlStr) { try { const u = new URL(urlStr, location.href); if (u.hostname !== 'backend.wplace.live') return null; const m = u.pathname.match(/\/s0\/pixel\/(\d+)\/(\d+)$/); if (!m) return null; const sp = u.searchParams; return { normalized: `https://backend.wplace.live/s0/pixel/${m[1]}/${m[2]}?x=${sp.get('x')||0}&y=${sp.get('y')||0}` }; } catch { return null; } } function rectIntersect(ax, ay, aw, ah, bx, by, bw, bh) { const x = Math.max(ax, bx); const y = Math.max(ay, by); const r = Math.min(ax + aw, bx + bw); const b = Math.min(ay + ah, by + bh); const w = Math.max(0, r - x); const h = Math.max(0, b - y); return { x, y, w, h }; } const overlayCache = new Map(); function overlaySignature(ov) { const imgKey = ov.imageBase64 ? ov.imageBase64.slice(0, 64) + ':' + ov.imageBase64.length : 'none'; return [imgKey, ov.pixelUrl || 'null', ov.offsetX, ov.offsetY, ov.opacity].join('|'); } function clearOverlayCache() { overlayCache.clear(); } async function buildOverlayDataForChunk(ov, targetChunk1, targetChunk2) { if (!ov.enabled || !ov.imageBase64 || !ov.pixelUrl) return null; const sig = overlaySignature(ov); const cacheKey = `${ov.id}|${sig}|${targetChunk1}|${targetChunk2}`; if (overlayCache.has(cacheKey)) return overlayCache.get(cacheKey); const img = await loadImage(ov.imageBase64); if (!img) return null; const base = extractPixelCoords(ov.pixelUrl); if (!Number.isFinite(base.chunk1) || !Number.isFinite(base.chunk2)) return null; const drawX = (base.chunk1 * TILE_SIZE + base.posX + ov.offsetX) - (targetChunk1 * TILE_SIZE); const drawY = (base.chunk2 * TILE_SIZE + base.posY + ov.offsetY) - (targetChunk2 * TILE_SIZE); const isect = rectIntersect(0, 0, TILE_SIZE, TILE_SIZE, drawX, drawY, img.width, img.height); if (isect.w === 0 || isect.h === 0) { overlayCache.set(cacheKey, null); return null; } const canvas = createCanvas(TILE_SIZE, TILE_SIZE); const ctx = canvas.getContext('2d'); ctx.drawImage(img, drawX, drawY); const imageData = ctx.getImageData(isect.x, isect.y, isect.w, isect.h); const data = imageData.data; const colorStrength = ov.opacity; const whiteStrength = 1 - colorStrength; for (let i = 0; i < data.length; i += 4) { if (data[i + 3] > 0) { data[i ] = Math.round(data[i ] * colorStrength + 255 * whiteStrength); data[i + 1] = Math.round(data[i + 1] * colorStrength + 255 * whiteStrength); data[i + 2] = Math.round(data[i + 2] * colorStrength + 255 * whiteStrength); data[i + 3] = 255; } } const result = { imageData, dx: isect.x, dy: isect.y }; overlayCache.set(cacheKey, result); return result; } async function mergeOverlaysBehind(originalBlob, overlayDatas) { if (!overlayDatas || overlayDatas.length === 0) return originalBlob; const originalImage = await blobToImage(originalBlob); const w = originalImage.width; const h = originalImage.height; const canvas = createCanvas(w, h); const ctx = canvas.getContext('2d'); for (const ovd of overlayDatas) { if (!ovd) continue; ctx.putImageData(ovd.imageData, ovd.dx, ovd.dy); } ctx.drawImage(originalImage, 0, 0); return await canvasToBlob(canvas); } function hookFetch() { const originalFetch = page.fetch; if (!originalFetch || originalFetch.__overlayHooked) return; const hookedFetch = async (input, init) => { const urlStr = typeof input === 'string' ? input : (input && input.url) || ''; if (config.autoCapturePixelUrl && config.activeOverlayId) { const pixelMatch = matchPixelUrl(urlStr); if (pixelMatch) { const ov = config.overlays.find(o => o.id === config.activeOverlayId); if (ov) { if (ov.pixelUrl !== pixelMatch.normalized) { ov.pixelUrl = pixelMatch.normalized; await saveConfig(['overlays']); clearOverlayCache(); updateUI(); } } } } const tileMatch = matchTileUrl(urlStr); if (!tileMatch || config.overlayMode !== 'overlay') { return originalFetch(input, init); } try { const response = await originalFetch(input, init); if (!response.ok) return response; const enabledOverlays = config.overlays.filter(o => o.enabled && o.imageBase64 && o.pixelUrl); if (enabledOverlays.length === 0) return response; const originalBlob = await response.blob(); const overlayDatas = []; for (const ov of enabledOverlays) { overlayDatas.push(await buildOverlayDataForChunk(ov, tileMatch.chunk1, tileMatch.chunk2)); } const mergedBlob = await mergeOverlaysBehind(originalBlob, overlayDatas.filter(Boolean)); const headers = new Headers(response.headers); headers.set('Content-Type', 'image/png'); headers.delete('Content-Length'); return new Response(mergedBlob, { status: response.status, statusText: response.statusText, headers }); } catch (e) { console.error("Overlay Pro: Error processing tile", e); return originalFetch(input, init); } }; hookedFetch.__overlayHooked = true; page.fetch = hookedFetch; window.fetch = hookedFetch; console.log('Overlay Pro: Fetch hook installed.'); } function injectStyles() { const style = document.createElement('style'); style.textContent = ` #overlay-pro-panel { position: fixed; top: 230px; right: 15px; z-index: 10001; background: rgba(20,20,20,0.9); backdrop-filter: blur(8px); border: 1px solid #444; border-radius: 8px; color: white; font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; font-size: 14px; width: 320px; } .op-header { padding: 8px 12px; background: #333; cursor: pointer; display: flex; justify-content: space-between; align-items: center; border-top-left-radius: 8px; border-top-right-radius: 8px; } .op-header h3 { margin: 0; font-size: 16px; user-select: none; } .op-toggle-btn { font-size: 20px; background: none; border: none; color: white; cursor: pointer; padding: 0 5px; } .op-content { padding: 12px; display: flex; flex-direction: column; gap: 12px; } .op-section { display: flex; flex-direction: column; gap: 8px; } .op-row { display: flex; align-items: center; gap: 8px; } .op-button { background: #555; color: white; border: 1px solid #777; border-radius: 4px; padding: 6px 10px; cursor: pointer; } .op-button:hover { background: #666; } .op-button.danger { background: #7a2b2b; border-color: #a34242; } .op-input, .op-select { background: #222; border: 1px solid #555; color: white; border-radius: 4px; padding: 5px; } .op-input[type="number"] { width: 80px; } .op-input[type="text"] { width: 100%; } .op-slider { width: 100%; } .op-list { display: flex; flex-direction: column; gap: 6px; max-height: 200px; overflow: auto; border: 1px solid #444; padding: 6px; border-radius: 6px; background: #1a1a1a; } .op-item { display: flex; align-items: center; gap: 6px; padding: 4px; border-radius: 4px; } .op-item.active { background: #2a2a2a; } .op-item-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .op-grow { flex: 1; } .op-muted { color: #bbb; font-size: 12px; } .op-preview { width: 100%; height: 80px; background: #111; display: flex; align-items: center; justify-content: center; border: 1px dashed #444; border-radius: 4px; overflow: hidden; } .op-preview img { max-width: 100%; max-height: 100%; display: block; } `; document.head.appendChild(style); } function createUI() { if (document.getElementById('overlay-pro-panel')) return; const panel = document.createElement('div'); panel.id = 'overlay-pro-panel'; panel.innerHTML = ` <div class="op-header" id="op-header"> <h3>Overlay Pro</h3> <button class="op-toggle-btn" id="op-panel-toggle">▶</button> </div> <div class="op-content" id="op-content"> <div class="op-section"> <div class="op-row" style="justify-content: space-between;"> <div> <button class="op-button" id="op-mode-toggle">Mode</button> </div> <div class="op-row"> <label class="op-muted">Place overlay:</label> <button class="op-button" id="op-autocap-toggle">OFF</button> </div> </div> </div> <div class="op-section"> <div class="op-row" style="justify-content: space-between;"> <strong>Overlays</strong> <div class="op-row"> <button class="op-button" id="op-add-overlay">+ Add</button> <button class="op-button" id="op-import-overlay">Import</button> </div> </div> <div class="op-list" id="op-overlay-list"></div> <div class="op-row" style="justify-content: flex-end; gap: 6px;"> <button class="op-button" id="op-export-overlay">Export</button> <button class="op-button danger" id="op-delete-overlay">Delete</button> </div> </div> <div class="op-section" id="op-editor-section"> <strong>Editor</strong> <div class="op-row"><span class="op-muted">Active overlay fields</span></div> <div class="op-row"> <label style="width: 90px;">Name</label> <input type="text" class="op-input op-grow" id="op-name"> </div> <div class="op-row"> <label style="width: 90px;">PNG URL</label> <input type="text" class="op-input op-grow" id="op-image-url" placeholder="https://files.catbox.moe/....png"> <button class="op-button" id="op-load-image">Load</button> </div> <div class="op-preview"><img id="op-image-preview" alt="No image"></div> <div class="op-row"> <label style="width: 90px;">Pixel URL</label> <input type="text" class="op-input op-grow" id="op-pixel-url" placeholder="Place overlay!"> </div> <div class="op-row"><span class="op-muted" id="op-coord-display"></span></div> <div class="op-row" style="gap: 5px;"> <div class="op-row"> <span>X</span> <input type="number" class="op-input" style="width: 55px;" id="op-offset-x"> <button class="op-button" data-offset="x" data-amount="-1">-</button> <button class="op-button" data-offset="x" data-amount="1">+</button> </div> <div class="op-row"> <span>Y</span> <input type="number" class="op-input" style="width: 55px;" id="op-offset-y"> <button class="op-button" data-offset="y" data-amount="-1">-</button> <button class="op-button" data-offset="y" data-amount="1">+</button> </div> </div> <div class="op-row" style="width: 100%; gap: 12px; padding: 10px;"> <label style="width: 40px;">Opacity</label> <input type="range" min="0" max="1" step="0.05" class="op-slider op-grow" id="op-opacity-slider"> <span id="op-opacity-value" style="width: 25px; text-align: right;">70%</span> </div> </div> <div class="op-section"> <button class="op-button" id="op-reload-btn">Reload Tiles (Refresh Page)</button> </div> </div> `; document.body.appendChild(panel); addEventListeners(); updateUI(); } function getActiveOverlay() { return config.overlays.find(o => o.id === config.activeOverlayId) || null; } function rebuildOverlayListUI() { const list = document.getElementById('op-overlay-list'); if (!list) return; list.innerHTML = ''; for (const ov of config.overlays) { const item = document.createElement('div'); item.className = 'op-item' + (ov.id === config.activeOverlayId ? ' active' : ''); item.innerHTML = ` <input type="radio" name="op-active" ${ov.id === config.activeOverlayId ? 'checked' : ''} /> <input type="checkbox" ${ov.enabled ? 'checked' : ''} title="Toggle enabled" /> <div class="op-item-name" title="${ov.name || '(unnamed)'}">${ov.name || '(unnamed)'}</div> `; const [radio, checkbox, nameDiv] = item.children; radio.addEventListener('change', () => { config.activeOverlayId = ov.id; saveConfig(['activeOverlayId']); updateUI(); }); checkbox.addEventListener('change', () => { ov.enabled = checkbox.checked; saveConfig(['overlays']); clearOverlayCache(); }); nameDiv.addEventListener('click', () => { config.activeOverlayId = ov.id; saveConfig(['activeOverlayId']); updateUI(); }); list.appendChild(item); } } async function addOverlayFromUrl(url, name = '') { if (!/\.png(\?|$)/i.test(url)) { if (!confirm("The URL does not look like a .png. Try anyway?")) return null; } const base64 = await urlToDataURL(url); const ov = { id: uid(), name: name || 'Overlay', enabled: true, imageUrl: url, imageBase64: base64, pixelUrl: null, offsetX: 0, offsetY: 0, opacity: 0.7 }; config.overlays.push(ov); config.activeOverlayId = ov.id; await saveConfig(['overlays', 'activeOverlayId']); clearOverlayCache(); updateUI(); return ov; } async function importOverlayFromJSON(jsonText) { let obj; try { obj = JSON.parse(jsonText); } catch { alert('Invalid JSON'); return; } const arr = Array.isArray(obj) ? obj : [obj]; let imported = 0, failed = 0; for (const item of arr) { const name = item.name || 'Imported Overlay'; const imageUrl = item.imageUrl; const pixelUrl = item.pixelUrl ?? null; const offsetX = Number.isFinite(item.offsetX) ? item.offsetX : 0; const offsetY = Number.isFinite(item.offsetY) ? item.offsetY : 0; const opacity = Number.isFinite(item.opacity) ? item.opacity : 0.7; if (!imageUrl) { failed++; continue; } try { const base64 = await urlToDataURL(imageUrl); const ov = { id: uid(), name, enabled: true, imageUrl, imageBase64: base64, pixelUrl, offsetX, offsetY, opacity }; config.overlays.push(ov); imported++; } catch (e) { console.error('Import failed for', imageUrl, e); failed++; } } if (imported > 0) { config.activeOverlayId = config.overlays[config.overlays.length - 1].id; await saveConfig(['overlays', 'activeOverlayId']); clearOverlayCache(); updateUI(); } alert(`Import finished. Imported: ${imported}${failed ? `, Failed: ${failed}` : ''}`); } function exportActiveOverlayToClipboard() { const ov = getActiveOverlay(); if (!ov) { alert('No active overlay selected.'); return; } if (!ov.imageUrl) { alert('This overlay has no image URL. Set a direct PNG URL to export a compact JSON.'); return; } const payload = { version: 1, name: ov.name, imageUrl: ov.imageUrl, pixelUrl: ov.pixelUrl ?? null, offsetX: ov.offsetX, offsetY: ov.offsetY, opacity: ov.opacity }; const text = JSON.stringify(payload, null, 2); copyText(text).then(() => alert('Overlay JSON copied to clipboard!')).catch(() => { prompt('Copy the JSON below:', text); }); } function copyText(text) { if (navigator.clipboard && navigator.clipboard.writeText) { return navigator.clipboard.writeText(text); } return Promise.reject(new Error('Clipboard API not available')); } function addEventListeners() { const $ = (id) => document.getElementById(id); $('op-header').addEventListener('click', () => { config.isPanelCollapsed = !config.isPanelCollapsed; saveConfig(['isPanelCollapsed']); updateUI(); }); $('op-mode-toggle').addEventListener('click', () => { config.overlayMode = config.overlayMode === 'overlay' ? 'original' : 'overlay'; saveConfig(['overlayMode']); updateUI(); }); $('op-autocap-toggle').addEventListener('click', () => { config.autoCapturePixelUrl = !config.autoCapturePixelUrl; saveConfig(['autoCapturePixelUrl']); updateUI(); }); $('op-add-overlay').addEventListener('click', async () => { const url = prompt('Enter direct PNG URL (e.g., https://files.catbox.moe/....png):'); if (!url) return; const name = prompt('Enter a name for this overlay:', 'Overlay'); try { await addOverlayFromUrl(url.trim(), (name || '').trim()); } catch (e) { console.error(e); alert('Failed to add overlay from URL. See console for details.'); } }); $('op-import-overlay').addEventListener('click', async () => { const text = prompt('Paste overlay JSON (single or array):'); if (!text) return; await importOverlayFromJSON(text); }); $('op-export-overlay').addEventListener('click', () => exportActiveOverlayToClipboard()); $('op-delete-overlay').addEventListener('click', async () => { const ov = getActiveOverlay(); if (!ov) { alert('No active overlay selected.'); return; } if (!confirm(`Delete overlay "${ov.name}"?`)) return; const idx = config.overlays.findIndex(o => o.id === ov.id); if (idx >= 0) { config.overlays.splice(idx, 1); if (config.activeOverlayId === ov.id) { config.activeOverlayId = config.overlays[0]?.id || null; } await saveConfig(['overlays', 'activeOverlayId']); clearOverlayCache(); updateUI(); } }); $('op-load-image').addEventListener('click', async () => { const ov = getActiveOverlay(); if (!ov) { alert('No active overlay selected.'); return; } const url = $('op-image-url').value.trim(); if (!url) { alert('Enter a PNG URL first.'); return; } try { const base64 = await urlToDataURL(url); ov.imageUrl = url; ov.imageBase64 = base64; await saveConfig(['overlays']); clearOverlayCache(); updateUI(); } catch (e) { console.error(e); alert('Failed to load image from URL. Check the link or try a direct PNG.'); } }); $('op-name').addEventListener('change', async (e) => { const ov = getActiveOverlay(); if (!ov) return; ov.name = e.target.value; await saveConfig(['overlays']); rebuildOverlayListUI(); }); $('op-image-url').addEventListener('change', (e) => { }); $('op-pixel-url').addEventListener('change', async (e) => { const ov = getActiveOverlay(); if (!ov) return; const v = e.target.value.trim() || null; ov.pixelUrl = v; await saveConfig(['overlays']); clearOverlayCache(); updateUI(); }); document.querySelectorAll('[data-offset]').forEach(btn => btn.addEventListener('click', async () => { const ov = getActiveOverlay(); if (!ov) return; const offset = btn.dataset.offset; const amount = parseInt(btn.dataset.amount, 10); if (offset === 'x') ov.offsetX += amount; if (offset === 'y') ov.offsetY += amount; await saveConfig(['overlays']); clearOverlayCache(); updateUI(); })); $('op-offset-x').addEventListener('change', async (e) => { const ov = getActiveOverlay(); if (!ov) return; const v = parseInt(e.target.value, 10); ov.offsetX = Number.isFinite(v) ? v : 0; await saveConfig(['overlays']); clearOverlayCache(); updateUI(); }); $('op-offset-y').addEventListener('change', async (e) => { const ov = getActiveOverlay(); if (!ov) return; const v = parseInt(e.target.value, 10); ov.offsetY = Number.isFinite(v) ? v : 0; await saveConfig(['overlays']); clearOverlayCache(); updateUI(); }); $('op-opacity-slider').addEventListener('input', (e) => { const ov = getActiveOverlay(); if (!ov) return; ov.opacity = parseFloat(e.target.value); document.getElementById('op-opacity-value').textContent = Math.round(ov.opacity * 100) + '%'; }); $('op-opacity-slider').addEventListener('change', async () => { await saveConfig(['overlays']); clearOverlayCache(); }); $('op-reload-btn').addEventListener('click', () => location.reload()); } function updateEditorUI() { const $ = (id) => document.getElementById(id); const ov = getActiveOverlay(); const editor = $('op-editor-section'); editor.style.display = ov ? 'block' : 'none'; if (!ov) return; $('op-name').value = ov.name || ''; $('op-image-url').value = ov.imageUrl || ''; $('op-pixel-url').value = ov.pixelUrl || ''; const preview = $('op-image-preview'); if (ov.imageBase64) { preview.src = ov.imageBase64; } else { preview.removeAttribute('src'); } const coords = ov.pixelUrl ? extractPixelCoords(ov.pixelUrl) : { chunk1: '-', chunk2: '-', posX: '-', posY: '-' }; $('op-coord-display').textContent = ov.pixelUrl ? `Ref: chunk ${coords.chunk1}/${coords.chunk2} at (${coords.posX}, ${coords.posY})` : `No pixel URL set`; $('op-offset-x').value = ov.offsetX; $('op-offset-y').value = ov.offsetY; $('op-opacity-slider').value = String(ov.opacity); $('op-opacity-value').textContent = Math.round(ov.opacity * 100) + '%'; } function updateUI() { const $ = (id) => document.getElementById(id); const panel = $('overlay-pro-panel'); if (!panel) return; const content = $('op-content'); const toggle = $('op-panel-toggle'); const collapsed = !!config.isPanelCollapsed; content.style.display = collapsed ? 'none' : 'flex'; toggle.textContent = collapsed ? '▶' : '◀'; const modeBtn = $('op-mode-toggle'); modeBtn.textContent = `Mode: ${config.overlayMode === 'overlay' ? 'Overlay' : 'Original'}`; modeBtn.classList.toggle('active', config.overlayMode === 'overlay'); const autoBtn = $('op-autocap-toggle'); autoBtn.textContent = config.autoCapturePixelUrl ? 'ON' : 'OFF'; autoBtn.classList.toggle('active', !!config.autoCapturePixelUrl); rebuildOverlayListUI(); updateEditorUI(); } async function main() { await loadConfig(); injectStyles(); if (document.readyState === 'loading') { window.addEventListener('DOMContentLoaded', createUI); } else { createUI(); } hookFetch(); console.log("Overlay Pro — Collections v2.0.0: Initialized."); } main(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址