diff --git a/GoogleAI_Search_Google_Gemini_Deepseek_ChatGPT_UnifiedHarness.js b/GoogleAI_Search_Google_Gemini_Deepseek_ChatGPT_UnifiedHarness.js new file mode 100644 index 0000000..9504ac3 --- /dev/null +++ b/GoogleAI_Search_Google_Gemini_Deepseek_ChatGPT_UnifiedHarness.js @@ -0,0 +1,928 @@ +// ==UserScript== +// @name Unified AI C2 Harness +// @namespace http://tampermonkey.net/ +// @version 1.3 +// @description Unified C2 harness for Google AI Search, ChatGPT, DeepSeek, and Google Gemini. +// @author Vanguard +// @match https://*.google.com/* +// @match http://*.google.com/* +// @match https://chatgpt.com/* +// @match https://chat.openai.com/* +// @match https://chat.deepseek.com/* +// @match https://gemini.google.com/* +// @connect localhost +// @connect 127.0.0.1 +// @grant GM_xmlhttpRequest +// @run-at document-idle +// ==/UserScript== + +(function() { + 'use strict'; + + if (window.top !== window.self) { + return; + } + + const hostname = window.location.hostname; + let currentPlatform = null; + + if (hostname.includes('chatgpt.com') || hostname.includes('chat.openai.com')) { + currentPlatform = 'ChatGPT'; + } else if (hostname.includes('chat.deepseek.com')) { + currentPlatform = 'DeepSeek'; + } else if (hostname.includes('gemini.google.com')) { + currentPlatform = 'Gemini'; + } else if (hostname.includes('google.com')) { + currentPlatform = 'Google'; + } else { + return; // Not a supported platform + } + + console.log(`[Harness] Initializing Unified Harness for ${currentPlatform}...`); + + const API_BASE = "http://localhost:8080/api/relay"; + const POLLING_INTERVAL = 1000; + const STATE_INTERVAL = 2000; + + let isBusy = false; + let isPolling = false; + let pollIntervalId = null; + let stateIntervalId = null; + + let PLATFORM_NAME = ""; + let ID_PREFIX = ""; + let activeActions = {}; + let activeTactics = null; + let reportStateLogic = null; + + // --- TAMPERMONKEY FETCH WRAPPER (CORS BYPASS) --- + function tmFetch(url, options = {}) { + return new Promise((resolve, reject) => { + GM_xmlhttpRequest({ + method: options.method || 'GET', + url: url, + headers: options.headers || {}, + data: options.body, + onload: function(response) { + resolve({ + status: response.status, + json: () => { + try { + return Promise.resolve(JSON.parse(response.responseText)); + } catch (e) { + return Promise.resolve({}); + } + }, + text: () => Promise.resolve(response.responseText) + }); + }, + onerror: function(error) { + reject(error); + } + }); + }); + } + + // --- DOM TACTICS --- + const standardTactics = { + setNativeValue: (element, value) => { + element.focus(); + let success = false; + try { success = document.execCommand('insertText', false, value); } catch (e) {} + if (!success) { + const valueSetter = Object.getOwnPropertyDescriptor(element, 'value')?.set; + const prototype = Object.getPrototypeOf(element); + const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, 'value')?.set; + if (prototypeValueSetter && valueSetter !== prototypeValueSetter) { + prototypeValueSetter.call(element, value); + } else if (valueSetter) { + valueSetter.call(element, value); + } else { + element.value = value; + } + } + element.dispatchEvent(new Event('input', { bubbles: true })); + element.dispatchEvent(new Event('change', { bubbles: true })); + } + }; + + const contentEditableTactics = { + setNativeValue: (element, value) => { + element.focus(); + + // Safely select only the contents of the target element + const selection = window.getSelection(); + const range = document.createRange(); + range.selectNodeContents(element); + selection.removeAllRanges(); + selection.addRange(range); + + try { + document.execCommand('delete', false, null); + document.execCommand('insertText', false, value); + } catch (e) { + console.error("[Harness] execCommand failed, falling back to textContent", e); + element.textContent = value; + } + + element.dispatchEvent(new Event('input', { bubbles: true })); + element.dispatchEvent(new Event('change', { bubbles: true })); + } + }; + + // --- SMART EXTRACTION (Markdown Parsers) --- + function googleHtmlToMarkdown(node) { + if (node.nodeType === Node.TEXT_NODE) return node.textContent; + if (node.nodeType !== Node.ELEMENT_NODE) return ""; + const tag = node.tagName.toUpperCase(); + if (tag === 'BUTTON' || tag === 'SVG' || tag === 'STYLE' || tag === 'SCRIPT' || tag === 'NOSCRIPT') return ""; + if (node.style && node.style.display === 'none') return ""; + if (node.getAttribute('aria-hidden') === 'true') return ""; + if (node.classList) { + if (node.classList.contains('DHPVt') || node.classList.contains('YHsVn') || node.classList.contains('bQ0Yzc') || node.classList.contains('CxFouc') || node.classList.contains('ZFcyjd') || node.classList.contains('lHqILb')) return ""; + } + if (node.hasAttribute('data-xpm-latex')) return node.getAttribute('data-xpm-latex'); + if (tag === 'PRE') return `\n\n\`\`\`\n${node.textContent}\n\`\`\`\n\n`; + if (tag === 'BR') return '\n'; + let md = ""; + for (let child of node.childNodes) md += googleHtmlToMarkdown(child); + if (tag === 'P' || tag === 'DIV') { + const displayStyle = node.style ? node.style.display : ''; + if (displayStyle.includes('inline')) return md; + return `\n\n${md}\n\n`; + } + if (tag === 'STRONG' || tag === 'B') return `**${md}**`; + if (tag === 'EM' || tag === 'I') return `*${md}*`; + if (tag === 'CODE') return `\`${md}\``; + if (tag.match(/^H[1-6]$/)) return `\n\n${'#'.repeat(parseInt(tag[1]))} ${md}\n\n`; + if (tag === 'LI') return `\n- ${md.trim()}`; + if (tag === 'UL' || tag === 'OL') return `\n${md}\n`; + if (tag === 'A') return `[${md}](${node.getAttribute('href') || ''})`; + return md; + } + + function chatgptHtmlToMarkdown(node) { + if (node.nodeType === Node.TEXT_NODE) return node.textContent; + if (node.nodeType !== Node.ELEMENT_NODE) return ""; + const tag = node.tagName.toUpperCase(); + if (tag === 'BUTTON' || tag === 'SVG' || tag === 'STYLE' || tag === 'SCRIPT' || tag === 'NOSCRIPT') return ""; + if (node.style && node.style.display === 'none') return ""; + if (node.getAttribute('aria-hidden') === 'true') return ""; + if (tag === 'PRE') { + const codeNode = node.querySelector('code'); + const codeText = codeNode ? codeNode.textContent : node.textContent; + return `\n\n\`\`\`\n${codeText}\n\`\`\`\n\n`; + } + if (tag === 'BR') return '\n'; + let md = ""; + for (let child of node.childNodes) md += chatgptHtmlToMarkdown(child); + if (tag === 'P' || tag === 'DIV') return `\n\n${md}\n\n`; + if (tag === 'STRONG' || tag === 'B') return `**${md}**`; + if (tag === 'EM' || tag === 'I') return `*${md}*`; + if (tag === 'CODE') return `\`${md}\``; + if (tag.match(/^H[1-6]$/)) return `\n\n${'#'.repeat(parseInt(tag[1]))} ${md}\n\n`; + if (tag === 'LI') return `\n- ${md.trim()}`; + if (tag === 'UL' || tag === 'OL') return `\n${md}\n`; + if (tag === 'A') { + const href = node.getAttribute('href') || ''; + const cleanMd = md.trim(); + if (cleanMd && (href.includes(cleanMd) || cleanMd.includes(href))) return md; + return `[${md}](${href})`; + } + return md; + } + + function deepseekHtmlToMarkdown(node) { + if (node.nodeType === Node.TEXT_NODE) return node.textContent; + if (node.nodeType !== Node.ELEMENT_NODE) return ""; + const tag = node.tagName.toUpperCase(); + if (tag === 'BUTTON' || tag === 'SVG' || tag === 'STYLE' || tag === 'SCRIPT' || tag === 'NOSCRIPT') return ""; + if (node.style && node.style.display === 'none') return ""; + if (node.getAttribute('aria-hidden') === 'true') return ""; + if (tag === 'PRE') return `\n\n\`\`\`\n${node.textContent}\n\`\`\`\n\n`; + if (tag === 'BR') return '\n'; + let md = ""; + for (let child of node.childNodes) md += deepseekHtmlToMarkdown(child); + if (tag === 'P' || tag === 'DIV') return `\n\n${md}\n\n`; + if (tag === 'STRONG' || tag === 'B') return `**${md}**`; + if (tag === 'EM' || tag === 'I') return `*${md}*`; + if (tag === 'CODE') return `\`${md}\``; + if (tag.match(/^H[1-6]$/)) return `\n\n${'#'.repeat(parseInt(tag[1]))} ${md}\n\n`; + if (tag === 'LI') return `\n- ${md.trim()}`; + if (tag === 'UL' || tag === 'OL') return `\n${md}\n`; + if (tag === 'A') { + const href = node.getAttribute('href') || ''; + const cleanMd = md.trim(); + if (cleanMd && (href.includes(cleanMd) || cleanMd.includes(href))) return md; + return `[${md}](${href})`; + } + return md; + } + + // --- ACTION MODULES --- + + const googleActions = { + SHUTDOWN: async () => { + clearInterval(pollIntervalId); + clearInterval(stateIntervalId); + console.log("[Harness] Shutdown command received. Loops stopped."); + return "Harness shut down."; + }, + GENERATE: async (payload) => { + const textareas = Array.from(document.querySelectorAll('textarea')).filter(el => el.offsetWidth > 0 && el.offsetHeight > 0); + let input = textareas[textareas.length - 1]; + if (!input) input = document.querySelector('input[name="q"]'); + if (!input) return "Error: Prompt textarea not found on this Google page. SGE may not be active."; + + const preSubmitParagraphs = document.querySelectorAll('.n6owBd'); + let preSubmitText = ""; + let preSubmitNoResponseEl = null; + const noRespSelector = '.Y3BBE, .luHWlf, [data-sfc-root], [jscontroller="zcfIf"]'; + + if (preSubmitParagraphs.length > 0) { + preSubmitText = preSubmitParagraphs[preSubmitParagraphs.length - 1].parentElement.innerText || ""; + } else { + const noRespNodes = document.querySelectorAll(noRespSelector); + for (let n of noRespNodes) { + if (n.innerText && n.innerText.includes("no response available")) { + preSubmitText = n.innerText; + preSubmitNoResponseEl = n; + break; + } + } + } + + standardTactics.setNativeValue(input, payload); + await new Promise(r => setTimeout(r, 800)); + + const submitSelectors = ['button.SAvKK', 'button.qmJOdc', 'button[aria-label="Submit"]', 'button[aria-label="Search"]']; + let runBtn = null; + for(let sel of submitSelectors) { + const btns = Array.from(document.querySelectorAll(sel)).filter(b => b.offsetWidth > 0); + for(let btn of btns) { + if(!btn.disabled && btn.getAttribute('aria-disabled') !== 'true') { + runBtn = btn; + break; + } + } + if(runBtn) break; + } + + if (runBtn) { + runBtn.click(); + } else { + console.log("[Harness] Submit button not found or disabled, dispatching Enter key fallback..."); + const enterEvent = new KeyboardEvent('keydown', { bubbles: true, cancelable: true, key: 'Enter', code: 'Enter', keyCode: 13 }); + input.dispatchEvent(enterEvent); + } + + console.log("[Harness] Command sent. Entering Kinetic Wait (2500ms)..."); + await new Promise(r => setTimeout(r, 2500)); + console.log("[Harness] Wait complete. Engaging Semantic Lock..."); + + return new Promise((resolve) => { + let lastTextLength = -1; + let stabilityStreak = 0; + const REQUIRED_STREAK = 1; + let maxWait = 180; + let isNewResponseStarted = false; + + const checkLoop = setInterval(() => { + maxWait--; + if (maxWait <= 0) { + clearInterval(checkLoop); + resolve("Error: Generation timed out or hung."); + return; + } + + const noResponseNodes = document.querySelectorAll(noRespSelector); + let currentNoResponseEl = null; + let currentNoResponseText = ""; + + for (let node of noResponseNodes) { + if (node.innerText && node.innerText.includes("no response available")) { + currentNoResponseEl = node; + currentNoResponseText = node.innerText; + break; + } + } + + if (currentNoResponseEl) { + if (!isNewResponseStarted) { + if (currentNoResponseText !== preSubmitText || currentNoResponseEl !== preSubmitNoResponseEl) { + isNewResponseStarted = true; + console.log("[Harness] Detected new 'no response' stream starting."); + } else { + console.log("[Harness] Waiting for old 'no response' to clear/change..."); + return; + } + } + if (isNewResponseStarted) { + clearInterval(checkLoop); + console.log("[Harness] Detected 'no response available' message. Returning empty response."); + resolve(""); + return; + } + } + + const paragraphs = document.querySelectorAll('.n6owBd'); + if (paragraphs.length === 0) { + console.log("[Harness] Waiting for SGE turn container..."); + return; + } + + const lastTurnContainer = paragraphs[paragraphs.length - 1].parentElement; + const currentTextContent = lastTurnContainer.innerText || ""; + const currentTextLength = currentTextContent.length; + + if (!isNewResponseStarted) { + if (currentTextContent !== preSubmitText || currentTextContent === "" || currentTextLength < preSubmitText.length) { + isNewResponseStarted = true; + console.log("[Harness] Detected new response stream starting."); + } else { + console.log("[Harness] Waiting for old response to clear/change..."); + return; + } + } + + const isTextGrowing = currentTextLength > lastTextLength; + + if (isTextGrowing) { + console.log(`[Harness] Status: Streaming... (+${currentTextLength - lastTextLength})`); + lastTextLength = currentTextLength; + stabilityStreak = 0; + } else if (currentTextLength === 0) { + console.log(`[Harness] Status: Waiting for text generation to begin...`); + stabilityStreak = 0; + } else { + stabilityStreak++; + console.log(`[Harness] Status: Stable... (${stabilityStreak}/${REQUIRED_STREAK})`); + lastTextLength = currentTextLength; + } + + if (stabilityStreak >= REQUIRED_STREAK) { + clearInterval(checkLoop); + console.log("[Harness] Lock Released. Extraction initiated."); + let turnMd = googleHtmlToMarkdown(lastTurnContainer); + turnMd = turnMd.replace(/\n{3,}/g, '\n\n').trim(); + resolve(turnMd); + } + }, 1000); + }); + }, + NEW_CHAT: async () => { + const newThreadBtn = document.querySelector('button[aria-label="New thread"]') || document.querySelector('.cV1Mfc'); + if (newThreadBtn) { + newThreadBtn.click(); + return "Chat reset."; + } + return "Error: New thread button not found."; + }, + GET_CHAT_HISTORY: async () => { + const history = []; + const turns = document.querySelectorAll('.VndcI, .n6owBd'); + turns.forEach(turn => { + if (turn.classList.contains('VndcI')) { + let text = turn.innerText.replace('You said:', '').trim(); + if (text) history.push({ role: "User", text: text }); + } else if (turn.classList.contains('n6owBd')) { + let text = googleHtmlToMarkdown(turn).trim(); + if (text) history.push({ role: "Model", text: text }); + } + }); + return JSON.stringify(history); + }, + ANALYZE_SESSION: async () => { + return JSON.stringify({ + isHydrated: document.querySelectorAll('.n6owBd').length > 0, + turnCount: document.querySelectorAll('.n6owBd').length, + url: window.location.href + }); + }, + GET_SETTINGS: async () => "{}", + SET_SETTINGS: async () => "Success: Ignored in Google AI Search Mode", + SET_URL_CONTEXT: async () => "Success: Ignored in Google AI Search Mode", + SET_RESOLUTION: async () => "Success: Ignored in Google AI Search Mode", + DELETE_TURN: async () => "Success: Ignored in Google AI Search Mode", + CREATE_SHARD: async () => "Error: Context Sharding is not implemented for AI Search Mode", + INJECT_SHARD: async () => "Error: Context Sharding is not implemented for AI Search Mode", + UPLOAD_MEDIA: async () => "Error: Media Upload is not currently supported in AI Search Mode", + INJECT_DOC: async () => "Error: Doc Injection is not currently supported in AI Search Mode", + SCRAPE_HISTORY: async () => "[]" + }; + + const chatgptActions = { + SHUTDOWN: async () => { + clearInterval(pollIntervalId); + clearInterval(stateIntervalId); + console.log("[Harness] Shutdown command received. Loops stopped."); + return "Harness shut down."; + }, + GENERATE: async (payload) => { + const input = document.querySelector('#prompt-textarea'); + if (!input) return "Error: Prompt textarea not found on ChatGPT."; + + const preSubmitResponses = document.querySelectorAll('[data-message-author-role="assistant"]'); + const preSubmitCount = preSubmitResponses.length; + + activeTactics.setNativeValue(input, payload); + await new Promise(r => setTimeout(r, 500)); + + const sendBtn = document.querySelector('[data-testid="send-button"]'); + if (sendBtn && !sendBtn.disabled) { + sendBtn.click(); + } else { + console.log("[Harness] Submit button not found or disabled, dispatching Enter key fallback..."); + const enterEvent = new KeyboardEvent('keydown', { bubbles: true, cancelable: true, key: 'Enter', code: 'Enter', keyCode: 13 }); + input.dispatchEvent(enterEvent); + } + + console.log("[Harness] Command sent. Entering Kinetic Wait (2000ms)..."); + await new Promise(r => setTimeout(r, 2000)); + console.log("[Harness] Wait complete. Engaging Semantic Lock..."); + + return new Promise((resolve) => { + let lastTextLength = -1; + let stabilityStreak = 0; + const REQUIRED_STREAK = 2; + let maxWait = 300; + + const checkLoop = setInterval(() => { + maxWait--; + if (maxWait <= 0) { + clearInterval(checkLoop); + resolve("Error: Generation timed out or hung."); + return; + } + + const responses = document.querySelectorAll('[data-message-author-role="assistant"]'); + if (responses.length <= preSubmitCount && responses.length !== 0) { + console.log("[Harness] Waiting for new ChatGPT response container..."); + return; + } + + const lastResponse = responses[responses.length - 1]; + if (!lastResponse) return; + + const markdownContainer = lastResponse.querySelector('.markdown') || lastResponse; + const currentTextContent = markdownContainer.innerText || ""; + const currentTextLength = currentTextContent.length; + const isTextGrowing = currentTextLength > lastTextLength; + + if (isTextGrowing) { + console.log(`[Harness] Status: Streaming... (+${currentTextLength - lastTextLength})`); + lastTextLength = currentTextLength; + stabilityStreak = 0; + } else if (currentTextLength === 0) { + console.log(`[Harness] Status: Waiting for text generation to begin...`); + stabilityStreak = 0; + } else { + stabilityStreak++; + console.log(`[Harness] Status: Stable... (${stabilityStreak}/${REQUIRED_STREAK})`); + lastTextLength = currentTextLength; + } + + if (stabilityStreak >= REQUIRED_STREAK) { + clearInterval(checkLoop); + console.log("[Harness] Lock Released. Extraction initiated."); + let turnMd = chatgptHtmlToMarkdown(markdownContainer); + turnMd = turnMd.replace(/\n{3,}/g, '\n\n').trim(); + resolve(turnMd); + } + }, 1000); + }); + }, + NEW_CHAT: async () => { + const newChatBtn = document.querySelector('[data-testid="create-new-chat-button"]'); + if (newChatBtn) { + newChatBtn.click(); + return "Chat reset."; + } + return "Error: New chat button not found."; + }, + GET_CHAT_HISTORY: async () => { + const history = []; + const messages = document.querySelectorAll('[data-message-author-role]'); + messages.forEach(msg => { + const role = msg.getAttribute('data-message-author-role'); + if (role === 'assistant') { + const markdownContainer = msg.querySelector('.markdown') || msg; + let text = chatgptHtmlToMarkdown(markdownContainer).trim(); + if (text) history.push({ role: "Model", text: text }); + } else if (role === 'user') { + let text = msg.innerText.trim(); + if (text) history.push({ role: "User", text: text }); + } + }); + return JSON.stringify(history); + }, + ANALYZE_SESSION: async () => { + return JSON.stringify({ + isHydrated: document.querySelectorAll('[data-message-author-role="assistant"]').length > 0, + turnCount: document.querySelectorAll('[data-message-author-role="assistant"]').length, + url: window.location.href + }); + }, + GET_SETTINGS: async () => "{}", + SET_SETTINGS: async () => "Success: Ignored in ChatGPT Mode", + SET_URL_CONTEXT: async () => "Success: Ignored in ChatGPT Mode", + SET_RESOLUTION: async () => "Success: Ignored in ChatGPT Mode", + DELETE_TURN: async () => "Success: Ignored in ChatGPT Mode", + CREATE_SHARD: async () => "Error: Context Sharding is not implemented for ChatGPT Mode", + INJECT_SHARD: async () => "Error: Context Sharding is not implemented for ChatGPT Mode", + UPLOAD_MEDIA: async () => "Error: Media Upload is not currently supported in ChatGPT Mode", + INJECT_DOC: async () => "Error: Doc Injection is not currently supported in ChatGPT Mode", + SCRAPE_HISTORY: async () => "[]" + }; + + const deepseekActions = { + SHUTDOWN: async () => { + clearInterval(pollIntervalId); + clearInterval(stateIntervalId); + console.log("[Harness] Shutdown command received. Loops stopped."); + return "Harness shut down."; + }, + GENERATE: async (payload) => { + let input = document.querySelector('textarea[placeholder="Message DeepSeek"]'); + if (!input) input = document.querySelector('textarea'); + if (!input) return "Error: Prompt textarea not found on DeepSeek."; + + const preSubmitResponses = document.querySelectorAll('.ds-assistant-message-main-content'); + const preSubmitCount = preSubmitResponses.length; + + activeTactics.setNativeValue(input, payload); + await new Promise(r => setTimeout(r, 800)); + + const sendBtn = document.querySelector('div[role="button"].ds-button--primary'); + if (sendBtn && !sendBtn.classList.contains('ds-button--disabled')) { + sendBtn.click(); + } else { + console.log("[Harness] Submit button not found or disabled, dispatching Enter key fallback..."); + const enterEvent = new KeyboardEvent('keydown', { bubbles: true, cancelable: true, key: 'Enter', code: 'Enter', keyCode: 13 }); + input.dispatchEvent(enterEvent); + } + + console.log("[Harness] Command sent. Entering Kinetic Wait (2500ms)..."); + await new Promise(r => setTimeout(r, 2500)); + console.log("[Harness] Wait complete. Engaging Semantic Lock..."); + + return new Promise((resolve) => { + let lastTextLength = -1; + let stabilityStreak = 0; + const REQUIRED_STREAK = 3; + let maxWait = 600; + + const checkLoop = setInterval(() => { + maxWait--; + if (maxWait <= 0) { + clearInterval(checkLoop); + resolve("Error: Generation timed out or hung."); + return; + } + + const responses = document.querySelectorAll('.ds-assistant-message-main-content'); + if (responses.length <= preSubmitCount && responses.length !== 0) { + console.log("[Harness] Waiting for new DeepSeek response container..."); + return; + } + + const lastResponse = responses[responses.length - 1]; + if (!lastResponse) return; + + const currentTextContent = lastResponse.innerText || ""; + const currentTextLength = currentTextContent.length; + const isTextGrowing = currentTextLength > lastTextLength; + + if (isTextGrowing) { + console.log(`[Harness] Status: Streaming... (+${currentTextLength - lastTextLength})`); + lastTextLength = currentTextLength; + stabilityStreak = 0; + } else if (currentTextLength === 0) { + console.log(`[Harness] Status: Thinking / Waiting for text generation to begin...`); + stabilityStreak = 0; + } else { + stabilityStreak++; + console.log(`[Harness] Status: Stable... (${stabilityStreak}/${REQUIRED_STREAK})`); + lastTextLength = currentTextLength; + } + + if (stabilityStreak >= REQUIRED_STREAK) { + clearInterval(checkLoop); + console.log("[Harness] Lock Released. Extraction initiated."); + let turnMd = deepseekHtmlToMarkdown(lastResponse); + turnMd = turnMd.replace(/\n{3,}/g, '\n\n').trim(); + resolve(turnMd); + } + }, 1000); + }); + }, + NEW_CHAT: async () => { + const spans = Array.from(document.querySelectorAll('span')); + const newChatSpan = spans.find(el => el.innerText.trim() === 'New chat'); + if (newChatSpan && newChatSpan.parentElement) { + newChatSpan.parentElement.click(); + return "Chat reset."; + } + return "Error: New chat button not found."; + }, + GET_CHAT_HISTORY: async () => { + const history = []; + const messages = document.querySelectorAll('.ds-message'); + messages.forEach(msg => { + const assistantContent = msg.querySelector('.ds-assistant-message-main-content'); + if (assistantContent) { + let text = deepseekHtmlToMarkdown(assistantContent).trim(); + if (text) history.push({ role: "Model", text: text }); + } else { + let text = msg.innerText.trim(); + if (text) history.push({ role: "User", text: text }); + } + }); + return JSON.stringify(history); + }, + ANALYZE_SESSION: async () => { + return JSON.stringify({ + isHydrated: document.querySelectorAll('.ds-assistant-message-main-content').length > 0, + turnCount: document.querySelectorAll('.ds-assistant-message-main-content').length, + url: window.location.href + }); + }, + GET_SETTINGS: async () => "{}", + SET_SETTINGS: async () => "Success: Ignored in DeepSeek Mode", + SET_URL_CONTEXT: async () => "Success: Ignored in DeepSeek Mode", + SET_RESOLUTION: async () => "Success: Ignored in DeepSeek Mode", + DELETE_TURN: async () => "Success: Ignored in DeepSeek Mode", + CREATE_SHARD: async () => "Error: Context Sharding is not implemented for DeepSeek Mode", + INJECT_SHARD: async () => "Error: Context Sharding is not implemented for DeepSeek Mode", + UPLOAD_MEDIA: async () => "Error: Media Upload is not currently supported in DeepSeek Mode", + INJECT_DOC: async () => "Error: Doc Injection is not currently supported in DeepSeek Mode", + SCRAPE_HISTORY: async () => "[]" + }; + + const geminiActions = { + SHUTDOWN: async () => { + clearInterval(pollIntervalId); + clearInterval(stateIntervalId); + console.log("[Harness] Shutdown command received. Loops stopped."); + return "Harness shut down."; + }, + GENERATE: async (payload) => { + + // Helper to dismiss A/B testing dialogs that block the UI + const dismissABTest = () => { + const titles = document.querySelectorAll('span.title'); + for (let title of titles) { + if (title.textContent && title.textContent.includes('Which response is more helpful?')) { + const container = title.closest('.container'); + if (container) { + const closeBtn = container.querySelector('button[aria-label="Close"]'); + if (closeBtn) { + console.log("[Harness] Dismissing A/B test dialog..."); + closeBtn.click(); + } + } + } + } + }; + + dismissABTest(); + + const input = document.querySelector('.ql-editor[contenteditable="true"]'); + if (!input) return "Error: Prompt textarea not found on Gemini."; + + const preSubmitResponses = document.querySelectorAll('model-response'); + const preSubmitCount = preSubmitResponses.length; + + activeTactics.setNativeValue(input, payload); + await new Promise(r => setTimeout(r, 800)); + + const sendBtn = document.querySelector('button[aria-label="Send message"]'); + if (sendBtn && !sendBtn.disabled && sendBtn.getAttribute('aria-disabled') !== 'true') { + sendBtn.click(); + } else { + console.log("[Harness] Submit button not found or disabled, dispatching Enter key fallback..."); + const enterEvent = new KeyboardEvent('keydown', { bubbles: true, cancelable: true, key: 'Enter', code: 'Enter', keyCode: 13 }); + input.dispatchEvent(enterEvent); + } + + console.log("[Harness] Command sent. Entering Kinetic Wait (2000ms)..."); + await new Promise(r => setTimeout(r, 2000)); + console.log("[Harness] Wait complete. Engaging Semantic Lock..."); + + return new Promise((resolve) => { + let lastTextLength = -1; + let stabilityStreak = 0; + const REQUIRED_STREAK = 3; + let maxWait = 300; + + const checkLoop = setInterval(() => { + maxWait--; + if (maxWait <= 0) { + clearInterval(checkLoop); + resolve("Error: Generation timed out or hung."); + return; + } + + dismissABTest(); + + const responses = document.querySelectorAll('model-response'); + if (responses.length <= preSubmitCount && responses.length !== 0) { + console.log("[Harness] Waiting for new Gemini response container..."); + return; + } + + const lastResponse = responses[responses.length - 1]; + if (!lastResponse) return; + + const markdownContainer = lastResponse.querySelector('.markdown') || lastResponse; + const currentTextContent = markdownContainer.innerText || ""; + const currentTextLength = currentTextContent.length; + const isTextGrowing = currentTextLength > lastTextLength; + + // Gemini uses aria-busy to indicate active generation + const isBusy = markdownContainer.getAttribute('aria-busy') === 'true' || lastResponse.getAttribute('aria-busy') === 'true'; + + if (isTextGrowing || isBusy) { + console.log(`[Harness] Status: Streaming... (+${currentTextLength - lastTextLength})`); + lastTextLength = currentTextLength; + stabilityStreak = 0; + } else if (currentTextLength === 0) { + console.log(`[Harness] Status: Waiting for text generation to begin...`); + stabilityStreak = 0; + } else { + stabilityStreak++; + console.log(`[Harness] Status: Stable... (${stabilityStreak}/${REQUIRED_STREAK})`); + lastTextLength = currentTextLength; + } + + if (stabilityStreak >= REQUIRED_STREAK && !isBusy) { + clearInterval(checkLoop); + console.log("[Harness] Lock Released. Extraction initiated."); + let turnMd = googleHtmlToMarkdown(markdownContainer); + turnMd = turnMd.replace(/\n{3,}/g, '\n\n').trim(); + resolve(turnMd); + } + }, 1000); + }); + }, + NEW_CHAT: async () => { + const newChatBtn = document.querySelector('a[aria-label="New chat"]') || document.querySelector('a[href="/app"]'); + if (newChatBtn) { + newChatBtn.click(); + return "Chat reset."; + } + return "Error: New chat button not found."; + }, + GET_CHAT_HISTORY: async () => { + const history = []; + const turns = document.querySelectorAll('user-query, model-response'); + turns.forEach(turn => { + if (turn.tagName.toLowerCase() === 'USER-QUERY') { + const textEl = turn.querySelector('.query-text'); + if (textEl) { + let text = textEl.textContent.replace('You said', '').trim(); + if (text) history.push({ role: "User", text: text }); + } + } else if (turn.tagName.toLowerCase() === 'MODEL-RESPONSE') { + const markdownContainer = turn.querySelector('.markdown'); + if (markdownContainer) { + let text = googleHtmlToMarkdown(markdownContainer).trim(); + if (text) history.push({ role: "Model", text: text }); + } + } + }); + return JSON.stringify(history); + }, + ANALYZE_SESSION: async () => { + return JSON.stringify({ + isHydrated: document.querySelectorAll('model-response').length > 0, + turnCount: document.querySelectorAll('model-response').length, + url: window.location.href + }); + }, + GET_SETTINGS: async () => "{}", + SET_SETTINGS: async () => "Success: Ignored in Gemini Mode", + SET_URL_CONTEXT: async () => "Success: Ignored in Gemini Mode", + SET_RESOLUTION: async () => "Success: Ignored in Gemini Mode", + DELETE_TURN: async () => "Success: Ignored in Gemini Mode", + CREATE_SHARD: async () => "Error: Context Sharding is not implemented for Gemini Mode", + INJECT_SHARD: async () => "Error: Context Sharding is not implemented for Gemini Mode", + UPLOAD_MEDIA: async () => "Error: Media Upload is not currently supported in Gemini Mode", + INJECT_DOC: async () => "Error: Doc Injection is not currently supported in Gemini Mode", + SCRAPE_HISTORY: async () => "[]" + }; + + // --- PLATFORM ASSIGNMENT --- + if (currentPlatform === 'Google') { + PLATFORM_NAME = "Google AI Search"; + ID_PREFIX = "inst_gai_"; + activeActions = googleActions; + activeTactics = standardTactics; + reportStateLogic = () => document.querySelectorAll('.n6owBd').length; + } else if (currentPlatform === 'ChatGPT') { + PLATFORM_NAME = "ChatGPT"; + ID_PREFIX = "inst_cgpt_"; + activeActions = chatgptActions; + activeTactics = contentEditableTactics; + reportStateLogic = () => document.querySelectorAll('[data-message-author-role="assistant"]').length; + } else if (currentPlatform === 'DeepSeek') { + PLATFORM_NAME = "DeepSeek"; + ID_PREFIX = "inst_ds_"; + activeActions = deepseekActions; + activeTactics = standardTactics; + reportStateLogic = () => document.querySelectorAll('.ds-assistant-message-main-content').length; + } else if (currentPlatform === 'Gemini') { + PLATFORM_NAME = "Google Gemini"; + ID_PREFIX = "inst_gem_"; + activeActions = geminiActions; + activeTactics = contentEditableTactics; + reportStateLogic = () => document.querySelectorAll('model-response').length; + } + + // --- INSTANCE IDENTITY --- + function getInstanceId() { + let id = sessionStorage.getItem('tm_instance_id'); + if (!id || !id.startsWith(ID_PREFIX)) { + id = ID_PREFIX + Math.random().toString(36).substr(2, 9) + '_' + Date.now(); + sessionStorage.setItem('tm_instance_id', id); + } + return id; + } + const INSTANCE_ID = getInstanceId(); + console.log(`[Harness] Bound to Instance ID: ${INSTANCE_ID}`); + + // --- MAIN LOOP & NETWORKING --- + async function processCommand(cmdData) { + const { type, payload } = cmdData.command; + console.log(`[Harness] Processing: ${type}`, payload); + + let result = ""; + try { + if (activeActions[type]) { + result = await activeActions[type](payload); + } else { + result = "Unknown command type"; + } + } catch (e) { + console.error("Execution failed:", e); + result = `Error: ${e.message}`; + } + + console.log(`[Harness] Result for ${type}:`, result); + await sendResult(result); + } + + async function sendResult(data) { + try { + await tmFetch(`${API_BASE}/result/${INSTANCE_ID}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ timestamp: Date.now(), output: data }) + }); + } catch (e) { + console.error("Relay failed:", e); + } + } + + async function poll() { + if (isBusy || isPolling) return; + isPolling = true; + + try { + const res = await tmFetch(`${API_BASE}/command/${INSTANCE_ID}`); + if (res.status === 200) { + const data = await res.json(); + if (data && data.command) { + isBusy = true; + await processCommand(data); + isBusy = false; + } + } + } catch (e) { + // Network error or timeout, safely ignored. + } finally { + isPolling = false; + } + } + + function reportState() { + if (isBusy) return; + const turnCount = reportStateLogic(); + tmFetch(`${API_BASE}/state/${INSTANCE_ID}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + url: window.location.href, + turn_count: turnCount, + is_busy: isBusy, + platform: PLATFORM_NAME + }) + }).catch(() => {}); + } + + pollIntervalId = setInterval(poll, POLLING_INTERVAL); + stateIntervalId = setInterval(reportState, STATE_INTERVAL); + + console.log(`[Harness] ${PLATFORM_NAME} Active & Polling on Instance ${INSTANCE_ID}.`); + +})(); \ No newline at end of file diff --git a/README.md b/README.md index 0a2c0fe..329e21e 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ The Voice Assistant PoC requires an offline voice model to function. ### 2. Install the Tampermonkey Harness 1. Open your browser and click the Tampermonkey extension icon -> **Create a new script**. -2. Copy the entire contents of `GoogleAI_Search_Deepseek_ChatGPT_UnifiedHarness.js` from this repository. +2. Copy the entire contents of `GoogleAI_Search_Google_Gemini_Deepseek_ChatGPT_UnifiedHarness.js` from this repository. 3. Paste it into the Tampermonkey editor, overwriting the default template. 4. Go to File -> **Save** (or press Ctrl+S).