first commit
This commit is contained in:
@@ -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
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
email|password
|
||||||
|
[email protected]|password123
|
||||||
|
[email protected]|password456
|
||||||
@@ -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
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"nineRouter": {
|
||||||
|
"url": "http://localhost:20128",
|
||||||
|
"password": "123456"
|
||||||
|
},
|
||||||
|
"accountsFile": "accounts.txt",
|
||||||
|
"settings": {
|
||||||
|
"headless": false,
|
||||||
|
"slowMo": 300,
|
||||||
|
"timeout": 60000,
|
||||||
|
"delayBetweenAccounts": 5000
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+353
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user