您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Preloads images; download prompt filename; auto open slideshow; negative prompt persist option; keeps selection highlighted.
当前为
// ==UserScript== // @name PixAI Utilities Mod // @namespace Violentmonkey Scripts // @match https://pixai.art/* // @version 1.2.0 // @author brunon // @description Preloads images; download prompt filename; auto open slideshow; negative prompt persist option; keeps selection highlighted. // @grant GM_addStyle // @grant GM_download // @grant GM_info // @grant GM_setClipboard // @require https://cdn.jsdelivr.net/npm/@violentmonkey/shortcut@1 // @icon https://www.google.com/s2/favicons?sz=64&domain=pixai.art // ==/UserScript== (async () => { if (!VM.shortcut) { console.error('VM.shortcut is not available!'); return; } const shortcuts = new VM.shortcut.KeyboardService(); shortcuts.enable(); shortcuts.setContext('slideshowOpen', false); shortcuts.setContext('isInput', false); shortcuts.register( 'd', () => { let downloadBtn = document.querySelector('#custom-download'); if (!!downloadBtn) downloadBtn.click(); }, { condition: 'slideshowOpen', } ); const shortcutMapping = { up: -3, down: 3, right: 1, left: -1 }; Object.entries(shortcutMapping).forEach(([shortcut, direction]) => { shortcuts.register( shortcut, () => arrowNavigation(direction), { condition: '!slideshowOpen && !isInput', } ); }); let regenerateBtnText = 'Regenerate All'; let imgPreviewSelector = 'main img[src^="https://images-ng.pixai.art/images/thumb/"]'; let imgOriginalSelector = 'main img[src^="https://images-ng.pixai.art/images/orig/"]'; let imgThumbsSelector = '[data-test-id="virtuoso-item-list"] .contents>button'; let promptTextareaSelector = 'textarea.w-full'; let scrollListSelector = '[data-test-id="virtuoso-scroller"]'; let selectedListThumbSelector = `${scrollListSelector} button.outline`; let thumbIcons = []; let openedPreviewCache = new Map(); let lastCacheUpdate = 0; let slideshowPresent = false; let dbNameAntiban = `${GM_info.script.name}-${GM_info.uuid}`.replace(/\s+/g, '-'); let generateButtonListener; let pauseThumbListener = false; let latestClickedElement; let currentImgSrcObserver; let latestEventListener; let shouldEnforceNegative = !!localStorage.getItem('enforceNegativeNegativePrompt'); let previewListCheckListener; let latestClickedID; let stopGenerationLoop = false; let latestToastMessage = ""; performDatabaseOperation(1, 'previews', (store, id) => {}); async function waitForFocus() { return new Promise(resolve => { function onFocus() { window.removeEventListener('focus', onFocus); resolve(); } if (document.hasFocus()) { resolve(); } else { window.addEventListener('focus', onFocus); } }); } function findButtonByInnerText(innerText, extraSelector = '') { return [...document.querySelectorAll('button'+extraSelector)].find(button => button.textContent.includes(innerText)) || null; } await waitForElement(imgThumbsSelector); console.log("Running") window.print = function () { }; function preloadImages(imageUrls) { imageUrls.forEach(url => { const img = new Image(); img.src = url; }); } async function waitForElements(selector) { const startTime = Date.now(); const waitTime = 10000; return new Promise(resolve => { const checkInterval = setInterval(() => { const elements = document.querySelectorAll(selector); if (elements.length >= 4 || Date.now() - startTime > waitTime) { clearInterval(checkInterval); resolve(elements); } }, 150); }); } async function updateThumbs(refresh = false) { if (refresh) { thumbIcons = []; await updatePreviewCache(); } let newThumbs = await waitForElements(imgThumbsSelector); newThumbs.forEach(newThumb => { let id = extractPreviewId(newThumb); displayOpenedState(id, newThumb); if (!thumbIcons.includes(newThumb)) thumbIcons.push(newThumb); }); updateListeners(); } async function getImagePreviews() { return await waitForElements(imgPreviewSelector); } async function preloadFullImages() { const imagePreviews = await getImagePreviews(); preloadImages(Array.from(imagePreviews).map(img => { return img.src.replace("thumb", "orig"); })); } async function waitForElement(selector, timeout = 10) { return new Promise((resolve) => { const timeoutId = setTimeout(() => resolve(null), timeout * 1000); const observer = new MutationObserver(() => { const element = document.querySelector(selector); if (!!element) { clearTimeout(timeoutId); observer.disconnect(); resolve(element); } }); observer.observe(document.body, { childList: true, subtree: true }); }); } async function waitForClass(element, className) { if (!element) { return Promise.reject('Element is null'); } return new Promise(resolve => { const checkInterval = setInterval(() => { if (element.classList.contains(className)) { clearInterval(checkInterval); resolve(); } }, 100); // Check every 100 milliseconds }); } async function highlightSelected(target) { await waitForClass(target, 'ring-theme-primary'); thumbIcons.forEach(icon => { icon.classList.remove('selected-thumb'); }); target.classList.add('selected-thumb'); } function eventToElement(event) { return event.currentTarget; } async function updatePreviewCache() { if (Date.now() - lastCacheUpdate < 100) return Promise.resolve(); lastCacheUpdate = Date.now(); try { const request = openDatabase(); // Get the database request const db = await new Promise((resolve, reject) => { request.onsuccess = ({ target }) => resolve(target.result); // Resolve with the database object request.onerror = () => reject('IndexedDB error'); }); const store = db.transaction('previews', 'readonly').objectStore('previews'); const allValues = []; return new Promise((resolve, reject) => { const cursorRequest = store.openCursor(); cursorRequest.onsuccess = ({ target }) => { const cursor = target.result; if (!cursor) { allValues.forEach(item => openedPreviewCache.set(item.id, item)); resolve(); } else { allValues.push(cursor.value); cursor.continue(); } }; cursorRequest.onerror = () => reject('Error retrieving cache values'); }); } catch (error) { // console.error("Error accessing the database:", error); throw new Error("Error accessing the database:", error); } } async function measureUpdatePreviewCacheTime() { const startTime = performance.now(); // Start timing await updatePreviewCache(); const endTime = performance.now(); // End timing console.log(`updatePreviewCache execution time: ${endTime - startTime} ms`); } measureUpdatePreviewCacheTime(); function extractSrcId(src) { try { return src.split('/').pop(); } catch (error) { console.error('Error occurred with src:', src); } } function extractPreviewId(element) { const img = element.querySelector('div img'); const src = img?.getAttribute('src'); if (!src) return null; return (extractSrcId(src)) } function openDatabase() { return indexedDB.open(dbNameAntiban, 3); } function performDatabaseOperation(id, storeName, operation) { const request = openDatabase(); request.onupgradeneeded = function (event) { const db = event.target.result; if (!db.objectStoreNames.contains(storeName)) { db.createObjectStore(storeName, { keyPath: 'id' }); } }; request.onsuccess = function (event) { const db = event.target.result; const transaction = db.transaction(storeName, 'readwrite'); const store = transaction.objectStore(storeName); operation(store, id); transaction.oncomplete = () => null; transaction.onerror = () => console.error(`Transaction ${id} error: ${event.target.error}`); }; request.onerror = function (event) { console.error('IndexedDB error:', event.target.error); }; } function upsertDatabase(id) { if (!id) { console.error('Invalid ID provided for upsert operation'); return; } let infoID = { id, timestamp: new Date().toISOString() }; performDatabaseOperation(id, 'previews', (store, id) => { store.put(infoID); }); openedPreviewCache.set(id, infoID); } async function isIdPresentInDatabase(id, storeName) { return new Promise((resolve) => { performDatabaseOperation(id, storeName, (store, id) => { const request = store.get(id); request.onsuccess = () => resolve(request.result !== undefined); request.onerror = () => resolve(false); }); }); } async function getValueById(id, storeName) { const request = openDatabase(); return new Promise((resolve, reject) => { request.onsuccess = ({ target }) => { const store = target.result.transaction(storeName, 'readonly').objectStore(storeName); store.get(id).onsuccess = e => resolve(e.target.result || null); store.get(id).onerror = () => reject('Error retrieving value'); }; request.onerror = () => reject('IndexedDB error'); }); } async function wasAlreadyOpenedCheck(id, forceRefresh = false) { if (forceRefresh) { let storedValue = await getValueById(id, 'previews'); if (storedValue) openedPreviewCache.set(id, storedValue); return !!storedValue; } return openedPreviewCache.has(id); } function displayOpenedState(id, previewButton, forceRecheckDB = false) { let existingSpan = previewButton.querySelector('span[data-label="check"]'); wasAlreadyOpenedCheck(id, forceRecheckDB).then(isPresent => { if (!isPresent && !!existingSpan) { existingSpan.remove(); return; } if (isPresent && !existingSpan) { let span = document.createElement('span'); span.setAttribute('data-label', 'check'); span.textContent = '✔️'; span.style.opacity = '0'; previewButton.appendChild(span); setTimeout(() => span.style.opacity = '1', 5) } }); } function selectThumbFromId(id) { let img = document.querySelector(`[data-test-id="virtuoso-item-list"] .contents>button img[src$="${id}"]`); if (!img) return; thumbIcons.forEach(icon => { icon.classList.remove('selected-thumb'); }); let elementToSelect = img.parentElement.parentElement; elementToSelect.classList.add('selected-thumb'); // console.log("Selecting",elementToSelect,'because of ID',id); } function latestClickSrc() { if (!latestClickedElement) return { src: null, img: null }; let latestClickImg = latestClickedElement.querySelector("img"); if (!latestClickImg) return; return { src: latestClickImg.src, img: latestClickImg }; } async function updateListenersOnNewGeneration() { let srcRestore = latestClickSrc(); if (!srcRestore) return; if (currentImgSrcObserver) currentImgSrcObserver.disconnect(); currentImgSrcObserver = new MutationObserver(mutations => { mutations.forEach(mutation => { if (mutation.type === 'attributes' && mutation.attributeName === 'src') { selectThumbFromId(extractSrcId(srcRestore.src)); updateThumbs(true); currentImgSrcObserver.disconnect(); } }); }); currentImgSrcObserver.observe(srcRestore.img, { attributes: true, attributeFilter: ['src'] }); } async function addGenerateButtonListener() { while (!findButtonByInnerText('Generate', ".outline-2")) { await new Promise(resolve => setTimeout(resolve, 100)); } const button = findButtonByInnerText('Generate', ".outline-2"); if (!button) return; if (generateButtonListener) button.removeEventListener('click', generateButtonListener); generateButtonListener = () => { updateListenersOnNewGeneration(); } button.addEventListener('click', generateButtonListener); } async function openFirstImage() { let observer = new MutationObserver(() => { if (!document.body.innerText.match(/completed/i)) { let firstPreview = document.querySelector(imgOriginalSelector); if (firstPreview) { firstPreview.click(); observer.disconnect(); } else { console.error("Couldn't locate", firstPreview, "with", imgPreviewSelector) } } }); observer.observe(document.body, { childList: true, subtree: true }); } async function thumbListener(event) { if(isTabActive('favorites')) { console.log("Returning, because we're inside favourites"); return; } let clickedElementTarget = eventToElement(event); let latestSrc = latestClickSrc(); let id = extractPreviewId(clickedElementTarget); if(!id) { console.log("Element has no ID. Returning"); return; } // console.log("ID:",id); // console.log("Clicked:", clickedElementTarget, event) latestClickedElement = clickedElementTarget; let alreadyInsideDB = await isIdPresentInDatabase(id, 'previews'); // console.log(alreadyInsideDB ? "Was already inside DB" : "Wasn't previously inside DB"); await highlightSelected(clickedElementTarget); console.log(clickedElementTarget, "has been highlighted"); // upsertDatabase(id); latestClickedID = id; preloadFullImages(); updateThumbs(); displayOpenedState(id, clickedElementTarget, true); addGenerateButtonListener(); createEnforceNegativeCheckbox(); if (!alreadyInsideDB) openFirstImage() } function manageEnforceNegative(isChecked) { const textarea = document.querySelector('textarea[placeholder="Enter negative prompt here"]'); let storedValue = localStorage.getItem('enforceNegativeNegativePrompt'); if (!isChecked && !storedValue) { localStorage.removeItem('enforceNegativeNegativePrompt'); shouldEnforceNegative = false; return; } localStorage.setItem('enforceNegativeNegativePrompt', textarea.value); shouldEnforceNegative = true; } let toggleCheckbox = (selector, isChecked) => { let checkboxLabel = document.querySelector(selector); if (!checkboxLabel) return; let checkedPath = checkboxLabel.querySelector('.checked-path'); checkedPath.style.display = !isChecked ? 'none' : 'block'; let checkbox = checkboxLabel.querySelector('input[type="checkbox"]'); checkbox.checked = isChecked; // console.log("setting",checkbox, "to", isChecked) }; function syncNegativePrompt() { toggleCheckbox('#enforce-negative', shouldEnforceNegative); if (!shouldEnforceNegative) { // console.log("if (!shouldEnforceNegative)") return; } let textarea = document.querySelector('textarea[placeholder="Enter negative prompt here"]'); if (document.activeElement === textarea) { localStorage.removeItem('enforceNegativeNegativePrompt'); shouldEnforceNegative = false; // console.log("Active element, skipping") return; } let storedValue = localStorage.getItem('enforceNegativeNegativePrompt'); if (!storedValue) { // console.log("f (!storedValue) {") return; } if (textarea.value.trim() === storedValue.trim()) { // console.log("alrready changed"); return; } textarea.value = ''; // Clear the textarea textarea.value = storedValue; // Set the new value textarea.dispatchEvent(new Event('input', { bubbles: true })); // Trigger input event console.log("set to", storedValue); awakeTextarea(textarea); } setInterval(syncNegativePrompt, 1 * 1000); async function slideShowDowloadButtonManager() { const observer = new MutationObserver(() => { const nextButton = document.querySelector('.pswp__button--arrow--next'); if (!nextButton || !!document.querySelector('#custom-download')) return; const button = document.createElement('button'); button.id = 'custom-download'; button.title = 'Download with prompt as file name'; button.innerHTML = `<svg aria-hidden="true" viewBox="0 0 32 32" width="32" height="32"><use class="pswp__icn-shadow" xlink:href="#pswp__icn-download"></use><path d="M20.5 14.3 17.1 18V10h-2.2v7.9l-3.4-3.6L10 16l6 6.1 6-6.1ZM23 23H9v2h14Z" id="pswp__icn-download"></path></svg>`; button.onclick = async () => { nextButton.style.cursor = 'pointer'; await saveImage(); }; nextButton.insertAdjacentElement('beforebegin', button); setTimeout(() => button.classList.add('show'), 10); addHideSlideshowListeners(button); }); observer.observe(document.body, { childList: true, subtree: true }); window.addEventListener('beforeunload', () => { observer.disconnect(); }); } function sanitizeFilename(filename) { const maxLength = 125; const dotIndex = filename.lastIndexOf('.'); const extension = dotIndex !== -1 ? filename.substring(dotIndex) : ''; // Get the file extension const baseFilename = dotIndex !== -1 ? filename.substring(0, dotIndex) : filename; // Get the base filename const sanitizedBase = baseFilename .replace(/[^a-zA-Z0-9-_\. ]/g, '_') // Replace invalid characters with underscores .replace(/\s+/g, '_') // Replace spaces with underscores .replace(/_+/g, '_') // Remove duplicate underscores .substring(0, maxLength - extension.length); // Truncate to max length minus extension return sanitizedBase + extension; // Combine sanitized base with extension } async function saveImage() { const textarea = document.querySelector(promptTextareaSelector); const imgSrc = document.querySelector('#pswp__items .pswp__item[aria-hidden="false"] img.pswp__img')?.src; if (!textarea || !imgSrc) return; let filename = sanitizeFilename(`${textarea.value.trim()}.png`); await GM_download({ url: imgSrc, name: filename, saveAs: false }); } function highlightOpenThumbnail() { let firstPreviewSrc = document.querySelector(imgPreviewSelector); if (!firstPreviewSrc) return console.warn(`Element not found for selector: ${imgPreviewSelector}`); let currentId = extractSrcId(firstPreviewSrc.src); if (!currentId) return console.warn('Current ID could not be extracted from the image source.'); console.log("highlightOpenThumbnail(): Selection id", currentId, "from", firstPreviewSrc.src, 'of', firstPreviewSrc) selectThumbFromId(currentId); } function createCheckbox(id, labelText, onChangeFunction) { const newLabel = document.createElement('label'); newLabel.style.userSelect = "none"; newLabel.id = id; newLabel.innerHTML = ` ${labelText} <input type="checkbox" style="display:none"> <svg class="sc-eDvSVe cSfylm MuiSvgIcon-root MuiSvgIcon-fontSizeMedium" focusable="false" aria-hidden="true" viewBox="0 0 24 24"> <path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"></path> <path class="checked-path" d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-9 14l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"></path> </svg>`; const checkbox = newLabel.querySelector('input[type="checkbox"]'); const checkedPath = newLabel.querySelector('.checked-path'); checkbox.checked = shouldEnforceNegative; checkbox.addEventListener('change', function () { checkedPath.style.display = !this.checked ? 'none' : 'block'; if (typeof onChangeFunction === 'function') onChangeFunction(this.checked); }); return newLabel; } function createEnforceNegativeCheckbox() { const negativeLabel = Array.from(document.querySelectorAll('label')).find(label => label.textContent === 'Negative'); if (!negativeLabel || document.getElementById('enforce-negative')) return; negativeLabel.parentElement.insertBefore(createCheckbox('enforce-negative', 'Enforce negative for every task', manageEnforceNegative), negativeLabel); } async function slideShowLifetimeMonitor() { while (true) { while (!document.querySelector('#pswp__items')) await new Promise(resolve => setTimeout(resolve, 100)); slideshowPresent = true; shortcuts.setContext('slideshowOpen', true); if (latestClickedID) { console.log("Adding", latestClickedID, "(latestClickedID) to DB"); upsertDatabase(latestClickedID); } while (!!document.querySelector('#pswp__items')) await new Promise(resolve => setTimeout(resolve, 100)); slideshowPresent = false; shortcuts.setContext('slideshowOpen', false); await new Promise(resolve => setTimeout(resolve, 100)); highlightOpenThumbnail(); } } function checkOpenedImageOpacity() { let openedImage = document.querySelector('div.pswp__item[aria-hidden="false"] > div.pswp__zoom-wrap > img'); // console.log("opened",openedImage,"opacity:",parseFloat(openedImage.style.opacity)); return !!openedImage && (openedImage.complete && openedImage.naturalWidth !== 0) && (!openedImage.style.opacity || parseFloat(openedImage.style.opacity) >= 1); } async function addHideSlideshowListeners(customDownload) { if (!customDownload) return; let firstImageShowed = false; const elements = document.querySelectorAll('.pswp__scroll-wrap, .pswp__button--close'); const bg = document.querySelector('.pswp__bg'); bg.classList.add("black-bg"); if (!firstImageShowed) { firstImageShowed = checkOpenedImageOpacity(); } const hideDownload = async (e) => { const initialOpacity = parseFloat(bg.style.opacity) || 1; let opacityChanged = false; if (!firstImageShowed) { firstImageShowed = checkOpenedImageOpacity(); } const observer = new MutationObserver(() => { if (firstImageShowed && !bg.classList.contains("black-bg") && !opacityChanged) bg.classList.add("black-bg"); if (parseFloat(bg.style.opacity) !== initialOpacity) { customDownload.classList.add('hide'); opacityChanged = true; bg.classList.remove("black-bg"); observer.disconnect(); } }); observer.observe(bg, { attributes: true }); document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible' && !opacityChanged) observer.disconnect(); }); bg.classList.remove("black-bg"); await new Promise(resolve => setTimeout(resolve, 2000)); if (opacityChanged) { customDownload.classList.add('hide'); } observer.disconnect(); }; elements.forEach(el => el.addEventListener('click', hideDownload)); document.addEventListener('keydown', (e) => { if (e.key === 'Escape') hideDownload(e); }); } function isTabActive(tab = "favorites") { /* tasks / favorites */ let tabFavourites = document.querySelector(`#radix-\\:r9\\:-trigger-${tab}[data-state="active"]`); return !!tabFavourites && tabFavourites.getAttribute('data-state') === 'active'; } function createActionButton(id = "newBtn", text = "Go!", action = () => {}, remove = false) { let generateButton = findButtonByInnerText('Generate', '.outline-2'); if (!generateButton) return; let button = document.querySelector(`#${id}`); if (remove && !!button){ button.remove(); return; } if (!!button || remove) return; button = document.createElement('button'); button.id = id; button.textContent = text; button.classList.add('mr-3', 'grid-cols-[auto_1fr_auto]', 'focus:outline', 'outline-2', 'outline-offset-1', 'duration-75', 'disabled:text-black/50', 'disabled:bg-black/15', 'dark:disabled:text-white/50', 'dark:disabled:bg-white/15', 'text-white', 'bg-purple-600', 'hover:bg-purple-500', 'outline-purple-600', 'dark:outline-purple-500', 'text-sm', '[--ui-size:theme(spacing.8)]', 'w-full', 'mlg:w-auto', 'h-[--height]', 'flex', 'items-center', 'transition', 'font-semibold', 'dense:text-sm', 'px-4', 'mlg:py-2', 'dense:px-3', 'dense:py-0.5', 'rounded-xl', 'mlg:rounded-lg', 'dense:rounded-md'); button.classList.add('bg-gradient-member'); generateButton.parentNode.insertBefore(button, generateButton); button.addEventListener('click', action); } createActionButton("Copy", "Copy!", ()=>{ const textarea = document.querySelector(promptTextareaSelector); if (!textarea) return; GM_setClipboard(textarea.value.trim(),'text/plain'); }); function pasteButton(remove = false) { let generateButton = findButtonByInnerText('Generate', '.outline-2'); if (!generateButton) return; let pasteTbn = document.querySelector('#pasteButton'); if (remove && !!pasteTbn){ pasteTbn.remove(); return; } if (!!pasteTbn || remove) return; pasteTbn = document.createElement('button'); pasteTbn.id = 'pasteButton'; pasteTbn.textContent = "Paste! (demo)"; pasteTbn.classList.add('mr-3', 'grid-cols-[auto_1fr_auto]', 'focus:outline', 'outline-2', 'outline-offset-1', 'duration-75', 'disabled:text-black/50', 'disabled:bg-black/15', 'dark:disabled:text-white/50', 'dark:disabled:bg-white/15', 'text-white', 'bg-purple-600', 'hover:bg-purple-500', 'outline-purple-600', 'dark:outline-purple-500', 'text-sm', '[--ui-size:theme(spacing.8)]', 'w-full', 'mlg:w-auto', 'h-[--height]', 'flex', 'items-center', 'transition', 'font-semibold', 'dense:text-sm', 'px-4', 'mlg:py-2', 'dense:px-3', 'dense:py-0.5', 'rounded-xl', 'mlg:rounded-lg', 'dense:rounded-md'); pasteTbn.classList.add('bg-gradient-member'); generateButton.parentNode.insertBefore(pasteTbn, generateButton); pasteTbn.addEventListener('click', pasteIntoTextarea); const textarea = document.querySelector(promptTextareaSelector); console.log(textarea) } // pasteButton(); function pasteIntoTextarea() { const textarea = document.querySelector(promptTextareaSelector); if (textarea) { const currentValue = 'Your text here'; // Focus on the textarea textarea.focus(); textarea.dispatchEvent(new Event('focus', { bubbles: true })); // Simulate paste event const clipboardData = new DataTransfer(); clipboardData.setData('text/plain', currentValue); const pasteEvent = new ClipboardEvent('paste', { bubbles: true, cancelable: true }); Object.defineProperty(pasteEvent, 'clipboardData', { get: () => clipboardData }); textarea.dispatchEvent(pasteEvent); // Directly trigger React’s onChange handler const changeEvent = new Event('change', { bubbles: true }); textarea.value = currentValue; textarea.dispatchEvent(changeEvent); // Manually trigger input and change events to ensure React updates state textarea.dispatchEvent(new Event('input', { bubbles: true })); textarea.dispatchEvent(new Event('change', { bubbles: true })); // Optionally simulate submit action const submitButton = document.querySelector('button[type="submit"]'); if (submitButton) submitButton.click(); } } function favouritesRegenerateButton(remove = false) { let generateButton = findButtonByInnerText('Generate', '.outline-2'); if (!generateButton) return; let regenerateBtn = document.querySelector('#favouriteRegenerateAutomation'); if (remove && !!regenerateBtn){ regenerateBtn.remove(); return; } if (!!regenerateBtn || remove) return; // console.log("Adding because remove =",remove) regenerateBtn = document.createElement('button'); regenerateBtn.id = 'favouriteRegenerateAutomation'; regenerateBtn.textContent = regenerateBtnText; regenerateBtn.classList.add('mr-3', 'grid-cols-[auto_1fr_auto]', 'focus:outline', 'outline-2', 'outline-offset-1', 'duration-75', 'disabled:text-black/50', 'disabled:bg-black/15', 'dark:disabled:text-white/50', 'dark:disabled:bg-white/15', 'text-white', 'bg-purple-600', 'hover:bg-purple-500', 'outline-purple-600', 'dark:outline-purple-500', 'text-sm', '[--ui-size:theme(spacing.8)]', 'w-full', 'mlg:w-auto', 'h-[--height]', 'flex', 'items-center', 'transition', 'font-semibold', 'dense:text-sm', 'px-4', 'mlg:py-2', 'dense:px-3', 'dense:py-0.5', 'rounded-xl', 'mlg:rounded-lg', 'dense:rounded-md'); regenerateBtn.classList.add('bg-gradient-member'); generateButton.parentNode.insertBefore(regenerateBtn, generateButton); regenerateBtn.addEventListener('click', favouriteRegenerateAutomation); let tasksTab = findButtonByInnerText('Tasks', '[role="tab"]'); tasksTab.addEventListener('click', () => favouritesRegenerateButton(true)); } async function favouriteRegenerateAutomation(){ let regenerateBtn = document.querySelector('#favouriteRegenerateAutomation'); if (!!regenerateBtn && regenerateBtn.getAttribute('data-running') === 'true') { console.log("Stopping") stopGenerationLoop = true; stoppedFavouriteLoopButtonDOM(regenerateBtn); return; } let generateButton = findButtonByInnerText('Generate', '.outline-2'); if (!generateButton || !regenerateBtn) throw new Error('Generate button or regenerate button not found. What the actual..? Impossible!'); let currentlySelectedItem = document.querySelector(selectedListThumbSelector); if(!currentlySelectedItem) { alert("Please select the oldest item to start") return; } regenerateBtn.textContent = 'Stop!'; regenerateBtn.classList.remove('bg-gradient-member'); regenerateBtn.setAttribute('data-running', 'true'); stopGenerationLoop = false; let successCount = 0; while (true) { if(successCount > 0) await new Promise(resolve => setTimeout(resolve, 1000)); if(stopGenerationLoop) break; let selectedButton = document.querySelector('button.outline'); console.info("Start wait for Generate Button") while (!findButtonByInnerText('Generate', ".outline-2")) { await new Promise(resolve => setTimeout(resolve, 100)); } console.info("End wait for Generate Button") generateButton.click(); console.log("Waiting for toast") const toast = await waitForToastify(); console.log("Toast found"); let toastText = toast.textContent; let closeBtn = toast.querySelector('.Toastify__close-button'); if(!!closeBtn) { closeBtn.click(); console.log("Closed toast"); } if(stopGenerationLoop) break; if (toastText.includes('submitted')) { console.log('Success for',document.querySelector(promptTextareaSelector).value); successCount++; } else if (toastText.includes('error')) { console.log('Error, retrying...'); continue; } else if(document.body.innerText.includes("Too many tasks in queue")) { console.log("Too many tasks in queue"); findButtonByInnerText('OK').click(); continue; } let newestElement = document.querySelector('[data-state="active"][role="tabpanel"] button'); // console.log("Newest element:", newestElement); if(selectedButton === newestElement){ console.log("End reached"); break; } if (!arrowNavigation(-1, selectedButton)) break; } console.log("Success count:",successCount) stoppedFavouriteLoopButtonDOM(regenerateBtn); } function stoppedFavouriteLoopButtonDOM(regenerateBtn){ regenerateBtn.textContent = regenerateBtnText; regenerateBtn.classList.add('bg-gradient-member'); regenerateBtn.removeAttribute('data-running'); } async function waitForToastify() { return new Promise(resolve => { const checkToast = setInterval(() => { if (stopGenerationLoop) { clearInterval(checkToast); return; } const toastElement = document.querySelector('.Toastify'); let messageText = toastElement.textContent; if (!!toastElement && (messageText.includes('submitted') || messageText.includes('fail'))) { latestToastMessage = messageText; clearInterval(checkToast); resolve(toastElement); } }, 100); }); } function arrowNavigation(direction = -1, givenButton = null) { let selectedButton = givenButton || document.querySelector('button.outline'); if (!selectedButton) return; let tileHeight = selectedButton.offsetHeight; let scrollContainer = document.querySelector(scrollListSelector); if (!scrollContainer) return console.error("There is no scrollContainer"); let positionFromTop = selectedButton.getBoundingClientRect().top - scrollContainer.getBoundingClientRect().top; if (positionFromTop <= tileHeight) { scrollContainer.scrollTop -= tileHeight * 1.5; } let buttons = Array.from(document.querySelectorAll('button')); let selectedIndex = buttons.indexOf(selectedButton); if (selectedIndex <= 0) return; let previousButton = buttons[selectedIndex + direction]; if (!previousButton) { console.log("No more buttons") return false; } previousButton.click(); return true; } function updateListeners() { if(isTabActive('favorites')){ thumbIcons = []; favouritesRegenerateButton(); console.log("Returning because we are inside Favourites"); return; } // console.info("Running checks for listeners inside updateListeners()"); thumbIcons.forEach(icon => { if (!icon.thumbClickListenerAdded) { icon.addEventListener('click', thumbListener); icon.thumbClickListenerAdded = true; } }); favouritesRegenerateButton(true); // remove } async function detectScroll() { let scroller = await waitForElements(scrollListSelector); scroller[0].addEventListener('scroll', () => { requestAnimationFrame(() => updateThumbs(true)); }); } const scale = (x) => { if (x >= 2) return 1.5; if (x < 1) return x; return 0.5 * (x - 1) + 1; }; function awakeTextarea(textarea, input = null) { textarea.focus(); if (!input) { textarea.value += ' '; } else { textarea.value = input; } textarea.dispatchEvent(new InputEvent('input', { bubbles: true })); setTimeout(() => { textarea.value = textarea.value.slice(0, -1); textarea.dispatchEvent(new InputEvent('input', { bubbles: true })); textarea.dispatchEvent(new Event('change', { bubbles: true })); textarea.blur(); }, 500); }; const textareaPasteFix = async (promptTextareaSelector) => { const textarea = await waitForElement(promptTextareaSelector); textarea.addEventListener('paste', (event) => { const clipboardData = event.clipboardData.getData('text/plain'); const modifiedText = clipboardData .replace(/(\d+) year old/g, '$1yo') .replace(/(\d+) years old/g, '$1yo') .replace(/(\d+) years/g, '$1yo') .replace(/(\d+) years-old/g, '$1yo') .replace(/(\d+) year-old/g, '$1yo') .replace(/(\d+)-year-old/g, '$1yo') .replace(/(\d+)-years-old/g, '$1yo') .replace(/thx/g, 'thanks') .replace(/\(\(/g, '(') .replace(/\)\)/g, ')') .replace(/:(\d+(\.\d+)?)/g, (match, num) => { const scaledNum = scale(parseFloat(num)); return `:${Math.round(scaledNum * 10) / 10}`; }); if (clipboardData !== modifiedText) { event.preventDefault(); const { selectionStart: start, selectionEnd: end } = textarea; const textBefore = textarea.value.slice(0, start); const textAfter = textarea.value.slice(end); textarea.value = textBefore + modifiedText + textAfter; textarea.selectionStart = textarea.selectionEnd = start + modifiedText.length; awakeTextarea(textarea); } }); }; await waitForFocus(); const textarea = await waitForElement(promptTextareaSelector, 60*60); textarea.addEventListener('focus', () => { shortcuts.setContext('isInput', true); // console.log("isInput", true) }); textarea.addEventListener('blur', () => { shortcuts.setContext('isInput', false); // console.log("isInput", false) }); window.addEventListener('blur', () => { if(!previewListCheckListener) return; previewListCheckListener.disconnect(); console.log('previewListCheckListener has been disconnected'); }); window.addEventListener('focus', () => { startScrollListListener(); console.log('startScrollListListener has been triggered'); }); function startScrollListListener() { const targetNode = document.querySelector(scrollListSelector); if (!targetNode) { console.log("Returning because there is no scrollListSelector inside startScrollListListener()"); return; } previewListCheckListener = new MutationObserver(mutations => { mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node.nodeType !== 1 || !node.matches('[data-label="check"]')) return; const parent = node.parentElement; const id = extractPreviewId(parent); if (!openedPreviewCache.get(id)) { node.remove(); console.log("Removed", node, "because", id, "not present") } }); }); }); previewListCheckListener.observe(targetNode, { childList: true, subtree: true }); } startScrollListListener(); textareaPasteFix(promptTextareaSelector) upsertDatabase(1) detectScroll(); slideShowLifetimeMonitor(); slideShowDowloadButtonManager(); createEnforceNegativeCheckbox(); updateThumbs(true); window.addEventListener('focus', () => { updateThumbs(true); highlightOpenThumbnail(); }); let scrollList = await waitForElement(scrollListSelector); // let scrollList = document.querySelector(scrollListSelector); scrollList.addEventListener('mouseenter', () => updateThumbs(true)); scrollList.addEventListener('mouseleave', () => updateThumbs(true)); scrollList.addEventListener('mousemove', () => updateThumbs()); GM_addStyle(` #favouriteRegenerateAutomation:hover{ filter: brightness(1.1) contrast(1.05); } .black-bg{ opacity: 1 !important; } .pswp__bg{ transition: opacity .5s cubic-bezier(0.25,0.1,0.25,1); } .selected-thumb{ outline: 2px solid hsla(0, 12%, 85.3%, 0.77); transition: outline 100ms; } #app .ring-2{ box-shadow: none; } [data-test-id="virtuoso-item-list"] .contents>button img{ cursor:pointer; transition: filter .1s ease; } [data-test-id="virtuoso-item-list"] .contents>button img:hover{ filter: brightness(1.05); } [data-label="check"] { position: absolute; left: 0; bottom: 0; background: #ffffff2b; border-top-right-radius: 5px; backdrop-filter: blur(10px); filter: brightness(1.3); transition: opacity .5s ease; opacity: 0; } #custom-download{ width: 75px; height: 100px; margin-top: -50px; position: absolute; top: 50%; right: calc(75px + .5rem); display: flex; justify-content: center; align-items: center; opacity:0; will-change: opacity; transition: opacity 333ms cubic-bezier(0.4, 0, 0.22, 1); } #custom-download.show{ opacity:1; } #custom-download.hide{ opacity:0; } #custom-download > svg{ fill: var(--pswp-icon-color); /* color: var(--pswp-icon-color-secondary); */ width: 60px; height: 60px; } #custom-download > svg > .pswp__icn-shadow { stroke-width: 1px; } button[aria-label="Download"][type="button"]{ display:none } `); })();