alicezetion 1.9.5 → 1.9.7

Sign up to get free protection for your applications and to get access to all the features.
package/src/listenMqtt.js CHANGED
@@ -10,844 +10,845 @@ const EventEmitter = require('events');
10
10
  const identity = function () { };
11
11
 
12
12
  const topics = [
13
- "/legacy_web",
14
- "/webrtc",
15
- "/rtc_multi",
16
- "/onevc",
17
- "/br_sr", //Notification
18
- //Need to publish /br_sr right after this
19
- "/sr_res",
20
- "/t_ms",
21
- "/thread_typing",
22
- "/orca_typing_notifications",
23
- "/notify_disconnect",
24
- //Need to publish /messenger_sync_create_queue right after this
25
- "/orca_presence",
26
- //Will receive /sr_res right here.
27
-
28
- "/legacy_web_mtouch"
29
- // "/inbox",
30
- // "/mercury",
31
- // "/messaging_events",
32
- // "/orca_message_notifications",
33
- // "/pp",
34
- // "/webrtc_response",
13
+ "/legacy_web",
14
+ "/webrtc",
15
+ "/rtc_multi",
16
+ "/onevc",
17
+ "/br_sr", //Notification
18
+ //Need to publish /br_sr right after this
19
+ "/sr_res",
20
+ "/t_ms",
21
+ "/thread_typing",
22
+ "/orca_typing_notifications",
23
+ "/notify_disconnect",
24
+ //Need to publish /messenger_sync_create_queue right after this
25
+ "/orca_presence",
26
+ //Will receive /sr_res right here.
27
+
28
+ "/legacy_web_mtouch"
29
+ // "/inbox",
30
+ // "/mercury",
31
+ // "/messaging_events",
32
+ // "/orca_message_notifications",
33
+ // "/pp",
34
+ // "/webrtc_response",
35
35
  ];
36
36
 
37
37
  function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
38
- //Don't really know what this does but I think it's for the active state?
39
- //TODO: Move to ctx when implemented
40
- const chatOn = ctx.globalOptions.online;
41
- const foreground = false;
42
-
43
- const sessionID = Math.floor(Math.random() * 9007199254740991) + 1;
44
- const username = {
45
- u: ctx.i_userID || ctx.userID,
46
- s: sessionID,
47
- chat_on: chatOn,
48
- fg: foreground,
49
- d: utils.getGUID(),
50
- ct: "websocket",
51
- //App id from facebook
52
- aid: "219994525426954",
53
- mqtt_sid: "",
54
- cp: 3,
55
- ecp: 10,
56
- st: [],
57
- pm: [],
58
- dc: "",
59
- no_auto_fg: true,
60
- gas: null,
61
- pack: [],
62
- a: ctx.globalOptions.userAgent,
63
- aids: null
64
- };
65
- const cookies = ctx.jar.getCookies("https://www.facebook.com").join("; ");
66
-
67
- let host;
68
- if (ctx.mqttEndpoint) {
69
- host = `${ctx.mqttEndpoint}&sid=${sessionID}`;
70
- } else if (ctx.region) {
71
- host = `wss://edge-chat.facebook.com/chat?region=${ctx.region.toLocaleLowerCase()}&sid=${sessionID}`;
72
- } else {
73
- host = `wss://edge-chat.facebook.com/chat?sid=${sessionID}`;
74
- }
75
-
76
- const options = {
77
- clientId: "mqttwsclient",
78
- protocolId: 'MQIsdp',
79
- protocolVersion: 3,
80
- username: JSON.stringify(username),
81
- clean: true,
82
- wsOptions: {
83
- headers: {
84
- 'Cookie': cookies,
85
- 'Origin': 'https://www.facebook.com',
86
- 'User-Agent': ctx.globalOptions.userAgent,
87
- 'Referer': 'https://www.facebook.com/',
88
- 'Host': new URL(host).hostname //'edge-chat.facebook.com'
89
- },
90
- origin: 'https://www.facebook.com',
91
- protocolVersion: 13
92
- },
93
- keepalive: 10,
94
- reschedulePings: false
95
- };
96
-
97
- if (typeof ctx.globalOptions.proxy != "undefined") {
98
- const agent = new HttpsProxyAgent(ctx.globalOptions.proxy);
99
- options.wsOptions.agent = agent;
100
- }
101
-
102
- ctx.mqttClient = new mqtt.Client(_ => websocket(host, options.wsOptions), options);
103
-
104
- const mqttClient = ctx.mqttClient;
105
-
106
- mqttClient.on('error', function (err) {
107
- log.error("listenMqtt", err);
108
- mqttClient.end();
109
- if (ctx.globalOptions.autoReconnect) {
110
- listenMqtt(defaultFuncs, api, ctx, globalCallback);
111
- } else {
112
- utils.checkLiveCookie(ctx, defaultFuncs)
113
- .then(res => {
114
- globalCallback({
115
- type: "stop_listen",
116
- error: "Connection refused: Server unavailable"
117
- }, null);
118
- })
119
- .catch(err => {
120
- globalCallback({
121
- type: "account_inactive",
122
- error: "Maybe your account is blocked by facebook, please login and check at https://facebook.com"
123
- }, null);
124
- });
125
- }
126
- });
127
-
128
- mqttClient.on('close', function () {
129
-
130
- });
131
-
132
- mqttClient.on('connect', function () {
133
- topics.forEach(function (topicsub) {
134
- mqttClient.subscribe(topicsub);
135
- });
136
-
137
- let topic;
138
- const queue = {
139
- sync_api_version: 10,
140
- max_deltas_able_to_process: 1000,
141
- delta_batch_size: 500,
142
- encoding: "JSON",
143
- entity_fbid: ctx.i_userID || ctx.userID
144
- };
145
-
146
- if (ctx.syncToken) {
147
- topic = "/messenger_sync_get_diffs";
148
- queue.last_seq_id = ctx.lastSeqId;
149
- queue.sync_token = ctx.syncToken;
150
- } else {
151
- topic = "/messenger_sync_create_queue";
152
- queue.initial_titan_sequence_id = ctx.lastSeqId;
153
- queue.device_params = null;
154
- }
155
-
156
- mqttClient.publish(topic, JSON.stringify(queue), { qos: 1, retain: false });
157
- // set status online
158
- // fix by NTKhang
159
- mqttClient.publish("/foreground_state", JSON.stringify({ foreground: chatOn }), { qos: 1 });
160
- mqttClient.publish("/set_client_settings", JSON.stringify({ make_user_available_when_in_foreground: true }), { qos: 1 });
161
-
162
- const rTimeout = setTimeout(function () {
163
- mqttClient.end();
164
- listenMqtt(defaultFuncs, api, ctx, globalCallback);
165
- }, 5000);
166
-
167
- ctx.tmsWait = function () {
168
- clearTimeout(rTimeout);
169
- ctx.globalOptions.emitReady ? globalCallback({
170
- type: "ready",
171
- error: null
172
- }) : "";
173
- delete ctx.tmsWait;
174
- };
175
-
176
- });
177
-
178
- mqttClient.on('message', function (topic, message, _packet) {
179
- let jsonMessage = Buffer.isBuffer(message) ? Buffer.from(message).toString() : message;
180
- try {
181
- jsonMessage = JSON.parse(jsonMessage);
182
- }
183
- catch (e) {
184
- jsonMessage = {};
185
- }
186
-
187
- if (jsonMessage.type === "jewel_requests_add") {
188
- globalCallback(null, {
189
- type: "friend_request_received",
190
- actorFbId: jsonMessage.from.toString(),
191
- timestamp: Date.now().toString()
192
- });
193
- }
194
- else if (jsonMessage.type === "jewel_requests_remove_old") {
195
- globalCallback(null, {
196
- type: "friend_request_cancel",
197
- actorFbId: jsonMessage.from.toString(),
198
- timestamp: Date.now().toString()
199
- });
200
- }
201
- else if (topic === "/t_ms") {
202
- if (ctx.tmsWait && typeof ctx.tmsWait == "function") {
203
- ctx.tmsWait();
204
- }
205
-
206
- if (jsonMessage.firstDeltaSeqId && jsonMessage.syncToken) {
207
- ctx.lastSeqId = jsonMessage.firstDeltaSeqId;
208
- ctx.syncToken = jsonMessage.syncToken;
209
- }
210
-
211
- if (jsonMessage.lastIssuedSeqId) {
212
- ctx.lastSeqId = parseInt(jsonMessage.lastIssuedSeqId);
213
- }
214
-
215
- //If it contains more than 1 delta
216
- for (const i in jsonMessage.deltas) {
217
- const delta = jsonMessage.deltas[i];
218
- parseDelta(defaultFuncs, api, ctx, globalCallback, { "delta": delta });
219
- }
220
- } else if (topic === "/thread_typing" || topic === "/orca_typing_notifications") {
221
- const typ = {
222
- type: "typ",
223
- isTyping: !!jsonMessage.state,
224
- from: jsonMessage.sender_fbid.toString(),
225
- threadID: utils.formatID((jsonMessage.thread || jsonMessage.sender_fbid).toString())
226
- };
227
- (function () { globalCallback(null, typ); })();
228
- } else if (topic === "/orca_presence") {
229
- if (!ctx.globalOptions.updatePresence) {
230
- for (const i in jsonMessage.list) {
231
- const data = jsonMessage.list[i];
232
- const userID = data["u"];
233
-
234
- const presence = {
235
- type: "presence",
236
- userID: userID.toString(),
237
- //Convert to ms
238
- timestamp: data["l"] * 1000,
239
- statuses: data["p"]
240
- };
241
- (function () { globalCallback(null, presence); })();
242
- }
243
- }
244
- }
245
-
246
- });
247
-
38
+ //Don't really know what this does but I think it's for the active state?
39
+ //TODO: Move to ctx when implemented
40
+ const chatOn = ctx.globalOptions.online;
41
+ const foreground = false;
42
+
43
+ const sessionID = Math.floor(Math.random() * 9007199254740991) + 1;
44
+ const username = {
45
+ u: ctx.i_userID || ctx.userID,
46
+ s: sessionID,
47
+ chat_on: chatOn,
48
+ fg: foreground,
49
+ d: utils.getGUID(),
50
+ ct: "websocket",
51
+ //App id from facebook
52
+ aid: "219994525426954",
53
+ mqtt_sid: "",
54
+ cp: 3,
55
+ ecp: 10,
56
+ st: [],
57
+ pm: [],
58
+ dc: "",
59
+ no_auto_fg: true,
60
+ gas: null,
61
+ pack: [],
62
+ a: ctx.globalOptions.userAgent,
63
+ aids: null
64
+ };
65
+ const cookies = ctx.jar.getCookies("https://www.facebook.com").join("; ");
66
+
67
+ let host;
68
+ if (ctx.mqttEndpoint) {
69
+ host = `${ctx.mqttEndpoint}&sid=${sessionID}`;
70
+ } else if (ctx.region) {
71
+ host = `wss://edge-chat.facebook.com/chat?region=${ctx.region.toLocaleLowerCase()}&sid=${sessionID}`;
72
+ } else {
73
+ host = `wss://edge-chat.facebook.com/chat?sid=${sessionID}`;
74
+ }
75
+
76
+ const options = {
77
+ clientId: "mqttwsclient",
78
+ protocolId: 'MQIsdp',
79
+ protocolVersion: 3,
80
+ username: JSON.stringify(username),
81
+ clean: true,
82
+ wsOptions: {
83
+ headers: {
84
+ 'Cookie': cookies,
85
+ 'Origin': 'https://www.facebook.com',
86
+ 'User-Agent': ctx.globalOptions.userAgent,
87
+ 'Referer': 'https://www.facebook.com/',
88
+ 'Host': new URL(host).hostname //'edge-chat.facebook.com'
89
+ },
90
+ origin: 'https://www.facebook.com',
91
+ protocolVersion: 13
92
+ },
93
+ keepalive: 10,
94
+ reschedulePings: false
95
+ };
96
+
97
+ if (typeof ctx.globalOptions.proxy != "undefined") {
98
+ const agent = new HttpsProxyAgent(ctx.globalOptions.proxy);
99
+ options.wsOptions.agent = agent;
100
+ }
101
+
102
+ ctx.mqttClient = new mqtt.Client(_ => websocket(host, options.wsOptions), options);
103
+
104
+ const mqttClient = ctx.mqttClient;
105
+
106
+ mqttClient.on('error', function (err) {
107
+ log.error("listenMqtt", err);
108
+ mqttClient.end();
109
+ if (ctx.globalOptions.autoReconnect) {
110
+ listenMqtt(defaultFuncs, api, ctx, globalCallback);
111
+ } else {
112
+ utils.checkLiveCookie(ctx, defaultFuncs)
113
+ .then(res => {
114
+ globalCallback({
115
+ type: "stop_listen",
116
+ error: "Connection refused: Server unavailable"
117
+ }, null);
118
+ })
119
+ .catch(err => {
120
+ globalCallback({
121
+ type: "account_inactive",
122
+ error: "Maybe your account is blocked by facebook, please login and check at https://facebook.com"
123
+ }, null);
124
+ });
125
+ }
126
+ });
127
+
128
+ mqttClient.on('close', function () {
129
+
130
+ });
131
+
132
+ mqttClient.on('connect', function () {
133
+ topics.forEach(function (topicsub) {
134
+ mqttClient.subscribe(topicsub);
135
+ });
136
+
137
+ let topic;
138
+ const queue = {
139
+ sync_api_version: 10,
140
+ max_deltas_able_to_process: 1000,
141
+ delta_batch_size: 500,
142
+ encoding: "JSON",
143
+ entity_fbid: ctx.i_userID || ctx.userID
144
+ };
145
+
146
+ if (ctx.syncToken) {
147
+ topic = "/messenger_sync_get_diffs";
148
+ queue.last_seq_id = ctx.lastSeqId;
149
+ queue.sync_token = ctx.syncToken;
150
+ } else {
151
+ topic = "/messenger_sync_create_queue";
152
+ queue.initial_titan_sequence_id = ctx.lastSeqId;
153
+ queue.device_params = null;
154
+ }
155
+
156
+ mqttClient.publish(topic, JSON.stringify(queue), { qos: 1, retain: false });
157
+ // set status online
158
+ // fix by NTKhang
159
+ mqttClient.publish("/foreground_state", JSON.stringify({ foreground: chatOn }), { qos: 1 });
160
+ mqttClient.publish("/set_client_settings", JSON.stringify({ make_user_available_when_in_foreground: true }), { qos: 1 });
161
+
162
+ const rTimeout = setTimeout(function () {
163
+ mqttClient.end();
164
+ listenMqtt(defaultFuncs, api, ctx, globalCallback);
165
+ }, 5000);
166
+
167
+ ctx.tmsWait = function () {
168
+ clearTimeout(rTimeout);
169
+ ctx.globalOptions.emitReady ? globalCallback({
170
+ type: "ready",
171
+ error: null
172
+ }) : "";
173
+ delete ctx.tmsWait;
174
+ };
175
+
176
+ });
177
+
178
+ mqttClient.on('message', function (topic, message, _packet) {
179
+ let jsonMessage = Buffer.isBuffer(message) ? Buffer.from(message).toString() : message;
180
+ try {
181
+ jsonMessage = JSON.parse(jsonMessage);
182
+ }
183
+ catch (e) {
184
+ jsonMessage = {};
185
+ }
186
+
187
+ if (jsonMessage.type === "jewel_requests_add") {
188
+ globalCallback(null, {
189
+ type: "friend_request_received",
190
+ actorFbId: jsonMessage.from.toString(),
191
+ timestamp: Date.now().toString()
192
+ });
193
+ }
194
+ else if (jsonMessage.type === "jewel_requests_remove_old") {
195
+ globalCallback(null, {
196
+ type: "friend_request_cancel",
197
+ actorFbId: jsonMessage.from.toString(),
198
+ timestamp: Date.now().toString()
199
+ });
200
+ }
201
+ else if (topic === "/t_ms") {
202
+ if (ctx.tmsWait && typeof ctx.tmsWait == "function") {
203
+ ctx.tmsWait();
204
+ }
205
+
206
+ if (jsonMessage.firstDeltaSeqId && jsonMessage.syncToken) {
207
+ ctx.lastSeqId = jsonMessage.firstDeltaSeqId;
208
+ ctx.syncToken = jsonMessage.syncToken;
209
+ }
210
+
211
+ if (jsonMessage.lastIssuedSeqId) {
212
+ ctx.lastSeqId = parseInt(jsonMessage.lastIssuedSeqId);
213
+ }
214
+
215
+ //If it contains more than 1 delta
216
+ for (const i in jsonMessage.deltas) {
217
+ const delta = jsonMessage.deltas[i];
218
+ parseDelta(defaultFuncs, api, ctx, globalCallback, { "delta": delta });
219
+ }
220
+ } else if (topic === "/thread_typing" || topic === "/orca_typing_notifications") {
221
+ const typ = {
222
+ type: "typ",
223
+ isTyping: !!jsonMessage.state,
224
+ from: jsonMessage.sender_fbid.toString(),
225
+ threadID: utils.formatID((jsonMessage.thread || jsonMessage.sender_fbid).toString())
226
+ };
227
+ (function () { globalCallback(null, typ); })();
228
+ } else if (topic === "/orca_presence") {
229
+ if (!ctx.globalOptions.updatePresence) {
230
+ for (const i in jsonMessage.list) {
231
+ const data = jsonMessage.list[i];
232
+ const userID = data["u"];
233
+
234
+ const presence = {
235
+ type: "presence",
236
+ userID: userID.toString(),
237
+ //Convert to ms
238
+ timestamp: data["l"] * 1000,
239
+ statuses: data["p"]
240
+ };
241
+ (function () { globalCallback(null, presence); })();
242
+ }
243
+ }
244
+ }
245
+
246
+ });
247
+ mqttClient.on('close', function () {
248
+ globalCallback("Connection closed.");
249
+ });
248
250
  }
249
251
 
250
252
  function parseDelta(defaultFuncs, api, ctx, globalCallback, v) {
251
- if (v.delta.class == "NewMessage") {
252
- //Not tested for pages
253
- if (ctx.globalOptions.pageID &&
254
- ctx.globalOptions.pageID != v.queue
255
- )
256
- return;
257
-
258
- (function resolveAttachmentUrl(i) {
259
- if (i == (v.delta.attachments || []).length) {
260
- let fmtMsg;
261
- try {
262
- fmtMsg = utils.formatDeltaMessage(v);
263
- } catch (err) {
264
- return globalCallback({
265
- error: "Problem parsing message object. Please open an issue at https://github.com/ntkhang03/fb-chat-api/issues.",
266
- detail: err,
267
- res: v,
268
- type: "parse_error"
269
- });
270
- }
271
- if (fmtMsg) {
272
- if (ctx.globalOptions.autoMarkDelivery) {
273
- markDelivery(ctx, api, fmtMsg.threadID, fmtMsg.messageID);
274
- }
275
- }
276
- return !ctx.globalOptions.selfListen &&
277
- (fmtMsg.senderID === ctx.i_userID || fmtMsg.senderID === ctx.userID) ?
278
- undefined :
279
- (function () { globalCallback(null, fmtMsg); })();
280
- } else {
281
- if (v.delta.attachments[i].mercury.attach_type == "photo") {
282
- api.resolvePhotoUrl(
283
- v.delta.attachments[i].fbid,
284
- (err, url) => {
285
- if (!err)
286
- v.delta.attachments[
287
- i
288
- ].mercury.metadata.url = url;
289
- return resolveAttachmentUrl(i + 1);
290
- }
291
- );
292
- } else {
293
- return resolveAttachmentUrl(i + 1);
294
- }
295
- }
296
- })(0);
297
- }
298
-
299
- if (v.delta.class == "ClientPayload") {
300
- const clientPayload = utils.decodeClientPayload(
301
- v.delta.payload
302
- );
303
-
304
- if (clientPayload && clientPayload.deltas) {
305
- for (const i in clientPayload.deltas) {
306
- const delta = clientPayload.deltas[i];
307
- if (delta.deltaMessageReaction && !!ctx.globalOptions.listenEvents) {
308
- (function () {
309
- globalCallback(null, {
310
- type: "message_reaction",
311
- threadID: (delta.deltaMessageReaction.threadKey
312
- .threadFbId ?
313
- delta.deltaMessageReaction.threadKey.threadFbId : delta.deltaMessageReaction.threadKey
314
- .otherUserFbId).toString(),
315
- messageID: delta.deltaMessageReaction.messageId,
316
- reaction: delta.deltaMessageReaction.reaction,
317
- senderID: delta.deltaMessageReaction.senderId == 0 ? delta.deltaMessageReaction.userId.toString() : delta.deltaMessageReaction.senderId.toString(),
318
- userID: (delta.deltaMessageReaction.userId || delta.deltaMessageReaction.senderId).toString()
319
- });
320
- })();
321
- } else if (delta.deltaRecallMessageData && !!ctx.globalOptions.listenEvents) {
322
- (function () {
323
- globalCallback(null, {
324
- type: "message_unsend",
325
- threadID: (delta.deltaRecallMessageData.threadKey.threadFbId ?
326
- delta.deltaRecallMessageData.threadKey.threadFbId : delta.deltaRecallMessageData.threadKey
327
- .otherUserFbId).toString(),
328
- messageID: delta.deltaRecallMessageData.messageID,
329
- senderID: delta.deltaRecallMessageData.senderID.toString(),
330
- deletionTimestamp: delta.deltaRecallMessageData.deletionTimestamp,
331
- timestamp: delta.deltaRecallMessageData.timestamp
332
- });
333
- })();
334
- } else if (delta.deltaRemoveMessage && !!ctx.globalOptions.listenEvents) {
335
- (function () {
336
- globalCallback(null, {
337
- type: "message_self_delete",
338
- threadID: (delta.deltaRemoveMessage.threadKey.threadFbId ?
339
- delta.deltaRemoveMessage.threadKey.threadFbId : delta.deltaRemoveMessage.threadKey
340
- .otherUserFbId).toString(),
341
- messageID: delta.deltaRemoveMessage.messageIds.length == 1 ? delta.deltaRemoveMessage.messageIds[0] : delta.deltaRemoveMessage.messageIds,
342
- senderID: api.getCurrentUserID(),
343
- deletionTimestamp: delta.deltaRemoveMessage.deletionTimestamp,
344
- timestamp: delta.deltaRemoveMessage.timestamp
345
- });
346
- })();
347
- }
348
- else if (delta.deltaMessageReply) {
349
- //Mention block - #1
350
- let mdata =
351
- delta.deltaMessageReply.message === undefined ? [] :
352
- delta.deltaMessageReply.message.data === undefined ? [] :
353
- delta.deltaMessageReply.message.data.prng === undefined ? [] :
354
- JSON.parse(delta.deltaMessageReply.message.data.prng);
355
- let m_id = mdata.map(u => u.i);
356
- let m_offset = mdata.map(u => u.o);
357
- let m_length = mdata.map(u => u.l);
358
-
359
- const mentions = {};
360
-
361
- for (let i = 0; i < m_id.length; i++) {
362
- mentions[m_id[i]] = (delta.deltaMessageReply.message.body || "").substring(
363
- m_offset[i],
364
- m_offset[i] + m_length[i]
365
- );
366
- }
367
- //Mention block - 1#
368
- const callbackToReturn = {
369
- type: "message_reply",
370
- threadID: (delta.deltaMessageReply.message.messageMetadata.threadKey.threadFbId ?
371
- delta.deltaMessageReply.message.messageMetadata.threadKey.threadFbId : delta.deltaMessageReply.message.messageMetadata.threadKey
372
- .otherUserFbId).toString(),
373
- messageID: delta.deltaMessageReply.message.messageMetadata.messageId,
374
- senderID: delta.deltaMessageReply.message.messageMetadata.actorFbId.toString(),
375
- attachments: (delta.deltaMessageReply.message.attachments || []).map(function (att) {
376
- const mercury = JSON.parse(att.mercuryJSON);
377
- Object.assign(att, mercury);
378
- return att;
379
- }).map(att => {
380
- let x;
381
- try {
382
- x = utils._formatAttachment(att);
383
- } catch (ex) {
384
- x = att;
385
- x.error = ex;
386
- x.type = "unknown";
387
- }
388
- return x;
389
- }),
390
- body: delta.deltaMessageReply.message.body || "",
391
- isGroup: !!delta.deltaMessageReply.message.messageMetadata.threadKey.threadFbId,
392
- mentions: mentions,
393
- timestamp: delta.deltaMessageReply.message.messageMetadata.timestamp,
394
- participantIDs: (delta.deltaMessageReply.message.messageMetadata.cid.canonicalParticipantFbids || delta.deltaMessageReply.message.participants || []).map(e => e.toString())
395
- };
396
-
397
- if (delta.deltaMessageReply.repliedToMessage) {
398
- //Mention block - #2
399
- mdata =
400
- delta.deltaMessageReply.repliedToMessage === undefined ? [] :
401
- delta.deltaMessageReply.repliedToMessage.data === undefined ? [] :
402
- delta.deltaMessageReply.repliedToMessage.data.prng === undefined ? [] :
403
- JSON.parse(delta.deltaMessageReply.repliedToMessage.data.prng);
404
- m_id = mdata.map(u => u.i);
405
- m_offset = mdata.map(u => u.o);
406
- m_length = mdata.map(u => u.l);
407
-
408
- const rmentions = {};
409
-
410
- for (let i = 0; i < m_id.length; i++) {
411
- rmentions[m_id[i]] = (delta.deltaMessageReply.repliedToMessage.body || "").substring(
412
- m_offset[i],
413
- m_offset[i] + m_length[i]
414
- );
415
- }
416
- //Mention block - 2#
417
- callbackToReturn.messageReply = {
418
- threadID: (delta.deltaMessageReply.repliedToMessage.messageMetadata.threadKey.threadFbId ?
419
- delta.deltaMessageReply.repliedToMessage.messageMetadata.threadKey.threadFbId : delta.deltaMessageReply.repliedToMessage.messageMetadata.threadKey
420
- .otherUserFbId).toString(),
421
- messageID: delta.deltaMessageReply.repliedToMessage.messageMetadata.messageId,
422
- senderID: delta.deltaMessageReply.repliedToMessage.messageMetadata.actorFbId.toString(),
423
- attachments: delta.deltaMessageReply.repliedToMessage.attachments.map(function (att) {
424
- const mercury = JSON.parse(att.mercuryJSON);
425
- Object.assign(att, mercury);
426
- return att;
427
- }).map(att => {
428
- let x;
429
- try {
430
- x = utils._formatAttachment(att);
431
- } catch (ex) {
432
- x = att;
433
- x.error = ex;
434
- x.type = "unknown";
435
- }
436
- return x;
437
- }),
438
- body: delta.deltaMessageReply.repliedToMessage.body || "",
439
- isGroup: !!delta.deltaMessageReply.repliedToMessage.messageMetadata.threadKey.threadFbId,
440
- mentions: rmentions,
441
- timestamp: delta.deltaMessageReply.repliedToMessage.messageMetadata.timestamp
442
- };
443
- } else if (delta.deltaMessageReply.replyToMessageId) {
444
- return defaultFuncs
445
- .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, {
446
- "av": ctx.globalOptions.pageID,
447
- "queries": JSON.stringify({
448
- "o0": {
449
- //Using the same doc_id as forcedFetch
450
- "doc_id": "2848441488556444",
451
- "query_params": {
452
- "thread_and_message_id": {
453
- "thread_id": callbackToReturn.threadID,
454
- "message_id": delta.deltaMessageReply.replyToMessageId.id
455
- }
456
- }
457
- }
458
- })
459
- })
460
- .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
461
- .then((resData) => {
462
- if (resData[resData.length - 1].error_results > 0) {
463
- throw resData[0].o0.errors;
464
- }
465
-
466
- if (resData[resData.length - 1].successful_results === 0) {
467
- throw { error: "forcedFetch: there was no successful_results", res: resData };
468
- }
469
-
470
- const fetchData = resData[0].o0.data.message;
471
-
472
- const mobj = {};
473
- for (const n in fetchData.message.ranges) {
474
- mobj[fetchData.message.ranges[n].entity.id] = (fetchData.message.text || "").substr(fetchData.message.ranges[n].offset, fetchData.message.ranges[n].length);
475
- }
476
-
477
- callbackToReturn.messageReply = {
478
- threadID: callbackToReturn.threadID,
479
- messageID: fetchData.message_id,
480
- senderID: fetchData.message_sender.id.toString(),
481
- attachments: fetchData.message.blob_attachment.map(att => {
482
- let x;
483
- try {
484
- x = utils._formatAttachment({
485
- blob_attachment: att
486
- });
487
- } catch (ex) {
488
- x = att;
489
- x.error = ex;
490
- x.type = "unknown";
491
- }
492
- return x;
493
- }),
494
- body: fetchData.message.text || "",
495
- isGroup: callbackToReturn.isGroup,
496
- mentions: mobj,
497
- timestamp: parseInt(fetchData.timestamp_precise)
498
- };
499
- })
500
- .catch((err) => {
501
- log.error("forcedFetch", err);
502
- })
503
- .finally(function () {
504
- if (ctx.globalOptions.autoMarkDelivery) {
505
- markDelivery(ctx, api, callbackToReturn.threadID, callbackToReturn.messageID);
506
- }
507
- !ctx.globalOptions.selfListen &&
508
- (callbackToReturn.senderID === ctx.i_userID || callbackToReturn.senderID === ctx.userID) ?
509
- undefined :
510
- (function () { globalCallback(null, callbackToReturn); })();
511
- });
512
- } else {
513
- callbackToReturn.delta = delta;
514
- }
515
-
516
- if (ctx.globalOptions.autoMarkDelivery) {
517
- markDelivery(ctx, api, callbackToReturn.threadID, callbackToReturn.messageID);
518
- }
519
-
520
- return !ctx.globalOptions.selfListen &&
521
- (callbackToReturn.senderID === ctx.i_userID || callbackToReturn.senderID === ctx.userID) ?
522
- undefined :
523
- (function () { globalCallback(null, callbackToReturn); })();
524
- }
525
- }
526
- return;
527
- }
528
- }
529
-
530
- if (v.delta.class !== "NewMessage" &&
531
- !ctx.globalOptions.listenEvents
532
- )
533
- return;
534
-
535
- switch (v.delta.class) {
536
- case "ReadReceipt":
537
- var fmtMsg;
538
- try {
539
- fmtMsg = utils.formatDeltaReadReceipt(v.delta);
540
- }
541
- catch (err) {
542
- return globalCallback({
543
- error: "Problem parsing message object. Please open an issue at https://github.com/ntkhang03/fb-chat-api/issues.",
544
- detail: err,
545
- res: v.delta,
546
- type: "parse_error"
547
- });
548
- }
549
- return (function () { globalCallback(null, fmtMsg); })();
550
- case "AdminTextMessage":
551
- switch (v.delta.type) {
552
- case "change_thread_theme":
553
- case "change_thread_nickname":
554
- case "change_thread_icon":
555
- case "change_thread_quick_reaction":
556
- case "change_thread_admins":
557
- case "group_poll":
558
- case "joinable_group_link_mode_change":
559
- case "magic_words":
560
- case "change_thread_approval_mode":
561
- case "messenger_call_log":
562
- case "participant_joined_group_call":
563
- var fmtMsg;
564
- try {
565
- fmtMsg = utils.formatDeltaEvent(v.delta);
566
- }
567
- catch (err) {
568
- return globalCallback({
569
- error: "Problem parsing message object. Please open an issue at https://github.com/ntkhang03/fb-chat-api/issues.",
570
- detail: err,
571
- res: v.delta,
572
- type: "parse_error"
573
- });
574
- }
575
- return (function () { globalCallback(null, fmtMsg); })();
576
- default:
577
- return;
578
- }
579
- //For group images
580
- case "ForcedFetch":
581
- if (!v.delta.threadKey) return;
582
- var mid = v.delta.messageId;
583
- var tid = v.delta.threadKey.threadFbId;
584
- if (mid && tid) {
585
- const form = {
586
- "av": ctx.globalOptions.pageID,
587
- "queries": JSON.stringify({
588
- "o0": {
589
- //This doc_id is valid as of March 25, 2020
590
- "doc_id": "2848441488556444",
591
- "query_params": {
592
- "thread_and_message_id": {
593
- "thread_id": tid.toString(),
594
- "message_id": mid
595
- }
596
- }
597
- }
598
- })
599
- };
600
-
601
- defaultFuncs
602
- .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form)
603
- .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
604
- .then((resData) => {
605
- if (resData[resData.length - 1].error_results > 0) {
606
- throw resData[0].o0.errors;
607
- }
608
-
609
- if (resData[resData.length - 1].successful_results === 0) {
610
- throw { error: "forcedFetch: there was no successful_results", res: resData };
611
- }
612
-
613
- const fetchData = resData[0].o0.data.message;
614
-
615
- if (utils.getType(fetchData) == "Object") {
616
- log.info("forcedFetch", fetchData);
617
- switch (fetchData.__typename) {
618
- case "ThreadImageMessage":
619
- (!ctx.globalOptions.selfListenEvent && (fetchData.message_sender.id.toString() === ctx.i_userID || fetchData.message_sender.id.toString() === ctx.userID)) || !ctx.loggedIn ?
620
- undefined :
621
- (function () {
622
- globalCallback(null, {
623
- type: "event",
624
- threadID: utils.formatID(tid.toString()),
625
- messageID: fetchData.message_id,
626
- logMessageType: "log:thread-image",
627
- logMessageData: {
628
- attachmentID: fetchData.image_with_metadata && fetchData.image_with_metadata.legacy_attachment_id,
629
- width: fetchData.image_with_metadata && fetchData.image_with_metadata.original_dimensions.x,
630
- height: fetchData.image_with_metadata && fetchData.image_with_metadata.original_dimensions.y,
631
- url: fetchData.image_with_metadata && fetchData.image_with_metadata.preview.uri
632
- },
633
- logMessageBody: fetchData.snippet,
634
- timestamp: fetchData.timestamp_precise,
635
- author: fetchData.message_sender.id
636
- });
637
- })();
638
- break;
639
- case "UserMessage":
640
- log.info("ff-Return", {
641
- type: "message",
642
- senderID: utils.formatID(fetchData.message_sender.id),
643
- body: fetchData.message.text || "",
644
- threadID: utils.formatID(tid.toString()),
645
- messageID: fetchData.message_id,
646
- attachments: [{
647
- type: "share",
648
- ID: fetchData.extensible_attachment.legacy_attachment_id,
649
- url: fetchData.extensible_attachment.story_attachment.url,
650
-
651
- title: fetchData.extensible_attachment.story_attachment.title_with_entities.text,
652
- description: fetchData.extensible_attachment.story_attachment.description.text,
653
- source: fetchData.extensible_attachment.story_attachment.source,
654
-
655
- image: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).uri,
656
- width: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).width,
657
- height: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).height,
658
- playable: (fetchData.extensible_attachment.story_attachment.media || {}).is_playable || false,
659
- duration: (fetchData.extensible_attachment.story_attachment.media || {}).playable_duration_in_ms || 0,
660
-
661
- subattachments: fetchData.extensible_attachment.subattachments,
662
- properties: fetchData.extensible_attachment.story_attachment.properties
663
- }],
664
- mentions: {},
665
- timestamp: parseInt(fetchData.timestamp_precise),
666
- participantIDs: (fetchData.participants || (fetchData.messageMetadata ? fetchData.messageMetadata.cid ? fetchData.messageMetadata.cid.canonicalParticipantFbids : fetchData.messageMetadata.participantIds : []) || []),
667
- isGroup: (fetchData.message_sender.id != tid.toString())
668
- });
669
- globalCallback(null, {
670
- type: "message",
671
- senderID: utils.formatID(fetchData.message_sender.id),
672
- body: fetchData.message.text || "",
673
- threadID: utils.formatID(tid.toString()),
674
- messageID: fetchData.message_id,
675
- attachments: [{
676
- type: "share",
677
- ID: fetchData.extensible_attachment.legacy_attachment_id,
678
- url: fetchData.extensible_attachment.story_attachment.url,
679
-
680
- title: fetchData.extensible_attachment.story_attachment.title_with_entities.text,
681
- description: fetchData.extensible_attachment.story_attachment.description.text,
682
- source: fetchData.extensible_attachment.story_attachment.source,
683
-
684
- image: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).uri,
685
- width: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).width,
686
- height: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).height,
687
- playable: (fetchData.extensible_attachment.story_attachment.media || {}).is_playable || false,
688
- duration: (fetchData.extensible_attachment.story_attachment.media || {}).playable_duration_in_ms || 0,
689
-
690
- subattachments: fetchData.extensible_attachment.subattachments,
691
- properties: fetchData.extensible_attachment.story_attachment.properties
692
- }],
693
- mentions: {},
694
- timestamp: parseInt(fetchData.timestamp_precise),
695
- participantIDs: (fetchData.participants || (fetchData.messageMetadata ? fetchData.messageMetadata.cid ? fetchData.messageMetadata.cid.canonicalParticipantFbids : fetchData.messageMetadata.participantIds : []) || []),
696
- isGroup: (fetchData.message_sender.id != tid.toString())
697
- });
698
- }
699
- } else {
700
- log.error("forcedFetch", fetchData);
701
- }
702
- })
703
- .catch((err) => {
704
- log.error("forcedFetch", err);
705
- });
706
- }
707
- break;
708
- case "ThreadName":
709
- case "ParticipantsAddedToGroupThread":
710
- case "ParticipantLeftGroupThread":
711
- case "ApprovalQueue":
712
- var formattedEvent;
713
- try {
714
- formattedEvent = utils.formatDeltaEvent(v.delta);
715
- } catch (err) {
716
- return globalCallback({
717
- error: "Problem parsing message object. Please open an issue at https://github.com/ntkhang03/fb-chat-api/issues.",
718
- detail: err,
719
- res: v.delta,
720
- type: "parse_error"
721
- });
722
- }
723
- return (!ctx.globalOptions.selfListenEvent && (formattedEvent.author.toString() === ctx.i_userID || formattedEvent.author.toString() === ctx.userID)) || !ctx.loggedIn ?
724
- undefined :
725
- (function () { globalCallback(null, formattedEvent); })();
726
- }
253
+ if (v.delta.class == "NewMessage") {
254
+ //Not tested for pages
255
+ if (ctx.globalOptions.pageID &&
256
+ ctx.globalOptions.pageID != v.queue
257
+ )
258
+ return;
259
+
260
+ (function resolveAttachmentUrl(i) {
261
+ if (i == (v.delta.attachments || []).length) {
262
+ let fmtMsg;
263
+ try {
264
+ fmtMsg = utils.formatDeltaMessage(v);
265
+ } catch (err) {
266
+ return globalCallback({
267
+ error: "Problem parsing message object. Please open an issue at https://github.com/ntkhang03/fb-chat-api/issues.",
268
+ detail: err,
269
+ res: v,
270
+ type: "parse_error"
271
+ });
272
+ }
273
+ if (fmtMsg) {
274
+ if (ctx.globalOptions.autoMarkDelivery) {
275
+ markDelivery(ctx, api, fmtMsg.threadID, fmtMsg.messageID);
276
+ }
277
+ }
278
+ return !ctx.globalOptions.selfListen &&
279
+ (fmtMsg.senderID === ctx.i_userID || fmtMsg.senderID === ctx.userID) ?
280
+ undefined :
281
+ (function () { globalCallback(null, fmtMsg); })();
282
+ } else {
283
+ if (v.delta.attachments[i].mercury.attach_type == "photo") {
284
+ api.resolvePhotoUrl(
285
+ v.delta.attachments[i].fbid,
286
+ (err, url) => {
287
+ if (!err)
288
+ v.delta.attachments[
289
+ i
290
+ ].mercury.metadata.url = url;
291
+ return resolveAttachmentUrl(i + 1);
292
+ }
293
+ );
294
+ } else {
295
+ return resolveAttachmentUrl(i + 1);
296
+ }
297
+ }
298
+ })(0);
299
+ }
300
+
301
+ if (v.delta.class == "ClientPayload") {
302
+ const clientPayload = utils.decodeClientPayload(
303
+ v.delta.payload
304
+ );
305
+
306
+ if (clientPayload && clientPayload.deltas) {
307
+ for (const i in clientPayload.deltas) {
308
+ const delta = clientPayload.deltas[i];
309
+ if (delta.deltaMessageReaction && !!ctx.globalOptions.listenEvents) {
310
+ (function () {
311
+ globalCallback(null, {
312
+ type: "message_reaction",
313
+ threadID: (delta.deltaMessageReaction.threadKey
314
+ .threadFbId ?
315
+ delta.deltaMessageReaction.threadKey.threadFbId : delta.deltaMessageReaction.threadKey
316
+ .otherUserFbId).toString(),
317
+ messageID: delta.deltaMessageReaction.messageId,
318
+ reaction: delta.deltaMessageReaction.reaction,
319
+ senderID: delta.deltaMessageReaction.senderId == 0 ? delta.deltaMessageReaction.userId.toString() : delta.deltaMessageReaction.senderId.toString(),
320
+ userID: (delta.deltaMessageReaction.userId || delta.deltaMessageReaction.senderId).toString()
321
+ });
322
+ })();
323
+ } else if (delta.deltaRecallMessageData && !!ctx.globalOptions.listenEvents) {
324
+ (function () {
325
+ globalCallback(null, {
326
+ type: "message_unsend",
327
+ threadID: (delta.deltaRecallMessageData.threadKey.threadFbId ?
328
+ delta.deltaRecallMessageData.threadKey.threadFbId : delta.deltaRecallMessageData.threadKey
329
+ .otherUserFbId).toString(),
330
+ messageID: delta.deltaRecallMessageData.messageID,
331
+ senderID: delta.deltaRecallMessageData.senderID.toString(),
332
+ deletionTimestamp: delta.deltaRecallMessageData.deletionTimestamp,
333
+ timestamp: delta.deltaRecallMessageData.timestamp
334
+ });
335
+ })();
336
+ } else if (delta.deltaRemoveMessage && !!ctx.globalOptions.listenEvents) {
337
+ (function () {
338
+ globalCallback(null, {
339
+ type: "message_self_delete",
340
+ threadID: (delta.deltaRemoveMessage.threadKey.threadFbId ?
341
+ delta.deltaRemoveMessage.threadKey.threadFbId : delta.deltaRemoveMessage.threadKey
342
+ .otherUserFbId).toString(),
343
+ messageID: delta.deltaRemoveMessage.messageIds.length == 1 ? delta.deltaRemoveMessage.messageIds[0] : delta.deltaRemoveMessage.messageIds,
344
+ senderID: api.getCurrentUserID(),
345
+ deletionTimestamp: delta.deltaRemoveMessage.deletionTimestamp,
346
+ timestamp: delta.deltaRemoveMessage.timestamp
347
+ });
348
+ })();
349
+ }
350
+ else if (delta.deltaMessageReply) {
351
+ //Mention block - #1
352
+ let mdata =
353
+ delta.deltaMessageReply.message === undefined ? [] :
354
+ delta.deltaMessageReply.message.data === undefined ? [] :
355
+ delta.deltaMessageReply.message.data.prng === undefined ? [] :
356
+ JSON.parse(delta.deltaMessageReply.message.data.prng);
357
+ let m_id = mdata.map(u => u.i);
358
+ let m_offset = mdata.map(u => u.o);
359
+ let m_length = mdata.map(u => u.l);
360
+
361
+ const mentions = {};
362
+
363
+ for (let i = 0; i < m_id.length; i++) {
364
+ mentions[m_id[i]] = (delta.deltaMessageReply.message.body || "").substring(
365
+ m_offset[i],
366
+ m_offset[i] + m_length[i]
367
+ );
368
+ }
369
+ //Mention block - 1#
370
+ const callbackToReturn = {
371
+ type: "message_reply",
372
+ threadID: (delta.deltaMessageReply.message.messageMetadata.threadKey.threadFbId ?
373
+ delta.deltaMessageReply.message.messageMetadata.threadKey.threadFbId : delta.deltaMessageReply.message.messageMetadata.threadKey
374
+ .otherUserFbId).toString(),
375
+ messageID: delta.deltaMessageReply.message.messageMetadata.messageId,
376
+ senderID: delta.deltaMessageReply.message.messageMetadata.actorFbId.toString(),
377
+ attachments: (delta.deltaMessageReply.message.attachments || []).map(function (att) {
378
+ const mercury = JSON.parse(att.mercuryJSON);
379
+ Object.assign(att, mercury);
380
+ return att;
381
+ }).map(att => {
382
+ let x;
383
+ try {
384
+ x = utils._formatAttachment(att);
385
+ } catch (ex) {
386
+ x = att;
387
+ x.error = ex;
388
+ x.type = "unknown";
389
+ }
390
+ return x;
391
+ }),
392
+ body: delta.deltaMessageReply.message.body || "",
393
+ isGroup: !!delta.deltaMessageReply.message.messageMetadata.threadKey.threadFbId,
394
+ mentions: mentions,
395
+ timestamp: delta.deltaMessageReply.message.messageMetadata.timestamp,
396
+ participantIDs: (delta.deltaMessageReply.message.messageMetadata.cid.canonicalParticipantFbids || delta.deltaMessageReply.message.participants || []).map(e => e.toString())
397
+ };
398
+
399
+ if (delta.deltaMessageReply.repliedToMessage) {
400
+ //Mention block - #2
401
+ mdata =
402
+ delta.deltaMessageReply.repliedToMessage === undefined ? [] :
403
+ delta.deltaMessageReply.repliedToMessage.data === undefined ? [] :
404
+ delta.deltaMessageReply.repliedToMessage.data.prng === undefined ? [] :
405
+ JSON.parse(delta.deltaMessageReply.repliedToMessage.data.prng);
406
+ m_id = mdata.map(u => u.i);
407
+ m_offset = mdata.map(u => u.o);
408
+ m_length = mdata.map(u => u.l);
409
+
410
+ const rmentions = {};
411
+
412
+ for (let i = 0; i < m_id.length; i++) {
413
+ rmentions[m_id[i]] = (delta.deltaMessageReply.repliedToMessage.body || "").substring(
414
+ m_offset[i],
415
+ m_offset[i] + m_length[i]
416
+ );
417
+ }
418
+ //Mention block - 2#
419
+ callbackToReturn.messageReply = {
420
+ threadID: (delta.deltaMessageReply.repliedToMessage.messageMetadata.threadKey.threadFbId ?
421
+ delta.deltaMessageReply.repliedToMessage.messageMetadata.threadKey.threadFbId : delta.deltaMessageReply.repliedToMessage.messageMetadata.threadKey
422
+ .otherUserFbId).toString(),
423
+ messageID: delta.deltaMessageReply.repliedToMessage.messageMetadata.messageId,
424
+ senderID: delta.deltaMessageReply.repliedToMessage.messageMetadata.actorFbId.toString(),
425
+ attachments: delta.deltaMessageReply.repliedToMessage.attachments.map(function (att) {
426
+ const mercury = JSON.parse(att.mercuryJSON);
427
+ Object.assign(att, mercury);
428
+ return att;
429
+ }).map(att => {
430
+ let x;
431
+ try {
432
+ x = utils._formatAttachment(att);
433
+ } catch (ex) {
434
+ x = att;
435
+ x.error = ex;
436
+ x.type = "unknown";
437
+ }
438
+ return x;
439
+ }),
440
+ body: delta.deltaMessageReply.repliedToMessage.body || "",
441
+ isGroup: !!delta.deltaMessageReply.repliedToMessage.messageMetadata.threadKey.threadFbId,
442
+ mentions: rmentions,
443
+ timestamp: delta.deltaMessageReply.repliedToMessage.messageMetadata.timestamp
444
+ };
445
+ } else if (delta.deltaMessageReply.replyToMessageId) {
446
+ return defaultFuncs
447
+ .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, {
448
+ "av": ctx.globalOptions.pageID,
449
+ "queries": JSON.stringify({
450
+ "o0": {
451
+ //Using the same doc_id as forcedFetch
452
+ "doc_id": "2848441488556444",
453
+ "query_params": {
454
+ "thread_and_message_id": {
455
+ "thread_id": callbackToReturn.threadID,
456
+ "message_id": delta.deltaMessageReply.replyToMessageId.id
457
+ }
458
+ }
459
+ }
460
+ })
461
+ })
462
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
463
+ .then((resData) => {
464
+ if (resData[resData.length - 1].error_results > 0) {
465
+ throw resData[0].o0.errors;
466
+ }
467
+
468
+ if (resData[resData.length - 1].successful_results === 0) {
469
+ throw { error: "forcedFetch: there was no successful_results", res: resData };
470
+ }
471
+
472
+ const fetchData = resData[0].o0.data.message;
473
+
474
+ const mobj = {};
475
+ for (const n in fetchData.message.ranges) {
476
+ mobj[fetchData.message.ranges[n].entity.id] = (fetchData.message.text || "").substr(fetchData.message.ranges[n].offset, fetchData.message.ranges[n].length);
477
+ }
478
+
479
+ callbackToReturn.messageReply = {
480
+ threadID: callbackToReturn.threadID,
481
+ messageID: fetchData.message_id,
482
+ senderID: fetchData.message_sender.id.toString(),
483
+ attachments: fetchData.message.blob_attachment.map(att => {
484
+ let x;
485
+ try {
486
+ x = utils._formatAttachment({
487
+ blob_attachment: att
488
+ });
489
+ } catch (ex) {
490
+ x = att;
491
+ x.error = ex;
492
+ x.type = "unknown";
493
+ }
494
+ return x;
495
+ }),
496
+ body: fetchData.message.text || "",
497
+ isGroup: callbackToReturn.isGroup,
498
+ mentions: mobj,
499
+ timestamp: parseInt(fetchData.timestamp_precise)
500
+ };
501
+ })
502
+ .catch((err) => {
503
+ log.error("forcedFetch", err);
504
+ })
505
+ .finally(function () {
506
+ if (ctx.globalOptions.autoMarkDelivery) {
507
+ markDelivery(ctx, api, callbackToReturn.threadID, callbackToReturn.messageID);
508
+ }
509
+ !ctx.globalOptions.selfListen &&
510
+ (callbackToReturn.senderID === ctx.i_userID || callbackToReturn.senderID === ctx.userID) ?
511
+ undefined :
512
+ (function () { globalCallback(null, callbackToReturn); })();
513
+ });
514
+ } else {
515
+ callbackToReturn.delta = delta;
516
+ }
517
+
518
+ if (ctx.globalOptions.autoMarkDelivery) {
519
+ markDelivery(ctx, api, callbackToReturn.threadID, callbackToReturn.messageID);
520
+ }
521
+
522
+ return !ctx.globalOptions.selfListen &&
523
+ (callbackToReturn.senderID === ctx.i_userID || callbackToReturn.senderID === ctx.userID) ?
524
+ undefined :
525
+ (function () { globalCallback(null, callbackToReturn); })();
526
+ }
527
+ }
528
+ return;
529
+ }
530
+ }
531
+
532
+ if (v.delta.class !== "NewMessage" &&
533
+ !ctx.globalOptions.listenEvents
534
+ )
535
+ return;
536
+
537
+ switch (v.delta.class) {
538
+ case "ReadReceipt":
539
+ var fmtMsg;
540
+ try {
541
+ fmtMsg = utils.formatDeltaReadReceipt(v.delta);
542
+ }
543
+ catch (err) {
544
+ return globalCallback({
545
+ error: "Problem parsing message object. Please open an issue at https://github.com/ntkhang03/fb-chat-api/issues.",
546
+ detail: err,
547
+ res: v.delta,
548
+ type: "parse_error"
549
+ });
550
+ }
551
+ return (function () { globalCallback(null, fmtMsg); })();
552
+ case "AdminTextMessage":
553
+ switch (v.delta.type) {
554
+ case "change_thread_theme":
555
+ case "change_thread_nickname":
556
+ case "change_thread_icon":
557
+ case "change_thread_admins":
558
+ case "group_poll":
559
+ case "joinable_group_link_mode_change":
560
+ case "magic_words":
561
+ case "change_thread_approval_mode":
562
+ case "messenger_call_log":
563
+ case "participant_joined_group_call":
564
+ var fmtMsg;
565
+ try {
566
+ fmtMsg = utils.formatDeltaEvent(v.delta);
567
+ }
568
+ catch (err) {
569
+ return globalCallback({
570
+ error: "Problem parsing message object. Please open an issue at https://github.com/ntkhang03/fb-chat-api/issues.",
571
+ detail: err,
572
+ res: v.delta,
573
+ type: "parse_error"
574
+ });
575
+ }
576
+ return (function () { globalCallback(null, fmtMsg); })();
577
+ default:
578
+ return;
579
+ }
580
+ //For group images
581
+ case "ForcedFetch":
582
+ if (!v.delta.threadKey) return;
583
+ var mid = v.delta.messageId;
584
+ var tid = v.delta.threadKey.threadFbId;
585
+ if (mid && tid) {
586
+ const form = {
587
+ "av": ctx.globalOptions.pageID,
588
+ "queries": JSON.stringify({
589
+ "o0": {
590
+ //This doc_id is valid as of March 25, 2020
591
+ "doc_id": "2848441488556444",
592
+ "query_params": {
593
+ "thread_and_message_id": {
594
+ "thread_id": tid.toString(),
595
+ "message_id": mid
596
+ }
597
+ }
598
+ }
599
+ })
600
+ };
601
+
602
+ defaultFuncs
603
+ .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form)
604
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
605
+ .then((resData) => {
606
+ if (resData[resData.length - 1].error_results > 0) {
607
+ throw resData[0].o0.errors;
608
+ }
609
+
610
+ if (resData[resData.length - 1].successful_results === 0) {
611
+ throw { error: "forcedFetch: there was no successful_results", res: resData };
612
+ }
613
+
614
+ const fetchData = resData[0].o0.data.message;
615
+
616
+ if (utils.getType(fetchData) == "Object") {
617
+ log.info("forcedFetch", fetchData);
618
+ switch (fetchData.__typename) {
619
+ case "ThreadImageMessage":
620
+ (!ctx.globalOptions.selfListenEvent && (fetchData.message_sender.id.toString() === ctx.i_userID || fetchData.message_sender.id.toString() === ctx.userID)) || !ctx.loggedIn ?
621
+ undefined :
622
+ (function () {
623
+ globalCallback(null, {
624
+ type: "event",
625
+ threadID: utils.formatID(tid.toString()),
626
+ messageID: fetchData.message_id,
627
+ logMessageType: "log:thread-image",
628
+ logMessageData: {
629
+ attachmentID: fetchData.image_with_metadata && fetchData.image_with_metadata.legacy_attachment_id,
630
+ width: fetchData.image_with_metadata && fetchData.image_with_metadata.original_dimensions.x,
631
+ height: fetchData.image_with_metadata && fetchData.image_with_metadata.original_dimensions.y,
632
+ url: fetchData.image_with_metadata && fetchData.image_with_metadata.preview.uri
633
+ },
634
+ logMessageBody: fetchData.snippet,
635
+ timestamp: fetchData.timestamp_precise,
636
+ author: fetchData.message_sender.id
637
+ });
638
+ })();
639
+ break;
640
+ case "UserMessage":
641
+ log.info("ff-Return", {
642
+ type: "message",
643
+ senderID: utils.formatID(fetchData.message_sender.id),
644
+ body: fetchData.message.text || "",
645
+ threadID: utils.formatID(tid.toString()),
646
+ messageID: fetchData.message_id,
647
+ attachments: [{
648
+ type: "share",
649
+ ID: fetchData.extensible_attachment.legacy_attachment_id,
650
+ url: fetchData.extensible_attachment.story_attachment.url,
651
+
652
+ title: fetchData.extensible_attachment.story_attachment.title_with_entities.text,
653
+ description: fetchData.extensible_attachment.story_attachment.description.text,
654
+ source: fetchData.extensible_attachment.story_attachment.source,
655
+
656
+ image: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).uri,
657
+ width: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).width,
658
+ height: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).height,
659
+ playable: (fetchData.extensible_attachment.story_attachment.media || {}).is_playable || false,
660
+ duration: (fetchData.extensible_attachment.story_attachment.media || {}).playable_duration_in_ms || 0,
661
+
662
+ subattachments: fetchData.extensible_attachment.subattachments,
663
+ properties: fetchData.extensible_attachment.story_attachment.properties
664
+ }],
665
+ mentions: {},
666
+ timestamp: parseInt(fetchData.timestamp_precise),
667
+ participantIDs: (fetchData.participants || (fetchData.messageMetadata ? fetchData.messageMetadata.cid ? fetchData.messageMetadata.cid.canonicalParticipantFbids : fetchData.messageMetadata.participantIds : []) || []),
668
+ isGroup: (fetchData.message_sender.id != tid.toString())
669
+ });
670
+ globalCallback(null, {
671
+ type: "message",
672
+ senderID: utils.formatID(fetchData.message_sender.id),
673
+ body: fetchData.message.text || "",
674
+ threadID: utils.formatID(tid.toString()),
675
+ messageID: fetchData.message_id,
676
+ attachments: [{
677
+ type: "share",
678
+ ID: fetchData.extensible_attachment.legacy_attachment_id,
679
+ url: fetchData.extensible_attachment.story_attachment.url,
680
+
681
+ title: fetchData.extensible_attachment.story_attachment.title_with_entities.text,
682
+ description: fetchData.extensible_attachment.story_attachment.description.text,
683
+ source: fetchData.extensible_attachment.story_attachment.source,
684
+
685
+ image: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).uri,
686
+ width: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).width,
687
+ height: ((fetchData.extensible_attachment.story_attachment.media || {}).image || {}).height,
688
+ playable: (fetchData.extensible_attachment.story_attachment.media || {}).is_playable || false,
689
+ duration: (fetchData.extensible_attachment.story_attachment.media || {}).playable_duration_in_ms || 0,
690
+
691
+ subattachments: fetchData.extensible_attachment.subattachments,
692
+ properties: fetchData.extensible_attachment.story_attachment.properties
693
+ }],
694
+ mentions: {},
695
+ timestamp: parseInt(fetchData.timestamp_precise),
696
+ participantIDs: (fetchData.participants || (fetchData.messageMetadata ? fetchData.messageMetadata.cid ? fetchData.messageMetadata.cid.canonicalParticipantFbids : fetchData.messageMetadata.participantIds : []) || []),
697
+ isGroup: (fetchData.message_sender.id != tid.toString())
698
+ });
699
+ }
700
+ } else {
701
+ log.error("forcedFetch", fetchData);
702
+ }
703
+ })
704
+ .catch((err) => {
705
+ log.error("forcedFetch", err);
706
+ });
707
+ }
708
+ break;
709
+ case "ThreadName":
710
+ case "ParticipantsAddedToGroupThread":
711
+ case "ParticipantLeftGroupThread":
712
+ case "ApprovalQueue":
713
+ var formattedEvent;
714
+ try {
715
+ formattedEvent = utils.formatDeltaEvent(v.delta);
716
+ } catch (err) {
717
+ return globalCallback({
718
+ error: "Problem parsing message object. Please open an issue at https://github.com/ntkhang03/fb-chat-api/issues.",
719
+ detail: err,
720
+ res: v.delta,
721
+ type: "parse_error"
722
+ });
723
+ }
724
+ return (!ctx.globalOptions.selfListenEvent && (formattedEvent.author.toString() === ctx.i_userID || formattedEvent.author.toString() === ctx.userID)) || !ctx.loggedIn ?
725
+ undefined :
726
+ (function () { globalCallback(null, formattedEvent); })();
727
+ }
727
728
  }
728
729
 
729
730
  function markDelivery(ctx, api, threadID, messageID) {
730
- if (threadID && messageID) {
731
- api.markAsDelivered(threadID, messageID, (err) => {
732
- if (err) {
733
- log.error("markAsDelivered", err);
734
- } else {
735
- if (ctx.globalOptions.autoMarkRead) {
736
- api.markAsRead(threadID, (err) => {
737
- if (err) {
738
- log.error("markAsDelivered", err);
739
- }
740
- });
741
- }
742
- }
743
- });
744
- }
731
+ if (threadID && messageID) {
732
+ api.markAsDelivered(threadID, messageID, (err) => {
733
+ if (err) {
734
+ log.error("markAsDelivered", err);
735
+ } else {
736
+ if (ctx.globalOptions.autoMarkRead) {
737
+ api.markAsRead(threadID, (err) => {
738
+ if (err) {
739
+ log.error("markAsDelivered", err);
740
+ }
741
+ });
742
+ }
743
+ }
744
+ });
745
+ }
745
746
  }
746
747
 
747
748
  function getSeqId(defaultFuncs, api, ctx, globalCallback) {
748
- const jar = ctx.jar;
749
- utils
750
- .get('https://www.facebook.com/', jar, null, ctx.globalOptions, { noRef: true })
751
- .then(utils.saveCookies(jar))
752
- .then(function (resData) {
753
- const html = resData.body;
754
- const oldFBMQTTMatch = html.match(/irisSeqID:"(.+?)",appID:219994525426954,endpoint:"(.+?)"/);
755
- let mqttEndpoint = null;
756
- let region = null;
757
- let irisSeqID = null;
758
- let noMqttData = null;
759
-
760
- if (oldFBMQTTMatch) {
761
- irisSeqID = oldFBMQTTMatch[1];
762
- mqttEndpoint = oldFBMQTTMatch[2];
763
- region = new URL(mqttEndpoint).searchParams.get("region").toUpperCase();
764
- log.info("login", `Got this account's message region: ${region}`);
765
- } else {
766
- const newFBMQTTMatch = html.match(/{"app_id":"219994525426954","endpoint":"(.+?)","iris_seq_id":"(.+?)"}/);
767
- if (newFBMQTTMatch) {
768
- irisSeqID = newFBMQTTMatch[2];
769
- mqttEndpoint = newFBMQTTMatch[1].replace(/\\\//g, "/");
770
- region = new URL(mqttEndpoint).searchParams.get("region").toUpperCase();
771
- log.info("login", `Got this account's message region: ${region}`);
772
- } else {
773
- const legacyFBMQTTMatch = html.match(/(\["MqttWebConfig",\[\],{fbid:")(.+?)(",appID:219994525426954,endpoint:")(.+?)(",pollingEndpoint:")(.+?)(3790])/);
774
- if (legacyFBMQTTMatch) {
775
- mqttEndpoint = legacyFBMQTTMatch[4];
776
- region = new URL(mqttEndpoint).searchParams.get("region").toUpperCase();
777
- log.warn("login", `Cannot get sequence ID with new RegExp. Fallback to old RegExp (without seqID)...`);
778
- log.info("login", `Got this account's message region: ${region}`);
779
- log.info("login", `[Unused] Polling endpoint: ${legacyFBMQTTMatch[6]}`);
780
- } else {
781
- log.warn("login", "Cannot get MQTT region & sequence ID.");
782
- noMqttData = html;
783
- }
784
- }
785
- }
786
-
787
- ctx.lastSeqId = irisSeqID;
788
- ctx.mqttEndpoint = mqttEndpoint;
789
- ctx.region = region;
790
- if (noMqttData) {
791
- api["htmlData"] = noMqttData;
792
- }
793
-
794
- listenMqtt(defaultFuncs, api, ctx, globalCallback);
795
- })
796
- .catch(function (err) {
797
- log.error("getSeqId", err);
798
- });
749
+ const jar = ctx.jar;
750
+ utils
751
+ .get('https://www.facebook.com/', jar, null, ctx.globalOptions, { noRef: true })
752
+ .then(utils.saveCookies(jar))
753
+ .then(function (resData) {
754
+ const html = resData.body;
755
+ const oldFBMQTTMatch = html.match(/irisSeqID:"(.+?)",appID:219994525426954,endpoint:"(.+?)"/);
756
+ let mqttEndpoint = null;
757
+ let region = null;
758
+ let irisSeqID = null;
759
+ let noMqttData = null;
760
+
761
+ if (oldFBMQTTMatch) {
762
+ irisSeqID = oldFBMQTTMatch[1];
763
+ mqttEndpoint = oldFBMQTTMatch[2];
764
+ region = new URL(mqttEndpoint).searchParams.get("region").toUpperCase();
765
+ log.info("login", `Got this account's message region: ${region}`);
766
+ } else {
767
+ const newFBMQTTMatch = html.match(/{"app_id":"219994525426954","endpoint":"(.+?)","iris_seq_id":"(.+?)"}/);
768
+ if (newFBMQTTMatch) {
769
+ irisSeqID = newFBMQTTMatch[2];
770
+ mqttEndpoint = newFBMQTTMatch[1].replace(/\\\//g, "/");
771
+ region = new URL(mqttEndpoint).searchParams.get("region").toUpperCase();
772
+ log.info("login", `Got this account's message region: ${region}`);
773
+ } else {
774
+ const legacyFBMQTTMatch = html.match(/(\["MqttWebConfig",\[\],{fbid:")(.+?)(",appID:219994525426954,endpoint:")(.+?)(",pollingEndpoint:")(.+?)(3790])/);
775
+ if (legacyFBMQTTMatch) {
776
+ mqttEndpoint = legacyFBMQTTMatch[4];
777
+ region = new URL(mqttEndpoint).searchParams.get("region").toUpperCase();
778
+ log.warn("login", `Cannot get sequence ID with new RegExp. Fallback to old RegExp (without seqID)...`);
779
+ log.info("login", `Got this account's message region: ${region}`);
780
+ log.info("login", `[Unused] Polling endpoint: ${legacyFBMQTTMatch[6]}`);
781
+ } else {
782
+ log.warn("login", "Cannot get MQTT region & sequence ID.");
783
+ noMqttData = html;
784
+ }
785
+ }
786
+ }
787
+
788
+ ctx.lastSeqId = irisSeqID;
789
+ ctx.mqttEndpoint = mqttEndpoint;
790
+ ctx.region = region;
791
+ if (noMqttData) {
792
+ api["htmlData"] = noMqttData;
793
+ }
794
+
795
+ listenMqtt(defaultFuncs, api, ctx, globalCallback);
796
+ })
797
+ .catch(function (err) {
798
+ log.error("getSeqId", err);
799
+ });
799
800
  }
800
801
 
801
802
  module.exports = function (defaultFuncs, api, ctx) {
802
- let globalCallback = identity;
803
-
804
- return function (callback) {
805
- class MessageEmitter extends EventEmitter {
806
- stopListening(callback) {
807
-
808
- callback = callback || (() => { });
809
- globalCallback = identity;
810
- if (ctx.mqttClient) {
811
- ctx.mqttClient.unsubscribe("/webrtc");
812
- ctx.mqttClient.unsubscribe("/rtc_multi");
813
- ctx.mqttClient.unsubscribe("/onevc");
814
- ctx.mqttClient.publish("/browser_close", "{}");
815
- ctx.mqttClient.end(false, function (...data) {
816
- callback(data);
817
- ctx.mqttClient = undefined;
818
- });
819
- }
820
- }
821
-
822
- async stopListeningAsync() {
823
- return new Promise((resolve) => {
824
- this.stopListening(resolve);
825
- });
826
- }
827
- }
828
-
829
- const msgEmitter = new MessageEmitter();
830
- globalCallback = (callback || function (error, message) {
831
- if (error) {
832
- return msgEmitter.emit("error", error);
833
- }
834
- msgEmitter.emit("message", message);
835
- });
836
-
837
- // Reset some stuff
838
- if (!ctx.firstListen)
839
- ctx.lastSeqId = null;
840
- ctx.syncToken = undefined;
841
- ctx.t_mqttCalled = false;
842
-
843
- if (!ctx.firstListen || !ctx.lastSeqId) {
844
- getSeqId(defaultFuncs, api, ctx, globalCallback);
845
- } else {
846
- listenMqtt(defaultFuncs, api, ctx, globalCallback);
847
- }
848
-
849
- api.stopListening = msgEmitter.stopListening;
850
- api.stopListeningAsync = msgEmitter.stopListeningAsync;
851
- return msgEmitter;
852
- };
803
+ let globalCallback = identity;
804
+
805
+ return function (callback) {
806
+ class MessageEmitter extends EventEmitter {
807
+ stopListening(callback) {
808
+
809
+ callback = callback || (() => { });
810
+ globalCallback = identity;
811
+ if (ctx.mqttClient) {
812
+ ctx.mqttClient.unsubscribe("/webrtc");
813
+ ctx.mqttClient.unsubscribe("/rtc_multi");
814
+ ctx.mqttClient.unsubscribe("/onevc");
815
+ ctx.mqttClient.publish("/browser_close", "{}");
816
+ ctx.mqttClient.end(false, function (...data) {
817
+ callback(data);
818
+ ctx.mqttClient = undefined;
819
+ });
820
+ }
821
+ }
822
+
823
+ async stopListeningAsync() {
824
+ return new Promise((resolve) => {
825
+ this.stopListening(resolve);
826
+ });
827
+ }
828
+ }
829
+
830
+ const msgEmitter = new MessageEmitter();
831
+ globalCallback = (callback || function (error, message) {
832
+ if (error) {
833
+ return msgEmitter.emit("error", error);
834
+ }
835
+ msgEmitter.emit("message", message);
836
+ });
837
+
838
+ // Reset some stuff
839
+ if (!ctx.firstListen)
840
+ ctx.lastSeqId = null;
841
+ ctx.syncToken = undefined;
842
+ ctx.t_mqttCalled = false;
843
+
844
+ if (!ctx.firstListen || !ctx.lastSeqId) {
845
+ getSeqId(defaultFuncs, api, ctx, globalCallback);
846
+ } else {
847
+ listenMqtt(defaultFuncs, api, ctx, globalCallback);
848
+ }
849
+
850
+ api.stopListening = msgEmitter.stopListening;
851
+ api.stopListeningAsync = msgEmitter.stopListeningAsync;
852
+ return msgEmitter;
853
+ };
853
854
  };