scripter-x 1.0.12 → 1.0.14
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/commands.js +12 -2
- package/src/index.js +9 -2
- package/src/ui/App.js +9 -1
- package/src/ui/Shell.js +6 -2
- package/src/util.js +68 -8
- package/src/worker.js +15 -0
package/package.json
CHANGED
package/src/commands.js
CHANGED
|
@@ -208,7 +208,12 @@ export async function run(io, api, args = {}) {
|
|
|
208
208
|
if (a > 0) await new Promise((r) => setTimeout(r, 1000));
|
|
209
209
|
try { saved = await saveSessions(api, cid, { campaignName: name }); } catch (e) { lastErr = e; }
|
|
210
210
|
}
|
|
211
|
-
if (saved) {
|
|
211
|
+
if (saved) {
|
|
212
|
+
io.print(' ✓ saved session(s) to:');
|
|
213
|
+
for (const res of saved) {
|
|
214
|
+
io.print(` ${res.path} (${res.count} sessions)`, 'accent');
|
|
215
|
+
}
|
|
216
|
+
}
|
|
212
217
|
else io.print(` ! couldn't auto-save${lastErr ? ` (${lastErr.message})` : ''} — run \`export ${name}\` to retry`);
|
|
213
218
|
} else io.print(' ◉ no sessions extracted — nothing to save.');
|
|
214
219
|
if (worker.logPath) io.print(` ◉ log saved to: ${worker.logPath}`);
|
|
@@ -329,7 +334,12 @@ export async function exportCmd(io, api, args = {}) {
|
|
|
329
334
|
if (a > 0) await new Promise((r) => setTimeout(r, 800));
|
|
330
335
|
try { saved = await saveSessions(api, c.id, { campaignName: c.name, out: args.out }); } catch (e) { lastErr = e; }
|
|
331
336
|
}
|
|
332
|
-
if (saved) {
|
|
337
|
+
if (saved) {
|
|
338
|
+
io.print(' ✓ exported session(s) to:');
|
|
339
|
+
for (const res of saved) {
|
|
340
|
+
io.print(` ${res.path} (${res.count} sessions)`, 'accent');
|
|
341
|
+
}
|
|
342
|
+
}
|
|
333
343
|
else if (lastErr) io.print(` ✗ ${lastErr.message}`);
|
|
334
344
|
else io.print(' ! no extracted sessions in this campaign yet.');
|
|
335
345
|
}
|
package/src/index.js
CHANGED
|
@@ -9,10 +9,17 @@ import { ApiClient } from './api.js';
|
|
|
9
9
|
import { paint } from './util.js';
|
|
10
10
|
import { ask, askSecret, confirm, askChoice } from './prompt.js';
|
|
11
11
|
import { REGISTRY } from './commands.js';
|
|
12
|
+
import { readFileSync } from 'node:fs';
|
|
12
13
|
import { App } from './ui/App.js';
|
|
13
14
|
import { FullScreen, enterAltScreen } from './ui/fullscreen.js';
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
let VERSION = '1.0.0';
|
|
17
|
+
try {
|
|
18
|
+
const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
19
|
+
VERSION = pkg.version;
|
|
20
|
+
} catch {
|
|
21
|
+
// Fallback if read fails
|
|
22
|
+
}
|
|
16
23
|
const h = React.createElement;
|
|
17
24
|
|
|
18
25
|
// ── one-shot io: plain stdio, for `scripterx <cmd>` ──
|
|
@@ -81,7 +88,7 @@ async function main() {
|
|
|
81
88
|
let updateNotice = null;
|
|
82
89
|
try { const { checkForUpdate } = await import('./update.js'); const v = await checkForUpdate(VERSION); if (v) updateNotice = v; } catch { /* */ }
|
|
83
90
|
const ctx = {
|
|
84
|
-
username, server: cfg.server_base_url, updateNotice,
|
|
91
|
+
username, server: cfg.server_base_url, updateNotice, version: VERSION,
|
|
85
92
|
dispatch: async (name, io) => {
|
|
86
93
|
const fn = REGISTRY[name] || REGISTRY[name.split(' ')[0]];
|
|
87
94
|
if (!fn) { io.print(` ✗ unknown command: ${name} (type 'help')`); return; }
|
package/src/ui/App.js
CHANGED
|
@@ -144,7 +144,15 @@ export function App({ ctx }) {
|
|
|
144
144
|
// Layout (top → bottom): banner · output · prompt/palette. So command output appears
|
|
145
145
|
// BELOW the banner and ABOVE the prompt — never above the banner.
|
|
146
146
|
return h(Box, { flexDirection: 'column', ref: frameRef },
|
|
147
|
-
h(WelcomeBox, {
|
|
147
|
+
h(WelcomeBox, {
|
|
148
|
+
username: me.username,
|
|
149
|
+
server: me.server,
|
|
150
|
+
version: ctx.version,
|
|
151
|
+
updateNotice: ctx.updateNotice,
|
|
152
|
+
onUpdate: () => runCommand('update'),
|
|
153
|
+
frameRef,
|
|
154
|
+
onLogout: () => runCommand('logout')
|
|
155
|
+
}),
|
|
148
156
|
scroll,
|
|
149
157
|
screen === 'run' && runController ? h(RunView, { controller: runController, embedded: true }) : null,
|
|
150
158
|
screen === 'prompts' && prompt ? h(PromptView, { prompt, frameRef }) : null,
|
package/src/ui/Shell.js
CHANGED
|
@@ -28,18 +28,22 @@ export const COMMANDS = [
|
|
|
28
28
|
{ name: 'exit', desc: 'quit ScripterX' },
|
|
29
29
|
];
|
|
30
30
|
|
|
31
|
-
export function WelcomeBox({ username, server, frameRef, onLogout }) {
|
|
31
|
+
export function WelcomeBox({ username, server, version, updateNotice, onUpdate, frameRef, onLogout }) {
|
|
32
32
|
const logo = logoLines();
|
|
33
33
|
return h(Box, { borderStyle: 'round', borderColor: COLORS.accent, paddingX: 2, paddingY: 1, flexDirection: 'column' },
|
|
34
34
|
// ASCII wordmark with a top→bottom gradient
|
|
35
35
|
...logo.map((l, i) => h(Text, { key: i, color: l.color, bold: true }, l.text)),
|
|
36
|
-
h(Text, { color: 'gray' },
|
|
36
|
+
h(Text, { color: 'gray' }, ` Flipkart session extractor${version ? ` (v${version})` : ''} — runs on your residential IP`),
|
|
37
37
|
h(Box, { marginTop: 1 },
|
|
38
38
|
h(Text, { color: username ? COLORS.success : 'gray' }, username ? ' ● ' : ' ○ '),
|
|
39
39
|
h(Text, { color: 'white' }, username || 'not signed in'),
|
|
40
40
|
h(Text, { color: 'gray' }, ` · ${server}`),
|
|
41
41
|
username ? h(Clickable, { frameRef, onClick: onLogout },
|
|
42
42
|
h(Text, { color: COLORS.accent }, ' [ logout ]')) : null),
|
|
43
|
+
updateNotice ? h(Box, { marginTop: 1 },
|
|
44
|
+
h(Clickable, { frameRef, onClick: onUpdate },
|
|
45
|
+
h(Text, { color: COLORS.warn, bold: true }, ` ⬆ update available: v${updateNotice} — click here or type 'update' to upgrade`))
|
|
46
|
+
) : null,
|
|
43
47
|
h(Text, { color: 'gray' }, " type a command, or / to browse all · 1-9 / ↑↓+enter / click to pick · exit to quit"),
|
|
44
48
|
);
|
|
45
49
|
}
|
package/src/util.js
CHANGED
|
@@ -37,21 +37,81 @@ export const log = {
|
|
|
37
37
|
export async function saveSessions(api, cid, { campaignName, out } = {}) {
|
|
38
38
|
let name = (campaignName || cid.slice(0, 8)).replace(/[^A-Za-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '') || cid.slice(0, 8);
|
|
39
39
|
const accounts = await api.exportCampaign(cid);
|
|
40
|
-
|
|
41
|
-
if (!sessions.length) return null;
|
|
40
|
+
if (!accounts || !accounts.length) return null;
|
|
42
41
|
|
|
42
|
+
let checkMinutes = false;
|
|
43
|
+
try {
|
|
44
|
+
const { campaign } = await api.getCampaign(cid);
|
|
45
|
+
checkMinutes = !!campaign.check_minutes;
|
|
46
|
+
} catch (e) {
|
|
47
|
+
// ignore
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Resolve base directory and optional forced filename.
|
|
51
|
+
let baseDir, forcedName = null;
|
|
43
52
|
const defaultName = `${name}-${cid.slice(0, 8)}.json`;
|
|
44
|
-
let dest;
|
|
45
53
|
if (out) {
|
|
46
54
|
const expanded = out.startsWith('~') ? join(homedir(), out.slice(1)) : out;
|
|
47
55
|
const isDir = (existsSync(expanded) && statSync(expanded).isDirectory()) || /[/\\]$/.test(out);
|
|
48
|
-
|
|
56
|
+
if (isDir) {
|
|
57
|
+
baseDir = expanded;
|
|
58
|
+
} else {
|
|
59
|
+
baseDir = dirname(expanded);
|
|
60
|
+
forcedName = expanded.split(/[/\\]/).pop();
|
|
61
|
+
}
|
|
49
62
|
} else {
|
|
50
63
|
const downloads = join(homedir(), 'Downloads');
|
|
51
64
|
const base = existsSync(downloads) ? downloads : homedir();
|
|
52
|
-
|
|
65
|
+
baseDir = join(base, 'scripterx');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function write(suffix, sessionsToWrite) {
|
|
69
|
+
if (!sessionsToWrite || !sessionsToWrite.length) return null;
|
|
70
|
+
let fname;
|
|
71
|
+
if (forcedName) {
|
|
72
|
+
fname = suffix === '' ? forcedName : forcedName.replace(/\.json$/i, `${suffix}.json`);
|
|
73
|
+
} else {
|
|
74
|
+
fname = `${name}-${cid.slice(0, 8)}${suffix}.json`;
|
|
75
|
+
}
|
|
76
|
+
const dest = join(baseDir, fname);
|
|
77
|
+
mkdirSync(baseDir, { recursive: true });
|
|
78
|
+
writeFileSync(dest, JSON.stringify(sessionsToWrite, null, 2));
|
|
79
|
+
return dest;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Partition by coupon_eligible when minutes check was requested.
|
|
83
|
+
const minutesFree = []; // coupon_eligible=true → no Minutes order, ₹100 coupon available
|
|
84
|
+
const minutesUsed = []; // coupon_eligible=false → already placed a Minutes order
|
|
85
|
+
const unchecked = []; // coupon_eligible absent → check wasn't done for this account
|
|
86
|
+
|
|
87
|
+
for (const a of accounts) {
|
|
88
|
+
const sess = a.session;
|
|
89
|
+
if (!sess) continue;
|
|
90
|
+
const eligible = a.coupon_eligible;
|
|
91
|
+
if (checkMinutes && eligible !== undefined && eligible !== null) {
|
|
92
|
+
(eligible ? minutesFree : minutesUsed).push(sess);
|
|
93
|
+
} else {
|
|
94
|
+
unchecked.push(sess);
|
|
95
|
+
}
|
|
53
96
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
97
|
+
|
|
98
|
+
const results = [];
|
|
99
|
+
if (checkMinutes && (minutesFree.length || minutesUsed.length)) {
|
|
100
|
+
const destFree = write('-minutes-free', minutesFree);
|
|
101
|
+
if (destFree) results.push({ label: '🟢 minutes-free (₹100 coupon)', path: destFree, count: minutesFree.length });
|
|
102
|
+
|
|
103
|
+
const destUsed = write('-minutes-used', minutesUsed);
|
|
104
|
+
if (destUsed) results.push({ label: '🔴 minutes-used (coupon gone)', path: destUsed, count: minutesUsed.length });
|
|
105
|
+
|
|
106
|
+
if (unchecked.length) {
|
|
107
|
+
const destUnk = write('-unchecked', unchecked);
|
|
108
|
+
if (destUnk) results.push({ label: '⚪ unchecked', path: destUnk, count: unchecked.length });
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
const allSessions = [...minutesFree, ...minutesUsed, ...unchecked];
|
|
112
|
+
const dest = write('', allSessions);
|
|
113
|
+
if (dest) results.push({ label: 'combined', path: dest, count: allSessions.length });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return results.length ? results : null;
|
|
57
117
|
}
|
package/src/worker.js
CHANGED
|
@@ -48,6 +48,8 @@ export class Worker {
|
|
|
48
48
|
_claim() {
|
|
49
49
|
if (this.stopped) return 0;
|
|
50
50
|
if (this.stats.succeeded >= this.requested) return 0;
|
|
51
|
+
const active = this.stats.attempts - (this.stats.succeeded + this.stats.failed + this.stats.cancelled);
|
|
52
|
+
if (this.stats.succeeded + active >= this.requested) return 0;
|
|
51
53
|
if (this.stats.attempts >= this.requested * ATTEMPT_CAP_MULT) return 0;
|
|
52
54
|
return ++this.stats.attempts;
|
|
53
55
|
}
|
|
@@ -119,9 +121,22 @@ export class Worker {
|
|
|
119
121
|
const idNo = this._nextSeq();
|
|
120
122
|
const res = { id_no: idNo, status: 'failed', cost: 0, detail: '' };
|
|
121
123
|
|
|
124
|
+
if (this.stats.succeeded >= this.requested) {
|
|
125
|
+
res.status = 'cancelled';
|
|
126
|
+
res.detail = 'goal reached';
|
|
127
|
+
return res;
|
|
128
|
+
}
|
|
129
|
+
|
|
122
130
|
this._emit(slot, { mobile: '', phase: 'renting', detail: 'requesting a number', wait: 0 });
|
|
123
131
|
const number = await this._rent(slot);
|
|
124
132
|
if (!number) { res.status = 'cancelled'; res.detail = 'stopped while renting'; return res; }
|
|
133
|
+
if (this.stats.succeeded >= this.requested) {
|
|
134
|
+
this.log(`goal reached by another slot; releasing ${number.mobile} immediately`);
|
|
135
|
+
this._release(number, Date.now());
|
|
136
|
+
res.status = 'cancelled';
|
|
137
|
+
res.detail = 'goal reached';
|
|
138
|
+
return res;
|
|
139
|
+
}
|
|
125
140
|
const rentedAt = Date.now();
|
|
126
141
|
res.mobile = number.mobile;
|
|
127
142
|
this._emit(slot, { mobile: number.mobile, phase: 'rented', detail: 'got a number' });
|