alicezetion 1.9.4 → 1.9.6

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
@@ -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
  };