@dongdev/fca-unofficial 2.0.11 → 2.0.13

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.
@@ -304,72 +304,62 @@ module.exports = function (defaultFuncs, api, ctx) {
304
304
  resolveFunc(data);
305
305
  };
306
306
  }
307
-
308
- try {
309
- const msgType = getType(msg);
310
- const threadIDType = getType(threadID);
311
- const messageIDType = getType(replyToMessage);
312
- if (msgType !== "String" && msgType !== "Object") return callback({ error: "Message should be of type string or object and not " + msgType + "." });
313
- if (threadIDType !== "Array" && threadIDType !== "Number" && threadIDType !== "String") return callback({ error: "ThreadID should be of type number, string, or array and not " + threadIDType + "." });
314
- if (replyToMessage && messageIDType !== "String") return callback({ error: "MessageID should be of type string and not " + threadIDType + "." });
315
- if (msgType === "String") msg = { body: msg };
316
-
317
- const disallowedProperties = Object.keys(msg).filter(prop => !allowedProperties[prop]);
318
- if (disallowedProperties.length > 0) return callback({ error: "Disallowed props: `" + disallowedProperties.join(", ") + "`" });
319
-
320
- const messageAndOTID = generateOfflineThreadingID();
321
- const form = {
322
- client: "mercury",
323
- action_type: "ma-type:user-generated-message",
324
- author: "fbid:" + ctx.userID,
325
- timestamp: Date.now(),
326
- timestamp_absolute: "Today",
327
- timestamp_relative: generateTimestampRelative(),
328
- timestamp_time_passed: "0",
329
- is_unread: false,
330
- is_cleared: false,
331
- is_forward: false,
332
- is_filtered_content: false,
333
- is_filtered_content_bh: false,
334
- is_filtered_content_account: false,
335
- is_filtered_content_quasar: false,
336
- is_filtered_content_invalid_app: false,
337
- is_spoof_warning: false,
338
- source: "source:chat:web",
339
- "source_tags[0]": "source:chat",
340
- body: msg.body ? msg.body.toString() : "",
341
- html_body: false,
342
- ui_push_phase: "V3",
343
- status: "0",
344
- offline_threading_id: messageAndOTID,
345
- message_id: messageAndOTID,
346
- threading_id: generateThreadingID(ctx.clientID),
347
- ephemeral_ttl_mode: "0",
348
- manual_retry_cnt: "0",
349
- signatureID: getSignatureID(),
350
- replied_to_message_id: replyToMessage ? replyToMessage.toString() : ""
351
- };
352
- applyPageAuthor(form, msg);
353
- handleLocation(msg, form, callback, () =>
354
- handleSticker(msg, form, callback, () =>
355
- handleAttachment(msg, form, callback, () =>
356
- handleUrl(msg, form, callback, () =>
357
- handleEmoji(msg, form, callback, () =>
358
- handleMention(msg, form, callback, () => {
359
- finalizeHasAttachment(form);
360
- send(form, threadID, messageAndOTID, callback, isGroup);
361
- })
362
- )
307
+ const msgType = getType(msg);
308
+ const threadIDType = getType(threadID);
309
+ const messageIDType = getType(replyToMessage);
310
+ if (msgType !== "String" && msgType !== "Object") return callback({ error: "Message should be of type string or object and not " + msgType + "." });
311
+ if (threadIDType !== "Array" && threadIDType !== "Number" && threadIDType !== "String") return callback({ error: "ThreadID should be of type number, string, or array and not " + threadIDType + "." });
312
+ if (replyToMessage && messageIDType !== "String") return callback({ error: "MessageID should be of type string and not " + threadIDType + "." });
313
+ if (msgType === "String") msg = { body: msg };
314
+ const disallowedProperties = Object.keys(msg).filter(prop => !allowedProperties[prop]);
315
+ if (disallowedProperties.length > 0) return callback({ error: "Disallowed props: `" + disallowedProperties.join(", ") + "`" });
316
+ const messageAndOTID = generateOfflineThreadingID();
317
+ const form = {
318
+ client: "mercury",
319
+ action_type: "ma-type:user-generated-message",
320
+ author: "fbid:" + ctx.userID,
321
+ timestamp: Date.now(),
322
+ timestamp_absolute: "Today",
323
+ timestamp_relative: generateTimestampRelative(),
324
+ timestamp_time_passed: "0",
325
+ is_unread: false,
326
+ is_cleared: false,
327
+ is_forward: false,
328
+ is_filtered_content: false,
329
+ is_filtered_content_bh: false,
330
+ is_filtered_content_account: false,
331
+ is_filtered_content_quasar: false,
332
+ is_filtered_content_invalid_app: false,
333
+ is_spoof_warning: false,
334
+ source: "source:chat:web",
335
+ "source_tags[0]": "source:chat",
336
+ body: msg.body ? msg.body.toString() : "",
337
+ html_body: false,
338
+ ui_push_phase: "V3",
339
+ status: "0",
340
+ offline_threading_id: messageAndOTID,
341
+ message_id: messageAndOTID,
342
+ threading_id: generateThreadingID(ctx.clientID),
343
+ ephemeral_ttl_mode: "0",
344
+ manual_retry_cnt: "0",
345
+ signatureID: getSignatureID(),
346
+ replied_to_message_id: replyToMessage ? replyToMessage.toString() : ""
347
+ };
348
+ applyPageAuthor(form, msg);
349
+ handleLocation(msg, form, callback, () =>
350
+ handleSticker(msg, form, callback, () =>
351
+ handleAttachment(msg, form, callback, () =>
352
+ handleUrl(msg, form, callback, () =>
353
+ handleEmoji(msg, form, callback, () =>
354
+ handleMention(msg, form, callback, () => {
355
+ finalizeHasAttachment(form);
356
+ send(form, threadID, messageAndOTID, callback, isGroup);
357
+ })
363
358
  )
364
359
  )
365
360
  )
366
- );
367
- } catch (e) {
368
- log.error("sendMessage", e);
369
- if (getType(e) === "Object" && e.error === "Not logged in.") ctx.loggedIn = false;
370
- return callback(e);
371
- }
372
-
361
+ )
362
+ );
373
363
  return returnPromise;
374
364
  };
375
365
 
@@ -75,17 +75,15 @@ module.exports = function createListenMqtt(deps) {
75
75
  const mqttClient = ctx.mqttClient;
76
76
  global.mqttClient = mqttClient;
77
77
  mqttClient.on("error", function (err) {
78
- logger("listenMqtt" + err, "error");
79
- mqttClient.end();
80
- if (ctx.globalOptions.autoReconnect) {
81
- listenMqtt(defaultFuncs, api, ctx, globalCallback);
82
- } else {
83
- // utils.checkLiveCookie(ctx, defaultFuncs).then(res => {
84
- // globalCallback({ type: "stop_listen", error: "Connection refused: Server unavailable" }, null);
85
- // }).catch(err => {
86
- // globalCallback({ type: "account_inactive", error: "Maybe your account is blocked by facebook, please login and check at https://facebook.com" }, null);
87
- // });
78
+ const msg = String(err && err.message ? err.message : err || "");
79
+ if (ctx._ending && /No subscription existed/i.test(msg)) {
80
+ logger("MQTT ignore unsubscribe error during shutdown", "warn");
81
+ return;
88
82
  }
83
+ logger(`MQTT error: ${msg}`, "error");
84
+ mqttClient.end();
85
+ logger("MQTT autoReconnect listenMqtt() in 2000ms", "warn");
86
+ setTimeout(() => listenMqtt(defaultFuncs, api, ctx, globalCallback), 2000);
89
87
  });
90
88
  mqttClient.on("connect", function () {
91
89
  if (process.env.OnStatus === undefined) {
@@ -174,7 +172,7 @@ module.exports = function createListenMqtt(deps) {
174
172
  return;
175
173
  }
176
174
  });
177
- mqttClient.on("close", function () {});
178
- mqttClient.on("disconnect", () => {});
175
+ mqttClient.on("close", function () { });
176
+ mqttClient.on("disconnect", () => { });
179
177
  };
180
178
  };
@@ -3,7 +3,7 @@ const mqtt = require("mqtt");
3
3
  const WebSocket = require("ws");
4
4
  const HttpsProxyAgent = require("https-proxy-agent");
5
5
  const EventEmitter = require("events");
6
- const logger = require("../../../func/logger.js");
6
+ const logger = require("../../../func/logger");
7
7
  const { parseAndCheckLogin } = require("../../utils/client");
8
8
  const { buildProxy, buildStream } = require("./detail/buildStream");
9
9
  const { topics } = require("./detail/constants");
@@ -14,63 +14,117 @@ const markDelivery = require("./core/markDelivery");
14
14
  const getTaskResponseData = require("./core/getTaskResponseData");
15
15
  const parseDelta = createParseDelta({ markDelivery, parseAndCheckLogin });
16
16
  const listenMqtt = createListenMqtt({ WebSocket, mqtt, HttpsProxyAgent, buildStream, buildProxy, topics, parseDelta, getTaskResponseData, logger });
17
- const getSeqIDFactory = createGetSeqID({ listenMqtt });
17
+ const getSeqIDFactory = createGetSeqID({ parseAndCheckLogin, listenMqtt, logger });
18
18
  module.exports = function (defaultFuncs, api, ctx) {
19
- const identity = function () {};
19
+ const identity = function () { };
20
20
  let globalCallback = identity;
21
21
  function getSeqIDWrapper() {
22
22
  const form = {
23
- av: ctx.globalOptions.pageID,
23
+ av: ctx.userID,
24
24
  queries: JSON.stringify({
25
- o0: {
26
- doc_id: "3336396659757871",
27
- query_params: {
28
- limit: 1,
29
- before: null,
30
- tags: ["INBOX"],
31
- includeDeliveryReceipts: false,
32
- includeSeqID: true
33
- }
34
- }
25
+ o0: { doc_id: "3336396659757871", query_params: { limit: 1, before: null, tags: ["INBOX"], includeDeliveryReceipts: false, includeSeqID: true } }
35
26
  })
36
27
  };
37
- return getSeqIDFactory(defaultFuncs, api, ctx, globalCallback, form);
28
+ logger("MQTT getSeqID call", "info");
29
+ return getSeqIDFactory(defaultFuncs, api, ctx, globalCallback, form).then(() => {
30
+ logger("MQTT getSeqID done", "info");
31
+ }).catch(e => {
32
+ logger(`MQTT getSeqID error: ${e && e.message ? e.message : e}`, "error");
33
+ });
34
+ }
35
+ function isConnected() {
36
+ return !!(ctx.mqttClient && ctx.mqttClient.connected);
37
+ }
38
+ function unsubAll(cb) {
39
+ if (!isConnected()) return cb && cb();
40
+ let pending = topics.length;
41
+ if (!pending) return cb && cb();
42
+ let done = false;
43
+ topics.forEach(t => {
44
+ ctx.mqttClient.unsubscribe(t, err => {
45
+ const msg = String(err && err.message ? err.message : err || "");
46
+ if (msg && /No subscription existed/i.test(msg)) err = null;
47
+ if (--pending === 0 && !done) {
48
+ done = true;
49
+ cb && cb();
50
+ }
51
+ });
52
+ });
53
+ }
54
+ function endQuietly(next) {
55
+ const finish = () => {
56
+ ctx.mqttClient = undefined;
57
+ ctx.lastSeqId = null;
58
+ ctx.syncToken = undefined;
59
+ ctx.t_mqttCalled = false;
60
+ ctx._ending = false;
61
+ next && next();
62
+ };
63
+ try {
64
+ if (ctx.mqttClient) {
65
+ if (isConnected()) {
66
+ try { ctx.mqttClient.publish("/browser_close", "{}"); } catch (_) { }
67
+ }
68
+ ctx.mqttClient.end(true, finish);
69
+ } else finish();
70
+ } catch (_) {
71
+ finish();
72
+ }
73
+ }
74
+ function delayedReconnect() {
75
+ logger("MQTT reconnect in 2000ms", "info");
76
+ setTimeout(() => getSeqIDWrapper(), 2000);
77
+ }
78
+ function forceCycle() {
79
+ ctx._ending = true;
80
+ logger("MQTT force cycle begin", "warn");
81
+ unsubAll(() => {
82
+ endQuietly(() => {
83
+ delayedReconnect();
84
+ });
85
+ });
38
86
  }
39
87
  return function (callback) {
40
88
  class MessageEmitter extends EventEmitter {
41
89
  stopListening(callback2) {
42
- const cb = callback2 || function () {};
90
+ const cb = callback2 || function () { };
91
+ logger("MQTT stop requested", "info");
43
92
  globalCallback = identity;
44
- if (ctx.mqttClient) {
45
- ctx.mqttClient.unsubscribe("/webrtc");
46
- ctx.mqttClient.unsubscribe("/rtc_multi");
47
- ctx.mqttClient.unsubscribe("/onevc");
48
- ctx.mqttClient.publish("/browser_close", "{}");
49
- ctx.mqttClient.end(false, function (...data) {
50
- cb(data);
51
- ctx.mqttClient = undefined;
52
- });
93
+ if (ctx._autoCycleTimer) {
94
+ clearInterval(ctx._autoCycleTimer);
95
+ ctx._autoCycleTimer = null;
96
+ logger("MQTT auto-cycle cleared", "info");
53
97
  }
98
+ ctx._ending = true;
99
+ unsubAll(() => {
100
+ endQuietly(() => {
101
+ logger("MQTT stopped", "info");
102
+ cb();
103
+ delayedReconnect();
104
+ });
105
+ });
54
106
  }
55
107
  async stopListeningAsync() {
56
- return new Promise(resolve => {
57
- this.stopListening(resolve);
58
- });
108
+ return new Promise(resolve => { this.stopListening(resolve); });
59
109
  }
60
110
  }
61
111
  const msgEmitter = new MessageEmitter();
62
112
  globalCallback = callback || function (error, message) {
63
- if (error) {
64
- return msgEmitter.emit("error", error);
65
- }
113
+ if (error) { logger("MQTT emit error", "error"); return msgEmitter.emit("error", error); }
66
114
  msgEmitter.emit("message", message);
67
115
  };
68
116
  if (!ctx.firstListen) ctx.lastSeqId = null;
69
117
  ctx.syncToken = undefined;
70
118
  ctx.t_mqttCalled = false;
71
- if (!ctx.firstListen || !ctx.lastSeqId) {
72
- getSeqIDWrapper();
73
- } else {
119
+ if (ctx._autoCycleTimer) {
120
+ clearInterval(ctx._autoCycleTimer);
121
+ ctx._autoCycleTimer = null;
122
+ }
123
+ ctx._autoCycleTimer = setInterval(forceCycle, 60 * 60 * 1000);
124
+ logger("MQTT auto-cycle enabled 3600000ms", "info");
125
+ if (!ctx.firstListen || !ctx.lastSeqId) getSeqIDWrapper();
126
+ else {
127
+ logger("MQTT starting listenMqtt", "info");
74
128
  listenMqtt(defaultFuncs, api, ctx, globalCallback);
75
129
  }
76
130
  api.stopListening = msgEmitter.stopListening;
@@ -1,4 +1,5 @@
1
1
  "use strict";
2
+ const logger = require('../../func/logger');
2
3
 
3
4
  function saveCookies(jar) {
4
5
  return res => {
@@ -77,6 +78,41 @@ function parseAndCheckLogin(ctx, http, retryCount = 0) {
77
78
  return cfg?.url || "";
78
79
  }
79
80
  };
81
+
82
+ const formatCookie = (arr, service) => {
83
+ const n = String(arr?.[0] || "");
84
+ const v = String(arr?.[1] || "");
85
+ return `${n}=${v}; Domain=.${service}.com; Path=/; Secure`;
86
+ };
87
+
88
+ const maybeAutoLogin = async (resData) => {
89
+ if (ctx.auto_login) {
90
+ const e = new Error("Not logged in.");
91
+ e.error = "Not logged in.";
92
+ e.res = resData;
93
+ throw e;
94
+ }
95
+ if (typeof ctx.performAutoLogin !== "function") {
96
+ const e = new Error("Not logged in.");
97
+ e.error = "Not logged in.";
98
+ e.res = resData;
99
+ throw e;
100
+ }
101
+ ctx.auto_login = true;
102
+ logL("Phiên đăng nhập hết hạn", "warn");
103
+ const ok = await ctx.performAutoLogin();
104
+ if (ok) {
105
+ logL("Auto login successful! Restarting...", "AUTO-LOGIN");
106
+ ctx.auto_login = false;
107
+ process.exit(1);
108
+ } else {
109
+ ctx.auto_login = false;
110
+ const e = new Error("Not logged in.");
111
+ e.error = "Not logged in.";
112
+ e.res = resData;
113
+ throw e;
114
+ }
115
+ };
80
116
  return async (res) => {
81
117
  const status = res?.status ?? 0;
82
118
  if (status >= 500 && status < 600) {
@@ -90,11 +126,16 @@ function parseAndCheckLogin(ctx, http, retryCount = 0) {
90
126
  const retryTime = Math.floor(Math.random() * 5000);
91
127
  await delay(retryTime);
92
128
  const url = buildUrl(res?.config);
129
+ const method = String(res?.config?.method || "GET").toUpperCase();
93
130
  const ctype = String(headerOf(res?.config?.headers, "content-type") || "").toLowerCase();
94
131
  const isMultipart = ctype.includes("multipart/form-data");
95
132
  const payload = res?.config?.data;
96
133
  const params = res?.config?.params;
97
134
  retryCount += 1;
135
+ if (method === "GET") {
136
+ const newData = await http.get(url, ctx.jar, params || null, ctx.globalOptions, ctx);
137
+ return await parseAndCheckLogin(ctx, http, retryCount)(newData);
138
+ }
98
139
  if (isMultipart) {
99
140
  const newData = await http.postFormData(url, ctx.jar, payload, params, ctx.globalOptions, ctx);
100
141
  return await parseAndCheckLogin(ctx, http, retryCount)(newData);
@@ -123,17 +164,17 @@ function parseAndCheckLogin(ctx, http, retryCount = 0) {
123
164
  throw err;
124
165
  }
125
166
  const method = String(res?.config?.method || "GET").toUpperCase();
126
- if (parsed.redirect && method === "GET") {
167
+ if (parsed?.redirect && method === "GET") {
127
168
  const redirectRes = await http.get(parsed.redirect, ctx.jar, null, ctx.globalOptions, ctx);
128
169
  return await parseAndCheckLogin(ctx, http)(redirectRes);
129
170
  }
130
- if (parsed.jsmods && parsed.jsmods.require && Array.isArray(parsed.jsmods.require[0]) && parsed.jsmods.require[0][0] === "Cookie") {
131
- parsed.jsmods.require[0][3][0] = parsed.jsmods.require[0][3][0].replace("_js_", "");
171
+ if (parsed?.jsmods && parsed.jsmods.require && Array.isArray(parsed.jsmods.require[0]) && parsed.jsmods.require[0][0] === "Cookie") {
172
+ parsed.jsmods.require[0][3][0] = String(parsed.jsmods.require[0][3][0] || "").replace("_js_", "");
132
173
  const requireCookie = parsed.jsmods.require[0][3];
133
- ctx.jar.setCookie(formatCookie(requireCookie, "facebook"), "https://www.facebook.com");
134
- ctx.jar.setCookie(formatCookie(requireCookie, "messenger"), "https://www.messenger.com");
174
+ await ctx.jar.setCookie(formatCookie(requireCookie, "facebook"), "https://www.facebook.com");
175
+ await ctx.jar.setCookie(formatCookie(requireCookie, "messenger"), "https://www.messenger.com");
135
176
  }
136
- if (parsed.jsmods && Array.isArray(parsed.jsmods.require)) {
177
+ if (parsed?.jsmods && Array.isArray(parsed.jsmods.require)) {
137
178
  for (const item of parsed.jsmods.require) {
138
179
  if (item[0] === "DTSG" && item[1] === "setToken") {
139
180
  ctx.fb_dtsg = item[3][0];
@@ -143,11 +184,26 @@ function parseAndCheckLogin(ctx, http, retryCount = 0) {
143
184
  }
144
185
  }
145
186
  }
146
- if (parsed.error === 1357001) {
187
+ if (parsed?.error === 1357001) {
147
188
  const err = new Error("Facebook blocked the login");
148
189
  err.error = "Not logged in.";
149
190
  throw err;
150
191
  }
192
+ const resData = parsed;
193
+ const resStr = JSON.stringify(resData);
194
+ if (resStr.includes("XCheckpointFBScrapingWarningController") || resStr.includes("601051028565049")) {
195
+ await maybeAutoLogin(resData);
196
+ }
197
+ if (resStr.includes("https://www.facebook.com/login.php?") || String(parsed?.redirect || "").includes("login.php?")) {
198
+ await maybeAutoLogin(resData);
199
+ }
200
+ if (resStr.includes("1501092823525282")) {
201
+ logger("Bot checkpoint 282 detected, please check the account!", "error");
202
+ process.exit(0);
203
+ }
204
+ if (resStr.includes("828281030927956")) {
205
+ logger("Bot checkpoint 956 detected, please check the account!", "error");
206
+ }
151
207
  return parsed;
152
208
  };
153
209
  }