aurix-ai 2.5.9 → 2.6.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.
@@ -40,9 +40,11 @@ function readFileBase64(path) {
40
40
  }
41
41
  async function visionClassify(imageBase64, prompt) {
42
42
  const config = loadConfig();
43
- const model = config.model || 'gpt-4o';
43
+ const visionModel = config.visionModel || config.model || 'gpt-4o';
44
+ const visionBaseUrl = config.visionBaseUrl || config.baseUrl;
45
+ const visionApiKey = config.visionApiKey || config.apiKey;
44
46
  const body = {
45
- model,
47
+ model: visionModel,
46
48
  messages: [{
47
49
  role: 'user',
48
50
  content: [
@@ -52,38 +54,46 @@ async function visionClassify(imageBase64, prompt) {
52
54
  }],
53
55
  max_tokens: 100,
54
56
  };
55
- const resp = await fetch(`${config.baseUrl}/chat/completions`, {
56
- method: 'POST',
57
- headers: {
58
- 'Content-Type': 'application/json',
59
- ...(config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}),
60
- },
61
- body: JSON.stringify(body),
62
- });
63
- if (!resp.ok)
64
- throw new Error(`Vision API error: ${resp.status}`);
65
- const text = await resp.text();
66
- if (text.includes('data: ')) {
67
- let content = '';
68
- for (const line of text.split('\n')) {
69
- if (line.startsWith('data: ') && line.trim() !== 'data: [DONE]') {
70
- try {
71
- const ev = JSON.parse(line.slice(6));
72
- const delta = ev.choices?.[0]?.delta;
73
- if (delta?.content)
74
- content += delta.content;
75
- if (delta?.text)
76
- content += delta.text;
77
- if (ev.choices?.[0]?.message?.content)
78
- content += ev.choices[0].message.content;
57
+ const controller = new AbortController();
58
+ const fetchTimeout = setTimeout(() => controller.abort(), 15_000);
59
+ try {
60
+ const resp = await fetch(`${visionBaseUrl}/chat/completions`, {
61
+ method: 'POST',
62
+ headers: {
63
+ 'Content-Type': 'application/json',
64
+ ...(visionApiKey ? { Authorization: `Bearer ${visionApiKey}` } : {}),
65
+ },
66
+ body: JSON.stringify(body),
67
+ signal: controller.signal,
68
+ });
69
+ if (!resp.ok)
70
+ throw new Error(`Vision API error: ${resp.status}`);
71
+ const text = await resp.text();
72
+ if (text.includes('data: ')) {
73
+ let content = '';
74
+ for (const line of text.split('\n')) {
75
+ if (line.startsWith('data: ') && line.trim() !== 'data: [DONE]') {
76
+ try {
77
+ const ev = JSON.parse(line.slice(6));
78
+ const delta = ev.choices?.[0]?.delta;
79
+ if (delta?.content)
80
+ content += delta.content;
81
+ if (delta?.text)
82
+ content += delta.text;
83
+ if (ev.choices?.[0]?.message?.content)
84
+ content += ev.choices[0].message.content;
85
+ }
86
+ catch { }
79
87
  }
80
- catch { }
81
88
  }
89
+ return content.trim();
82
90
  }
83
- return content.trim();
91
+ const json = JSON.parse(text);
92
+ return (json.choices?.[0]?.message?.content || '').trim();
93
+ }
94
+ finally {
95
+ clearTimeout(fetchTimeout);
84
96
  }
85
- const json = JSON.parse(text);
86
- return (json.choices?.[0]?.message?.content || '').trim();
87
97
  }
88
98
  async function solveCaptchaGrid(page, frame, provider) {
89
99
  const results = [];
@@ -189,56 +199,7 @@ async function solveCaptchaGrid(page, frame, provider) {
189
199
  }
190
200
  }
191
201
  if (isRecaptcha && matchedIndices.length > 0) {
192
- await page.waitForTimeout(2000 + Math.random() * 1000);
193
- const afterTiles = await findGridTiles(frame, provider);
194
- const evalPromises = matchedIndices
195
- .filter(idx => idx < afterTiles.length)
196
- .map(async (idx) => {
197
- try {
198
- const tilePath = join(homedir(), `.aurix-tile-after-${idx}.png`);
199
- await afterTiles[idx].screenshot({ path: tilePath });
200
- const base64 = readFileBase64(tilePath);
201
- const resp = await visionClassify(base64, `Does this image contain ${instruction}? Reply YES or NO only.`);
202
- return { idx, match: resp.toLowerCase().includes('yes') };
203
- }
204
- catch {
205
- return { idx, match: false };
206
- }
207
- });
208
- const evalResults = await Promise.all(evalPromises);
209
- const newMatches = evalResults.filter(r => r.match);
210
- if (newMatches.length > 0) {
211
- results.push(` Replacement tiles matched: [${newMatches.map(r => r.idx).join(', ')}]`);
212
- for (const { idx } of newMatches) {
213
- try {
214
- const freshTiles = await findGridTiles(frame, provider);
215
- if (idx >= freshTiles.length)
216
- continue;
217
- const tile = freshTiles[idx];
218
- const tileBox = await tile.boundingBox();
219
- if (tileBox) {
220
- const cx = tileBox.x + tileBox.width * (0.3 + Math.random() * 0.4);
221
- const cy = tileBox.y + tileBox.height * (0.3 + Math.random() * 0.4);
222
- await humanMove(cx, cy, page);
223
- await page.waitForTimeout(80 + Math.random() * 120);
224
- await page.mouse.down();
225
- await page.waitForTimeout(60 + Math.random() * 100);
226
- await page.mouse.up();
227
- }
228
- else {
229
- await tile.click({ force: true });
230
- }
231
- results.push(` Clicked replacement tile ${idx}`);
232
- }
233
- catch (e) {
234
- results.push(` Failed replacement tile ${idx}: ${e.message}`);
235
- }
236
- }
237
- await page.waitForTimeout(1500 + Math.random() * 1000);
238
- }
239
- else {
240
- results.push(' No replacement tiles matched');
241
- }
202
+ await page.waitForTimeout(1500 + Math.random() * 500);
242
203
  }
243
204
  results.push('Clicking verify...');
244
205
  try {
@@ -1581,562 +1542,614 @@ Sessions: session="a"/"b"/"c" for parallel browsers. proxy="host:port:user:pass"
1581
1542
  case 'solve-captcha': {
1582
1543
  const p = await ensureBrowser();
1583
1544
  const results = [];
1584
- const frames = p.frames();
1585
- let captchaType = 'unknown';
1586
- let recaptchaAnchor = null;
1587
- let recaptchaBframe = null;
1588
- let hcaptchaCheckbox = null;
1589
- let hcaptchaChallenge = null;
1590
- let funcaptchaFrame = null;
1591
- let mtcaptchaFrame = null;
1592
- let geetestFrame = null;
1593
- for (const frame of frames) {
1594
- const url = frame.url();
1595
- if (url.includes('/recaptcha/') && url.includes('/anchor'))
1596
- recaptchaAnchor = frame;
1597
- if (url.includes('/recaptcha/') && url.includes('/bframe'))
1598
- recaptchaBframe = frame;
1599
- if (url.includes('newassets.hcaptcha.com') && !url.includes('challenge'))
1600
- hcaptchaCheckbox = frame;
1601
- if (url.includes('hcaptcha') && url.includes('challenge'))
1602
- hcaptchaChallenge = frame;
1603
- if (url.includes('funcaptcha') || url.includes('arkoselabs'))
1604
- funcaptchaFrame = frame;
1605
- if (url.includes('service.mtcaptcha'))
1606
- mtcaptchaFrame = frame;
1607
- if ((url.includes('geetest.com') || url.includes('captcha.com')) && !url.includes('recaptcha') && !url.includes('hcaptcha'))
1608
- geetestFrame = frame;
1609
- }
1610
- if (recaptchaAnchor)
1611
- captchaType = 'recaptcha';
1612
- else if (hcaptchaCheckbox)
1613
- captchaType = 'hcaptcha';
1614
- else if (funcaptchaFrame)
1615
- captchaType = 'funcaptcha';
1616
- else if (mtcaptchaFrame)
1617
- captchaType = 'mtcaptcha';
1618
- else if (geetestFrame)
1619
- captchaType = 'geetest';
1620
- const pageContent = await p.content();
1621
- if (captchaType === 'unknown' && (pageContent.includes('cf-turnstile') || pageContent.includes('challenges.cloudflare'))) {
1622
- captchaType = 'turnstile';
1623
- }
1624
- if (captchaType === 'unknown' && (pageContent.includes('mtcaptcha') || pageContent.includes('MTCaptcha'))) {
1625
- captchaType = 'mtcaptcha';
1626
- }
1627
- if (captchaType === 'unknown' && recaptchaBframe)
1628
- captchaType = 'recaptcha';
1629
- if (captchaType === 'recaptcha') {
1630
- results.push('Attempting reCAPTCHA...');
1631
- try {
1632
- let checkboxFrame = recaptchaAnchor;
1633
- if (!checkboxFrame) {
1634
- for (const frame of frames) {
1635
- const url = frame.url();
1636
- if (url.includes('recaptcha') && (url.includes('anchor') || url.includes('api2/'))) {
1637
- checkboxFrame = frame;
1638
- break;
1545
+ const _solveTimeout = 60_000;
1546
+ const _solveLogic = async () => {
1547
+ const frames = p.frames();
1548
+ let captchaType = 'unknown';
1549
+ let recaptchaAnchor = null;
1550
+ let recaptchaBframe = null;
1551
+ let hcaptchaCheckbox = null;
1552
+ let hcaptchaChallenge = null;
1553
+ let funcaptchaFrame = null;
1554
+ let mtcaptchaFrame = null;
1555
+ let geetestFrame = null;
1556
+ for (const frame of frames) {
1557
+ const url = frame.url();
1558
+ if (url.includes('/recaptcha/') && url.includes('/anchor'))
1559
+ recaptchaAnchor = frame;
1560
+ if (url.includes('/recaptcha/') && url.includes('/bframe'))
1561
+ recaptchaBframe = frame;
1562
+ if (url.includes('newassets.hcaptcha.com') && !url.includes('challenge'))
1563
+ hcaptchaCheckbox = frame;
1564
+ if (url.includes('hcaptcha') && url.includes('challenge'))
1565
+ hcaptchaChallenge = frame;
1566
+ if (url.includes('funcaptcha') || url.includes('arkoselabs'))
1567
+ funcaptchaFrame = frame;
1568
+ if (url.includes('service.mtcaptcha'))
1569
+ mtcaptchaFrame = frame;
1570
+ if ((url.includes('geetest.com') || url.includes('captcha.com')) && !url.includes('recaptcha') && !url.includes('hcaptcha'))
1571
+ geetestFrame = frame;
1572
+ }
1573
+ if (recaptchaAnchor)
1574
+ captchaType = 'recaptcha';
1575
+ else if (hcaptchaCheckbox)
1576
+ captchaType = 'hcaptcha';
1577
+ else if (funcaptchaFrame)
1578
+ captchaType = 'funcaptcha';
1579
+ else if (mtcaptchaFrame)
1580
+ captchaType = 'mtcaptcha';
1581
+ else if (geetestFrame)
1582
+ captchaType = 'geetest';
1583
+ const pageContent = await p.content();
1584
+ if (captchaType === 'unknown' && (pageContent.includes('cf-turnstile') || pageContent.includes('challenges.cloudflare'))) {
1585
+ captchaType = 'turnstile';
1586
+ }
1587
+ if (captchaType === 'unknown' && (pageContent.includes('mtcaptcha') || pageContent.includes('MTCaptcha'))) {
1588
+ captchaType = 'mtcaptcha';
1589
+ }
1590
+ if (captchaType === 'unknown' && recaptchaBframe)
1591
+ captchaType = 'recaptcha';
1592
+ if (captchaType === 'recaptcha') {
1593
+ results.push('Attempting reCAPTCHA...');
1594
+ try {
1595
+ let checkboxFrame = recaptchaAnchor;
1596
+ if (!checkboxFrame) {
1597
+ for (const frame of frames) {
1598
+ const url = frame.url();
1599
+ if (url.includes('recaptcha') && (url.includes('anchor') || url.includes('api2/'))) {
1600
+ checkboxFrame = frame;
1601
+ break;
1602
+ }
1639
1603
  }
1640
1604
  }
1641
- }
1642
- if (checkboxFrame) {
1643
- const checkbox = checkboxFrame.locator('#recaptcha-anchor, .recaptcha-checkbox, .rc-anchor-checkbox');
1644
- if (await checkbox.count() > 0) {
1645
- await p.waitForTimeout(1000 + Math.random() * 1500);
1646
- await humanClick(checkbox, p);
1647
- await p.waitForTimeout(3000);
1648
- const updatedFrames = p.frames();
1649
- const challengeFrame = updatedFrames.find(f => f.url().includes('/recaptcha/') && f.url().includes('/bframe'));
1650
- if (challengeFrame) {
1651
- results.push('Image challenge appeared. Auto-solving...');
1652
- const maxRetries = 3;
1653
- let solved = false;
1654
- for (let attempt = 0; attempt < maxRetries; attempt++) {
1655
- if (attempt > 0)
1656
- results.push(`\nRetry attempt ${attempt}/${maxRetries - 1}...`);
1657
- const solveResult = await solveCaptchaGrid(p, challengeFrame, 'recaptcha');
1658
- results.push(solveResult);
1659
- if (solveResult.includes('Captcha solved!')) {
1660
- solved = true;
1661
- break;
1662
- }
1663
- if (solveResult.includes('Falling back to manual mode')) {
1664
- break;
1605
+ if (checkboxFrame) {
1606
+ const checkbox = checkboxFrame.locator('#recaptcha-anchor, .recaptcha-checkbox, .rc-anchor-checkbox');
1607
+ if (await checkbox.count() > 0) {
1608
+ await p.waitForTimeout(1000 + Math.random() * 1500);
1609
+ await humanClick(checkbox, p);
1610
+ await p.waitForTimeout(3000);
1611
+ const updatedFrames = p.frames();
1612
+ const challengeFrame = updatedFrames.find(f => f.url().includes('/recaptcha/') && f.url().includes('/bframe'));
1613
+ if (challengeFrame) {
1614
+ results.push('Image challenge appeared. Auto-solving...');
1615
+ const maxRetries = 3;
1616
+ let solved = false;
1617
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
1618
+ if (attempt > 0)
1619
+ results.push(`\nRetry attempt ${attempt}/${maxRetries - 1}...`);
1620
+ const solveResult = await solveCaptchaGrid(p, challengeFrame, 'recaptcha');
1621
+ results.push(solveResult);
1622
+ if (solveResult.includes('Captcha solved!')) {
1623
+ solved = true;
1624
+ break;
1625
+ }
1626
+ if (solveResult.includes('Falling back to manual mode')) {
1627
+ break;
1628
+ }
1629
+ await p.waitForTimeout(2000);
1630
+ const refreshedFrames = p.frames();
1631
+ const newChallenge = refreshedFrames.find(f => f.url().includes('/recaptcha/') && f.url().includes('/bframe'));
1632
+ if (!newChallenge) {
1633
+ results.push('Challenge frame disappeared, captcha may be solved');
1634
+ solved = true;
1635
+ break;
1636
+ }
1665
1637
  }
1666
- await p.waitForTimeout(2000);
1667
- const refreshedFrames = p.frames();
1668
- const newChallenge = refreshedFrames.find(f => f.url().includes('/recaptcha/') && f.url().includes('/bframe'));
1669
- if (!newChallenge) {
1670
- results.push('Challenge frame disappeared, captcha may be solved');
1671
- solved = true;
1672
- break;
1638
+ if (!solved && !results.some(r => r.includes('Falling back'))) {
1639
+ results.push(`\nAuto-solve exhausted after ${maxRetries} attempts. Use "captcha-grid" and "click-tile" for manual solving.`);
1673
1640
  }
1674
1641
  }
1675
- if (!solved && !results.some(r => r.includes('Falling back'))) {
1676
- results.push(`\nAuto-solve exhausted after ${maxRetries} attempts. Use "captcha-grid" and "click-tile" for manual solving.`);
1642
+ else {
1643
+ const checkmark = checkboxFrame.locator('.recaptcha-checkbox-checked, .rc-anchor-checkbox-checked');
1644
+ if (await checkmark.count() > 0) {
1645
+ results.push(ok('reCAPTCHA solved — no image challenge needed', { status: 'verified' }));
1646
+ }
1647
+ else {
1648
+ results.push(warn('Checkbox clicked but verification unclear', { suggestion: 'Use "captcha-grid" to check for image challenge' }));
1649
+ }
1677
1650
  }
1678
1651
  }
1679
1652
  else {
1680
- const checkmark = checkboxFrame.locator('.recaptcha-checkbox-checked, .rc-anchor-checkbox-checked');
1681
- if (await checkmark.count() > 0) {
1682
- results.push(ok('reCAPTCHA solved no image challenge needed', { status: 'verified' }));
1653
+ results.push(warn('reCAPTCHA anchor frame found but checkbox element missing', { action: 'clicking anchor body to trigger challenge' }));
1654
+ const anchor = checkboxFrame.locator('#recaptcha-anchor, .recaptcha-checkbox-area, [role="presentation"]').first();
1655
+ if (await anchor.count() === 0) {
1656
+ const body = checkboxFrame.locator('body');
1657
+ await humanClick(body, p).catch(() => { });
1658
+ }
1659
+ else {
1660
+ await humanClick(anchor, p).catch(() => { });
1661
+ }
1662
+ await p.waitForTimeout(3000);
1663
+ const retryFrames = p.frames();
1664
+ const challengeFrame = retryFrames.find(f => f.url().includes('/recaptcha/') && f.url().includes('/bframe'));
1665
+ if (challengeFrame) {
1666
+ results.push('Image challenge appeared after clicking anchor. Auto-solving...');
1667
+ const maxRetries = 3;
1668
+ let solved = false;
1669
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
1670
+ if (attempt > 0)
1671
+ results.push(`\nRetry attempt ${attempt}/${maxRetries - 1}...`);
1672
+ const solveResult = await solveCaptchaGrid(p, challengeFrame, 'recaptcha');
1673
+ results.push(solveResult);
1674
+ if (solveResult.includes('Captcha solved!')) {
1675
+ solved = true;
1676
+ break;
1677
+ }
1678
+ if (solveResult.includes('Falling back to manual mode'))
1679
+ break;
1680
+ await p.waitForTimeout(2000);
1681
+ const refreshedFrames = p.frames();
1682
+ const newChallenge = refreshedFrames.find(f => f.url().includes('/recaptcha/') && f.url().includes('/bframe'));
1683
+ if (!newChallenge) {
1684
+ results.push('Challenge frame disappeared, captcha may be solved');
1685
+ solved = true;
1686
+ break;
1687
+ }
1688
+ }
1689
+ if (!solved && !results.some(r => r.includes('Falling back'))) {
1690
+ results.push(`\nAuto-solve exhausted after ${maxRetries} attempts. Use "captcha-grid" and "click-tile" for manual solving.`);
1691
+ }
1683
1692
  }
1684
1693
  else {
1685
- results.push(warn('Checkbox clicked but verification unclear', { suggestion: 'Use "captcha-grid" to check for image challenge' }));
1694
+ results.push('No challenge appeared after clicking anchor. Use "captcha-grid" to check state.');
1686
1695
  }
1687
1696
  }
1688
1697
  }
1689
1698
  else {
1690
- results.push(warn('reCAPTCHA anchor frame found but checkbox element missing', { action: 'trying click on anchor body' }));
1691
- const anchor = checkboxFrame.locator('#recaptcha-anchor');
1692
- await humanClick(anchor, p).catch(() => { });
1693
- await p.waitForTimeout(3000);
1694
- results.push('Use "captcha-grid" to check for image challenge.');
1699
+ results.push(warn('No reCAPTCHA anchor frame found', { action: 'trying main page widget' }));
1700
+ const mainCheckbox = p.locator('.g-recaptcha, [data-sitekey]');
1701
+ if (await mainCheckbox.count() > 0) {
1702
+ await humanClick(mainCheckbox, p);
1703
+ await p.waitForTimeout(3000);
1704
+ results.push('Clicked reCAPTCHA widget. Use "captcha-grid" if image challenge appeared.');
1705
+ }
1706
+ else {
1707
+ results.push(err('reCAPTCHA widget not found on page', 'Check if the page has loaded or use "detect-captcha" first'));
1708
+ }
1695
1709
  }
1696
1710
  }
1697
- else {
1698
- results.push(warn('No reCAPTCHA anchor frame found', { action: 'trying main page widget' }));
1699
- const mainCheckbox = p.locator('.g-recaptcha, [data-sitekey]');
1700
- if (await mainCheckbox.count() > 0) {
1701
- await humanClick(mainCheckbox, p);
1702
- await p.waitForTimeout(3000);
1703
- results.push('Clicked reCAPTCHA widget. Use "captcha-grid" if image challenge appeared.');
1711
+ catch (e) {
1712
+ results.push(err(`reCAPTCHA click failed: ${e.message}`, 'Use "detect-captcha" first, then retry "solve-captcha"'));
1713
+ }
1714
+ }
1715
+ if (captchaType === 'hcaptcha') {
1716
+ results.push('Attempting hCaptcha...');
1717
+ try {
1718
+ const checkboxFrame = hcaptchaCheckbox;
1719
+ if (checkboxFrame) {
1720
+ const checkbox = checkboxFrame.locator('#checkbox, .check');
1721
+ if (await checkbox.count() > 0) {
1722
+ await p.waitForTimeout(800 + Math.random() * 1200);
1723
+ await humanClick(checkbox, p);
1724
+ await p.waitForTimeout(3000);
1725
+ const updatedFrames = p.frames();
1726
+ const challengeFrame = updatedFrames.find((f) => f.url().includes('hcaptcha') && f.url().includes('challenge'));
1727
+ if (challengeFrame) {
1728
+ results.push('Image challenge appeared. Auto-solving...');
1729
+ const maxRetries = 3;
1730
+ let solved = false;
1731
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
1732
+ if (attempt > 0)
1733
+ results.push(`\nRetry attempt ${attempt}/${maxRetries - 1}...`);
1734
+ const solveResult = await solveCaptchaGrid(p, challengeFrame, 'hcaptcha');
1735
+ results.push(solveResult);
1736
+ if (solveResult.includes('Captcha solved!')) {
1737
+ solved = true;
1738
+ break;
1739
+ }
1740
+ if (solveResult.includes('Falling back to manual mode')) {
1741
+ break;
1742
+ }
1743
+ await p.waitForTimeout(2000);
1744
+ const refreshedFrames = p.frames();
1745
+ const newChallenge = refreshedFrames.find((f) => f.url().includes('hcaptcha') && f.url().includes('challenge'));
1746
+ if (!newChallenge) {
1747
+ results.push('Challenge frame disappeared, captcha may be solved');
1748
+ solved = true;
1749
+ break;
1750
+ }
1751
+ }
1752
+ if (!solved && !results.some(r => r.includes('Falling back'))) {
1753
+ results.push(`\nAuto-solve exhausted after ${maxRetries} attempts. Use "captcha-grid" and "click-tile" for manual solving.`);
1754
+ }
1755
+ }
1756
+ else {
1757
+ const checkmark = checkboxFrame.locator('.check.solved, #checkbox[aria-checked="true"]');
1758
+ if (await checkmark.count() > 0) {
1759
+ results.push(ok('hCaptcha solved', { status: 'verified' }));
1760
+ }
1761
+ else {
1762
+ results.push(warn('hCaptcha checkbox clicked, status unclear', { suggestion: 'Use "captcha-grid" to check for image challenge' }));
1763
+ }
1764
+ }
1765
+ }
1766
+ else {
1767
+ results.push(err('hCaptcha checkbox element not found in frame'));
1768
+ }
1704
1769
  }
1705
1770
  else {
1706
- results.push(err('reCAPTCHA widget not found on page', 'Check if the page has loaded or use "detect-captcha" first'));
1771
+ results.push(err('hCaptcha checkbox frame not found', 'Use "detect-captcha" to scan for captcha type'));
1707
1772
  }
1708
1773
  }
1774
+ catch (e) {
1775
+ results.push(err(`hCaptcha click failed: ${e.message}`));
1776
+ }
1709
1777
  }
1710
- catch (e) {
1711
- results.push(err(`reCAPTCHA click failed: ${e.message}`, 'Use "detect-captcha" first, then retry "solve-captcha"'));
1778
+ if (captchaType === 'turnstile') {
1779
+ results.push('Attempting Cloudflare Turnstile...');
1780
+ try {
1781
+ const turnstileFrame = frames.find(f => f.url().includes('challenges.cloudflare'));
1782
+ if (turnstileFrame) {
1783
+ await p.waitForTimeout(1500 + Math.random() * 1000);
1784
+ const cb = turnstileFrame.locator('input[type="checkbox"], .cb-lb');
1785
+ if (await cb.count() > 0) {
1786
+ await humanClick(cb, p);
1787
+ await p.waitForTimeout(3000);
1788
+ results.push(ok('Turnstile checkbox clicked'));
1789
+ }
1790
+ else {
1791
+ await turnstileFrame.locator('body').click();
1792
+ await p.waitForTimeout(3000);
1793
+ results.push(warn('Turnstile frame clicked (managed challenge)', { next: 'Check if challenge resolved with screenshot' }));
1794
+ }
1795
+ }
1796
+ }
1797
+ catch (e) {
1798
+ results.push(err(`Turnstile failed: ${e.message}`));
1799
+ }
1712
1800
  }
1713
- }
1714
- if (captchaType === 'hcaptcha') {
1715
- results.push('Attempting hCaptcha...');
1716
- try {
1717
- const checkboxFrame = hcaptchaCheckbox;
1718
- if (checkboxFrame) {
1719
- const checkbox = checkboxFrame.locator('#checkbox, .check');
1720
- if (await checkbox.count() > 0) {
1721
- await p.waitForTimeout(800 + Math.random() * 1200);
1722
- await humanClick(checkbox, p);
1723
- await p.waitForTimeout(3000);
1724
- const updatedFrames = p.frames();
1725
- const challengeFrame = updatedFrames.find((f) => f.url().includes('hcaptcha') && f.url().includes('challenge'));
1726
- if (challengeFrame) {
1727
- results.push('Image challenge appeared. Auto-solving...');
1728
- const maxRetries = 3;
1729
- let solved = false;
1730
- for (let attempt = 0; attempt < maxRetries; attempt++) {
1731
- if (attempt > 0)
1732
- results.push(`\nRetry attempt ${attempt}/${maxRetries - 1}...`);
1733
- const solveResult = await solveCaptchaGrid(p, challengeFrame, 'hcaptcha');
1734
- results.push(solveResult);
1735
- if (solveResult.includes('Captcha solved!')) {
1736
- solved = true;
1801
+ if (captchaType === 'funcaptcha') {
1802
+ results.push('FunCaptcha (Arkose Labs) detected. Auto-solving...');
1803
+ try {
1804
+ const fcFrame = funcaptchaFrame;
1805
+ if (fcFrame) {
1806
+ await p.waitForTimeout(2000);
1807
+ const instruction = await fcFrame.evaluate(() => {
1808
+ const h2 = document.querySelector('h2, h3, .challenge-title, #challenge-stage .title, [class*="instruction"], [class*="prompt"]');
1809
+ return h2?.textContent?.trim() || '';
1810
+ }).catch(() => '');
1811
+ if (instruction)
1812
+ results.push(`Instruction: "${instruction}"`);
1813
+ const maxAttempts = 3;
1814
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
1815
+ if (attempt > 0)
1816
+ results.push(`\nRetry ${attempt}/${maxAttempts - 1}...`);
1817
+ const screenshotPath = join(homedir(), '.aurix-funcaptcha-puzzle.png');
1818
+ try {
1819
+ await fcFrame.locator('#challenge-stage, .challenge-content, .game-content, body').first().screenshot({ path: screenshotPath });
1820
+ }
1821
+ catch {
1822
+ await p.screenshot({ path: screenshotPath });
1823
+ }
1824
+ try {
1825
+ const ssBase64 = readFileBase64(screenshotPath);
1826
+ const prompt = instruction
1827
+ ? `This is a FunCaptcha puzzle. The instruction is: "${instruction}". Analyze the image and tell me EXACTLY what to do. Reply in this format:\n- For clicking: "CLICK x,y" (pixel coordinates relative to the puzzle image)\n- For dragging: "DRAG fromX,fromY toX,toY"\n- For rotating: "ROTATE degrees" (estimated rotation angle in degrees)\n- For selecting an option: "CLICK x,y" on the correct answer\nBe precise with coordinates.`
1828
+ : `This is a FunCaptcha puzzle. Analyze the image and determine what action is needed to solve it. Reply in this format:\n- For clicking: "CLICK x,y"\n- For dragging: "DRAG fromX,fromY toX,toY"\n- For rotating: "ROTATE degrees"\nBe precise with coordinates.`;
1829
+ const visionResp = await visionClassify(ssBase64, prompt);
1830
+ results.push(`Vision model: "${visionResp}"`);
1831
+ const clickMatch = visionResp.match(/CLICK\s+([\d.]+)\s*,\s*([\d.]+)/i);
1832
+ const dragMatch = visionResp.match(/DRAG\s+([\d.]+)\s*,\s*([\d.]+)\s+([\d.]+)\s*,\s*([\d.]+)/i);
1833
+ const rotateMatch = visionResp.match(/ROTATE\s+(-?[\d.]+)/i);
1834
+ const puzzleBox = await fcFrame.locator('#challenge-stage, .challenge-content, .game-content, body').first().boundingBox().catch(() => null);
1835
+ const offsetX = puzzleBox?.x || 0;
1836
+ const offsetY = puzzleBox?.y || 0;
1837
+ if (clickMatch) {
1838
+ const cx = offsetX + parseFloat(clickMatch[1]);
1839
+ const cy = offsetY + parseFloat(clickMatch[2]);
1840
+ await humanMove(cx, cy, p);
1841
+ await p.waitForTimeout(100 + Math.random() * 150);
1842
+ await p.mouse.down();
1843
+ await p.waitForTimeout(60 + Math.random() * 80);
1844
+ await p.mouse.up();
1845
+ results.push(`Clicked at (${Math.round(cx)}, ${Math.round(cy)})`);
1846
+ await p.waitForTimeout(2000);
1847
+ }
1848
+ else if (dragMatch) {
1849
+ const fromX = offsetX + parseFloat(dragMatch[1]);
1850
+ const fromY = offsetY + parseFloat(dragMatch[2]);
1851
+ const toX = offsetX + parseFloat(dragMatch[3]);
1852
+ const toY = offsetY + parseFloat(dragMatch[4]);
1853
+ await humanMove(fromX, fromY, p);
1854
+ await p.waitForTimeout(150 + Math.random() * 200);
1855
+ await p.mouse.down();
1856
+ await p.waitForTimeout(200 + Math.random() * 300);
1857
+ const steps = 20 + Math.floor(Math.random() * 15);
1858
+ for (let i = 1; i <= steps; i++) {
1859
+ const progress = i / steps;
1860
+ const eased = progress < 0.5 ? 2 * progress * progress : 1 - Math.pow(-2 * progress + 2, 2) / 2;
1861
+ await p.mouse.move(fromX + (toX - fromX) * eased, fromY + (toY - fromY) * eased + (Math.random() - 0.5) * 2);
1862
+ await p.waitForTimeout(10 + Math.random() * 15);
1863
+ }
1864
+ await p.mouse.move(toX, toY);
1865
+ await p.waitForTimeout(150);
1866
+ await p.mouse.up();
1867
+ results.push(`Dragged from (${Math.round(fromX)},${Math.round(fromY)}) to (${Math.round(toX)},${Math.round(toY)})`);
1868
+ await p.waitForTimeout(2000);
1869
+ }
1870
+ else if (rotateMatch) {
1871
+ const degrees = parseFloat(rotateMatch[1]);
1872
+ const rotator = fcFrame.locator('.rotator, [class*="rotate"], [class*="spinner"], canvas, .game-item').first();
1873
+ if (await rotator.count() > 0) {
1874
+ const rBox = await rotator.boundingBox();
1875
+ if (rBox) {
1876
+ const cx = rBox.x + rBox.width / 2;
1877
+ const cy = rBox.y + rBox.height / 2;
1878
+ const radius = rBox.width / 2;
1879
+ const startX = cx + radius;
1880
+ const startY = cy;
1881
+ const endAngle = (degrees * Math.PI) / 180;
1882
+ const endX = cx + radius * Math.cos(endAngle);
1883
+ const endY = cy + radius * Math.sin(endAngle);
1884
+ await humanMove(startX, startY, p);
1885
+ await p.waitForTimeout(150);
1886
+ await p.mouse.down();
1887
+ await p.waitForTimeout(200);
1888
+ const steps = 30;
1889
+ for (let i = 1; i <= steps; i++) {
1890
+ const angle = (endAngle * i) / steps;
1891
+ await p.mouse.move(cx + radius * Math.cos(angle), cy + radius * Math.sin(angle));
1892
+ await p.waitForTimeout(15 + Math.random() * 10);
1893
+ }
1894
+ await p.mouse.move(endX, endY);
1895
+ await p.waitForTimeout(150);
1896
+ await p.mouse.up();
1897
+ results.push(`Rotated ${degrees}°`);
1898
+ await p.waitForTimeout(2000);
1899
+ }
1900
+ }
1901
+ else {
1902
+ results.push('[WARN] No rotatable element found');
1903
+ }
1904
+ }
1905
+ else {
1906
+ results.push(`Could not parse vision model response: "${visionResp}"`);
1907
+ results.push('Falling back to manual mode. Read the puzzle screenshot and use click/drag-to/evaluate to solve.');
1737
1908
  break;
1738
1909
  }
1739
- if (solveResult.includes('Falling back to manual mode')) {
1910
+ const stillChallenge = await fcFrame.locator('#challenge-stage, .challenge-content').count();
1911
+ const successIndicators = await fcFrame.locator('[class*="success"], [class*="correct"], [class*="verified"], .game-success').count();
1912
+ if (successIndicators > 0) {
1913
+ results.push('[OK] FunCaptcha solved!');
1740
1914
  break;
1741
1915
  }
1742
- await p.waitForTimeout(2000);
1743
- const refreshedFrames = p.frames();
1744
- const newChallenge = refreshedFrames.find((f) => f.url().includes('hcaptcha') && f.url().includes('challenge'));
1745
- if (!newChallenge) {
1746
- results.push('Challenge frame disappeared, captcha may be solved');
1747
- solved = true;
1916
+ if (stillChallenge === 0) {
1917
+ results.push('[OK] FunCaptcha challenge dismissed — likely solved.');
1748
1918
  break;
1749
1919
  }
1920
+ if (attempt === maxAttempts - 1) {
1921
+ results.push(`Auto-solve exhausted after ${maxAttempts} attempts. Use click/drag-to/evaluate for manual solving.`);
1922
+ }
1923
+ else {
1924
+ results.push('Attempt did not solve, retrying...');
1925
+ await p.waitForTimeout(1500);
1926
+ }
1750
1927
  }
1751
- if (!solved && !results.some(r => r.includes('Falling back'))) {
1752
- results.push(`\nAuto-solve exhausted after ${maxRetries} attempts. Use "captcha-grid" and "click-tile" for manual solving.`);
1753
- }
1754
- }
1755
- else {
1756
- const checkmark = checkboxFrame.locator('.check.solved, #checkbox[aria-checked="true"]');
1757
- if (await checkmark.count() > 0) {
1758
- results.push(ok('hCaptcha solved', { status: 'verified' }));
1759
- }
1760
- else {
1761
- results.push(warn('hCaptcha checkbox clicked, status unclear', { suggestion: 'Use "captcha-grid" to check for image challenge' }));
1928
+ catch (e) {
1929
+ results.push(`Vision model failed: ${e.message}`);
1930
+ results.push('Auto-solve requires a vision-capable model. Read the puzzle screenshot at .aurix-funcaptcha-puzzle.png and use click/drag-to/evaluate to solve manually.');
1931
+ break;
1762
1932
  }
1763
1933
  }
1764
1934
  }
1765
1935
  else {
1766
- results.push(err('hCaptcha checkbox element not found in frame'));
1936
+ results.push(err('FunCaptcha frame not found', 'Use "detect-captcha" to scan the page first'));
1767
1937
  }
1768
1938
  }
1769
- else {
1770
- results.push(err('hCaptcha checkbox frame not found', 'Use "detect-captcha" to scan for captcha type'));
1939
+ catch (e) {
1940
+ results.push(err(`FunCaptcha auto-solve failed: ${e.message}`));
1771
1941
  }
1772
1942
  }
1773
- catch (e) {
1774
- results.push(err(`hCaptcha click failed: ${e.message}`));
1775
- }
1776
- }
1777
- if (captchaType === 'turnstile') {
1778
- results.push('Attempting Cloudflare Turnstile...');
1779
- try {
1780
- const turnstileFrame = frames.find(f => f.url().includes('challenges.cloudflare'));
1781
- if (turnstileFrame) {
1782
- await p.waitForTimeout(1500 + Math.random() * 1000);
1783
- const cb = turnstileFrame.locator('input[type="checkbox"], .cb-lb');
1784
- if (await cb.count() > 0) {
1785
- await humanClick(cb, p);
1786
- await p.waitForTimeout(3000);
1787
- results.push(ok('Turnstile checkbox clicked'));
1943
+ if (captchaType === 'mtcaptcha' || captchaType === 'geetest') {
1944
+ results.push(`Detected ${captchaType} challenge. Analyzing...`);
1945
+ const targetFrame = mtcaptchaFrame || geetestFrame || p;
1946
+ const hasSlider = await targetFrame.locator('.geetest_slider_button, .geetest_slider, [class*="slider_button"], [class*="slider-track"]').count();
1947
+ if (hasSlider > 0) {
1948
+ results.push('Type: SLIDER puzzle');
1949
+ const puzzleEl = targetFrame.locator('.geetest_panel, .geetest_widget, [class*="geetest_container"]').first();
1950
+ const screenshotPath = join(homedir(), '.aurix-slider-puzzle.png');
1951
+ try {
1952
+ if (await puzzleEl.count() > 0)
1953
+ await puzzleEl.screenshot({ path: screenshotPath });
1954
+ else
1955
+ await p.screenshot({ path: screenshotPath });
1788
1956
  }
1789
- else {
1790
- await turnstileFrame.locator('body').click();
1791
- await p.waitForTimeout(3000);
1792
- results.push(warn('Turnstile frame clicked (managed challenge)', { next: 'Check if challenge resolved with screenshot' }));
1957
+ catch {
1958
+ await p.screenshot({ path: screenshotPath });
1793
1959
  }
1794
- }
1795
- }
1796
- catch (e) {
1797
- results.push(err(`Turnstile failed: ${e.message}`));
1798
- }
1799
- }
1800
- if (captchaType === 'funcaptcha') {
1801
- results.push('FunCaptcha (Arkose Labs) detected. Auto-solving...');
1802
- try {
1803
- const fcFrame = funcaptchaFrame;
1804
- if (fcFrame) {
1805
- await p.waitForTimeout(2000);
1806
- const instruction = await fcFrame.evaluate(() => {
1807
- const h2 = document.querySelector('h2, h3, .challenge-title, #challenge-stage .title, [class*="instruction"], [class*="prompt"]');
1808
- return h2?.textContent?.trim() || '';
1809
- }).catch(() => '');
1810
- if (instruction)
1811
- results.push(`Instruction: "${instruction}"`);
1960
+ results.push(`Puzzle screenshot: ${screenshotPath}`);
1812
1961
  const maxAttempts = 3;
1813
1962
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
1814
1963
  if (attempt > 0)
1815
- results.push(`\nRetry ${attempt}/${maxAttempts - 1}...`);
1816
- const screenshotPath = join(homedir(), '.aurix-funcaptcha-puzzle.png');
1817
- try {
1818
- await fcFrame.locator('#challenge-stage, .challenge-content, .game-content, body').first().screenshot({ path: screenshotPath });
1964
+ results.push(`\nSlider retry ${attempt}/${maxAttempts - 1}...`);
1965
+ const sliderInfo = await targetFrame.evaluate(() => {
1966
+ const info = {};
1967
+ const cut = document.querySelector('.geetest_cut, .geetest_piece_bg, [class*="geetest_cut"], [class*="slider_cut"], [class*="puzzle-gap"]');
1968
+ if (cut) {
1969
+ const cutRect = cut.getBoundingClientRect();
1970
+ const style = window.getComputedStyle(cut);
1971
+ info.cut = { left: cutRect.left, width: cutRect.width, styleLeft: parseFloat(style.left) || null, transform: style.transform || null };
1972
+ }
1973
+ const bg = document.querySelector('.geetest_canvas_bg, .geetest_bg, [class*="geetest_canvas"], canvas[class*="bg"]');
1974
+ if (bg) {
1975
+ const bgRect = bg.getBoundingClientRect();
1976
+ info.bg = { left: bgRect.left, width: bgRect.width };
1977
+ }
1978
+ const piece = document.querySelector('.geetest_piece, .geetest_slider_piece, [class*="slider_piece"]');
1979
+ if (piece) {
1980
+ const pieceRect = piece.getBoundingClientRect();
1981
+ info.piece = { left: pieceRect.left, width: pieceRect.width };
1982
+ }
1983
+ const slider = document.querySelector('.geetest_slider_button, .geetest_slider_knob, [class*="slider_button"]');
1984
+ if (slider) {
1985
+ const sliderRect = slider.getBoundingClientRect();
1986
+ info.slider = { left: sliderRect.left, width: sliderRect.width, centerX: sliderRect.left + sliderRect.width / 2, centerY: sliderRect.top + sliderRect.height / 2 };
1987
+ }
1988
+ const track = document.querySelector('.geetest_slider_track, .geetest_slider, [class*="slider_track"]');
1989
+ if (track)
1990
+ info.track = { width: track.getBoundingClientRect().width };
1991
+ return info;
1992
+ });
1993
+ let gapOffset = null;
1994
+ if (sliderInfo.cut && sliderInfo.bg) {
1995
+ if (sliderInfo.cut.styleLeft && sliderInfo.cut.styleLeft > 0) {
1996
+ gapOffset = Math.round(sliderInfo.cut.styleLeft);
1997
+ }
1998
+ else {
1999
+ gapOffset = Math.round(sliderInfo.cut.left - sliderInfo.bg.left);
2000
+ }
1819
2001
  }
1820
- catch {
1821
- await p.screenshot({ path: screenshotPath });
2002
+ if (gapOffset === null && sliderInfo.cut?.transform && sliderInfo.cut.transform !== 'none') {
2003
+ const match = sliderInfo.cut.transform.match(/matrix\(.*?,\s*([\d.]+)/);
2004
+ if (match)
2005
+ gapOffset = Math.round(parseFloat(match[1]));
1822
2006
  }
1823
- try {
1824
- const ssBase64 = readFileBase64(screenshotPath);
1825
- const prompt = instruction
1826
- ? `This is a FunCaptcha puzzle. The instruction is: "${instruction}". Analyze the image and tell me EXACTLY what to do. Reply in this format:\n- For clicking: "CLICK x,y" (pixel coordinates relative to the puzzle image)\n- For dragging: "DRAG fromX,fromY toX,toY"\n- For rotating: "ROTATE degrees" (estimated rotation angle in degrees)\n- For selecting an option: "CLICK x,y" on the correct answer\nBe precise with coordinates.`
1827
- : `This is a FunCaptcha puzzle. Analyze the image and determine what action is needed to solve it. Reply in this format:\n- For clicking: "CLICK x,y"\n- For dragging: "DRAG fromX,fromY toX,toY"\n- For rotating: "ROTATE degrees"\nBe precise with coordinates.`;
1828
- const visionResp = await visionClassify(ssBase64, prompt);
1829
- results.push(`Vision model: "${visionResp}"`);
1830
- const clickMatch = visionResp.match(/CLICK\s+([\d.]+)\s*,\s*([\d.]+)/i);
1831
- const dragMatch = visionResp.match(/DRAG\s+([\d.]+)\s*,\s*([\d.]+)\s+([\d.]+)\s*,\s*([\d.]+)/i);
1832
- const rotateMatch = visionResp.match(/ROTATE\s+(-?[\d.]+)/i);
1833
- const puzzleBox = await fcFrame.locator('#challenge-stage, .challenge-content, .game-content, body').first().boundingBox().catch(() => null);
1834
- const offsetX = puzzleBox?.x || 0;
1835
- const offsetY = puzzleBox?.y || 0;
1836
- if (clickMatch) {
1837
- const cx = offsetX + parseFloat(clickMatch[1]);
1838
- const cy = offsetY + parseFloat(clickMatch[2]);
1839
- await humanMove(cx, cy, p);
1840
- await p.waitForTimeout(100 + Math.random() * 150);
1841
- await p.mouse.down();
1842
- await p.waitForTimeout(60 + Math.random() * 80);
1843
- await p.mouse.up();
1844
- results.push(`Clicked at (${Math.round(cx)}, ${Math.round(cy)})`);
1845
- await p.waitForTimeout(2000);
2007
+ if (gapOffset === null) {
2008
+ results.push('DOM gap detection failed, using vision model...');
2009
+ try {
2010
+ const ssBase64 = readFileBase64(screenshotPath);
2011
+ const visionResp = await visionClassify(ssBase64, 'This is a slider puzzle captcha. There is a gap/hole in the background image where a puzzle piece needs to go. Estimate the horizontal pixel position of the CENTER of the gap, measured from the LEFT edge of the puzzle image. Reply with ONLY the number (e.g. "145").');
2012
+ const parsed = parseInt(visionResp.replace(/[^\d]/g, ''));
2013
+ if (!isNaN(parsed) && parsed > 10 && parsed < 500) {
2014
+ gapOffset = parsed;
2015
+ results.push(`Vision model: gap at ~${gapOffset}px`);
2016
+ }
2017
+ else {
2018
+ results.push(`Vision model returned: "${visionResp}" could not parse gap position`);
2019
+ }
1846
2020
  }
1847
- else if (dragMatch) {
1848
- const fromX = offsetX + parseFloat(dragMatch[1]);
1849
- const fromY = offsetY + parseFloat(dragMatch[2]);
1850
- const toX = offsetX + parseFloat(dragMatch[3]);
1851
- const toY = offsetY + parseFloat(dragMatch[4]);
1852
- await humanMove(fromX, fromY, p);
1853
- await p.waitForTimeout(150 + Math.random() * 200);
2021
+ catch (e) {
2022
+ results.push(`Vision model failed: ${e.message}`);
2023
+ }
2024
+ }
2025
+ if (gapOffset === null) {
2026
+ results.push('[WARN] Could not determine gap position. Use "slider-analyze" for manual analysis, then "drag-to" to slide.');
2027
+ break;
2028
+ }
2029
+ const pieceHalf = Math.round((sliderInfo.piece?.width || 44) / 2);
2030
+ const adjusted = gapOffset - pieceHalf;
2031
+ results.push(`Gap: ${gapOffset}px, piece half: ${pieceHalf}px, drag distance: ${adjusted}px`);
2032
+ if (sliderInfo.slider) {
2033
+ try {
2034
+ const startX = sliderInfo.slider.centerX;
2035
+ const startY = sliderInfo.slider.centerY;
2036
+ const endX = startX + adjusted;
2037
+ await humanMove(startX, startY, p);
2038
+ await p.waitForTimeout(150 + Math.random() * 250);
1854
2039
  await p.mouse.down();
1855
2040
  await p.waitForTimeout(200 + Math.random() * 300);
1856
- const steps = 20 + Math.floor(Math.random() * 15);
2041
+ const steps = 25 + Math.floor(Math.random() * 20);
1857
2042
  for (let i = 1; i <= steps; i++) {
1858
2043
  const progress = i / steps;
1859
2044
  const eased = progress < 0.5 ? 2 * progress * progress : 1 - Math.pow(-2 * progress + 2, 2) / 2;
1860
- await p.mouse.move(fromX + (toX - fromX) * eased, fromY + (toY - fromY) * eased + (Math.random() - 0.5) * 2);
1861
- await p.waitForTimeout(10 + Math.random() * 15);
2045
+ const x = startX + adjusted * eased + (Math.random() - 0.5) * 2;
2046
+ const y = startY + (Math.random() - 0.5) * 2;
2047
+ await p.mouse.move(x, y);
2048
+ await p.waitForTimeout(10 + Math.random() * 20);
1862
2049
  }
1863
- await p.mouse.move(toX, toY);
2050
+ await p.mouse.move(endX, startY);
1864
2051
  await p.waitForTimeout(150);
1865
2052
  await p.mouse.up();
1866
- results.push(`Dragged from (${Math.round(fromX)},${Math.round(fromY)}) to (${Math.round(toX)},${Math.round(toY)})`);
1867
2053
  await p.waitForTimeout(2000);
1868
- }
1869
- else if (rotateMatch) {
1870
- const degrees = parseFloat(rotateMatch[1]);
1871
- const rotator = fcFrame.locator('.rotator, [class*="rotate"], [class*="spinner"], canvas, .game-item').first();
1872
- if (await rotator.count() > 0) {
1873
- const rBox = await rotator.boundingBox();
1874
- if (rBox) {
1875
- const cx = rBox.x + rBox.width / 2;
1876
- const cy = rBox.y + rBox.height / 2;
1877
- const radius = rBox.width / 2;
1878
- const startX = cx + radius;
1879
- const startY = cy;
1880
- const endAngle = (degrees * Math.PI) / 180;
1881
- const endX = cx + radius * Math.cos(endAngle);
1882
- const endY = cy + radius * Math.sin(endAngle);
1883
- await humanMove(startX, startY, p);
1884
- await p.waitForTimeout(150);
1885
- await p.mouse.down();
1886
- await p.waitForTimeout(200);
1887
- const steps = 30;
1888
- for (let i = 1; i <= steps; i++) {
1889
- const angle = (endAngle * i) / steps;
1890
- await p.mouse.move(cx + radius * Math.cos(angle), cy + radius * Math.sin(angle));
1891
- await p.waitForTimeout(15 + Math.random() * 10);
1892
- }
1893
- await p.mouse.move(endX, endY);
1894
- await p.waitForTimeout(150);
1895
- await p.mouse.up();
1896
- results.push(`Rotated ${degrees}°`);
1897
- await p.waitForTimeout(2000);
1898
- }
2054
+ results.push('Slider dragged, checking result...');
2055
+ const successEl = await targetFrame.locator('.geetest_success, .geetest_tip_success, [class*="success"], [class*="verified"]').count();
2056
+ if (successEl > 0) {
2057
+ results.push('[OK] Slider captcha solved!');
2058
+ break;
1899
2059
  }
1900
- else {
1901
- results.push('[WARN] No rotatable element found');
2060
+ const failEl = await targetFrame.locator('.geetest_fail, .geetest_tip_fail, [class*="fail"], [class*="error"], [class*="retry"]').count();
2061
+ if (failEl > 0) {
2062
+ results.push('Slider attempt failed, retrying...');
2063
+ const refreshBtn = targetFrame.locator('.geetest_refresh, [class*="refresh"], [class*="retry"]').first();
2064
+ if (await refreshBtn.count() > 0)
2065
+ await refreshBtn.click().catch(() => { });
2066
+ await p.waitForTimeout(1500);
2067
+ try {
2068
+ if (await puzzleEl.count() > 0)
2069
+ await puzzleEl.screenshot({ path: screenshotPath });
2070
+ }
2071
+ catch { }
2072
+ continue;
1902
2073
  }
1903
- }
1904
- else {
1905
- results.push(`Could not parse vision model response: "${visionResp}"`);
1906
- results.push('Falling back to manual mode. Read the puzzle screenshot and use click/drag-to/evaluate to solve.');
2074
+ results.push('[OK] Slider dragged — outcome unconfirmed, check page state.');
1907
2075
  break;
1908
2076
  }
1909
- const stillChallenge = await fcFrame.locator('#challenge-stage, .challenge-content').count();
1910
- const successIndicators = await fcFrame.locator('[class*="success"], [class*="correct"], [class*="verified"], .game-success').count();
1911
- if (successIndicators > 0) {
1912
- results.push('[OK] FunCaptcha solved!');
2077
+ catch (e) {
2078
+ results.push(`Drag failed: ${e.message}`);
1913
2079
  break;
1914
2080
  }
1915
- if (stillChallenge === 0) {
1916
- results.push('[OK] FunCaptcha challenge dismissed — likely solved.');
1917
- break;
1918
- }
1919
- if (attempt === maxAttempts - 1) {
1920
- results.push(`Auto-solve exhausted after ${maxAttempts} attempts. Use click/drag-to/evaluate for manual solving.`);
1921
- }
1922
- else {
1923
- results.push('Attempt did not solve, retrying...');
1924
- await p.waitForTimeout(1500);
1925
- }
1926
2081
  }
1927
- catch (e) {
1928
- results.push(`Vision model failed: ${e.message}`);
1929
- results.push('Auto-solve requires a vision-capable model. Read the puzzle screenshot at .aurix-funcaptcha-puzzle.png and use click/drag-to/evaluate to solve manually.');
2082
+ else {
2083
+ results.push('[WARN] Slider handle not found in DOM.');
1930
2084
  break;
1931
2085
  }
1932
2086
  }
1933
2087
  }
1934
2088
  else {
1935
- results.push(err('FunCaptcha frame not found', 'Use "detect-captcha" to scan the page first'));
2089
+ results.push('Type: IMAGE challenge');
2090
+ const gridResult = await solveCaptchaGrid(p, targetFrame, captchaType);
2091
+ results.push(gridResult);
1936
2092
  }
1937
2093
  }
1938
- catch (e) {
1939
- results.push(err(`FunCaptcha auto-solve failed: ${e.message}`));
1940
- }
1941
- }
1942
- if (captchaType === 'mtcaptcha' || captchaType === 'geetest') {
1943
- results.push(`Detected ${captchaType} challenge. Analyzing...`);
1944
- const targetFrame = mtcaptchaFrame || geetestFrame || p;
1945
- const hasSlider = await targetFrame.locator('.geetest_slider_button, .geetest_slider, [class*="slider_button"], [class*="slider-track"]').count();
1946
- if (hasSlider > 0) {
1947
- results.push('Type: SLIDER puzzle');
1948
- const puzzleEl = targetFrame.locator('.geetest_panel, .geetest_widget, [class*="geetest_container"]').first();
1949
- const screenshotPath = join(homedir(), '.aurix-slider-puzzle.png');
1950
- try {
1951
- if (await puzzleEl.count() > 0)
1952
- await puzzleEl.screenshot({ path: screenshotPath });
1953
- else
1954
- await p.screenshot({ path: screenshotPath });
1955
- }
1956
- catch {
1957
- await p.screenshot({ path: screenshotPath });
1958
- }
1959
- results.push(`Puzzle screenshot: ${screenshotPath}`);
1960
- const maxAttempts = 3;
1961
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
1962
- if (attempt > 0)
1963
- results.push(`\nSlider retry ${attempt}/${maxAttempts - 1}...`);
1964
- const sliderInfo = await targetFrame.evaluate(() => {
1965
- const info = {};
1966
- const cut = document.querySelector('.geetest_cut, .geetest_piece_bg, [class*="geetest_cut"], [class*="slider_cut"], [class*="puzzle-gap"]');
1967
- if (cut) {
1968
- const cutRect = cut.getBoundingClientRect();
1969
- const style = window.getComputedStyle(cut);
1970
- info.cut = { left: cutRect.left, width: cutRect.width, styleLeft: parseFloat(style.left) || null, transform: style.transform || null };
1971
- }
1972
- const bg = document.querySelector('.geetest_canvas_bg, .geetest_bg, [class*="geetest_canvas"], canvas[class*="bg"]');
1973
- if (bg) {
1974
- const bgRect = bg.getBoundingClientRect();
1975
- info.bg = { left: bgRect.left, width: bgRect.width };
1976
- }
1977
- const piece = document.querySelector('.geetest_piece, .geetest_slider_piece, [class*="slider_piece"]');
1978
- if (piece) {
1979
- const pieceRect = piece.getBoundingClientRect();
1980
- info.piece = { left: pieceRect.left, width: pieceRect.width };
1981
- }
1982
- const slider = document.querySelector('.geetest_slider_button, .geetest_slider_knob, [class*="slider_button"]');
1983
- if (slider) {
1984
- const sliderRect = slider.getBoundingClientRect();
1985
- info.slider = { left: sliderRect.left, width: sliderRect.width, centerX: sliderRect.left + sliderRect.width / 2, centerY: sliderRect.top + sliderRect.height / 2 };
1986
- }
1987
- const track = document.querySelector('.geetest_slider_track, .geetest_slider, [class*="slider_track"]');
1988
- if (track)
1989
- info.track = { width: track.getBoundingClientRect().width };
1990
- return info;
1991
- });
1992
- let gapOffset = null;
1993
- if (sliderInfo.cut && sliderInfo.bg) {
1994
- if (sliderInfo.cut.styleLeft && sliderInfo.cut.styleLeft > 0) {
1995
- gapOffset = Math.round(sliderInfo.cut.styleLeft);
1996
- }
1997
- else {
1998
- gapOffset = Math.round(sliderInfo.cut.left - sliderInfo.bg.left);
1999
- }
2000
- }
2001
- if (gapOffset === null && sliderInfo.cut?.transform && sliderInfo.cut.transform !== 'none') {
2002
- const match = sliderInfo.cut.transform.match(/matrix\(.*?,\s*([\d.]+)/);
2003
- if (match)
2004
- gapOffset = Math.round(parseFloat(match[1]));
2005
- }
2006
- if (gapOffset === null) {
2007
- results.push('DOM gap detection failed, using vision model...');
2008
- try {
2009
- const ssBase64 = readFileBase64(screenshotPath);
2010
- const visionResp = await visionClassify(ssBase64, 'This is a slider puzzle captcha. There is a gap/hole in the background image where a puzzle piece needs to go. Estimate the horizontal pixel position of the CENTER of the gap, measured from the LEFT edge of the puzzle image. Reply with ONLY the number (e.g. "145").');
2011
- const parsed = parseInt(visionResp.replace(/[^\d]/g, ''));
2012
- if (!isNaN(parsed) && parsed > 10 && parsed < 500) {
2013
- gapOffset = parsed;
2014
- results.push(`Vision model: gap at ~${gapOffset}px`);
2015
- }
2016
- else {
2017
- results.push(`Vision model returned: "${visionResp}" — could not parse gap position`);
2018
- }
2019
- }
2020
- catch (e) {
2021
- results.push(`Vision model failed: ${e.message}`);
2022
- }
2023
- }
2024
- if (gapOffset === null) {
2025
- results.push('[WARN] Could not determine gap position. Use "slider-analyze" for manual analysis, then "drag-to" to slide.');
2026
- break;
2027
- }
2028
- const pieceHalf = Math.round((sliderInfo.piece?.width || 44) / 2);
2029
- const adjusted = gapOffset - pieceHalf;
2030
- results.push(`Gap: ${gapOffset}px, piece half: ${pieceHalf}px, drag distance: ${adjusted}px`);
2031
- if (sliderInfo.slider) {
2032
- try {
2033
- const startX = sliderInfo.slider.centerX;
2034
- const startY = sliderInfo.slider.centerY;
2035
- const endX = startX + adjusted;
2036
- await humanMove(startX, startY, p);
2037
- await p.waitForTimeout(150 + Math.random() * 250);
2038
- await p.mouse.down();
2039
- await p.waitForTimeout(200 + Math.random() * 300);
2040
- const steps = 25 + Math.floor(Math.random() * 20);
2041
- for (let i = 1; i <= steps; i++) {
2042
- const progress = i / steps;
2043
- const eased = progress < 0.5 ? 2 * progress * progress : 1 - Math.pow(-2 * progress + 2, 2) / 2;
2044
- const x = startX + adjusted * eased + (Math.random() - 0.5) * 2;
2045
- const y = startY + (Math.random() - 0.5) * 2;
2046
- await p.mouse.move(x, y);
2047
- await p.waitForTimeout(10 + Math.random() * 20);
2048
- }
2049
- await p.mouse.move(endX, startY);
2050
- await p.waitForTimeout(150);
2051
- await p.mouse.up();
2052
- await p.waitForTimeout(2000);
2053
- results.push('Slider dragged, checking result...');
2054
- const successEl = await targetFrame.locator('.geetest_success, .geetest_tip_success, [class*="success"], [class*="verified"]').count();
2055
- if (successEl > 0) {
2056
- results.push('[OK] Slider captcha solved!');
2057
- break;
2058
- }
2059
- const failEl = await targetFrame.locator('.geetest_fail, .geetest_tip_fail, [class*="fail"], [class*="error"], [class*="retry"]').count();
2060
- if (failEl > 0) {
2061
- results.push('Slider attempt failed, retrying...');
2062
- const refreshBtn = targetFrame.locator('.geetest_refresh, [class*="refresh"], [class*="retry"]').first();
2063
- if (await refreshBtn.count() > 0)
2064
- await refreshBtn.click().catch(() => { });
2065
- await p.waitForTimeout(1500);
2066
- try {
2067
- if (await puzzleEl.count() > 0)
2068
- await puzzleEl.screenshot({ path: screenshotPath });
2094
+ if (captchaType === 'unknown') {
2095
+ const imgCaptcha = p.locator('img[src*="captcha"], #captcha-image, .captcha-image, img.captcha');
2096
+ if (await imgCaptcha.count() > 0) {
2097
+ results.push('Text-based captcha detected.');
2098
+ const screenshotPath = join(homedir(), '.aurix-captcha-challenge.png');
2099
+ await imgCaptcha.first().screenshot({ path: screenshotPath });
2100
+ results.push(`Captcha image saved: ${screenshotPath}`);
2101
+ try {
2102
+ const ssBase64 = readFileBase64(screenshotPath);
2103
+ const visionResp = await visionClassify(ssBase64, 'Read the text/numbers in this captcha image. Reply with ONLY the exact text shown, nothing else.');
2104
+ const captchaText = visionResp.replace(/[^a-zA-Z0-9]/g, '').trim();
2105
+ if (captchaText.length >= 2) {
2106
+ const input = p.locator('input[name*="captcha"], input[id*="captcha"], input[placeholder*="captcha" i], input[placeholder*="code" i]');
2107
+ if (await input.count() > 0) {
2108
+ await input.first().click();
2109
+ await input.first().fill('');
2110
+ for (const char of captchaText) {
2111
+ await input.first().type(char, { delay: 80 + Math.random() * 120 });
2069
2112
  }
2070
- catch { }
2071
- continue;
2113
+ results.push(`[OK] Auto-filled captcha text: "${captchaText}"`);
2072
2114
  }
2073
- results.push('[OK] Slider dragged — outcome unconfirmed, check page state.');
2074
- break;
2075
- }
2076
- catch (e) {
2077
- results.push(`Drag failed: ${e.message}`);
2078
- break;
2079
- }
2080
- }
2081
- else {
2082
- results.push('[WARN] Slider handle not found in DOM.');
2083
- break;
2084
- }
2085
- }
2086
- }
2087
- else {
2088
- results.push('Type: IMAGE challenge');
2089
- const gridResult = await solveCaptchaGrid(p, targetFrame, captchaType);
2090
- results.push(gridResult);
2091
- }
2092
- }
2093
- if (captchaType === 'unknown') {
2094
- const imgCaptcha = p.locator('img[src*="captcha"], #captcha-image, .captcha-image, img.captcha');
2095
- if (await imgCaptcha.count() > 0) {
2096
- results.push('Text-based captcha detected.');
2097
- const screenshotPath = join(homedir(), '.aurix-captcha-challenge.png');
2098
- await imgCaptcha.first().screenshot({ path: screenshotPath });
2099
- results.push(`Captcha image saved: ${screenshotPath}`);
2100
- try {
2101
- const ssBase64 = readFileBase64(screenshotPath);
2102
- const visionResp = await visionClassify(ssBase64, 'Read the text/numbers in this captcha image. Reply with ONLY the exact text shown, nothing else.');
2103
- const captchaText = visionResp.replace(/[^a-zA-Z0-9]/g, '').trim();
2104
- if (captchaText.length >= 2) {
2105
- const input = p.locator('input[name*="captcha"], input[id*="captcha"], input[placeholder*="captcha" i], input[placeholder*="code" i]');
2106
- if (await input.count() > 0) {
2107
- await input.first().click();
2108
- await input.first().fill('');
2109
- for (const char of captchaText) {
2110
- await input.first().type(char, { delay: 80 + Math.random() * 120 });
2115
+ else {
2116
+ results.push(`Vision model read: "${captchaText}" — but no captcha input field found. Use "fill" to type it manually.`);
2111
2117
  }
2112
- results.push(`[OK] Auto-filled captcha text: "${captchaText}"`);
2113
2118
  }
2114
2119
  else {
2115
- results.push(`Vision model read: "${captchaText}" — but no captcha input field found. Use "fill" to type it manually.`);
2120
+ results.push(`Vision model returned: "${visionResp}" — could not read captcha text`);
2121
+ results.push('Read the screenshot and use "fill" to type the captcha text manually.');
2116
2122
  }
2117
2123
  }
2118
- else {
2119
- results.push(`Vision model returned: "${visionResp}" — could not read captcha text`);
2120
- results.push('Read the screenshot and use "fill" to type the captcha text manually.');
2124
+ catch (e) {
2125
+ results.push(`Vision auto-fill failed: ${e.message}`);
2126
+ results.push('Read the captcha screenshot and use "fill" to type it manually.');
2121
2127
  }
2122
2128
  }
2123
- catch (e) {
2124
- results.push(`Vision auto-fill failed: ${e.message}`);
2125
- results.push('Read the captcha screenshot and use "fill" to type it manually.');
2129
+ else {
2130
+ results.push('No recognizable captcha. Taking screenshot and scanning...');
2131
+ const screenshotPath = join(homedir(), '.aurix-captcha-challenge.png');
2132
+ await p.screenshot({ path: screenshotPath });
2133
+ results.push(`Screenshot saved: ${screenshotPath}`);
2134
+ results.push('Use "captcha-grid" to scan for any challenge overlay.');
2126
2135
  }
2127
2136
  }
2128
- else {
2129
- results.push('No recognizable captcha. Taking screenshot and scanning...');
2130
- const screenshotPath = join(homedir(), '.aurix-captcha-challenge.png');
2131
- await p.screenshot({ path: screenshotPath });
2132
- results.push(`Screenshot saved: ${screenshotPath}`);
2133
- results.push('Use "captcha-grid" to scan for any challenge overlay.');
2134
- }
2137
+ const screenshotPath = join(homedir(), '.aurix-captcha-after.png');
2138
+ await p.screenshot({ path: screenshotPath });
2139
+ results.push(`\nPost-attempt screenshot: ${screenshotPath}`);
2140
+ return results.join('\n');
2141
+ };
2142
+ try {
2143
+ return await Promise.race([
2144
+ _solveLogic(),
2145
+ new Promise((_, rej) => setTimeout(() => rej(new Error('solve-captcha timed out (60s)')), _solveTimeout)),
2146
+ ]);
2147
+ }
2148
+ catch (e) {
2149
+ results.push(`\n[TIMEOUT] ${e.message}`);
2150
+ results.push('Auto-solve did not complete. Use "captcha-grid" and "click-tile" for manual solving.');
2151
+ return results.join('\n');
2135
2152
  }
2136
- const screenshotPath = join(homedir(), '.aurix-captcha-after.png');
2137
- await p.screenshot({ path: screenshotPath });
2138
- results.push(`\nPost-attempt screenshot: ${screenshotPath}`);
2139
- return results.join('\n');
2140
2153
  }
2141
2154
  case 'captcha-grid': {
2142
2155
  const p = await ensureBrowser();
@@ -2881,13 +2894,27 @@ Sessions: session="a"/"b"/"c" for parallel browsers. proxy="host:port:user:pass"
2881
2894
  results.push('Verification confirmed. Continuing form submission...');
2882
2895
  }
2883
2896
  }
2884
- const clicked = await clickField([
2897
+ let clicked = await clickField([
2885
2898
  'button[type="submit"]', 'input[type="submit"]',
2886
- 'button:has-text("Sign up")', 'button:has-text("Register")',
2899
+ 'button:has-text("Sign Up With Email")', 'button:has-text("Sign up")',
2900
+ 'button:has-text("Sign Up")', 'button:has-text("sign up")',
2901
+ 'button:has-text("Create Account")', 'button:has-text("create account")',
2902
+ 'button:has-text("Register")', 'button:has-text("register")',
2887
2903
  'button:has-text("Next")', 'button:has-text("Continue")',
2888
2904
  'button:has-text("Submit")', 'button:has-text("Create")',
2889
2905
  '#signup-button', '#submit-btn',
2890
2906
  ], 'Submit/Next button');
2907
+ if (!clicked) {
2908
+ const submitBtn = activeFrame.locator('button').filter({ hasText: /sign\s*up|register|submit|create|continue|next/i }).first();
2909
+ if (await submitBtn.count() > 0 && await submitBtn.isVisible()) {
2910
+ try {
2911
+ await submitBtn.click({ timeout: 3000 });
2912
+ results.push(' ✓ Submit button: clicked (regex match)');
2913
+ clicked = true;
2914
+ }
2915
+ catch { }
2916
+ }
2917
+ }
2891
2918
  await p.waitForTimeout(2000);
2892
2919
  results.push('');
2893
2920
  results.push(`--- Result ---`);