(function initGeminiOps(){ const S = { promptInput: [ 'div.ql-editor[contenteditable="true"][role="textbox"]', '[contenteditable="true"][aria-label*="Gemini"]', '[contenteditable="true"][data-placeholder*="Gemini"]', 'div[contenteditable="true"][role="textbox"]' ], actionBtn: [ '.send-button-container button.send-button', '.send-button-container button' ], newChatBtn: [ '[data-test-id="new-chat-button"] a', '[data-test-id="new-chat-button"]', 'a[aria-label="发起新对话"]', 'a[aria-label*="new chat" i]' ], modelBtn: [ 'button:has-text("Gemini")', '[role="button"][aria-haspopup="menu"]' ] }; function visible(el){ if(!el) return false; const r=el.getBoundingClientRect(); const st=getComputedStyle(el); return r.width>0 && r.height>0 && st.display!=='none' && st.visibility!=='hidden'; } function q(sel){ try{ if(sel.includes(':has-text(')){ const m=sel.match(/^(.*):has-text\("(.*)"\)$/); if(!m) return null; const nodes=[...document.querySelectorAll(m[1]||'*')]; return nodes.find(n=>visible(n)&&n.textContent?.includes(m[2]))||null; } return [...document.querySelectorAll(sel)].find(visible)||null; }catch{return null;} } function find(key){ for(const s of (S[key]||[])){ const el=q(s); if(el) return el; } return null; } function click(key){ const el=find(key); if(!el) return {ok:false,key,error:'not_found'}; el.click(); return {ok:true,key}; } function fillPrompt(text){ const el=find('promptInput'); if(!el) return {ok:false,error:'prompt_not_found'}; el.focus(); if(el.tagName==='TEXTAREA'){ el.value=text; el.dispatchEvent(new Event('input',{bubbles:true})); }else{ document.execCommand('selectAll',false,null); document.execCommand('insertText',false,text); el.dispatchEvent(new Event('input',{bubbles:true})); } return {ok:true}; } function getStatus(){ const btn=find('actionBtn'); if(!btn) return {status:'unknown',error:'btn_not_found'}; const label=(btn.getAttribute('aria-label')||'').trim(); const disabled=btn.getAttribute('aria-disabled')==='true'; if(/停止|Stop/i.test(label)) return {status:'loading',label}; if(/发送|Send|Submit/i.test(label)) return {status:'ready',label,disabled}; return {status:'idle',label,disabled}; } /* ── 保活式轮询 ── * 不在页面内做长 Promise 等待(会导致 CDP 连接因长时间无消息被网关判定空闲断开)。 * 改为:调用端每 8-10s evaluate 一次 GeminiOps.pollStatus(),立即拿到结果。 * 调用端自行累计耗时并判断超时。 */ function pollStatus(){ var s=getStatus(); // 顺便返回页面可见性,帮助调用端判断 tab 是否还活着 return {status:s.status, label:s.label, pageVisible:!document.hidden, ts:Date.now()}; } /* ── 最新图片获取与下载 ── * Gemini 一次只生成一张图片,流程上只关心最新生成的那张。 * DOM 中 img.image.loaded 按顺序排列,最后一个即为最新生成。 * * DOM 结构: *
* *
* *
*
*/ function _findContainer(img){ var el=img; while(el&&el!==document.body){ if(el.classList&&el.classList.contains('image-container')) return el; el=el.parentElement; } return null; } function _findDownloadBtn(container){ if(!container) return null; return container.querySelector('mat-icon[fonticon="download"]') || container.querySelector('mat-icon[data-mat-icon-name="download"]') || null; } /** 获取最新生成的一张图片信息(DOM 中最后一个 img.image.loaded) */ function getLatestImage(){ var imgs=[...document.querySelectorAll('img.image.loaded')]; if(!imgs.length) return {ok:false, error:'no_loaded_images'}; var img=imgs[imgs.length-1]; var container=_findContainer(img); var dlBtn=_findDownloadBtn(container); return { ok: true, src: img.src||'', alt: img.alt||'', width: img.naturalWidth||0, height: img.naturalHeight||0, hasDownloadBtn: !!dlBtn }; } /** 点击最新图片的"下载原图"按钮 */ function downloadLatestImage(){ var imgs=[...document.querySelectorAll('img.image.loaded')]; if(!imgs.length) return {ok:false, error:'no_loaded_images'}; var img=imgs[imgs.length-1]; var container=_findContainer(img); var dlBtn=_findDownloadBtn(container); if(!dlBtn) return {ok:false, error:'download_btn_not_found'}; var clickable=dlBtn.closest('button,[role="button"],.button-icon-wrapper')||dlBtn; clickable.click(); return {ok:true, src:img.src||''}; } function probe(){ var s=getStatus(); return { promptInput: !!find('promptInput'), actionBtn: !!find('actionBtn'), newChatBtn: !!find('newChatBtn'), modelBtn: !!find('modelBtn'), status: s.status }; } window.GeminiOps = {probe, click, fillPrompt, getStatus, pollStatus, getLatestImage, downloadLatestImage, selectors:S, version:'0.7.0'}; })();