@dongdev/fca-unofficial 0.0.5 → 0.0.7

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/src/listenMqtt.js CHANGED
@@ -3,97 +3,137 @@
3
3
  const utils = require("../utils");
4
4
  const log = require("npmlog");
5
5
  const mqtt = require('mqtt');
6
- const websocket = require('websocket-stream');
6
+ const WebSocket = require('ws');
7
7
  const HttpsProxyAgent = require('https-proxy-agent');
8
8
  const EventEmitter = require('events');
9
+ const Duplexify = require('duplexify');
10
+ const {
11
+ Transform
12
+ } = require('stream');
9
13
  const debugSeq = false;
10
- var identity = function () { };
14
+ var identity = function() {};
11
15
  var form = {};
12
- var getSeqId = function () { };
13
-
14
- const topics = [
15
- "/legacy_web",
16
- "/webrtc",
17
- "/rtc_multi",
18
- "/onevc",
19
- "/br_sr", //Notification
20
- //Need to publish /br_sr right after this
21
- "/sr_res",
22
- "/t_ms",
23
- "/thread_typing",
24
- "/orca_typing_notifications",
25
- "/notify_disconnect",
26
- //Need to publish /messenger_sync_create_queue right after this
27
- "/orca_presence",
28
- //Will receive /sr_res right here.
29
-
30
- "/legacy_web_mtouch"
31
- // "/inbox",
32
- // "/mercury",
33
- // "/messaging_events",
34
- // "/orca_message_notifications",
35
- // "/pp",
36
- // "/webrtc_response",
37
- ];
38
-
16
+ var getSeqId = function() {};
17
+ const topics = ['/ls_req', '/ls_resp', '/legacy_web', '/webrtc', '/rtc_multi', '/onevc', '/br_sr', '/sr_res', '/t_ms', '/thread_typing', '/orca_typing_notifications', '/notify_disconnect', '/orca_presence', '/inbox', '/mercury', '/messaging_events', '/orca_message_notifications', '/pp', '/webrtc_response'];
18
+ let WebSocket_Global;
19
+ function buildProxy() {
20
+ const Proxy = new Transform({
21
+ objectMode: false,
22
+ transform(chunk, enc, next) {
23
+ if (WebSocket_Global.readyState !== WebSocket_Global.OPEN) {
24
+ return next();
25
+ }
26
+ let data;
27
+ if (typeof chunk === 'string') {
28
+ data = Buffer.from(chunk, 'utf8');
29
+ } else {
30
+ data = chunk;
31
+ }
32
+ WebSocket_Global.send(data);
33
+ next();
34
+ },
35
+ flush(done) {
36
+ WebSocket_Global.close();
37
+ done();
38
+ },
39
+ writev(chunks, cb) {
40
+ const buffers = chunks.map(({ chunk }) => {
41
+ if (typeof chunk === 'string') {
42
+ return Buffer.from(chunk, 'utf8');
43
+ }
44
+ return chunk;
45
+ });
46
+ this._write(Buffer.concat(buffers), 'binary', cb);
47
+ },
48
+ });
49
+ return Proxy;
50
+ }
51
+ function buildStream(options, WebSocket, Proxy) {
52
+ const Stream = Duplexify(undefined, undefined, options);
53
+ Stream.socket = WebSocket;
54
+ WebSocket.onclose = () => {
55
+ Stream.end();
56
+ Stream.destroy();
57
+ };
58
+ WebSocket.onerror = (err) => {
59
+ Stream.destroy(err);
60
+ };
61
+ WebSocket.onmessage = (event) => {
62
+ const data = event.data instanceof ArrayBuffer ? Buffer.from(event.data) : Buffer.from(event.data, 'utf8');
63
+ Stream.push(data);
64
+ };
65
+ WebSocket.onopen = () => {
66
+ Stream.setReadable(Proxy);
67
+ Stream.setWritable(Proxy);
68
+ Stream.emit('connect');
69
+ };
70
+ WebSocket_Global = WebSocket;
71
+ Proxy.on('close', () => WebSocket.close());
72
+ return Stream;
73
+ }
39
74
  function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
40
75
  const chatOn = ctx.globalOptions.online;
41
76
  const foreground = false;
42
- const sessionID = Math.floor(Math.random() * 9007199254740991) + 1;
77
+ const sessionID = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER) + 1;
78
+ const GUID = utils.getGUID();
43
79
  const username = {
44
- u: ctx.i_userID || ctx.userID,
80
+ u: ctx.userID,
45
81
  s: sessionID,
46
82
  chat_on: chatOn,
47
83
  fg: foreground,
48
- d: utils.getGUID(),
49
- ct: "websocket",
50
- aid: "219994525426954",
51
- mqtt_sid: "",
84
+ d: GUID,
85
+ ct: 'websocket',
86
+ aid: '219994525426954',
87
+ aids: null,
88
+ mqtt_sid: '',
52
89
  cp: 3,
53
90
  ecp: 10,
54
91
  st: [],
55
92
  pm: [],
56
- dc: "",
93
+ dc: '',
57
94
  no_auto_fg: true,
58
95
  gas: null,
59
96
  pack: [],
60
- a: ctx.globalOptions.userAgent,
61
- aids: null
97
+ p: null,
98
+ php_override: ""
62
99
  };
63
- const cookies = ctx.jar.getCookies("https://www.facebook.com").join("; ");
100
+ const cookies = ctx.jar.getCookies('https://www.facebook.com').join('; ');
64
101
  let host;
65
102
  if (ctx.mqttEndpoint) {
66
- host = `${ctx.mqttEndpoint}&sid=${sessionID}`;
103
+ host = `${ctx.mqttEndpoint}&sid=${sessionID}&cid=${GUID}`;
67
104
  } else if (ctx.region) {
68
- host = `wss://edge-chat.facebook.com/chat?region=${ctx.region.toLocaleLowerCase()}&sid=${sessionID}`;
105
+ host = `wss://edge-chat.facebook.com/chat?region=${ctx.region.toLowerCase()}&sid=${sessionID}&cid=${GUID}`;
69
106
  } else {
70
- host = `wss://edge-chat.facebook.com/chat?sid=${sessionID}`;
107
+ host = `wss://edge-chat.facebook.com/chat?sid=${sessionID}&cid=${GUID}`;
71
108
  }
72
109
  const options = {
73
- clientId: "mqttwsclient",
110
+ clientId: 'mqttwsclient',
74
111
  protocolId: 'MQIsdp',
75
112
  protocolVersion: 3,
76
113
  username: JSON.stringify(username),
77
114
  clean: true,
78
115
  wsOptions: {
79
116
  headers: {
80
- 'Cookie': cookies,
81
- 'Origin': 'https://www.facebook.com',
82
- 'User-Agent': ctx.globalOptions.userAgent,
83
- 'Referer': 'https://www.facebook.com/',
84
- 'Host': new URL(host).hostname
117
+ Cookie: cookies,
118
+ Origin: 'https://www.facebook.com',
119
+ 'User-Agent': ctx.globalOptions.userAgent || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36',
120
+ Referer: 'https://www.facebook.com/',
121
+ Host: new URL(host).hostname,
85
122
  },
86
123
  origin: 'https://www.facebook.com',
87
- protocolVersion: 13
124
+ protocolVersion: 13,
125
+ binaryType: 'arraybuffer',
88
126
  },
89
- keepalive: 10,
90
- reschedulePings: false
127
+ keepalive: 60,
128
+ reschedulePings: true,
129
+ reconnectPeriod: 2000,
130
+ connectTimeout: 10000,
91
131
  };
92
132
  if (typeof ctx.globalOptions.proxy != "undefined") {
93
133
  const agent = new HttpsProxyAgent(ctx.globalOptions.proxy);
94
134
  options.wsOptions.agent = agent;
95
135
  }
96
- ctx.mqttClient = new mqtt.Client(_ => websocket(host, options.wsOptions), options);
136
+ ctx.mqttClient = new mqtt.Client(() => buildStream(options, new WebSocket(host, options.wsOptions), buildProxy()), options);
97
137
  const mqttClient = ctx.mqttClient;
98
138
  global.mqttClient = mqttClient;
99
139
  mqttClient.on('error', function (err) {
@@ -117,6 +157,7 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
117
157
  });
118
158
  }
119
159
  });
160
+
120
161
  mqttClient.on('close', function () {
121
162
 
122
163
  });
@@ -125,6 +166,7 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
125
166
  topics.forEach(function (topicsub) {
126
167
  mqttClient.subscribe(topicsub);
127
168
  });
169
+
128
170
  let topic;
129
171
  const queue = {
130
172
  sync_api_version: 10,
@@ -148,6 +190,8 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
148
190
  qos: 1,
149
191
  retain: false
150
192
  });
193
+ // set status online
194
+ // fix by NTKhang
151
195
  mqttClient.publish("/foreground_state", JSON.stringify({
152
196
  foreground: chatOn
153
197
  }), {
@@ -158,10 +202,12 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
158
202
  }), {
159
203
  qos: 1
160
204
  });
205
+
161
206
  const rTimeout = setTimeout(function () {
162
207
  mqttClient.end();
163
208
  listenMqtt(defaultFuncs, api, ctx, globalCallback);
164
209
  }, 5000);
210
+
165
211
  ctx.tmsWait = function () {
166
212
  clearTimeout(rTimeout);
167
213
  ctx.globalOptions.emitReady ? globalCallback({
@@ -170,6 +216,7 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
170
216
  }) : "";
171
217
  delete ctx.tmsWait;
172
218
  };
219
+
173
220
  });
174
221
 
175
222
  mqttClient.on('message', function (topic, message, _packet) {
@@ -179,6 +226,7 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
179
226
  } catch (e) {
180
227
  jsonMessage = {};
181
228
  }
229
+
182
230
  if (jsonMessage.type === "jewel_requests_add") {
183
231
  globalCallback(null, {
184
232
  type: "friend_request_received",
@@ -204,6 +252,8 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
204
252
  if (jsonMessage.lastIssuedSeqId) {
205
253
  ctx.lastSeqId = parseInt(jsonMessage.lastIssuedSeqId);
206
254
  }
255
+
256
+ //If it contains more than 1 delta
207
257
  for (const i in jsonMessage.deltas) {
208
258
  const delta = jsonMessage.deltas[i];
209
259
  parseDelta(defaultFuncs, api, ctx, globalCallback, {
@@ -225,9 +275,11 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
225
275
  for (const i in jsonMessage.list) {
226
276
  const data = jsonMessage.list[i];
227
277
  const userID = data["u"];
278
+
228
279
  const presence = {
229
280
  type: "presence",
230
281
  userID: userID.toString(),
282
+ //Convert to ms
231
283
  timestamp: data["l"] * 1000,
232
284
  statuses: data["p"]
233
285
  };
@@ -237,12 +289,19 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
237
289
  }
238
290
  }
239
291
  }
292
+
240
293
  });
294
+
241
295
  }
242
296
 
243
297
  function parseDelta(defaultFuncs, api, ctx, globalCallback, v) {
244
298
  if (v.delta.class == "NewMessage") {
245
- if (ctx.globalOptions.pageID && ctx.globalOptions.pageID != v.queue) return;
299
+ //Not tested for pages
300
+ if (ctx.globalOptions.pageID &&
301
+ ctx.globalOptions.pageID != v.queue
302
+ )
303
+ return;
304
+
246
305
  (function resolveAttachmentUrl(i) {
247
306
  if (i == (v.delta.attachments || []).length) {
248
307
  let fmtMsg;
@@ -335,6 +394,7 @@ function parseDelta(defaultFuncs, api, ctx, globalCallback, v) {
335
394
  });
336
395
  })();
337
396
  } else if (delta.deltaMessageReply) {
397
+ //Mention block - #1
338
398
  let mdata =
339
399
  delta.deltaMessageReply.message === undefined ? [] :
340
400
  delta.deltaMessageReply.message.data === undefined ? [] :
@@ -746,82 +806,123 @@ function markDelivery(ctx, api, threadID, messageID) {
746
806
 
747
807
  module.exports = function (defaultFuncs, api, ctx) {
748
808
  let globalCallback = identity;
809
+ let sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
749
810
  getSeqId = function getSeqId() {
750
811
  ctx.t_mqttCalled = false;
751
- const forms = {
752
- av: ctx.userID || i_userID,
753
- fb_dtsg: ctx.fb_dtsg,
754
- queries: JSON.stringify({
755
- o0: {
756
- doc_id: '3336396659757871',
757
- query_params: {
758
- limit: 1,
759
- before: null,
760
- tags: ['INBOX'],
761
- includeDeliveryReceipts: false,
762
- includeSeqID: true,
763
- },
764
- },
765
- }),
766
- __user: ctx.userID,
767
- __a: '1',
768
- __req: '8',
769
- __hs: '19577.HYP:comet_pkg.2.1..2.1',
770
- dpr: '1',
771
- fb_api_caller_class: 'RelayModern',
772
- fb_api_req_friendly_name: 'MessengerGraphQLThreadlistFetcher',
773
- };
774
- const headers = {
775
- 'Content-Type': 'application/x-www-form-urlencoded',
776
- Referer: 'https://www.facebook.com/',
777
- Origin: 'https://www.facebook.com',
778
- 'sec-fetch-site': 'same-origin',
779
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
780
- Cookie: ctx.jar.getCookieString('https://www.facebook.com'),
781
- accept: '*/*',
782
- 'accept-encoding': 'gzip, deflate, br',
783
- };
784
- defaultFuncs
785
- .post('https://www.facebook.com/api/graphqlbatch/', ctx.jar, forms, { headers })
786
- .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
787
- .then((resData) => {
788
- if (utils.getType(resData) !== "Array") {
812
+ async function attemptRequest(retries = 3) {
813
+ try {
814
+ if (!ctx.fb_dtsg) {
815
+ const dtsg = await api.getFreshDtsg();
816
+ if (!dtsg) {
817
+ if (retries > 0) {
818
+ console.log("Failed to get fb_dtsg, retrying...");
819
+ await sleep(2000);
820
+ return attemptRequest(retries - 1);
821
+ }
822
+ throw {
823
+ error: "Could not obtain fb_dtsg after multiple attempts"
824
+ };
825
+ }
826
+ ctx.fb_dtsg = dtsg;
827
+ }
828
+ const form = {
829
+ av: ctx.userID,
830
+ fb_dtsg: ctx.fb_dtsg,
831
+ queries: JSON.stringify({
832
+ o0: {
833
+ doc_id: '3336396659757871',
834
+ query_params: {
835
+ limit: 1,
836
+ before: null,
837
+ tags: ['INBOX'],
838
+ includeDeliveryReceipts: false,
839
+ includeSeqID: true
840
+ }
841
+ }
842
+ }),
843
+ __user: ctx.userID,
844
+ __a: '1',
845
+ __req: '8',
846
+ __hs: '19577.HYP:comet_pkg.2.1..2.1',
847
+ dpr: '1',
848
+ fb_api_caller_class: 'RelayModern',
849
+ fb_api_req_friendly_name: 'MessengerGraphQLThreadlistFetcher'
850
+ };
851
+ const headers = {
852
+ 'Content-Type': 'application/x-www-form-urlencoded',
853
+ 'Referer': 'https://www.facebook.com/',
854
+ 'Origin': 'https://www.facebook.com',
855
+ 'sec-fetch-site': 'same-origin',
856
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
857
+ 'Cookie': ctx.jar.getCookieString('https://www.facebook.com'),
858
+ 'accept': '*/*',
859
+ 'accept-encoding': 'gzip, deflate, br'
860
+ };
861
+
862
+ const resData = await defaultFuncs
863
+ .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form, {
864
+ headers
865
+ })
866
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs));
867
+ if (debugSeq) {
868
+ console.log('GraphQL SeqID Response:', JSON.stringify(resData, null, 2));
869
+ }
870
+
871
+ if (resData.error === 1357004 || resData.error === 1357001) {
872
+ if (retries > 0) {
873
+ console.log("Session error, refreshing token and retrying...");
874
+ ctx.fb_dtsg = null;
875
+ await sleep(2000);
876
+ return attemptRequest(retries - 1);
877
+ }
789
878
  throw {
790
- error: "Not logged in",
791
- res: resData,
879
+ error: "Session refresh failed after retries"
792
880
  };
793
881
  }
794
- if (resData && resData[resData.length - 1].error_results > 0) {
795
- throw resData[0].o0.errors;
796
- }
797
- if (resData[resData.length - 1].successful_results === 0) {
882
+
883
+ if (!Array.isArray(resData)) {
798
884
  throw {
799
- error: "getSeqId: there was no successful_results",
800
- res: resData,
885
+ error: "Invalid response format",
886
+ res: resData
801
887
  };
802
888
  }
803
- if (resData[0].o0.data.viewer.message_threads.sync_sequence_id) {
804
- ctx.lastSeqId =
805
- resData[0].o0.data.viewer.message_threads.sync_sequence_id;
806
- listenMqtt(defaultFuncs, api, ctx, globalCallback);
807
- } else {
889
+
890
+ const seqID = resData[0]?.o0?.data?.viewer?.message_threads?.sync_sequence_id;
891
+ if (!seqID) {
808
892
  throw {
809
- error: "getSeqId: no sync_sequence_id found.",
810
- res: resData,
893
+ error: "Missing sync_sequence_id",
894
+ res: resData
811
895
  };
812
896
  }
813
- })
897
+
898
+ ctx.lastSeqId = seqID;
899
+ if (debugSeq) {
900
+ console.log('Got SeqID:', ctx.lastSeqId);
901
+ }
902
+
903
+ return listenMqtt(defaultFuncs, api, ctx, globalCallback);
904
+
905
+ } catch (err) {
906
+ if (retries > 0) {
907
+ console.log("Request failed, retrying...");
908
+
909
+ return attemptRequest(retries - 1);
910
+ }
911
+ throw err;
912
+ }
913
+ }
914
+
915
+ return attemptRequest()
814
916
  .catch((err) => {
815
917
  log.error("getSeqId", err);
816
- if (utils.getType(err) === "Object" && err.error === "Not logged in") {
817
- ctx.loggedIn = false;
818
- }
918
+ if (utils.getType(err) == "Object" && err.error === "Not logged in") ctx.loggedIn = false;
819
919
  return globalCallback(err);
820
920
  });
821
- };
921
+ }
822
922
  return function (callback) {
823
923
  class MessageEmitter extends EventEmitter {
824
924
  stopListening(callback) {
925
+
825
926
  callback = callback || (() => { });
826
927
  globalCallback = identity;
827
928
  if (ctx.mqttClient) {
@@ -829,50 +930,41 @@ module.exports = function (defaultFuncs, api, ctx) {
829
930
  ctx.mqttClient.unsubscribe("/rtc_multi");
830
931
  ctx.mqttClient.unsubscribe("/onevc");
831
932
  ctx.mqttClient.publish("/browser_close", "{}");
832
- ctx.mqttClient.end(false, () => {
933
+ ctx.mqttClient.end(false, function (...data) {
934
+ callback(data);
833
935
  ctx.mqttClient = undefined;
834
- callback();
835
936
  });
836
- } else {
837
- callback();
838
937
  }
839
938
  }
939
+
840
940
  async stopListeningAsync() {
841
941
  return new Promise((resolve) => {
842
942
  this.stopListening(resolve);
843
943
  });
844
944
  }
845
945
  }
946
+
846
947
  const msgEmitter = new MessageEmitter();
847
- globalCallback = callback || function (error, message) {
948
+ globalCallback = (callback || function (error, message) {
848
949
  if (error) {
849
950
  return msgEmitter.emit("error", error);
850
951
  }
851
952
  msgEmitter.emit("message", message);
852
- };
853
- if (!ctx.firstListen) ctx.lastSeqId = null;
953
+ });
954
+
955
+ if (!ctx.firstListen)
956
+ ctx.lastSeqId = null;
854
957
  ctx.syncToken = undefined;
855
958
  ctx.t_mqttCalled = false;
856
- form = {
857
- av: ctx.globalOptions.pageID,
858
- queries: JSON.stringify({
859
- o0: {
860
- doc_id: "3336396659757871",
861
- query_params: {
862
- limit: 1,
863
- before: null,
864
- tags: ["INBOX"],
865
- includeDeliveryReceipts: false,
866
- includeSeqID: true,
867
- },
868
- },
869
- }),
870
- };
871
- if (!ctx.firstListen || !ctx.lastSeqId) getSeqId();
872
- else listenMqtt(defaultFuncs, api, ctx, globalCallback);
873
- ctx.firstListen = false;
874
- api.stopListening = msgEmitter.stopListening.bind(msgEmitter);
875
- api.stopListeningAsync = msgEmitter.stopListeningAsync.bind(msgEmitter);
959
+
960
+ if (!ctx.firstListen || !ctx.lastSeqId) {
961
+ getSeqId(defaultFuncs, api, ctx, globalCallback);
962
+ } else {
963
+ listenMqtt(defaultFuncs, api, ctx, globalCallback);
964
+ }
965
+
966
+ api.stopListening = msgEmitter.stopListening;
967
+ api.stopListeningAsync = msgEmitter.stopListeningAsync;
876
968
  return msgEmitter;
877
969
  };
878
970
  };