[go: up one dir, main page]

Guest User

Seedship planet image generator

a guest
Sep 9th, 2025
154
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
JavaScript 17.26 KB | Source Code | 0 0
  1. // ==UserScript==
  2. // @name         Seedship → Image Gen (Gemini/OpenAI) — Live Scrape + Copy
  3. // @namespace    seedship.hybrid
  4. // @version      1.4
  5. // @description  Scrape Seedship planet data, generate two images via Google Gemini or OpenAI, auto-refresh the live prompt every second, and let you copy the live prompt.
  6. // @match        https://philome.la/johnayliff/seedship/play/*
  7. // @grant        GM_addStyle
  8. // @grant        GM_setValue
  9. // @grant        GM_getValue
  10. // @grant        GM_xmlhttpRequest
  11. // @connect      generativelanguage.googleapis.com
  12. // @connect      api.openai.com
  13. // @run-at       document-idle
  14. // ==/UserScript==
  15.  
  16. (function () {
  17.   'use strict';
  18.  
  19.   // =============== CONFIG (editable via UI) ===============
  20.   const DEFAULT_PROVIDER = GM_getValue('PROVIDER', 'gemini'); // 'gemini' | 'openai'
  21.   const GEMINI_MODEL = GM_getValue('GEMINI_MODEL', 'gemini-2.5-flash-image-preview');
  22.   const OPENAI_MODEL = GM_getValue('OPENAI_IMAGE_MODEL', 'gpt-image-1');
  23.   const OPENAI_SIZE  = GM_getValue('OPENAI_IMAGE_SIZE', '1024x1024');
  24.  
  25.   // =============== UI ===============
  26.   GM_addStyle(`
  27.     #seedship-gen-btn {
  28.       position: fixed; right: 16px; bottom: 16px; z-index: 2147483647;
  29.       padding: 10px 14px; border-radius: 10px; border: 1px solid #333;
  30.       background: #111; color: #fff; font: 600 13px system-ui, sans-serif; cursor: pointer; opacity:.95;
  31.     }
  32.     #seedship-gen-btn:hover { opacity:1; }
  33.     #seedship-panel {
  34.       position: fixed; right: 16px; bottom: 64px; width: 520px; max-height: 85vh; overflow: auto;
  35.       background: #0b0b0b; color: #eee; border: 1px solid #333; border-radius: 12px; padding: 12px;
  36.       z-index: 2147483646; box-shadow: 0 12px 32px rgba(0,0,0,.45);
  37.     }
  38.     #seedship-panel h3 { margin: 0 0 6px; font: 700 14px/1 system-ui; }
  39.     #seedship-panel img { width: 100%; height: auto; display: block; border-radius: 8px; margin-top: 8px; }
  40.     #seedship-row { display: grid; grid-template-columns: 1fr; gap: 12px; }
  41.     .seedship-small { font-size: 12px; opacity:.85; }
  42.     .seedship-err { color:#ff6b6b; white-space: pre-wrap; }
  43.     .seedship-chip { font-size:11px; padding:2px 6px; border:1px solid #333; border-radius:999px; background:#171717; color:#ddd; }
  44.     .seedship-field { width:100%; background:#121212; color:#ddd; border:1px solid #333; border-radius:8px; padding:8px; font: 13px system-ui; }
  45.     .seedship-row { display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
  46.     .seedship-col { display:flex; flex-direction:column; gap:6px; }
  47.     .seedship-btn { background:#1f1f1f; border:1px solid #333; color:#ddd; border-radius:8px; padding:6px 10px; cursor:pointer; }
  48.     .seedship-btn[disabled] { opacity:.6; cursor: default; }
  49.     details summary { cursor:pointer; }
  50.     label { font: 12px system-ui; opacity:.9; }
  51.   `);
  52.  
  53.   const btn = document.createElement('button');
  54.   btn.id = 'seedship-gen-btn';
  55.   btn.textContent = '🎨 Seedship → Images';
  56.   document.body.appendChild(btn);
  57.  
  58.   const panel = document.createElement('div');
  59.   panel.id = 'seedship-panel';
  60.   panel.style.display = 'none';
  61.   panel.innerHTML = `
  62.     <div class="seedship-row">
  63.       <h3>Image Generator</h3>
  64.       <span class="seedship-chip" id="seedship-model-chip"></span>
  65.       <button id="seedship-close" class="seedship-btn" style="margin-left:auto;">Close</button>
  66.     </div>
  67.  
  68.     <div class="seedship-row" style="margin-top:8px">
  69.       <label>Provider</label>
  70.       <select id="seedship-provider" class="seedship-field" style="max-width:190px">
  71.         <option value="gemini">Google Gemini</option>
  72.         <option value="openai">OpenAI (ChatGPT)</option>
  73.       </select>
  74.  
  75.       <div class="seedship-row" style="margin-left:auto; gap:6px;">
  76.         <button id="set-gemini-key" class="seedship-btn">Set Gemini key</button>
  77.         <button id="set-openai-key" class="seedship-btn">Set OpenAI key</button>
  78.       </div>
  79.     </div>
  80.  
  81.     <details style="margin-top:8px;">
  82.       <summary>Show scraped prompt (live from page)</summary>
  83.       <div class="seedship-row" style="margin-top:6px; width:100%; align-items:flex-start;">
  84.         <div id="scraped-prompt" class="seedship-small" style="white-space:pre-wrap; flex:1 1 auto;"></div>
  85.       </div>
  86.     </details>
  87.  
  88.     <div id="seedship-status" class="seedship-small" style="margin-top:8px;"></div>
  89.  
  90.     <div id="seedship-row" style="margin-top:8px">
  91.       <div>
  92.         <div class="seedship-small">Space view</div>
  93.         <img id="img-space" style="display:none" alt="Space view">
  94.       </div>
  95.       <div>
  96.         <div class="seedship-small">Surface view</div>
  97.         <img id="img-surface" style="display:none" alt="Surface view">
  98.       </div>
  99.     </div>
  100.  
  101.     <div class="seedship-row" style="margin-top:10px">
  102.       <button id="generate" class="seedship-btn">Generate both</button>
  103.       <button id="regen-space" class="seedship-btn">Regen space</button>
  104.       <button id="regen-surface" class="seedship-btn">Regen surface</button>
  105.       <button id="copy-prompt" class="seedship-btn" title="Copy live prompt">Copy prompt</button>
  106.     </div>
  107.  
  108.     <details style="margin-top:8px;">
  109.       <summary>Advanced</summary>
  110.       <div class="seedship-col" style="margin-top:6px; gap:8px;">
  111.         <div class="seedship-row">
  112.           <label style="min-width:145px">Gemini model</label>
  113.           <input id="gemini-model" class="seedship-field" value="">
  114.           <button id="save-gemini-model" class="seedship-btn">Save</button>
  115.         </div>
  116.         <div class="seedship-row">
  117.           <label style="min-width:145px">OpenAI image model</label>
  118.           <input id="openai-model" class="seedship-field" value="">
  119.           <label>Size</label>
  120.           <input id="openai-size" class="seedship-field" style="max-width:120px" value="">
  121.           <button id="save-openai-model" class="seedship-btn">Save</button>
  122.         </div>
  123.       </div>
  124.     </details>
  125.  
  126.     <div id="seedship-error" class="seedship-err" style="margin-top:8px;"></div>
  127.   `;
  128.   document.body.appendChild(panel);
  129.  
  130.   // =============== Helpers ===============
  131.   const $ = (sel) => panel.querySelector(sel);
  132.   const setText = (sel, v) => ($(sel).textContent = v);
  133.   const setVal  = (sel, v) => ($(sel).value = v);
  134.   const getVal  = (sel) => $(sel).value;
  135.   const setDisplay = (el, show) => { el.style.display = show ? '' : 'none'; };
  136.  
  137.   // Initial UI state
  138.   setVal('#seedship-provider', DEFAULT_PROVIDER);
  139.   setVal('#gemini-model', GEMINI_MODEL);
  140.   setVal('#openai-model', OPENAI_MODEL);
  141.   setVal('#openai-size', OPENAI_SIZE);
  142.   updateModelChip();
  143.  
  144.   function updateModelChip() {
  145.     const provider = getVal('#seedship-provider');
  146.     if (provider === 'gemini') {
  147.       setText('#seedship-model-chip', getVal('#gemini-model') || 'gemini');
  148.     } else {
  149.       setText('#seedship-model-chip', `${getVal('#openai-model') || 'gpt-image-1'} @ ${getVal('#openai-size') || '1024x1024'}`);
  150.     }
  151.   }
  152.  
  153.   $('#seedship-provider').addEventListener('change', () => {
  154.     GM_setValue('PROVIDER', getVal('#seedship-provider'));
  155.     updateModelChip();
  156.   });
  157.   $('#save-gemini-model').onclick = () => { GM_setValue('GEMINI_MODEL', getVal('#gemini-model').trim()); updateModelChip(); };
  158.   $('#save-openai-model').onclick = () => {
  159.     GM_setValue('OPENAI_IMAGE_MODEL', getVal('#openai-model').trim() || 'gpt-image-1');
  160.     GM_setValue('OPENAI_IMAGE_SIZE', getVal('#openai-size').trim() || '1024x1024');
  161.     updateModelChip();
  162.   };
  163.  
  164.   // Keys
  165.   $('#set-gemini-key').onclick = async () => {
  166.     const current = GM_getValue('GEMINI_API_KEY', '');
  167.     const key = prompt('Enter Google AI Studio API key (ai.google.dev):', current || '');
  168.     if (key !== null) GM_setValue('GEMINI_API_KEY', key.trim());
  169.   };
  170.   $('#set-openai-key').onclick = async () => {
  171.     const current = GM_getValue('OPENAI_API_KEY', '');
  172.     const key = prompt('Enter OpenAI API key:', current || '');
  173.     if (key !== null) GM_setValue('OPENAI_API_KEY', key.trim());
  174.   };
  175.  
  176.   // Open/close with auto-refresh loop
  177.   let refreshTimer = null;
  178.   function startRefreshLoop() {
  179.     if (refreshTimer) return;
  180.     refreshTimer = setInterval(() => {
  181.       if (panel.style.display === 'block') refreshScrapedPreview();
  182.     }, 1000);
  183.   }
  184.   function stopRefreshLoop() {
  185.     if (refreshTimer) {
  186.       clearInterval(refreshTimer);
  187.       refreshTimer = null;
  188.     }
  189.   }
  190.   btn.onclick = () => {
  191.     panel.style.display = 'block';
  192.     refreshScrapedPreview();
  193.     startRefreshLoop();
  194.   };
  195.   $('#seedship-close').onclick = () => {
  196.     panel.style.display = 'none';
  197.     stopRefreshLoop();
  198.   };
  199.  
  200.   // =============== Scraping ===============
  201.   function clean(t) { return (t || '').replace(/\s+/g, ' ').trim(); }
  202.   function lines(el) { return clean(el?.innerText || '').split(/\n+/).map(s => s.trim()).filter(Boolean); }
  203.   function getText(sel) { const el = document.querySelector(sel); return clean(el?.innerText || ''); }
  204.  
  205.   function extractPlanetData() {
  206.     const situation = getText('#pre-planet-stats');
  207.     const atmosphere = getText('#atmosphere_text');
  208.     const gravity = getText('#gravity_text');
  209.     const temperature = getText('#temperature_text');
  210.     const water = getText('#water_text');
  211.     const resources = getText('#resources_text');
  212.     const anomaliesEl = document.querySelector('#anomalies_text');
  213.     const anomalies = anomaliesEl ? lines(anomaliesEl) : [];
  214.     return { situation, atmosphere, gravity, temperature, water, resources, anomalies };
  215.   }
  216.  
  217.   function buildScrapedPrompt() {
  218.     const d = extractPlanetData();
  219.     const anomaliesText = d.anomalies.length ? `\nSurface features: ${d.anomalies.join(', ')}` : '';
  220.     return `Create images of an alien planet with the following characteristics:
  221. Situation: ${d.situation || '(unspecified)'}
  222. Atmosphere: ${d.atmosphere || '(unspecified)'}
  223. Gravity: ${d.gravity || '(unspecified)'}
  224. Temperature: ${d.temperature || '(unspecified)'}
  225. Water: ${d.water || '(unspecified)'}
  226. Resources: ${d.resources || '(unspecified)'}${anomaliesText}
  227.  
  228. Generate 2 images:
  229. - this planet from space, starry background, picture taken from space. Space surrounds the planet. Realistic depiction of an exoplanet. Whole planet centered in view. Realistic scale of geological features. No America, no Africa, no Asia. If a moon or other space elements are mentioned, depict them.
  230. - view from its surface. do not depict the planet itself in the sky.
  231.  
  232. Keep it scientific and realistic. Keep chemistry and physics in mind. Analyze the planet's properties thoroughly and do not add extra elements that do not match requirements. Avoid images generated in Bryce, Terragen and 3d modeling apps. Write out the detailed requirements before generating each image. Painted, not 3d. If civilizations and animals are mentioned, they should not be human. Do not depict seedship itself. Avoid cliched compositions, sometimes not every element can be visible and in focus. You must write out the list of requirements and elements as separate points before generating the images.`;
  233.  }
  234.  
  235.  function refreshScrapedPreview() {
  236.    $('#scraped-prompt').textContent = buildScrapedPrompt();
  237.  }
  238.  
  239.  // Copy live prompt
  240.  $('#copy-prompt'). () => {
  241.    const prompt = buildScrapedPrompt();
  242.    try {
  243.      await navigator.clipboard.writeText(prompt);
  244.      flashStatus('Prompt copied to clipboard.');
  245.    } catch {
  246.      // Fallback
  247.      const ta = document.createElement('textarea');
  248.      ta.value = prompt;
  249.      document.body.appendChild(ta);
  250.      ta.select(); document.execCommand('copy');
  251.      ta.remove();
  252.      flashStatus('Prompt copied to clipboard.');
  253.    }
  254.  };
  255.  
  256.  function flashStatus(msg) {
  257.    const el = $('#seedship-status');
  258.    const prev = el.textContent;
  259.    el.textContent = msg;
  260.    setTimeout(() => { if (el.textContent === msg) el.textContent = prev; }, 1500);
  261.  }
  262.  
  263.  // =============== Prompt splitter (scraped only) ===============
  264.  function makePairPrompts() {
  265.    const base = buildScrapedPrompt();
  266.    const spacePrompt =
  267.      `${base}\n\nNow: Generate only the first image (space view).\nBefore internally listing requirements, do not output them; output only the image.`;
  268.    const surfacePrompt =
  269.      `${base}\n\nNow: Generate only the second image (surface view).\nBefore internally listing requirements, do not output them; output only the image.`;
  270.    return { spacePrompt, surfacePrompt, baseShown: base };
  271.  }
  272.  
  273.  // =============== Provider calls ===============
  274.  async function callGemini(prompt) {
  275.    const apiKey = GM_getValue('GEMINI_API_KEY', '');
  276.    if (!apiKey) throw new Error('No Gemini API key set.');
  277.    const model = GM_getValue('GEMINI_MODEL', GEMINI_MODEL);
  278.  
  279.    const url = `https://generativelanguage.googleapis.com/v1beta/models/${encodeURIComponent(model)}:generateContent`;
  280.    const payload = JSON.stringify({ contents: [{ parts: [{ text: prompt }]}] });
  281.  
  282.    return new Promise((resolve, reject) => {
  283.      GM_xmlhttpRequest({
  284.        method: 'POST',
  285.        url,
  286.        headers: {
  287.          'Content-Type': 'application/json',
  288.          'x-goog-api-key': apiKey
  289.        },
  290.        data: payload,
  291.        timeout: 120000,
  292.        onload: (res) => {
  293.          try {
  294.            if (res.status < 200 || res.status >= 300) {
  295.              return reject(new Error(`Gemini HTTP ${res.status}: ${res.responseText?.slice(0,400) || 'error'}`));
  296.            }
  297.            const json = JSON.parse(res.responseText || '{}');
  298.            const candidates = json.candidates || [];
  299.            for (const c of candidates) {
  300.              const parts = c.content?.parts || [];
  301.              for (const p of parts) {
  302.                const b64 = p?.inline_data?.data || p?.inlineData?.data;
  303.                const mime = p?.inline_data?.mime_type || p?.inlineData?.mimeType || 'image/png';
  304.                if (b64) return resolve(`data:${mime};base64,${b64}`);
  305.              }
  306.            }
  307.            reject(new Error('Gemini returned no image (blocked by safety or empty).'));
  308.          } catch (e) { reject(e); }
  309.        },
  310.        onerror: (e) => reject(new Error(`Gemini network error: ${e?.error || 'unknown'}`)),
  311.        ontimeout: () => reject(new Error('Gemini request timed out'))
  312.      });
  313.    });
  314.  }
  315.  
  316.  async function callOpenAI(prompt) {
  317.    const apiKey = GM_getValue('OPENAI_API_KEY', '');
  318.    if (!apiKey) throw new Error('No OpenAI API key set.');
  319.    const model = GM_getValue('OPENAI_IMAGE_MODEL', OPENAI_MODEL) || 'gpt-image-1';
  320.    const size  = GM_getValue('OPENAI_IMAGE_SIZE', OPENAI_SIZE) || '1024x1024';
  321.  
  322.    const url = 'https://api.openai.com/v1/images/generations';
  323.     const body = JSON.stringify({ model, prompt, size });
  324.  
  325.     return new Promise((resolve, reject) => {
  326.       GM_xmlhttpRequest({
  327.         method: 'POST',
  328.         url,
  329.         headers: {
  330.           'Content-Type': 'application/json',
  331.           'Authorization': `Bearer ${apiKey}`
  332.         },
  333.         data: body,
  334.         timeout: 120000,
  335.         onload: (res) => {
  336.           try {
  337.             if (res.status < 200 || res.status >= 300) {
  338.               return reject(new Error(`OpenAI HTTP ${res.status}: ${res.responseText?.slice(0,400) || 'error'}`));
  339.             }
  340.             const json = JSON.parse(res.responseText || '{}');
  341.  
  342.             // Prefer URL if provided
  343.             const urlResult = json?.data?.[0]?.url;
  344.             if (urlResult) return resolve(urlResult);
  345.  
  346.             // Fallback: base64
  347.             const b64 = json?.data?.[0]?.b64_json || json?.data?.[0]?.b64Json;
  348.             if (b64) return resolve(`data:image/png;base64,${b64}`);
  349.  
  350.             return reject(new Error('OpenAI returned neither url nor b64_json. Partial: ' + JSON.stringify(json).slice(0,200)));
  351.           } catch (e) { reject(e); }
  352.         },
  353.         onerror: (e) => reject(new Error(`OpenAI network error: ${e?.error || 'unknown'}`)),
  354.         ontimeout: () => reject(new Error('OpenAI request timed out'))
  355.       });
  356.     });
  357.   }
  358.  
  359.   async function generateOne(which) {
  360.     const { spacePrompt, surfacePrompt, baseShown } = makePairPrompts();
  361.     const provider = getVal('#seedship-provider');
  362.     const status = $('#seedship-status');
  363.     const errBox = $('#seedship-error');
  364.     errBox.textContent = '';
  365.     $('#scraped-prompt').textContent = baseShown;
  366.  
  367.     const p = which === 'space' ? spacePrompt : surfacePrompt;
  368.     status.textContent = (which === 'space') ? 'Generating: space view…' : 'Generating: surface view…';
  369.     disableButtons(true);
  370.  
  371.     try {
  372.       const dataUrl = (provider === 'gemini') ? await callGemini(p) : await callOpenAI(p);
  373.       const imgEl = (which === 'space') ? $('#img-space') : $('#img-surface');
  374.       imgEl.src = dataUrl;
  375.       setDisplay(imgEl, true);
  376.       status.textContent = '';
  377.     } catch (e) {
  378.       errBox.textContent = String(e);
  379.       status.textContent = '';
  380.     } finally {
  381.       disableButtons(false);
  382.     }
  383.   }
  384.  
  385.   async function generateBoth() {
  386.     await generateOne('space');
  387.     await generateOne('surface');
  388.   }
  389.  
  390.   function disableButtons(disabled) {
  391.     ['#generate','#regen-space','#regen-surface','#set-gemini-key','#set-openai-key','#save-gemini-model','#save-openai-model','#copy-prompt']
  392.       .forEach(s => { const el = $(s); if (el) el.disabled = disabled; });
  393.   }
  394.  
  395.   // Events
  396.   $('#generate').onclick = generateBoth;
  397.   $('#regen-space').onclick = () => generateOne('space');
  398.   $('#regen-surface').onclick = () => generateOne('surface');
  399.  
  400. })();
  401.  
Add Comment
Please, Sign In to add comment