domain setup
This commit is contained in:
@@ -18,7 +18,8 @@ Edit `config.json`:
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"tempmail": {
|
"tempmail": {
|
||||||
"apiUrl": "https://tempmail.adrnode.com/api"
|
"apiUrl": "https://tempmail.adrnode.com/api",
|
||||||
|
"domain": "shitoors.fun"
|
||||||
},
|
},
|
||||||
"captcha": {
|
"captcha": {
|
||||||
"provider": "2captcha",
|
"provider": "2captcha",
|
||||||
@@ -44,6 +45,7 @@ Yang **wajib** diisi:
|
|||||||
- `xiaomi.referralLink` — URL referral, format `?ref=XXXXXX` (sama dengan inviteCode)
|
- `xiaomi.referralLink` — URL referral, format `?ref=XXXXXX` (sama dengan inviteCode)
|
||||||
|
|
||||||
Yang opsional:
|
Yang opsional:
|
||||||
|
- `tempmail.domain` — domain kustom untuk pendaftaran email (misal: `"shitoors.fun"`)
|
||||||
- `xiaomi.password` — password untuk semua akun baru (default OK)
|
- `xiaomi.password` — password untuk semua akun baru (default OK)
|
||||||
- `browser.headless` — `true` tanpa UI, `false` (default) buat lihat browser
|
- `browser.headless` — `true` tanpa UI, `false` (default) buat lihat browser
|
||||||
|
|
||||||
@@ -61,6 +63,7 @@ node chain-loop.js --count 5
|
|||||||
| `--count N` | 1 | jumlah akun yang dibuat |
|
| `--count N` | 1 | jumlah akun yang dibuat |
|
||||||
| `--seed XXXXXX` | `config.xiaomi.inviteCode` | invite code untuk akun pertama |
|
| `--seed XXXXXX` | `config.xiaomi.inviteCode` | invite code untuk akun pertama |
|
||||||
| `--output file.txt` | `chain-result.txt` | file output |
|
| `--output file.txt` | `chain-result.txt` | file output |
|
||||||
|
| `--proxy [arg]` | - | Menentukan proxy. Jika diisi string url (`http://user:pass@host:port`), akan memakai proxy tersebut. Jika bendera `--proxy` ditulis tanpa nilai (atau `--proxy proxy.txt`), program akan otomatis memuat list proxy dari file `proxy.txt` di workspace dan merotasinya per iterasi. |
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 5 akun, seed dari config
|
# 5 akun, seed dari config
|
||||||
@@ -84,6 +87,7 @@ node regis-loop.js --count 5
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `--count N` | 1 | jumlah akun yang dibuat |
|
| `--count N` | 1 | jumlah akun yang dibuat |
|
||||||
| `--output file.txt` | `regis-result.txt` | file output (format: email:password:refCode:apiKey) |
|
| `--output file.txt` | `regis-result.txt` | file output (format: email:password:refCode:apiKey) |
|
||||||
|
| `--proxy [arg]` | - | Sama seperti di `chain-loop.js`. Berguna untuk merotasi proxy per iterasi agar setiap akun terdaftar menggunakan IP berbeda. |
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Membuat 5 akun baru secara bergantian tanpa referral link / redeem code sama sekali
|
# Membuat 5 akun baru secara bergantian tanpa referral link / redeem code sama sekali
|
||||||
@@ -94,18 +98,18 @@ node regis-loop.js --count 5
|
|||||||
## Per-akun flow
|
## Per-akun flow
|
||||||
|
|
||||||
```
|
```
|
||||||
[1] Bikin temp email
|
[1] Bikin temp email (menggunakan domain kustom jika diatur)
|
||||||
[2] Launch browser dengan random fingerprint (UA, viewport, locale, GPU, dst)
|
[2] Launch browser dengan random fingerprint (dan proxy yang dirotasi jika diaktifkan)
|
||||||
[3] Buka referral link (?ref=<currentSeed>)
|
[3] Buka referral link (?ref=<currentSeed>)
|
||||||
[4] Sign up form (email + password + confirm + terms checkbox)
|
[4] Sign up form (email + password + confirm + terms checkbox)
|
||||||
[5] Solve captcha (reCAPTCHA via 2Captcha + image captcha)
|
[5] Solve captcha (reCAPTCHA via 2Captcha + image captcha)
|
||||||
[6] Verify email (kode 6 digit dari tempmail)
|
[6] Verify email (kode 6 digit dari tempmail)
|
||||||
[7] Klik Console → Balance → "Enter invite code" → isi 6-box → Redeem
|
[7] Pengecekan Balance & Terms:
|
||||||
→ cek restriction (kalau kena, STOP)
|
- **Regis Loop**: Klik Console → Balance → deteksi & setujui Terms modal → cek balance (baca saldo).
|
||||||
→ reload halaman, verify balance bertambah ~$2
|
- **Chain Loop**: Klik Console → Balance → deteksi & setujui Terms modal → "Enter invite code" → isi 6-box → Redeem → cek restriction (kalau kena, STOP) → reload halaman → verify balance bertambah ~$2.
|
||||||
[8] Create API key (atau ambil existing kalau sudah ada)
|
[8] Create API key (atau ambil existing kalau sudah ada)
|
||||||
[9] Fill Ultraspeed beta application form
|
[9] Fill Ultraspeed beta application form
|
||||||
[10] Klik "Refer & earn" → ambil ref code akun ini → seed untuk akun berikut
|
[10] Klik "Refer & earn" → ambil ref code akun ini
|
||||||
```
|
```
|
||||||
|
|
||||||
## Output
|
## Output
|
||||||
@@ -136,9 +140,13 @@ akan di-skip otomatis, aman untuk re-run.
|
|||||||
**Verifikasi balance** — Setelah redeem invite code, halaman di-reload dan balance
|
**Verifikasi balance** — Setelah redeem invite code, halaman di-reload dan balance
|
||||||
dibaca ulang. Harus bertambah ~$2.00. Kalau tidak, iterasi dianggap gagal.
|
dibaca ulang. Harus bertambah ~$2.00. Kalau tidak, iterasi dianggap gagal.
|
||||||
|
|
||||||
**Restriction detection** — Kalau setelah redeem muncul notif "risk control restrictions",
|
**Restriction detection** — Kalau setelah redeem atau saat membuka halaman balance muncul notif "risk control restrictions",
|
||||||
loop berhenti total (akun kena cap, gak bisa lanjut).
|
loop berhenti total (akun kena cap, gak bisa lanjut).
|
||||||
|
|
||||||
|
**Proxy Auto-Rotation & Sanitized Logs** — Mendukung rotasi proxy otomatis per iterasi dengan membaca file `proxy.txt`. Username/password autentikasi disembunyikan dari logs untuk menjaga kerahasiaan.
|
||||||
|
|
||||||
|
**Custom Email Domain** — Mendukung domain email kustom via properti `tempmail.domain` di `config.json`.
|
||||||
|
|
||||||
## Stop conditions
|
## Stop conditions
|
||||||
|
|
||||||
Loop berhenti kalau:
|
Loop berhenti kalau:
|
||||||
|
|||||||
+23
-2
@@ -18,7 +18,7 @@ import { readFileSync, appendFileSync, existsSync } from 'fs';
|
|||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import { dirname, join } from 'path';
|
import { dirname, join } from 'path';
|
||||||
|
|
||||||
import { MimoRegistration } from './index.js';
|
import { MimoRegistration, loadProxyList } from './index.js';
|
||||||
import './extras.js'; // attach getReferralCode
|
import './extras.js'; // attach getReferralCode
|
||||||
import { generateFingerprint, buildInitScript, buildExtraHeaders } from './fingerprint.js';
|
import { generateFingerprint, buildInitScript, buildExtraHeaders } from './fingerprint.js';
|
||||||
|
|
||||||
@@ -50,6 +50,27 @@ const outputFile = outputIdx !== -1
|
|||||||
|
|
||||||
const failLog = join(outputDir, 'chain-fail.log');
|
const failLog = join(outputDir, 'chain-fail.log');
|
||||||
|
|
||||||
|
const proxyIdx = args.indexOf('--proxy');
|
||||||
|
let proxyList = [];
|
||||||
|
if (proxyIdx !== -1) {
|
||||||
|
const nextArg = args[proxyIdx + 1];
|
||||||
|
let proxyPath = 'proxy.txt';
|
||||||
|
if (nextArg && !nextArg.startsWith('-')) {
|
||||||
|
proxyPath = nextArg;
|
||||||
|
}
|
||||||
|
const resolvedPath = join(__dirname, proxyPath);
|
||||||
|
if (existsSync(resolvedPath)) {
|
||||||
|
proxyList = loadProxyList(resolvedPath);
|
||||||
|
console.log(`Loaded ${proxyList.length} proxies from ${proxyPath}`);
|
||||||
|
} else if (nextArg && !nextArg.startsWith('-')) {
|
||||||
|
proxyList = [nextArg];
|
||||||
|
} else {
|
||||||
|
console.log(`Proxy option enabled but ${proxyPath} not found.`);
|
||||||
|
}
|
||||||
|
} else if (config.proxy) {
|
||||||
|
proxyList = [config.proxy];
|
||||||
|
}
|
||||||
|
|
||||||
// ---- File helpers -----------------------------------------------------
|
// ---- File helpers -----------------------------------------------------
|
||||||
|
|
||||||
function saveResult(row) {
|
function saveResult(row) {
|
||||||
@@ -75,4 +96,4 @@ function logFail(email, error) {
|
|||||||
appendFileSync(failLog, line, 'utf8');
|
appendFileSync(failLog, line, 'utf8');
|
||||||
}
|
}
|
||||||
|
|
||||||
export { config, saveResult, logFail, count, seedRef, outputFile, failLog, __dirname };
|
export { config, saveResult, logFail, count, seedRef, outputFile, failLog, __dirname, proxyList };
|
||||||
|
|||||||
+9
-2
@@ -17,12 +17,12 @@
|
|||||||
import { chromium } from 'playwright';
|
import { chromium } from 'playwright';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
|
|
||||||
import { MimoRegistration } from './index.js';
|
import { MimoRegistration, parseProxy } from './index.js';
|
||||||
import './extras.js'; // attach getReferralCode
|
import './extras.js'; // attach getReferralCode
|
||||||
import { generateFingerprint, buildInitScript, buildExtraHeaders } from './fingerprint.js';
|
import { generateFingerprint, buildInitScript, buildExtraHeaders } from './fingerprint.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
config, saveResult, logFail, count, seedRef, outputFile, failLog, __dirname,
|
config, saveResult, logFail, count, seedRef, outputFile, failLog, __dirname, proxyList,
|
||||||
} from './chain-loop-config.js';
|
} from './chain-loop-config.js';
|
||||||
|
|
||||||
// ---- One-iteration runner --------------------------------------------
|
// ---- One-iteration runner --------------------------------------------
|
||||||
@@ -34,6 +34,12 @@ async function runIteration(idx, total, currentRef) {
|
|||||||
|
|
||||||
// Per-iter: clone config + override invite code & referral link
|
// Per-iter: clone config + override invite code & referral link
|
||||||
const iterConfig = JSON.parse(JSON.stringify(config));
|
const iterConfig = JSON.parse(JSON.stringify(config));
|
||||||
|
if (proxyList && proxyList.length > 0) {
|
||||||
|
const selectedProxy = proxyList[idx % proxyList.length];
|
||||||
|
iterConfig.proxy = selectedProxy;
|
||||||
|
const cleanProxyName = selectedProxy.split('@').pop();
|
||||||
|
console.log(` ↳ Using proxy [${(idx % proxyList.length) + 1}/${proxyList.length}]: ${cleanProxyName}`);
|
||||||
|
}
|
||||||
iterConfig.xiaomi.inviteCode = currentRef;
|
iterConfig.xiaomi.inviteCode = currentRef;
|
||||||
iterConfig.xiaomi.referralLink =
|
iterConfig.xiaomi.referralLink =
|
||||||
`https://platform.xiaomimimo.com/?ref=${encodeURIComponent(currentRef)}`;
|
`https://platform.xiaomimimo.com/?ref=${encodeURIComponent(currentRef)}`;
|
||||||
@@ -67,6 +73,7 @@ async function runIteration(idx, total, currentRef) {
|
|||||||
reg.browser = await chromium.launch({
|
reg.browser = await chromium.launch({
|
||||||
headless: iterConfig.browser.headless,
|
headless: iterConfig.browser.headless,
|
||||||
channel: 'chrome',
|
channel: 'chrome',
|
||||||
|
proxy: parseProxy(iterConfig.proxy),
|
||||||
args: [
|
args: [
|
||||||
`--window-size=${fp.viewport.width},${fp.viewport.height}`,
|
`--window-size=${fp.viewport.width},${fp.viewport.height}`,
|
||||||
'--disable-blink-features=AutomationControlled',
|
'--disable-blink-features=AutomationControlled',
|
||||||
|
|||||||
+3
-2
@@ -1,10 +1,11 @@
|
|||||||
{
|
{
|
||||||
"tempmail": {
|
"tempmail": {
|
||||||
"apiUrl": "https://tempmail.adrnode.com/api"
|
"apiUrl": "https://tempmail.adrnode.com/api",
|
||||||
|
"domain": "foxsight.xyz"
|
||||||
},
|
},
|
||||||
"captcha": {
|
"captcha": {
|
||||||
"provider": "2captcha",
|
"provider": "2captcha",
|
||||||
"apiKey": "YOUR_2CAPTCHA_API_KEY"
|
"apiKey": "82d7c3c7dfca21006510df3c6993b807"
|
||||||
},
|
},
|
||||||
"xiaomi": {
|
"xiaomi": {
|
||||||
"referralLink": "https://platform.xiaomimimo.com/?ref=XXXXXX",
|
"referralLink": "https://platform.xiaomimimo.com/?ref=XXXXXX",
|
||||||
|
|||||||
+8
-8
@@ -34,10 +34,10 @@ const VIEWPORTS = [
|
|||||||
{ width: 1280, height: 800 },
|
{ width: 1280, height: 800 },
|
||||||
];
|
];
|
||||||
|
|
||||||
const LOCALES = ['en-US', 'en-GB', 'id-ID', 'en-AU', 'en-CA', 'en-SG'];
|
const LOCALES = ['en-US', 'en-GB', 'en-AU', 'en-CA', 'en-SG'];
|
||||||
|
|
||||||
const TIMEZONES = [
|
const TIMEZONES = [
|
||||||
'Asia/Jakarta', 'Asia/Singapore', 'Asia/Bangkok',
|
'Asia/Singapore', 'Asia/Bangkok',
|
||||||
'Asia/Kuala_Lumpur', 'Asia/Manila', 'Asia/Ho_Chi_Minh',
|
'Asia/Kuala_Lumpur', 'Asia/Manila', 'Asia/Ho_Chi_Minh',
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -52,11 +52,11 @@ const WEBGL_RENDERERS = [
|
|||||||
{ vendor: 'Google Inc. (NVIDIA)', renderer: 'ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Direct3D11 vs_5_0 ps_5_0, D3D11)' },
|
{ vendor: 'Google Inc. (NVIDIA)', renderer: 'ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Direct3D11 vs_5_0 ps_5_0, D3D11)' },
|
||||||
{ vendor: 'Google Inc. (NVIDIA)', renderer: 'ANGLE (NVIDIA, NVIDIA GeForce RTX 4060 Ti Direct3D11 vs_5_0 ps_5_0, D3D11)' },
|
{ vendor: 'Google Inc. (NVIDIA)', renderer: 'ANGLE (NVIDIA, NVIDIA GeForce RTX 4060 Ti Direct3D11 vs_5_0 ps_5_0, D3D11)' },
|
||||||
{ vendor: 'Google Inc. (NVIDIA)', renderer: 'ANGLE (NVIDIA, NVIDIA GeForce GTX 1050 Ti Direct3D11 vs_5_0 ps_5_0, D3D11)' },
|
{ vendor: 'Google Inc. (NVIDIA)', renderer: 'ANGLE (NVIDIA, NVIDIA GeForce GTX 1050 Ti Direct3D11 vs_5_0 ps_5_0, D3D11)' },
|
||||||
{ vendor: 'Google Inc. (Intel)', renderer: 'ANGLE (Intel, Intel(R) UHD Graphics 630 Direct3D11 vs_5_0 ps_5_0, D3D11)' },
|
{ vendor: 'Google Inc. (Intel)', renderer: 'ANGLE (Intel, Intel(R) UHD Graphics 630 Direct3D11 vs_5_0 ps_5_0, D3D11)' },
|
||||||
{ vendor: 'Google Inc. (Intel)', renderer: 'ANGLE (Intel, Intel(R) Iris(R) Xe Graphics Direct3D11 vs_5_0 ps_5_0, D3D11)' },
|
{ vendor: 'Google Inc. (Intel)', renderer: 'ANGLE (Intel, Intel(R) Iris(R) Xe Graphics Direct3D11 vs_5_0 ps_5_0, D3D11)' },
|
||||||
{ vendor: 'Google Inc. (Intel)', renderer: 'ANGLE (Intel, Intel(R) HD Graphics 620 Direct3D11 vs_5_0 ps_5_0, D3D11)' },
|
{ vendor: 'Google Inc. (Intel)', renderer: 'ANGLE (Intel, Intel(R) HD Graphics 620 Direct3D11 vs_5_0 ps_5_0, D3D11)' },
|
||||||
{ vendor: 'Google Inc. (AMD)', renderer: 'ANGLE (AMD, AMD Radeon RX 6600 XT Direct3D11 vs_5_0 ps_5_0, D3D11)' },
|
{ vendor: 'Google Inc. (AMD)', renderer: 'ANGLE (AMD, AMD Radeon RX 6600 XT Direct3D11 vs_5_0 ps_5_0, D3D11)' },
|
||||||
{ vendor: 'Google Inc. (AMD)', renderer: 'ANGLE (AMD, AMD Radeon Graphics Direct3D11 vs_5_0 ps_5_0, D3D11)' },
|
{ vendor: 'Google Inc. (AMD)', renderer: 'ANGLE (AMD, AMD Radeon Graphics Direct3D11 vs_5_0 ps_5_0, D3D11)' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// ---- Helpers ------------------------------------------------------------
|
// ---- Helpers ------------------------------------------------------------
|
||||||
@@ -249,7 +249,7 @@ export function buildInitScript(fp) {
|
|||||||
|
|
||||||
// CLI test: `node fingerprint.js` — print 3 contoh fingerprint
|
// CLI test: `node fingerprint.js` — print 3 contoh fingerprint
|
||||||
if (import.meta.url === `file:///${process.argv[1].replace(/\\/g, '/')}` ||
|
if (import.meta.url === `file:///${process.argv[1].replace(/\\/g, '/')}` ||
|
||||||
(process.argv[1] && process.argv[1].endsWith('fingerprint.js'))) {
|
(process.argv[1] && process.argv[1].endsWith('fingerprint.js'))) {
|
||||||
for (let i = 0; i < 3; i++) {
|
for (let i = 0; i < 3; i++) {
|
||||||
const fp = generateFingerprint();
|
const fp = generateFingerprint();
|
||||||
console.log(`\n--- Sample ${i + 1} ---`);
|
console.log(`\n--- Sample ${i + 1} ---`);
|
||||||
|
|||||||
@@ -23,8 +23,9 @@ const config = JSON.parse(readFileSync(join(__dirname, 'config.json'), 'utf8'));
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
class TempmailClient {
|
class TempmailClient {
|
||||||
constructor(apiUrl) {
|
constructor(apiUrl, domain) {
|
||||||
this.apiUrl = apiUrl;
|
this.apiUrl = apiUrl;
|
||||||
|
this.domain = domain;
|
||||||
this.sessionId = null;
|
this.sessionId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,13 +49,19 @@ class TempmailClient {
|
|||||||
async createInbox() {
|
async createInbox() {
|
||||||
await this.initSession();
|
await this.initSession();
|
||||||
|
|
||||||
const response = await fetch(`${this.apiUrl}/inboxes`, {
|
const headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'x-session-id': this.sessionId
|
||||||
|
};
|
||||||
|
const options = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers
|
||||||
'Content-Type': 'application/json',
|
};
|
||||||
'x-session-id': this.sessionId
|
if (this.domain) {
|
||||||
}
|
options.body = JSON.stringify({ domain: this.domain });
|
||||||
});
|
}
|
||||||
|
|
||||||
|
const response = await fetch(`${this.apiUrl}/inboxes`, options);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Tempmail API error: ${response.status}`);
|
throw new Error(`Tempmail API error: ${response.status}`);
|
||||||
@@ -219,7 +226,7 @@ class CaptchaSolver {
|
|||||||
class MimoRegistration {
|
class MimoRegistration {
|
||||||
constructor(config) {
|
constructor(config) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.tempmail = new TempmailClient(config.tempmail.apiUrl);
|
this.tempmail = new TempmailClient(config.tempmail.apiUrl, config.tempmail.domain);
|
||||||
this.captcha = new CaptchaSolver(config.captcha.apiKey);
|
this.captcha = new CaptchaSolver(config.captcha.apiKey);
|
||||||
this.browser = null;
|
this.browser = null;
|
||||||
this.page = null;
|
this.page = null;
|
||||||
@@ -249,9 +256,15 @@ class MimoRegistration {
|
|||||||
console.log(` ↳ CPU/Mem : ${fp.hardwareConcurrency} cores / ${fp.deviceMemory} GB`);
|
console.log(` ↳ CPU/Mem : ${fp.hardwareConcurrency} cores / ${fp.deviceMemory} GB`);
|
||||||
console.log(` ↳ GPU : ${fp.webgl.renderer.substring(0, 70)}…`);
|
console.log(` ↳ GPU : ${fp.webgl.renderer.substring(0, 70)}…`);
|
||||||
|
|
||||||
|
const proxyConfig = parseProxy(config.proxy);
|
||||||
|
if (proxyConfig) {
|
||||||
|
console.log(` ↳ Proxy : ${proxyConfig.server}`);
|
||||||
|
}
|
||||||
|
|
||||||
this.browser = await chromium.launch({
|
this.browser = await chromium.launch({
|
||||||
headless: config.browser.headless,
|
headless: config.browser.headless,
|
||||||
channel: 'chrome',
|
channel: 'chrome',
|
||||||
|
proxy: proxyConfig,
|
||||||
args: [
|
args: [
|
||||||
// Hindari flag --headless yg kelihatan, dan window-size konsisten dgn viewport
|
// Hindari flag --headless yg kelihatan, dan window-size konsisten dgn viewport
|
||||||
`--window-size=${fp.viewport.width},${fp.viewport.height}`,
|
`--window-size=${fp.viewport.width},${fp.viewport.height}`,
|
||||||
@@ -2008,6 +2021,33 @@ async function main() {
|
|||||||
count = parseInt(config.registration.count, 10) || 1;
|
count = parseInt(config.registration.count, 10) || 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const proxyArgIndex = process.argv.indexOf('--proxy');
|
||||||
|
let proxyList = [];
|
||||||
|
if (proxyArgIndex !== -1) {
|
||||||
|
const nextArg = process.argv[proxyArgIndex + 1];
|
||||||
|
// if nextArg is another flag or empty, assume default proxy.txt
|
||||||
|
let proxyPath = 'proxy.txt';
|
||||||
|
if (nextArg && !nextArg.startsWith('-')) {
|
||||||
|
proxyPath = nextArg;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the parameter is a file path that exists, or default proxy.txt exists
|
||||||
|
const { existsSync } = await import('fs');
|
||||||
|
const resolvedPath = join(__dirname, proxyPath);
|
||||||
|
if (existsSync(resolvedPath)) {
|
||||||
|
proxyList = loadProxyList(resolvedPath);
|
||||||
|
console.log(`Loaded ${proxyList.length} proxies from ${proxyPath}`);
|
||||||
|
} else if (nextArg && !nextArg.startsWith('-')) {
|
||||||
|
// Direct proxy string instead of file
|
||||||
|
proxyList = [nextArg];
|
||||||
|
} else {
|
||||||
|
// Default file doesn't exist either
|
||||||
|
console.log(`Proxy option enabled but ${proxyPath} not found.`);
|
||||||
|
}
|
||||||
|
} else if (config.proxy) {
|
||||||
|
proxyList = [config.proxy];
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`Starting loop to create ${count} account(s)...`);
|
console.log(`Starting loop to create ${count} account(s)...`);
|
||||||
|
|
||||||
let successCount = 0;
|
let successCount = 0;
|
||||||
@@ -2018,7 +2058,12 @@ async function main() {
|
|||||||
console.log(` [Loop ${i + 1}/${count}] Starting Auto-Registration`);
|
console.log(` [Loop ${i + 1}/${count}] Starting Auto-Registration`);
|
||||||
console.log('='.repeat(70));
|
console.log('='.repeat(70));
|
||||||
|
|
||||||
const registration = new MimoRegistration(config);
|
const iterConfig = JSON.parse(JSON.stringify(config));
|
||||||
|
if (proxyList.length > 0) {
|
||||||
|
iterConfig.proxy = proxyList[i % proxyList.length];
|
||||||
|
}
|
||||||
|
|
||||||
|
const registration = new MimoRegistration(iterConfig);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await registration.run();
|
const result = await registration.run();
|
||||||
@@ -2082,4 +2127,44 @@ if (import.meta.url === `file:///${process.argv[1].replace(/\\/g, '/')}` ||
|
|||||||
main();
|
main();
|
||||||
}
|
}
|
||||||
|
|
||||||
export { MimoRegistration, TempmailClient, CaptchaSolver };
|
function loadProxyList(filePath) {
|
||||||
|
try {
|
||||||
|
const { readFileSync } = require('fs'); // or use fs import
|
||||||
|
} catch (e) {}
|
||||||
|
// Since we already import readFileSync from 'fs' at the top of this file, we can use it directly
|
||||||
|
try {
|
||||||
|
const content = readFileSync(filePath, 'utf8');
|
||||||
|
return content
|
||||||
|
.split(/\r?\n/)
|
||||||
|
.map(line => line.trim())
|
||||||
|
.filter(line => line && !line.startsWith('#'));
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Error loading proxy list: ${e.message}`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseProxy(proxyStr) {
|
||||||
|
if (!proxyStr) return undefined;
|
||||||
|
let formatted = proxyStr.trim();
|
||||||
|
if (!/^https?:\/\//i.test(formatted) && !/^socks[45]:\/\//i.test(formatted)) {
|
||||||
|
formatted = 'http://' + formatted;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const url = new URL(formatted);
|
||||||
|
const proxyObj = {
|
||||||
|
server: `${url.protocol}//${url.host}`,
|
||||||
|
};
|
||||||
|
if (url.username) {
|
||||||
|
proxyObj.username = decodeURIComponent(url.username);
|
||||||
|
}
|
||||||
|
if (url.password) {
|
||||||
|
proxyObj.password = decodeURIComponent(url.password);
|
||||||
|
}
|
||||||
|
return proxyObj;
|
||||||
|
} catch (e) {
|
||||||
|
return { server: proxyStr };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { MimoRegistration, TempmailClient, CaptchaSolver, parseProxy, loadProxyList };
|
||||||
|
|||||||
+154
-10
@@ -15,7 +15,7 @@ import { readFileSync, appendFileSync, existsSync } from 'fs';
|
|||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import { dirname, join } from 'path';
|
import { dirname, join } from 'path';
|
||||||
|
|
||||||
import { MimoRegistration } from './index.js';
|
import { MimoRegistration, parseProxy, loadProxyList } from './index.js';
|
||||||
import './extras.js'; // attach getReferralCode
|
import './extras.js'; // attach getReferralCode
|
||||||
import { generateFingerprint, buildInitScript, buildExtraHeaders } from './fingerprint.js';
|
import { generateFingerprint, buildInitScript, buildExtraHeaders } from './fingerprint.js';
|
||||||
|
|
||||||
@@ -43,6 +43,27 @@ const outputFile = outputIdx !== -1
|
|||||||
|
|
||||||
const failLog = join(outputDir, 'regis-fail.log');
|
const failLog = join(outputDir, 'regis-fail.log');
|
||||||
|
|
||||||
|
const proxyIdx = args.indexOf('--proxy');
|
||||||
|
let proxyList = [];
|
||||||
|
if (proxyIdx !== -1) {
|
||||||
|
const nextArg = args[proxyIdx + 1];
|
||||||
|
let proxyPath = 'proxy.txt';
|
||||||
|
if (nextArg && !nextArg.startsWith('-')) {
|
||||||
|
proxyPath = nextArg;
|
||||||
|
}
|
||||||
|
const resolvedPath = join(__dirname, proxyPath);
|
||||||
|
if (existsSync(resolvedPath)) {
|
||||||
|
proxyList = loadProxyList(resolvedPath);
|
||||||
|
console.log(`Loaded ${proxyList.length} proxies from ${proxyPath}`);
|
||||||
|
} else if (nextArg && !nextArg.startsWith('-')) {
|
||||||
|
proxyList = [nextArg];
|
||||||
|
} else {
|
||||||
|
console.log(`Proxy option enabled but ${proxyPath} not found.`);
|
||||||
|
}
|
||||||
|
} else if (config.proxy) {
|
||||||
|
proxyList = [config.proxy];
|
||||||
|
}
|
||||||
|
|
||||||
// ---- File helpers -----------------------------------------------------
|
// ---- File helpers -----------------------------------------------------
|
||||||
|
|
||||||
function saveResult(row) {
|
function saveResult(row) {
|
||||||
@@ -76,6 +97,12 @@ async function runIteration(idx, total) {
|
|||||||
|
|
||||||
// Per-iter: clone config + set referral link to direct URL (no referral parameter)
|
// Per-iter: clone config + set referral link to direct URL (no referral parameter)
|
||||||
const iterConfig = JSON.parse(JSON.stringify(config));
|
const iterConfig = JSON.parse(JSON.stringify(config));
|
||||||
|
if (proxyList && proxyList.length > 0) {
|
||||||
|
const selectedProxy = proxyList[idx % proxyList.length];
|
||||||
|
iterConfig.proxy = selectedProxy;
|
||||||
|
const cleanProxyName = selectedProxy.split('@').pop();
|
||||||
|
console.log(` ↳ Using proxy [${(idx % proxyList.length) + 1}/${proxyList.length}]: ${cleanProxyName}`);
|
||||||
|
}
|
||||||
iterConfig.xiaomi.inviteCode = '';
|
iterConfig.xiaomi.inviteCode = '';
|
||||||
iterConfig.xiaomi.referralLink = 'https://platform.xiaomimimo.com/';
|
iterConfig.xiaomi.referralLink = 'https://platform.xiaomimimo.com/';
|
||||||
|
|
||||||
@@ -95,12 +122,12 @@ async function runIteration(idx, total) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. Tempmail
|
// 1. Tempmail
|
||||||
console.log('[1/7] Creating temporary email...');
|
console.log('[1/8] Creating temporary email...');
|
||||||
email = await reg.tempmail.createInbox();
|
email = await reg.tempmail.createInbox();
|
||||||
console.log(`✓ Email: ${email}`);
|
console.log(`✓ Email: ${email}`);
|
||||||
|
|
||||||
// 2. Browser
|
// 2. Browser
|
||||||
console.log('[2/7] Launching browser...');
|
console.log('[2/8] Launching browser...');
|
||||||
const fp = generateFingerprint();
|
const fp = generateFingerprint();
|
||||||
reg.fingerprint = fp;
|
reg.fingerprint = fp;
|
||||||
console.log(` ↳ Chrome ${fp.chromeMajor}, ${fp.viewport.width}x${fp.viewport.height}, ${fp.locale}/${fp.timezone}`);
|
console.log(` ↳ Chrome ${fp.chromeMajor}, ${fp.viewport.width}x${fp.viewport.height}, ${fp.locale}/${fp.timezone}`);
|
||||||
@@ -108,6 +135,7 @@ async function runIteration(idx, total) {
|
|||||||
reg.browser = await chromium.launch({
|
reg.browser = await chromium.launch({
|
||||||
headless: iterConfig.browser.headless,
|
headless: iterConfig.browser.headless,
|
||||||
channel: 'chrome',
|
channel: 'chrome',
|
||||||
|
proxy: parseProxy(iterConfig.proxy),
|
||||||
args: [
|
args: [
|
||||||
`--window-size=${fp.viewport.width},${fp.viewport.height}`,
|
`--window-size=${fp.viewport.width},${fp.viewport.height}`,
|
||||||
'--disable-blink-features=AutomationControlled',
|
'--disable-blink-features=AutomationControlled',
|
||||||
@@ -134,31 +162,135 @@ async function runIteration(idx, total) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 3. Open registration page directly
|
// 3. Open registration page directly
|
||||||
console.log('[3/7] Opening registration page...');
|
console.log('[3/8] Opening registration page...');
|
||||||
await reg.page.goto(iterConfig.xiaomi.referralLink, {
|
await reg.page.goto(iterConfig.xiaomi.referralLink, {
|
||||||
waitUntil: 'networkidle',
|
waitUntil: 'networkidle',
|
||||||
timeout: iterConfig.browser.timeout,
|
timeout: iterConfig.browser.timeout,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 4. Sign up form
|
// 4. Sign up form
|
||||||
console.log('[4/7] Filling Sign up form...');
|
console.log('[4/8] Filling Sign up form...');
|
||||||
await reg.fillRegistrationForm(email);
|
await reg.fillRegistrationForm(email);
|
||||||
await reg.submitRegistration();
|
await reg.submitRegistration();
|
||||||
|
|
||||||
// 5. Captcha
|
// 5. Captcha
|
||||||
console.log('[5/7] Handling captcha...');
|
console.log('[5/8] Handling captcha...');
|
||||||
await reg.handleXiaomiCaptcha();
|
await reg.handleXiaomiCaptcha();
|
||||||
await reg.handleImageCaptcha();
|
await reg.handleImageCaptcha();
|
||||||
|
|
||||||
// 6. Verify email
|
// 6. Verify email
|
||||||
console.log('[6/7] Verifying email...');
|
console.log('[6/8] Verifying email...');
|
||||||
await reg.verifyEmail(email);
|
await reg.verifyEmail(email);
|
||||||
|
|
||||||
// 7. Skip Redeem
|
// 7. Check Balance (Terms modal will be handled here if opened)
|
||||||
console.log('[Info] Skipping redeem/enter invite code step.');
|
console.log('[7/8] Checking balance...');
|
||||||
|
try {
|
||||||
|
// 1. Klik menu "Console" dulu — biar navigasi-nya mirip user beneran
|
||||||
|
await reg.clickConsoleMenu();
|
||||||
|
|
||||||
|
// 2. Navigasi ke balance page jika belum
|
||||||
|
let url = reg.page.url();
|
||||||
|
if (!url.includes('/console/balance')) {
|
||||||
|
console.log(` Not on balance page yet (current: ${url}), navigating...`);
|
||||||
|
|
||||||
|
const balanceClicked = await reg.page.evaluate(() => {
|
||||||
|
const links = Array.from(document.querySelectorAll('a, [role="link"], li, [role="menuitem"]'));
|
||||||
|
const target = links.find(el => {
|
||||||
|
const txt = (el.textContent || '').trim();
|
||||||
|
return /^Balance$/i.test(txt) && el.offsetHeight > 0;
|
||||||
|
});
|
||||||
|
if (target) {
|
||||||
|
target.scrollIntoView({ block: 'center' });
|
||||||
|
target.click();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (balanceClicked) {
|
||||||
|
console.log(' ✓ Clicked Balance link in console sidebar');
|
||||||
|
await reg.page.waitForURL(/\/console\/balance/, { timeout: 8000 }).catch(() => {});
|
||||||
|
await reg.page.waitForLoadState('networkidle', { timeout: 8000 }).catch(() => {});
|
||||||
|
} else {
|
||||||
|
console.log(' ! Balance link not found, falling back to direct goto...');
|
||||||
|
await reg.page.goto('https://platform.xiaomimimo.com/console/balance', {
|
||||||
|
waitUntil: 'networkidle',
|
||||||
|
timeout: iterConfig.browser.timeout,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await reg.page.waitForTimeout(2500);
|
||||||
|
|
||||||
|
await reg.handleOAuthRedirect();
|
||||||
|
await reg.page.waitForTimeout(2000);
|
||||||
|
|
||||||
|
// Close cookies banner if any
|
||||||
|
console.log(' Checking for cookies banner...');
|
||||||
|
const acceptCookiesBtn = await reg.page.waitForSelector('button:has-text("Accept All"), button:has-text("Accept"), button:has-text("Allow All")', { timeout: 4000 }).catch(() => null);
|
||||||
|
if (acceptCookiesBtn) {
|
||||||
|
await acceptCookiesBtn.click({ force: true }).catch(() => {});
|
||||||
|
console.log(' ✓ Accepted cookies banner');
|
||||||
|
await reg.page.waitForTimeout(2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Terms modal
|
||||||
|
await reg.handleTermsModal();
|
||||||
|
|
||||||
|
// Overlays
|
||||||
|
await reg.waitForOverlaysGone();
|
||||||
|
|
||||||
|
// Take screenshot
|
||||||
|
await reg.page.screenshot({ path: 'screenshot-balance.png', fullPage: false });
|
||||||
|
console.log(' ✓ Captured screenshot-balance.png');
|
||||||
|
|
||||||
|
// Cek apakah ada notifikasi risk control / block/ restricted
|
||||||
|
const restrictionMsg = await reg.page.evaluate(() => {
|
||||||
|
const text = document.body.innerText || '';
|
||||||
|
const patterns = [
|
||||||
|
/risk\s*control\s*restriction/i,
|
||||||
|
/account\s+has\s+risk\s+control/i,
|
||||||
|
/contact\s+customer\s+service/i,
|
||||||
|
/account\s+(is\s+)?restricted/i,
|
||||||
|
];
|
||||||
|
for (const re of patterns) {
|
||||||
|
const m = text.match(new RegExp('([^\\n]{0,200}' + re.source + '[^\\n]{0,200})', re.flags));
|
||||||
|
if (m) return m[1].trim();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}).catch(() => null);
|
||||||
|
|
||||||
|
if (restrictionMsg) {
|
||||||
|
console.log(' ❌ ACCOUNT RESTRICTED:');
|
||||||
|
console.log(` ${restrictionMsg}`);
|
||||||
|
try {
|
||||||
|
await reg.page.screenshot({
|
||||||
|
path: `error-restriction-${Date.now()}.png`,
|
||||||
|
fullPage: true,
|
||||||
|
});
|
||||||
|
} catch (e) {}
|
||||||
|
const err = new Error(`ACCOUNT_RESTRICTED: ${restrictionMsg}`);
|
||||||
|
err.code = 'ACCOUNT_RESTRICTED';
|
||||||
|
err.restrictionMsg = restrictionMsg;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read balance
|
||||||
|
const balance = await reg.readBalance();
|
||||||
|
console.log(` 💰 Balance: $${balance !== null ? balance.toFixed(2) : 'unknown'}`);
|
||||||
|
if (balance === 0) {
|
||||||
|
const err = new Error('Balance is $0.00');
|
||||||
|
err.code = 'BALANCE_ZERO';
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e.code === 'ACCOUNT_RESTRICTED' || e.code === 'BALANCE_ZERO') {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
console.log(` ! check balance error (lanjut): ${e.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
// 8. Create API key (atau ambil existing)
|
// 8. Create API key (atau ambil existing)
|
||||||
console.log('[7/7] Creating API key...');
|
console.log('[8/8] Creating API key...');
|
||||||
try {
|
try {
|
||||||
apiKey = await reg.createApiKey();
|
apiKey = await reg.createApiKey();
|
||||||
console.log(`✓ API key: ${apiKey || '(none)'}`);
|
console.log(`✓ API key: ${apiKey || '(none)'}`);
|
||||||
@@ -201,6 +333,18 @@ async function runIteration(idx, total) {
|
|||||||
|
|
||||||
return { ok: true, email, refCode, apiKey };
|
return { ok: true, email, refCode, apiKey };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
if (err.code === 'BALANCE_ZERO') {
|
||||||
|
console.log(`\n⚠ Regis Iteration ${idx + 1} skipped: ${err.message}`);
|
||||||
|
logFail(email, err.message);
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
email,
|
||||||
|
error: err.message,
|
||||||
|
restricted: false,
|
||||||
|
stopReason: err.code,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
console.error(`\n❌ Regis Iteration ${idx + 1} failed: ${err.message}`);
|
console.error(`\n❌ Regis Iteration ${idx + 1} failed: ${err.message}`);
|
||||||
if (reg.page) {
|
if (reg.page) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,2 +1,7 @@
|
|||||||
# Regis loop results. Format: email:password:refCode:apiKey
|
# Regis loop results. Format: email:password:refCode:apiKey
|
||||||
[email protected]:MimoAuto2024!:DPWQPR:sk-spot7dmzwv05ijj960sz2vab17h3ii42x01zcyg31834cpun
|
[email protected]:MimoAuto2024!:DPWQPR:sk-spot7dmzwv05ijj960sz2vab17h3ii42x01zcyg31834cpun
|
||||||
|
[email protected]:MimoAuto2024!::
|
||||||
|
[email protected]:MimoAuto2024!:T58EXJ:sk-snu9xvlbnir5ofgne07liaa0juagrr5a9l3939wii7oz8lo7
|
||||||
|
[email protected]:MimoAuto2024!::
|
||||||
|
[email protected]:MimoAuto2024!::
|
||||||
|
[email protected]:MimoAuto2024!:XAFA8U:sk-scb9vvdzsv5h3wirlrkto59v4e11in0gubz36kdnijk0r5xo
|
||||||
|
|||||||
Reference in New Issue
Block a user