fca-neokex-fix 1.0.1

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 (114) hide show
  1. package/CHANGELOG.md +220 -0
  2. package/LICENSE +26 -0
  3. package/README.md +346 -0
  4. package/THEME_FEATURES.md +137 -0
  5. package/examples/README.md +131 -0
  6. package/examples/apply-ai-theme.js +127 -0
  7. package/examples/check-current-theme.js +74 -0
  8. package/examples/simple-bot.js +114 -0
  9. package/examples/test-bot.js +752 -0
  10. package/examples/test-logging.js +85 -0
  11. package/examples/theme-usage-example.js +53 -0
  12. package/index.js +2 -0
  13. package/package.json +105 -0
  14. package/src/apis/addExternalModule.js +24 -0
  15. package/src/apis/addUserToGroup.js +108 -0
  16. package/src/apis/changeAdminStatus.js +148 -0
  17. package/src/apis/changeArchivedStatus.js +61 -0
  18. package/src/apis/changeAvatar.js +103 -0
  19. package/src/apis/changeBio.js +69 -0
  20. package/src/apis/changeBlockedStatus.js +54 -0
  21. package/src/apis/changeGroupImage.js +136 -0
  22. package/src/apis/changeThreadColor.js +116 -0
  23. package/src/apis/comment.js +207 -0
  24. package/src/apis/createAITheme.js +129 -0
  25. package/src/apis/createNewGroup.js +79 -0
  26. package/src/apis/createPoll.js +73 -0
  27. package/src/apis/deleteMessage.js +44 -0
  28. package/src/apis/deleteThread.js +52 -0
  29. package/src/apis/editMessage.js +70 -0
  30. package/src/apis/emoji.js +124 -0
  31. package/src/apis/fetchThemeData.js +65 -0
  32. package/src/apis/follow.js +81 -0
  33. package/src/apis/forwardMessage.js +52 -0
  34. package/src/apis/friend.js +243 -0
  35. package/src/apis/gcmember.js +122 -0
  36. package/src/apis/gcname.js +123 -0
  37. package/src/apis/gcrule.js +119 -0
  38. package/src/apis/getAccess.js +111 -0
  39. package/src/apis/getBotInfo.js +88 -0
  40. package/src/apis/getBotInitialData.js +43 -0
  41. package/src/apis/getFriendsList.js +79 -0
  42. package/src/apis/getMessage.js +423 -0
  43. package/src/apis/getTheme.js +104 -0
  44. package/src/apis/getThemeInfo.js +96 -0
  45. package/src/apis/getThreadHistory.js +239 -0
  46. package/src/apis/getThreadInfo.js +257 -0
  47. package/src/apis/getThreadList.js +222 -0
  48. package/src/apis/getThreadPictures.js +58 -0
  49. package/src/apis/getUserID.js +83 -0
  50. package/src/apis/getUserInfo.js +495 -0
  51. package/src/apis/getUserInfoV2.js +146 -0
  52. package/src/apis/handleMessageRequest.js +50 -0
  53. package/src/apis/httpGet.js +63 -0
  54. package/src/apis/httpPost.js +89 -0
  55. package/src/apis/httpPostFormData.js +69 -0
  56. package/src/apis/listenMqtt.js +796 -0
  57. package/src/apis/listenSpeed.js +170 -0
  58. package/src/apis/logout.js +63 -0
  59. package/src/apis/markAsDelivered.js +47 -0
  60. package/src/apis/markAsRead.js +95 -0
  61. package/src/apis/markAsReadAll.js +41 -0
  62. package/src/apis/markAsSeen.js +70 -0
  63. package/src/apis/mqttDeltaValue.js +330 -0
  64. package/src/apis/muteThread.js +45 -0
  65. package/src/apis/nickname.js +132 -0
  66. package/src/apis/notes.js +163 -0
  67. package/src/apis/pinMessage.js +141 -0
  68. package/src/apis/produceMetaTheme.js +180 -0
  69. package/src/apis/realtime.js +161 -0
  70. package/src/apis/removeUserFromGroup.js +117 -0
  71. package/src/apis/resolvePhotoUrl.js +58 -0
  72. package/src/apis/searchForThread.js +154 -0
  73. package/src/apis/sendMessage.js +281 -0
  74. package/src/apis/sendMessageMqtt.js +188 -0
  75. package/src/apis/sendTypingIndicator.js +41 -0
  76. package/src/apis/setMessageReaction.js +27 -0
  77. package/src/apis/setMessageReactionMqtt.js +61 -0
  78. package/src/apis/setThreadTheme.js +260 -0
  79. package/src/apis/setThreadThemeMqtt.js +94 -0
  80. package/src/apis/share.js +107 -0
  81. package/src/apis/shareContact.js +66 -0
  82. package/src/apis/stickers.js +257 -0
  83. package/src/apis/story.js +181 -0
  84. package/src/apis/theme.js +233 -0
  85. package/src/apis/unfriend.js +47 -0
  86. package/src/apis/unsendMessage.js +17 -0
  87. package/src/database/appStateBackup.js +189 -0
  88. package/src/database/models/index.js +56 -0
  89. package/src/database/models/thread.js +31 -0
  90. package/src/database/models/user.js +32 -0
  91. package/src/database/threadData.js +101 -0
  92. package/src/database/userData.js +90 -0
  93. package/src/engine/client.js +91 -0
  94. package/src/engine/models/buildAPI.js +109 -0
  95. package/src/engine/models/loginHelper.js +326 -0
  96. package/src/engine/models/setOptions.js +53 -0
  97. package/src/utils/auth-helpers.js +149 -0
  98. package/src/utils/autoReLogin.js +169 -0
  99. package/src/utils/axios.js +290 -0
  100. package/src/utils/clients.js +270 -0
  101. package/src/utils/constants.js +396 -0
  102. package/src/utils/formatters/data/formatAttachment.js +370 -0
  103. package/src/utils/formatters/data/formatDelta.js +153 -0
  104. package/src/utils/formatters/index.js +159 -0
  105. package/src/utils/formatters/value/formatCookie.js +91 -0
  106. package/src/utils/formatters/value/formatDate.js +36 -0
  107. package/src/utils/formatters/value/formatID.js +16 -0
  108. package/src/utils/formatters.js +1067 -0
  109. package/src/utils/headers.js +199 -0
  110. package/src/utils/index.js +151 -0
  111. package/src/utils/monitoring.js +358 -0
  112. package/src/utils/rateLimiter.js +380 -0
  113. package/src/utils/tokenRefresh.js +311 -0
  114. package/src/utils/user-agents.js +238 -0
@@ -0,0 +1,495 @@
1
+ "use strict";
2
+
3
+ const utils = require('../utils');
4
+ const _ = require('lodash');
5
+ const deepdash = require('deepdash');
6
+ deepdash(_);
7
+
8
+ const DOC_PRIMARY = "5009315269112105";
9
+ const BATCH_PRIMARY = "MessengerParticipantsFetcher";
10
+ const DOC_V2 = "24418640587785718";
11
+ const FRIENDLY_V2 = "CometHovercardQueryRendererQuery";
12
+ const CALLER_V2 = "RelayModern";
13
+
14
+ function toJSONMaybe(s) {
15
+ if (!s) return null;
16
+ if (typeof s === "string") {
17
+ const t = s.trim().replace(/^for\s*\(\s*;\s*;\s*\)\s*;/, "");
18
+ try { return JSON.parse(t); } catch { return null; }
19
+ }
20
+ return s;
21
+ }
22
+
23
+ function usernameFromUrl(raw) {
24
+ if (!raw) return null;
25
+ try {
26
+ const u = new URL(raw);
27
+ if (/^www\.facebook\.com$/i.test(u.hostname)) {
28
+ const seg = u.pathname.replace(/^\//, "").replace(/\/$/, "");
29
+ if (seg && !/^profile\.php$/i.test(seg) && !seg.includes("/")) return seg;
30
+ }
31
+ } catch { }
32
+ return null;
33
+ }
34
+
35
+ function pickMeta(u) {
36
+ let friendshipStatus = null;
37
+ let gender = null;
38
+ let shortName = u?.short_name || null;
39
+ const pa = Array.isArray(u?.primaryActions) ? u.primaryActions : [];
40
+ const sa = Array.isArray(u?.secondaryActions) ? u.secondaryActions : [];
41
+ const aFriend = pa.find(x => x?.profile_action_type === "FRIEND");
42
+ if (aFriend?.client_handler?.profile_action?.restrictable_profile_owner) {
43
+ const p = aFriend.client_handler.profile_action.restrictable_profile_owner;
44
+ friendshipStatus = p?.friendship_status || null;
45
+ gender = p?.gender || gender;
46
+ shortName = p?.short_name || shortName;
47
+ }
48
+ if (!gender || !shortName) {
49
+ const aBlock = sa.find(x => x?.profile_action_type === "BLOCK");
50
+ const p2 = aBlock?.client_handler?.profile_action?.profile_owner;
51
+ if (p2) {
52
+ gender = p2.gender || gender;
53
+ shortName = p2.short_name || shortName;
54
+ }
55
+ }
56
+ return { friendshipStatus, gender, shortName };
57
+ }
58
+
59
+ function normalizePrimaryActor(a) {
60
+ if (!a) return null;
61
+ return {
62
+ id: a.id || null,
63
+ name: a.name || null,
64
+ firstName: a.short_name || null,
65
+ vanity: a.username || null,
66
+ thumbSrc: a.big_image_src?.uri || null,
67
+ profileUrl: a.url || null,
68
+ gender: a.gender || null,
69
+ type: a.__typename || null,
70
+ isFriend: !!a.is_viewer_friend,
71
+ isMessengerUser: !!a.is_messenger_user,
72
+ isMessageBlockedByViewer: !!a.is_message_blocked_by_viewer,
73
+ workInfo: a.work_info || null,
74
+ messengerStatus: a.messenger_account_status_category || null
75
+ };
76
+ }
77
+
78
+ function normalizeV2User(u) {
79
+ if (!u) return null;
80
+ const vanity = usernameFromUrl(u.profile_url || u.url);
81
+ const meta = pickMeta(u);
82
+ return {
83
+ id: u.id || null,
84
+ name: u.name || null,
85
+ firstName: meta.shortName || null,
86
+ vanity: vanity || u.username_for_profile || null,
87
+ thumbSrc: u.profile_picture?.uri || null,
88
+ profileUrl: u.profile_url || u.url || null,
89
+ gender: meta.gender || null,
90
+ type: "User",
91
+ isFriend: meta.friendshipStatus === "ARE_FRIENDS",
92
+ isMessengerUser: null,
93
+ isMessageBlockedByViewer: false,
94
+ workInfo: null,
95
+ messengerStatus: null
96
+ };
97
+ }
98
+
99
+ /**
100
+ * @param {object} data
101
+ * @param {string} userID
102
+ * @returns {object|null}
103
+ */
104
+ function findMainUserObject(data, userID) {
105
+ let mainUserObject = null;
106
+ if (!Array.isArray(data)) return null;
107
+ function deepFind(obj) {
108
+ if (mainUserObject || typeof obj !== 'object' || obj === null) return;
109
+ if (obj.id === userID && obj.__typename === 'User' && obj.profile_tabs) {
110
+ mainUserObject = obj;
111
+ return;
112
+ }
113
+ for (const k in obj) {
114
+ if (obj.hasOwnProperty(k)) {
115
+ deepFind(obj[k]);
116
+ }
117
+ }
118
+ }
119
+ deepFind({ all: data });
120
+ return mainUserObject;
121
+ }
122
+
123
+ /**
124
+ * @param {object} socialContext
125
+ * @param {string} keyword
126
+ * @returns {string|null}
127
+ */
128
+ function findSocialContextText(socialContext, keyword) {
129
+ if (socialContext && Array.isArray(socialContext.content)) {
130
+ for (const item of socialContext.content) {
131
+ const text = item?.text?.text;
132
+ if (text && text.toLowerCase().includes(keyword.toLowerCase())) {
133
+ return text;
134
+ }
135
+ }
136
+ }
137
+ return null;
138
+ }
139
+
140
+ /**
141
+ * @param {Array<Object>} dataArray
142
+ * @param {string} key
143
+ * @returns {any}
144
+ */
145
+ function findFirstValueByKey(dataArray, key) {
146
+ if (!Array.isArray(dataArray)) return null;
147
+ let found = null;
148
+ function deepSearch(obj) {
149
+ if (found !== null || typeof obj !== 'object' || obj === null) return;
150
+ if (obj.hasOwnProperty(key)) {
151
+ found = obj[key];
152
+ return;
153
+ }
154
+ for (const k in obj) {
155
+ if (obj.hasOwnProperty(k)) {
156
+ deepSearch(obj[k]);
157
+ }
158
+ }
159
+ }
160
+ for (const obj of dataArray) {
161
+ deepSearch(obj);
162
+ }
163
+ return found;
164
+ }
165
+
166
+ /**
167
+ * @param {Array<Object>} allJsonData
168
+ * @returns {string|null}
169
+ */
170
+ function findBioFromProfileTiles(allJsonData) {
171
+ try {
172
+ const bio = findFirstValueByKey(allJsonData, 'profile_status_text');
173
+ return bio?.text || null;
174
+ } catch {
175
+ return null;
176
+ }
177
+ }
178
+
179
+ /**
180
+ * @param {Array<Object>} allJsonData
181
+ * @returns {string|null}
182
+ */
183
+ function findLiveCityFromProfileTiles(allJsonData) {
184
+ try {
185
+ const result = _.findDeep(allJsonData, (value, key, parent) => {
186
+ return key === 'text' &&
187
+ typeof value === 'string' &&
188
+ value.includes('Lives in') &&
189
+ parent?.ranges?.[0]?.entity?.category_type === "CITY_WITH_ID";
190
+ });
191
+
192
+ if (result) {
193
+ return result.value;
194
+ }
195
+
196
+ return null;
197
+ } catch (err) {
198
+ return null;
199
+ }
200
+ }
201
+
202
+ module.exports = (defaultFuncs, api, ctx) => {
203
+ function createDefaultUser(id) {
204
+ return {
205
+ id,
206
+ name: "Facebook User",
207
+ firstName: "Facebook",
208
+ lastName: null,
209
+ vanity: id,
210
+ profilePicUrl: `https://graph.facebook.com/${id}/picture?width=720&height=720&access_token=6628568379%7Cc1e620fa708a1d5696fb991c1bde5662`,
211
+ profileUrl: `https://www.facebook.com/profile.php?id=${id}`,
212
+ gender: "no specific gender",
213
+ type: "user",
214
+ isFriend: false,
215
+ isBirthday: false,
216
+ isMessengerUser: null,
217
+ isMessageBlockedByViewer: false,
218
+ workInfo: null,
219
+ messengerStatus: null
220
+ };
221
+ }
222
+
223
+ async function fetchPrimaryBatch(ids) {
224
+ try {
225
+ const form = {
226
+ queries: JSON.stringify({
227
+ o0: {
228
+ doc_id: DOC_PRIMARY,
229
+ query_params: { ids }
230
+ }
231
+ }),
232
+ batch_name: BATCH_PRIMARY
233
+ };
234
+ const resData = await defaultFuncs.post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form)
235
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs));
236
+ if (!resData || resData.length === 0) return {};
237
+ const first = resData[0];
238
+ if (!first || !first.o0) return {};
239
+ if (first.o0.errors && first.o0.errors.length) return {};
240
+ const result = first.o0.data;
241
+ if (!result || !Array.isArray(result.messaging_actors)) return {};
242
+ const out = {};
243
+ for (const actor of result.messaging_actors) {
244
+ const n = normalizePrimaryActor(actor);
245
+ if (n?.id) out[n.id] = n;
246
+ }
247
+ return out;
248
+ } catch (err) {
249
+ utils.error("fetchPrimaryBatch", err);
250
+ return {};
251
+ }
252
+ }
253
+
254
+ async function fetchV2Single(uid) {
255
+ try {
256
+ const av = String(ctx?.userID || "");
257
+ const variablesObj = {
258
+ actionBarRenderLocation: "WWW_COMET_HOVERCARD",
259
+ context: "DEFAULT",
260
+ entityID: String(uid),
261
+ scale: 1,
262
+ __relay_internal__pv__WorkCometIsEmployeeGKProviderrelayprovider: false
263
+ };
264
+ const form = {
265
+ av,
266
+ fb_api_caller_class: CALLER_V2,
267
+ fb_api_req_friendly_name: FRIENDLY_V2,
268
+ server_timestamps: true,
269
+ doc_id: DOC_V2,
270
+ variables: JSON.stringify(variablesObj)
271
+ };
272
+ const raw = await defaultFuncs.post("https://www.facebook.com/api/graphql/", ctx.jar, form)
273
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs));
274
+ const parsed = toJSONMaybe(raw) ?? raw;
275
+ const root = Array.isArray(parsed) ? parsed[0] : parsed;
276
+ const user = root?.data?.node?.comet_hovercard_renderer?.user || null;
277
+ const n = normalizeV2User(user);
278
+ return n && n.id ? { [n.id]: n } : {};
279
+ } catch (err) {
280
+ utils.error(`fetchV2Single ${uid}`, err);
281
+ return {};
282
+ }
283
+ }
284
+
285
+ return function getUserInfo(id, usePayload, callback, groupFields = []) {
286
+ let resolveFunc = () => {};
287
+ let rejectFunc = () => {};
288
+ const returnPromise = new Promise((resolve, reject) => {
289
+ resolveFunc = resolve;
290
+ rejectFunc = reject;
291
+ });
292
+
293
+ if (typeof usePayload === 'function') {
294
+ callback = usePayload;
295
+ usePayload = true;
296
+ }
297
+ if (usePayload === undefined) usePayload = true;
298
+ if (!callback) {
299
+ callback = (err, data) => {
300
+ if (err) return rejectFunc(err);
301
+ resolveFunc(data);
302
+ };
303
+ }
304
+
305
+ const originalIdIsArray = Array.isArray(id);
306
+ const ids = originalIdIsArray ? id : [id];
307
+
308
+ if (usePayload) {
309
+ (async () => {
310
+ try {
311
+ const retObj = {};
312
+ const graphqlData = await fetchPrimaryBatch(ids);
313
+
314
+ const needFallback = [];
315
+ for (const id of ids) {
316
+ if (graphqlData[id]) {
317
+ const gd = graphqlData[id];
318
+ retObj[id] = {
319
+ id: gd.id,
320
+ name: gd.name,
321
+ firstName: gd.firstName,
322
+ lastName: gd.name ? (gd.name.split(' ').slice(1).join(' ') || null) : null,
323
+ vanity: gd.vanity,
324
+ profilePicUrl: gd.thumbSrc || `https://graph.facebook.com/${id}/picture?width=720&height=720&access_token=6628568379%7Cc1e620fa708a1d5696fb991c1bde5662`,
325
+ profileUrl: gd.profileUrl,
326
+ gender: gd.gender,
327
+ type: gd.type,
328
+ isFriend: gd.isFriend,
329
+ isBirthday: false,
330
+ isMessengerUser: gd.isMessengerUser,
331
+ isMessageBlockedByViewer: gd.isMessageBlockedByViewer,
332
+ workInfo: gd.workInfo,
333
+ messengerStatus: gd.messengerStatus
334
+ };
335
+ } else {
336
+ needFallback.push(id);
337
+ }
338
+ }
339
+
340
+ let stillNeedFallback = needFallback;
341
+ if (needFallback.length > 0) {
342
+ const v2Results = await Promise.allSettled(needFallback.map(id => fetchV2Single(id)));
343
+ stillNeedFallback = [];
344
+ for (let i = 0; i < needFallback.length; i++) {
345
+ const id = needFallback[i];
346
+ const v2Data = v2Results[i].status === "fulfilled" ? v2Results[i].value : {};
347
+ if (v2Data[id]) {
348
+ const gd = v2Data[id];
349
+ retObj[id] = {
350
+ id: gd.id,
351
+ name: gd.name,
352
+ firstName: gd.firstName,
353
+ lastName: gd.name ? (gd.name.split(' ').slice(1).join(' ') || null) : null,
354
+ vanity: gd.vanity,
355
+ profilePicUrl: gd.thumbSrc || `https://graph.facebook.com/${id}/picture?width=720&height=720&access_token=6628568379%7Cc1e620fa708a1d5696fb991c1bde5662`,
356
+ profileUrl: gd.profileUrl,
357
+ gender: gd.gender,
358
+ type: gd.type,
359
+ isFriend: gd.isFriend,
360
+ isBirthday: false,
361
+ isMessengerUser: gd.isMessengerUser,
362
+ isMessageBlockedByViewer: gd.isMessageBlockedByViewer,
363
+ workInfo: gd.workInfo,
364
+ messengerStatus: gd.messengerStatus
365
+ };
366
+ } else {
367
+ stillNeedFallback.push(id);
368
+ }
369
+ }
370
+ }
371
+
372
+ if (stillNeedFallback.length > 0) {
373
+ const form = {};
374
+ stillNeedFallback.forEach((v, i) => { form[`ids[${i}]`] = v; });
375
+ const getGenderString = (code) => code === 1 ? "male" : code === 2 ? "female" : "no specific gender";
376
+ const resData = await defaultFuncs.post("https://www.facebook.com/chat/user_info/", ctx.jar, form)
377
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs));
378
+
379
+ if (resData?.error) {
380
+ for (const prop of stillNeedFallback) {
381
+ retObj[prop] = createDefaultUser(prop);
382
+ }
383
+ } else {
384
+ const profiles = resData?.payload?.profiles;
385
+ if (profiles) {
386
+ for (const prop in profiles) {
387
+ if (profiles.hasOwnProperty(prop)) {
388
+ const inner = profiles[prop];
389
+ const nameParts = inner.name ? inner.name.split(' ') : [];
390
+ retObj[prop] = {
391
+ id: prop,
392
+ name: inner.name,
393
+ firstName: inner.firstName,
394
+ lastName: nameParts.length > 1 ? nameParts[nameParts.length - 1] : null,
395
+ vanity: inner.vanity,
396
+ profilePicUrl: `https://graph.facebook.com/${prop}/picture?width=720&height=720&access_token=6628568379%7Cc1e620fa708a1d5696fb991c1bde5662`,
397
+ profileUrl: inner.uri,
398
+ gender: getGenderString(inner.gender),
399
+ type: inner.type,
400
+ isFriend: inner.is_friend,
401
+ isBirthday: !!inner.is_birthday,
402
+ searchTokens: inner.searchTokens,
403
+ alternateName: inner.alternateName,
404
+ isMessengerUser: null,
405
+ isMessageBlockedByViewer: false,
406
+ workInfo: null,
407
+ messengerStatus: null
408
+ };
409
+ }
410
+ }
411
+ } else {
412
+ for (const prop of stillNeedFallback) {
413
+ retObj[prop] = createDefaultUser(prop);
414
+ }
415
+ }
416
+ }
417
+ }
418
+
419
+ // Ensure all requested IDs have entries in retObj
420
+ for (const id of ids) {
421
+ if (!retObj[id]) {
422
+ retObj[id] = createDefaultUser(id);
423
+ }
424
+ }
425
+
426
+ // ALWAYS return retObj (map format) for consistency
427
+ return callback(null, retObj);
428
+ } catch (err) {
429
+ utils.error("getUserInfo (payload)", err);
430
+ // Instead of returning error, return default users
431
+ const retObj = {};
432
+ for (const id of ids) {
433
+ retObj[id] = createDefaultUser(id);
434
+ }
435
+ // ALWAYS return retObj (map format) for consistency
436
+ return callback(null, retObj);
437
+ }
438
+ })();
439
+ } else {
440
+ const fetchProfile = async (userID) => {
441
+ try {
442
+ const url = `https://www.facebook.com/${userID}`;
443
+ const allJsonData = await utils.json(url, ctx.jar, null, ctx.globalOptions, ctx);
444
+ if (!allJsonData || allJsonData.length === 0) throw new Error(`Could not find JSON data for ID: ${userID}`);
445
+ const mainUserObject = findMainUserObject(allJsonData, userID);
446
+ if (!mainUserObject) throw new Error(`Could not isolate main user object for ID: ${userID}`);
447
+ const get = (obj, path) => {
448
+ if (!obj || !path) return null;
449
+ return path.split('.').reduce((prev, curr) => (prev ? prev[curr] : undefined), obj);
450
+ };
451
+ const name = mainUserObject.name;
452
+ const nameParts = name ? name.split(' ') : [];
453
+ const result = {
454
+ id: mainUserObject.id,
455
+ name: name,
456
+ firstName: nameParts[0] || get(mainUserObject, 'short_name') || get(findFirstValueByKey(allJsonData, 'profile_owner'), 'short_name'),
457
+ lastName: nameParts.length > 1 ? nameParts[nameParts.length - 1] : null,
458
+ vanity: get(mainUserObject, 'vanity') || get(findFirstValueByKey(allJsonData, 'props'), 'userVanity') || null,
459
+ profileUrl: mainUserObject.url,
460
+ profilePicUrl: `https://graph.facebook.com/${userID}/picture?width=720&height=720&access_token=6628568379%7Cc1e620fa708a1d5696fb991c1bde5662`,
461
+ gender: mainUserObject.gender,
462
+ type: mainUserObject.__typename,
463
+ isFriend: mainUserObject.is_viewer_friend,
464
+ isBirthday: !!mainUserObject.is_birthday,
465
+ isVerified: !!mainUserObject.show_verified_badge_on_profile,
466
+ bio: findBioFromProfileTiles(allJsonData) || get(findFirstValueByKey(allJsonData, 'delegate_page'), 'best_description.text'),
467
+ live_city: findLiveCityFromProfileTiles(allJsonData),
468
+ headline: get(mainUserObject, 'contextual_headline.text') || get(findFirstValueByKey(allJsonData, 'meta_verified_section'), 'headline'),
469
+ followers: findSocialContextText(mainUserObject.profile_social_context, "followers"),
470
+ following: findSocialContextText(mainUserObject.profile_social_context, "following"),
471
+ coverPhoto: get(mainUserObject, 'cover_photo.photo.image.uri'),
472
+ isMessengerUser: null,
473
+ isMessageBlockedByViewer: false,
474
+ workInfo: null,
475
+ messengerStatus: null
476
+ };
477
+ return result;
478
+ } catch (err) {
479
+ utils.error(`Failed to fetch profile for ${userID}: ${err.message}`, err);
480
+ return createDefaultUser(userID);
481
+ }
482
+ };
483
+
484
+ Promise.all(ids.map(fetchProfile))
485
+ .then(results => {
486
+ return originalIdIsArray ? callback(null, results) : callback(null, results[0] || null);
487
+ })
488
+ .catch(err => {
489
+ utils.error("getUserInfo (fetch)", err);
490
+ callback(err);
491
+ });
492
+ }
493
+ return returnPromise;
494
+ };
495
+ };
@@ -0,0 +1,146 @@
1
+ "use strict";
2
+
3
+ const utils = require('../utils');
4
+
5
+ const DOC_V2 = "24418640587785718";
6
+ const FRIENDLY_V2 = "CometHovercardQueryRendererQuery";
7
+ const CALLER_V2 = "RelayModern";
8
+
9
+ function toJSONMaybe(s) {
10
+ if (!s) return null;
11
+ if (typeof s === "string") {
12
+ const t = s.trim().replace(/^for\s*\(\s*;\s*;\s*\)\s*;/, "");
13
+ try { return JSON.parse(t); } catch { return null; }
14
+ }
15
+ return s;
16
+ }
17
+
18
+ function usernameFromUrl(raw) {
19
+ if (!raw) return null;
20
+ try {
21
+ const u = new URL(raw);
22
+ if (/^www\.facebook\.com$/i.test(u.hostname)) {
23
+ const seg = u.pathname.replace(/^\//, "").replace(/\/$/, "");
24
+ if (seg && !/^profile\.php$/i.test(seg) && !seg.includes("/")) return seg;
25
+ }
26
+ } catch { }
27
+ return null;
28
+ }
29
+
30
+ function pickMeta(u) {
31
+ let friendshipStatus = null;
32
+ let gender = null;
33
+ let shortName = u?.short_name || null;
34
+ const pa = Array.isArray(u?.primaryActions) ? u.primaryActions : [];
35
+ const sa = Array.isArray(u?.secondaryActions) ? u.secondaryActions : [];
36
+ const aFriend = pa.find(x => x?.profile_action_type === "FRIEND");
37
+ if (aFriend?.client_handler?.profile_action?.restrictable_profile_owner) {
38
+ const p = aFriend.client_handler.profile_action.restrictable_profile_owner;
39
+ friendshipStatus = p?.friendship_status || null;
40
+ gender = p?.gender || gender;
41
+ shortName = p?.short_name || shortName;
42
+ }
43
+ if (!gender || !shortName) {
44
+ const aBlock = sa.find(x => x?.profile_action_type === "BLOCK");
45
+ const p2 = aBlock?.client_handler?.profile_action?.profile_owner;
46
+ if (p2) {
47
+ gender = p2.gender || gender;
48
+ shortName = p2.short_name || shortName;
49
+ }
50
+ }
51
+ return { friendshipStatus, gender, shortName };
52
+ }
53
+
54
+ function normalizeUser(u) {
55
+ if (!u) return null;
56
+ const vanity = usernameFromUrl(u.profile_url || u.url);
57
+ const meta = pickMeta(u);
58
+ return {
59
+ id: u.id || null,
60
+ name: u.name || null,
61
+ username: vanity || u.username_for_profile || null,
62
+ profileUrl: u.profile_url || u.url || null,
63
+ url: u.url || null,
64
+ isVerified: !!u.is_verified,
65
+ isMemorialized: !!u.is_visibly_memorialized,
66
+ avatar: u.profile_picture?.uri || null,
67
+ shortName: meta.shortName || null,
68
+ gender: meta.gender || null,
69
+ friendshipStatus: meta.friendshipStatus || null
70
+ };
71
+ }
72
+
73
+ function toRetObjEntry(nu) {
74
+ return {
75
+ name: nu?.name || null,
76
+ firstName: nu?.shortName || null,
77
+ vanity: nu?.username || null,
78
+ thumbSrc: nu?.avatar || null,
79
+ profileUrl: nu?.profileUrl || null,
80
+ gender: nu?.gender || null,
81
+ type: "User",
82
+ isFriend: nu?.friendshipStatus === "ARE_FRIENDS",
83
+ isMessengerUser: null,
84
+ isMessageBlockedByViewer: false,
85
+ workInfo: null,
86
+ messengerStatus: null,
87
+ isVerified: nu?.isVerified || false,
88
+ isMemorialized: nu?.isMemorialized || false
89
+ };
90
+ }
91
+
92
+ module.exports = (defaultFuncs, api, ctx) => {
93
+ async function fetchOne(uid) {
94
+ const av = String(ctx?.userID || "");
95
+ const variablesObj = {
96
+ actionBarRenderLocation: "WWW_COMET_HOVERCARD",
97
+ context: "DEFAULT",
98
+ entityID: String(uid),
99
+ scale: 1,
100
+ __relay_internal__pv__WorkCometIsEmployeeGKProviderrelayprovider: false
101
+ };
102
+ const form = {
103
+ av,
104
+ fb_api_caller_class: CALLER_V2,
105
+ fb_api_req_friendly_name: FRIENDLY_V2,
106
+ server_timestamps: true,
107
+ doc_id: DOC_V2,
108
+ variables: JSON.stringify(variablesObj)
109
+ };
110
+ const raw = await defaultFuncs.post("https://www.facebook.com/api/graphql/", ctx.jar, form)
111
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs));
112
+ const parsed = toJSONMaybe(raw) ?? raw;
113
+ const root = Array.isArray(parsed) ? parsed[0] : parsed;
114
+ const user = root?.data?.node?.comet_hovercard_renderer?.user || null;
115
+ return normalizeUser(user);
116
+ }
117
+
118
+ return function getUserInfoV2(idOrList, callback) {
119
+ let resolveFunc, rejectFunc;
120
+ const returnPromise = new Promise((resolve, reject) => {
121
+ resolveFunc = resolve;
122
+ rejectFunc = reject;
123
+ });
124
+ if (typeof callback !== "function") {
125
+ callback = (err, data) => {
126
+ if (err) return rejectFunc(err);
127
+ resolveFunc(data);
128
+ };
129
+ }
130
+ const ids = Array.isArray(idOrList) ? idOrList.map(v => String(v)) : [String(idOrList)];
131
+ Promise.allSettled(ids.map(fetchOne))
132
+ .then(results => {
133
+ const retObj = {};
134
+ for (let i = 0; i < ids.length; i++) {
135
+ const nu = results[i].status === "fulfilled" ? results[i].value : null;
136
+ retObj[ids[i]] = toRetObjEntry(nu);
137
+ }
138
+ return callback(null, retObj);
139
+ })
140
+ .catch(err => {
141
+ utils.error("getUserInfoV2", err);
142
+ callback(err);
143
+ });
144
+ return returnPromise;
145
+ };
146
+ };
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+
3
+ const utils = require('../utils');
4
+
5
+ module.exports = (defaultFuncs, api, ctx) => {
6
+ return async function handleMessageRequest(threadID, accept, callback) {
7
+ let resolveFunc = () => {};
8
+ let rejectFunc = () => {};
9
+ const returnPromise = new Promise((resolve, reject) => {
10
+ resolveFunc = resolve;
11
+ rejectFunc = reject;
12
+ });
13
+
14
+ if (!callback) {
15
+ callback = (err, result) => {
16
+ if (err) return rejectFunc(err);
17
+ resolveFunc(result);
18
+ };
19
+ }
20
+
21
+ try {
22
+ if (utils.getType(accept) === "Function") {
23
+ callback = accept;
24
+ accept = true;
25
+ }
26
+
27
+ const form = {
28
+ ids: `ids[${threadID}]=${threadID}`,
29
+ action: accept ? "accept" : "reject"
30
+ };
31
+
32
+ const res = await defaultFuncs.post(
33
+ "https://www.facebook.com/ajax/mercury/handle_message_requests.php",
34
+ ctx.jar,
35
+ form
36
+ ).then(utils.parseAndCheckLogin(ctx, defaultFuncs));
37
+
38
+ if (res && res.error) {
39
+ throw res;
40
+ }
41
+
42
+ return callback(null, { success: true, accepted: accept });
43
+ } catch (err) {
44
+ utils.error("handleMessageRequest", err);
45
+ callback(err);
46
+ }
47
+
48
+ return returnPromise;
49
+ };
50
+ };