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 = {
"input_selector": "textarea",
"send_button_selector": "button[aria-label='Grok something']",
"container_selector": "#react-root main",
"spy_word_base": "SPYWORD_123"
"spy_word_base": "SPYWORD_123",
"prompt_template": "grok"
},
"grok-beta:latest": {
"url": "https://grok.com",
"input_selector": "textarea",
"send_button_selector": "button[aria-label='Grok something']",
"container_selector": "#react-root main",
"spy_word_base": "SPYWORD_123"
"spy_word_base": "SPYWORD_123",
"prompt_template": "grok"
},
"llama2:latest": {
"url": "https://x.com/i/grok",
"input_selector": "textarea",
"send_button_selector": "button[aria-label='Grok something']",
"container_selector": "#react-root main",
"spy_word_base": "SPYWORD_123"
"gemini:latest": {
"url": "https://gemini.google.com/app",
"input_selector": 'div[aria-label="Enter a prompt here"][data-placeholder="Ask Gemini"] p',
"send_button_selector": False,
"container_selector": "message-content div p",
"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": {
"url": "https://x.com/i/grok",
"input_selector": "textarea",
"send_button_selector": "button[aria-label='Grok something']",
"container_selector": "#react-root main",
"spy_word_base": "SPYWORD_123"
"gemini": {
"system_instruction": """RESPOND WITH JSON ONLY - NO EXPLANATIONS:
Output format (plain text, no code blocks):
{{"response": "BASE64_ENCODED_ANSWER", "id": "{request_id}"}}
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):
# Generate unique ID for this request using UUID for better uniqueness
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
json_instruction = f'''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:
'''
# Get the appropriate prompt template for this chatbot
template_name = config.get('prompt_template', 'default')
template = PROMPT_TEMPLATES.get(template_name, PROMPT_TEMPLATES['default'])
# Create JSON-based prompt with chatbot-specific template
json_instruction = template['system_instruction'].format(request_id=request_id)
modified_prompt = f"{json_instruction}\n\n{prompt}"
logging.info(f"Request ID: {request_id}, Modified prompt: {modified_prompt}")
try:
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:
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']
try:
......@@ -494,14 +555,11 @@ async def detect_json_response_with_id(page, container_selector, request_id, pro
// Clean the text first
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 = [
// Standard JSON object patterns - simplified
new RegExp('\\{[^{}]*"id"[^{}]*"' + requestId + '"[^{}]*"response"[^{}]*\\}', 'g'),
new RegExp('\\{[^{}]*"response"[^{}]*"id"[^{}]*"' + requestId + '"[^{}]*\\}', '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')
// Standard JSON object patterns - using string methods instead of complex regex
new RegExp('\\{[^{}]*"id"[^{}]*"' + requestId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '"[^{}]*"response"[^{}]*\\}', 'g'),
new RegExp('\\{[^{}]*"response"[^{}]*"id"[^{}]*"' + requestId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '"[^{}]*\\}', 'g')
];
// Try both original and cleaned text
......@@ -521,17 +579,17 @@ async def detect_json_response_with_id(page, container_selector, request_id, pro
} catch (e) {
// Try to extract response value from malformed JSON - simplified
try {
const responsePattern = /"response"\\s*:\\s*"([^"]*)"/;
const responsePattern = /"response"\s*:\s*"([^"]*)"/;
const responseMatch = match.match(responsePattern);
if (responseMatch && match.includes(requestId)) {
const responseValue = responseMatch[1];
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;
}
}
} 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
const partialMatch = cleanedText.match(partialJsonRegex);
if (partialMatch && partialMatch[0].length > partialJsonContent.length) {
partialJsonContent = partialMatch[0];
console.log(`Found partial JSON: ${partialJsonContent.substring(0, 100)}...`);
console.log('Found partial JSON: ' + partialJsonContent.substring(0, 100) + '...');
}
return null;
......@@ -565,7 +623,7 @@ async def detect_json_response_with_id(page, container_selector, request_id, pro
for (const codeBlock of codeBlocks) {
const jsonText = codeBlock.textContent ? codeBlock.textContent.trim() : '';
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);
if (result) return result;
}
......@@ -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
const result = getCompleteJsonResponse();
if (result && typeof result === 'string' && result.length > 0) {
console.log(`🎯 Observer found complete response, disconnecting...`);
console.log('Observer found complete response, disconnecting...');
observer.disconnect();
resolveOnce(result);
return;
......@@ -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
const hasPartialId = container.textContent && container.textContent.includes(requestId);
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
}
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;
} 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;
}
} catch (e) {
console.log(`✗ JSON parse error: ${e.message}`);
console.log('JSON parse error: ' + e.message);
return false;
}
};
// Enhanced check that only returns complete, valid JSON with exact ID match
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
const jsonContainers = container.querySelectorAll([
......@@ -706,10 +764,10 @@ async def detect_json_response_with_id(page, container_selector, request_id, pro
for (const container of jsonContainers) {
const jsonText = container.textContent ? container.textContent.trim() : '';
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)) {
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;
}
}
......@@ -720,21 +778,22 @@ async def detect_json_response_with_id(page, container_selector, request_id, pro
for (const element of allElements) {
const text = element.textContent ? element.textContent.trim() : '';
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
const jsonPattern1 = new RegExp('\\{[^{}]*"id"[^{}]*"' + requestId + '"[^{}]*"response"[^{}]*\\}');
const jsonPattern2 = new RegExp('\\{[^{}]*"response"[^{}]*"id"[^{}]*"' + requestId + '"[^{}]*\\}');
// Try to extract JSON from this text - using escaped requestId
const escapedRequestId = requestId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const jsonPattern1 = new RegExp('\\{[^{}]*"id"[^{}]*"' + escapedRequestId + '"[^{}]*"response"[^{}]*\\}');
const jsonPattern2 = new RegExp('\\{[^{}]*"response"[^{}]*"id"[^{}]*"' + escapedRequestId + '"[^{}]*\\}');
const jsonMatch = text.match(jsonPattern1) || text.match(jsonPattern2);
if (jsonMatch && isCompleteValidJson(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;
}
}
}
console.log(`❌ No complete JSON found with ID: ${requestId}`);
console.log('No complete JSON found with ID: ' + requestId);
return null;
};
......@@ -754,7 +813,7 @@ async def detect_json_response_with_id(page, container_selector, request_id, pro
observer.disconnect();
resolveOnce(result);
} 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();
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
// Final safety timeout
setTimeout(() => {
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");
}, 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