From 7061970effb280b96fb9f99fb6e2a7fcbcba5d93 Mon Sep 17 00:00:00 2001 From: adrapier03 Date: Mon, 29 Jun 2026 22:21:33 +0700 Subject: [PATCH] first commit --- README.md | 186 +++++++++++++++++++ accounts.txt.example | 3 + bot.js | 412 +++++++++++++++++++++++++++++++++++++++++++ config.json | 13 ++ package-lock.json | 353 ++++++++++++++++++++++++++++++++++++ package.json | 18 ++ 6 files changed, 985 insertions(+) create mode 100644 README.md create mode 100644 accounts.txt.example create mode 100644 bot.js create mode 100644 config.json create mode 100644 package-lock.json create mode 100644 package.json diff --git a/README.md b/README.md new file mode 100644 index 0000000..04e2d14 --- /dev/null +++ b/README.md @@ -0,0 +1,186 @@ +# 9router-Antigravity Auto Add Bot + +Bot otomatis untuk menambahkan akun GSuite ke **9router-Antigravity** via Google OAuth. +Bot Harus Jalan di Host yang sama dengan 9router + +## Stack + +- **CloakBrowser** — Stealth Chromium (anti bot detection, bypass Google CAPTCHA) +- **Playwright API** compatible +- **Node.js** ESM + +## Install + +```bash +cd gsuite-auto +npm install +``` + +Pastikan CloakBrowser binary sudah ter-download. Kalau download otomatis gagal (network/firewall), manual: + +1. Download dari https://github.com/CloakHQ/cloakbrowser/releases +2. Extract ke `~/.cloakbrowser/chromium-/` + - Windows: `C:\Users\\.cloakbrowser\chromium-\` + +## Config + +### `config.json` + +```json +{ + "nineRouter": { + "url": "http://localhost:20128", + "password": "123456" + }, + "accountsFile": "accounts.txt", + "settings": { + "headless": true, + "slowMo": 300, + "timeout": 60000, + "delayBetweenAccounts": 5000 + } +} +``` + +| Key | Description | +|-----|-------------| +| `nineRouter.url` | URL 9router instance | +| `nineRouter.password` | Login password 9router | +| `accountsFile` | File list akun (default: `accounts.txt`) | +| `headless` | `true` = tanpa browser window, `false` = visible | +| `slowMo` | Delay antar action (ms) | +| `timeout` | Max tunggu per step (ms) | +| `delayBetweenAccounts` | Delay antar akun (ms) | + +### `accounts.txt` + +Format: `email|password` per baris + +``` +user1@astrora.me|password123 +user2@astrora.me|password456 +# comment bisa pakai # +baris kosong di-skip +``` + +Support multiple file untuk batch berbeda: +``` +accounts-batch1.txt +accounts-batch2.txt +``` + +## Usage + +```bash +# Normal run (headless dari config.json) +node bot.js + +# Force headless via CLI +node bot.js --headless + +# Force visible browser +node bot.js --no-headless + +# Dry run (test tanpa eksekusi) +node bot.js --dry-run + +# Pakai file accounts lain +node bot.js --file accounts-batch2.txt +``` + +## Flow + +``` +Untuk SETIAP akun (fresh browser context): +│ +├─ 1. Buka browser baru (clean session, ga ada cookie Google lama) +├─ 2. Login 9router +├─ 3. Navigate → Providers → Antigravity +├─ 4. Click "Add" +├─ 5. Click "I Understand, Continue" → buka popup Google OAuth +├─ 6. Force hl=en di URL Google (paksa English UI) +├─ 7. Input email → click Next +│ (multi-bahasa: EN/ID/JA/KO/ZH/FR/ES/DE/RU) +├─ 8. Input password → Enter +│ (multi-bahasa: EN/ID/JA/KO/ZH/FR/ES/DE/RU) +├─ 9. Verify "I understand" (Workspace Terms) +│ → click kalau ada, skip kalau ga ada +├─ 10. Wait consent page → click Sign in/Continue/Allow +│ (multi-bahasa: EN/ID/JA/KO/ZH/FR/ES/DE/RU) +├─ 11. Verify email muncul di Antigravity page +│ (max 3x refresh) +├─ 12. Close browser context +└─ 13. Next akun (fresh context lagi) +``` + +## Multi-Language Support + +Google kadang render UI sesuai bahasa akun, bukan bahasa browser. Bot handle semua variasi: + +| Element | Bahasa | +|---------|--------| +| Email input | Email or phone,Alamat email atau nomor telepon,メールアドレスまたは電話番号,이메일 또는 전화번호,电子邮件或电话号码,... | +| Next button | Next,Berikutnya,Lanjut,次へ,다음,下一步,Suivant,Siguiente,Weiter,Далее | +| Password | Enter your password,Masukkan kata sandi Anda,パスワードを入力,비밀번호 입력,输入密码,... | +| Terms | I understand,Saya mengerti,理解しました,이해했습니다,我明白了,... | +| Consent | Sign in,Masuk,ログイン,登录,Se connecter,Iniciar sesión,Anmelden,Войти,... | + +Log output nunjukin match yang mana: +``` +[>] Email → "Alamat email atau nomor telepon" +[>] Next → "Berikutnya" +[>] OAuth Consent → "Masuk" +``` + +## Output + +| File | Description | +|------|-------------| +| `accounts.txt` | Auto-update — akun yang berhasil dihapus, sisa yang belum | +| `log-YYYY-MM-DD.json` | Hasil detail per akun (success/fail/error) | +| `screenshots/` | Screenshot otomatis kalau error | + +## File Structure + +``` +9router-autobot/ +├── bot.js # Main bot script +├── config.json # Config (URL, password, settings) +├── accounts.txt # List akun (email|password) +├── package.json +├── README.md +├── recorded-actions.js # Raw Playwright codegen recording (reference) +├── record.js # Playwright codegen launcher (untuk re-record) +└── screenshots/ # Error screenshots (auto-generated) +``` + +## Fitur + +- **Fresh context per akun** — session Google ga nyangkut antar akun +- **CloakBrowser stealth** — bypass bot detection Google +- **Multi-language** — handle UI Google dalam bahasa apapun +- **Auto-remove** — akun yang berhasil dihapus dari `accounts.txt` +- **Auto-retry verify** — refresh max 3x kalau email belum muncul +- **Error screenshot** — auto-capture kalau gagal +- **Log harian** — `log-YYYY-MM-DD.json` per run + +## Troubleshooting + +| Masalah | Solusi | +|---------|--------| +| `CLOAKBROWSER_BINARY_PATH` error | Download manual binary ke `~/.cloakbrowser/` | +| Download binary gagal (network) | Download via browser, extract manual ke `~/.cloakbrowser/chromium-/` | +| Google CAPTCHA di headless | Pastikan pakai CloakBrowser (bukan Playwright biasa) | +| UI Google bahasa lokal | Normal — bot handle multi-bahasa otomatis | +| Email ga verify di page | Bot auto-refresh 3x. Kalau tetap gagal, cek manual | +| `Failed to assign a backend` | Akun sudah ada di 9router atau quota habis | +| Akun 1 berhasil, akun 2 gagal | Normal kalau Terms baru pertama kali — jalankan lagi | +| Popup consent ga ke-click | Cek log, kemungkinan bahasa belum ter-cover | + +## Tips + +- **Test dulu** dengan `node bot.js --dry-run` sebelum run beneran +- **1 akun dulu** — pastikan flow-nya benar sebelum batch +- **Crash-safe** — bot update `accounts.txt` per akun, jadi restart lanjut dari yang belum selesai +- **Headless** lebih cepat tapi kadang beda behavior — test visible dulu +- **Delay** — kalau sering gagal, naikkan `delayBetweenAccounts` di config diff --git a/accounts.txt.example b/accounts.txt.example new file mode 100644 index 0000000..67acafc --- /dev/null +++ b/accounts.txt.example @@ -0,0 +1,3 @@ +email|password +user1@astrora.me|password123 +user2@astrora.me|password456 \ No newline at end of file diff --git a/bot.js b/bot.js new file mode 100644 index 0000000..f686f43 --- /dev/null +++ b/bot.js @@ -0,0 +1,412 @@ +/** + * 9ROUTER-ANTIGRAVITY AUTO ADD BOT + * + * Uses CloakBrowser (stealth Chromium) to bypass bot detection + * Accounts loaded from accounts.txt (format: email|password per line) + * + * Usage: node bot.js [--headless] [--dry-run] [--file accounts2.txt] + */ + +import { launch, getDefaultStealthArgs, ensureBinary } from 'cloakbrowser'; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const CONFIG_PATH = path.join(__dirname, 'config.json'); +const args = process.argv.slice(2); +const HEADLESS = args.includes('--headless') || undefined; +const DRY_RUN = args.includes('--dry-run'); + +const fileArgIndex = args.indexOf('--file'); +const CUSTOM_FILE = fileArgIndex !== -1 ? args[fileArgIndex + 1] : null; + +function loadConfig() { + if (!fs.existsSync(CONFIG_PATH)) { + console.error(`[X] Config not found: ${CONFIG_PATH}`); + process.exit(1); + } + return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8')); +} + +function loadAccounts(config) { + const fileName = CUSTOM_FILE || config.accountsFile || 'accounts.txt'; + const filePath = path.resolve(__dirname, fileName); + + if (!fs.existsSync(filePath)) { + console.error(`[X] Accounts file not found: ${filePath}`); + process.exit(1); + } + + const lines = fs.readFileSync(filePath, 'utf-8') + .split('\n') + .map(l => l.trim()) + .filter(l => l && !l.startsWith('#')); + + const accounts = []; + for (const line of lines) { + const parts = line.split('|'); + if (parts.length < 2) { + console.warn(`[!] Skipping invalid line: ${line}`); + continue; + } + accounts.push({ email: parts[0].trim(), password: parts[1].trim() }); + } + + return accounts; +} + +function sleep(ms) { + return new Promise(r => setTimeout(r, ms)); +} + +async function screenshot(page, name) { + const dir = path.join(__dirname, 'screenshots'); + if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); + const file = path.join(dir, `${name}-${Date.now()}.png`); + await page.screenshot({ path: file, fullPage: true }); + console.log(` [IMG] Screenshot: ${file}`); + return file; +} + +// Try multiple selectors, click first one that's visible +async function clickAny(page, selectors, label, timeout = 5000) { + for (const sel of selectors) { + const el = page.getByRole('button', { name: sel }); + const visible = await el.isVisible({ timeout }).catch(() => false); + if (visible) { + console.log(` [>] ${label} → "${sel}"`); + await el.click(); + return true; + } + } + return false; +} + +// Try multiple textbox labels, fill first one that's visible +async function fillAny(page, labels, value, label, timeout = 10000) { + for (const lbl of labels) { + const el = page.getByRole('textbox', { name: lbl }); + const visible = await el.isVisible({ timeout: Math.min(timeout, 3000) }).catch(() => false); + if (visible) { + console.log(` [>] ${label} → "${lbl}"`); + await el.click(); + await el.fill(value); + return true; + } + } + return false; +} + +async function addAccount(browser, account, index, total, config) { + const baseUrl = config.nineRouter.url; + console.log(`\n[ADD] [${index}/${total}] ${account.email}`); + + if (DRY_RUN) { + console.log(' [~] DRY RUN - skipping'); + return { success: true, email: account.email, action: 'dry-run' }; + } + + // Fresh context per akun — clean session, forced English locale + const context = await browser.newContext({ + viewport: { width: 1280, height: 800 }, + locale: 'en-US', + extraHTTPHeaders: { 'Accept-Language': 'en-US,en;q=0.9' } + }); + const page = await context.newPage(); + page.setDefaultTimeout(config.settings?.timeout || 60000); + + try { + // Login 9router fresh setiap akun + console.log(' [>] Login 9router...'); + await page.goto(`${baseUrl}/login`); + await page.waitForLoadState('networkidle'); + await page.getByRole('textbox', { name: 'Enter password' }).click(); + await page.getByRole('textbox', { name: 'Enter password' }).fill(config.nineRouter.password); + await page.getByRole('textbox', { name: 'Enter password' }).press('Enter'); + await page.waitForLoadState('networkidle'); + + // Navigate ke Antigravity + console.log(' [>] Navigate to Antigravity...'); + await page.getByRole('link', { name: 'dns Providers' }).click(); + await page.waitForLoadState('networkidle'); + await page.getByRole('link', { name: /Antigravity/ }).click(); + await page.waitForLoadState('networkidle'); + + // Click Add button + await page.getByRole('button', { name: 'add Add', exact: true }).click(); + await sleep(1000); + + // Click "I Understand, Continue" - triggers Google OAuth popup + const popupPromise = page.waitForEvent('popup', { timeout: 15000 }); + await clickAny(page, [ + 'I Understand, Continue', + 'I understand, continue', + /understand/i, + ], 'I Understand'); + + const popup = await popupPromise; + popup.setDefaultTimeout(60000); + await popup.waitForLoadState('domcontentloaded'); + + // Force English UI on Google OAuth page + const popupUrl = new URL(popup.url()); + popupUrl.searchParams.set('hl', 'en'); + await popup.goto(popupUrl.toString()); + await popup.waitForLoadState('networkidle'); + await sleep(2000); + + // Google Sign-in - Email + console.log(' [>] Email...'); + const emailFilled = await fillAny(popup, [ + 'Email or phone', + 'Enter your email', + 'Email', + 'Alamat email atau nomor telepon', + 'E-mail atau telepon', + 'メールアドレスまたは電話番号', + '이메일 또는 전화번호', + '电子邮件或电话号码', + 'E-mail ou téléphone', + 'Correo electrónico o teléfono', + 'E-Mail oder Telefon', + 'Электронная почта или телефон', + ], account.email, 'Email'); + if (!emailFilled) throw new Error('Could not find email input field'); + + await clickAny(popup, [ + 'Next', + 'Berikutnya', + 'Lanjut', + '次へ', + '다음', + '下一步', + 'Suivant', + 'Siguiente', + 'Weiter', + 'Далее', + ], 'Next', 5000); + await sleep(3000); + + // Google Sign-in - Password + console.log(' [>] Password...'); + const pwFilled = await fillAny(popup, [ + 'Enter your password', + 'Enter password', + 'Password', + 'Masukkan kata sandi Anda', + 'Kata sandi', + 'パスワードを入力', + '비밀번호 입력', + '输入密码', + 'Entrez votre mot de passe', + 'Introduce tu contraseña', + 'Geben Sie Ihr Passwort ein', + 'Введите пароль', + ], account.password, 'Password'); + if (!pwFilled) throw new Error('Could not find password input field'); + await popup.keyboard.press('Enter'); + await sleep(3000); + + // Verify: Workspace Terms - click if exists, skip if not + const hasTerms = await clickAny(popup, [ + 'I understand', + 'Saya mengerti', + '理解しました', + '이해했습니다', + '我明白了', + 'Je comprends', + 'Entiendo', + 'Ich verstehe', + 'Я понимаю', + ], 'Workspace Terms', 3000); + if (hasTerms) { + await popup.waitForLoadState('networkidle'); + await sleep(3000); + } else { + console.log(' [~] No Terms page, skipping'); + } + + // Handle OAuth consent in popup (MUST click before popup closes) + console.log(' [>] Waiting for OAuth consent page...'); + try { + await popup.waitForURL(/oauth\/consent|nativeapp/, { timeout: 30000 }); + await popup.waitForLoadState('networkidle'); + await sleep(2000); + + const consentClicked = await clickAny(popup, [ + 'Sign in', + 'Continue', + 'Allow', + 'Login', + 'Masuk', + 'Lanjutkan', + 'Izinkan', + 'ログイン', + '続行', + '許可', + '로그인', + '계속', + '허용', + '登录', + '继续', + '允许', + 'Se connecter', + 'Continuer', + 'Autoriser', + 'Iniciar sesión', + 'Continuar', + 'Permitir', + 'Anmelden', + 'Fortfahren', + 'Zulassen', + 'Войти', + 'Продолжить', + 'Разрешить', + ], 'OAuth Consent', 15000); + if (!consentClicked) throw new Error('Could not find consent button'); + + await sleep(5000); + } catch (err) { + console.log(` [!] Consent step: ${err.message}`); + } + + // Popup auto-closes after consent + await sleep(2000); + await page.waitForLoadState('networkidle'); + + // Verify: refresh Antigravity page and check if email appears + console.log(' [>] Verifying...'); + await page.goto(`${baseUrl}/dashboard/providers/antigravity`); + await page.waitForLoadState('networkidle'); + await sleep(3000); + + let verified = false; + for (let attempt = 1; attempt <= 3; attempt++) { + const pageText = await page.textContent('body'); + if (pageText.includes(account.email)) { + console.log(` [OK] Verified: ${account.email} found on page`); + verified = true; + break; + } + if (attempt < 3) { + console.log(` [~] Not found yet, refresh ${attempt}/3...`); + await page.reload(); + await page.waitForLoadState('networkidle'); + await sleep(3000); + } + } + if (!verified) { + console.log(` [!] Warning: ${account.email} NOT found after 3 refreshes`); + throw new Error('Account not verified on 9router page after OAuth'); + } + + // Check for backend error + const pageContent = await page.content(); + if (pageContent.includes('Failed to assign a backend')) { + throw new Error('Failed to assign a backend - account may already exist or quota exceeded'); + } + + console.log(` [OK] Done: ${account.email}`); + return { success: true, email: account.email }; + + } catch (err) { + console.error(` [X] Failed: ${err.message}`); + try { await screenshot(page, `error-${account.email.replace(/[@.]/g, '_')}`); } catch {} + return { success: false, email: account.email, error: err.message }; + } finally { + await context.close(); + } +} + +async function main() { + console.log('========================================'); + console.log(' 9ROUTER-ANTIGRAVITY AUTO ADD BOT'); + console.log(' [CloakBrowser Stealth Mode]'); + console.log('========================================'); + + // Ensure CloakBrowser binary is ready + console.log('[*] Checking CloakBrowser binary...'); + await ensureBinary(); + + const config = loadConfig(); + const accounts = loadAccounts(config); + + if (accounts.length === 0) { + console.error('[X] No accounts found. Format: email|password (one per line)'); + process.exit(1); + } + + const isHeadless = HEADLESS ?? config.settings?.headless ?? false; + + console.log(`[*] Accounts: ${accounts.length}`); + console.log(`[*] File: ${CUSTOM_FILE || config.accountsFile || 'accounts.txt'}`); + console.log(`[*] Headless: ${isHeadless}`); + console.log(`[*] Dry Run: ${DRY_RUN}`); + console.log(`[*] Target: ${config.nineRouter.url}`); + + if (DRY_RUN) console.log('\n[!] DRY RUN MODE\n'); + + // Launch CloakBrowser with stealth args + const browser = await launch({ + headless: isHeadless, + slowMo: config.settings?.slowMo || 0, + args: getDefaultStealthArgs() + }); + + const results = []; + + try { + for (let i = 0; i < accounts.length; i++) { + const result = await addAccount(browser, accounts[i], i + 1, accounts.length, config); + results.push(result); + + if (i < accounts.length - 1) { + const delay = config.settings?.delayBetweenAccounts || 5000; + console.log(` [~] Waiting ${(delay / 1000).toFixed(1)}s...`); + await sleep(delay); + } + } + + } catch (err) { + console.error(`\n[FATAL] ${err.message}`); + } finally { + const success = results.filter(r => r.success).length; + const failed = results.filter(r => !r.success).length; + + console.log('\n========================================'); + console.log(' RESULTS'); + console.log('========================================'); + console.log(`[OK] Success: ${success}/${results.length}`); + if (failed > 0) { + console.log(`[X] Failed: ${failed}/${results.length}`); + console.log('\nFailed:'); + results.filter(r => !r.success).forEach(r => console.log(` - ${r.email}: ${r.error}`)); + } + + // Remove successfully added accounts from file + if (success > 0 && !DRY_RUN) { + const successEmails = new Set(results.filter(r => r.success).map(r => r.email)); + const remaining = accounts.filter(a => !successEmails.has(a.email)); + const remainingLines = remaining.map(a => `${a.email}|${a.password}`); + const accountsFile = path.resolve(__dirname, CUSTOM_FILE || config.accountsFile || 'accounts.txt'); + fs.writeFileSync(accountsFile, remainingLines.join('\n') + (remainingLines.length ? '\n' : '')); + console.log(`\n[FILE] Updated ${accountsFile} - removed ${success} done, ${remaining.length} remaining`); + } + + // Save log + const logFile = path.join(__dirname, `log-${new Date().toISOString().slice(0, 10)}.json`); + fs.writeFileSync(logFile, JSON.stringify({ timestamp: new Date().toISOString(), results }, null, 2)); + console.log(`[LOG] ${logFile}`); + + await browser.close(); + console.log('\n[DONE]'); + } +} + +main().catch(err => { + console.error('[FATAL]', err); + process.exit(1); +}); diff --git a/config.json b/config.json new file mode 100644 index 0000000..f85760c --- /dev/null +++ b/config.json @@ -0,0 +1,13 @@ +{ + "nineRouter": { + "url": "http://localhost:20128", + "password": "123456" + }, + "accountsFile": "accounts.txt", + "settings": { + "headless": false, + "slowMo": 300, + "timeout": 60000, + "delayBetweenAccounts": 5000 + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..06f223d --- /dev/null +++ b/package-lock.json @@ -0,0 +1,353 @@ +{ + "name": "9router-autobot", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "9router-autobot", + "version": "1.0.0", + "dependencies": { + "cloakbrowser": "^0.4.5", + "playwright": "^1.50.0", + "rebrowser-patches": "^1.0.19" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cloakbrowser": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/cloakbrowser/-/cloakbrowser-0.4.5.tgz", + "integrity": "sha512-FLEOoznA/d4SbUT1zi8BiMqH+xt/eCoCWeLHnEC7Wn1WBGR31QHSh93PSfS/WcovGaxQxxOQPKtF8+1IkdEp1g==", + "license": "MIT", + "dependencies": { + "tar": "^7.0.0" + }, + "bin": { + "cloakbrowser": "dist/cli.js" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "mmdb-lib": ">=2.0.0", + "playwright-core": ">=1.53.0", + "puppeteer-core": ">=21.0.0", + "socks-proxy-agent": ">=10.0.0" + }, + "peerDependenciesMeta": { + "mmdb-lib": { + "optional": true + }, + "playwright-core": { + "optional": true + }, + "puppeteer-core": { + "optional": true + }, + "socks-proxy-agent": { + "optional": true + } + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/playwright": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz", + "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==", + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.61.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz", + "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", + "license": "Apache-2.0", + "peer": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/rebrowser-patches": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/rebrowser-patches/-/rebrowser-patches-1.0.19.tgz", + "integrity": "sha512-0qvN6rDNmOnKe0UTTnX2Aub0UwBM34HhLUDGnsAG/uIV5KpcO47SmA81RmCbSITKxHjD74yj9pdVP3JZG7ublg==", + "license": "MIT", + "dependencies": { + "yargs": "^17.7.2" + }, + "bin": { + "rebrowser-patches": "scripts/patcher.js" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "7.5.19", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.19.tgz", + "integrity": "sha512-4LeEWl96twnS2Q7Bz4MGqgazLqO+hJN63GZxXoIqh1T3VweYD997gbU1ItNsQafqqXTXd5WFyFdReLtwvRBNiw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..9cd769a --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "9router-autobot", + "version": "1.0.0", + "description": "Auto add accounts to 9router-Antigravity using GSuite", + "type": "module", + "scripts": { + "record": "node record.js", + "start": "node bot.js", + "start:headless": "node bot.js --headless", + "dry-run": "node bot.js --dry-run", + "codegen": "npx playwright codegen --target javascript" + }, + "dependencies": { + "cloakbrowser": "^0.4.5", + "playwright": "^1.50.0", + "rebrowser-patches": "^1.0.19" + } +}