scripter-x 1.0.8 → 1.0.10
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 +82 -37
- package/src/index.js +1 -1
- package/src/prompt.js +15 -4
- package/src/providers/tempotp.js +4 -1
- package/src/ui/App.js +6 -2
- package/src/util.js +3 -0
package/package.json
CHANGED
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';
|
|
@@ -96,7 +96,11 @@ async function buildProvider(io, name) {
|
|
|
96
96
|
io.print(` ✓ TempOTP balance: ₹${bal}`);
|
|
97
97
|
if (freshlyEntered && await io.confirm('save this key locally for next time?', true)) config.setMany({ tempotp_api_key: key });
|
|
98
98
|
|
|
99
|
-
const svc = await io.select('service id', Object.keys(tp.SERVICES).map((id) => ({
|
|
99
|
+
const svc = await io.select('service id', Object.keys(tp.SERVICES).map((id) => ({
|
|
100
|
+
label: tp.SERVICE_NAMES?.[id] ? `${id} · ${tp.SERVICE_NAMES[id]}` : id,
|
|
101
|
+
value: id,
|
|
102
|
+
description: `₹${tp.SERVICES[id]}`,
|
|
103
|
+
})));
|
|
100
104
|
return new tp.TempOTPProvider(key, svc.value || svc);
|
|
101
105
|
}
|
|
102
106
|
|
|
@@ -210,44 +214,85 @@ export async function run(io, api, args = {}) {
|
|
|
210
214
|
if (worker.logPath) io.print(` ◉ log saved to: ${worker.logPath}`);
|
|
211
215
|
}
|
|
212
216
|
|
|
213
|
-
// ── zepto: local OTP login → extract session to
|
|
217
|
+
// ── zepto: local OTP login → extract session to a ZAUTH1 envelope file ──
|
|
214
218
|
// Dead-simple, no backend: enter a number → we SMS an OTP locally → enter the
|
|
215
|
-
// code → we verify
|
|
219
|
+
// code → we verify, build the ZAUTH1 envelope, and write {phone}-{timestamp}.txt.
|
|
220
|
+
// LOOPS: after each one it asks for the next number — keep going until the user
|
|
221
|
+
// presses Esc (or double Ctrl+C). Runs entirely on your IP.
|
|
216
222
|
export async function zeptoCmd(io, _api, args = {}) {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
io.
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
if (args.
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
223
|
+
io.print(' ◉ Zepto — local OTP → ZAUTH1 envelope');
|
|
224
|
+
io.print(' ◉ press esc to cancel', 'accent');
|
|
225
|
+
|
|
226
|
+
let done = 0;
|
|
227
|
+
// double-Ctrl+C to exit (only meaningful in one-shot mode; the ink App owns its own Ctrl+C)
|
|
228
|
+
let lastSig = 0, stopping = false;
|
|
229
|
+
const onSigint = () => {
|
|
230
|
+
const now = Date.now();
|
|
231
|
+
if (now - lastSig < 2000) { stopping = true; process.exit(0); }
|
|
232
|
+
lastSig = now;
|
|
233
|
+
io.print(' ⚠ press Ctrl+C again to exit');
|
|
234
|
+
};
|
|
235
|
+
const interactive = !!io.startRun; // ink App present → don't grab SIGINT
|
|
236
|
+
if (!interactive) process.on('SIGINT', onSigint);
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
for (;;) {
|
|
240
|
+
// number entry — escapable: Esc stops the whole loop
|
|
241
|
+
const raw = args.number || await io.ask('zepto phone number', { escapable: true });
|
|
242
|
+
if (raw === CANCEL) break;
|
|
243
|
+
const number = String(raw).replace(/\D/g, '').slice(-10);
|
|
244
|
+
if (number.length !== 10) { io.print(' ! enter a 10-digit number', 'danger'); if (args.number) break; continue; }
|
|
245
|
+
|
|
246
|
+
try {
|
|
247
|
+
const session = zepto.newSession(number);
|
|
248
|
+
io.print(` ◉ sending OTP to ${number} …`);
|
|
249
|
+
const s = await zepto.sendOtp(session);
|
|
250
|
+
if (!s.ok) { io.print(` ✗ could not send OTP: ${s.msg || s.status}`, 'danger'); if (args.number) break; continue; }
|
|
251
|
+
io.print(' ✓ OTP sent');
|
|
252
|
+
|
|
253
|
+
// OTP entry — escapable too (Esc abandons this number and loops back)
|
|
254
|
+
const otpRaw = args.otp || await io.ask('enter the OTP', { escapable: true });
|
|
255
|
+
if (otpRaw === CANCEL) break;
|
|
256
|
+
const v = await zepto.verifyOtp(session, String(otpRaw).trim());
|
|
257
|
+
if (!v.ok) { io.print(` ✗ OTP verify failed: ${v.error || v.status}`, 'danger'); if (args.number) break; continue; }
|
|
258
|
+
io.print(` ✓ logged in${v.user?.fullName ? ` as ${v.user.fullName}` : ''}`);
|
|
259
|
+
|
|
260
|
+
// Build the ZAUTH1 envelope — the format the ZeptoAuthManager tool imports.
|
|
261
|
+
const envelope = zepto.encodeEnvelope(zepto.toSessionKeys(session), args.cert);
|
|
262
|
+
|
|
263
|
+
// {phone}-{timestamp}.txt in the chosen dir (default ~/Downloads/scripterx/zepto)
|
|
264
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
265
|
+
const fname = `${number}-${stamp}.txt`;
|
|
266
|
+
let dest;
|
|
267
|
+
if (args.out) {
|
|
268
|
+
const expanded = args.out.startsWith('~') ? join(homedir(), args.out.slice(1)) : args.out;
|
|
269
|
+
dest = /\.(txt|json)$/i.test(expanded) ? expanded : join(expanded, fname);
|
|
270
|
+
} else {
|
|
271
|
+
const downloads = join(homedir(), 'Downloads');
|
|
272
|
+
dest = join(existsSync(downloads) ? downloads : homedir(), 'scripterx', 'zepto', fname);
|
|
273
|
+
}
|
|
274
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
275
|
+
writeFileSync(dest, envelope + '\n');
|
|
276
|
+
done++;
|
|
277
|
+
io.print(' ✓ envelope saved to:');
|
|
278
|
+
io.print(` ${dest}`, 'accent');
|
|
279
|
+
io.print(' ◉ paste into ZeptoAuthManager → Import:');
|
|
280
|
+
io.print(` ${envelope}`, 'accent');
|
|
281
|
+
} catch (e) {
|
|
282
|
+
// never let one bad number kill the loop — surface the real reason and keep going
|
|
283
|
+
io.print(` ✗ ${number}: ${e.message}`, 'danger');
|
|
284
|
+
if (args.number) break;
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// one-shot (a number was passed on the CLI): do exactly one and stop
|
|
289
|
+
if (args.number) break;
|
|
290
|
+
io.print(' ◉ next number (esc to finish)', 'accent');
|
|
291
|
+
}
|
|
292
|
+
} finally {
|
|
293
|
+
if (!interactive && !stopping) process.off('SIGINT', onSigint);
|
|
244
294
|
}
|
|
245
|
-
|
|
246
|
-
writeFileSync(dest, envelope + '\n');
|
|
247
|
-
io.print(' ✓ envelope saved to:');
|
|
248
|
-
io.print(` ${dest}`, 'accent');
|
|
249
|
-
io.print(' ◉ paste into ZeptoAuthManager → Import:');
|
|
250
|
-
io.print(` ${envelope}`, 'accent');
|
|
295
|
+
io.print(` ✓ done — ${done} envelope(s) saved.`);
|
|
251
296
|
}
|
|
252
297
|
|
|
253
298
|
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));
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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/providers/tempotp.js
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
import { extractOtp } from '../flipkart.js';
|
|
3
3
|
|
|
4
4
|
const BASE = 'https://api.tempotp.online';
|
|
5
|
-
|
|
5
|
+
// serviceId -> cost (₹). Keep ids as strings (they go straight into the GET params).
|
|
6
|
+
export const SERVICES = { '1040': 10.0, '940': 16.0, '2451': 12.5 };
|
|
7
|
+
// optional friendly names shown in the service picker
|
|
8
|
+
export const SERVICE_NAMES = { '1040': 'Flipkart', '940': 'Flipkart', '2451': 'Shopsy/Flipkart 1 (SERVER 16)' };
|
|
6
9
|
export const DEFAULT_SERVICE = '940';
|
|
7
10
|
const COUNTRY = '22';
|
|
8
11
|
|
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
|
-
|
|
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
|