@dongdev/fca-unofficial 0.0.7 → 0.0.9

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.
@@ -2,231 +2,301 @@
2
2
 
3
3
  const utils = require("../utils");
4
4
  const log = require("npmlog");
5
-
5
+ const fs = require("fs");
6
+ const path = require("path");
7
+ const logger = require("../lib/logger");
6
8
  function formatEventReminders(reminder) {
7
- return {
8
- reminderID: reminder.id,
9
- eventCreatorID: reminder.lightweight_event_creator.id,
10
- time: reminder.time,
11
- eventType: reminder.lightweight_event_type.toLowerCase(),
12
- locationName: reminder.location_name,
13
- // @TODO verify this
14
- locationCoordinates: reminder.location_coordinates,
15
- locationPage: reminder.location_page,
16
- eventStatus: reminder.lightweight_event_status.toLowerCase(),
17
- note: reminder.note,
18
- repeatMode: reminder.repeat_mode.toLowerCase(),
19
- eventTitle: reminder.event_title,
20
- triggerMessage: reminder.trigger_message,
21
- secondsToNotifyBefore: reminder.seconds_to_notify_before,
22
- allowsRsvp: reminder.allows_rsvp,
23
- relatedEvent: reminder.related_event,
24
- members: reminder.event_reminder_members.edges.map(function (member) {
25
- return {
26
- memberID: member.node.id,
27
- state: member.guest_list_state.toLowerCase()
28
- };
29
- })
30
- };
9
+ return {
10
+ reminderID: reminder.id,
11
+ eventCreatorID: reminder.lightweight_event_creator.id,
12
+ time: reminder.time,
13
+ eventType: reminder.lightweight_event_type.toLowerCase(),
14
+ locationName: reminder.location_name,
15
+ locationCoordinates: reminder.location_coordinates,
16
+ locationPage: reminder.location_page,
17
+ eventStatus: reminder.lightweight_event_status.toLowerCase(),
18
+ note: reminder.note,
19
+ repeatMode: reminder.repeat_mode.toLowerCase(),
20
+ eventTitle: reminder.event_title,
21
+ triggerMessage: reminder.trigger_message,
22
+ secondsToNotifyBefore: reminder.seconds_to_notify_before,
23
+ allowsRsvp: reminder.allows_rsvp,
24
+ relatedEvent: reminder.related_event,
25
+ members: reminder.event_reminder_members.edges.map(function(member) {
26
+ return {
27
+ memberID: member.node.id,
28
+ state: member.guest_list_state.toLowerCase(),
29
+ };
30
+ }),
31
+ };
31
32
  }
32
-
33
33
  function formatThreadGraphQLResponse(data) {
34
- if (data.errors)
35
- return data.errors;
36
- const messageThread = data.message_thread;
37
- if (!messageThread)
38
- return null;
39
- const threadID = messageThread.thread_key.thread_fbid
40
- ? messageThread.thread_key.thread_fbid
41
- : messageThread.thread_key.other_user_id;
42
-
43
- // Remove me
44
- const lastM = messageThread.last_message;
45
- const snippetID =
46
- lastM &&
47
- lastM.nodes &&
48
- lastM.nodes[0] &&
49
- lastM.nodes[0].message_sender &&
50
- lastM.nodes[0].message_sender.messaging_actor
51
- ? lastM.nodes[0].message_sender.messaging_actor.id
52
- : null;
53
- const snippetText =
54
- lastM && lastM.nodes && lastM.nodes[0] ? lastM.nodes[0].snippet : null;
55
- const lastR = messageThread.last_read_receipt;
56
- const lastReadTimestamp =
57
- lastR && lastR.nodes && lastR.nodes[0] && lastR.nodes[0].timestamp_precise
58
- ? lastR.nodes[0].timestamp_precise
59
- : null;
60
-
61
- return {
62
- threadID: threadID,
63
- threadName: messageThread.name,
64
- participantIDs: messageThread.all_participants.edges.map(d => d.node.messaging_actor.id),
65
- userInfo: messageThread.all_participants.edges.map(d => ({
66
- id: d.node.messaging_actor.id,
67
- name: d.node.messaging_actor.name,
68
- firstName: d.node.messaging_actor.short_name,
69
- vanity: d.node.messaging_actor.username,
70
- url: d.node.messaging_actor.url,
71
- thumbSrc: d.node.messaging_actor.big_image_src.uri,
72
- profileUrl: d.node.messaging_actor.big_image_src.uri,
73
- gender: d.node.messaging_actor.gender,
74
- type: d.node.messaging_actor.__typename,
75
- isFriend: d.node.messaging_actor.is_viewer_friend,
76
- isBirthday: !!d.node.messaging_actor.is_birthday //not sure?
77
- })),
78
- unreadCount: messageThread.unread_count,
79
- messageCount: messageThread.messages_count,
80
- timestamp: messageThread.updated_time_precise,
81
- muteUntil: messageThread.mute_until,
82
- isGroup: messageThread.thread_type == "GROUP",
83
- isSubscribed: messageThread.is_viewer_subscribed,
84
- isArchived: messageThread.has_viewer_archived,
85
- folder: messageThread.folder,
86
- cannotReplyReason: messageThread.cannot_reply_reason,
87
- eventReminders: messageThread.event_reminders
88
- ? messageThread.event_reminders.nodes.map(formatEventReminders)
89
- : null,
90
- emoji: messageThread.customization_info
91
- ? messageThread.customization_info.emoji
92
- : null,
93
- color:
94
- messageThread.customization_info &&
95
- messageThread.customization_info.outgoing_bubble_color
96
- ? messageThread.customization_info.outgoing_bubble_color.slice(2)
97
- : null,
98
- threadTheme: messageThread.thread_theme,
99
- nicknames:
100
- messageThread.customization_info &&
101
- messageThread.customization_info.participant_customizations
102
- ? messageThread.customization_info.participant_customizations.reduce(
103
- function (res, val) {
104
- if (val.nickname) res[val.participant_id] = val.nickname;
105
- return res;
106
- },
107
- {}
108
- )
109
- : {},
110
- adminIDs: messageThread.thread_admins,
111
- approvalMode: Boolean(messageThread.approval_mode),
112
- approvalQueue: messageThread.group_approval_queue.nodes.map(a => ({
113
- inviterID: a.inviter.id,
114
- requesterID: a.requester.id,
115
- timestamp: a.request_timestamp,
116
- request_source: a.request_source // @Undocumented
117
- })),
34
+ if (!data) return;
35
+ if (data?.errors) return data.errors;
36
+ const messageThread = data.message_thread;
37
+ if (!messageThread) return null;
38
+ const threadID = messageThread.thread_key.thread_fbid
39
+ ? messageThread.thread_key.thread_fbid
40
+ : messageThread.thread_key.other_user_id;
41
+ const lastM = messageThread.last_message;
42
+ const snippetID =
43
+ lastM &&
44
+ lastM.nodes &&
45
+ lastM.nodes[0] &&
46
+ lastM.nodes[0].message_sender &&
47
+ lastM.nodes[0].message_sender.messaging_actor
48
+ ? lastM.nodes[0].message_sender.messaging_actor.id
49
+ : null;
50
+ const snippetText =
51
+ lastM && lastM.nodes && lastM.nodes[0] ? lastM.nodes[0].snippet : null;
52
+ const lastR = messageThread.last_read_receipt;
53
+ const lastReadTimestamp =
54
+ lastR && lastR.nodes && lastR.nodes[0] && lastR.nodes[0].timestamp_precise
55
+ ? lastR.nodes[0].timestamp_precise
56
+ : null;
118
57
 
119
- // @Undocumented
120
- reactionsMuteMode: messageThread.reactions_mute_mode.toLowerCase(),
121
- mentionsMuteMode: messageThread.mentions_mute_mode.toLowerCase(),
122
- isPinProtected: messageThread.is_pin_protected,
123
- relatedPageThread: messageThread.related_page_thread,
124
-
125
- // @Legacy
126
- name: messageThread.name,
127
- snippet: snippetText,
128
- snippetSender: snippetID,
129
- snippetAttachments: [],
130
- serverTimestamp: messageThread.updated_time_precise,
131
- imageSrc: messageThread.image ? messageThread.image.uri : null,
132
- isCanonicalUser: messageThread.is_canonical_neo_user,
133
- isCanonical: messageThread.thread_type != "GROUP",
134
- recipientsLoadable: true,
135
- hasEmailParticipant: false,
136
- readOnly: false,
137
- canReply: messageThread.cannot_reply_reason == null,
138
- lastMessageTimestamp: messageThread.last_message
139
- ? messageThread.last_message.timestamp_precise
140
- : null,
141
- lastMessageType: "message",
142
- lastReadTimestamp: lastReadTimestamp,
143
- threadType: messageThread.thread_type == "GROUP" ? 2 : 1,
144
-
145
- // update in Wed, 13 Jul 2022 19:41:12 +0700
146
- inviteLink: {
147
- enable: messageThread.joinable_mode ? messageThread.joinable_mode.mode == 1 : false,
148
- link: messageThread.joinable_mode ? messageThread.joinable_mode.link : null
149
- }
150
- };
58
+ return {
59
+ threadID: threadID,
60
+ threadName: messageThread.name,
61
+ participantIDs: messageThread.all_participants.edges.map(
62
+ (d) => d.node.messaging_actor.id
63
+ ),
64
+ userInfo: messageThread.all_participants.edges.map((d) => ({
65
+ id: d.node.messaging_actor.id,
66
+ name: d.node.messaging_actor.name,
67
+ firstName: d.node.messaging_actor.short_name,
68
+ vanity: d.node.messaging_actor.username,
69
+ url: d.node.messaging_actor.url,
70
+ thumbSrc: d.node.messaging_actor.big_image_src.uri,
71
+ profileUrl: d.node.messaging_actor.big_image_src.uri,
72
+ gender: d.node.messaging_actor.gender,
73
+ type: d.node.messaging_actor.__typename,
74
+ isFriend: d.node.messaging_actor.is_viewer_friend,
75
+ isBirthday: !!d.node.messaging_actor.is_birthday,
76
+ })),
77
+ unreadCount: messageThread.unread_count,
78
+ messageCount: messageThread.messages_count,
79
+ timestamp: messageThread.updated_time_precise,
80
+ muteUntil: messageThread.mute_until,
81
+ isGroup: messageThread.thread_type == "GROUP",
82
+ isSubscribed: messageThread.is_viewer_subscribed,
83
+ isArchived: messageThread.has_viewer_archived,
84
+ folder: messageThread.folder,
85
+ cannotReplyReason: messageThread.cannot_reply_reason,
86
+ eventReminders: messageThread.event_reminders
87
+ ? messageThread.event_reminders.nodes.map(formatEventReminders)
88
+ : null,
89
+ emoji: messageThread.customization_info
90
+ ? messageThread.customization_info.emoji
91
+ : null,
92
+ color:
93
+ messageThread.customization_info &&
94
+ messageThread.customization_info.outgoing_bubble_color
95
+ ? messageThread.customization_info.outgoing_bubble_color.slice(2)
96
+ : null,
97
+ threadTheme: messageThread.thread_theme,
98
+ nicknames:
99
+ messageThread.customization_info &&
100
+ messageThread.customization_info.participant_customizations
101
+ ? messageThread.customization_info.participant_customizations.reduce(
102
+ function(res, val) {
103
+ if (val.nickname) res[val.participant_id] = val.nickname;
104
+ return res;
105
+ },
106
+ {}
107
+ )
108
+ : {},
109
+ adminIDs: messageThread.thread_admins,
110
+ approvalMode: Boolean(messageThread.approval_mode),
111
+ approvalQueue: messageThread.group_approval_queue.nodes.map((a) => ({
112
+ inviterID: a.inviter.id,
113
+ requesterID: a.requester.id,
114
+ timestamp: a.request_timestamp,
115
+ request_source: a.request_source,
116
+ })),
117
+ reactionsMuteMode: messageThread.reactions_mute_mode.toLowerCase(),
118
+ mentionsMuteMode: messageThread.mentions_mute_mode.toLowerCase(),
119
+ isPinProtected: messageThread.is_pin_protected,
120
+ relatedPageThread: messageThread.related_page_thread,
121
+ name: messageThread.name,
122
+ snippet: snippetText,
123
+ snippetSender: snippetID,
124
+ snippetAttachments: [],
125
+ serverTimestamp: messageThread.updated_time_precise,
126
+ imageSrc: messageThread.image ? messageThread.image.uri : null,
127
+ isCanonicalUser: messageThread.is_canonical_neo_user,
128
+ isCanonical: messageThread.thread_type != "GROUP",
129
+ recipientsLoadable: true,
130
+ hasEmailParticipant: false,
131
+ readOnly: false,
132
+ canReply: messageThread.cannot_reply_reason == null,
133
+ lastMessageTimestamp: messageThread.last_message
134
+ ? messageThread.last_message.timestamp_precise
135
+ : null,
136
+ lastMessageType: "message",
137
+ lastReadTimestamp: lastReadTimestamp,
138
+ threadType: messageThread.thread_type == "GROUP" ? 2 : 1,
139
+ inviteLink: {
140
+ enable: messageThread.joinable_mode
141
+ ? messageThread.joinable_mode.mode == 1
142
+ : false,
143
+ link: messageThread.joinable_mode
144
+ ? messageThread.joinable_mode.link
145
+ : null,
146
+ },
147
+ };
151
148
  }
152
-
153
- module.exports = function (defaultFuncs, api, ctx) {
154
- return function getThreadInfoGraphQL(threadID, callback) {
155
- let resolveFunc = function () { };
156
- let rejectFunc = function () { };
157
- const returnPromise = new Promise(function (resolve, reject) {
158
- resolveFunc = resolve;
159
- rejectFunc = reject;
160
- });
161
-
162
- if (utils.getType(callback) != "Function" && utils.getType(callback) != "AsyncFunction") {
163
- callback = function (err, data) {
164
- if (err) {
165
- return rejectFunc(err);
166
- }
167
- resolveFunc(data);
168
- };
169
- }
170
-
171
- if (utils.getType(threadID) !== "Array") {
172
- threadID = [threadID];
173
- }
174
-
175
- let form = {};
176
- // `queries` has to be a string. I couldn't tell from the dev console. This
177
- // took me a really long time to figure out. I deserve a cookie for this.
178
- threadID.map(function (t, i) {
179
- form["o" + i] = {
180
- doc_id: "3449967031715030",
181
- query_params: {
182
- id: t,
183
- message_limit: 0,
184
- load_messages: false,
185
- load_read_receipts: false,
186
- before: null
187
- }
188
- };
189
- });
190
-
191
- form = {
192
- queries: JSON.stringify(form),
193
- batch_name: "MessengerGraphQLThreadFetcher"
194
- };
195
-
196
- defaultFuncs
197
- .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form)
198
- .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
199
- .then(function (resData) {
200
-
201
- if (resData.error) {
202
- throw resData;
203
- }
204
- // This returns us an array of things. The last one is the success /
205
- // failure one.
206
- // @TODO What do we do in this case?
207
- // if (resData[resData.length - 1].error_results !== 0) {
208
- // throw resData[0].o0.errors[0];
209
- // }
210
- // if (!resData[0].o0.data.message_thread) {
211
- // throw new Error("can't find this thread");
212
- // }
213
- const threadInfos = {};
214
- for (let i = resData.length - 2; i >= 0; i--) {
215
- const threadInfo = formatThreadGraphQLResponse(resData[i][Object.keys(resData[i])[0]].data);
216
- threadInfos[threadInfo?.threadID || threadID[threadID.length - 1 - i]] = threadInfo;
217
- }
218
- if (Object.values(threadInfos).length == 1) {
219
- callback(null, Object.values(threadInfos)[0]);
220
- }
221
- else {
222
- callback(null, threadInfos);
223
- }
224
- })
225
- .catch(function (err) {
226
- log.error("getThreadInfoGraphQL", err);
227
- return callback(err);
228
- });
229
-
230
- return returnPromise;
231
- };
232
- };
149
+ const queue = [];
150
+ let isProcessingQueue = false;
151
+ const processingThreads = new Set();
152
+ const queuedThreads = new Set();
153
+ module.exports = function(defaultFuncs, api, ctx) {
154
+ const getMultiInfo = async function(threadIDs) {
155
+ let form = {};
156
+ let tempThreadInf = [];
157
+ threadIDs.forEach((x, y) => {
158
+ form["o" + y] = {
159
+ doc_id: "3449967031715030",
160
+ query_params: {
161
+ id: x,
162
+ message_limit: 0,
163
+ load_messages: false,
164
+ load_read_receipts: false,
165
+ before: null,
166
+ },
167
+ };
168
+ });
169
+ let Submit = {
170
+ queries: JSON.stringify(form),
171
+ batch_name: "MessengerGraphQLThreadFetcher",
172
+ };
173
+ try {
174
+ const resData = await defaultFuncs
175
+ .post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, Submit)
176
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs));
177
+ if (resData.error || resData[resData.length - 1].error_results !== 0)
178
+ throw new Error(
179
+ "Error: getThreadInfoGraphQL - You may be rate limited"
180
+ );
181
+ resData
182
+ .slice(0, -1)
183
+ .sort((a, b) => Object.keys(a)[0].localeCompare(Object.keys(b)[0]))
184
+ .forEach((x, y) =>
185
+ tempThreadInf.push(formatThreadGraphQLResponse(x["o" + y].data))
186
+ );
187
+ return { Success: true, Data: tempThreadInf };
188
+ } catch (err) {
189
+ return { Success: false, Data: "" };
190
+ }
191
+ };
192
+ const dbFiles = fs
193
+ .readdirSync(path.join(__dirname, "../lib/database"))
194
+ .filter((f) => path.extname(f) === ".js")
195
+ .reduce((acc, file) => {
196
+ acc[path.basename(file, ".js")] = require(path.join(
197
+ __dirname,
198
+ "../lib/database",
199
+ file
200
+ ))(api);
201
+ return acc;
202
+ }, {});
203
+ const { threadData } = dbFiles;
204
+ const { create, get, update, getAll } = threadData;
205
+ async function fetchThreadInfo(tID, isNew) {
206
+ try {
207
+ const response = await getMultiInfo([tID]);
208
+ if (!response.Success) throw new Error("Failed to fetch thread info");
209
+ const threadInfo = response.Data[0];
210
+ if (isNew) {
211
+ await create(tID, { data: threadInfo });
212
+ logger(`Success create data thread: ${tID}`, 'info');
213
+ } else {
214
+ await update(tID, { data: threadInfo });
215
+ logger(`Success update data thread: ${tID}`, 'info');
216
+ }
217
+ } catch (err) {
218
+ console.error("fetchThreadInfo", err);
219
+ } finally {
220
+ queuedThreads.delete(tID);
221
+ }
222
+ }
223
+ async function checkAndUpdateThreads() {
224
+ try {
225
+ const allThreads = await getAll("threadID");
226
+ const existingThreadIDs = new Set(allThreads.map((t) => t.threadID));
227
+ for (const t of existingThreadIDs) {
228
+ const result = await get(t);
229
+ if (!result) continue;
230
+ const now = Date.now();
231
+ const lastUpdated = new Date(result.updatedAt).getTime();
232
+ if ((now - lastUpdated) / (1000 * 60) > 10 && !queuedThreads.has(t)) {
233
+ queuedThreads.add(t);
234
+ logger(`ThreadID ${t} aready update data`, 'info');
235
+ queue.push(() => fetchThreadInfo(t, false));
236
+ }
237
+ }
238
+ } catch (err) {
239
+ console.error("checkAndUpdateThreads", err);
240
+ }
241
+ }
242
+ async function processQueue() {
243
+ if (isProcessingQueue) return;
244
+ isProcessingQueue = true;
245
+ while (queue.length > 0) {
246
+ const task = queue.shift();
247
+ try {
248
+ await task();
249
+ } catch (err) {
250
+ console.error("Queue processing error", err);
251
+ }
252
+ }
253
+ isProcessingQueue = false;
254
+ }
255
+ setInterval(() => {
256
+ checkAndUpdateThreads();
257
+ processQueue();
258
+ }, 10000);
259
+ return async function getThreadInfoGraphQL(threadID, callback) {
260
+ let resolveFunc = function() {};
261
+ let rejectFunc = function() {};
262
+ const returnPromise = new Promise(function(resolve, reject) {
263
+ resolveFunc = resolve;
264
+ rejectFunc = reject;
265
+ });
266
+ if (
267
+ utils.getType(callback) != "Function" &&
268
+ utils.getType(callback) != "AsyncFunction"
269
+ ) {
270
+ callback = function(err, data) {
271
+ if (err) {
272
+ return rejectFunc(err);
273
+ }
274
+ resolveFunc(data);
275
+ };
276
+ }
277
+ if (utils.getType(threadID) !== "Array") {
278
+ threadID = [threadID];
279
+ }
280
+ let result;
281
+ try {
282
+ result = await get(threadID[0]);
283
+ if (result) {
284
+ callback(null, result.data);
285
+ } else {
286
+ if (!processingThreads.has(threadID[0])) {
287
+ processingThreads.add(threadID[0]);
288
+ logger(`Created new thread data: ${threadID[0]}`, 'info');
289
+ const response = await getMultiInfo(threadID);
290
+ if (!response.Success) throw new Error("Failed to get thread info");
291
+ const data = response.Data[0];
292
+ await create(threadID[0], { data });
293
+ callback(null, data);
294
+ processingThreads.delete(threadID[0]);
295
+ }
296
+ }
297
+ } catch (err) {
298
+ callback(err);
299
+ }
300
+ return returnPromise;
301
+ };
302
+ };
@@ -5,9 +5,7 @@ const log = require("npmlog");
5
5
 
6
6
  function formatData(data) {
7
7
  const retObj = {};
8
-
9
8
  for (const prop in data) {
10
- // eslint-disable-next-line no-prototype-builtins
11
9
  if (data.hasOwnProperty(prop)) {
12
10
  const innerObj = data[prop];
13
11
  retObj[prop] = {
@@ -25,7 +23,6 @@ function formatData(data) {
25
23
  };
26
24
  }
27
25
  }
28
-
29
26
  return retObj;
30
27
  }
31
28
 
@@ -37,7 +34,6 @@ module.exports = function (defaultFuncs, api, ctx) {
37
34
  resolveFunc = resolve;
38
35
  rejectFunc = reject;
39
36
  });
40
-
41
37
  if (!callback) {
42
38
  callback = function (err, friendList) {
43
39
  if (err) {
@@ -46,11 +42,9 @@ module.exports = function (defaultFuncs, api, ctx) {
46
42
  resolveFunc(friendList);
47
43
  };
48
44
  }
49
-
50
45
  if (utils.getType(id) !== "Array") {
51
46
  id = [id];
52
47
  }
53
-
54
48
  const form = {};
55
49
  id.map(function (v, i) {
56
50
  form["ids[" + i + "]"] = v;
@@ -71,4 +65,4 @@ module.exports = function (defaultFuncs, api, ctx) {
71
65
 
72
66
  return returnPromise;
73
67
  };
74
- };
68
+ };