@dongdev/fca-unofficial 2.0.22 → 2.0.24

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 CHANGED
@@ -101,3 +101,9 @@ Too lazy to write changelog, sorry! (will write changelog in the next release, t
101
101
 
102
102
  ## v2.0.21 - 2025-10-08
103
103
  - Hotfix / auto bump
104
+
105
+ ## v2.0.22 - 2025-10-09
106
+ - Hotfix / auto bump
107
+
108
+ ## v2.0.23 - 2025-10-11
109
+ - Hotfix / auto bump
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dongdev/fca-unofficial",
3
- "version": "2.0.22",
3
+ "version": "2.0.24",
4
4
  "description": "Unofficial Facebook Chat API for Node.js - Interact with Facebook Messenger programmatically",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -1,41 +1,42 @@
1
1
  "use strict";
2
2
  const { formatID } = require("../../../utils/format");
3
+ const uuid = require("uuid");
4
+ "use strict";
3
5
  module.exports = function createListenMqtt(deps) {
4
- const { WebSocket, mqtt, HttpsProxyAgent, buildStream, buildProxy, topics, parseDelta, getTaskResponseData, logger } = deps;
6
+ const { WebSocket, mqtt, HttpsProxyAgent, buildStream, buildProxy,
7
+ topics, parseDelta, getTaskResponseData, logger
8
+ } = deps;
9
+
5
10
  return function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
11
+
12
+ function scheduleReconnect(delayMs) {
13
+ const d = (ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || 2000;
14
+ const ms = typeof delayMs === "number" ? delayMs : d;
15
+ if (ctx._reconnectTimer) return; // debounce
16
+ logger(`mqtt will reconnect in ${ms}ms`, "warn");
17
+ ctx._reconnectTimer = setTimeout(() => {
18
+ ctx._reconnectTimer = null;
19
+ listenMqtt(defaultFuncs, api, ctx, globalCallback);
20
+ }, ms);
21
+ }
22
+ function isEndingLikeError(msg) {
23
+ return /No subscription existed|client disconnecting|socket hang up|ECONNRESET/i.test(msg || "");
24
+ }
25
+
6
26
  const chatOn = ctx.globalOptions.online;
7
- const foreground = false;
8
27
  const sessionID = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER) + 1;
9
28
  const username = {
10
- u: ctx.userID,
11
- s: sessionID,
12
- chat_on: chatOn,
13
- fg: foreground,
14
- d: ctx.clientId,
15
- ct: "websocket",
16
- aid: 219994525426954,
17
- aids: null,
18
- mqtt_sid: "",
19
- cp: 3,
20
- ecp: 10,
21
- st: [],
22
- pm: [],
23
- dc: "",
24
- no_auto_fg: true,
25
- gas: null,
26
- pack: [],
27
- p: null,
28
- php_override: ""
29
+ u: ctx.userID, s: sessionID, chat_on: chatOn, fg: false, d: ctx.clientId,
30
+ ct: "websocket", aid: 219994525426954, aids: null, mqtt_sid: "",
31
+ cp: 3, ecp: 10, st: [], pm: [], dc: "", no_auto_fg: true, gas: null, pack: [], p: null, php_override: ""
29
32
  };
33
+
30
34
  const cookies = api.getCookies();
31
35
  let host;
32
- if (ctx.mqttEndpoint) {
33
- host = `${ctx.mqttEndpoint}&sid=${sessionID}&cid=${ctx.clientId}`;
34
- } else if (ctx.region) {
35
- host = `wss://edge-chat.facebook.com/chat?region=${ctx.region.toLowerCase()}&sid=${sessionID}&cid=${ctx.clientId}`;
36
- } else {
37
- host = `wss://edge-chat.facebook.com/chat?sid=${sessionID}&cid=${ctx.clientId}`;
38
- }
36
+ if (ctx.mqttEndpoint) host = `${ctx.mqttEndpoint}&sid=${sessionID}&cid=${ctx.clientId}`;
37
+ else if (ctx.region) host = `wss://edge-chat.facebook.com/chat?region=${ctx.region.toLowerCase()}&sid=${sessionID}&cid=${ctx.clientId}`;
38
+ else host = `wss://edge-chat.facebook.com/chat?sid=${sessionID}&cid=${ctx.clientId}`;
39
+
39
40
  const options = {
40
41
  clientId: "mqttwsclient",
41
42
  protocolId: "MQIsdp",
@@ -46,7 +47,7 @@ module.exports = function createListenMqtt(deps) {
46
47
  headers: {
47
48
  Cookie: cookies,
48
49
  Origin: "https://www.facebook.com",
49
- "User-Agent": ctx.globalOptions.userAgent || "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36",
50
+ "User-Agent": ctx.globalOptions.userAgent || "Mozilla/5.0",
50
51
  Referer: "https://www.facebook.com/",
51
52
  Host: "edge-chat.facebook.com",
52
53
  Connection: "Upgrade",
@@ -64,115 +65,124 @@ module.exports = function createListenMqtt(deps) {
64
65
  },
65
66
  keepalive: 30,
66
67
  reschedulePings: true,
67
- reconnectPeriod: 1000,
68
+ reconnectPeriod: 0,
68
69
  connectTimeout: 5000
69
70
  };
70
71
  if (ctx.globalOptions.proxy !== undefined) {
71
72
  const agent = new HttpsProxyAgent(ctx.globalOptions.proxy);
72
73
  options.wsOptions.agent = agent;
73
74
  }
74
- ctx.mqttClient = new mqtt.Client(() => buildStream(options, new WebSocket(host, options.wsOptions), buildProxy()), options);
75
+
76
+ ctx.mqttClient = new mqtt.Client(
77
+ () => buildStream(options, new WebSocket(host, options.wsOptions), buildProxy()),
78
+ options
79
+ );
75
80
  const mqttClient = ctx.mqttClient;
76
81
  global.mqttClient = mqttClient;
82
+
83
+
77
84
  mqttClient.on("error", function (err) {
78
85
  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");
86
+
87
+ if ((ctx._ending || ctx._cycling) && isEndingLikeError(msg)) {
88
+ logger(`mqtt expected during shutdown: ${msg}`, "info");
81
89
  return;
82
90
  }
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);
87
- process.exit(1);
91
+ logger(`mqtt error: ${msg}`, "error");
92
+ try { mqttClient.end(true); } catch (_) { }
93
+ if (ctx._ending || ctx._cycling) return;
94
+
95
+ if (ctx.globalOptions.autoReconnect) {
96
+ scheduleReconnect();
97
+ } else {
98
+
99
+ globalCallback({ type: "stop_listen", error: msg || "Connection refused: Server unavailable" }, null);
100
+ }
88
101
  });
102
+
89
103
  mqttClient.on("connect", function () {
90
104
  if (process.env.OnStatus === undefined) {
91
105
  logger("fca-unoffcial premium", "info");
92
106
  process.env.OnStatus = true;
93
107
  }
94
- topics.forEach(topicsub => mqttClient.subscribe(topicsub));
95
- let topic;
96
- const queue = { sync_api_version: 11, max_deltas_able_to_process: 100, delta_batch_size: 500, encoding: "JSON", entity_fbid: ctx.userID, initial_titan_sequence_id: ctx.lastSeqId, device_params: null };
97
- if (ctx.syncToken) {
98
- topic = "/messenger_sync_get_diffs";
99
- queue.last_seq_id = ctx.lastSeqId;
100
- queue.sync_token = ctx.syncToken;
101
- } else {
102
- topic = "/messenger_sync_create_queue";
103
- queue.initial_titan_sequence_id = ctx.lastSeqId;
104
- queue.device_params = null;
105
- }
108
+ ctx._cycling = false;
109
+
110
+ topics.forEach(t => mqttClient.subscribe(t));
111
+
112
+
113
+ const queue = {
114
+ sync_api_version: 11, max_deltas_able_to_process: 100, delta_batch_size: 500,
115
+ encoding: "JSON", entity_fbid: ctx.userID, initial_titan_sequence_id: ctx.lastSeqId, device_params: null
116
+ };
117
+ const topic = ctx.syncToken ? "/messenger_sync_get_diffs" : "/messenger_sync_create_queue";
118
+ if (ctx.syncToken) { queue.last_seq_id = ctx.lastSeqId; queue.sync_token = ctx.syncToken; }
106
119
  mqttClient.publish(topic, JSON.stringify(queue), { qos: 1, retain: false });
107
120
  mqttClient.publish("/foreground_state", JSON.stringify({ foreground: chatOn }), { qos: 1 });
108
121
  mqttClient.publish("/set_client_settings", JSON.stringify({ make_user_available_when_in_foreground: true }), { qos: 1 });
122
+ const d = (ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || 2000;
109
123
  const rTimeout = setTimeout(function () {
110
- mqttClient.end();
111
- listenMqtt(defaultFuncs, api, ctx, globalCallback);
124
+ logger(`mqtt t_ms timeout, cycling in ${d}ms`, "warn");
125
+ try { mqttClient.end(true); } catch (_) { }
126
+ scheduleReconnect(d);
112
127
  }, 5000);
128
+
113
129
  ctx.tmsWait = function () {
114
130
  clearTimeout(rTimeout);
115
- ctx.globalOptions.emitReady ? globalCallback({ type: "ready", error: null }) : "";
131
+ if (ctx.globalOptions.emitReady) globalCallback({ type: "ready", error: null });
116
132
  delete ctx.tmsWait;
117
133
  };
118
134
  });
119
- mqttClient.on("message", function (topic, message, _packet) {
135
+
136
+ mqttClient.on("message", function (topic, message) {
120
137
  try {
121
138
  let jsonMessage = Buffer.isBuffer(message) ? Buffer.from(message).toString() : message;
122
- try {
123
- jsonMessage = JSON.parse(jsonMessage);
124
- } catch (e) {
125
- jsonMessage = {};
126
- }
139
+ try { jsonMessage = JSON.parse(jsonMessage); } catch (_) { jsonMessage = {}; }
140
+
127
141
  if (jsonMessage.type === "jewel_requests_add") {
128
142
  globalCallback(null, { type: "friend_request_received", actorFbId: jsonMessage.from.toString(), timestamp: Date.now().toString() });
129
143
  } else if (jsonMessage.type === "jewel_requests_remove_old") {
130
144
  globalCallback(null, { type: "friend_request_cancel", actorFbId: jsonMessage.from.toString(), timestamp: Date.now().toString() });
131
145
  } else if (topic === "/t_ms") {
132
- if (ctx.tmsWait && typeof ctx.tmsWait == "function") {
133
- ctx.tmsWait();
134
- }
146
+ if (ctx.tmsWait && typeof ctx.tmsWait == "function") ctx.tmsWait();
135
147
  if (jsonMessage.firstDeltaSeqId && jsonMessage.syncToken) {
136
148
  ctx.lastSeqId = jsonMessage.firstDeltaSeqId;
137
149
  ctx.syncToken = jsonMessage.syncToken;
138
150
  }
139
- if (jsonMessage.lastIssuedSeqId) {
140
- ctx.lastSeqId = parseInt(jsonMessage.lastIssuedSeqId);
141
- }
142
- for (const i in jsonMessage.deltas) {
143
- const delta = jsonMessage.deltas[i];
144
- parseDelta(defaultFuncs, api, ctx, globalCallback, { delta });
151
+ if (jsonMessage.lastIssuedSeqId) ctx.lastSeqId = parseInt(jsonMessage.lastIssuedSeqId);
152
+ for (const dlt of (jsonMessage.deltas || [])) {
153
+ parseDelta(defaultFuncs, api, ctx, globalCallback, { delta: dlt });
145
154
  }
146
155
  } else if (topic === "/thread_typing" || topic === "/orca_typing_notifications") {
147
- const typ = { type: "typ", isTyping: !!jsonMessage.state, from: jsonMessage.sender_fbid.toString(), threadID: formatID((jsonMessage.thread || jsonMessage.sender_fbid).toString()) };
156
+ const typ = {
157
+ type: "typ",
158
+ isTyping: !!jsonMessage.state,
159
+ from: jsonMessage.sender_fbid.toString(),
160
+ threadID: formatID((jsonMessage.thread || jsonMessage.sender_fbid).toString())
161
+ };
148
162
  globalCallback(null, typ);
149
163
  } else if (topic === "/orca_presence") {
150
164
  if (!ctx.globalOptions.updatePresence) {
151
- for (const i in jsonMessage.list) {
152
- const data = jsonMessage.list[i];
153
- const userID = data["u"];
154
- const presence = { type: "presence", userID: userID.toString(), timestamp: data["l"] * 1000, statuses: data["p"] };
165
+ for (const data of (jsonMessage.list || [])) {
166
+ const presence = { type: "presence", userID: String(data.u), timestamp: data.l * 1000, statuses: data.p };
155
167
  globalCallback(null, presence);
156
168
  }
157
169
  }
158
- } else if (topic == "/ls_resp") {
170
+ } else if (topic === "/ls_resp") {
159
171
  const parsedPayload = JSON.parse(jsonMessage.payload);
160
172
  const reqID = jsonMessage.request_id;
161
173
  if (ctx["tasks"].has(reqID)) {
162
174
  const taskData = ctx["tasks"].get(reqID);
163
175
  const { type: taskType, callback: taskCallback } = taskData;
164
176
  const taskRespData = getTaskResponseData(taskType, parsedPayload);
165
- if (taskRespData == null) {
166
- taskCallback("error", null);
167
- } else {
168
- taskCallback(null, Object.assign({ type: taskType, reqID: reqID }, taskRespData));
169
- }
177
+ if (taskRespData == null) taskCallback("error", null);
178
+ else taskCallback(null, Object.assign({ type: taskType, reqID }, taskRespData));
170
179
  }
171
180
  }
172
181
  } catch (ex) {
173
- return;
182
+ logger(`mqtt message parse error: ${ex && ex.message ? ex.message : ex}`, "error");
174
183
  }
175
184
  });
185
+
176
186
  mqttClient.on("close", function () { });
177
187
  mqttClient.on("disconnect", () => { });
178
188
  };
@@ -15,44 +15,54 @@ 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
17
  const getSeqIDFactory = createGetSeqID({ parseAndCheckLogin, listenMqtt, logger });
18
- module.exports = function (defaultFuncs, api, ctx) {
18
+
19
+ const MQTT_DEFAULTS = { cycleMs: 60 * 60 * 1000, reconnectDelayMs: 2000, autoReconnect: true, reconnectAfterStop: false };
20
+ function mqttConf(ctx, overrides) {
21
+ ctx._mqttOpt = Object.assign({}, MQTT_DEFAULTS, ctx._mqttOpt || {}, overrides || {});
22
+ if (typeof ctx._mqttOpt.autoReconnect === "boolean") ctx.globalOptions.autoReconnect = ctx._mqttOpt.autoReconnect;
23
+ return ctx._mqttOpt;
24
+ }
25
+
26
+ module.exports = function (defaultFuncs, api, ctx, opts) {
19
27
  const identity = function () { };
20
28
  let globalCallback = identity;
29
+ let conf = mqttConf(ctx, opts);
30
+
21
31
  function getSeqIDWrapper() {
22
32
  const form = {
23
- av: ctx.userID,
33
+ av: ctx.globalOptions.pageID,
24
34
  queries: JSON.stringify({
25
- o0: { doc_id: "3336396659757871", query_params: { limit: 1, before: null, tags: ["INBOX"], includeDeliveryReceipts: false, includeSeqID: true } }
35
+ o0: {
36
+ doc_id: "3336396659757871",
37
+ query_params: { limit: 1, before: null, tags: ["INBOX"], includeDeliveryReceipts: false, includeSeqID: true }
38
+ }
26
39
  })
27
40
  };
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
- });
41
+ logger("mqtt getSeqID call", "info");
42
+ return getSeqIDFactory(defaultFuncs, api, ctx, globalCallback, form)
43
+ .then(() => { logger("mqtt getSeqID done", "info"); ctx._cycling = false; })
44
+ .catch(e => { logger(`mqtt getSeqID error: ${e && e.message ? e.message : e}`, "error"); });
34
45
  }
46
+
35
47
  function isConnected() {
36
48
  return !!(ctx.mqttClient && ctx.mqttClient.connected);
37
49
  }
50
+
38
51
  function unsubAll(cb) {
39
52
  if (!isConnected()) return cb && cb();
40
53
  let pending = topics.length;
41
54
  if (!pending) return cb && cb();
42
- let done = false;
55
+ let fired = false;
43
56
  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
- }
57
+ ctx.mqttClient.unsubscribe(t, () => {
58
+ if (--pending === 0 && !fired) { fired = true; cb && cb(); }
51
59
  });
52
60
  });
53
61
  }
62
+
54
63
  function endQuietly(next) {
55
64
  const finish = () => {
65
+ try { ctx.mqttClient && ctx.mqttClient.removeAllListeners(); } catch (_) { }
56
66
  ctx.mqttClient = undefined;
57
67
  ctx.lastSeqId = null;
58
68
  ctx.syncToken = undefined;
@@ -62,71 +72,77 @@ module.exports = function (defaultFuncs, api, ctx) {
62
72
  };
63
73
  try {
64
74
  if (ctx.mqttClient) {
65
- if (isConnected()) {
66
- try { ctx.mqttClient.publish("/browser_close", "{}"); } catch (_) { }
67
- }
75
+ if (isConnected()) { try { ctx.mqttClient.publish("/browser_close", "{}"); } catch (_) { } }
68
76
  ctx.mqttClient.end(true, finish);
69
77
  } else finish();
70
- } catch (_) {
71
- finish();
72
- }
78
+ } catch (_) { finish(); }
73
79
  }
80
+
74
81
  function delayedReconnect() {
75
- logger("MQTT reconnect in 2000ms", "info");
76
- setTimeout(() => getSeqIDWrapper(), 2000);
82
+ const d = conf.reconnectDelayMs;
83
+ logger(`mqtt reconnect in ${d}ms`, "info");
84
+ setTimeout(() => getSeqIDWrapper(), d);
77
85
  }
86
+
78
87
  function forceCycle() {
88
+ if (ctx._cycling) return; // đừng cycle chồng
89
+ ctx._cycling = true;
79
90
  ctx._ending = true;
80
- logger("MQTT force cycle begin", "warn");
81
- unsubAll(() => {
82
- endQuietly(() => {
83
- delayedReconnect();
84
- });
85
- });
91
+ logger("mqtt force cycle begin", "warn");
92
+ unsubAll(() => endQuietly(() => delayedReconnect()));
86
93
  }
94
+
87
95
  return function (callback) {
88
96
  class MessageEmitter extends EventEmitter {
89
97
  stopListening(callback2) {
90
98
  const cb = callback2 || function () { };
91
- logger("MQTT stop requested", "info");
99
+ logger("mqtt stop requested", "info");
92
100
  globalCallback = identity;
101
+
93
102
  if (ctx._autoCycleTimer) {
94
103
  clearInterval(ctx._autoCycleTimer);
95
104
  ctx._autoCycleTimer = null;
96
- logger("MQTT auto-cycle cleared", "info");
105
+ logger("mqtt auto-cycle cleared", "info");
97
106
  }
107
+
98
108
  ctx._ending = true;
99
- unsubAll(() => {
100
- endQuietly(() => {
101
- logger("MQTT stopped", "info");
102
- cb();
103
- delayedReconnect();
104
- });
105
- });
109
+ unsubAll(() => endQuietly(() => {
110
+ logger("mqtt stopped", "info");
111
+ cb();
112
+ conf = mqttConf(ctx, conf);
113
+ if (conf.reconnectAfterStop) delayedReconnect();
114
+ }));
106
115
  }
107
116
  async stopListeningAsync() {
108
117
  return new Promise(resolve => { this.stopListening(resolve); });
109
118
  }
110
119
  }
120
+
111
121
  const msgEmitter = new MessageEmitter();
112
122
  globalCallback = callback || function (error, message) {
113
- if (error) { logger("MQTT emit error", "error"); return msgEmitter.emit("error", error); }
123
+ if (error) { logger("mqtt emit error", "error"); return msgEmitter.emit("error", error); }
114
124
  msgEmitter.emit("message", message);
115
125
  };
126
+
127
+ conf = mqttConf(ctx, conf);
116
128
  if (!ctx.firstListen) ctx.lastSeqId = null;
117
129
  ctx.syncToken = undefined;
118
130
  ctx.t_mqttCalled = false;
119
- if (ctx._autoCycleTimer) {
120
- clearInterval(ctx._autoCycleTimer);
121
- ctx._autoCycleTimer = null;
131
+
132
+ if (ctx._autoCycleTimer) { clearInterval(ctx._autoCycleTimer); ctx._autoCycleTimer = null; }
133
+ if (conf.cycleMs && conf.cycleMs > 0) {
134
+ ctx._autoCycleTimer = setInterval(forceCycle, conf.cycleMs);
135
+ logger(`mqtt auto-cycle enabled ${conf.cycleMs}ms`, "info");
136
+ } else {
137
+ logger("mqtt auto-cycle disabled", "info");
122
138
  }
123
- ctx._autoCycleTimer = setInterval(forceCycle, 60 * 60 * 1000);
124
- logger("MQTT auto-cycle enabled 3600000ms", "info");
139
+
125
140
  if (!ctx.firstListen || !ctx.lastSeqId) getSeqIDWrapper();
126
141
  else {
127
- logger("MQTT starting listenMqtt", "info");
142
+ logger("mqtt starting listenMqtt", "info");
128
143
  listenMqtt(defaultFuncs, api, ctx, globalCallback);
129
144
  }
145
+
130
146
  api.stopListening = msgEmitter.stopListening;
131
147
  api.stopListeningAsync = msgEmitter.stopListeningAsync;
132
148
  return msgEmitter;