Guest User

Untitled

a guest
Dec 7th, 2025
36
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
JavaScript 11.73 KB | Source Code | 0 0
  1. // ==UserScript==
  2. // @name         Reddit ComfyUI Smart Reader
  3. // @namespace    http://tampermonkey.net/
  4. // @version      3.0
  5. // @description  Adds buttons to Reddit images to open Raw PNGs and extract ComfyUI Prompts smartly.
  6. // @author       Unknown
  7. // @match        https://*.reddit.com/*
  8. // @icon         https://www.google.com/s2/favicons?sz=64&domain=reddit.com
  9. // @grant        GM_xmlhttpRequest
  10. // @grant        GM_setClipboard
  11. // ==/UserScript==
  12.  
  13. (function() {
  14.     'use strict';
  15.  
  16.     // --- STYLES ---
  17.     // Position: Bottom-Right of the image
  18.     const btnContainerStyle = `
  19.         position: absolute;
  20.         bottom: 10px;
  21.         right: 10px;
  22.         z-index: 9999;
  23.         display: flex;
  24.         gap: 6px;
  25.         font-family: sans-serif;
  26.     `;
  27.  
  28.     const baseBtnStyle = `
  29.         color: white;
  30.         padding: 6px 12px;
  31.         border-radius: 4px;
  32.         font-size: 12px;
  33.         font-weight: bold;
  34.         cursor: pointer;
  35.         border: none;
  36.         box-shadow: 0px 2px 5px rgba(0,0,0,0.6);
  37.         opacity: 0.9;
  38.         transition: transform 0.1s;
  39.     `;
  40.  
  41.     const modalStyle = `
  42.         position: fixed;
  43.         top: 50%;
  44.         left: 50%;
  45.         transform: translate(-50%, -50%);
  46.         width: 60%;
  47.         max-height: 80%;
  48.         background: #1a1a1b;
  49.         color: #d7dadc;
  50.         border: 1px solid #343536;
  51.         box-shadow: 0 0 50px rgba(0,0,0,0.9);
  52.         z-index: 10000;
  53.         padding: 20px;
  54.         border-radius: 8px;
  55.         display: flex;
  56.         flex-direction: column;
  57.         font-family: sans-serif;
  58.     `;
  59.  
  60.     const overlayStyle = `
  61.         position: fixed;
  62.         top: 0; left: 0; right: 0; bottom: 0;
  63.         background: rgba(0,0,0,0.8);
  64.         z-index: 9999;
  65.         backdrop-filter: blur(2px);
  66.     `;
  67.  
  68.     // --- LOGIC ---
  69.  
  70.     function getRawUrl(currentUrl) {
  71.         try {
  72.             let decoded = decodeURIComponent(currentUrl);
  73.             if (decoded.includes('preview.redd.it')) {
  74.                 let newUrl = decoded.replace('preview.redd.it', 'i.redd.it');
  75.                 if (newUrl.includes('?')) newUrl = newUrl.split('?')[0];
  76.                 return newUrl;
  77.             }
  78.         } catch (e) { console.error(e); }
  79.         return null;
  80.     }
  81.  
  82.     // Helper to find text strings inside binary data
  83.     function extractPngMetadata(arrayBuffer) {
  84.         const dataView = new DataView(arrayBuffer);
  85.         const textData = {};
  86.         let offset = 8; // Skip PNG header
  87.         const decoder = new TextDecoder('utf-8');
  88.  
  89.         while (offset < dataView.byteLength) {
  90.             const length = dataView.getUint32(offset);
  91.             const type = decoder.decode(new Uint8Array(arrayBuffer, offset + 4, 4));
  92.  
  93.             if (type === 'tEXt') {
  94.                 const chunkData = new Uint8Array(arrayBuffer, offset + 8, length);
  95.                 let nullIndex = chunkData.indexOf(0);
  96.                 if (nullIndex > -1) {
  97.                     const keyword = decoder.decode(chunkData.slice(0, nullIndex));
  98.                     const text = decoder.decode(chunkData.slice(nullIndex + 1));
  99.                     textData[keyword] = text;
  100.                 }
  101.             }
  102.             // For ComfyUI, 'tEXt' is usually where 'prompt' and 'workflow' live.
  103.             // 'iTXt' support can be added if needed, but your file used tEXt.
  104.             offset += length + 12;
  105.         }
  106.         return textData;
  107.     }
  108.  
  109.     // Smart Parser for ComfyUI JSON
  110.     function parseComfyJson(jsonString) {
  111.         let output = "";
  112.         try {
  113.             const data = JSON.parse(jsonString);
  114.  
  115.             // 1. Look for CLIPTextEncode nodes (Standard Positive/Negative prompts)
  116.             if (data.nodes) {
  117.                 data.nodes.forEach(node => {
  118.                     if (node.type === "CLIPTextEncode") {
  119.                         const title = node.title || node.properties?.["Node name for S&R"] || "Text Node";
  120.                         const textVal = node.widgets_values ? node.widgets_values[0] : "";
  121.                         if (textVal && typeof textVal === 'string' && textVal.length > 2) {
  122.                             output += `### ${title} ###\n${textVal}\n\n`;
  123.                         }
  124.                     }
  125.                     // 2. Look for specialized text nodes (like QwenVL in your file)
  126.                     if (node.type.includes("QwenVL") || node.type.includes("Text")) {
  127.                          if (node.widgets_values) {
  128.                              // Join all string values found in widgets
  129.                              const strings = node.widgets_values.filter(v => typeof v === 'string' && v.length > 10);
  130.                              if(strings.length > 0) {
  131.                                  output += `### ${node.title || node.type} ###\n${strings.join('\n')}\n\n`;
  132.                              }
  133.                          }
  134.                     }
  135.                 });
  136.             }
  137.         } catch (e) {
  138.             return "Could not parse JSON structure. Raw data:\n" + jsonString.substring(0, 500) + "...";
  139.         }
  140.         return output || "No text prompts found in the workflow nodes.";
  141.     }
  142.  
  143.     function showModal(displayText, rawJson) {
  144.         const overlay = document.createElement('div');
  145.         overlay.style.cssText = overlayStyle;
  146.  
  147.         const modal = document.createElement('div');
  148.         modal.style.cssText = modalStyle;
  149.  
  150.         const title = document.createElement('h3');
  151.         title.innerText = "Extracted Prompts";
  152.         title.style.margin = "0 0 10px 0";
  153.         title.style.color = "#fff";
  154.  
  155.         const textArea = document.createElement('textarea');
  156.         textArea.value = displayText;
  157.         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;";
  158.         textArea.readOnly = true;
  159.  
  160.         const btnBar = document.createElement('div');
  161.         btnBar.style.display = 'flex';
  162.         btnBar.style.justifyContent = 'space-between';
  163.  
  164.         const leftGroup = document.createElement('div');
  165.  
  166.         // Toggle Raw Button
  167.         const toggleBtn = document.createElement('button');
  168.         toggleBtn.innerText = "Show Raw JSON";
  169.         toggleBtn.style.cssText = "padding: 8px 12px; background: #333; color: white; border: none; border-radius: 4px; cursor: pointer; margin-right: 10px;";
  170.         let isRaw = false;
  171.         toggleBtn.onclick = () => {
  172.             isRaw = !isRaw;
  173.             textArea.value = isRaw ? rawJson : displayText;
  174.             toggleBtn.innerText = isRaw ? "Show Clean Prompts" : "Show Raw JSON";
  175.         };
  176.         leftGroup.appendChild(toggleBtn);
  177.  
  178.         const rightGroup = document.createElement('div');
  179.         rightGroup.style.gap = "10px";
  180.         rightGroup.style.display = "flex";
  181.  
  182.         const copyBtn = document.createElement('button');
  183.         copyBtn.innerText = "Copy Text";
  184.         copyBtn.style.cssText = "padding: 8px 16px; background: #0079D3; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;";
  185.         copyBtn.onclick = () => {
  186.             textArea.select();
  187.             document.execCommand('copy');
  188.             copyBtn.innerText = "Copied!";
  189.             setTimeout(() => copyBtn.innerText = "Copy Text", 2000);
  190.         };
  191.  
  192.         const closeBtn = document.createElement('button');
  193.         closeBtn.innerText = "Close";
  194.         closeBtn.style.cssText = "padding: 8px 16px; background: #555; color: white; border: none; border-radius: 4px; cursor: pointer;";
  195.         closeBtn.onclick = () => { document.body.removeChild(overlay); document.body.removeChild(modal); };
  196.  
  197.         rightGroup.appendChild(copyBtn);
  198.         rightGroup.appendChild(closeBtn);
  199.         btnBar.appendChild(leftGroup);
  200.         btnBar.appendChild(rightGroup);
  201.  
  202.         modal.appendChild(title);
  203.         modal.appendChild(textArea);
  204.         modal.appendChild(btnBar);
  205.  
  206.         document.body.appendChild(overlay);
  207.         document.body.appendChild(modal);
  208.     }
  209.  
  210.     function fetchAndParse(url, btnElement) {
  211.         const originalText = btnElement.innerText;
  212.         btnElement.innerText = "Reading...";
  213.  
  214.         GM_xmlhttpRequest({
  215.             method: "GET",
  216.             url: url,
  217.             responseType: "arraybuffer",
  218.             onload: function(response) {
  219.                 try {
  220.                     const metadata = extractPngMetadata(response.response);
  221.  
  222.                     let cleanText = "";
  223.                     let rawData = "";
  224.  
  225.                     // Check for ComfyUI 'workflow' or 'prompt'
  226.                     if (metadata.workflow) {
  227.                         cleanText = parseComfyJson(metadata.workflow);
  228.                         rawData = metadata.workflow;
  229.                     } else if (metadata.prompt) {
  230.                         // Sometimes simple API format is in 'prompt'
  231.                         // but usually workflow is better for reading
  232.                         cleanText = parseComfyJson(metadata.prompt);
  233.                         rawData = metadata.prompt;
  234.                     } else if (metadata.parameters) {
  235.                         cleanText = "### A1111 Parameters ###\n" + metadata.parameters;
  236.                         rawData = metadata.parameters;
  237.                     } else {
  238.                         cleanText = "No ComfyUI metadata found.";
  239.                         rawData = "Available chunks: " + Object.keys(metadata).join(", ");
  240.                     }
  241.  
  242.                     showModal(cleanText, rawData);
  243.                 } catch (e) {
  244.                     alert("Error parsing: " + e.message);
  245.                 } finally {
  246.                     btnElement.innerText = originalText;
  247.                 }
  248.             },
  249.             onerror: function(err) {
  250.                 alert("Network error. Could not fetch image data.");
  251.                 btnElement.innerText = originalText;
  252.             }
  253.         });
  254.     }
  255.  
  256.     function processImages() {
  257.         // Target Reddit Preview images
  258.         const images = document.querySelectorAll('img[src*="preview.redd.it"]:not(.comfy-processed)');
  259.  
  260.         images.forEach(img => {
  261.             img.classList.add('comfy-processed');
  262.             let container = img.parentElement;
  263.  
  264.             if (window.getComputedStyle(container).position === 'static') {
  265.                 container.style.position = 'relative';
  266.             }
  267.  
  268.             const rawUrl = getRawUrl(img.src);
  269.  
  270.             if (rawUrl) {
  271.                 const btnWrapper = document.createElement('div');
  272.                 btnWrapper.style.cssText = btnContainerStyle;
  273.  
  274.                 // 1. Scan Button (Blue)
  275.                 const btnScan = document.createElement('button');
  276.                 btnScan.innerText = 'Show Prompt';
  277.                 btnScan.style.cssText = baseBtnStyle + "background-color: #0079D3;";
  278.                 btnScan.onclick = (e) => {
  279.                     e.preventDefault(); e.stopPropagation();
  280.                     fetchAndParse(rawUrl, btnScan);
  281.                 };
  282.  
  283.                 // 2. Open Raw Button (Orange)
  284.                 const btnOpen = document.createElement('button');
  285.                 btnOpen.innerText = 'Raw PNG';
  286.                 btnOpen.style.cssText = baseBtnStyle + "background-color: #ff4500;";
  287.                 btnOpen.onclick = (e) => {
  288.                     e.preventDefault(); e.stopPropagation();
  289.                     window.open(rawUrl, '_blank');
  290.                 };
  291.  
  292.                 btnWrapper.appendChild(btnScan);
  293.                 btnWrapper.appendChild(btnOpen);
  294.                 container.appendChild(btnWrapper);
  295.             }
  296.         });
  297.     }
  298.  
  299.     const observer = new MutationObserver(() => { processImages(); });
  300.     observer.observe(document.body, { childList: true, subtree: true });
  301.     processImages();
  302.  
  303. })();
Advertisement
Add Comment
Please, Sign In to add comment