// ==UserScript== // @name 豆包自动批量发送 (v0.1.0) // @namespace https://scriptcat.org/zh-CN // @version 0.1.0 // @description 豆包自动批量发送,支持单条和多条发送与自动下载 // @author AI 辅助完成 // @match https://www.doubao.com/* // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_setClipboard // @grant GM_download // @grant GM_xmlhttpRequest // @connect byteimg.com // @connect * // ==/UserScript== (function() { 'use strict'; const STORAGE_KEY_PROMPTS = "db_img_prompts"; const STORAGE_KEY_MIN = "db_interval_min"; const STORAGE_KEY_MAX = "db_interval_max"; const STORAGE_KEY_BATCH = "db_batch_size"; const STORAGE_KEY_AUTODL = "db_auto_download"; const STORAGE_KEY_DL_OLD = "db_dl_historical"; const STORAGE_KEY_SUBFOLDER = "db_dl_subfolder"; const style = document.createElement('style'); style.innerHTML = ` #db-auto-sidebar { position: fixed; top: 0; right: 0; width: 380px; height: 100vh; background: #ffffff; box-shadow: -5px 0 25px rgba(0,0,0,0.15); z-index: 99999; display: flex; flex-direction: column; transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); transform: translateX(0); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; } #db-auto-sidebar.hidden { transform: translateX(100%); } #db-sidebar-trigger { position: fixed; top: 50%; right: 0; transform: translateY(-50%) translateX(100%); background: #3b82f6; color: white; padding: 20px 8px; border-radius: 8px 0 0 8px; cursor: pointer; z-index: 99998; font-size: 14px; font-weight: bold; line-height: 1.5; box-shadow: -2px 0 10px rgba(0,0,0,0.1); transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); display: flex; align-items: center; justify-content: center; text-align: center; } #db-sidebar-trigger.visible { transform: translateY(-50%) translateX(0); } #db-sidebar-header { padding: 18px 20px; border-bottom: 1px solid #edf2f7; background: #f8fafc; display: flex; justify-content: space-between; align-items: center; } #db-sidebar-body { padding: 20px; display: flex; flex-direction: column; flex: 1; overflow: hidden; } ::-webkit-scrollbar { width: 6px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 3px; } ::-webkit-scrollbar-thumb:hover { background: #94a3b8; } .setting-row { font-size: 13px; margin-bottom: 8px; color: #475569; display: flex; justify-content: space-between; align-items: center; background: #f8fafc; padding: 10px 12px; border: 1px solid #e2e8f0; border-radius: 6px; } .setting-row.highlight { background: #eff6ff; border-color: #bfdbfe; } `; document.head.appendChild(style); const container = document.createElement('div'); container.innerHTML = `



🤖 智控画图终端

收起 ➔
待发任务队列 待处理 0
发送模式:
自动下载新图 (雷达模式)
归类文件夹:
自动下载历史旧图
画完冷却: -
📡 实时监控中心
`; document.body.appendChild(container); const sidebar = document.getElementById('db-auto-sidebar'), triggerBtn = document.getElementById('db-sidebar-trigger'), hideBtn = document.getElementById('hide-sidebar-btn'), startBtn = document.getElementById('start-send'), promptInput = document.getElementById('prompt-list'), promptCount = document.getElementById('prompt-count'), logPanel = document.getElementById('log-panel'), minInput = document.getElementById('min-interval'), maxInput = document.getElementById('max-interval'), batchSelect = document.getElementById('send-batch-size'), autoDlCheckbox = document.getElementById('auto-download-cb'), historicalDlCheckbox = document.getElementById('dl-historical-cb'), subfolderInput = document.getElementById('subfolder-name'); let isRunning = false; let isCurrentlyGenerating = false; const processedImageMemory = new Set(); hideBtn.onclick = () => { sidebar.classList.add('hidden'); triggerBtn.classList.add('visible'); }; triggerBtn.onclick = () => { sidebar.classList.remove('hidden'); triggerBtn.classList.remove('visible'); }; function updatePromptCount() { const validPrompts = promptInput.value.split('\n').filter(p => p.trim() !== ''); promptCount.innerText = validPrompts.length; GM_setValue(STORAGE_KEY_PROMPTS, promptInput.value); } promptInput.addEventListener('input', updatePromptCount); autoDlCheckbox.addEventListener('change', () => GM_setValue(STORAGE_KEY_AUTODL, autoDlCheckbox.checked)); historicalDlCheckbox.addEventListener('change', () => GM_setValue(STORAGE_KEY_DL_OLD, historicalDlCheckbox.checked)); batchSelect.addEventListener('change', () => GM_setValue(STORAGE_KEY_BATCH, batchSelect.value)); subfolderInput.addEventListener('input', () => GM_setValue(STORAGE_KEY_SUBFOLDER, subfolderInput.value)); function addLog(msg, type = 'info') { const now = new Date(); const timeStr = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`; const logEntry = document.createElement('div'); logEntry.style.marginBottom = '4px'; logEntry.style.color = type === 'error' ? '#f87171' : (type === 'success' ? '#4ade80' : (type === 'highlight' ? '#fcd34d' : (type === 'dl' ? '#38bdf8' : '#94a3b8'))); logEntry.innerText = `[${timeStr}] ${msg}`; logPanel.appendChild(logEntry); logPanel.scrollTop = logPanel.scrollHeight; } setTimeout(() => { const oldImages = document.querySelectorAll('img[src*="rc_gen_image"]'); oldImages.forEach(img => { let key = img.src.split('?')[0]; processedImageMemory.add(key); }); console.log(`[记忆库] 已静默收录页面原有 ${oldImages.length} 张图片的哈希`); }, 2500); // ========================================== // 核心黑科技:破壁建文件夹引擎 // ========================================== setInterval(async () => { const breakBtn = document.querySelector('[data-testid="chat_input_local_break_button"]'); const autoDlChecked = autoDlCheckbox.checked; const dlHistorical = historicalDlCheckbox.checked; const rawTargetFolder = subfolderInput.value.trim(); if (breakBtn) { isCurrentlyGenerating = true; } const btns = document.querySelectorAll('[data-testid="edit_image_hover_tag_download_btn"]'); if (btns.length > 0) { if (isCurrentlyGenerating || isRunning) { if (autoDlChecked && !breakBtn) { let newFoundCount = 0; for (let btn of btns) { let pNode = btn.parentElement; let imgNode = null; for(let k = 0; k < 6; k++) { if(!pNode) break; imgNode = pNode.querySelector('img[src*="rc_gen_image"]'); if(imgNode) break; pNode = pNode.parentElement; } if (imgNode && imgNode.src) { let imgKey = imgNode.src.split('?')[0]; // 记忆锁 if (processedImageMemory.has(imgKey)) continue; processedImageMemory.add(imgKey); newFoundCount++; if (rawTargetFolder === "") { // 没有写文件夹:直接点官方按钮,混着下 btn.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })); btn.dispatchEvent(new MouseEvent('mouseup', { bubbles: true })); btn.click(); } else { // 写了文件夹:使用满血跨域 GM_download 建文件夹! const safeName = rawTargetFolder.replace(/[\/\\:*?"<>|]/g, ''); const ts = new Date().getTime(); const rn = Math.floor(Math.random() * 10000); // 核心路径构造:"文件夹名/文件名.png" const fileName = `${safeName}/doubao_art_${ts}_${rn}.png`; GM_download({ url: imgNode.src, name: fileName, saveAs: false, // 强制不弹窗 onload: () => {}, onerror: (e) => { if (newFoundCount === 1) addLog(`⚠️ 目录创建受阻(${e.error || '未知'}),已降级防丢图`, 'error'); btn.click(); } }); } // 延时防崩 await new Promise(r => setTimeout(r, 600)); } } if (newFoundCount > 0) { addLog(`✅ 成功将 ${newFoundCount} 张图打包存入本地!`, 'success'); } isCurrentlyGenerating = false; } } else { if (dlHistorical && autoDlChecked) { let historicalFound = 0; for (let btn of btns) { let pNode = btn.parentElement; let imgNode = null; for(let k = 0; k < 6; k++) { if(!pNode) break; imgNode = pNode.querySelector('img[src*="rc_gen_image"]'); if(imgNode) break; pNode = pNode.parentElement; } if (imgNode && imgNode.src) { let imgKey = imgNode.src.split('?')[0]; if (!processedImageMemory.has(imgKey)) { processedImageMemory.add(imgKey); historicalFound++; btn.click(); await new Promise(r => setTimeout(r, 500)); } } } if(historicalFound > 0) addLog(`📚 补下 ${historicalFound} 张历史图完成。`, 'dim'); } else { btns.forEach(btn => { let pNode = btn.parentElement; let imgNode = null; for(let k = 0; k < 6; k++) { if(!pNode) break; imgNode = pNode.querySelector('img[src*="rc_gen_image"]'); if(imgNode) break; pNode = pNode.parentElement; } if (imgNode && imgNode.src) processedImageMemory.add(imgNode.src.split('?')[0]); }); } } } }, 1500); // --- 核心发送引擎 --- async function trySwitchToImageMode() { if (document.body.innerText.includes("描述你想要的图片")) return true; const elements = document.querySelectorAll('div.min-w-0.truncate'); let targetMenu = Array.from(elements).find(el => el.innerText.trim() === "图像生成"); if (targetMenu) { let p = targetMenu; for (let i = 0; i < 4; i++) { if (p) { p.click(); p = p.parentElement; } } await new Promise(r => setTimeout(r, 2000)); } return true; } async function waitUntilFinished() { addLog("正在监控画师进度...", 'dim'); await new Promise(r => setTimeout(r, 3000)); let waitTime = 3; while (waitTime < 180) { if (!isRunning) return false; if (!document.querySelector('[data-testid="chat_input_local_break_button"]')) return true; await new Promise(r => setTimeout(r, 2000)); waitTime += 2; } return true; } async function simulateInputAndSend(text) { const textAreaEditor = document.querySelector('textarea[data-testid="chat_input_input"]'); const slateEditors = document.querySelectorAll('[contenteditable="true"]'); let editor = Array.from(slateEditors).find(el => el.innerText.includes("描述你想要的图片") || el.getAttribute('data-slate-editor') === 'true') || slateEditors[0] || textAreaEditor; if (!editor) return false; editor.focus(); const dt = new DataTransfer(); dt.setData('text/plain', text); editor.dispatchEvent(new ClipboardEvent('paste', { clipboardData: dt, bubbles: true, cancelable: true })); await new Promise(r => setTimeout(r, 200)); if (!(editor.innerText || editor.value || "").includes(text.substring(0,5))) document.execCommand('insertText', false, text); editor.dispatchEvent(new Event('input', { bubbles: true })); await new Promise(r => setTimeout(r, 800)); const sendBtn = document.getElementById('flow-end-msg-send') || document.querySelector('button[data-testid="chat_input_send_button"]'); if (sendBtn && sendBtn.getAttribute('data-disabled') !== 'true') sendBtn.click(); else editor.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true })); for (let i = 0; i < 10; i++) { await new Promise(r => setTimeout(r, 500)); let val = editor.value || editor.innerText || ""; if (!val || val.trim() === "" || val.includes("描述你想要的图片")) return true; } return false; } startBtn.onclick = async () => { if (isRunning) { isRunning = false; startBtn.innerText = '▶ 继续运行'; startBtn.style.background = '#3b82f6'; return; } let prompts = promptInput.value.split('\n').filter(p => p.trim() !== ''); if (prompts.length === 0) return alert("请先输入提示词"); isRunning = true; startBtn.innerText = '⏸ 停止运行'; startBtn.style.background = '#ef4444'; const batchSize = parseInt(batchSelect.value); promptInput.value = ""; updatePromptCount(); addLog(`=== 全自动引擎启动,连发: ${batchSize} ===`, 'success'); await trySwitchToImageMode(); while (prompts.length > 0 && isRunning) { let currentBatch = prompts.splice(0, batchSize); let currentPromptText = currentBatch.join('\n'); addLog(`📊 进度: 剩余 ${prompts.length} 条`, 'highlight'); if (await simulateInputAndSend(currentPromptText)) { await waitUntilFinished(); if (prompts.length > 0 && isRunning) { const wait = Math.floor(Math.random() * (parseInt(maxInput.value) - parseInt(minInput.value) + 1) + parseInt(minInput.value)); addLog(`冷却:${wait} 秒...`, 'dim'); for (let s = wait; s > 0; s--) { if (!isRunning) break; await new Promise(r => setTimeout(r, 1000)); } } } else { addLog(`❌ 发送异常`, 'error'); prompts = currentBatch.concat(prompts); isRunning = false; break; } } if (isRunning && prompts.length === 0) { addLog("🎉 任务处理完毕!", 'success'); isRunning = false; startBtn.innerText = '▶ 开始运行'; startBtn.style.background = '#3b82f6'; } else if (prompts.length > 0) { promptInput.value = prompts.join('\n'); updatePromptCount(); } }; document.getElementById('reset-progress').onclick = () => { if (confirm("确定重置?")) { promptInput.value = ""; updatePromptCount(); logPanel.innerHTML = ""; isRunning = false; startBtn.innerText = "▶ 开始运行"; } }; document.getElementById('copy-logs').onclick = () => { GM_setClipboard(logPanel.innerText); alert("日志已复制"); }; promptInput.value = GM_getValue(STORAGE_KEY_PROMPTS, ""); updatePromptCount(); batchSelect.value = GM_getValue(STORAGE_KEY_BATCH, "1"); autoDlCheckbox.checked = GM_getValue(STORAGE_KEY_AUTODL, true); historicalDlCheckbox.checked = GM_getValue(STORAGE_KEY_DL_OLD, false); subfolderInput.value = GM_getValue(STORAGE_KEY_SUBFOLDER, ""); })();