c-capcut 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/lib/otp.js ADDED
@@ -0,0 +1,157 @@
1
+ /**
2
+ * lib/otp.js
3
+ * OTP polling - fetch OTP codes from GoMail API.
4
+ *
5
+ * Uses GET /api/v1/emails?alias=... to poll inbox.
6
+ *
7
+ * Flow:
8
+ * 1. Poll inbox every `pollInterval` ms
9
+ * 2. Filter for CapCut / TikTok verification emails
10
+ * 3. Extract 6-digit code from subject/text_body/html_body
11
+ * 4. Stabilize (wait for late-arriving emails)
12
+ * 5. Return code(s)
13
+ */
14
+
15
+ // ─── HTTP Helper ──────────────────────────────────────────────────────────────
16
+
17
+ async function getEmails(apiUrl, apiKey, alias, limit = 5) {
18
+ const res = await fetch(
19
+ `${apiUrl}/api/v1/emails?alias=${encodeURIComponent(alias)}&limit=${limit}`,
20
+ { headers: { "X-API-Key": apiKey, Accept: "application/json" } },
21
+ );
22
+ if (!res.ok) throw new Error(`Email API error ${res.status}`);
23
+ const json = await res.json();
24
+ return json.data || [];
25
+ }
26
+
27
+ // ─── OTP Extractor ────────────────────────────────────────────────────────────
28
+
29
+ function stripHtml(html) {
30
+ return html
31
+ .replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "")
32
+ .replace(/<[^>]+>/g, " ")
33
+ .replace(/&nbsp;/gi, " ")
34
+ .replace(/&#\d+;/g, " ")
35
+ .replace(/\s+/g, " ")
36
+ .trim();
37
+ }
38
+
39
+ function extractOtp(message) {
40
+ const precisePatterns = [
41
+ /verification code[:\s]+(\d{4,6})\b/i,
42
+ /\bcode\s+is[:\s]+(\d{4,6})\b/i,
43
+ /\bcode[:\s]+(\d{4,6})\b/i,
44
+ /(\d{6})\s+is your/i,
45
+ /\bis\s+(\d{4,6})\b/i,
46
+ ];
47
+
48
+ const textBody = message.text_body || "";
49
+ const htmlBody = message.html_body || "";
50
+ const cleanHtml = htmlBody ? stripHtml(htmlBody) : "";
51
+ const subject = message.subject || "";
52
+
53
+ // Try precise patterns first
54
+ for (const source of [textBody, cleanHtml, subject]) {
55
+ if (!source) continue;
56
+ for (const pat of precisePatterns) {
57
+ const m = source.match(pat);
58
+ if (m) return m[1];
59
+ }
60
+ }
61
+
62
+ // Fallback: first standalone 6-digit number in cleaned text (avoid # color codes)
63
+ for (const source of [textBody, cleanHtml]) {
64
+ if (!source) continue;
65
+ const fallback = source.match(/(?<![#\w])(\d{6})(?!\w)/);
66
+ if (fallback) return fallback[1];
67
+ }
68
+
69
+ // Last resort: subject
70
+ const fallbackSubject = subject.match(/\b(\d{6})\b/);
71
+ if (fallbackSubject) return fallbackSubject[1];
72
+
73
+ return null;
74
+ }
75
+
76
+ // ─── Collect Codes from Messages ──────────────────────────────────────────────
77
+
78
+ function collectCodes(messages, minDate) {
79
+ const sorted = [...messages].sort(
80
+ (a, b) => new Date(b.received_at || 0) - new Date(a.received_at || 0),
81
+ );
82
+
83
+ const codes = [];
84
+ for (const msg of sorted) {
85
+ const from = (msg.from_address || "").toLowerCase();
86
+ const subj = (msg.subject || msg.text_body || "").toLowerCase();
87
+ const isFromCapCut =
88
+ from.includes("capcut") ||
89
+ from.includes("tiktok") ||
90
+ from.includes("bytedance") ||
91
+ from.includes("byteoversea");
92
+ const isOtpSubject = /capcut|verification|verify|code|confirm/i.test(subj);
93
+ const isFresh = new Date(msg.received_at || 0) >= minDate;
94
+
95
+ if ((isFromCapCut || isOtpSubject) && isFresh) {
96
+ const otp = extractOtp(msg);
97
+ if (otp && !codes.includes(otp)) codes.push(otp);
98
+ }
99
+ }
100
+ return codes;
101
+ }
102
+
103
+ // ─── Main: Polling OTP ───────────────────────────────────────────────────────
104
+
105
+ /**
106
+ * Create a configured OTP waiter.
107
+ *
108
+ * @param {{ apiUrl: string, apiKey: string, timeout?: number, pollInterval?: number }} config
109
+ * @returns {(email: string, options?: {after?: Date|number}) => Promise<string[]>}
110
+ */
111
+ export function createOtpWaiter(config) {
112
+ const { apiUrl, apiKey, timeout = 90_000, pollInterval = 4_000 } = config;
113
+
114
+ return async function waitForOtp(email, options = {}) {
115
+ if (!email || !email.includes("@"))
116
+ throw new Error(`Invalid email format: ${email}`);
117
+
118
+ const startTime = Date.now();
119
+ const minDate = options.after
120
+ ? new Date(options.after)
121
+ : new Date(startTime - 30_000);
122
+
123
+ while (Date.now() - startTime < timeout) {
124
+ try {
125
+ const messages = await getEmails(apiUrl, apiKey, email, 5);
126
+ if (messages.length > 0) {
127
+ const codes = collectCodes(messages, minDate);
128
+ if (codes.length > 0) {
129
+ // Stabilize: wait for late-arriving emails
130
+ const STABILIZE_MS = 8_000;
131
+ const STABILIZE_POLL = 2_000;
132
+ const stabilizeEnd = Date.now() + STABILIZE_MS;
133
+ let latestCodes = codes;
134
+
135
+ while (Date.now() < stabilizeEnd) {
136
+ await new Promise((r) => setTimeout(r, STABILIZE_POLL));
137
+ try {
138
+ const recheck = await getEmails(apiUrl, apiKey, email, 5);
139
+ const newCodes = collectCodes(recheck, minDate);
140
+ if (newCodes.length > 0) latestCodes = newCodes;
141
+ } catch {
142
+ // silent
143
+ }
144
+ }
145
+
146
+ return latestCodes;
147
+ }
148
+ }
149
+ } catch {
150
+ // silent retry
151
+ }
152
+ await new Promise((r) => setTimeout(r, pollInterval));
153
+ }
154
+
155
+ throw new Error(`OTP not received after ${timeout / 1000}s.`);
156
+ };
157
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * lib/storage.js
3
+ * Save and load accounts from data/accounts.json.
4
+ * LocalDB email in data/email-db.json (never deleted).
5
+ * Thread-safe for concurrent writes using write-lock.
6
+ */
7
+
8
+ import fs from "fs";
9
+ import path from "path";
10
+ import { ACCOUNTS_FILE, EMAIL_DB_FILE } from "../config.js";
11
+
12
+ const FILE_PATH = path.resolve(ACCOUNTS_FILE);
13
+ const EMAIL_DB_PATH = path.resolve(EMAIL_DB_FILE);
14
+
15
+ // Ensure data/ directory exists
16
+ const dataDir = path.dirname(FILE_PATH);
17
+ if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true });
18
+
19
+ // ─── Mutex Write-Lock (prevents race conditions during concurrent writes) ────
20
+ let _writeLock = Promise.resolve();
21
+
22
+ // ─── Accounts ─────────────────────────────────────────────────────────────────
23
+
24
+ export function loadAccounts() {
25
+ if (!fs.existsSync(FILE_PATH)) return [];
26
+ try {
27
+ return JSON.parse(fs.readFileSync(FILE_PATH, "utf-8"));
28
+ } catch {
29
+ return [];
30
+ }
31
+ }
32
+
33
+ export function saveAccount(account) {
34
+ _writeLock = _writeLock.then(() => {
35
+ const accounts = loadAccounts();
36
+ accounts.push({ ...account, createdAt: new Date().toISOString() });
37
+ fs.writeFileSync(FILE_PATH, JSON.stringify(accounts, null, 2));
38
+ });
39
+ return _writeLock;
40
+ }
41
+
42
+ export function clearAccounts() {
43
+ _writeLock = _writeLock.then(() => {
44
+ fs.writeFileSync(FILE_PATH, JSON.stringify([], null, 2));
45
+ });
46
+ return _writeLock;
47
+ }
48
+
49
+ // ─── LocalDB Email (persistent, never deleted) ────────────────────────────────
50
+
51
+ export function loadEmailDb() {
52
+ if (!fs.existsSync(EMAIL_DB_PATH)) return [];
53
+ try {
54
+ return JSON.parse(fs.readFileSync(EMAIL_DB_PATH, "utf-8"));
55
+ } catch {
56
+ return [];
57
+ }
58
+ }
59
+
60
+ /** Check if email has been used before */
61
+ export function isEmailUsed(email) {
62
+ const db = loadEmailDb();
63
+ return db.includes(email);
64
+ }
65
+
66
+ /** Save email to LocalDB after account is successfully created */
67
+ export function saveEmailToDb(email) {
68
+ _writeLock = _writeLock.then(() => {
69
+ const db = loadEmailDb();
70
+ if (!db.includes(email)) {
71
+ db.push(email);
72
+ fs.writeFileSync(EMAIL_DB_PATH, JSON.stringify(db, null, 2));
73
+ }
74
+ });
75
+ return _writeLock;
76
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * ui/banner.js
3
+ * Premium terminal UI for BROM4KER CapCut.
4
+ * Uses figlet for dynamic ASCII art generation.
5
+ */
6
+
7
+ import { readFileSync } from "fs";
8
+ import { resolve, dirname } from "path";
9
+ import { fileURLToPath } from "url";
10
+ import chalk from "chalk";
11
+ import figlet from "figlet";
12
+
13
+ const __dirname = dirname(fileURLToPath(import.meta.url));
14
+ const pkg = JSON.parse(
15
+ readFileSync(resolve(__dirname, "../../package.json"), "utf-8"),
16
+ );
17
+
18
+ const VERSION = `v${pkg.version}`;
19
+
20
+ export function printBanner() {
21
+ const art = figlet.textSync("RyotaXD", {
22
+ font: "ANSI Shadow",
23
+ horizontalLayout: "default",
24
+ });
25
+
26
+ const colors = [
27
+ "#00D4AA",
28
+ "#00C9B7",
29
+ "#00BEC4",
30
+ "#00B3D1",
31
+ "#00A8DE",
32
+ "#009DEB",
33
+ "#0092F8",
34
+ "#1A87FF",
35
+ "#337CFF",
36
+ "#4D71FF",
37
+ "#6666FF",
38
+ "#805BFF",
39
+ "#9950FF",
40
+ "#B345FF",
41
+ "#CC3AFF",
42
+ ];
43
+
44
+ const lines = art.split("\n");
45
+ lines.forEach((line, i) => {
46
+ const color = colors[i % colors.length];
47
+ console.log(chalk.hex(color)(` ${line}`));
48
+ });
49
+
50
+ console.log();
51
+ console.log(chalk.gray(" " + "─".repeat(52)));
52
+ console.log();
53
+ console.log(
54
+ chalk.gray(" (Premium CapCut Account Creator - ") +
55
+ chalk.hex("#00D4AA")(VERSION) +
56
+ chalk.gray(")"),
57
+ );
58
+ console.log(
59
+ chalk.hex("#00D4AA")(" └ ") +
60
+ chalk.gray("GITHUB : ") +
61
+ chalk.hex("#00A8DE")(
62
+ "https://github.com/RyotaXD",
63
+ ),
64
+ );
65
+ console.log(
66
+ chalk.hex("#00D4AA")(" └ ") +
67
+ chalk.gray("CHANNEL : ") +
68
+ chalk.hex("#00A8DE")(
69
+ "https://whatsapp.com/channel/0029VbDbheV8fewqfqsLB13y",
70
+ ),
71
+ );
72
+ console.log(
73
+ chalk.hex("#00D4AA")(" └ ") +
74
+ chalk.gray("COMMUNITY : ") +
75
+ chalk.hex("#00A8DE")("KeyboardWarrior"),
76
+ );
77
+ }
78
+
79
+ export function printSection(title) {
80
+ console.log();
81
+ console.log(chalk.hex("#00D4AA")(" ┌─ ") + chalk.bold.white(title));
82
+ }
83
+
84
+ export function printField(icon, text) {
85
+ console.log(chalk.hex("#00D4AA")(" | ") + icon + " " + text);
86
+ }
87
+
88
+ export function printResult(stats) {
89
+ const { total, success, failed, duration } = stats;
90
+ const rate = total > 0 ? ((success / total) * 100).toFixed(1) + "%" : "0.0%";
91
+
92
+ printSection("RESULT");
93
+ console.log(
94
+ chalk.hex("#00D4AA")(" | ") +
95
+ chalk.gray("> Total: ") +
96
+ chalk.white(total),
97
+ );
98
+ console.log(
99
+ chalk.hex("#00D4AA")(" | ") +
100
+ chalk.gray("> Success: ") +
101
+ chalk.green(success),
102
+ );
103
+ console.log(
104
+ chalk.hex("#00D4AA")(" | ") +
105
+ chalk.gray("> Failed: ") +
106
+ chalk.red(failed),
107
+ );
108
+ console.log(
109
+ chalk.hex("#00D4AA")(" | ") +
110
+ chalk.gray("> Rate: ") +
111
+ chalk.hex("#00D4AA")(rate),
112
+ );
113
+ console.log(
114
+ chalk.hex("#00D4AA")(" | ") +
115
+ chalk.gray("> Duration: ") +
116
+ chalk.white(duration),
117
+ );
118
+ console.log();
119
+ }
120
+
121
+ export function printSaved() {
122
+ console.log(
123
+ chalk.gray(" 📁 Saved: ") +
124
+ chalk.hex("#00D4AA")("data/accounts.json") +
125
+ chalk.gray(" & ") +
126
+ chalk.hex("#00D4AA")("data/result.txt"),
127
+ );
128
+ console.log(
129
+ chalk.gray(" 📧 Mail: ") +
130
+ chalk.hex("#00A8DE")("https://mail.gopretstudio.com") +
131
+ chalk.gray(" (access your email inbox here)"),
132
+ );
133
+ console.log();
134
+ }
@@ -0,0 +1,175 @@
1
+ /**
2
+ * ui/display.js
3
+ * Live terminal display with progress bars, status indicators,
4
+ * and real-time updates for concurrent account creation.
5
+ */
6
+
7
+ import chalk from "chalk";
8
+ import { TOTAL_STEPS } from "../lib/capcut.js";
9
+
10
+ const BAR_WIDTH = 22;
11
+ const EMAIL_WIDTH = 32;
12
+ const STATUS_WIDTH = 38;
13
+
14
+ // ─── Progress Bar Renderer ────────────────────────────────────────────────────
15
+
16
+ function renderBar(step, total) {
17
+ const pct = Math.round((step / total) * 100);
18
+ const filled = Math.round((step / total) * BAR_WIDTH);
19
+ const empty = BAR_WIDTH - filled;
20
+
21
+ const filledChar = "━";
22
+ const emptyChar = "╌";
23
+
24
+ let bar, pctStr;
25
+
26
+ if (pct === 100) {
27
+ bar =
28
+ chalk.hex("#00D4AA")(filledChar.repeat(filled)) +
29
+ chalk.gray(emptyChar.repeat(empty));
30
+ pctStr = chalk.hex("#00D4AA").bold(`${pct}%`);
31
+ } else if (pct >= 60) {
32
+ bar =
33
+ chalk.hex("#FFD700")(filledChar.repeat(filled)) +
34
+ chalk.gray(emptyChar.repeat(empty));
35
+ pctStr = chalk.hex("#FFD700")(`${pct}%`);
36
+ } else if (pct > 0) {
37
+ bar =
38
+ chalk.hex("#00A8DE")(filledChar.repeat(filled)) +
39
+ chalk.gray(emptyChar.repeat(empty));
40
+ pctStr = chalk.hex("#00A8DE")(`${pct}%`);
41
+ } else {
42
+ bar = chalk.gray(emptyChar.repeat(BAR_WIDTH));
43
+ pctStr = chalk.gray("0%");
44
+ }
45
+
46
+ return `${bar} ${String(pctStr).padStart(4)}`;
47
+ }
48
+
49
+ // ─── Truncate Helper ──────────────────────────────────────────────────────────
50
+
51
+ function truncate(str, maxLen) {
52
+ if (str.length <= maxLen) return str.padEnd(maxLen);
53
+ return str.slice(0, maxLen - 1) + "…";
54
+ }
55
+
56
+ // ─── Status Icons ─────────────────────────────────────────────────────────────
57
+
58
+ function getIcon(row) {
59
+ if (row.done && row.success) return chalk.hex("#00D4AA")("◉");
60
+ if (row.done && !row.success) return chalk.red("◉");
61
+ if (row.step > 0) return chalk.hex("#FFD700")("◎");
62
+ return chalk.gray("○");
63
+ }
64
+
65
+ // ─── Live Display Class ───────────────────────────────────────────────────────
66
+
67
+ export class LiveDisplay {
68
+ constructor(totalCount) {
69
+ this.totalCount = totalCount;
70
+ this.numberWidth = Math.max(2, String(totalCount).length);
71
+ this.rows = Array.from({ length: totalCount }, () => ({
72
+ email: "-",
73
+ step: 0,
74
+ status: "Queued",
75
+ done: false,
76
+ success: false,
77
+ }));
78
+ this.rendered = false;
79
+ this._pendingRender = false;
80
+ this.startTime = Date.now();
81
+ }
82
+
83
+ updateRow(rowIdx, data) {
84
+ Object.assign(this.rows[rowIdx], data);
85
+ if (!this._pendingRender) {
86
+ this._pendingRender = true;
87
+ queueMicrotask(() => {
88
+ this._pendingRender = false;
89
+ this._render();
90
+ });
91
+ }
92
+ }
93
+
94
+ _renderStatsLine() {
95
+ const elapsed = this._getElapsed();
96
+ const done = this.rows.filter((r) => r.done).length;
97
+ const success = this.rows.filter((r) => r.done && r.success).length;
98
+ const failed = this.rows.filter((r) => r.done && !r.success).length;
99
+ const active = this.rows.filter((r) => !r.done && r.step > 0).length;
100
+
101
+ return (
102
+ chalk.hex("#00D4AA")(" │ ") +
103
+ chalk.gray("⏱ ") +
104
+ chalk.white(elapsed) +
105
+ " " +
106
+ chalk.hex("#00D4AA")("✓ ") +
107
+ chalk.hex("#00D4AA")(success) +
108
+ " " +
109
+ chalk.red("✗ ") +
110
+ chalk.red(failed) +
111
+ " " +
112
+ chalk.hex("#FFD700")("⟳ ") +
113
+ chalk.hex("#FFD700")(active) +
114
+ " " +
115
+ chalk.gray(`[${done}/${this.totalCount}]`)
116
+ );
117
+ }
118
+
119
+ _getElapsed() {
120
+ const ms = Date.now() - this.startTime;
121
+ const s = Math.floor(ms / 1000) % 60;
122
+ const m = Math.floor(ms / 60000);
123
+ return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
124
+ }
125
+
126
+ _render() {
127
+ // Layout: 1 stats line + 1 blank line + N rows = 2 + totalCount
128
+ const totalLines = 2 + this.totalCount;
129
+
130
+ if (this.rendered) {
131
+ process.stdout.write(`\x1B[${totalLines}A`);
132
+ }
133
+
134
+ // Stats line
135
+ process.stdout.write(`\x1B[2K${this._renderStatsLine()}\n`);
136
+
137
+ // Blank separator
138
+ process.stdout.write(`\x1B[2K\n`);
139
+
140
+ // Render rows
141
+ for (let i = 0; i < this.totalCount; i++) {
142
+ const s = this.rows[i];
143
+ const num = String(i + 1).padStart(this.numberWidth, "0");
144
+ const icon = getIcon(s);
145
+ const emailStr = truncate(s.email, EMAIL_WIDTH);
146
+ const bar = renderBar(s.step, TOTAL_STEPS);
147
+ const statusStr = truncate(s.status, STATUS_WIDTH);
148
+
149
+ let emailColor, statusColor;
150
+ if (s.done && s.success) {
151
+ emailColor = chalk.hex("#00D4AA");
152
+ statusColor = chalk.hex("#00D4AA");
153
+ } else if (s.done && !s.success) {
154
+ emailColor = chalk.red;
155
+ statusColor = chalk.red;
156
+ } else if (s.step > 0) {
157
+ emailColor = chalk.white.bold;
158
+ statusColor = chalk.gray;
159
+ } else {
160
+ emailColor = chalk.gray;
161
+ statusColor = chalk.gray.dim;
162
+ }
163
+
164
+ process.stdout.write(
165
+ `\x1B[2K ${chalk.gray(num + ".")} ${icon} ${emailColor(emailStr)} ${chalk.gray("│")} ${bar} ${chalk.gray("│")} ${statusColor(statusStr)}\n`,
166
+ );
167
+ }
168
+
169
+ this.rendered = true;
170
+ }
171
+
172
+ initialRender() {
173
+ this._render();
174
+ }
175
+ }