@dongdev/fca-unofficial 2.0.25 → 2.0.27
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/CHANGELOG.md +6 -0
- package/module/loginHelper.js +166 -56
- package/package.json +1 -1
- package/src/api/socket/core/connectMqtt.js +1 -1
- package/src/api/socket/listenMqtt.js +5 -2
package/CHANGELOG.md
CHANGED
package/module/loginHelper.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
const fs = require("fs");
|
|
2
3
|
const path = require("path");
|
|
3
4
|
const models = require("../src/database/models");
|
|
@@ -230,6 +231,10 @@ async function backupAppStateSQL(j, userID) {
|
|
|
230
231
|
const ck = cookieHeaderFromJar(j);
|
|
231
232
|
await upsertBackup(Model, userID, "appstate", JSON.stringify(appJson));
|
|
232
233
|
await upsertBackup(Model, userID, "cookie", ck);
|
|
234
|
+
try {
|
|
235
|
+
const out = path.join(process.cwd(), "appstate.json");
|
|
236
|
+
fs.writeFileSync(out, JSON.stringify(appJson, null, 2));
|
|
237
|
+
} catch { }
|
|
233
238
|
logger("Backup stored (overwrite mode)", "info");
|
|
234
239
|
} catch (e) {
|
|
235
240
|
logger(`Failed to save appstate backup ${e && e.message ? e.message : String(e)}`, "warn");
|
|
@@ -258,66 +263,171 @@ async function getLatestBackupAny(type) {
|
|
|
258
263
|
}
|
|
259
264
|
}
|
|
260
265
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
+
const MESSENGER_USER_AGENT = "Dalvik/2.1.0 (Linux; U; Android 9; ASUS_Z01QD Build/PQ3A.190605.003) [FBAN/Orca-Android;FBAV/391.2.0.20.404;FBPN/com.facebook.orca;FBLC/vi_VN;FBBV/437533963;FBCR/Viettel Telecom;FBMF/asus;FBBD/asus;FBDV/ASUS_Z01QD;FBSV/9;FBCA/x86:armeabi-v7a;FBDM/{density=1.5,width=1600,height=900};FB_FW/1;]";
|
|
267
|
+
|
|
268
|
+
function encodesig(obj) {
|
|
269
|
+
let data = "";
|
|
270
|
+
Object.keys(obj).forEach(k => { data += `${k}=${obj[k]}`; });
|
|
271
|
+
return md5(data + "62f8ce9f74b12f84c123cc23437a4a32");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function sort(obj) {
|
|
275
|
+
const keys = Object.keys(obj).sort();
|
|
276
|
+
const out = {};
|
|
277
|
+
for (const k of keys) out[k] = obj[k];
|
|
278
|
+
return out;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async function setJarCookies(j, appstate) {
|
|
282
|
+
const tasks = [];
|
|
283
|
+
for (const c of appstate) {
|
|
284
|
+
const dom = (c.domain || ".facebook.com").replace(/^\./, "");
|
|
285
|
+
const path = c.path || "/";
|
|
286
|
+
const base1 = `https://${dom}${path}`;
|
|
287
|
+
const base2 = `https://www.${dom}${path}`;
|
|
288
|
+
const str = `${c.key}=${c.value}; Domain=${c.domain || ".facebook.com"}; Path=${path};`;
|
|
289
|
+
tasks.push(j.setCookie(str, base1));
|
|
290
|
+
tasks.push(j.setCookie(str, base2));
|
|
266
291
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
292
|
+
await Promise.all(tasks);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async function loginViaGraph(username, password, twofactorSecretOrCode, i_user, externalJar) {
|
|
296
|
+
const cookieJar = externalJar instanceof CookieJar ? externalJar : new CookieJar();
|
|
297
|
+
const client = wrapper(axiosBase.create({ jar: cookieJar, withCredentials: true, timeout: 30000, validateStatus: () => true }));
|
|
298
|
+
const device_id = uuidv4();
|
|
299
|
+
const family_device_id = device_id;
|
|
300
|
+
const machine_id = randomString(24);
|
|
301
|
+
const base = {
|
|
302
|
+
adid: "00000000-0000-0000-0000-000000000000",
|
|
303
|
+
format: "json",
|
|
304
|
+
device_id,
|
|
305
|
+
email: username,
|
|
306
|
+
password,
|
|
307
|
+
generate_analytics_claim: "1",
|
|
308
|
+
community_id: "",
|
|
309
|
+
cpl: "true",
|
|
310
|
+
try_num: "1",
|
|
311
|
+
family_device_id,
|
|
312
|
+
secure_family_device_id: "",
|
|
313
|
+
credentials_type: "password",
|
|
314
|
+
enroll_misauth: "false",
|
|
315
|
+
generate_session_cookies: "1",
|
|
316
|
+
source: "login",
|
|
317
|
+
generate_machine_id: "1",
|
|
318
|
+
jazoest: "22297",
|
|
319
|
+
meta_inf_fbmeta: "NO_FILE",
|
|
320
|
+
advertiser_id: "00000000-0000-0000-0000-000000000000",
|
|
321
|
+
currently_logged_in_userid: "0",
|
|
322
|
+
locale: "vi_VN",
|
|
323
|
+
client_country_code: "VN",
|
|
324
|
+
fb_api_req_friendly_name: "authenticate",
|
|
325
|
+
fb_api_caller_class: "AuthOperations$PasswordAuthOperation",
|
|
326
|
+
api_key: "256002347743983",
|
|
327
|
+
access_token: "256002347743983|374e60f8b9bb6b8cbb30f78030438895"
|
|
328
|
+
};
|
|
329
|
+
const headers = {
|
|
330
|
+
"User-Agent": MESSENGER_USER_AGENT,
|
|
331
|
+
"Accept-Encoding": "gzip, deflate",
|
|
332
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
333
|
+
"x-fb-connection-quality": "EXCELLENT",
|
|
334
|
+
"x-fb-sim-hni": "45204",
|
|
335
|
+
"x-fb-net-hni": "45204",
|
|
336
|
+
"x-fb-connection-type": "WIFI",
|
|
337
|
+
"x-tigon-is-retry": "False",
|
|
338
|
+
"x-fb-friendly-name": "authenticate",
|
|
339
|
+
"x-fb-request-analytics-tags": '{"network_tags":{"retry_attempt":"0"},"application_tags":"unknown"}',
|
|
340
|
+
"x-fb-http-engine": "Liger",
|
|
341
|
+
"x-fb-client-ip": "True",
|
|
342
|
+
"x-fb-server-cluster": "True",
|
|
343
|
+
authorization: `OAuth ${base.access_token}`
|
|
344
|
+
};
|
|
345
|
+
const form1 = { ...base };
|
|
346
|
+
form1.sig = encodesig(sort(form1));
|
|
347
|
+
const res1 = await client.request({ url: "https://b-graph.facebook.com/auth/login", method: "post", data: qs.stringify(form1), headers });
|
|
348
|
+
if (res1.status === 200 && res1.data && res1.data.session_cookies) {
|
|
349
|
+
const appstate = res1.data.session_cookies.map(c => ({ key: c.name, value: c.value, domain: c.domain, path: c.path }));
|
|
350
|
+
const cUserCookie = appstate.find(c => c.key === "c_user");
|
|
351
|
+
if (i_user) appstate.push({ key: "i_user", value: i_user, domain: ".facebook.com", path: "/" });
|
|
352
|
+
else if (cUserCookie) appstate.push({ key: "i_user", value: cUserCookie.value, domain: ".facebook.com", path: "/" });
|
|
353
|
+
await setJarCookies(cookieJar, appstate);
|
|
354
|
+
let eaau = null;
|
|
355
|
+
let eaad6v7 = null;
|
|
356
|
+
try {
|
|
357
|
+
const r1 = await client.request({ url: `https://api.facebook.com/method/auth.getSessionforApp?format=json&access_token=${res1.data.access_token}&new_app_id=350685531728`, method: "get", headers: { "user-agent": MESSENGER_USER_AGENT, "x-fb-connection-type": "WIFI", authorization: `OAuth ${res1.data.access_token}` } });
|
|
358
|
+
eaau = r1.data && r1.data.access_token ? r1.data.access_token : null;
|
|
359
|
+
} catch { }
|
|
300
360
|
try {
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
361
|
+
const r2 = await client.request({ url: `https://api.facebook.com/method/auth.getSessionforApp?format=json&access_token=${res1.data.access_token}&new_app_id=275254692598279`, method: "get", headers: { "user-agent": MESSENGER_USER_AGENT, "x-fb-connection-type": "WIFI", authorization: `OAuth ${res1.data.access_token}` } });
|
|
362
|
+
eaad6v7 = r2.data && r2.data.access_token ? r2.data.access_token : null;
|
|
363
|
+
} catch { }
|
|
364
|
+
return { ok: true, cookies: appstate.map(c => ({ key: c.key, value: c.value })), jar: cookieJar, access_token_mess: res1.data.access_token || null, access_token: eaau, access_token_eaad6v7: eaad6v7, uid: res1.data.uid || cUserCookie?.value || null, session_key: res1.data.session_key || null };
|
|
365
|
+
}
|
|
366
|
+
const err = res1 && res1.data && res1.data.error ? res1.data.error : {};
|
|
367
|
+
if (err && err.code === 406) {
|
|
368
|
+
const data = err.error_data || {};
|
|
369
|
+
let code = null;
|
|
370
|
+
if (twofactorSecretOrCode && /^\d{6}$/.test(String(twofactorSecretOrCode))) code = String(twofactorSecretOrCode);
|
|
371
|
+
else if (twofactorSecretOrCode) {
|
|
372
|
+
try {
|
|
373
|
+
const clean = decodeURI(twofactorSecretOrCode).replace(/\s+/g, "").toUpperCase();
|
|
374
|
+
const { otp } = await TOTP.generate(clean);
|
|
375
|
+
code = otp;
|
|
376
|
+
} catch { }
|
|
377
|
+
} else if (config.credentials?.twofactor) {
|
|
378
|
+
const { otp } = await TOTP.generate(String(config.credentials.twofactor).replace(/\s+/g, "").toUpperCase());
|
|
379
|
+
code = otp;
|
|
319
380
|
}
|
|
381
|
+
if (!code) return { ok: false, message: "2FA required" };
|
|
382
|
+
const form2 = {
|
|
383
|
+
...base,
|
|
384
|
+
credentials_type: "two_factor",
|
|
385
|
+
twofactor_code: code,
|
|
386
|
+
userid: data.uid || username,
|
|
387
|
+
first_factor: data.login_first_factor || "",
|
|
388
|
+
machine_id: data.machine_id || machine_id
|
|
389
|
+
};
|
|
390
|
+
form2.sig = encodesig(sort(form2));
|
|
391
|
+
const res2 = await client.request({ url: "https://b-graph.facebook.com/auth/login", method: "post", data: qs.stringify(form2), headers });
|
|
392
|
+
if (res2.status === 200 && res2.data && res2.data.session_cookies) {
|
|
393
|
+
const appstate = res2.data.session_cookies.map(c => ({ key: c.name, value: c.value, domain: c.domain, path: c.path }));
|
|
394
|
+
const cUserCookie = appstate.find(c => c.key === "c_user");
|
|
395
|
+
if (i_user) appstate.push({ key: "i_user", value: i_user, domain: ".facebook.com", path: "/" });
|
|
396
|
+
else if (cUserCookie) appstate.push({ key: "i_user", value: cUserCookie.value, domain: ".facebook.com", path: "/" });
|
|
397
|
+
await setJarCookies(cookieJar, appstate);
|
|
398
|
+
let eaau = null;
|
|
399
|
+
let eaad6v7 = null;
|
|
400
|
+
try {
|
|
401
|
+
const r1 = await client.request({ url: `https://api.facebook.com/method/auth.getSessionforApp?format=json&access_token=${res2.data.access_token}&new_app_id=350685531728`, method: "get", headers: { "user-agent": MESSENGER_USER_AGENT, "x-fb-connection-type": "WIFI", authorization: `OAuth ${res2.data.access_token}` } });
|
|
402
|
+
eaau = r1.data && r1.data.access_token ? r1.data.access_token : null;
|
|
403
|
+
} catch { }
|
|
404
|
+
try {
|
|
405
|
+
const r2 = await client.request({ url: `https://api.facebook.com/method/auth.getSessionforApp?format=json&access_token=${res2.data.access_token}&new_app_id=275254692598279`, method: "get", headers: { "user-agent": MESSENGER_USER_AGENT, "x-fb-connection-type": "WIFI", authorization: `OAuth ${res2.data.access_token}` } });
|
|
406
|
+
eaad6v7 = r2.data && r2.data.access_token ? r2.data.access_token : null;
|
|
407
|
+
} catch { }
|
|
408
|
+
return { ok: true, cookies: appstate.map(c => ({ key: c.key, value: c.value })), jar: cookieJar, access_token_mess: res2.data.access_token || null, access_token: eaau, access_token_eaad6v7: eaad6v7, uid: res2.data.uid || cUserCookie?.value || null, session_key: res2.data.session_key || null };
|
|
409
|
+
}
|
|
410
|
+
return { ok: false, message: "2FA failed" };
|
|
411
|
+
}
|
|
412
|
+
return { ok: false, message: "Login failed" };
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
async function tokens(username, password, twofactor = null) {
|
|
416
|
+
const t0 = process.hrtime.bigint();
|
|
417
|
+
if (!username || !password) return { status: false, message: "Please provide email and password" };
|
|
418
|
+
logger(`AUTO-LOGIN: Initialize login ${mask(username, 2)}`, "info");
|
|
419
|
+
const res = await loginViaGraph(username, password, twofactor, null, jar);
|
|
420
|
+
if (res && res.ok && Array.isArray(res.cookies)) {
|
|
421
|
+
logger(`AUTO-LOGIN: Login success ${res.cookies.length} cookies`, "info");
|
|
422
|
+
const t1 = Number(process.hrtime.bigint() - t0) / 1e6;
|
|
423
|
+
logger(`Done success login ${Math.round(t1)}ms`, "info");
|
|
424
|
+
return { status: true, cookies: res.cookies };
|
|
425
|
+
}
|
|
426
|
+
if (res && res.message === "2FA required") {
|
|
427
|
+
logger("AUTO-LOGIN: 2FA required but secret missing", "warn");
|
|
428
|
+
return { status: false, message: "Please provide the 2FA secret!" };
|
|
320
429
|
}
|
|
430
|
+
return { status: false, message: res && res.message ? res.message : "Login failed" };
|
|
321
431
|
}
|
|
322
432
|
|
|
323
433
|
async function hydrateJarFromDB(userID) {
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@ const uuid = require("uuid");
|
|
|
4
4
|
"use strict";
|
|
5
5
|
module.exports = function createListenMqtt(deps) {
|
|
6
6
|
const { WebSocket, mqtt, HttpsProxyAgent, buildStream, buildProxy,
|
|
7
|
-
topics, parseDelta, getTaskResponseData, logger
|
|
7
|
+
topics, parseDelta, getTaskResponseData, logger, emitAuth
|
|
8
8
|
} = deps;
|
|
9
9
|
|
|
10
10
|
return function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
|
|
@@ -14,9 +14,12 @@ const markDelivery = require("./core/markDelivery");
|
|
|
14
14
|
const getTaskResponseData = require("./core/getTaskResponseData");
|
|
15
15
|
const createEmitAuth = require("./core/emitAuth");
|
|
16
16
|
const parseDelta = createParseDelta({ markDelivery, parseAndCheckLogin });
|
|
17
|
-
|
|
18
|
-
const getSeqIDFactory = createGetSeqID({ parseAndCheckLogin, listenMqtt, logger });
|
|
17
|
+
// Create emitAuth first so it can be injected into both factories
|
|
19
18
|
const emitAuth = createEmitAuth({ logger });
|
|
19
|
+
// Pass emitAuth into connectMqtt so errors there can signal auth state
|
|
20
|
+
const listenMqtt = createListenMqtt({ WebSocket, mqtt, HttpsProxyAgent, buildStream, buildProxy, topics, parseDelta, getTaskResponseData, logger, emitAuth });
|
|
21
|
+
// Inject emitAuth into getSeqID so its catch handler can notify properly
|
|
22
|
+
const getSeqIDFactory = createGetSeqID({ parseAndCheckLogin, listenMqtt, logger, emitAuth });
|
|
20
23
|
|
|
21
24
|
const MQTT_DEFAULTS = { cycleMs: 60 * 60 * 1000, reconnectDelayMs: 2000, autoReconnect: true, reconnectAfterStop: false };
|
|
22
25
|
function mqttConf(ctx, overrides) {
|