scripter-x 1.0.24 → 1.0.26
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/flipkart.js +111 -31
package/package.json
CHANGED
package/src/flipkart.js
CHANGED
|
@@ -85,69 +85,145 @@ 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
|
-
|
|
91
|
+
// Send-OTP variants. The NATIVE app flow (okhttp UA, .net host, app body with
|
|
92
|
+
// addAppHash/phoneNumberFormat) is far less throttled than the browser/web flow —
|
|
93
|
+
// it mirrors exactly what the Flipkart app sends (captured). We try native first,
|
|
94
|
+
// and fall back to the web flow only if native yields no requestId.
|
|
95
|
+
async _sendNative(mobile) {
|
|
96
|
+
const body = { actionRequestContext: {
|
|
97
|
+
type: 'LOGIN_IDENTITY_VERIFY', loginId: mobile.slice(-10), loginIdPrefix: '+91',
|
|
98
|
+
phoneNumberFormat: 'E164', addAppHash: true, loginType: 'MOBILE',
|
|
99
|
+
verificationType: 'OTP', sourceContext: 'DEFAULT',
|
|
100
|
+
correlationId: null, snaFlowId: null, clientQueryParamMap: null } };
|
|
101
|
+
const res = await fetch(`${NATIVE_HOST}/1/action/view`, {
|
|
102
|
+
method: 'POST', body: JSON.stringify(body),
|
|
103
|
+
headers: {
|
|
104
|
+
'User-Agent': 'okhttp/4.9.2', 'X-User-Agent': nativeUa(this.vid),
|
|
105
|
+
'Content-Type': 'application/json; charset=UTF-8',
|
|
106
|
+
'x-request-metaInfo': '{"actionType":"LOGIN_IDENTITY_VERIFY","pageUri":"questContext"}',
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
return res;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async _sendWeb(mobile) {
|
|
92
113
|
const body = { actionRequestContext: {
|
|
93
114
|
type: 'LOGIN_IDENTITY_VERIFY', loginIdPrefix: '+91', loginId: mobile.slice(-10),
|
|
94
115
|
clientQueryParamMap: { ret: '/my-account', entryPage: 'DEFAULT' },
|
|
95
116
|
loginType: 'MOBILE', verificationType: 'OTP', screenName: 'LOGIN_V4_MOBILE',
|
|
96
117
|
triggerSna: false, sourceContext: 'DEFAULT' } };
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
118
|
+
return fetch(`${WEB_HOST}/1/action/view`, {
|
|
119
|
+
method: 'POST', body: JSON.stringify(body),
|
|
120
|
+
headers: {
|
|
121
|
+
Accept: '*/*', 'Content-Type': 'application/json',
|
|
122
|
+
'X-User-Agent': FKUA, 'User-Agent': FKUA,
|
|
123
|
+
Origin: 'https://www.flipkart.com', Referer: 'https://www.flipkart.com/',
|
|
124
|
+
'sec-ch-ua-mobile': '?1', 'sec-ch-ua-platform': '"iOS"', 'sec-fetch-site': 'same-site',
|
|
125
|
+
'x-request-metaInfo': '{"actionType":"LOGIN_IDENTITY_VERIFY","pageUri":"questContext"}',
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async sendOtp(mobile) {
|
|
131
|
+
await throttle.wait();
|
|
132
|
+
// The WEB flow establishes the SN session cookie that VERIFY needs, so it runs
|
|
133
|
+
// FIRST. If it throttles (200 but no requestId), retry on the NATIVE app flow —
|
|
134
|
+
// which is much less throttled and now reuses the SN web just set. Either flow
|
|
135
|
+
// that yields a requestId wins.
|
|
136
|
+
let res, data, rid;
|
|
137
|
+
for (const send of [this._sendWeb.bind(this), this._sendNative.bind(this)]) {
|
|
138
|
+
try {
|
|
139
|
+
res = await send(mobile);
|
|
140
|
+
} catch (e) { throw new TransientError(`send network error: ${e.message}`); }
|
|
141
|
+
// 529/5xx = Flipkart overloaded / blocking this IP — retryable.
|
|
142
|
+
if (res.status === 429 || res.status === 529 || res.status >= 500) {
|
|
143
|
+
throw new TransientError(`Flipkart busy on send (HTTP ${res.status})`);
|
|
144
|
+
}
|
|
145
|
+
parseCookies(res, this.jar);
|
|
146
|
+
// capture SN as soon as any flow provides it (web normally does)
|
|
147
|
+
if (this.jar.SN) { this.sn = this.jar.SN; this.vid = this.sn.split('.')[0]; }
|
|
148
|
+
data = await res.json().catch(() => ({}));
|
|
149
|
+
rid = data?.RESPONSE?.actionResponseContext?.requestId;
|
|
150
|
+
if (rid) break; // got it — done
|
|
113
151
|
}
|
|
114
|
-
|
|
115
|
-
const data = await res.json().catch(() => ({}));
|
|
116
|
-
const rid = data?.RESPONSE?.actionResponseContext?.requestId;
|
|
117
|
-
// no requestId on a 200 = soft anti-bot throttle — also retryable (often clears on retry).
|
|
152
|
+
// no requestId after both flows = soft anti-bot throttle — retryable (often clears on retry).
|
|
118
153
|
if (!rid) throw new TransientError('blocked while sending OTP (no requestId — throttled)');
|
|
119
154
|
this.reqId = rid;
|
|
155
|
+
// NEW vs EXISTING account: for an UNREGISTERED number FK still sends the OTP but
|
|
156
|
+
// routes the flow to signup. The reliable signals (verified against captures):
|
|
157
|
+
// • landingPageAction.url starts with "/signup" (existing accounts: no such url)
|
|
158
|
+
// • eVar79 tracking is "New|…|Sign-Up|…" (existing: "Existing|…|Sign-In|…")
|
|
159
|
+
// NOTE: a bare "isNewUser":true appears even for EXISTING accounts (unrelated page
|
|
160
|
+
// flag) — do NOT use it. verifyOtp uses type:SIGNUP when isNewUser, else LOGIN.
|
|
161
|
+
const landingUrl = data?.RESPONSE?.actionResponseContext?.landingPageAction?.url || '';
|
|
162
|
+
const eVar79 = data?.RESPONSE?.pageResponse?.pageData?.trackingContext?.tracking?.eVar79 || '';
|
|
163
|
+
this.isNewUser = /^\/signup\b/.test(landingUrl)
|
|
164
|
+
|| /\bSign-?Up\b/i.test(eVar79)
|
|
165
|
+
|| /^New\b/i.test(eVar79);
|
|
120
166
|
this.sn = this.jar.SN || '';
|
|
121
167
|
this.vid = this.sn ? this.sn.split('.')[0] : '';
|
|
122
168
|
return rid;
|
|
123
169
|
}
|
|
124
170
|
|
|
125
|
-
|
|
171
|
+
// Low-level OTP verify with an explicit action type ("LOGIN" for existing
|
|
172
|
+
// accounts, "SIGNUP" for brand-new numbers). Returns { res, text, data }.
|
|
173
|
+
async _verifyWith(type, mobile, otp) {
|
|
126
174
|
await throttle.wait();
|
|
127
175
|
const secureToken = this.vid ? `${this.vid}:${this.vid}` : '';
|
|
128
176
|
const body = { actionRequestContext: {
|
|
129
|
-
type
|
|
177
|
+
type, loginIdPrefix: '+91', loginId: mobile.slice(-10), password: null,
|
|
130
178
|
otp, otpRequestId: this.reqId, remainingAttempts: 5, phoneNumberFormat: 'E164',
|
|
131
|
-
loginType: 'MOBILE', verificationType: 'OTP', sourceContext: '
|
|
132
|
-
otpRegex: null, data: null, clientQueryParamMap: null } };
|
|
179
|
+
loginType: 'MOBILE', verificationType: 'OTP', sourceContext: type === 'SIGNUP' ? 'DEFAULT' : 'GO_LOGIN',
|
|
180
|
+
churned: false, otpRegex: null, data: null, clientQueryParamMap: null } };
|
|
133
181
|
const res = await fetch(`${NATIVE_HOST}/1/action/view`, {
|
|
134
182
|
method: 'POST', body: JSON.stringify(body),
|
|
135
183
|
headers: {
|
|
136
184
|
'User-Agent': 'okhttp/4.9.2', 'X-User-Agent': nativeUa(this.vid),
|
|
137
185
|
'Content-Type': 'application/json; charset=UTF-8',
|
|
138
|
-
'x-request-metaInfo':
|
|
186
|
+
'x-request-metaInfo': `{"actionType":"${type}","pageUri":"questContext"}`,
|
|
139
187
|
at: this.jar.at || '', sn: this.sn, secureToken, secureCookie: this.jar.S || '',
|
|
140
188
|
Cookie: cookieHeader(this.jar),
|
|
141
189
|
},
|
|
142
190
|
});
|
|
143
191
|
const text = await res.text();
|
|
192
|
+
let data = null;
|
|
193
|
+
try { data = JSON.parse(text); } catch { /* caller handles */ }
|
|
194
|
+
return { res, text, data };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async verifyOtp(mobile, otp) {
|
|
198
|
+
// New number → SIGNUP (FK rejects LOGIN with "account does not exist"); existing → LOGIN.
|
|
199
|
+
const primary = this.isNewUser ? 'SIGNUP' : 'LOGIN';
|
|
200
|
+
const fallback = this.isNewUser ? 'LOGIN' : 'SIGNUP';
|
|
201
|
+
|
|
202
|
+
let { res, text, data } = await this._verifyWith(primary, mobile, otp);
|
|
144
203
|
// 529/5xx = Flipkart overloaded / soft-blocking this IP — NOT the OTP's fault.
|
|
145
204
|
if (res.status === 429 || res.status === 529 || res.status >= 500) {
|
|
146
205
|
throw new TransientError(`Flipkart busy (HTTP ${res.status}) — will retry`);
|
|
147
206
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
207
|
+
if (!data) throw new TransientError('Flipkart returned a non-JSON response — will retry');
|
|
208
|
+
|
|
209
|
+
let env = data.SESSION;
|
|
210
|
+
// If the primary type didn't log in AND the failure looks like a wrong account-type
|
|
211
|
+
// (not a wrong OTP), retry once with the other type. This auto-creates a new account
|
|
212
|
+
// when a number we thought existed doesn't (and vice-versa) — same OTP, no extra SMS.
|
|
213
|
+
if ((!env || !env.isLoggedIn)) {
|
|
214
|
+
const fkMsg0 = flipkartErrorMessage(data, text);
|
|
215
|
+
const low0 = (fkMsg0 + ' ' + text).toLowerCase();
|
|
216
|
+
const wrongType = /does not exist|not registered|no account|sign\s*up|signup|already (registered|exists)|please login/.test(low0);
|
|
217
|
+
const wrongOtp = /incorrect|wrong|invalid|expired|otp.*not.*match|verification code/.test(low0);
|
|
218
|
+
if (wrongType && !wrongOtp) {
|
|
219
|
+
const r2 = await this._verifyWith(fallback, mobile, otp);
|
|
220
|
+
if (r2.data && r2.data.SESSION && r2.data.SESSION.isLoggedIn) {
|
|
221
|
+
({ res, text, data } = r2);
|
|
222
|
+
env = data.SESSION;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
151
227
|
if (!env || !env.isLoggedIn) {
|
|
152
228
|
// pull Flipkart's own human message if present (the real reason)
|
|
153
229
|
const fkMsg = flipkartErrorMessage(data, text);
|
|
@@ -163,11 +239,15 @@ export class FlipkartLogin {
|
|
|
163
239
|
parseCookies(res, fresh);
|
|
164
240
|
const ud = fresh.ud || this.jar.ud || '';
|
|
165
241
|
const vd = fresh.vd || '';
|
|
242
|
+
// secureToken: prefer the value FK returns in the SESSION (vid:vid); fall back to
|
|
243
|
+
// computing it from vid. (Was a free var in the old single-method verify — the
|
|
244
|
+
// SIGNUP/LOGIN split moved its definition into _verifyWith, hence recompute here.)
|
|
245
|
+
const secureToken = env.secureToken || (this.vid ? `${this.vid}:${this.vid}` : '');
|
|
166
246
|
return {
|
|
167
247
|
accountId: env.accountId || '', at: env.at || '', rt: env.rt || '', sn: env.sn || '',
|
|
168
248
|
secureToken, secureCookie: this.jar.S || '', ud, vd,
|
|
169
249
|
cookie_T: fresh.T || this.jar.T || '', // T cookie — needed for the email-attach calls
|
|
170
|
-
visitId: (env.sn || '').split('.')[0], nsid: env.nsid || '',
|
|
250
|
+
visitId: env.vid || (env.sn || '').split('.')[0], nsid: env.nsid || '',
|
|
171
251
|
mobileNo: mobile.slice(-10), isLoggedIn: true, rt_expires_at: rtExpiry(env.rt || ''),
|
|
172
252
|
};
|
|
173
253
|
}
|