Last active
December 3, 2025 20:35
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ========================================================== | |
| // Universal WebAction Engine | |
| // - 検索 / チャットの区別を完全撤廃 | |
| // - 「いい感じの入力欄を1つ見つけて、文字を入れて送信」だけやる | |
| // - WEB_ACTION_RULES = 世界AIサービス辞典(国別) | |
| // * input: "mindra-dummy" → 未確認 | |
| // * input: "mindra-ok" → 汎用でOK | |
| // * input: "mindra-ng" → 汎用でNG(専用ルートあり) | |
| // ========================================================== | |
| // ---------------------------------------------------------- | |
| // Pseudo-selector 判定(JS 側) | |
| // ---------------------------------------------------------- | |
| function isMindraPseudoSelector(sel) { | |
| return sel === "mindra-dummy" || sel === "mindra-ok" || sel === "mindra-ng"; | |
| } | |
| // ---------------------------------------------------------- | |
| // 世界 AI サイト辞典(WEB_ACTION_RULES) | |
| // - 1件ごとに「どこの何か」をコメントで明記 | |
| // ---------------------------------------------------------- | |
| const WEB_ACTION_RULES = [ | |
| // ===== Global / US ===== | |
| { | |
| // [US] OpenAI ChatGPT | |
| match: /https?:\/\/(chatgpt\.com|chat\.openai\.com)\//, | |
| chat: { | |
| input: | |
| '#prompt-textarea, div#prompt-textarea, textarea[role="textbox"], div[contenteditable="true"][role="textbox"]', | |
| sendButton: 'button[data-testid="send-button"]', | |
| }, | |
| }, | |
| { | |
| // [US] OpenAI Platform / Playground | |
| match: /https?:\/\/platform\.openai\.com\//, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| { | |
| // [US] Anthropic Claude | |
| match: /https?:\/\/claude\.ai\//, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| { | |
| // [US] Perplexity(汎用NG → 専用ルート使用) | |
| match: /https?:\/\/(www\.)?perplexity\.ai\//, | |
| chat: { input: "mindra-ng" }, | |
| }, | |
| { | |
| // [US] Microsoft Copilot Web | |
| match: /https?:\/\/copilot\.microsoft\.com\//, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| { | |
| // [US] Bing Chat / Copilot in Bing | |
| match: /https?:\/\/www\.bing\.com\/chat/, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| { | |
| // [US] xAI Grok (grok.com) | |
| match: /https?:\/\/grok\.com\//, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| { | |
| // [US] xAI Grok on X | |
| match: /https?:\/\/x\.com\/i\/grok/, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| { | |
| // [US] Meta AI(Meta / Facebook / Instagram / Threads) | |
| match: | |
| /https?:\/\/(meta\.ai|www\.facebook\.com|www\.instagram\.com|www\.threads\.net)\//, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| { | |
| // [US] Character.AI | |
| match: /https?:\/\/(www\.)?character\.ai\//, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| { | |
| // [US] Poe (Quora) | |
| match: /https?:\/\/poe\.com\//, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| { | |
| // [US] Replika | |
| match: /https?:\/\/replika\.com\//, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| { | |
| // [US] HuggingFace Chat | |
| match: /https?:\/\/huggingface\.co\/chat/, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| { | |
| // [US] Groq Chat / Console | |
| match: /https?:\/\/(groq\.com|console\.groq\.com)\//, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| { | |
| // [US] GitHub Copilot Chat(専用ルート) | |
| match: /https?:\/\/github\.com\/copilot/, | |
| chat: { input: "mindra-ng" }, | |
| }, | |
| // ===== Google 系 ===== | |
| { | |
| // [Google] Gemini | |
| match: /https?:\/\/gemini\.google\.com\//, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| { | |
| // [Google] AI Studio | |
| match: /https?:\/\/aistudio\.google\.com\//, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| { | |
| // [Google] 検索 | |
| match: /https?:\/\/www\.google\./, | |
| search: { | |
| input: 'mindra-ng', | |
| }, | |
| }, | |
| // ===== Europe 系 ===== | |
| { | |
| // [EU] Mistral / Chat | |
| match: /https?:\/\/(mistral\.ai|chat\.mistral\.ai)\//, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| { | |
| // [EU] Aleph Alpha | |
| match: /https?:\/\/(www\.)?aleph-alpha\.com\//, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| { | |
| // [EU] DeepL Chat | |
| match: /https?:\/\/www\.deepl\.com\/chat/, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| { | |
| // [EU] Pi (heyPi) | |
| match: /https?:\/\/heypi\.com\//, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| // ===== China 系 ===== | |
| { | |
| // [CN] Baidu ERNIE / 文心一言 | |
| match: /https?:\/\/(ernie\.baidu\.com|yiyan\.baidu\.com)\//, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| { | |
| // [CN] Alibaba Tongyi / Qwen | |
| match: | |
| /https?:\/\/(chat\.qwen\.ai|tongyi\.aliyun\.com|qwen\.ai|gpt\.aliyun\.com)\//, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| { | |
| // [CN] DeepSeek Chat | |
| match: /https?:\/\/chat\.deepseek\.com\//, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| { | |
| // [CN] Tencent Hunyuan | |
| match: /https?:\/\/hunyuan\.tencent\.com\//, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| { | |
| // [CN] ByteDance Doubao | |
| match: /https?:\/\/doubao\.com\//, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| { | |
| // [CN] SenseTime 日日新 | |
| match: /https?:\/\/ririshin\.com\//, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| { | |
| // [CN] iFlytek Spark | |
| match: /https?:\/\/.*xfyun\.cn\//, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| { | |
| // [CN] Zhipu GLM | |
| match: /https?:\/\/(chatglm\.cn|bigmodel\.cn|chat\.z\.ai)\//, | |
| chat: { | |
| input: 'textarea#chat-input', | |
| // FIXME: GLM かどうか判定するためのフラグ | |
| is_glm: true, | |
| }, | |
| }, | |
| { | |
| // [CN] Kimi | |
| match: /https?:\/\/(www\.)?kimi\.com\//, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| { | |
| // [CN] MiniMax | |
| match: /https?:\/\/agent\.minimax\.io\//, | |
| chat: { | |
| input: 'textarea#chat-input', | |
| }, | |
| }, | |
| // ===== Japan / Korea / Asia ===== | |
| { | |
| // [KR] Naver CLOVA Studio / X | |
| match: /https?:\/\/clovastudio\.ncloud\.com\//, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| { | |
| // [KR] Kakao / KoGPT など | |
| match: /https?:\/\/.*kakao\.com\//, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| { | |
| // [IN] Krutrim | |
| match: /https?:\/\/(www\.)?(krutrim\.com|kruti\.ai)\//, | |
| chat: { | |
| input: 'textarea.txt-primary', | |
| }, | |
| }, | |
| { | |
| // [IN] Hanooman | |
| match: /https?:\/\/(www\.)?hanooman\.ai\//, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| { | |
| // [IN] Sarvam AI | |
| match: /https?:\/\/(www\.)?sarvam\.ai\//, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| { | |
| // [IN] Bhashini / BharatGPT | |
| match: /https?:\/\/bhashini\.gov\.in\//, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| // ===== Agent / Work / Biz ===== | |
| { | |
| // [US] Jasper AI | |
| match: /https?:\/\/(www\.)?jasper\.ai\//, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| { | |
| // [US] Notion AI | |
| match: /https?:\/\/(www\.)?notion\.so\//, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| { | |
| // [US] Slack / Slack AI | |
| match: /https?:\/\/.*slack\.com\//, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| { | |
| // [US] Zoom / AI Companion | |
| match: /https?:\/\/.*zoom\.us\//, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| { | |
| // [US] AgentGPT | |
| match: /https?:\/\/agentgpt\.reworkd\.ai\//, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| { | |
| // [US] Aomni / Agent系 | |
| match: /https?:\/\/(www\.)?aomni\.com\//, | |
| chat: { input: "mindra-dummy" }, | |
| }, | |
| ]; | |
| // ---------------------------------------------------------- | |
| // 最低限ステート | |
| // ---------------------------------------------------------- | |
| window.UNIVERSAL_SEARCH = { | |
| lastQuery: "", | |
| lastResult: null, | |
| }; | |
| // ---------------------------------------------------------- | |
| // webview URL取得 | |
| // ---------------------------------------------------------- | |
| function getWebviewUrl(wv) { | |
| try { | |
| if (typeof wv.getURL === "function") return wv.getURL(); | |
| } catch (_) {} | |
| try { | |
| if (wv.src) return wv.src; | |
| } catch (_) {} | |
| return ""; | |
| } | |
| // ---------------------------------------------------------- | |
| // URL → WEB_ACTION_RULES | |
| // ---------------------------------------------------------- | |
| function getRuleForWebview(wv) { | |
| const url = getWebviewUrl(wv) || ""; | |
| for (const rule of WEB_ACTION_RULES) { | |
| try { | |
| if (rule.match && rule.match.test(url)) { | |
| return rule; | |
| } | |
| } catch (_) {} | |
| } | |
| return null; | |
| } | |
| // ---------------------------------------------------------- | |
| // 対象webview一覧(表示中のものだけに絞る) | |
| // ---------------------------------------------------------- | |
| // webview が画面上で「見えている」か判定 | |
| function isVisibleWebview(wv) { | |
| try { | |
| const style = window.getComputedStyle(wv); | |
| if (style.display === "none" || style.visibility === "hidden") { | |
| return false; | |
| } | |
| const rect = wv.getBoundingClientRect(); | |
| if (rect.width <= 0 || rect.height <= 0) { | |
| return false; | |
| } | |
| return true; | |
| } catch (_) { | |
| // 何か取れなかった場合は、とりあえず true 扱い(安全側) | |
| return true; | |
| } | |
| } | |
| function getTargets() { | |
| const views = window.mindraViews || {}; | |
| // splitview があるならまずそこから(その中でも表示されているものだけ) | |
| if (typeof views.getSplitWebviews === "function") { | |
| try { | |
| const split = views.getSplitWebviews(); | |
| if (Array.isArray(split) && split.length > 0) { | |
| const visibleSplit = split.filter(isVisibleWebview); | |
| if (visibleSplit.length > 0) return visibleSplit; | |
| // 全部不可視だった場合はそのまま split を返さず、下のフォールバックに回す | |
| } | |
| } catch (_) {} | |
| } | |
| // 全 webview から「見えているもの」だけ | |
| try { | |
| const all = Array.from(document.querySelectorAll("webview")); | |
| if (all.length > 0) { | |
| const visible = all.filter(isVisibleWebview); | |
| if (visible.length > 0) return visible; | |
| return all; // 全部不可視扱いなら一応 all を返す | |
| } | |
| } catch (_) {} | |
| // 最後の保険:アクティブ webview 単体 | |
| if (typeof views.getActiveWebview === "function") { | |
| try { | |
| const active = views.getActiveWebview(); | |
| if (active) return [active]; | |
| } catch (_) {} | |
| } | |
| return []; | |
| } | |
| // ---------------------------------------------------------- | |
| // 注入スクリプト(search/chat 完全統一版) | |
| // - 「候補セレクタのリスト」を 1 本だけ持つ | |
| // ---------------------------------------------------------- | |
| function buildInjectionScript(query, rule) { | |
| const q = JSON.stringify(query); | |
| const cfg = JSON.stringify( | |
| rule | |
| ? { | |
| search: rule.search || null, | |
| chat: rule.chat || null, | |
| } | |
| : null | |
| ); | |
| return ` | |
| (function(q, cfg) { | |
| function isPseudo(sel) { | |
| return sel === 'mindra-dummy' || sel === 'mindra-ng'; | |
| } | |
| function typeAndSend(el, cfg) { | |
| if (!el) return false; | |
| el.focus(); | |
| document.execCommand('insertText', false, q); | |
| try { | |
| el.dispatchEvent(new Event("input", { bubbles: true })); | |
| el.dispatchEvent(new Event("change", { bubbles: true })); | |
| } catch (e) {} | |
| // ここから少し待ってから送信する | |
| setTimeout(function () { | |
| var sent = false; | |
| // ルールに sendButton があれば優先してクリック | |
| var btnSel = cfg && cfg.chat && cfg.chat.sendButton; | |
| if (btnSel && !isPseudo(btnSel)) { | |
| try { | |
| var btn = document.querySelector(btnSel); | |
| if (btn) { | |
| btn.click(); | |
| sent = true; | |
| } | |
| } catch (e) {} | |
| } | |
| // クリックで送れなかった場合は Enter で送信 | |
| if (!sent) { | |
| try { | |
| var ev = { | |
| key: "Enter", | |
| code: "Enter", | |
| keyCode: 13, | |
| which: 13, | |
| bubbles: true, | |
| }; | |
| el.dispatchEvent(new KeyboardEvent("keydown", ev)); | |
| el.dispatchEvent(new KeyboardEvent("keypress", ev)); | |
| el.dispatchEvent(new KeyboardEvent("keyup", ev)); | |
| } catch (e) {} | |
| } | |
| // さらに保険として、フォーム経由なら submit も試す | |
| // FIXME: GLM ではトップページに戻ってしまうので実行しない(Claude では Enter で送信できないので実行する必要がある) | |
| if (!cfg?.chat?.is_glm) { | |
| try { | |
| var form = el.form; | |
| if (form) { | |
| form.submit(); | |
| } | |
| } catch (e) {} | |
| } | |
| }, 150); // ← ここでちょっと待つ | |
| // 非同期送信なので true 固定でOK | |
| return true; | |
| } | |
| // ===== ルール由来の優先セレクタ ===== | |
| var ruleSelectors = []; | |
| if (cfg && cfg.search && cfg.search.input && !isPseudo(cfg.search.input)) { | |
| ruleSelectors.push(cfg.search.input); | |
| } | |
| if (cfg && cfg.chat && cfg.chat.input && !isPseudo(cfg.chat.input)) { | |
| ruleSelectors.push(cfg.chat.input); | |
| } | |
| // ===== 汎用セレクタ ===== | |
| var unifiedSelectors = [ | |
| // 検索系 | |
| 'form[action="/search"] input[name="q"]', | |
| 'input[type="search"]', | |
| 'input[role="searchbox"]', | |
| 'input[name="q"]', | |
| 'input[placeholder*="検索"]', | |
| 'input[placeholder*="Search"]', | |
| // チャット系 | |
| 'textarea[placeholder*="メッセージ"]', | |
| 'textarea[placeholder*="message"]', | |
| 'textarea[role="textbox"]', | |
| 'div[contenteditable="true"][role="textbox"]', | |
| 'div[contenteditable="true"][data-lexical-editor="true"]', | |
| 'div[contenteditable="true"][data-placeholder]', | |
| 'div[contenteditable="true"]' | |
| ]; | |
| // ===== 候補の決定 ===== | |
| var target = null; | |
| // 1) ルールで指定されているセレクタを優先 | |
| for (var i = 0; i < ruleSelectors.length && !target; i++) { | |
| try { | |
| var elRule = document.querySelector(ruleSelectors[i]); | |
| if (elRule) target = elRule; | |
| } catch (_) {} | |
| } | |
| // 2) ルールで見つからなければ汎用セレクタから探す | |
| if (!target) { | |
| for (var j = 0; j < unifiedSelectors.length && !target; j++) { | |
| try { | |
| var elU = document.querySelector(unifiedSelectors[j]); | |
| if (elU) target = elU; | |
| } catch (_) {} | |
| } | |
| } | |
| // 3) 最後の保険 | |
| if (!target) { | |
| target = | |
| document.querySelector('input[type="text"]') || | |
| document.querySelector("textarea") || | |
| document.querySelector('[contenteditable="true"]'); | |
| } | |
| if (!target) { | |
| return "none"; | |
| } | |
| var ok = typeAndSend(target, cfg || {}); | |
| return ok ? "ok" : "none"; | |
| })(${q}, ${cfg}); | |
| `; | |
| } | |
| // ---------------------------------------------------------- | |
| // runActionInWebview :専用ルート → 汎用ルート | |
| // ---------------------------------------------------------- | |
| async function runActionInWebview(wv, query) { | |
| const url = getWebviewUrl(wv) || ""; | |
| // ========= Google 検索(トップ / 結果 共通) 専用ルート ========== | |
| if (/https?:\/\/www\.google\./.test(url)) { | |
| const code = `(function(q){ | |
| // トップページ・検索結果ページ両方に対応するため、textarea / input 両方を見る | |
| var input = | |
| document.querySelector('form[action="/search"] textarea[name="q"]') || | |
| document.querySelector('form[action="/search"] input[name="q"]') || | |
| document.querySelector('textarea[name="q"]') || | |
| document.querySelector('input[name="q"]'); | |
| if (!input) return "google:no-input"; | |
| input.focus(); | |
| if ("value" in input) { | |
| input.value = q; | |
| } else { | |
| input.textContent = q; | |
| } | |
| try { | |
| input.dispatchEvent(new Event('input', { bubbles: true })); | |
| input.dispatchEvent(new Event('change', { bubbles: true })); | |
| } catch (_) {} | |
| setTimeout(function () { | |
| try { | |
| // 検索実行ボタンいろいろ | |
| var btn = | |
| document.querySelector('button[aria-label="検索"]') || | |
| document.querySelector('button[aria-label="Google 検索"]') || | |
| document.querySelector('button[aria-label="Search"]') || | |
| document.querySelector('input[name="btnK"][type="submit"]'); | |
| if (btn) { | |
| btn.click(); | |
| } else if (input.form) { | |
| // フォームがあれば submit | |
| input.form.submit(); | |
| } else { | |
| // それでもダメなら Enter を投げる | |
| var evInit = { | |
| key: 'Enter', | |
| code: 'Enter', | |
| keyCode: 13, | |
| which: 13, | |
| bubbles: true | |
| }; | |
| ['keydown','keypress','keyup'].forEach(function(t){ | |
| input.dispatchEvent(new KeyboardEvent(t, evInit)); | |
| }); | |
| } | |
| } catch (_) {} | |
| }, 150); | |
| return "google:ok"; | |
| })(${JSON.stringify(query)});`; | |
| try { | |
| if (typeof wv.executeJavaScript !== "function") return "error:no-exec"; | |
| const result = await wv.executeJavaScript(code, false); | |
| return result || "none"; | |
| } catch (_) { | |
| return "error:exception"; | |
| } | |
| } | |
| // ========= Perplexity 専用ルート ========== | |
| if (/perplexity\.ai/.test(url)) { | |
| const code = `(function(q) { | |
| var el = document.querySelector( | |
| 'div#ask-input[contenteditable="true"],' + | |
| 'div[contenteditable="true"][data-test-id="user-textbox"],' + | |
| 'div[contenteditable="true"][data-lexical-editor="true"]' | |
| ); | |
| if (!el) return 'none'; | |
| el.focus(); | |
| try { | |
| var sel = window.getSelection(); | |
| if (sel) { | |
| var range = document.createRange(); | |
| range.selectNodeContents(el); | |
| range.collapse(false); | |
| sel.removeAllRanges(); | |
| sel.addRange(range); | |
| } | |
| } catch (_) {} | |
| try { | |
| document.execCommand('selectAll', false, null); | |
| document.execCommand('delete', false, null); | |
| var ok = false; | |
| try { ok = document.execCommand('insertText', false, q); } catch (_) {} | |
| if (!ok) el.textContent = q; | |
| } catch (_) { | |
| el.textContent = q; | |
| } | |
| try { | |
| el.dispatchEvent(new Event('input', { bubbles: true })); | |
| el.dispatchEvent(new Event('change', { bubbles: true })); | |
| } catch (_) {} | |
| return 'chat-perplexity-typed'; | |
| })(${JSON.stringify(query)});`; | |
| try { | |
| if (typeof wv.executeJavaScript !== "function") return "error:no-exec"; | |
| const result = await wv.executeJavaScript(code, false); | |
| return result || "none"; | |
| } catch (_) { | |
| return "error:exception"; | |
| } | |
| } | |
| // ========= GitHub Copilot 専用ルート ========== | |
| if (/github\.com\/copilot/.test(url)) { | |
| const code = `(function(q) { | |
| var el = document.querySelector('textarea'); | |
| if (!el) return 'none'; | |
| el.focus(); | |
| el.value = q; | |
| try { | |
| el.dispatchEvent(new Event('input', { bubbles: true })); | |
| el.dispatchEvent(new Event('change', { bubbles: true })); | |
| } catch (_) {} | |
| return 'chat-copilot-typed'; | |
| })(${JSON.stringify(query)});`; | |
| if (typeof wv.executeJavaScript !== "function") return "error:no-exec"; | |
| const result = await wv.executeJavaScript(code, false); | |
| return result || "none"; | |
| } | |
| // ========= Manus 専用ルート ========== | |
| if (/manus\.im\/app/.test(url)) { | |
| const code = `(function(q) { | |
| var el = | |
| document.querySelector('textarea[placeholder*="タスクを割り当てて"]') || | |
| document.querySelector('textarea'); | |
| if (!el) return 'none'; | |
| el.focus(); | |
| el.value = q; | |
| try { | |
| el.dispatchEvent(new Event('input', { bubbles: true })); | |
| el.dispatchEvent(new Event('change', { bubbles: true })); | |
| } catch (_) {} | |
| setTimeout(() => { | |
| try { | |
| var evInit = { | |
| key: 'Enter', | |
| code: 'Enter', | |
| keyCode: 13, | |
| which: 13, | |
| bubbles: true | |
| }; | |
| ['keydown', 'keypress', 'keyup'].forEach(t => { | |
| el.dispatchEvent(new KeyboardEvent(t, evInit)); | |
| }); | |
| } catch (_) {} | |
| }, 150); | |
| return 'chat-manus'; | |
| })(${JSON.stringify(query)});`; | |
| if (typeof wv.executeJavaScript !== "function") return "error:no-exec"; | |
| const result = await wv.executeJavaScript(code, false); | |
| return result || "none"; | |
| } | |
| // ========= ここから汎用ルート ========== | |
| const rule = getRuleForWebview(wv); | |
| const code = buildInjectionScript(query, rule); | |
| try { | |
| if (typeof wv.executeJavaScript !== "function") return "error:no-exec"; | |
| const result = await wv.executeJavaScript(code, false); | |
| return result || "none"; | |
| } catch (_) { | |
| return "error:exception"; | |
| } | |
| } | |
| // ---------------------------------------------------------- | |
| // エントリ(第2引数は互換のため受け取るだけで無視) | |
| // ---------------------------------------------------------- | |
| window.runUniversalSearch = async function (query, _ignored) { | |
| const targets = getTargets(); | |
| if (targets.length === 0) { | |
| const msg = | |
| "いい感じの入力欄が見つからなくて、操作できなかったよ。"; | |
| window.UNIVERSAL_SEARCH.lastQuery = query; | |
| window.UNIVERSAL_SEARCH.lastResult = { targets: 0 }; | |
| window.dispatchUniversalSearchResult(msg); | |
| return msg; | |
| } | |
| let successCount = 0; | |
| for (const wv of targets) { | |
| const status = await runActionInWebview(wv, query); | |
| if (status && !String(status).startsWith("error") && status !== "none") { | |
| successCount++; | |
| } | |
| } | |
| const msg = | |
| successCount === 0 | |
| ? "いい感じの入力欄が見つからなくて、何もできなかったよ。" | |
| : "送信したよ"; | |
| window.UNIVERSAL_SEARCH.lastQuery = query; | |
| window.UNIVERSAL_SEARCH.lastResult = { targets: targets.length }; | |
| window.dispatchUniversalSearchResult(msg); | |
| return msg; | |
| }; | |
| // ---------------------------------------------------------- | |
| // AI サイドバーへ結果通知 | |
| // ---------------------------------------------------------- | |
| window.dispatchUniversalSearchResult = function (msg) { | |
| if (window.mindraAI?.receiveSearchResult) { | |
| window.mindraAI.receiveSearchResult(msg); | |
| } else { | |
| //_ignore | |
| } | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment