aurix-ai 2.5.8 → 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(() => { });
1683
1658
  }
1684
1659
  else {
1685
- results.push(warn('Checkbox clicked but verification unclear', { suggestion: 'Use "captcha-grid" to check for image challenge' }));
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
+ }
1692
+ }
1693
+ else {
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();
@@ -2611,6 +2624,27 @@ Sessions: session="a"/"b"/"c" for parallel browsers. proxy="host:port:user:pass"
2611
2624
  results.push('=== SIGNUP ASSIST ===');
2612
2625
  results.push(`Provided: ${Object.keys(data).join(', ')}`);
2613
2626
  results.push('');
2627
+ const cookieSelectors = [
2628
+ 'button:has-text("Accept All")', 'button:has-text("Accept all")', 'button:has-text("accept all")',
2629
+ 'button:has-text("Accept")', 'button:has-text("Accept Cookies")',
2630
+ 'button:has-text("I agree")', 'button:has-text("Got it")', 'button:has-text("OK")',
2631
+ 'button:has-text("Allow All")', 'button:has-text("Allow all")',
2632
+ '[id*="cookie"] button', '[class*="cookie"] button',
2633
+ '[id*="consent"] button', '[class*="consent"] button',
2634
+ '.cc-accept', '.cookie-accept', '#accept-cookies',
2635
+ ];
2636
+ for (const sel of cookieSelectors) {
2637
+ try {
2638
+ const btn = p.locator(sel).first();
2639
+ if (await btn.count() > 0 && await btn.isVisible()) {
2640
+ await btn.click({ timeout: 2000 });
2641
+ results.push(` ✓ Dismissed cookie banner: ${sel}`);
2642
+ await p.waitForTimeout(500);
2643
+ break;
2644
+ }
2645
+ }
2646
+ catch { }
2647
+ }
2614
2648
  const allFrames = [p, ...p.frames().filter(f => f !== p.mainFrame())];
2615
2649
  let activeFrame = p;
2616
2650
  for (const frame of allFrames) {
@@ -2620,6 +2654,42 @@ Sessions: session="a"/"b"/"c" for parallel browsers. proxy="host:port:user:pass"
2620
2654
  break;
2621
2655
  }
2622
2656
  }
2657
+ const hasEmailField = await activeFrame.locator('input[type="email"]:visible, input[name*="email" i]:visible, input[autocomplete="email"]:visible').count() > 0;
2658
+ const hasPasswordField = await activeFrame.locator('input[type="password"]:visible').count() > 0;
2659
+ if (!hasEmailField && !hasPasswordField) {
2660
+ const ctaSelectors = [
2661
+ 'button:has-text("Sign Up With Email")', 'button:has-text("sign up with email")',
2662
+ 'a:has-text("Sign Up With Email")', 'a:has-text("sign up with email")',
2663
+ 'button:has-text("Sign up with email")',
2664
+ 'button:has-text("Create Account")', 'button:has-text("create account")',
2665
+ 'button:has-text("Sign Up")', 'button:has-text("sign up")',
2666
+ 'button:has-text("Register")', 'button:has-text("register")',
2667
+ 'button:has-text("Get Started")', 'button:has-text("get started")',
2668
+ 'button:has-text("Continue with Email")', 'button:has-text("continue with email")',
2669
+ 'button:has-text("Use Email")', 'button:has-text("use email")',
2670
+ 'a:has-text("Sign Up")', 'a:has-text("Register")',
2671
+ '[data-testid*="signup"]', '[data-testid*="email"]',
2672
+ ];
2673
+ for (const sel of ctaSelectors) {
2674
+ try {
2675
+ const btn = activeFrame.locator(sel).first();
2676
+ if (await btn.count() > 0 && await btn.isVisible()) {
2677
+ await btn.click({ timeout: 3000 });
2678
+ results.push(` ✓ Clicked CTA: ${sel}`);
2679
+ await p.waitForTimeout(1500);
2680
+ break;
2681
+ }
2682
+ }
2683
+ catch { }
2684
+ }
2685
+ for (const frame of [p, ...p.frames().filter(f => f !== p.mainFrame())]) {
2686
+ const inputs = await frame.locator('input:visible, select:visible, textarea:visible').count();
2687
+ if (inputs > 0) {
2688
+ activeFrame = frame;
2689
+ break;
2690
+ }
2691
+ }
2692
+ }
2623
2693
  results.push(`Active frame: ${activeFrame === p ? 'main page' : 'iframe'} (${await activeFrame.locator('input:visible, select:visible, textarea:visible').count()} fields)`);
2624
2694
  const fillField = async (selectors, val, label) => {
2625
2695
  for (const sel of selectors) {
@@ -2632,11 +2702,11 @@ Sessions: session="a"/"b"/"c" for parallel browsers. proxy="host:port:user:pass"
2632
2702
  return true;
2633
2703
  }
2634
2704
  try {
2635
- await loc.fill(val, { timeout: 3000 });
2705
+ await loc.fill(val, { timeout: 1500 });
2636
2706
  }
2637
2707
  catch {
2638
- await loc.click({ timeout: 3000 });
2639
- await loc.pressSequentially(val, { delay: 30, timeout: 10000 });
2708
+ await loc.click({ timeout: 1500 });
2709
+ await loc.pressSequentially(val, { delay: 30, timeout: 5000 });
2640
2710
  }
2641
2711
  results.push(` ✓ ${label}: filled`);
2642
2712
  return true;
@@ -2656,7 +2726,7 @@ Sessions: session="a"/"b"/"c" for parallel browsers. proxy="host:port:user:pass"
2656
2726
  results.push(` ✓ ${label}: already checked`);
2657
2727
  return true;
2658
2728
  }
2659
- await loc.click({ timeout: 3000 });
2729
+ await loc.click({ timeout: 1500 });
2660
2730
  results.push(` ✓ ${label}: clicked`);
2661
2731
  return true;
2662
2732
  }
@@ -2720,102 +2790,73 @@ Sessions: session="a"/"b"/"c" for parallel browsers. proxy="host:port:user:pass"
2720
2790
  results.push('--- Filling fields ---');
2721
2791
  if (data.email) {
2722
2792
  await fillField([
2723
- 'input[type="email"]',
2724
- 'input[name*="email" i]', 'input[name*="Email"]',
2725
- 'input[name*="username" i]', 'input[name*="MemberName"]',
2726
- 'input[id*="email" i]', 'input[id*="username" i]',
2727
- 'input[placeholder*="email" i]', 'input[placeholder*="Email"]',
2728
- 'input[autocomplete="email"]', 'input[autocomplete="username"]',
2729
- 'input[name="loginfmt"]',
2793
+ 'input[type="email"]', 'input[name*="email" i]', 'input[id*="email" i]',
2794
+ 'input[autocomplete="email"]', 'input[placeholder*="email" i]',
2730
2795
  ], data.email, 'Email');
2731
2796
  }
2732
2797
  if (data.password) {
2733
2798
  await fillField([
2734
- 'input[type="password"]',
2735
- 'input[name*="password" i]', 'input[name*="Password"]', 'input[name*="Passwd"]',
2736
- 'input[id*="password" i]', 'input[name*="pass" i]',
2737
- 'input[autocomplete="new-password"]', 'input[autocomplete="current-password"]',
2799
+ 'input[type="password"]', 'input[name*="password" i]', 'input[id*="password" i]',
2800
+ 'input[autocomplete="new-password"]',
2738
2801
  ], data.password, 'Password');
2739
2802
  }
2740
2803
  if (data.firstName) {
2741
2804
  await fillField([
2742
- 'input[name*="firstName" i]', 'input[name*="FirstName"]', 'input[name*="fname" i]',
2743
- 'input[id*="firstName" i]', 'input[id*="fname" i]',
2744
- 'input[autocomplete="given-name"]',
2745
- 'input[placeholder*="first name" i]', 'input[placeholder*="First"]',
2746
- 'input[name="NameInput"]',
2805
+ 'input[name*="first" i]', 'input[id*="first" i]',
2806
+ 'input[autocomplete="given-name"]', 'input[placeholder*="first" i]',
2747
2807
  ], data.firstName, 'First name');
2748
2808
  }
2749
2809
  if (data.lastName) {
2750
2810
  await fillField([
2751
- 'input[name*="lastName" i]', 'input[name*="LastName"]', 'input[name*="lname" i]',
2752
- 'input[id*="lastName" i]', 'input[id*="lname" i]',
2753
- 'input[autocomplete="family-name"]',
2754
- 'input[placeholder*="last name" i]', 'input[placeholder*="Last"]',
2755
- 'input[name="LastName"]',
2811
+ 'input[name*="last" i]', 'input[id*="last" i]',
2812
+ 'input[autocomplete="family-name"]', 'input[placeholder*="last" i]',
2756
2813
  ], data.lastName, 'Last name');
2757
2814
  }
2758
2815
  if (data.firstName && !data.lastName) {
2759
2816
  await fillField([
2760
- 'input[name*="name" i]', 'input[id*="name" i]',
2761
- 'input[autocomplete="name"]',
2817
+ 'input[name*="name" i]', 'input[id*="name" i]', 'input[autocomplete="name"]',
2762
2818
  ], data.firstName + ' User', 'Full name');
2763
2819
  }
2764
2820
  if (data.phone) {
2765
2821
  await fillField([
2766
- 'input[type="tel"]', 'input[name*="phone" i]', 'input[name*="Phone"]',
2767
- 'input[id*="phone" i]', 'input[autocomplete="tel"]',
2768
- 'input[placeholder*="phone" i]',
2822
+ 'input[type="tel"]', 'input[name*="phone" i]', 'input[autocomplete="tel"]',
2769
2823
  ], data.phone, 'Phone');
2770
2824
  }
2771
- const birthYear = data.birthYear || '2003';
2772
- const birthMonth = data.birthMonth || 'January';
2773
- const birthDay = data.birthDay || '15';
2774
- await selectDropdown([
2775
- 'select[id*="BirthYear"]', 'select[name*="birthYear" i]', 'select[id*="year" i]',
2776
- 'select[name*="year" i]', 'select[aria-label*="year" i]', 'select[aria-label*="Birth"]',
2777
- ], birthYear, 'Birth year');
2778
- await selectDropdown([
2779
- 'select[id*="BirthMonth"]', 'select[name*="birthMonth" i]', 'select[id*="month" i]',
2780
- 'select[name*="month" i]', 'select[aria-label*="month" i]',
2781
- ], birthMonth, 'Birth month');
2782
- await selectDropdown([
2783
- 'select[id*="BirthDay"]', 'select[name*="birthDay" i]', 'select[id*="day" i]',
2784
- 'select[name*="day" i]', 'select[aria-label*="day" i]',
2785
- ], birthDay, 'Birth day');
2786
- await selectDropdown([
2787
- 'select[id*="Country"]', 'select[name*="country" i]',
2788
- 'select[aria-label*="country" i]', 'select[name*="Country"]',
2789
- ], data.country || 'United States', 'Country');
2790
- await fillField([
2791
- 'input[name*="username" i]', 'input[id*="username" i]',
2792
- 'input[placeholder*="username" i]', 'input[name*="Username"]',
2793
- ], data.username || (data.email ? data.email.split('@')[0] + Math.floor(Math.random() * 999) : 'user' + Math.floor(Math.random() * 9999)), 'Username');
2825
+ if (data.birthYear || data.birthMonth || data.birthDay) {
2826
+ const birthYear = data.birthYear || '2003';
2827
+ const birthMonth = data.birthMonth || 'January';
2828
+ const birthDay = data.birthDay || '15';
2829
+ await selectDropdown([
2830
+ 'select[id*="year" i]', 'select[name*="year" i]',
2831
+ ], birthYear, 'Birth year');
2832
+ await selectDropdown([
2833
+ 'select[id*="month" i]', 'select[name*="month" i]',
2834
+ ], birthMonth, 'Birth month');
2835
+ await selectDropdown([
2836
+ 'select[id*="day" i]', 'select[name*="day" i]',
2837
+ ], birthDay, 'Birth day');
2838
+ }
2839
+ if (data.country) {
2840
+ await selectDropdown([
2841
+ 'select[name*="country" i]', 'select[id*="country" i]',
2842
+ ], data.country, 'Country');
2843
+ }
2844
+ if (data.username) {
2845
+ await fillField([
2846
+ 'input[name*="username" i]', 'input[id*="username" i]',
2847
+ ], data.username, 'Username');
2848
+ }
2794
2849
  await clickField([
2795
2850
  'input[type="checkbox"][name*="agree" i]',
2796
- 'input[type="checkbox"][name*="tos" i]',
2797
2851
  'input[type="checkbox"][name*="terms" i]',
2798
2852
  'input[type="checkbox"][name*="consent" i]',
2799
- 'input[type="checkbox"][name*="privacy" i]',
2800
- 'input[type="checkbox"][name*="policy" i]',
2801
2853
  'input[type="checkbox"][id*="agree" i]',
2802
2854
  'input[type="checkbox"][id*="terms" i]',
2803
- 'input[type="checkbox"][id*="consent" i]',
2804
- 'input[type="checkbox"][id*="privacy" i]',
2805
- 'input[type="checkbox"][aria-label*="agree" i]',
2806
- 'input[type="checkbox"][aria-label*="terms" i]',
2807
- 'input[type="checkbox"][aria-label*="consent" i]',
2808
- 'input[type="checkbox"][aria-label*="accept" i]',
2809
2855
  'label:has-text("agree") input[type="checkbox"]',
2810
2856
  'label:has-text("terms") input[type="checkbox"]',
2811
2857
  'label:has-text("accept") input[type="checkbox"]',
2812
- 'label:has-text("consent") input[type="checkbox"]',
2813
- 'label:has-text("privacy") input[type="checkbox"]',
2814
2858
  'label:has-text("I agree") input[type="checkbox"]',
2815
- 'label:has-text("I accept") input[type="checkbox"]',
2816
2859
  '[role="checkbox"][aria-checked="false"]',
2817
- 'div:has-text("I agree"):not(:has(div:has-text("I agree")))',
2818
- 'span:has-text("I agree"):not(:has(span:has-text("I agree")))',
2819
2860
  ], 'Terms/Agreement checkbox');
2820
2861
  await p.waitForTimeout(500);
2821
2862
  const hasCaptcha = p.frames().some(f => {
@@ -2828,7 +2869,16 @@ Sessions: session="a"/"b"/"c" for parallel browsers. proxy="host:port:user:pass"
2828
2869
  results.push('');
2829
2870
  results.push('--- Verification step detected ---');
2830
2871
  results.push('Attempting to complete automatically...');
2831
- const solveResults = await autoSolveCaptcha(p);
2872
+ let solveResults;
2873
+ try {
2874
+ solveResults = await Promise.race([
2875
+ autoSolveCaptcha(p),
2876
+ new Promise((_, rej) => setTimeout(() => rej(new Error('auto-solve timed out (30s)')), 30000)),
2877
+ ]);
2878
+ }
2879
+ catch (e) {
2880
+ solveResults = [`Auto-solve: ${e.message}`];
2881
+ }
2832
2882
  solveResults.forEach(r => results.push(` ${r}`));
2833
2883
  const needsVision = solveResults.some(r => r.includes('VERIFICATION COMPLETION STEPS') || r.includes('REQUIRES_VISION'));
2834
2884
  const unconfirmed = solveResults.some(r => /unconfirmed|shows an error/i.test(r));
@@ -2844,19 +2894,27 @@ Sessions: session="a"/"b"/"c" for parallel browsers. proxy="host:port:user:pass"
2844
2894
  results.push('Verification confirmed. Continuing form submission...');
2845
2895
  }
2846
2896
  }
2847
- const clicked = await clickField([
2848
- 'button[type="submit"]',
2849
- 'input[type="submit"]',
2850
- 'button:has-text("Next")', 'button:has-text("next")',
2851
- 'button:has-text("Continue")', 'button:has-text("continue")',
2852
- 'button:has-text("Submit")', 'button:has-text("submit")',
2853
- 'button:has-text("Create")', 'button:has-text("create")',
2854
- 'button:has-text("Sign up")', 'button:has-text("sign up")',
2897
+ let clicked = await clickField([
2898
+ 'button[type="submit"]', 'input[type="submit"]',
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")',
2855
2902
  'button:has-text("Register")', 'button:has-text("register")',
2856
- 'button:has-text("Accept")', 'button:has-text("accept")',
2857
- '#iSignupAction', '#signup-button', '#submit-btn',
2858
- 'button.fui-Button[type="button"]:visible',
2903
+ 'button:has-text("Next")', 'button:has-text("Continue")',
2904
+ 'button:has-text("Submit")', 'button:has-text("Create")',
2905
+ '#signup-button', '#submit-btn',
2859
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
+ }
2860
2918
  await p.waitForTimeout(2000);
2861
2919
  results.push('');
2862
2920
  results.push(`--- Result ---`);
@@ -2900,6 +2958,27 @@ Sessions: session="a"/"b"/"c" for parallel browsers. proxy="host:port:user:pass"
2900
2958
  }
2901
2959
  const results = [];
2902
2960
  results.push('=== SIGNIN ASSIST ===');
2961
+ const cookieSelectors = [
2962
+ 'button:has-text("Accept All")', 'button:has-text("Accept all")', 'button:has-text("accept all")',
2963
+ 'button:has-text("Accept")', 'button:has-text("Accept Cookies")',
2964
+ 'button:has-text("I agree")', 'button:has-text("Got it")', 'button:has-text("OK")',
2965
+ 'button:has-text("Allow All")', 'button:has-text("Allow all")',
2966
+ '[id*="cookie"] button', '[class*="cookie"] button',
2967
+ '[id*="consent"] button', '[class*="consent"] button',
2968
+ '.cc-accept', '.cookie-accept', '#accept-cookies',
2969
+ ];
2970
+ for (const sel of cookieSelectors) {
2971
+ try {
2972
+ const btn = p.locator(sel).first();
2973
+ if (await btn.count() > 0 && await btn.isVisible()) {
2974
+ await btn.click({ timeout: 2000 });
2975
+ results.push(` ✓ Dismissed cookie banner: ${sel}`);
2976
+ await p.waitForTimeout(500);
2977
+ break;
2978
+ }
2979
+ }
2980
+ catch { }
2981
+ }
2903
2982
  const allFrames = [p, ...p.frames().filter(f => f !== p.mainFrame())];
2904
2983
  let activeFrame = p;
2905
2984
  for (const frame of allFrames) {
@@ -2921,11 +3000,11 @@ Sessions: session="a"/"b"/"c" for parallel browsers. proxy="host:port:user:pass"
2921
3000
  return true;
2922
3001
  }
2923
3002
  try {
2924
- await loc.fill(val, { timeout: 3000 });
3003
+ await loc.fill(val, { timeout: 1500 });
2925
3004
  }
2926
3005
  catch {
2927
- await loc.click({ timeout: 3000 });
2928
- await loc.pressSequentially(val, { delay: 30, timeout: 10000 });
3006
+ await loc.click({ timeout: 1500 });
3007
+ await loc.pressSequentially(val, { delay: 30, timeout: 5000 });
2929
3008
  }
2930
3009
  results.push(` ✓ ${label}: filled`);
2931
3010
  return true;
@@ -2945,7 +3024,7 @@ Sessions: session="a"/"b"/"c" for parallel browsers. proxy="host:port:user:pass"
2945
3024
  results.push(` ✓ ${label}: already checked`);
2946
3025
  return true;
2947
3026
  }
2948
- await loc.click({ timeout: 3000 });
3027
+ await loc.click({ timeout: 1500 });
2949
3028
  results.push(` ✓ ${label}: clicked`);
2950
3029
  return true;
2951
3030
  }
@@ -2994,7 +3073,16 @@ Sessions: session="a"/"b"/"c" for parallel browsers. proxy="host:port:user:pass"
2994
3073
  results.push('');
2995
3074
  results.push('--- Verification step detected ---');
2996
3075
  results.push('Attempting to complete automatically...');
2997
- const solveResults = await autoSolveCaptcha(p);
3076
+ let solveResults;
3077
+ try {
3078
+ solveResults = await Promise.race([
3079
+ autoSolveCaptcha(p),
3080
+ new Promise((_, rej) => setTimeout(() => rej(new Error('auto-solve timed out (30s)')), 30000)),
3081
+ ]);
3082
+ }
3083
+ catch (e) {
3084
+ solveResults = [`Auto-solve: ${e.message}`];
3085
+ }
2998
3086
  solveResults.forEach(r => results.push(` ${r}`));
2999
3087
  const needsVision = solveResults.some(r => r.includes('VERIFICATION COMPLETION STEPS') || r.includes('REQUIRES_VISION'));
3000
3088
  const unconfirmed = solveResults.some(r => /unconfirmed|shows an error/i.test(r));