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.
- package/CHANGELOG.md +220 -0
- package/LICENSE +26 -0
- package/README.md +346 -0
- package/THEME_FEATURES.md +137 -0
- package/examples/README.md +131 -0
- package/examples/apply-ai-theme.js +127 -0
- package/examples/check-current-theme.js +74 -0
- package/examples/simple-bot.js +114 -0
- package/examples/test-bot.js +752 -0
- package/examples/test-logging.js +85 -0
- package/examples/theme-usage-example.js +53 -0
- package/index.js +2 -0
- package/package.json +105 -0
- package/src/apis/addExternalModule.js +24 -0
- package/src/apis/addUserToGroup.js +108 -0
- package/src/apis/changeAdminStatus.js +148 -0
- package/src/apis/changeArchivedStatus.js +61 -0
- package/src/apis/changeAvatar.js +103 -0
- package/src/apis/changeBio.js +69 -0
- package/src/apis/changeBlockedStatus.js +54 -0
- package/src/apis/changeGroupImage.js +136 -0
- package/src/apis/changeThreadColor.js +116 -0
- package/src/apis/comment.js +207 -0
- package/src/apis/createAITheme.js +129 -0
- package/src/apis/createNewGroup.js +79 -0
- package/src/apis/createPoll.js +73 -0
- package/src/apis/deleteMessage.js +44 -0
- package/src/apis/deleteThread.js +52 -0
- package/src/apis/editMessage.js +70 -0
- package/src/apis/emoji.js +124 -0
- package/src/apis/fetchThemeData.js +65 -0
- package/src/apis/follow.js +81 -0
- package/src/apis/forwardMessage.js +52 -0
- package/src/apis/friend.js +243 -0
- package/src/apis/gcmember.js +122 -0
- package/src/apis/gcname.js +123 -0
- package/src/apis/gcrule.js +119 -0
- package/src/apis/getAccess.js +111 -0
- package/src/apis/getBotInfo.js +88 -0
- package/src/apis/getBotInitialData.js +43 -0
- package/src/apis/getFriendsList.js +79 -0
- package/src/apis/getMessage.js +423 -0
- package/src/apis/getTheme.js +104 -0
- package/src/apis/getThemeInfo.js +96 -0
- package/src/apis/getThreadHistory.js +239 -0
- package/src/apis/getThreadInfo.js +257 -0
- package/src/apis/getThreadList.js +222 -0
- package/src/apis/getThreadPictures.js +58 -0
- package/src/apis/getUserID.js +83 -0
- package/src/apis/getUserInfo.js +495 -0
- package/src/apis/getUserInfoV2.js +146 -0
- package/src/apis/handleMessageRequest.js +50 -0
- package/src/apis/httpGet.js +63 -0
- package/src/apis/httpPost.js +89 -0
- package/src/apis/httpPostFormData.js +69 -0
- package/src/apis/listenMqtt.js +796 -0
- package/src/apis/listenSpeed.js +170 -0
- package/src/apis/logout.js +63 -0
- package/src/apis/markAsDelivered.js +47 -0
- package/src/apis/markAsRead.js +95 -0
- package/src/apis/markAsReadAll.js +41 -0
- package/src/apis/markAsSeen.js +70 -0
- package/src/apis/mqttDeltaValue.js +330 -0
- package/src/apis/muteThread.js +45 -0
- package/src/apis/nickname.js +132 -0
- package/src/apis/notes.js +163 -0
- package/src/apis/pinMessage.js +141 -0
- package/src/apis/produceMetaTheme.js +180 -0
- package/src/apis/realtime.js +161 -0
- package/src/apis/removeUserFromGroup.js +117 -0
- package/src/apis/resolvePhotoUrl.js +58 -0
- package/src/apis/searchForThread.js +154 -0
- package/src/apis/sendMessage.js +281 -0
- package/src/apis/sendMessageMqtt.js +188 -0
- package/src/apis/sendTypingIndicator.js +41 -0
- package/src/apis/setMessageReaction.js +27 -0
- package/src/apis/setMessageReactionMqtt.js +61 -0
- package/src/apis/setThreadTheme.js +260 -0
- package/src/apis/setThreadThemeMqtt.js +94 -0
- package/src/apis/share.js +107 -0
- package/src/apis/shareContact.js +66 -0
- package/src/apis/stickers.js +257 -0
- package/src/apis/story.js +181 -0
- package/src/apis/theme.js +233 -0
- package/src/apis/unfriend.js +47 -0
- package/src/apis/unsendMessage.js +17 -0
- package/src/database/appStateBackup.js +189 -0
- package/src/database/models/index.js +56 -0
- package/src/database/models/thread.js +31 -0
- package/src/database/models/user.js +32 -0
- package/src/database/threadData.js +101 -0
- package/src/database/userData.js +90 -0
- package/src/engine/client.js +91 -0
- package/src/engine/models/buildAPI.js +109 -0
- package/src/engine/models/loginHelper.js +326 -0
- package/src/engine/models/setOptions.js +53 -0
- package/src/utils/auth-helpers.js +149 -0
- package/src/utils/autoReLogin.js +169 -0
- package/src/utils/axios.js +290 -0
- package/src/utils/clients.js +270 -0
- package/src/utils/constants.js +396 -0
- package/src/utils/formatters/data/formatAttachment.js +370 -0
- package/src/utils/formatters/data/formatDelta.js +153 -0
- package/src/utils/formatters/index.js +159 -0
- package/src/utils/formatters/value/formatCookie.js +91 -0
- package/src/utils/formatters/value/formatDate.js +36 -0
- package/src/utils/formatters/value/formatID.js +16 -0
- package/src/utils/formatters.js +1067 -0
- package/src/utils/headers.js +199 -0
- package/src/utils/index.js +151 -0
- package/src/utils/monitoring.js +358 -0
- package/src/utils/rateLimiter.js +380 -0
- package/src/utils/tokenRefresh.js +311 -0
- 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
|
+
};
|