scripter-x 1.0.24 → 1.0.25

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/flipkart.js +50 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scripter-x",
3
- "version": "1.0.24",
3
+ "version": "1.0.25",
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/flipkart.js CHANGED
@@ -85,6 +85,7 @@ export class FlipkartLogin {
85
85
  this.reqId = null;
86
86
  this.sn = '';
87
87
  this.vid = '';
88
+ this.isNewUser = false; // set by sendOtp: new number → verify as SIGNUP, not LOGIN
88
89
  }
89
90
 
90
91
  async sendOtp(mobile) {
@@ -117,37 +118,78 @@ export class FlipkartLogin {
117
118
  // no requestId on a 200 = soft anti-bot throttle — also retryable (often clears on retry).
118
119
  if (!rid) throw new TransientError('blocked while sending OTP (no requestId — throttled)');
119
120
  this.reqId = rid;
121
+ // NEW vs EXISTING account: for an UNREGISTERED number FK still sends the OTP but
122
+ // routes the flow to signup. The reliable signals (verified against captures):
123
+ // • landingPageAction.url starts with "/signup" (existing accounts: no such url)
124
+ // • eVar79 tracking is "New|…|Sign-Up|…" (existing: "Existing|…|Sign-In|…")
125
+ // NOTE: a bare "isNewUser":true appears even for EXISTING accounts (unrelated page
126
+ // flag) — do NOT use it. verifyOtp uses type:SIGNUP when isNewUser, else LOGIN.
127
+ const landingUrl = data?.RESPONSE?.actionResponseContext?.landingPageAction?.url || '';
128
+ const eVar79 = data?.RESPONSE?.pageResponse?.pageData?.trackingContext?.tracking?.eVar79 || '';
129
+ this.isNewUser = /^\/signup\b/.test(landingUrl)
130
+ || /\bSign-?Up\b/i.test(eVar79)
131
+ || /^New\b/i.test(eVar79);
120
132
  this.sn = this.jar.SN || '';
121
133
  this.vid = this.sn ? this.sn.split('.')[0] : '';
122
134
  return rid;
123
135
  }
124
136
 
125
- async verifyOtp(mobile, otp) {
137
+ // Low-level OTP verify with an explicit action type ("LOGIN" for existing
138
+ // accounts, "SIGNUP" for brand-new numbers). Returns { res, text, data }.
139
+ async _verifyWith(type, mobile, otp) {
126
140
  await throttle.wait();
127
141
  const secureToken = this.vid ? `${this.vid}:${this.vid}` : '';
128
142
  const body = { actionRequestContext: {
129
- type: 'LOGIN', loginIdPrefix: '+91', loginId: mobile.slice(-10), password: null,
143
+ type, loginIdPrefix: '+91', loginId: mobile.slice(-10), password: null,
130
144
  otp, otpRequestId: this.reqId, remainingAttempts: 5, phoneNumberFormat: 'E164',
131
- loginType: 'MOBILE', verificationType: 'OTP', sourceContext: 'GO_LOGIN', churned: false,
132
- otpRegex: null, data: null, clientQueryParamMap: null } };
145
+ loginType: 'MOBILE', verificationType: 'OTP', sourceContext: type === 'SIGNUP' ? 'DEFAULT' : 'GO_LOGIN',
146
+ churned: false, otpRegex: null, data: null, clientQueryParamMap: null } };
133
147
  const res = await fetch(`${NATIVE_HOST}/1/action/view`, {
134
148
  method: 'POST', body: JSON.stringify(body),
135
149
  headers: {
136
150
  'User-Agent': 'okhttp/4.9.2', 'X-User-Agent': nativeUa(this.vid),
137
151
  'Content-Type': 'application/json; charset=UTF-8',
138
- 'x-request-metaInfo': '{"actionType":"LOGIN","pageUri":"questContext"}',
152
+ 'x-request-metaInfo': `{"actionType":"${type}","pageUri":"questContext"}`,
139
153
  at: this.jar.at || '', sn: this.sn, secureToken, secureCookie: this.jar.S || '',
140
154
  Cookie: cookieHeader(this.jar),
141
155
  },
142
156
  });
143
157
  const text = await res.text();
158
+ let data = null;
159
+ try { data = JSON.parse(text); } catch { /* caller handles */ }
160
+ return { res, text, data };
161
+ }
162
+
163
+ async verifyOtp(mobile, otp) {
164
+ // New number → SIGNUP (FK rejects LOGIN with "account does not exist"); existing → LOGIN.
165
+ const primary = this.isNewUser ? 'SIGNUP' : 'LOGIN';
166
+ const fallback = this.isNewUser ? 'LOGIN' : 'SIGNUP';
167
+
168
+ let { res, text, data } = await this._verifyWith(primary, mobile, otp);
144
169
  // 529/5xx = Flipkart overloaded / soft-blocking this IP — NOT the OTP's fault.
145
170
  if (res.status === 429 || res.status === 529 || res.status >= 500) {
146
171
  throw new TransientError(`Flipkart busy (HTTP ${res.status}) — will retry`);
147
172
  }
148
- let data;
149
- try { data = JSON.parse(text); } catch { throw new TransientError('Flipkart returned a non-JSON response — will retry'); }
150
- const env = data.SESSION;
173
+ if (!data) throw new TransientError('Flipkart returned a non-JSON response — will retry');
174
+
175
+ let env = data.SESSION;
176
+ // If the primary type didn't log in AND the failure looks like a wrong account-type
177
+ // (not a wrong OTP), retry once with the other type. This auto-creates a new account
178
+ // when a number we thought existed doesn't (and vice-versa) — same OTP, no extra SMS.
179
+ if ((!env || !env.isLoggedIn)) {
180
+ const fkMsg0 = flipkartErrorMessage(data, text);
181
+ const low0 = (fkMsg0 + ' ' + text).toLowerCase();
182
+ const wrongType = /does not exist|not registered|no account|sign\s*up|signup|already (registered|exists)|please login/.test(low0);
183
+ const wrongOtp = /incorrect|wrong|invalid|expired|otp.*not.*match|verification code/.test(low0);
184
+ if (wrongType && !wrongOtp) {
185
+ const r2 = await this._verifyWith(fallback, mobile, otp);
186
+ if (r2.data && r2.data.SESSION && r2.data.SESSION.isLoggedIn) {
187
+ ({ res, text, data } = r2);
188
+ env = data.SESSION;
189
+ }
190
+ }
191
+ }
192
+
151
193
  if (!env || !env.isLoggedIn) {
152
194
  // pull Flipkart's own human message if present (the real reason)
153
195
  const fkMsg = flipkartErrorMessage(data, text);