Uploaded the Tampermonkey Harness
This commit is contained in:
parent
ace1e61285
commit
86515e46dd
928
GoogleAI_Search_Google_Gemini_Deepseek_ChatGPT_UnifiedHarness.js
Normal file
928
GoogleAI_Search_Google_Gemini_Deepseek_ChatGPT_UnifiedHarness.js
Normal file
|
|
@ -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}.`);
|
||||
|
||||
})();
|
||||
|
|
@ -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).
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user