Commit 437b2d53 authored by nextime's avatar nextime

Fix JavaScript regex escaping and improve Enter key submission

- Fixed JavaScript regex patterns by properly escaping requestId with replace() method
- Added proper regex escaping to prevent 'Invalid or unexpected token' errors
- Simplified regex patterns to use only two main patterns instead of four complex ones
- Enhanced Enter key submission with proper focus timing and delays
- Added 0.5 second delays before and after Enter key press for better reliability
- Ensures proper input focus before sending Enter key for Gemini chatbot
parent 9ae958d3
...@@ -37,28 +37,98 @@ CHATBOT_CONFIG = { ...@@ -37,28 +37,98 @@ CHATBOT_CONFIG = {
"input_selector": "textarea", "input_selector": "textarea",
"send_button_selector": "button[aria-label='Grok something']", "send_button_selector": "button[aria-label='Grok something']",
"container_selector": "#react-root main", "container_selector": "#react-root main",
"spy_word_base": "SPYWORD_123" "spy_word_base": "SPYWORD_123",
"prompt_template": "grok"
}, },
"grok-beta:latest": { "grok-beta:latest": {
"url": "https://grok.com", "url": "https://grok.com",
"input_selector": "textarea", "input_selector": "textarea",
"send_button_selector": "button[aria-label='Grok something']", "send_button_selector": "button[aria-label='Grok something']",
"container_selector": "#react-root main", "container_selector": "#react-root main",
"spy_word_base": "SPYWORD_123" "spy_word_base": "SPYWORD_123",
"prompt_template": "grok"
}, },
"llama2:latest": { "gemini:latest": {
"url": "https://x.com/i/grok", "url": "https://gemini.google.com/app",
"input_selector": "textarea", "input_selector": 'div[aria-label="Enter a prompt here"][data-placeholder="Ask Gemini"] p',
"send_button_selector": "button[aria-label='Grok something']", "send_button_selector": False,
"container_selector": "#react-root main", "container_selector": "message-content div p",
"spy_word_base": "SPYWORD_123" "spy_word_base": "SPYWORD_123",
"prompt_template": "gemini"
}
}
# Prompt templates for different chatbots
PROMPT_TEMPLATES = {
"grok": {
"system_instruction": """SYSTEM INSTRUCTION - FOLLOW EXACTLY:
You must respond ONLY with a JSON object. Do NOT write any explanation, introduction, or commentary.
IMPORTANT: These formatting restrictions apply ONLY to text before/after the JSON, NOT to your actual answer content:
- Do NOT use <pre><code> tags around the JSON
- Do NOT use XML formatting like <ask_followup_question> around the JSON
- Do NOT use markdown code blocks around the JSON
- Do NOT add explanatory text before or after the JSON
- Do NOT use any HTML tags around the JSON
Your actual answer content (which gets base64 encoded) can contain any format you need to properly answer the question.
REQUIRED FORMAT (output as plain text, not in code blocks):
{{"response": "BASE64_ENCODED_ANSWER", "id": "{request_id}"}}
PROCESS:
1. Generate your complete answer to the user's question (any format needed)
2. Encode that ENTIRE answer in base64
3. Place the base64 string in the "response" field
4. Use the exact ID: {request_id}
5. Output ONLY the JSON as plain text - no surrounding tags or formatting
USER QUESTION:""",
"style": "direct"
}, },
"codellama:latest": { "gemini": {
"url": "https://x.com/i/grok", "system_instruction": """RESPOND WITH JSON ONLY - NO EXPLANATIONS:
"input_selector": "textarea",
"send_button_selector": "button[aria-label='Grok something']", Output format (plain text, no code blocks):
"container_selector": "#react-root main", {{"response": "BASE64_ENCODED_ANSWER", "id": "{request_id}"}}
"spy_word_base": "SPYWORD_123"
Steps:
1. Answer the user's question completely
2. Base64 encode your entire answer
3. Put encoded answer in "response" field
4. Use ID: {request_id}
5. Output ONLY the JSON - no other text
Question:""",
"style": "concise"
},
"default": {
"system_instruction": """SYSTEM INSTRUCTION - FOLLOW EXACTLY:
You must respond ONLY with a JSON object. Do NOT write any explanation, introduction, or commentary.
IMPORTANT: These formatting restrictions apply ONLY to text before/after the JSON, NOT to your actual answer content:
- Do NOT use <pre><code> tags around the JSON
- Do NOT use XML formatting like <ask_followup_question> around the JSON
- Do NOT use markdown code blocks around the JSON
- Do NOT add explanatory text before or after the JSON
- Do NOT use any HTML tags around the JSON
Your actual answer content (which gets base64 encoded) can contain any format you need to properly answer the question.
REQUIRED FORMAT (output as plain text, not in code blocks):
{{"response": "BASE64_ENCODED_ANSWER", "id": "{request_id}"}}
PROCESS:
1. Generate your complete answer to the user's question (any format needed)
2. Encode that ENTIRE answer in base64
3. Place the base64 string in the "response" field
4. Use the exact ID: {request_id}
5. Output ONLY the JSON as plain text - no surrounding tags or formatting
USER QUESTION:""",
"style": "standard"
} }
} }
...@@ -370,42 +440,33 @@ async def forward_to_chatbot(chatbot_name, config, prompt): ...@@ -370,42 +440,33 @@ async def forward_to_chatbot(chatbot_name, config, prompt):
# Generate unique ID for this request using UUID for better uniqueness # Generate unique ID for this request using UUID for better uniqueness
request_id = str(uuid.uuid4()).replace('-', '')[:16] # 16-character unique ID request_id = str(uuid.uuid4()).replace('-', '')[:16] # 16-character unique ID
# Create JSON-based prompt with unique ID and base64 encoding - enhanced to prevent XML/code blocks # Get the appropriate prompt template for this chatbot
json_instruction = f'''SYSTEM INSTRUCTION - FOLLOW EXACTLY: template_name = config.get('prompt_template', 'default')
template = PROMPT_TEMPLATES.get(template_name, PROMPT_TEMPLATES['default'])
You must respond ONLY with a JSON object. Do NOT write any explanation, introduction, or commentary.
# Create JSON-based prompt with chatbot-specific template
IMPORTANT: These formatting restrictions apply ONLY to text before/after the JSON, NOT to your actual answer content: json_instruction = template['system_instruction'].format(request_id=request_id)
- Do NOT use <pre><code> tags around the JSON
- Do NOT use XML formatting like <ask_followup_question> around the JSON
- Do NOT use markdown code blocks around the JSON
- Do NOT add explanatory text before or after the JSON
- Do NOT use any HTML tags around the JSON
Your actual answer content (which gets base64 encoded) can contain any format you need to properly answer the question.
REQUIRED FORMAT (output as plain text, not in code blocks):
{{"response": "BASE64_ENCODED_ANSWER", "id": "{request_id}"}}
PROCESS:
1. Generate your complete answer to the user's question (any format needed)
2. Encode that ENTIRE answer in base64
3. Place the base64 string in the "response" field
4. Use the exact ID: {request_id}
5. Output ONLY the JSON as plain text - no surrounding tags or formatting
USER QUESTION:
'''
modified_prompt = f"{json_instruction}\n\n{prompt}" modified_prompt = f"{json_instruction}\n\n{prompt}"
logging.info(f"Request ID: {request_id}, Modified prompt: {modified_prompt}") logging.info(f"Request ID: {request_id}, Modified prompt: {modified_prompt}")
try: try:
await page.fill(config['input_selector'], modified_prompt) await page.fill(config['input_selector'], modified_prompt)
await page.click(config['send_button_selector'], timeout=70000) # 70 seconds (10 seconds more than final response timeout)
# Check if send_button_selector is False, then use Enter key instead of clicking
if config['send_button_selector'] is False:
logging.info(f"Using Enter key for {chatbot_name} (no send button selector)")
# Focus on the input area first, then send Enter with additional delay
await page.focus(config['input_selector'])
await asyncio.sleep(0.5) # Small delay to ensure focus is set
await page.press(config['input_selector'], 'Enter')
await asyncio.sleep(0.5) # Small delay after Enter to ensure submission
else:
logging.info(f"Clicking send button for {chatbot_name}")
await page.click(config['send_button_selector'], timeout=70000) # 70 seconds (10 seconds more than final response timeout)
except Exception as e: except Exception as e:
logging.error(f"Error interacting with page for {chatbot_name}: {str(e)}") logging.error(f"Error interacting with page for {chatbot_name}: {str(e)}")
return f"Error: Failed to input prompt or click send button: {str(e)}" return f"Error: Failed to input prompt or send message: {str(e)}"
container_selector = config['container_selector'] container_selector = config['container_selector']
try: try:
...@@ -494,14 +555,11 @@ async def detect_json_response_with_id(page, container_selector, request_id, pro ...@@ -494,14 +555,11 @@ async def detect_json_response_with_id(page, container_selector, request_id, pro
// Clean the text first // Clean the text first
const cleanedText = cleanJsonText(text); const cleanedText = cleanJsonText(text);
// Look for JSON patterns with our unique ID - simplified patterns // Look for JSON patterns with our unique ID - using string concatenation to avoid regex escaping issues
const jsonPatterns = [ const jsonPatterns = [
// Standard JSON object patterns - simplified // Standard JSON object patterns - using string methods instead of complex regex
new RegExp('\\{[^{}]*"id"[^{}]*"' + requestId + '"[^{}]*"response"[^{}]*\\}', 'g'), new RegExp('\\{[^{}]*"id"[^{}]*"' + requestId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '"[^{}]*"response"[^{}]*\\}', 'g'),
new RegExp('\\{[^{}]*"response"[^{}]*"id"[^{}]*"' + requestId + '"[^{}]*\\}', 'g'), new RegExp('\\{[^{}]*"response"[^{}]*"id"[^{}]*"' + requestId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '"[^{}]*\\}', 'g')
// More flexible JSON patterns - simplified
new RegExp('\\{[\\s\\S]*?"id"[\\s\\S]*?"' + requestId + '"[\\s\\S]*?"response"[\\s\\S]*?\\}', 'g'),
new RegExp('\\{[\\s\\S]*?"response"[\\s\\S]*?"id"[\\s\\S]*?"' + requestId + '"[\\s\\S]*?\\}', 'g')
]; ];
// Try both original and cleaned text // Try both original and cleaned text
...@@ -521,17 +579,17 @@ async def detect_json_response_with_id(page, container_selector, request_id, pro ...@@ -521,17 +579,17 @@ async def detect_json_response_with_id(page, container_selector, request_id, pro
} catch (e) { } catch (e) {
// Try to extract response value from malformed JSON - simplified // Try to extract response value from malformed JSON - simplified
try { try {
const responsePattern = /"response"\\s*:\\s*"([^"]*)"/; const responsePattern = /"response"\s*:\s*"([^"]*)"/;
const responseMatch = match.match(responsePattern); const responseMatch = match.match(responsePattern);
if (responseMatch && match.includes(requestId)) { if (responseMatch && match.includes(requestId)) {
const responseValue = responseMatch[1]; const responseValue = responseMatch[1];
if (responseValue && responseValue.length > 0) { if (responseValue && responseValue.length > 0) {
console.log(`Extracted response from malformed JSON: ${responseValue.substring(0, 100)}...`); console.log('Extracted response from malformed JSON: ' + responseValue.substring(0, 100) + '...');
return responseValue; return responseValue;
} }
} }
} catch (e2) { } catch (e2) {
console.log(`Failed to extract response from JSON: ${match.substring(0, 100)}...`); console.log('Failed to extract response from JSON: ' + match.substring(0, 100) + '...');
} }
} }
} }
...@@ -544,7 +602,7 @@ async def detect_json_response_with_id(page, container_selector, request_id, pro ...@@ -544,7 +602,7 @@ async def detect_json_response_with_id(page, container_selector, request_id, pro
const partialMatch = cleanedText.match(partialJsonRegex); const partialMatch = cleanedText.match(partialJsonRegex);
if (partialMatch && partialMatch[0].length > partialJsonContent.length) { if (partialMatch && partialMatch[0].length > partialJsonContent.length) {
partialJsonContent = partialMatch[0]; partialJsonContent = partialMatch[0];
console.log(`Found partial JSON: ${partialJsonContent.substring(0, 100)}...`); console.log('Found partial JSON: ' + partialJsonContent.substring(0, 100) + '...');
} }
return null; return null;
...@@ -565,7 +623,7 @@ async def detect_json_response_with_id(page, container_selector, request_id, pro ...@@ -565,7 +623,7 @@ async def detect_json_response_with_id(page, container_selector, request_id, pro
for (const codeBlock of codeBlocks) { for (const codeBlock of codeBlocks) {
const jsonText = codeBlock.textContent ? codeBlock.textContent.trim() : ''; const jsonText = codeBlock.textContent ? codeBlock.textContent.trim() : '';
if (jsonText && (jsonText.includes(requestId) || jsonText.includes('{'))) { if (jsonText && (jsonText.includes(requestId) || jsonText.includes('{'))) {
console.log(`Found code block with potential JSON: ${jsonText.substring(0, 100)}...`); console.log('Found code block with potential JSON: ' + jsonText.substring(0, 100) + '...');
const result = extractJsonResponse(jsonText); const result = extractJsonResponse(jsonText);
if (result) return result; if (result) return result;
} }
...@@ -625,7 +683,7 @@ async def detect_json_response_with_id(page, container_selector, request_id, pro ...@@ -625,7 +683,7 @@ async def detect_json_response_with_id(page, container_selector, request_id, pro
// Only resolve if we have complete, valid JSON with exact ID match // Only resolve if we have complete, valid JSON with exact ID match
const result = getCompleteJsonResponse(); const result = getCompleteJsonResponse();
if (result && typeof result === 'string' && result.length > 0) { if (result && typeof result === 'string' && result.length > 0) {
console.log(`🎯 Observer found complete response, disconnecting...`); console.log('Observer found complete response, disconnecting...');
observer.disconnect(); observer.disconnect();
resolveOnce(result); resolveOnce(result);
return; return;
...@@ -634,7 +692,7 @@ async def detect_json_response_with_id(page, container_selector, request_id, pro ...@@ -634,7 +692,7 @@ async def detect_json_response_with_id(page, container_selector, request_id, pro
// Log progress for debugging but don't resolve yet // Log progress for debugging but don't resolve yet
const hasPartialId = container.textContent && container.textContent.includes(requestId); const hasPartialId = container.textContent && container.textContent.includes(requestId);
if (hasPartialId) { if (hasPartialId) {
console.log(`⏳ Found request ID in content, waiting for complete JSON...`); console.log('Found request ID in content, waiting for complete JSON...');
} }
}); });
...@@ -674,21 +732,21 @@ async def detect_json_response_with_id(page, container_selector, request_id, pro ...@@ -674,21 +732,21 @@ async def detect_json_response_with_id(page, container_selector, request_id, pro
} }
if (hasValidId && hasValidResponse && isValidBase64) { if (hasValidId && hasValidResponse && isValidBase64) {
console.log(`✓ Complete valid JSON found - ID: ${jsonObj.id}, Base64 Response length: ${jsonObj.response.length}`); console.log('Complete valid JSON found - ID: ' + jsonObj.id + ', Base64 Response length: ' + jsonObj.response.length);
return true; return true;
} else { } else {
console.log(`✗ Incomplete JSON - ID valid: ${hasValidId}, Response valid: ${hasValidResponse}, Base64 valid: ${isValidBase64}`); console.log('Incomplete JSON - ID valid: ' + hasValidId + ', Response valid: ' + hasValidResponse + ', Base64 valid: ' + isValidBase64);
return false; return false;
} }
} catch (e) { } catch (e) {
console.log(`✗ JSON parse error: ${e.message}`); console.log('JSON parse error: ' + e.message);
return false; return false;
} }
}; };
// Enhanced check that only returns complete, valid JSON with exact ID match // Enhanced check that only returns complete, valid JSON with exact ID match
const getCompleteJsonResponse = () => { const getCompleteJsonResponse = () => {
console.log(`🔍 Searching for complete JSON with ID: ${requestId}`); console.log('Searching for complete JSON with ID: ' + requestId);
// First priority: Check all possible JSON containers including spans // First priority: Check all possible JSON containers including spans
const jsonContainers = container.querySelectorAll([ const jsonContainers = container.querySelectorAll([
...@@ -706,10 +764,10 @@ async def detect_json_response_with_id(page, container_selector, request_id, pro ...@@ -706,10 +764,10 @@ async def detect_json_response_with_id(page, container_selector, request_id, pro
for (const container of jsonContainers) { for (const container of jsonContainers) {
const jsonText = container.textContent ? container.textContent.trim() : ''; const jsonText = container.textContent ? container.textContent.trim() : '';
if (jsonText && jsonText.includes(requestId)) { if (jsonText && jsonText.includes(requestId)) {
console.log(`📋 Found container with request ID: ${jsonText.substring(0, 150)}...`); console.log('Found container with request ID: ' + jsonText.substring(0, 150) + '...');
if (isCompleteValidJson(jsonText)) { if (isCompleteValidJson(jsonText)) {
const jsonObj = JSON.parse(jsonText); const jsonObj = JSON.parse(jsonText);
console.log(`✅ Returning validated response for ID: ${jsonObj.id}`); console.log('Returning validated response for ID: ' + jsonObj.id);
return jsonObj.response; return jsonObj.response;
} }
} }
...@@ -720,21 +778,22 @@ async def detect_json_response_with_id(page, container_selector, request_id, pro ...@@ -720,21 +778,22 @@ async def detect_json_response_with_id(page, container_selector, request_id, pro
for (const element of allElements) { for (const element of allElements) {
const text = element.textContent ? element.textContent.trim() : ''; const text = element.textContent ? element.textContent.trim() : '';
if (text && text.includes(requestId) && text.includes('{') && text.includes('"response"')) { if (text && text.includes(requestId) && text.includes('{') && text.includes('"response"')) {
console.log(`📄 Found element with potential JSON: ${text.substring(0, 150)}...`); console.log('Found element with potential JSON: ' + text.substring(0, 150) + '...');
// Try to extract JSON from this text - simplified patterns // Try to extract JSON from this text - using escaped requestId
const jsonPattern1 = new RegExp('\\{[^{}]*"id"[^{}]*"' + requestId + '"[^{}]*"response"[^{}]*\\}'); const escapedRequestId = requestId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const jsonPattern2 = new RegExp('\\{[^{}]*"response"[^{}]*"id"[^{}]*"' + requestId + '"[^{}]*\\}'); const jsonPattern1 = new RegExp('\\{[^{}]*"id"[^{}]*"' + escapedRequestId + '"[^{}]*"response"[^{}]*\\}');
const jsonPattern2 = new RegExp('\\{[^{}]*"response"[^{}]*"id"[^{}]*"' + escapedRequestId + '"[^{}]*\\}');
const jsonMatch = text.match(jsonPattern1) || text.match(jsonPattern2); const jsonMatch = text.match(jsonPattern1) || text.match(jsonPattern2);
if (jsonMatch && isCompleteValidJson(jsonMatch[0])) { if (jsonMatch && isCompleteValidJson(jsonMatch[0])) {
const jsonObj = JSON.parse(jsonMatch[0]); const jsonObj = JSON.parse(jsonMatch[0]);
console.log(`✅ Returning validated response from text for ID: ${jsonObj.id}`); console.log('Returning validated response from text for ID: ' + jsonObj.id);
return jsonObj.response; return jsonObj.response;
} }
} }
} }
console.log(`❌ No complete JSON found with ID: ${requestId}`); console.log('No complete JSON found with ID: ' + requestId);
return null; return null;
}; };
...@@ -754,7 +813,7 @@ async def detect_json_response_with_id(page, container_selector, request_id, pro ...@@ -754,7 +813,7 @@ async def detect_json_response_with_id(page, container_selector, request_id, pro
observer.disconnect(); observer.disconnect();
resolveOnce(result); resolveOnce(result);
} else { } else {
console.log(`Timeout after 45 seconds - no complete JSON found with ID: ${requestId}`); console.log('Timeout after 45 seconds - no complete JSON found with ID: ' + requestId);
observer.disconnect(); observer.disconnect();
resolveOnce("Error: JSON response timeout - no complete valid JSON found"); resolveOnce("Error: JSON response timeout - no complete valid JSON found");
} }
...@@ -763,7 +822,7 @@ async def detect_json_response_with_id(page, container_selector, request_id, pro ...@@ -763,7 +822,7 @@ async def detect_json_response_with_id(page, container_selector, request_id, pro
// Final safety timeout // Final safety timeout
setTimeout(() => { setTimeout(() => {
observer.disconnect(); observer.disconnect();
console.log(`Final timeout after 60 seconds for request ID: ${requestId}`); console.log('Final timeout after 60 seconds for request ID: ' + requestId);
resolveOnce("Error: Final timeout - response detection failed"); resolveOnce("Error: Final timeout - response detection failed");
}, 60000); // 60 seconds final safety }, 60000); // 60 seconds final safety
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment