Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ==UserScript==
- // @name Reddit ComfyUI Smart Reader
- // @namespace http://tampermonkey.net/
- // @version 3.0
- // @description Adds buttons to Reddit images to open Raw PNGs and extract ComfyUI Prompts smartly.
- // @author Unknown
- // @match https://*.reddit.com/*
- // @icon https://www.google.com/s2/favicons?sz=64&domain=reddit.com
- // @grant GM_xmlhttpRequest
- // @grant GM_setClipboard
- // ==/UserScript==
- (function() {
- 'use strict';
- // --- STYLES ---
- // Position: Bottom-Right of the image
- const btnContainerStyle = `
- position: absolute;
- bottom: 10px;
- right: 10px;
- z-index: 9999;
- display: flex;
- gap: 6px;
- font-family: sans-serif;
- `;
- const baseBtnStyle = `
- color: white;
- padding: 6px 12px;
- border-radius: 4px;
- font-size: 12px;
- font-weight: bold;
- cursor: pointer;
- border: none;
- box-shadow: 0px 2px 5px rgba(0,0,0,0.6);
- opacity: 0.9;
- transition: transform 0.1s;
- `;
- const modalStyle = `
- position: fixed;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- width: 60%;
- max-height: 80%;
- background: #1a1a1b;
- color: #d7dadc;
- border: 1px solid #343536;
- box-shadow: 0 0 50px rgba(0,0,0,0.9);
- z-index: 10000;
- padding: 20px;
- border-radius: 8px;
- display: flex;
- flex-direction: column;
- font-family: sans-serif;
- `;
- const overlayStyle = `
- position: fixed;
- top: 0; left: 0; right: 0; bottom: 0;
- background: rgba(0,0,0,0.8);
- z-index: 9999;
- backdrop-filter: blur(2px);
- `;
- // --- LOGIC ---
- function getRawUrl(currentUrl) {
- try {
- let decoded = decodeURIComponent(currentUrl);
- if (decoded.includes('preview.redd.it')) {
- let newUrl = decoded.replace('preview.redd.it', 'i.redd.it');
- if (newUrl.includes('?')) newUrl = newUrl.split('?')[0];
- return newUrl;
- }
- } catch (e) { console.error(e); }
- return null;
- }
- // Helper to find text strings inside binary data
- function extractPngMetadata(arrayBuffer) {
- const dataView = new DataView(arrayBuffer);
- const textData = {};
- let offset = 8; // Skip PNG header
- const decoder = new TextDecoder('utf-8');
- while (offset < dataView.byteLength) {
- const length = dataView.getUint32(offset);
- const type = decoder.decode(new Uint8Array(arrayBuffer, offset + 4, 4));
- if (type === 'tEXt') {
- const chunkData = new Uint8Array(arrayBuffer, offset + 8, length);
- let nullIndex = chunkData.indexOf(0);
- if (nullIndex > -1) {
- const keyword = decoder.decode(chunkData.slice(0, nullIndex));
- const text = decoder.decode(chunkData.slice(nullIndex + 1));
- textData[keyword] = text;
- }
- }
- // For ComfyUI, 'tEXt' is usually where 'prompt' and 'workflow' live.
- // 'iTXt' support can be added if needed, but your file used tEXt.
- offset += length + 12;
- }
- return textData;
- }
- // Smart Parser for ComfyUI JSON
- function parseComfyJson(jsonString) {
- let output = "";
- try {
- const data = JSON.parse(jsonString);
- // 1. Look for CLIPTextEncode nodes (Standard Positive/Negative prompts)
- if (data.nodes) {
- data.nodes.forEach(node => {
- if (node.type === "CLIPTextEncode") {
- const title = node.title || node.properties?.["Node name for S&R"] || "Text Node";
- const textVal = node.widgets_values ? node.widgets_values[0] : "";
- if (textVal && typeof textVal === 'string' && textVal.length > 2) {
- output += `### ${title} ###\n${textVal}\n\n`;
- }
- }
- // 2. Look for specialized text nodes (like QwenVL in your file)
- if (node.type.includes("QwenVL") || node.type.includes("Text")) {
- if (node.widgets_values) {
- // Join all string values found in widgets
- const strings = node.widgets_values.filter(v => typeof v === 'string' && v.length > 10);
- if(strings.length > 0) {
- output += `### ${node.title || node.type} ###\n${strings.join('\n')}\n\n`;
- }
- }
- }
- });
- }
- } catch (e) {
- return "Could not parse JSON structure. Raw data:\n" + jsonString.substring(0, 500) + "...";
- }
- return output || "No text prompts found in the workflow nodes.";
- }
- function showModal(displayText, rawJson) {
- const overlay = document.createElement('div');
- overlay.style.cssText = overlayStyle;
- const modal = document.createElement('div');
- modal.style.cssText = modalStyle;
- const title = document.createElement('h3');
- title.innerText = "Extracted Prompts";
- title.style.margin = "0 0 10px 0";
- title.style.color = "#fff";
- const textArea = document.createElement('textarea');
- textArea.value = displayText;
- textArea.style.cssText = "flex: 1; background: #272729; color: #a6e22e; border: 1px solid #343536; padding: 10px; margin-bottom: 10px; resize: none; font-family: 'Consolas', monospace; font-size: 13px; line-height: 1.4;";
- textArea.readOnly = true;
- const btnBar = document.createElement('div');
- btnBar.style.display = 'flex';
- btnBar.style.justifyContent = 'space-between';
- const leftGroup = document.createElement('div');
- // Toggle Raw Button
- const toggleBtn = document.createElement('button');
- toggleBtn.innerText = "Show Raw JSON";
- toggleBtn.style.cssText = "padding: 8px 12px; background: #333; color: white; border: none; border-radius: 4px; cursor: pointer; margin-right: 10px;";
- let isRaw = false;
- toggleBtn.onclick = () => {
- isRaw = !isRaw;
- textArea.value = isRaw ? rawJson : displayText;
- toggleBtn.innerText = isRaw ? "Show Clean Prompts" : "Show Raw JSON";
- };
- leftGroup.appendChild(toggleBtn);
- const rightGroup = document.createElement('div');
- rightGroup.style.gap = "10px";
- rightGroup.style.display = "flex";
- const copyBtn = document.createElement('button');
- copyBtn.innerText = "Copy Text";
- copyBtn.style.cssText = "padding: 8px 16px; background: #0079D3; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;";
- copyBtn.onclick = () => {
- textArea.select();
- document.execCommand('copy');
- copyBtn.innerText = "Copied!";
- setTimeout(() => copyBtn.innerText = "Copy Text", 2000);
- };
- const closeBtn = document.createElement('button');
- closeBtn.innerText = "Close";
- closeBtn.style.cssText = "padding: 8px 16px; background: #555; color: white; border: none; border-radius: 4px; cursor: pointer;";
- closeBtn.onclick = () => { document.body.removeChild(overlay); document.body.removeChild(modal); };
- rightGroup.appendChild(copyBtn);
- rightGroup.appendChild(closeBtn);
- btnBar.appendChild(leftGroup);
- btnBar.appendChild(rightGroup);
- modal.appendChild(title);
- modal.appendChild(textArea);
- modal.appendChild(btnBar);
- document.body.appendChild(overlay);
- document.body.appendChild(modal);
- }
- function fetchAndParse(url, btnElement) {
- const originalText = btnElement.innerText;
- btnElement.innerText = "Reading...";
- GM_xmlhttpRequest({
- method: "GET",
- url: url,
- responseType: "arraybuffer",
- onload: function(response) {
- try {
- const metadata = extractPngMetadata(response.response);
- let cleanText = "";
- let rawData = "";
- // Check for ComfyUI 'workflow' or 'prompt'
- if (metadata.workflow) {
- cleanText = parseComfyJson(metadata.workflow);
- rawData = metadata.workflow;
- } else if (metadata.prompt) {
- // Sometimes simple API format is in 'prompt'
- // but usually workflow is better for reading
- cleanText = parseComfyJson(metadata.prompt);
- rawData = metadata.prompt;
- } else if (metadata.parameters) {
- cleanText = "### A1111 Parameters ###\n" + metadata.parameters;
- rawData = metadata.parameters;
- } else {
- cleanText = "No ComfyUI metadata found.";
- rawData = "Available chunks: " + Object.keys(metadata).join(", ");
- }
- showModal(cleanText, rawData);
- } catch (e) {
- alert("Error parsing: " + e.message);
- } finally {
- btnElement.innerText = originalText;
- }
- },
- onerror: function(err) {
- alert("Network error. Could not fetch image data.");
- btnElement.innerText = originalText;
- }
- });
- }
- function processImages() {
- // Target Reddit Preview images
- const images = document.querySelectorAll('img[src*="preview.redd.it"]:not(.comfy-processed)');
- images.forEach(img => {
- img.classList.add('comfy-processed');
- let container = img.parentElement;
- if (window.getComputedStyle(container).position === 'static') {
- container.style.position = 'relative';
- }
- const rawUrl = getRawUrl(img.src);
- if (rawUrl) {
- const btnWrapper = document.createElement('div');
- btnWrapper.style.cssText = btnContainerStyle;
- // 1. Scan Button (Blue)
- const btnScan = document.createElement('button');
- btnScan.innerText = 'Show Prompt';
- btnScan.style.cssText = baseBtnStyle + "background-color: #0079D3;";
- btnScan.onclick = (e) => {
- e.preventDefault(); e.stopPropagation();
- fetchAndParse(rawUrl, btnScan);
- };
- // 2. Open Raw Button (Orange)
- const btnOpen = document.createElement('button');
- btnOpen.innerText = 'Raw PNG';
- btnOpen.style.cssText = baseBtnStyle + "background-color: #ff4500;";
- btnOpen.onclick = (e) => {
- e.preventDefault(); e.stopPropagation();
- window.open(rawUrl, '_blank');
- };
- btnWrapper.appendChild(btnScan);
- btnWrapper.appendChild(btnOpen);
- container.appendChild(btnWrapper);
- }
- });
- }
- const observer = new MutationObserver(() => { processImages(); });
- observer.observe(document.body, { childList: true, subtree: true });
- processImages();
- })();
Advertisement
Add Comment
Please, Sign In to add comment