@cexy/hoonfca 1.0.4 → 1.0.5

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.
Files changed (135) hide show
  1. package/CHANGELOG.md +181 -0
  2. package/DOCS.md +2636 -0
  3. package/LICENSE-MIT +21 -21
  4. package/README.md +632 -0
  5. package/func/checkUpdate.js +222 -0
  6. package/func/logger.js +48 -0
  7. package/index.d.ts +731 -605
  8. package/index.js +6 -774
  9. package/module/config.js +34 -0
  10. package/module/login.js +136 -0
  11. package/module/loginHelper.js +1034 -0
  12. package/module/options.js +45 -0
  13. package/package.json +54 -19
  14. package/src/api/action/addExternalModule.js +25 -0
  15. package/src/api/action/changeAvatar.js +137 -0
  16. package/src/{changeBio.js → api/action/changeBio.js} +75 -76
  17. package/src/api/action/enableAutoSaveAppState.js +73 -0
  18. package/src/{getCurrentUserID.js → api/action/getCurrentUserID.js} +7 -7
  19. package/src/{handleFriendRequest.js → api/action/handleFriendRequest.js} +57 -57
  20. package/src/api/action/logout.js +76 -0
  21. package/src/api/action/refreshFb_dtsg.js +48 -0
  22. package/src/api/action/setPostReaction.js +106 -0
  23. package/src/api/action/unfriend.js +54 -0
  24. package/src/api/http/httpGet.js +46 -0
  25. package/src/api/http/httpPost.js +52 -0
  26. package/src/api/http/postFormData.js +47 -0
  27. package/src/api/messaging/addUserToGroup.js +68 -0
  28. package/src/api/messaging/changeAdminStatus.js +122 -0
  29. package/src/api/messaging/changeArchivedStatus.js +55 -0
  30. package/src/api/messaging/changeBlockedStatus.js +48 -0
  31. package/src/api/messaging/changeGroupImage.js +90 -0
  32. package/src/api/messaging/changeNickname.js +70 -0
  33. package/src/api/messaging/changeThreadColor.js +79 -0
  34. package/src/api/messaging/changeThreadEmoji.js +106 -0
  35. package/src/{createNewGroup.js → api/messaging/createNewGroup.js} +88 -88
  36. package/src/api/messaging/createPoll.js +43 -0
  37. package/src/api/messaging/createThemeAI.js +98 -0
  38. package/src/{deleteMessage.js → api/messaging/deleteMessage.js} +56 -56
  39. package/src/{deleteThread.js → api/messaging/deleteThread.js} +56 -56
  40. package/src/api/messaging/editMessage.js +68 -0
  41. package/src/api/messaging/forwardAttachment.js +51 -0
  42. package/src/{getEmojiUrl.js → api/messaging/getEmojiUrl.js} +8 -8
  43. package/src/{getFriendsList.js → api/messaging/getFriendsList.js} +82 -84
  44. package/src/api/messaging/getMessage.js +829 -0
  45. package/src/api/messaging/getThemePictures.js +62 -0
  46. package/src/api/messaging/handleMessageRequest.js +65 -0
  47. package/src/{markAsDelivered.js → api/messaging/markAsDelivered.js} +57 -58
  48. package/src/{markAsRead.js → api/messaging/markAsRead.js} +88 -88
  49. package/src/{markAsReadAll.js → api/messaging/markAsReadAll.js} +49 -50
  50. package/src/api/messaging/markAsSeen.js +61 -0
  51. package/src/{muteThread.js → api/messaging/muteThread.js} +50 -52
  52. package/src/api/messaging/removeUserFromGroup.js +106 -0
  53. package/src/{resolvePhotoUrl.js → api/messaging/resolvePhotoUrl.js} +43 -45
  54. package/src/api/messaging/scheduler.js +264 -0
  55. package/src/{searchForThread.js → api/messaging/searchForThread.js} +52 -53
  56. package/src/api/messaging/sendMessage.js +272 -0
  57. package/src/api/messaging/sendTypingIndicator.js +67 -0
  58. package/src/api/messaging/setMessageReaction.js +76 -0
  59. package/src/api/messaging/setTitle.js +119 -0
  60. package/src/{shareContact.js → api/messaging/shareContact.js} +49 -53
  61. package/src/api/messaging/threadColors.js +128 -0
  62. package/src/api/messaging/unsendMessage.js +81 -0
  63. package/src/api/messaging/uploadAttachment.js +94 -0
  64. package/src/api/socket/core/connectMqtt.js +255 -0
  65. package/src/api/socket/core/emitAuth.js +106 -0
  66. package/src/api/socket/core/getSeqID.js +40 -0
  67. package/src/api/socket/core/getTaskResponseData.js +22 -0
  68. package/src/api/socket/core/markDelivery.js +12 -0
  69. package/src/api/socket/core/parseDelta.js +391 -0
  70. package/src/api/socket/detail/buildStream.js +208 -0
  71. package/src/api/socket/detail/constants.js +24 -0
  72. package/src/api/socket/listenMqtt.js +364 -0
  73. package/src/api/socket/middleware/index.js +216 -0
  74. package/src/{getThreadHistory.js → api/threads/getThreadHistory.js} +664 -647
  75. package/src/api/threads/getThreadInfo.js +438 -0
  76. package/src/{getThreadList.js → api/threads/getThreadList.js} +293 -292
  77. package/src/{getThreadPictures.js → api/threads/getThreadPictures.js} +78 -79
  78. package/src/{getUserID.js → api/users/getUserID.js} +65 -66
  79. package/src/api/users/getUserInfo.js +327 -0
  80. package/src/api/users/getUserInfoV2.js +134 -0
  81. package/src/core/sendReqMqtt.js +96 -0
  82. package/src/database/models/index.js +87 -0
  83. package/src/database/models/thread.js +45 -0
  84. package/src/database/models/user.js +46 -0
  85. package/src/database/threadData.js +98 -0
  86. package/src/database/userData.js +89 -0
  87. package/src/utils/client.js +320 -0
  88. package/src/utils/constants.js +23 -0
  89. package/{utils.js → src/utils/format.js} +1115 -1447
  90. package/src/utils/headers.js +115 -0
  91. package/src/utils/request.js +305 -0
  92. package/.travis.yml +0 -6
  93. package/src/addExternalModule.js +0 -23
  94. package/src/addUserToGroup.js +0 -113
  95. package/src/changeAdminStatus.js +0 -95
  96. package/src/changeApprovalMode.js +0 -79
  97. package/src/changeArchivedStatus.js +0 -55
  98. package/src/changeBlockedStatus.js +0 -47
  99. package/src/changeBlockedStatusMqtt.js +0 -86
  100. package/src/changeGroupImage.js +0 -133
  101. package/src/changeNickname.js +0 -59
  102. package/src/changeThreadColor.js +0 -71
  103. package/src/changeThreadEmoji.js +0 -55
  104. package/src/createPoll.js +0 -130
  105. package/src/editMessage.js +0 -63
  106. package/src/forwardAttachment.js +0 -60
  107. package/src/forwardMessage.js +0 -63
  108. package/src/getGroupsList.js +0 -83
  109. package/src/getMarketplace.js +0 -84
  110. package/src/getNotifications.js +0 -86
  111. package/src/getPagesList.js +0 -83
  112. package/src/getStories.js +0 -88
  113. package/src/getThreadInfo.js +0 -222
  114. package/src/getUserInfo.js +0 -128
  115. package/src/handleMessageRequest.js +0 -65
  116. package/src/httpGet.js +0 -58
  117. package/src/httpPost.js +0 -58
  118. package/src/listenMqtt.js +0 -1018
  119. package/src/logout.js +0 -75
  120. package/src/markAsSeen.js +0 -61
  121. package/src/pinMessage.js +0 -61
  122. package/src/removeUserFromGroup.js +0 -79
  123. package/src/searchUsers.js +0 -89
  124. package/src/sendComment.js +0 -161
  125. package/src/sendMessage.js +0 -457
  126. package/src/sendTypingIndicator.js +0 -179
  127. package/src/setBio.js +0 -86
  128. package/src/setMessageReaction.js +0 -186
  129. package/src/setPostReaction.js +0 -71
  130. package/src/setTheme.js +0 -313
  131. package/src/setTitle.js +0 -90
  132. package/src/shareLink.js +0 -62
  133. package/src/threadColors.js +0 -57
  134. package/src/unfriend.js +0 -53
  135. package/src/unsendMessage.js +0 -104
@@ -0,0 +1,438 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const logger = require("../../../func/logger");
4
+ const { parseAndCheckLogin } = require("../../utils/client");
5
+ const { formatID, getType } = require("../../utils/format");
6
+
7
+ function formatEventReminders(reminder) {
8
+ return {
9
+ reminderID: reminder?.id,
10
+ eventCreatorID: reminder?.lightweight_event_creator?.id,
11
+ time: reminder?.time,
12
+ eventType: String(reminder?.lightweight_event_type || "").toLowerCase(),
13
+ locationName: reminder?.location_name,
14
+ locationCoordinates: reminder?.location_coordinates,
15
+ locationPage: reminder?.location_page,
16
+ eventStatus: String(reminder?.lightweight_event_status || "").toLowerCase(),
17
+ note: reminder?.note,
18
+ repeatMode: String(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: Array.isArray(reminder?.event_reminder_members?.edges) ? reminder.event_reminder_members.edges.map(m => ({
25
+ memberID: m?.node?.id,
26
+ state: String(m?.guest_list_state || "").toLowerCase(),
27
+ })) : [],
28
+ };
29
+ }
30
+
31
+ function formatThreadGraphQLResponse(data) {
32
+ if (!data) return null;
33
+ if (data?.errors) return null;
34
+ const t = data.message_thread;
35
+ if (!t) return null;
36
+ const threadID = t?.thread_key?.thread_fbid || t?.thread_key?.other_user_id || null;
37
+ const lastM = t?.last_message;
38
+ const lastNode = Array.isArray(lastM?.nodes) && lastM.nodes[0] ? lastM.nodes[0] : null;
39
+ const snippetID = lastNode?.message_sender?.messaging_actor?.id || null;
40
+ const snippetText = lastNode?.snippet || null;
41
+ const lastRNode = Array.isArray(t?.last_read_receipt?.nodes) && t.last_read_receipt.nodes[0] ? t.last_read_receipt.nodes[0] : null;
42
+ const lastReadTimestamp = lastRNode?.timestamp_precise || null;
43
+ const participants = Array.isArray(t?.all_participants?.edges) ? t.all_participants.edges : [];
44
+ const approvals = Array.isArray(t?.group_approval_queue?.nodes) ? t.group_approval_queue.nodes : [];
45
+ const customInfo = t?.customization_info || {};
46
+ const bubble = customInfo?.outgoing_bubble_color;
47
+ const participantCustoms = Array.isArray(customInfo?.participant_customizations) ? customInfo.participant_customizations : [];
48
+ const nicknames = participantCustoms.reduce((res, val) => {
49
+ if (val?.nickname && val?.participant_id) res[val.participant_id] = val.nickname;
50
+ return res;
51
+ }, {});
52
+ return {
53
+ threadID,
54
+ threadName: t?.name || null,
55
+ participantIDs: participants.map(d => d?.node?.messaging_actor?.id).filter(Boolean),
56
+ userInfo: participants.map(d => ({
57
+ id: d?.node?.messaging_actor?.id || null,
58
+ name: d?.node?.messaging_actor?.name || null,
59
+ firstName: d?.node?.messaging_actor?.short_name || null,
60
+ vanity: d?.node?.messaging_actor?.username || null,
61
+ url: d?.node?.messaging_actor?.url || null,
62
+ thumbSrc: d?.node?.messaging_actor?.big_image_src?.uri || null,
63
+ profileUrl: d?.node?.messaging_actor?.big_image_src?.uri || null,
64
+ gender: d?.node?.messaging_actor?.gender || null,
65
+ type: d?.node?.messaging_actor?.__typename || null,
66
+ isFriend: !!d?.node?.messaging_actor?.is_viewer_friend,
67
+ isBirthday: !!d?.node?.messaging_actor?.is_birthday,
68
+ })),
69
+ unreadCount: t?.unread_count ?? 0,
70
+ messageCount: t?.messages_count ?? 0,
71
+ timestamp: t?.updated_time_precise || null,
72
+ muteUntil: t?.mute_until || null,
73
+ isGroup: t?.thread_type === "GROUP",
74
+ isSubscribed: !!t?.is_viewer_subscribed,
75
+ isArchived: !!t?.has_viewer_archived,
76
+ folder: t?.folder || null,
77
+ cannotReplyReason: t?.cannot_reply_reason || null,
78
+ eventReminders: Array.isArray(t?.event_reminders?.nodes) ? t.event_reminders.nodes.map(formatEventReminders) : [],
79
+ emoji: customInfo?.emoji || null,
80
+ color: bubble ? String(bubble).slice(2) : null,
81
+ threadTheme: t?.thread_theme || null,
82
+ nicknames,
83
+ adminIDs: Array.isArray(t?.thread_admins) ? t.thread_admins : [],
84
+ approvalMode: !!t?.approval_mode,
85
+ approvalQueue: approvals.map(a => ({
86
+ inviterID: a?.inviter?.id || null,
87
+ requesterID: a?.requester?.id || null,
88
+ timestamp: a?.request_timestamp || null,
89
+ request_source: a?.request_source || null,
90
+ })),
91
+ reactionsMuteMode: String(t?.reactions_mute_mode || "").toLowerCase(),
92
+ mentionsMuteMode: String(t?.mentions_mute_mode || "").toLowerCase(),
93
+ isPinProtected: !!t?.is_pin_protected,
94
+ relatedPageThread: t?.related_page_thread || null,
95
+ name: t?.name || null,
96
+ snippet: snippetText,
97
+ snippetSender: snippetID,
98
+ snippetAttachments: [],
99
+ serverTimestamp: t?.updated_time_precise || null,
100
+ imageSrc: t?.image?.uri || null,
101
+ isCanonicalUser: !!t?.is_canonical_neo_user,
102
+ isCanonical: t?.thread_type !== "GROUP",
103
+ recipientsLoadable: true,
104
+ hasEmailParticipant: false,
105
+ readOnly: false,
106
+ canReply: t?.cannot_reply_reason == null,
107
+ lastMessageTimestamp: t?.last_message ? t.last_message.timestamp_precise : null,
108
+ lastMessageType: "message",
109
+ lastReadTimestamp,
110
+ threadType: t?.thread_type === "GROUP" ? 2 : 1,
111
+ inviteLink: {
112
+ enable: t?.joinable_mode ? t.joinable_mode.mode == 1 : false,
113
+ link: t?.joinable_mode ? t.joinable_mode.link : null,
114
+ },
115
+ };
116
+ }
117
+
118
+ const queue = [];
119
+ let isProcessingQueue = false;
120
+ const processingThreads = new Set();
121
+ const queuedThreads = new Set();
122
+ const cooldown = new Map();
123
+ const inflight = new Map();
124
+ let loopStarted = false;
125
+
126
+ module.exports = function (defaultFuncs, api, ctx) {
127
+ const getMultiInfo = async function (threadIDs) {
128
+ const buildQueries = () => {
129
+ const form = {};
130
+ threadIDs.forEach((x, y) => {
131
+ form["o" + y] = {
132
+ doc_id: "3449967031715030",
133
+ query_params: {
134
+ id: x,
135
+ message_limit: 0,
136
+ load_messages: false,
137
+ load_read_receipts: false,
138
+ before: null,
139
+ },
140
+ };
141
+ });
142
+ return {
143
+ queries: JSON.stringify(form),
144
+ batch_name: "MessengerGraphQLThreadFetcher",
145
+ };
146
+ };
147
+ const maxAttempts = 3;
148
+ let lastErr = null;
149
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
150
+ try {
151
+ const Submit = buildQueries();
152
+ const resData = await defaultFuncs.post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, Submit).then(parseAndCheckLogin(ctx, defaultFuncs));
153
+ if (!Array.isArray(resData) || resData.length === 0) throw new Error("EmptyGraphBatch");
154
+ const tail = resData[resData.length - 1];
155
+ if (tail?.error_results && tail.error_results !== 0) throw new Error("GraphErrorResults");
156
+ const body = resData.slice(0, -1).sort((a, b) => Object.keys(a)[0].localeCompare(Object.keys(b)[0]));
157
+ const out = [];
158
+ body.forEach((x, y) => out.push(formatThreadGraphQLResponse(x["o" + y]?.data)));
159
+ const valid = out.some(d => !!d && !!d.threadID);
160
+ if (!valid) throw new Error("GraphNoData");
161
+ return { Success: true, Data: out };
162
+ } catch (e) {
163
+ lastErr = e;
164
+ if (attempt < maxAttempts) await new Promise(r => setTimeout(r, 300 * attempt));
165
+ }
166
+ }
167
+ return { Success: false, Data: null, Error: lastErr ? String(lastErr.message || lastErr) : "Unknown" };
168
+ };
169
+
170
+ const dbFiles = fs.readdirSync(path.join(__dirname, "../../database")).filter(f => path.extname(f) === ".js").reduce((acc, file) => {
171
+ acc[path.basename(file, ".js")] = require(path.join(__dirname, "../../database", file))(api);
172
+ return acc;
173
+ }, {});
174
+ const { threadData, userData } = dbFiles;
175
+ const { create, get, update, getAll } = threadData;
176
+ const { update: updateUser } = userData;
177
+
178
+ function isValidThread(d) {
179
+ return d && d.threadID;
180
+ }
181
+
182
+ async function upsertUsersFromThreadInfo(info) {
183
+ try {
184
+ if (!info || !Array.isArray(info.userInfo) || info.userInfo.length === 0) return;
185
+ const tasks = info.userInfo.filter(u => u && u.id).map(u => {
186
+ const data = {
187
+ id: u.id,
188
+ name: u.name || null,
189
+ firstName: u.firstName || null,
190
+ vanity: u.vanity || null,
191
+ url: u.url || null,
192
+ thumbSrc: u.thumbSrc || null,
193
+ profileUrl: u.profileUrl || null,
194
+ gender: u.gender || null,
195
+ type: u.type || null,
196
+ isFriend: !!u.isFriend,
197
+ isBirthday: !!u.isBirthday,
198
+ };
199
+ return updateUser(u.id, { data });
200
+ });
201
+ await Promise.allSettled(tasks);
202
+ } catch (e) {
203
+ logger(`upsertUsers error: ${e?.message || e}`, "warn");
204
+ }
205
+ }
206
+
207
+ async function createOrUpdateThread(id, data) {
208
+ const existing = await get(id);
209
+ if (existing) {
210
+ await update(id, { data });
211
+ return "update";
212
+ } else {
213
+ await create(id, { data });
214
+ return "create";
215
+ }
216
+ }
217
+
218
+ async function fetchThreadInfo(tID, isNew) {
219
+ try {
220
+ const response = await getMultiInfo([tID]);
221
+ if (!response.Success || !response.Data || !isValidThread(response.Data[0])) {
222
+ cooldown.set(tID, Date.now() + 5 * 60 * 1000);
223
+ logger(`GraphQL empty for ${tID}, cooldown applied`, "warn");
224
+ return;
225
+ }
226
+ const threadInfo = response.Data[0];
227
+ await upsertUsersFromThreadInfo(threadInfo);
228
+ const op = await createOrUpdateThread(tID, threadInfo);
229
+ logger(`${op === "create" ? "Success create data thread" : "Success update data thread"}: ${tID}`, "info");
230
+ } catch (err) {
231
+ cooldown.set(tID, Date.now() + 5 * 60 * 1000);
232
+ logger(`fetchThreadInfo error ${tID}: ${err?.message || err}`, "error");
233
+ } finally {
234
+ queuedThreads.delete(tID);
235
+ }
236
+ }
237
+
238
+ async function checkAndUpdateThreads() {
239
+ try {
240
+ const allThreads = await getAll("threadID");
241
+ const existingThreadIDs = new Set(allThreads.map(t => t.threadID));
242
+ const now = Date.now();
243
+ for (const t of existingThreadIDs) {
244
+ const cd = cooldown.get(t);
245
+ if (cd && now < cd) continue;
246
+ const result = await get(t);
247
+ if (!result) continue;
248
+ const lastUpdated = new Date(result.updatedAt).getTime();
249
+ if ((now - lastUpdated) / (1000 * 60) > 10 && !queuedThreads.has(t)) {
250
+ queuedThreads.add(t);
251
+ queue.push(() => fetchThreadInfo(t, false));
252
+ }
253
+ }
254
+ } catch (err) {
255
+ logger(`checkAndUpdateThreads error: ${err?.message || err}`, "error");
256
+ }
257
+ }
258
+
259
+ async function processQueue() {
260
+ if (isProcessingQueue) return;
261
+ isProcessingQueue = true;
262
+ while (queue.length > 0) {
263
+ const task = queue.shift();
264
+ try {
265
+ await task();
266
+ } catch (err) {
267
+ logger(`Queue processing error: ${err?.message || err}`, "error");
268
+ }
269
+ }
270
+ isProcessingQueue = false;
271
+ }
272
+
273
+ if (!loopStarted) {
274
+ loopStarted = true;
275
+ setInterval(() => {
276
+ checkAndUpdateThreads();
277
+ processQueue();
278
+ }, 10000);
279
+ }
280
+
281
+ return async function getThreadInfoGraphQL(threadID, callback) {
282
+ let resolveFunc = function () { };
283
+ let rejectFunc = function () { };
284
+ const returnPromise = new Promise(function (resolve, reject) {
285
+ resolveFunc = resolve;
286
+ rejectFunc = reject;
287
+ });
288
+ if (getType(callback) != "Function" && getType(callback) != "AsyncFunction") {
289
+ callback = function (err, data) {
290
+ if (err) return rejectFunc(err);
291
+ resolveFunc(data);
292
+ };
293
+ }
294
+ if (getType(threadID) !== "Array") threadID = [threadID];
295
+ const tid = String(threadID[0]);
296
+ try {
297
+ const cd = cooldown.get(tid);
298
+ if (cd && Date.now() < cd) {
299
+ const cachedCd = await get(tid);
300
+ if (cachedCd?.data && isValidThread(cachedCd.data)) {
301
+ await upsertUsersFromThreadInfo(cachedCd.data);
302
+ callback(null, cachedCd.data);
303
+ return returnPromise;
304
+ }
305
+ const stub = {
306
+ threadID: tid,
307
+ threadName: null,
308
+ participantIDs: [],
309
+ userInfo: [],
310
+ unreadCount: 0,
311
+ messageCount: 0,
312
+ timestamp: null,
313
+ muteUntil: null,
314
+ isGroup: false,
315
+ isSubscribed: false,
316
+ isArchived: false,
317
+ folder: null,
318
+ cannotReplyReason: null,
319
+ eventReminders: [],
320
+ emoji: null,
321
+ color: null,
322
+ threadTheme: null,
323
+ nicknames: {},
324
+ adminIDs: [],
325
+ approvalMode: false,
326
+ approvalQueue: [],
327
+ reactionsMuteMode: "",
328
+ mentionsMuteMode: "",
329
+ isPinProtected: false,
330
+ relatedPageThread: null,
331
+ name: null,
332
+ snippet: null,
333
+ snippetSender: null,
334
+ snippetAttachments: [],
335
+ serverTimestamp: null,
336
+ imageSrc: null,
337
+ isCanonicalUser: false,
338
+ isCanonical: true,
339
+ recipientsLoadable: false,
340
+ hasEmailParticipant: false,
341
+ readOnly: false,
342
+ canReply: false,
343
+ lastMessageTimestamp: null,
344
+ lastMessageType: "message",
345
+ lastReadTimestamp: null,
346
+ threadType: 1,
347
+ inviteLink: { enable: false, link: null },
348
+ __status: "cooldown",
349
+ };
350
+ await createOrUpdateThread(tid, stub);
351
+ callback(null, stub);
352
+ return returnPromise;
353
+ }
354
+
355
+ const cached = await get(tid);
356
+ if (cached?.data && isValidThread(cached.data)) {
357
+ await upsertUsersFromThreadInfo(cached.data);
358
+ callback(null, cached.data);
359
+ return returnPromise;
360
+ }
361
+
362
+ if (inflight.has(tid)) {
363
+ inflight.get(tid).then(data => callback(null, data)).catch(err => callback(err));
364
+ return returnPromise;
365
+ }
366
+
367
+ const p = (async () => {
368
+ processingThreads.add(tid);
369
+ const response = await getMultiInfo([tid]);
370
+ if (response.Success && response.Data && isValidThread(response.Data[0])) {
371
+ const data = response.Data[0];
372
+ await upsertUsersFromThreadInfo(data);
373
+ await createOrUpdateThread(tid, data);
374
+ return data;
375
+ } else {
376
+ const stub = {
377
+ threadID: tid,
378
+ threadName: null,
379
+ participantIDs: [],
380
+ userInfo: [],
381
+ unreadCount: 0,
382
+ messageCount: 0,
383
+ timestamp: null,
384
+ muteUntil: null,
385
+ isGroup: false,
386
+ isSubscribed: false,
387
+ isArchived: false,
388
+ folder: null,
389
+ cannotReplyReason: null,
390
+ eventReminders: [],
391
+ emoji: null,
392
+ color: null,
393
+ threadTheme: null,
394
+ nicknames: {},
395
+ adminIDs: [],
396
+ approvalMode: false,
397
+ approvalQueue: [],
398
+ reactionsMuteMode: "",
399
+ mentionsMuteMode: "",
400
+ isPinProtected: false,
401
+ relatedPageThread: null,
402
+ name: null,
403
+ snippet: null,
404
+ snippetSender: null,
405
+ snippetAttachments: [],
406
+ serverTimestamp: null,
407
+ imageSrc: null,
408
+ isCanonicalUser: false,
409
+ isCanonical: true,
410
+ recipientsLoadable: false,
411
+ hasEmailParticipant: false,
412
+ readOnly: false,
413
+ canReply: false,
414
+ lastMessageTimestamp: null,
415
+ lastMessageType: "message",
416
+ lastReadTimestamp: null,
417
+ threadType: 1,
418
+ inviteLink: { enable: false, link: null },
419
+ __status: "unavailable",
420
+ };
421
+ cooldown.set(tid, Date.now() + 5 * 60 * 1000);
422
+ await createOrUpdateThread(tid, stub);
423
+ return stub;
424
+ }
425
+ })()
426
+ .finally(() => {
427
+ processingThreads.delete(tid);
428
+ inflight.delete(tid);
429
+ });
430
+
431
+ inflight.set(tid, p);
432
+ p.then(data => callback(null, data)).catch(err => callback(err));
433
+ } catch (err) {
434
+ callback(err);
435
+ }
436
+ return returnPromise;
437
+ };
438
+ };