Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ==UserScript==
- // @name Seedship → Image Gen (Gemini/OpenAI) — Live Scrape + Copy
- // @namespace seedship.hybrid
- // @version 1.4
- // @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.
- // @match https://philome.la/johnayliff/seedship/play/*
- // @grant GM_addStyle
- // @grant GM_setValue
- // @grant GM_getValue
- // @grant GM_xmlhttpRequest
- // @connect generativelanguage.googleapis.com
- // @connect api.openai.com
- // @run-at document-idle
- // ==/UserScript==
- (function () {
- 'use strict';
- // =============== CONFIG (editable via UI) ===============
- const DEFAULT_PROVIDER = GM_getValue('PROVIDER', 'gemini'); // 'gemini' | 'openai'
- const GEMINI_MODEL = GM_getValue('GEMINI_MODEL', 'gemini-2.5-flash-image-preview');
- const OPENAI_MODEL = GM_getValue('OPENAI_IMAGE_MODEL', 'gpt-image-1');
- const OPENAI_SIZE = GM_getValue('OPENAI_IMAGE_SIZE', '1024x1024');
- // =============== UI ===============
- GM_addStyle(`
- #seedship-gen-btn {
- position: fixed; right: 16px; bottom: 16px; z-index: 2147483647;
- padding: 10px 14px; border-radius: 10px; border: 1px solid #333;
- background: #111; color: #fff; font: 600 13px system-ui, sans-serif; cursor: pointer; opacity:.95;
- }
- #seedship-gen-btn:hover { opacity:1; }
- #seedship-panel {
- position: fixed; right: 16px; bottom: 64px; width: 520px; max-height: 85vh; overflow: auto;
- background: #0b0b0b; color: #eee; border: 1px solid #333; border-radius: 12px; padding: 12px;
- z-index: 2147483646; box-shadow: 0 12px 32px rgba(0,0,0,.45);
- }
- #seedship-panel h3 { margin: 0 0 6px; font: 700 14px/1 system-ui; }
- #seedship-panel img { width: 100%; height: auto; display: block; border-radius: 8px; margin-top: 8px; }
- #seedship-row { display: grid; grid-template-columns: 1fr; gap: 12px; }
- .seedship-small { font-size: 12px; opacity:.85; }
- .seedship-err { color:#ff6b6b; white-space: pre-wrap; }
- .seedship-chip { font-size:11px; padding:2px 6px; border:1px solid #333; border-radius:999px; background:#171717; color:#ddd; }
- .seedship-field { width:100%; background:#121212; color:#ddd; border:1px solid #333; border-radius:8px; padding:8px; font: 13px system-ui; }
- .seedship-row { display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
- .seedship-col { display:flex; flex-direction:column; gap:6px; }
- .seedship-btn { background:#1f1f1f; border:1px solid #333; color:#ddd; border-radius:8px; padding:6px 10px; cursor:pointer; }
- .seedship-btn[disabled] { opacity:.6; cursor: default; }
- details summary { cursor:pointer; }
- label { font: 12px system-ui; opacity:.9; }
- `);
- const btn = document.createElement('button');
- btn.id = 'seedship-gen-btn';
- btn.textContent = '🎨 Seedship → Images';
- document.body.appendChild(btn);
- const panel = document.createElement('div');
- panel.id = 'seedship-panel';
- panel.style.display = 'none';
- panel.innerHTML = `
- <div class="seedship-row">
- <h3>Image Generator</h3>
- <span class="seedship-chip" id="seedship-model-chip"></span>
- <button id="seedship-close" class="seedship-btn" style="margin-left:auto;">Close</button>
- </div>
- <div class="seedship-row" style="margin-top:8px">
- <label>Provider</label>
- <select id="seedship-provider" class="seedship-field" style="max-width:190px">
- <option value="gemini">Google Gemini</option>
- <option value="openai">OpenAI (ChatGPT)</option>
- </select>
- <div class="seedship-row" style="margin-left:auto; gap:6px;">
- <button id="set-gemini-key" class="seedship-btn">Set Gemini key</button>
- <button id="set-openai-key" class="seedship-btn">Set OpenAI key</button>
- </div>
- </div>
- <details style="margin-top:8px;">
- <summary>Show scraped prompt (live from page)</summary>
- <div class="seedship-row" style="margin-top:6px; width:100%; align-items:flex-start;">
- <div id="scraped-prompt" class="seedship-small" style="white-space:pre-wrap; flex:1 1 auto;"></div>
- </div>
- </details>
- <div id="seedship-status" class="seedship-small" style="margin-top:8px;"></div>
- <div id="seedship-row" style="margin-top:8px">
- <div>
- <div class="seedship-small">Space view</div>
- <img id="img-space" style="display:none" alt="Space view">
- </div>
- <div>
- <div class="seedship-small">Surface view</div>
- <img id="img-surface" style="display:none" alt="Surface view">
- </div>
- </div>
- <div class="seedship-row" style="margin-top:10px">
- <button id="generate" class="seedship-btn">Generate both</button>
- <button id="regen-space" class="seedship-btn">Regen space</button>
- <button id="regen-surface" class="seedship-btn">Regen surface</button>
- <button id="copy-prompt" class="seedship-btn" title="Copy live prompt">Copy prompt</button>
- </div>
- <details style="margin-top:8px;">
- <summary>Advanced</summary>
- <div class="seedship-col" style="margin-top:6px; gap:8px;">
- <div class="seedship-row">
- <label style="min-width:145px">Gemini model</label>
- <input id="gemini-model" class="seedship-field" value="">
- <button id="save-gemini-model" class="seedship-btn">Save</button>
- </div>
- <div class="seedship-row">
- <label style="min-width:145px">OpenAI image model</label>
- <input id="openai-model" class="seedship-field" value="">
- <label>Size</label>
- <input id="openai-size" class="seedship-field" style="max-width:120px" value="">
- <button id="save-openai-model" class="seedship-btn">Save</button>
- </div>
- </div>
- </details>
- <div id="seedship-error" class="seedship-err" style="margin-top:8px;"></div>
- `;
- document.body.appendChild(panel);
- // =============== Helpers ===============
- const $ = (sel) => panel.querySelector(sel);
- const setText = (sel, v) => ($(sel).textContent = v);
- const setVal = (sel, v) => ($(sel).value = v);
- const getVal = (sel) => $(sel).value;
- const setDisplay = (el, show) => { el.style.display = show ? '' : 'none'; };
- // Initial UI state
- setVal('#seedship-provider', DEFAULT_PROVIDER);
- setVal('#gemini-model', GEMINI_MODEL);
- setVal('#openai-model', OPENAI_MODEL);
- setVal('#openai-size', OPENAI_SIZE);
- updateModelChip();
- function updateModelChip() {
- const provider = getVal('#seedship-provider');
- if (provider === 'gemini') {
- setText('#seedship-model-chip', getVal('#gemini-model') || 'gemini');
- } else {
- setText('#seedship-model-chip', `${getVal('#openai-model') || 'gpt-image-1'} @ ${getVal('#openai-size') || '1024x1024'}`);
- }
- }
- $('#seedship-provider').addEventListener('change', () => {
- GM_setValue('PROVIDER', getVal('#seedship-provider'));
- updateModelChip();
- });
- $('#save-gemini-model').onclick = () => { GM_setValue('GEMINI_MODEL', getVal('#gemini-model').trim()); updateModelChip(); };
- $('#save-openai-model').onclick = () => {
- GM_setValue('OPENAI_IMAGE_MODEL', getVal('#openai-model').trim() || 'gpt-image-1');
- GM_setValue('OPENAI_IMAGE_SIZE', getVal('#openai-size').trim() || '1024x1024');
- updateModelChip();
- };
- // Keys
- $('#set-gemini-key').onclick = async () => {
- const current = GM_getValue('GEMINI_API_KEY', '');
- const key = prompt('Enter Google AI Studio API key (ai.google.dev):', current || '');
- if (key !== null) GM_setValue('GEMINI_API_KEY', key.trim());
- };
- $('#set-openai-key').onclick = async () => {
- const current = GM_getValue('OPENAI_API_KEY', '');
- const key = prompt('Enter OpenAI API key:', current || '');
- if (key !== null) GM_setValue('OPENAI_API_KEY', key.trim());
- };
- // Open/close with auto-refresh loop
- let refreshTimer = null;
- function startRefreshLoop() {
- if (refreshTimer) return;
- refreshTimer = setInterval(() => {
- if (panel.style.display === 'block') refreshScrapedPreview();
- }, 1000);
- }
- function stopRefreshLoop() {
- if (refreshTimer) {
- clearInterval(refreshTimer);
- refreshTimer = null;
- }
- }
- btn.onclick = () => {
- panel.style.display = 'block';
- refreshScrapedPreview();
- startRefreshLoop();
- };
- $('#seedship-close').onclick = () => {
- panel.style.display = 'none';
- stopRefreshLoop();
- };
- // =============== Scraping ===============
- function clean(t) { return (t || '').replace(/\s+/g, ' ').trim(); }
- function lines(el) { return clean(el?.innerText || '').split(/\n+/).map(s => s.trim()).filter(Boolean); }
- function getText(sel) { const el = document.querySelector(sel); return clean(el?.innerText || ''); }
- function extractPlanetData() {
- const situation = getText('#pre-planet-stats');
- const atmosphere = getText('#atmosphere_text');
- const gravity = getText('#gravity_text');
- const temperature = getText('#temperature_text');
- const water = getText('#water_text');
- const resources = getText('#resources_text');
- const anomaliesEl = document.querySelector('#anomalies_text');
- const anomalies = anomaliesEl ? lines(anomaliesEl) : [];
- return { situation, atmosphere, gravity, temperature, water, resources, anomalies };
- }
- function buildScrapedPrompt() {
- const d = extractPlanetData();
- const anomaliesText = d.anomalies.length ? `\nSurface features: ${d.anomalies.join(', ')}` : '';
- return `Create images of an alien planet with the following characteristics:
- Situation: ${d.situation || '(unspecified)'}
- Atmosphere: ${d.atmosphere || '(unspecified)'}
- Gravity: ${d.gravity || '(unspecified)'}
- Temperature: ${d.temperature || '(unspecified)'}
- Water: ${d.water || '(unspecified)'}
- Resources: ${d.resources || '(unspecified)'}${anomaliesText}
- Generate 2 images:
- - 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.
- - view from its surface. do not depict the planet itself in the sky.
- 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.`;
- }
- function refreshScrapedPreview() {
- $('#scraped-prompt').textContent = buildScrapedPrompt();
- }
- // Copy live prompt
- $('#copy-prompt'). () => {
- const prompt = buildScrapedPrompt();
- try {
- await navigator.clipboard.writeText(prompt);
- flashStatus('Prompt copied to clipboard.');
- } catch {
- // Fallback
- const ta = document.createElement('textarea');
- ta.value = prompt;
- document.body.appendChild(ta);
- ta.select(); document.execCommand('copy');
- ta.remove();
- flashStatus('Prompt copied to clipboard.');
- }
- };
- function flashStatus(msg) {
- const el = $('#seedship-status');
- const prev = el.textContent;
- el.textContent = msg;
- setTimeout(() => { if (el.textContent === msg) el.textContent = prev; }, 1500);
- }
- // =============== Prompt splitter (scraped only) ===============
- function makePairPrompts() {
- const base = buildScrapedPrompt();
- const spacePrompt =
- `${base}\n\nNow: Generate only the first image (space view).\nBefore internally listing requirements, do not output them; output only the image.`;
- const surfacePrompt =
- `${base}\n\nNow: Generate only the second image (surface view).\nBefore internally listing requirements, do not output them; output only the image.`;
- return { spacePrompt, surfacePrompt, baseShown: base };
- }
- // =============== Provider calls ===============
- async function callGemini(prompt) {
- const apiKey = GM_getValue('GEMINI_API_KEY', '');
- if (!apiKey) throw new Error('No Gemini API key set.');
- const model = GM_getValue('GEMINI_MODEL', GEMINI_MODEL);
- const url = `https://generativelanguage.googleapis.com/v1beta/models/${encodeURIComponent(model)}:generateContent`;
- const payload = JSON.stringify({ contents: [{ parts: [{ text: prompt }]}] });
- return new Promise((resolve, reject) => {
- GM_xmlhttpRequest({
- method: 'POST',
- url,
- headers: {
- 'Content-Type': 'application/json',
- 'x-goog-api-key': apiKey
- },
- data: payload,
- timeout: 120000,
- onload: (res) => {
- try {
- if (res.status < 200 || res.status >= 300) {
- return reject(new Error(`Gemini HTTP ${res.status}: ${res.responseText?.slice(0,400) || 'error'}`));
- }
- const json = JSON.parse(res.responseText || '{}');
- const candidates = json.candidates || [];
- for (const c of candidates) {
- const parts = c.content?.parts || [];
- for (const p of parts) {
- const b64 = p?.inline_data?.data || p?.inlineData?.data;
- const mime = p?.inline_data?.mime_type || p?.inlineData?.mimeType || 'image/png';
- if (b64) return resolve(`data:${mime};base64,${b64}`);
- }
- }
- reject(new Error('Gemini returned no image (blocked by safety or empty).'));
- } catch (e) { reject(e); }
- },
- onerror: (e) => reject(new Error(`Gemini network error: ${e?.error || 'unknown'}`)),
- ontimeout: () => reject(new Error('Gemini request timed out'))
- });
- });
- }
- async function callOpenAI(prompt) {
- const apiKey = GM_getValue('OPENAI_API_KEY', '');
- if (!apiKey) throw new Error('No OpenAI API key set.');
- const model = GM_getValue('OPENAI_IMAGE_MODEL', OPENAI_MODEL) || 'gpt-image-1';
- const size = GM_getValue('OPENAI_IMAGE_SIZE', OPENAI_SIZE) || '1024x1024';
- const url = 'https://api.openai.com/v1/images/generations';
- const body = JSON.stringify({ model, prompt, size });
- return new Promise((resolve, reject) => {
- GM_xmlhttpRequest({
- method: 'POST',
- url,
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${apiKey}`
- },
- data: body,
- timeout: 120000,
- onload: (res) => {
- try {
- if (res.status < 200 || res.status >= 300) {
- return reject(new Error(`OpenAI HTTP ${res.status}: ${res.responseText?.slice(0,400) || 'error'}`));
- }
- const json = JSON.parse(res.responseText || '{}');
- // Prefer URL if provided
- const urlResult = json?.data?.[0]?.url;
- if (urlResult) return resolve(urlResult);
- // Fallback: base64
- const b64 = json?.data?.[0]?.b64_json || json?.data?.[0]?.b64Json;
- if (b64) return resolve(`data:image/png;base64,${b64}`);
- return reject(new Error('OpenAI returned neither url nor b64_json. Partial: ' + JSON.stringify(json).slice(0,200)));
- } catch (e) { reject(e); }
- },
- onerror: (e) => reject(new Error(`OpenAI network error: ${e?.error || 'unknown'}`)),
- ontimeout: () => reject(new Error('OpenAI request timed out'))
- });
- });
- }
- async function generateOne(which) {
- const { spacePrompt, surfacePrompt, baseShown } = makePairPrompts();
- const provider = getVal('#seedship-provider');
- const status = $('#seedship-status');
- const errBox = $('#seedship-error');
- errBox.textContent = '';
- $('#scraped-prompt').textContent = baseShown;
- const p = which === 'space' ? spacePrompt : surfacePrompt;
- status.textContent = (which === 'space') ? 'Generating: space view…' : 'Generating: surface view…';
- disableButtons(true);
- try {
- const dataUrl = (provider === 'gemini') ? await callGemini(p) : await callOpenAI(p);
- const imgEl = (which === 'space') ? $('#img-space') : $('#img-surface');
- imgEl.src = dataUrl;
- setDisplay(imgEl, true);
- status.textContent = '';
- } catch (e) {
- errBox.textContent = String(e);
- status.textContent = '';
- } finally {
- disableButtons(false);
- }
- }
- async function generateBoth() {
- await generateOne('space');
- await generateOne('surface');
- }
- function disableButtons(disabled) {
- ['#generate','#regen-space','#regen-surface','#set-gemini-key','#set-openai-key','#save-gemini-model','#save-openai-model','#copy-prompt']
- .forEach(s => { const el = $(s); if (el) el.disabled = disabled; });
- }
- // Events
- $('#generate').onclick = generateBoth;
- $('#regen-space').onclick = () => generateOne('space');
- $('#regen-surface').onclick = () => generateOne('surface');
- })();
Add Comment
Please, Sign In to add comment