scripter-x 1.0.26 β 1.0.27
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/package.json +1 -1
- package/src/ui/RunView.js +27 -6
- package/src/util.js +29 -14
- package/src/worker.js +27 -5
package/package.json
CHANGED
package/src/ui/RunView.js
CHANGED
|
@@ -30,14 +30,35 @@ function SlotRow({ slot, mobile, phase, detail, wait }) {
|
|
|
30
30
|
);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
// Map a result to a clean { glyph, label, color, note } for display. Driven by the
|
|
34
|
+
// worker's `category` so every outcome reads consistently (no "successed"/"success" mix).
|
|
35
|
+
function resultDisplay({ status, category, detail }) {
|
|
36
|
+
const C = COLORS;
|
|
37
|
+
switch (category) {
|
|
38
|
+
case 'fresh': return { glyph: 'π', label: 'fresh account', color: C.accent, note: 'new account created' };
|
|
39
|
+
case 'coupon': return { glyph: 'β', label: 'success', color: C.success, note: 'βΉ100 coupon available' };
|
|
40
|
+
case 'no_coupon': return { glyph: 'β', label: 'success', color: C.success, note: 'no βΉ100 coupon' };
|
|
41
|
+
case 'extracted': return { glyph: 'β', label: 'success', color: C.success, note: 'extracted' };
|
|
42
|
+
case 'timeout': return { glyph: 'β±', label: 'timeout', color: C.warn, note: 'no OTP in 90s' };
|
|
43
|
+
case 'cancelled_user': return { glyph: 'β', label: 'cancelled', color: C.warn, note: 'cancelled by user' };
|
|
44
|
+
case 'wrong_otp': return { glyph: 'β', label: 'failed', color: C.danger, note: 'wrong OTP code' };
|
|
45
|
+
case 'goal_reached': return { glyph: 'β’', label: 'skipped', color: C.muted, note: 'goal reached' };
|
|
46
|
+
case 'failed': return { glyph: 'β', label: 'failed', color: C.danger, note: detail || 'failed' };
|
|
47
|
+
default: {
|
|
48
|
+
const st = STATUS[status] || STATUS.pending;
|
|
49
|
+
const note = status === 'success' ? 'extracted' : (detail || '');
|
|
50
|
+
return { glyph: st.glyph, label: status, color: st.color, note };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function ResultRow({ status, category, mobile, detail, cost }) {
|
|
56
|
+
const d = resultDisplay({ status, category, detail });
|
|
35
57
|
const mob = (mobile || 'β').slice(-10) || 'β';
|
|
36
|
-
const text = status === 'success' ? (coupon ? 'βΉ100 coupon' : 'extracted') : (detail || '');
|
|
37
58
|
return h(Box, null,
|
|
38
59
|
h(Box, { width: 13 }, h(Text, null, mob)),
|
|
39
|
-
h(Box, { width:
|
|
40
|
-
h(Box, { flexGrow: 1 }, h(Text, { color: 'gray' },
|
|
60
|
+
h(Box, { width: 14 }, h(Text, { color: d.color }, `${d.glyph} ${d.label}`)),
|
|
61
|
+
h(Box, { flexGrow: 1 }, h(Text, { color: 'gray' }, d.note)),
|
|
41
62
|
h(Box, { width: 7, justifyContent: 'flex-end' }, h(Text, { color: 'gray' }, cost ? `βΉ${cost}` : '')),
|
|
42
63
|
);
|
|
43
64
|
}
|
|
@@ -76,7 +97,7 @@ export function RunView({ controller }) {
|
|
|
76
97
|
slotList.length ? slotList.map((sl) => h(SlotRow, { key: sl.slot, ...sl }))
|
|
77
98
|
: h(Text, { color: 'gray' }, 'startingβ¦')),
|
|
78
99
|
h(Panel, { title: 'results' },
|
|
79
|
-
lastRows.length ? lastRows.map((r, i) => h(ResultRow, { key: i, status: r.status,
|
|
100
|
+
lastRows.length ? lastRows.map((r, i) => h(ResultRow, { key: i, status: r.status, category: r.category, mobile: r.mobile, detail: r.detail, cost: r.cost }))
|
|
80
101
|
: h(Text, { color: 'gray' }, 'waiting for first resultβ¦')),
|
|
81
102
|
h(Box, { borderStyle: 'round', borderColor: COLORS.accent, paddingX: 1 },
|
|
82
103
|
h(Text, { color: COLORS.success }, `β ${stats.succeeded}`),
|
package/src/util.js
CHANGED
|
@@ -61,36 +61,51 @@ function writeAccounts(accounts, { name, out, checkMinutes } = {}) {
|
|
|
61
61
|
return dest;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
// Partition
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
// Partition into the THREE buckets the user wants:
|
|
65
|
+
// fresh β brand-new account just created (signup)
|
|
66
|
+
// coupon β existing account with the βΉ100 Minutes coupon available
|
|
67
|
+
// noCoupon β existing account, coupon already used
|
|
68
|
+
// plus `unchecked` (Minutes wasn't checked). Categorisation prefers the explicit
|
|
69
|
+
// `category` set by the worker; falls back to is_new_account / coupon_eligible.
|
|
70
|
+
const fresh = [];
|
|
71
|
+
const coupon = [];
|
|
72
|
+
const noCoupon = [];
|
|
73
|
+
const unchecked = [];
|
|
68
74
|
|
|
69
75
|
let hasChecked = false;
|
|
70
76
|
for (const a of accounts) {
|
|
71
77
|
const sess = a.session;
|
|
72
78
|
if (!sess) continue;
|
|
79
|
+
const cat = a.category;
|
|
73
80
|
const eligible = a.coupon_eligible;
|
|
74
|
-
if (
|
|
81
|
+
if (cat === 'fresh' || a.is_new_account) {
|
|
82
|
+
fresh.push(sess);
|
|
83
|
+
hasChecked = true;
|
|
84
|
+
} else if (cat === 'coupon' || eligible === true) {
|
|
85
|
+
coupon.push(sess);
|
|
86
|
+
hasChecked = true;
|
|
87
|
+
} else if (cat === 'no_coupon' || eligible === false) {
|
|
88
|
+
noCoupon.push(sess);
|
|
75
89
|
hasChecked = true;
|
|
76
|
-
(eligible ? minutesFree : minutesUsed).push(sess);
|
|
77
90
|
} else {
|
|
78
91
|
unchecked.push(sess);
|
|
79
92
|
}
|
|
80
93
|
}
|
|
81
94
|
|
|
82
95
|
const results = [];
|
|
83
|
-
if (hasChecked || (checkMinutes && (
|
|
84
|
-
const
|
|
85
|
-
if (
|
|
86
|
-
const
|
|
87
|
-
if (
|
|
96
|
+
if (hasChecked || (checkMinutes && (fresh.length || coupon.length || noCoupon.length))) {
|
|
97
|
+
const dFresh = write('-fresh-new-account', fresh);
|
|
98
|
+
if (dFresh) results.push({ label: 'π fresh new account', path: dFresh, count: fresh.length });
|
|
99
|
+
const dCoupon = write('-with-100-coupon', coupon);
|
|
100
|
+
if (dCoupon) results.push({ label: 'π’ βΉ100 coupon available', path: dCoupon, count: coupon.length });
|
|
101
|
+
const dNo = write('-no-100-coupon', noCoupon);
|
|
102
|
+
if (dNo) results.push({ label: 'π΄ no βΉ100 coupon', path: dNo, count: noCoupon.length });
|
|
88
103
|
if (unchecked.length) {
|
|
89
|
-
const
|
|
90
|
-
if (
|
|
104
|
+
const dUnk = write('-unchecked', unchecked);
|
|
105
|
+
if (dUnk) results.push({ label: 'βͺ unchecked', path: dUnk, count: unchecked.length });
|
|
91
106
|
}
|
|
92
107
|
} else {
|
|
93
|
-
const allSessions = [...
|
|
108
|
+
const allSessions = [...fresh, ...coupon, ...noCoupon, ...unchecked];
|
|
94
109
|
const dest = write('', allSessions);
|
|
95
110
|
if (dest) results.push({ label: 'combined', path: dest, count: allSessions.length });
|
|
96
111
|
}
|
package/src/worker.js
CHANGED
|
@@ -78,6 +78,8 @@ export class Worker {
|
|
|
78
78
|
if (res.status === 'success' && res.session) {
|
|
79
79
|
this.results.push({
|
|
80
80
|
id_no: res.id_no, mobile: res.mobile || '', session: res.session,
|
|
81
|
+
category: res.category, // 'fresh' | 'coupon' | 'no_coupon' | 'extracted'
|
|
82
|
+
is_new_account: !!res.is_new_account,
|
|
81
83
|
minutes_checked: !!res.minutes_checked,
|
|
82
84
|
coupon_eligible: res.coupon_eligible ?? undefined,
|
|
83
85
|
has_minutes_order: res.has_minutes_order ?? undefined,
|
|
@@ -152,18 +154,25 @@ export class Worker {
|
|
|
152
154
|
this._emit(slot, { phase: 'done', detail: 'goal reached' });
|
|
153
155
|
res.status = 'cancelled';
|
|
154
156
|
res.detail = 'goal reached';
|
|
157
|
+
res.category = 'goal_reached';
|
|
155
158
|
return res;
|
|
156
159
|
}
|
|
157
160
|
|
|
158
161
|
this._emit(slot, { mobile: '', phase: 'renting', detail: 'requesting a number', wait: 0 });
|
|
159
162
|
const number = await this._rent(slot);
|
|
160
|
-
if (!number) {
|
|
163
|
+
if (!number) {
|
|
164
|
+
res.status = 'cancelled';
|
|
165
|
+
res.detail = this.stopped ? 'cancelled by user' : 'stopped while renting';
|
|
166
|
+
res.category = this.stopped ? 'cancelled_user' : 'goal_reached';
|
|
167
|
+
return res;
|
|
168
|
+
}
|
|
161
169
|
if (this.stats.succeeded >= this.requested) {
|
|
162
170
|
this.log(`goal reached by another slot; releasing ${number.mobile} immediately`);
|
|
163
171
|
this._release(number, Date.now(), slot);
|
|
164
172
|
this._emit(slot, { phase: 'done', detail: 'goal reached' });
|
|
165
173
|
res.status = 'cancelled';
|
|
166
174
|
res.detail = 'goal reached';
|
|
175
|
+
res.category = 'goal_reached';
|
|
167
176
|
return res;
|
|
168
177
|
}
|
|
169
178
|
const rentedAt = Date.now();
|
|
@@ -194,11 +203,13 @@ export class Worker {
|
|
|
194
203
|
this._release(number, rentedAt, slot);
|
|
195
204
|
};
|
|
196
205
|
|
|
197
|
-
const fail = (detail) => {
|
|
206
|
+
const fail = (detail, category) => {
|
|
198
207
|
const charged = smsArrived.v;
|
|
199
208
|
res.status = charged ? 'failed' : 'cancelled';
|
|
200
209
|
res.cost = charged ? number.cost : 0;
|
|
201
210
|
res.detail = detail;
|
|
211
|
+
// category: timeout (90s, no OTP), wrong_otp, send_blocked, or generic failed/cancelled
|
|
212
|
+
res.category = category || (res.status === 'failed' ? 'failed' : 'cancelled');
|
|
202
213
|
this.log(`FAILED ${number.mobile}: ${detail} (charged: ${charged})`);
|
|
203
214
|
releaseOnce(charged, 'failed');
|
|
204
215
|
return res;
|
|
@@ -224,13 +235,14 @@ export class Worker {
|
|
|
224
235
|
const deadline = this.provider.name === 'tempotp' ? TEMPOTP_DEADLINE : OTPCART_DEADLINE;
|
|
225
236
|
const doResend = this.provider.name === 'otpcart';
|
|
226
237
|
const otp = await this._awaitOtp(slot, stream, fk, number, deadline, doResend, smsArrived);
|
|
227
|
-
if (!otp) return fail(`no OTP
|
|
238
|
+
if (!otp) return fail(`no OTP in ${deadline / 1000}s (timeout)`, 'timeout');
|
|
228
239
|
|
|
229
240
|
// 3. VERIFY (already retries transients internally via _verify; re-verify on blockage)
|
|
230
241
|
this._emit(slot, { phase: 'verify', detail: 'verifying OTP' });
|
|
231
242
|
const { session, error: verifyErr } = await this._verify(slot, fk, stream, number, otp, smsArrived);
|
|
232
243
|
if (!session) {
|
|
233
|
-
const
|
|
244
|
+
const isWrongOtp = /incorrect|wrong|invalid|expired|code/i.test(verifyErr || '');
|
|
245
|
+
const failed = fail(verifyErr || 'verify failed', isWrongOtp ? 'wrong_otp' : 'failed');
|
|
234
246
|
if (this.onFailure && !this.stopped && !this._asking) {
|
|
235
247
|
this._asking = true;
|
|
236
248
|
try {
|
|
@@ -282,7 +294,17 @@ export class Worker {
|
|
|
282
294
|
res.status = 'success';
|
|
283
295
|
res.cost = number.cost;
|
|
284
296
|
res.session = session;
|
|
285
|
-
|
|
297
|
+
res.is_new_account = !!fk.isNewUser; // a freshly-created (signup) account
|
|
298
|
+
// Derive the result CATEGORY for display + file routing:
|
|
299
|
+
// fresh β brand-new account just created
|
|
300
|
+
// coupon β existing account, βΉ100 Minutes coupon available
|
|
301
|
+
// no_coupon β existing account, coupon already used
|
|
302
|
+
// extracted β success but Minutes wasn't checked
|
|
303
|
+
res.category = res.is_new_account ? 'fresh'
|
|
304
|
+
: (res.minutes_checked ? (res.coupon_eligible ? 'coupon' : 'no_coupon') : 'extracted');
|
|
305
|
+
const doneDetail = res.is_new_account ? 'fresh account'
|
|
306
|
+
: res.minutes_checked ? (res.coupon_eligible ? 'βΉ100 coupon' : 'no coupon')
|
|
307
|
+
: 'extracted';
|
|
286
308
|
this._emit(slot, { phase: 'done', detail: doneDetail });
|
|
287
309
|
releaseOnce(true, doneDetail, true); // success: OTP consumed β no cancel
|
|
288
310
|
return res;
|