// ==UserScript== // @name PicWish 提示词提取器 // @namespace local.picwish.prompt.extractor // @version 0.3.0 // @description 从 PicWish listing image generator 页面提取可见提示词,并在新标签页中整理、复制和导出。 // @match https://picwish.cn/listing-image-generator* // @run-at document-idle // @grant none // ==/UserScript== (function () { 'use strict'; const BUTTON_ID = 'picwish-prompt-extractor-button'; function isVisible(element) { // ScriptCat may expose page DOM nodes from a different JS realm, so // `instanceof Element` is not reliable in every injection mode. if (!element || element.nodeType !== 1) return false; const style = window.getComputedStyle(element); if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') { return false; } if (element.closest('[hidden], [aria-hidden="true"]')) return false; // Do not require a non-zero rectangle: prompts can be inside a scroll // container or a virtualized list while still being present in the DOM. return true; } function documentRoots() { const roots = []; const visited = new Set(); const visit = (root) => { if (!root || visited.has(root)) return; visited.add(root); roots.push(root); // Include open Shadow DOM trees when the site uses web components. if (root.querySelectorAll) { root.querySelectorAll('*').forEach((element) => { if (element.shadowRoot) visit(element.shadowRoot); }); // A same-origin iframe can contain the detail panel. Cross-origin // frames are ignored safely because their DOM is inaccessible. root.querySelectorAll('iframe, frame').forEach((frame) => { try { visit(frame.contentDocument); } catch (_) { /* ignore */ } }); } }; visit(document); return roots; } function cleanPrompt(value) { return String(value || '') .replace(/\r\n?/g, '\n') .split('\n') // Keep intentional indentation in nested bullet points; only remove // trailing whitespace introduced by the rendered DOM. .map((line) => line.replace(/[ \t]+$/g, '')) .join('\n') .replace(/\n{3,}/g, '\n\n') .replace(/^\n+|\n+$/g, ''); } function promptTextFrom(element) { if (!element) return ''; // A prompt card may contain controls or other nested markup. Prefer the // dedicated text node and keep the card itself as the de-duplication unit. const textNode = element.matches('.whitespace-pre-wrap') ? element : element.querySelector('.whitespace-pre-wrap'); if (!textNode) return ''; return cleanPrompt(textNode.innerText || textNode.textContent); } function extractPrompts() { const prompts = []; const seen = new Set(); const addPrompt = (prompt) => { if (!prompt || seen.has(prompt)) return; seen.add(prompt); prompts.push(prompt); }; // Current PicWish structure: one complete prompt per data-scene-prompt card. // Search all reachable document roots so iframe/Shadow DOM placement does // not make an otherwise valid prompt invisible to the script. documentRoots().forEach((root) => { root.querySelectorAll('[data-scene-prompt]').forEach((card) => { addPrompt(promptTextFrom(card)); }); }); // Keep compatibility with alternate/older wrappers if the current structure // is absent. Each candidate is reduced to its dedicated text node first. if (prompts.length === 0) { documentRoots().forEach((root) => { root.querySelectorAll('.scene-prompt, [data-testid*="prompt" i], [class*="prompt" i], .whitespace-pre-wrap') .forEach((element) => addPrompt(promptTextFrom(element))); }); } return prompts; } function scanDiagnostics() { let sceneCards = 0; let textNodes = 0; documentRoots().forEach((root) => { sceneCards += root.querySelectorAll('[data-scene-prompt]').length; textNodes += root.querySelectorAll('.whitespace-pre-wrap').length; }); return { sceneCards, textNodes }; } function safeJson(value) { return JSON.stringify(value) .replace(//g, '\\u003e') .replace(/&/g, '\\u0026') .replace(/\u2028/g, '\\u2028') .replace(/\u2029/g, '\\u2029'); } function openResults(prompts, resultWindow) { if (!resultWindow) { resultWindow = window.open('about:blank', '_blank'); if (!resultWindow) { window.alert('浏览器阻止了新标签页,请允许此网站打开弹出窗口后重试。'); return; } } const promptData = safeJson(prompts); resultWindow.document.open(); resultWindow.document.write(`
正在打开提示词详情,请稍候…
'); resultWindow.document.close(); button.disabled = true; const oldText = button.textContent; button.textContent = '正在读取…'; const cachedPrompts = pressSnapshotAt && Date.now() - pressSnapshotAt < 3000 ? pressSnapshot : []; const prompts = cachedPrompts.length ? cachedPrompts : await collectPromptsAfterOpening(); button.disabled = false; button.textContent = oldText; if (!prompts.length) { resultWindow.close(); const { sceneCards, textNodes } = scanDiagnostics(); console.warn('[PicWish 提示词提取器] 未提取到提示词', { sceneCards, textNodes, url: location.href }); window.alert(`未找到提示词。当前页面检测到 ${sceneCards} 个提示词容器、${textNodes} 个文本节点。请先在页面中打开“详情页/A+”或“查看详情”,再重试。`); return; } openResults(prompts, resultWindow); }); document.body.appendChild(button); } if (document.body) addFloatingButton(); else window.addEventListener('DOMContentLoaded', addFloatingButton, { once: true }); })();