scripter-x 1.0.7 → 1.0.9

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scripter-x",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "ScripterX — local Flipkart session extractor (runs on your residential IP, syncs to marthunt)",
5
5
  "type": "module",
6
6
  "bin": {
package/src/commands.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // stdin/stdout). Each command: async (io, api, args) => void.
4
4
  import * as config from './config.js';
5
5
  import { ApiClient, AuthError } from './api.js';
6
- import { saveSessions } from './util.js';
6
+ import { saveSessions, CANCEL } from './util.js';
7
7
  import * as tp from './providers/tempotp.js';
8
8
  import * as oc from './providers/otpcart.js';
9
9
  import * as kuku from './providers/kuku.js';
@@ -122,6 +122,12 @@ async function buildProvider(io, name) {
122
122
  }
123
123
 
124
124
  export async function run(io, api, args = {}) {
125
+ // TARGET: Flipkart (the backend campaign flow) or Zepto (local OTP → ZAUTH1 envelope).
126
+ const target = args.target || (args.number ? 'zepto' : (await io.select('what to extract?', [
127
+ { label: 'Flipkart', value: 'flipkart', description: 'session JSON via OTP provider (campaign)' },
128
+ { label: 'Zepto', value: 'zepto', description: 'local OTP login → ZAUTH1 envelope for AuthManager' }])).value);
129
+ if (target === 'zepto') return zeptoCmd(io, api, args);
130
+
125
131
  api = api || await getApi(io);
126
132
 
127
133
  // MODE: extract JSON only, or also link a newaddr.com email to each account.
@@ -204,44 +210,78 @@ export async function run(io, api, args = {}) {
204
210
  if (worker.logPath) io.print(` ◉ log saved to: ${worker.logPath}`);
205
211
  }
206
212
 
207
- // ── zepto: local OTP login → extract session to {phone}-{timestamp}.json ──
213
+ // ── zepto: local OTP login → extract session to a ZAUTH1 envelope file ──
208
214
  // Dead-simple, no backend: enter a number → we SMS an OTP locally → enter the
209
- // code → we verify and write the session JSON. Runs entirely on your IP.
215
+ // code → we verify, build the ZAUTH1 envelope, and write {phone}-{timestamp}.txt.
216
+ // LOOPS: after each one it asks for the next number — keep going until the user
217
+ // presses Esc (or double Ctrl+C). Runs entirely on your IP.
210
218
  export async function zeptoCmd(io, _api, args = {}) {
211
- const number = (args.number || await io.ask('zepto phone number')).replace(/\D/g, '').slice(-10);
212
- if (number.length !== 10) throw new Error('enter a 10-digit number');
213
-
214
- const session = zepto.newSession(number);
215
- io.print(` ◉ sending OTP to ${number} …`);
216
- const s = await zepto.sendOtp(session);
217
- if (!s.ok) throw new Error(`could not send OTP: ${s.msg || s.status}`);
218
- io.print(' ✓ OTP sent');
219
-
220
- const otp = (args.otp || await io.ask('enter the OTP')).trim();
221
- const v = await zepto.verifyOtp(session, otp);
222
- if (!v.ok) throw new Error(`OTP verify failed: ${v.error || v.status}`);
223
- io.print(` ✓ logged in${v.user?.fullName ? ` as ${v.user.fullName}` : ''}`);
224
-
225
- // Build the ZAUTH1 envelope — the format the ZeptoAuthManager tool imports.
226
- const envelope = zepto.encodeEnvelope(zepto.toSessionKeys(session), args.cert);
227
-
228
- // {phone}-{timestamp}.txt in the chosen dir (default ~/Downloads/scripterx/zepto)
229
- const stamp = new Date().toISOString().replace(/[:.]/g, '-');
230
- const fname = `${number}-${stamp}.txt`;
231
- let dest;
232
- if (args.out) {
233
- const expanded = args.out.startsWith('~') ? join(homedir(), args.out.slice(1)) : args.out;
234
- dest = /\.(txt|json)$/i.test(expanded) ? expanded : join(expanded, fname);
235
- } else {
236
- const downloads = join(homedir(), 'Downloads');
237
- dest = join(existsSync(downloads) ? downloads : homedir(), 'scripterx', 'zepto', fname);
219
+ io.print(' ◉ Zepto local OTP ZAUTH1 envelope');
220
+ io.print(' ◉ press esc to cancel', 'accent');
221
+
222
+ let done = 0;
223
+ // double-Ctrl+C to exit (only meaningful in one-shot mode; the ink App owns its own Ctrl+C)
224
+ let lastSig = 0, stopping = false;
225
+ const onSigint = () => {
226
+ const now = Date.now();
227
+ if (now - lastSig < 2000) { stopping = true; process.exit(0); }
228
+ lastSig = now;
229
+ io.print(' press Ctrl+C again to exit');
230
+ };
231
+ const interactive = !!io.startRun; // ink App present don't grab SIGINT
232
+ if (!interactive) process.on('SIGINT', onSigint);
233
+
234
+ try {
235
+ for (;;) {
236
+ // number entry escapable: Esc stops the whole loop
237
+ const raw = args.number || await io.ask('zepto phone number', { escapable: true });
238
+ if (raw === CANCEL) break;
239
+ const number = String(raw).replace(/\D/g, '').slice(-10);
240
+ if (number.length !== 10) { io.print(' ! enter a 10-digit number', 'danger'); if (args.number) break; continue; }
241
+
242
+ const session = zepto.newSession(number);
243
+ io.print(` sending OTP to ${number} …`);
244
+ const s = await zepto.sendOtp(session);
245
+ if (!s.ok) { io.print(` ✗ could not send OTP: ${s.msg || s.status}`, 'danger'); if (args.number) break; continue; }
246
+ io.print(' ✓ OTP sent');
247
+
248
+ // OTP entry — escapable too (Esc abandons this number and loops back)
249
+ const otpRaw = args.otp || await io.ask('enter the OTP', { escapable: true });
250
+ if (otpRaw === CANCEL) break;
251
+ const v = await zepto.verifyOtp(session, String(otpRaw).trim());
252
+ if (!v.ok) { io.print(` ✗ OTP verify failed: ${v.error || v.status}`, 'danger'); if (args.number) break; continue; }
253
+ io.print(` ✓ logged in${v.user?.fullName ? ` as ${v.user.fullName}` : ''}`);
254
+
255
+ // Build the ZAUTH1 envelope — the format the ZeptoAuthManager tool imports.
256
+ const envelope = zepto.encodeEnvelope(zepto.toSessionKeys(session), args.cert);
257
+
258
+ // {phone}-{timestamp}.txt in the chosen dir (default ~/Downloads/scripterx/zepto)
259
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-');
260
+ const fname = `${number}-${stamp}.txt`;
261
+ let dest;
262
+ if (args.out) {
263
+ const expanded = args.out.startsWith('~') ? join(homedir(), args.out.slice(1)) : args.out;
264
+ dest = /\.(txt|json)$/i.test(expanded) ? expanded : join(expanded, fname);
265
+ } else {
266
+ const downloads = join(homedir(), 'Downloads');
267
+ dest = join(existsSync(downloads) ? downloads : homedir(), 'scripterx', 'zepto', fname);
268
+ }
269
+ mkdirSync(dirname(dest), { recursive: true });
270
+ writeFileSync(dest, envelope + '\n');
271
+ done++;
272
+ io.print(' ✓ envelope saved to:');
273
+ io.print(` ${dest}`, 'accent');
274
+ io.print(' ◉ paste into ZeptoAuthManager → Import:');
275
+ io.print(` ${envelope}`, 'accent');
276
+
277
+ // one-shot (a number was passed on the CLI): do exactly one and stop
278
+ if (args.number) break;
279
+ io.print(' ◉ next number (esc to finish)', 'accent');
280
+ }
281
+ } finally {
282
+ if (!interactive && !stopping) process.off('SIGINT', onSigint);
238
283
  }
239
- mkdirSync(dirname(dest), { recursive: true });
240
- writeFileSync(dest, envelope + '\n');
241
- io.print(' ✓ envelope saved to:');
242
- io.print(` ${dest}`, 'accent');
243
- io.print(' ◉ paste into ZeptoAuthManager → Import:');
244
- io.print(` ${envelope}`, 'accent');
284
+ io.print(` ✓ done ${done} envelope(s) saved.`);
245
285
  }
246
286
 
247
287
  export async function campaigns(io, api) {
package/src/index.js CHANGED
@@ -26,7 +26,7 @@ const cliIo = {
26
26
  else if (line.includes('◉')) console.log(line.replace('◉', paint.accent('◉')));
27
27
  else console.log(line);
28
28
  },
29
- ask: (msg, opts = {}) => (opts.mask ? askSecret(msg) : ask(msg, { dflt: opts.dflt })),
29
+ ask: (msg, opts = {}) => (opts.mask ? askSecret(msg) : ask(msg, { dflt: opts.dflt, escapable: opts.escapable })),
30
30
  confirm: (msg, dflt) => confirm(msg, { dflt }),
31
31
  select: async (msg, items) => {
32
32
  const norm = items.map((it) => (typeof it === 'string' ? { label: it, value: it } : it));
@@ -106,6 +106,7 @@ async function main() {
106
106
  concurrency: flags.concurrency ? +flags.concurrency : null, name: flags.name,
107
107
  checkMinutes: flags['check-minutes'] === true ? true : (flags['check-minutes'] === false ? false : null),
108
108
  mode: flags.email ? 'json_email' : flags.mode, // --email or --mode json_email
109
+ target: flags.target, // run: 'flipkart' | 'zepto'
109
110
  number: flags.number, otp: flags.otp, cert: flags.cert, // zepto: local OTP login → ZAUTH1 envelope
110
111
  campaign: flags._[0], out: flags.out, action: flags._[0], value: flags._[1],
111
112
  };
package/src/prompt.js CHANGED
@@ -1,18 +1,29 @@
1
1
  // Minimal interactive prompts (readline) for the non-ink screens (login, run setup).
2
2
  import readline from 'node:readline';
3
- import { paint } from './util.js';
3
+ import { paint, CANCEL } from './util.js';
4
4
 
5
5
  function makeRl() {
6
6
  return readline.createInterface({ input: process.stdin, output: process.stdout });
7
7
  }
8
8
 
9
- export function ask(question, { dflt } = {}) {
9
+ // ask() single-line input. With { escapable: true }, pressing Esc resolves CANCEL.
10
+ export function ask(question, { dflt, escapable } = {}) {
10
11
  const rl = makeRl();
11
12
  const suffix = dflt != null ? paint.dim(` (${dflt})`) : '';
12
13
  return new Promise((resolve) => {
13
- rl.question(` ${paint.bold(question)}${suffix}: `, (a) => {
14
+ let onKey = null;
15
+ const finish = (val) => {
16
+ if (onKey && process.stdin.off) process.stdin.off('keypress', onKey);
14
17
  rl.close();
15
- resolve(a.trim() || (dflt != null ? String(dflt) : ''));
18
+ resolve(val);
19
+ };
20
+ if (escapable && process.stdin.isTTY) {
21
+ readline.emitKeypressEvents(process.stdin, rl);
22
+ onKey = (_str, key) => { if (key && key.name === 'escape') { process.stdout.write('\n'); finish(CANCEL); } };
23
+ process.stdin.on('keypress', onKey);
24
+ }
25
+ rl.question(` ${paint.bold(question)}${suffix}: `, (a) => {
26
+ finish(a.trim() || (dflt != null ? String(dflt) : ''));
16
27
  });
17
28
  });
18
29
  }
package/src/ui/App.js CHANGED
@@ -12,6 +12,7 @@ import { Shell, WelcomeBox } from './Shell.js';
12
12
  import { SelectList, Confirm, TextField } from './components.js';
13
13
  import { RunView } from './RunView.js';
14
14
  import { COLORS } from '../theme.js';
15
+ import { CANCEL } from '../util.js';
15
16
  import { useMouse } from './mouse.js';
16
17
 
17
18
  const h = React.createElement;
@@ -91,7 +92,9 @@ export function App({ ctx }) {
91
92
  ask: (message, opts = {}) => new Promise((resolve) => {
92
93
  setScreen('prompts');
93
94
  setPrompt({ kind: opts.mask ? 'secret' : 'text', message, mask: opts.mask, dflt: opts.dflt || '',
94
- done: (v) => { setPrompt(null); setScreen(afterPrompt()); resolve(v || opts.dflt || ''); } });
95
+ escapable: !!opts.escapable,
96
+ done: (v) => { setPrompt(null); setScreen(afterPrompt()); resolve(v || opts.dflt || ''); },
97
+ cancel: () => { setPrompt(null); setScreen(afterPrompt()); resolve(CANCEL); } });
95
98
  }),
96
99
  select: (message, items, opts = {}) => new Promise((resolve) => {
97
100
  const norm = items.map((it) => (typeof it === 'string' ? { label: it, value: it } : it));
@@ -166,5 +169,6 @@ function PromptView({ prompt, frameRef }) {
166
169
  return h(Confirm, { message: prompt.message, defaultValue: prompt.dflt, frameRef, onAnswer: (v) => prompt.done(v) });
167
170
  }
168
171
  return h(TextField, { message: prompt.message, mask: prompt.kind === 'secret', defaultValue: prompt.dflt,
169
- onSubmit: (v) => prompt.done(v) });
172
+ onSubmit: (v) => prompt.done(v),
173
+ onCancel: prompt.escapable ? () => prompt.cancel() : undefined });
170
174
  }
package/src/util.js CHANGED
@@ -4,6 +4,9 @@ import { join, dirname } from 'node:path';
4
4
  import { mkdirSync, writeFileSync, existsSync, statSync } from 'node:fs';
5
5
  import { COLORS } from './theme.js';
6
6
 
7
+ // Sentinel returned by io.ask({ escapable: true }) when the user presses Esc.
8
+ export const CANCEL = Symbol('cancel');
9
+
7
10
  // minimal ANSI for the non-TUI commands (login, lists, etc.)
8
11
  const c = (hex, s) => {
9
12
  // map our hex accents to the nearest 256-color for plain stdout