first commit

This commit is contained in:
adrapier03
2026-06-29 22:21:33 +07:00
commit 7061970eff
6 changed files with 985 additions and 0 deletions
+186
View File
@@ -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-<version>/`
- Windows: `C:\Users\<username>\.cloakbrowser\chromium-<version>\`
## 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
```
[email protected]|password123
[email protected]|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-<version>/` |
| 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
+3
View File
@@ -0,0 +1,3 @@
email|password
[email protected]|password123
[email protected]|password456
+412
View File
@@ -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);
});
+13
View File
@@ -0,0 +1,13 @@
{
"nineRouter": {
"url": "http://localhost:20128",
"password": "123456"
},
"accountsFile": "accounts.txt",
"settings": {
"headless": false,
"slowMo": 300,
"timeout": 60000,
"delayBetweenAccounts": 5000
}
}
+353
View File
@@ -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"
}
}
}
}
+18
View File
@@ -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"
}
}