bb-fca 2.0.0 → 2.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.
@@ -1,4 +1,4 @@
1
- import utils = require("../../../utils");
1
+ import utils = require('../../../utils');
2
2
 
3
3
  /**
4
4
  * @ChoruOfficial
@@ -9,7 +9,6 @@ import utils = require("../../../utils");
9
9
  * @returns {Object} A `friendModule` object with methods for friend interactions.
10
10
  */
11
11
  export default function(defaultFuncs: any, api: any, ctx: any) {
12
-
13
12
  /**
14
13
  * A private helper function to standardize friend data from various GraphQL endpoints.
15
14
  * @private
@@ -21,23 +20,29 @@ export default function(defaultFuncs: any, api: any, ctx: any) {
21
20
  const viewer = data?.data?.viewer;
22
21
  let edges;
23
22
  if (type === 'requests' && viewer?.friend_requests?.edges) {
24
- edges = viewer.friend_requests.edges;
23
+ edges = viewer.friend_requests.edges;
25
24
  } else if (type === 'suggestions' && viewer?.people_you_may_know?.edges) {
26
- edges = viewer.people_you_may_know.edges;
27
- } else if (type === 'list' && data?.data?.node?.all_collections?.nodes[0]?.style_renderer?.collection?.pageItems?.edges) {
28
- edges = data.data.node.all_collections.nodes[0].style_renderer.collection.pageItems.edges;
25
+ edges = viewer.people_you_may_know.edges;
26
+ } else if (
27
+ type === 'list' &&
28
+ data?.data?.node?.all_collections?.nodes[0]?.style_renderer?.collection
29
+ ?.pageItems?.edges
30
+ ) {
31
+ edges =
32
+ data.data.node.all_collections.nodes[0].style_renderer.collection
33
+ .pageItems.edges;
29
34
  } else {
30
- return [];
35
+ return [];
31
36
  }
32
- return edges.map(edge => {
33
- const node = edge.node;
34
- return {
35
- userID: node.id || node.node?.id,
36
- name: node.name || node.title?.text,
37
- profilePicture: node.profile_picture?.uri || node.image?.uri,
38
- socialContext: node.social_context?.text || node.subtitle_text?.text,
39
- url: node.url
40
- };
37
+ return edges.map((edge) => {
38
+ const node = edge.node;
39
+ return {
40
+ userID: node.id || node.node?.id,
41
+ name: node.name || node.title?.text,
42
+ profilePicture: node.profile_picture?.uri || node.image?.uri,
43
+ socialContext: node.social_context?.text || node.subtitle_text?.text,
44
+ url: node.url,
45
+ };
41
46
  });
42
47
  }
43
48
 
@@ -50,79 +55,147 @@ export default function(defaultFuncs: any, api: any, ctx: any) {
50
55
  */
51
56
 
52
57
  /**
53
- * Fetches the list of incoming friend requests.
58
+ * Fetches the list of incoming friend requests by parsing the /friends/requests page.
54
59
  * @async
55
60
  * @returns {Promise<Array<Object>>} A promise that resolves to an array of friend request objects.
56
- * @throws {Error} If the API request fails or returns an error.
61
+ * @throws {Error} If the API request fails or HTML parsing fails.
57
62
  */
58
63
  requests: async function() {
59
64
  try {
65
+ const url = 'https://www.facebook.com/friends/requests';
66
+ const resData = await utils.get(
67
+ url,
68
+ ctx.jar,
69
+ {},
70
+ ctx.globalOptions,
71
+ ctx,
72
+ );
73
+ const html = resData.body.toString();
74
+ return extractFriendRequestsFromHTML(html);
75
+ } catch (err) {
76
+ utils.error('friend.requests', err);
77
+ throw err;
78
+ }
79
+ },
80
+
81
+ /**
82
+ * Accepts a friend request.
83
+ * @async
84
+ * @param {string} identifier The user ID or name of the person whose friend request is to be accepted. If a name is provided, the function will search through pending requests.
85
+ * @returns {Promise<Object>} A promise that resolves to the API response data upon successful acceptance.
86
+ * @throws {Error} If the identifier is missing, the user is not found in requests, or the API call fails.
87
+ */
88
+ accept: async function(identifier) {
89
+ try {
90
+ if (!identifier) throw new Error('A name or user ID is required.');
91
+ let targetUserID = identifier;
92
+ if (isNaN(identifier)) {
93
+ const requests = await friendModule.requests();
94
+ const found = requests.find((req) =>
95
+ req.name.toLowerCase().includes(identifier.toLowerCase()),
96
+ );
97
+ if (!found)
98
+ throw new Error(
99
+ `Could not find any friend request matching "${identifier}".`,
100
+ );
101
+ targetUserID = found.userID;
102
+ }
103
+ const variables = {
104
+ input: {
105
+ friend_requester_id: targetUserID,
106
+ friending_channel: 'FRIENDS_HOME_MAIN',
107
+ actor_id: ctx.userID,
108
+ client_mutation_id: Math.floor(Math.random() * 10 + 1).toString(),
109
+ },
110
+ scale: 3,
111
+ };
60
112
  const form = {
61
113
  av: ctx.userID,
62
114
  __user: ctx.userID,
63
- __a: "1",
115
+ __a: '1',
64
116
  fb_dtsg: ctx.fb_dtsg,
65
117
  jazoest: ctx.jazoest,
66
118
  lsd: ctx.lsd,
67
- fb_api_caller_class: "RelayModern",
68
- fb_api_req_friendly_name: "FriendingCometRootContentQuery",
69
- variables: JSON.stringify({ scale: 3 }),
70
- doc_id: "9103543533085580"
119
+ fb_api_caller_class: 'RelayModern',
120
+ fb_api_req_friendly_name:
121
+ 'FriendingCometFriendRequestConfirmMutation',
122
+ variables: JSON.stringify(variables),
123
+ doc_id: '24630768433181357',
71
124
  };
72
- const res = await defaultFuncs.post("https://www.facebook.com/api/graphql/", ctx.jar, form, {});
125
+ const res = await defaultFuncs.post(
126
+ 'https://www.facebook.com/api/graphql/',
127
+ ctx.jar,
128
+ form,
129
+ {},
130
+ );
73
131
  if (res.data.errors) throw new Error(JSON.stringify(res.data.errors));
74
- return formatFriends(res.data, 'requests');
132
+ return res.data.data;
75
133
  } catch (err) {
134
+ if (err.message?.includes('1431004')) {
135
+ throw new Error(
136
+ 'I cannot accept this friend request right now. There might be a problem with the account or you need to wait.',
137
+ );
138
+ }
76
139
  throw err;
77
140
  }
78
141
  },
79
142
 
80
143
  /**
81
- * Accepts a friend request.
144
+ * Deletes/rejects a friend request.
82
145
  * @async
83
- * @param {string} identifier The user ID or name of the person whose friend request is to be accepted. If a name is provided, the function will search through pending requests.
84
- * @returns {Promise<Object>} A promise that resolves to the API response data upon successful acceptance.
146
+ * @param {string} identifier The user ID or name of the person whose friend request is to be deleted. If a name is provided, the function will search through pending requests.
147
+ * @returns {Promise<Object>} A promise that resolves to the API response data upon successful deletion.
85
148
  * @throws {Error} If the identifier is missing, the user is not found in requests, or the API call fails.
86
149
  */
87
- accept: async function(identifier) {
150
+ delete: async function(identifier) {
88
151
  try {
89
- if (!identifier) throw new Error("A name or user ID is required.");
90
- let targetUserID = identifier;
91
- if (isNaN(identifier)) {
92
- const requests = await friendModule.requests();
93
- const found = requests.find(req => req.name.toLowerCase().includes(identifier.toLowerCase()));
94
- if (!found) throw new Error(`Could not find any friend request matching "${identifier}".`);
95
- targetUserID = found.userID;
96
- }
97
- const variables = {
98
- input: {
99
- friend_requester_id: targetUserID,
100
- friending_channel: "FRIENDS_HOME_MAIN",
101
- actor_id: ctx.userID,
102
- client_mutation_id: Math.floor(Math.random() * 10 + 1).toString()
103
- },
104
- scale: 3
105
- };
106
- const form = {
107
- av: ctx.userID,
108
- __user: ctx.userID,
109
- __a: "1",
110
- fb_dtsg: ctx.fb_dtsg,
111
- jazoest: ctx.jazoest,
112
- lsd: ctx.lsd,
113
- fb_api_caller_class: "RelayModern",
114
- fb_api_req_friendly_name: "FriendingCometFriendRequestConfirmMutation",
115
- variables: JSON.stringify(variables),
116
- doc_id: "24630768433181357"
117
- };
118
- const res = await defaultFuncs.post("https://www.facebook.com/api/graphql/", ctx.jar, form, {});
119
- if (res.data.errors) throw new Error(JSON.stringify(res.data.errors));
120
- return res.data.data;
152
+ if (!identifier) throw new Error('A name or user ID is required.');
153
+ let targetUserID = identifier;
154
+ if (isNaN(identifier)) {
155
+ const requests = await friendModule.requests();
156
+ const found = requests.find((req) =>
157
+ req.name.toLowerCase().includes(identifier.toLowerCase()),
158
+ );
159
+ if (!found)
160
+ throw new Error(
161
+ `Could not find any friend request matching "${identifier}".`,
162
+ );
163
+ targetUserID = found.userID;
164
+ }
165
+ const variables = {
166
+ input: {
167
+ click_correlation_id: Date.now().toString(),
168
+ click_proof_validation_result: '{"validated":true}',
169
+ friend_requester_id: targetUserID,
170
+ friending_channel: 'FRIENDS_HOME_REQUESTS',
171
+ actor_id: ctx.userID,
172
+ client_mutation_id: Math.floor(Math.random() * 10 + 1).toString(),
173
+ },
174
+ scale: 1,
175
+ refresh_num: 0,
176
+ };
177
+ const form = {
178
+ av: ctx.userID,
179
+ __user: ctx.userID,
180
+ __a: '1',
181
+ fb_dtsg: ctx.fb_dtsg,
182
+ jazoest: ctx.jazoest,
183
+ lsd: ctx.lsd,
184
+ fb_api_caller_class: 'RelayModern',
185
+ fb_api_req_friendly_name: 'FriendingCometFriendRequestDeleteMutation',
186
+ variables: JSON.stringify(variables),
187
+ doc_id: '25693816160239288',
188
+ };
189
+ const res = await defaultFuncs.post(
190
+ 'https://www.facebook.com/api/graphql/',
191
+ ctx.jar,
192
+ form,
193
+ {},
194
+ );
195
+ if (res.data.errors) throw new Error(JSON.stringify(res.data.errors));
196
+ return res.data.data;
121
197
  } catch (err) {
122
- if (err.message?.includes("1431004")) {
123
- throw new Error("I cannot accept this friend request right now. There might be a problem with the account or you need to wait.");
124
- }
125
- throw err;
198
+ throw err;
126
199
  }
127
200
  },
128
201
 
@@ -134,108 +207,395 @@ export default function(defaultFuncs: any, api: any, ctx: any) {
134
207
  * @throws {Error} If the API request fails.
135
208
  */
136
209
  list: async function(userID = ctx.userID) {
137
- try {
138
- const sectionToken = Buffer.from(`app_section:${userID}:2356318349`).toString('base64');
139
- const variables = {
140
- collectionToken: null,
141
- scale: 2,
142
- sectionToken: sectionToken,
143
- useDefaultActor: false,
144
- userID: userID
145
- };
146
- const form = {
147
- av: ctx.userID,
148
- __user: ctx.userID,
149
- __a: "1",
150
- fb_dtsg: ctx.fb_dtsg,
151
- jazoest: ctx.jazoest,
152
- lsd: ctx.lsd,
153
- fb_api_caller_class: "RelayModern",
154
- fb_api_req_friendly_name: "ProfileCometTopAppSectionQuery",
155
- variables: JSON.stringify(variables),
156
- doc_id: "24492266383698794"
157
- };
158
- const res = await defaultFuncs.post("https://www.facebook.com/api/graphql/", ctx.jar, form, {});
159
- if (res.data.errors) throw new Error(JSON.stringify(res.data.errors));
160
- return formatFriends(res.data, 'list');
161
- } catch(err) {
162
- throw err;
163
- }
210
+ try {
211
+ const sectionToken = Buffer.from(
212
+ `app_section:${userID}:2356318349`,
213
+ ).toString('base64');
214
+ const variables = {
215
+ collectionToken: null,
216
+ scale: 2,
217
+ sectionToken: sectionToken,
218
+ useDefaultActor: false,
219
+ userID: userID,
220
+ };
221
+ const form = {
222
+ av: ctx.userID,
223
+ __user: ctx.userID,
224
+ __a: '1',
225
+ fb_dtsg: ctx.fb_dtsg,
226
+ jazoest: ctx.jazoest,
227
+ lsd: ctx.lsd,
228
+ fb_api_caller_class: 'RelayModern',
229
+ fb_api_req_friendly_name: 'ProfileCometTopAppSectionQuery',
230
+ variables: JSON.stringify(variables),
231
+ doc_id: '24492266383698794',
232
+ };
233
+ const res = await defaultFuncs.post(
234
+ 'https://www.facebook.com/api/graphql/',
235
+ ctx.jar,
236
+ form,
237
+ {},
238
+ );
239
+ if (res.data.errors) throw new Error(JSON.stringify(res.data.errors));
240
+ return formatFriends(res.data, 'list');
241
+ } catch (err) {
242
+ throw err;
243
+ }
164
244
  },
165
245
 
166
246
  /**
167
247
  * @namespace api.friend.suggest
168
248
  * @description Functions for managing friend suggestions.
169
249
  */
250
+ getCurrentFriends: getCurrentFriends,
251
+
170
252
  suggest: {
171
- /**
172
- * Fetches a list of suggested friends (People You May Know).
173
- * @async
174
- * @param {number} [limit=30] The maximum number of suggestions to fetch.
175
- * @returns {Promise<Array<Object>>} A promise that resolves to an array of suggested friend objects.
176
- * @throws {Error} If the API request fails.
177
- */
178
- list: async function(limit = 30) {
179
- try {
180
- const form = {
181
- av: ctx.userID,
182
- __user: ctx.userID,
183
- __a: "1",
184
- fb_dtsg: ctx.fb_dtsg,
185
- jazoest: ctx.jazoest,
186
- lsd: ctx.lsd,
187
- fb_api_caller_class: "RelayModern",
188
- fb_api_req_friendly_name: "FriendingCometPYMKPanelPaginationQuery",
189
- variables: JSON.stringify({ count: limit, cursor: null, scale: 3 }),
190
- doc_id: "9917809191634193"
191
- };
192
- const res = await defaultFuncs.post("https://www.facebook.com/api/graphql/", ctx.jar, form, {});
193
- if (res.data.errors) throw new Error(JSON.stringify(res.data.errors));
194
- return formatFriends(res.data, 'suggestions');
195
- } catch(err) {
196
- throw err;
253
+ /**
254
+ * Fetches a list of suggested friends (People You May Know).
255
+ * @async
256
+ * @param {number} [limit=30] The maximum number of suggestions to fetch.
257
+ * @returns {Promise<Array<Object>>} A promise that resolves to an array of suggested friend objects.
258
+ * @throws {Error} If the API request fails.
259
+ */
260
+ list: async function(limit = 30) {
261
+ try {
262
+ const form = {
263
+ av: ctx.userID,
264
+ __user: ctx.userID,
265
+ __a: '1',
266
+ fb_dtsg: ctx.fb_dtsg,
267
+ jazoest: ctx.jazoest,
268
+ lsd: ctx.lsd,
269
+ fb_api_caller_class: 'RelayModern',
270
+ fb_api_req_friendly_name: 'FriendingCometPYMKPanelPaginationQuery',
271
+ variables: JSON.stringify({ count: limit, cursor: null, scale: 3 }),
272
+ doc_id: '9917809191634193',
273
+ };
274
+ const res = await defaultFuncs.post(
275
+ 'https://www.facebook.com/api/graphql/',
276
+ ctx.jar,
277
+ form,
278
+ {},
279
+ );
280
+ if (res.data.errors) throw new Error(JSON.stringify(res.data.errors));
281
+ return formatFriends(res.data, 'suggestions');
282
+ } catch (err) {
283
+ throw err;
284
+ }
285
+ },
286
+ /**
287
+ * Sends a friend request to a user.
288
+ * @async
289
+ * @param {string} userID The ID of the user to send the friend request to.
290
+ * @returns {Promise<Object>} A promise that resolves to the API response data on success.
291
+ * @throws {Error} If the userID is missing or the API request fails.
292
+ */
293
+ request: async function(userID) {
294
+ try {
295
+ if (!userID) throw new Error('userID is required.');
296
+ const variables = {
297
+ input: {
298
+ friend_requestee_ids: [userID],
299
+ friending_channel: 'FRIENDS_HOME_MAIN',
300
+ actor_id: ctx.userID,
301
+ client_mutation_id: Math.floor(Math.random() * 10 + 1).toString(),
302
+ },
303
+ scale: 3,
304
+ };
305
+ const form = {
306
+ av: ctx.userID,
307
+ __user: ctx.userID,
308
+ __a: '1',
309
+ fb_dtsg: ctx.fb_dtsg,
310
+ jazoest: ctx.jazoest,
311
+ lsd: ctx.lsd,
312
+ fb_api_caller_class: 'RelayModern',
313
+ fb_api_req_friendly_name: 'FriendingCometFriendRequestSendMutation',
314
+ variables: JSON.stringify(variables),
315
+ doc_id: '23982103144788355',
316
+ };
317
+ const res = await defaultFuncs.post(
318
+ 'https://www.facebook.com/api/graphql/',
319
+ ctx.jar,
320
+ form,
321
+ {},
322
+ );
323
+ if (res.data.errors) throw new Error(JSON.stringify(res.data.errors));
324
+ return res.data.data;
325
+ } catch (err) {
326
+ throw err;
327
+ }
328
+ },
329
+ },
330
+ };
331
+
332
+ /**
333
+ * Gets current user's friends list by fetching the friends/list page
334
+ * and extracting all_friends data from the embedded JSON.
335
+ * @async
336
+ * @param {Function} [callback] - Optional callback function.
337
+ * @returns {Promise<Array<Object>>} A promise that resolves to an array of friend objects.
338
+ */
339
+ async function getCurrentFriends(callback?) {
340
+ let resolveFunc: Function = function() {};
341
+ let rejectFunc: Function = function() {};
342
+
343
+ const returnPromise = new Promise<any>(function(resolve, reject) {
344
+ resolveFunc = resolve;
345
+ rejectFunc = reject;
346
+ });
347
+
348
+ callback =
349
+ callback ||
350
+ function(err, data) {
351
+ if (err) return rejectFunc(err);
352
+ resolveFunc(data);
353
+ };
354
+
355
+ try {
356
+ const url = 'https://www.facebook.com/friends/list';
357
+ const resData = await utils.get(url, ctx.jar, {}, ctx.globalOptions, ctx);
358
+ const html = resData.body.toString();
359
+
360
+ const friends = extractFriendsFromHTML(html);
361
+ callback(null, friends);
362
+ } catch (err) {
363
+ utils.error('getCurrentFriends', err);
364
+ callback(err);
365
+ }
366
+
367
+ return returnPromise;
368
+ }
369
+
370
+ /**
371
+ * Extracts all_friends data from the friends/list HTML page.
372
+ * Parses embedded <script type="application/json" data-sjs> tags
373
+ * to find the RelayPrefetchedStreamCache containing all_friends.
374
+ * @private
375
+ */
376
+ function extractFriendsFromHTML(htmlContent) {
377
+ const friends: any[] = [];
378
+
379
+ try {
380
+ const scriptMatches = htmlContent.match(
381
+ /<script type="application\/json"\s+data-content-len="\d+"\s+data-sjs>(.*?)<\/script>/gs,
382
+ );
383
+
384
+ if (!scriptMatches) return friends;
385
+
386
+ for (const scriptMatch of scriptMatches) {
387
+ try {
388
+ const jsonMatch = scriptMatch.match(/<script[^>]*>(.*?)<\/script>/s);
389
+ if (!jsonMatch) continue;
390
+
391
+ const jsonData = JSON.parse(jsonMatch[1]);
392
+
393
+ if (!jsonData.require || !Array.isArray(jsonData.require)) continue;
394
+
395
+ for (const req of jsonData.require) {
396
+ if (!Array.isArray(req)) continue;
397
+
398
+ if (req[0] === 'ScheduledServerJS' && req[1] === 'handle') {
399
+ const payload = req[3];
400
+ if (!payload || !Array.isArray(payload)) continue;
401
+
402
+ for (const item of payload) {
403
+ if (!item.__bbox || !item.__bbox.require) continue;
404
+
405
+ for (const bboxReq of item.__bbox.require) {
406
+ if (!Array.isArray(bboxReq)) continue;
407
+
408
+ if (
409
+ bboxReq[0] === 'RelayPrefetchedStreamCache' &&
410
+ bboxReq[1] === 'next'
411
+ ) {
412
+ const streamData = bboxReq[3];
413
+ if (
414
+ !streamData ||
415
+ !Array.isArray(streamData) ||
416
+ streamData.length < 2
417
+ )
418
+ continue;
419
+
420
+ const cacheKey = streamData[0];
421
+ if (
422
+ typeof cacheKey !== 'string' ||
423
+ !cacheKey.includes('FriendingCometAllFriendsRootQuery')
424
+ )
425
+ continue;
426
+
427
+ const bboxData = streamData[1];
428
+ if (
429
+ !bboxData ||
430
+ !bboxData.__bbox ||
431
+ !bboxData.__bbox.result
432
+ )
433
+ continue;
434
+
435
+ const viewer = bboxData.__bbox.result?.data?.viewer;
436
+ if (!viewer || !viewer.all_friends) continue;
437
+
438
+ const allFriends = viewer.all_friends;
439
+ const friendCount =
440
+ viewer.all_friends_data?.friend_count ||
441
+ viewer.all_friends_data?.count ||
442
+ 0;
443
+
444
+ if (allFriends.edges && Array.isArray(allFriends.edges)) {
445
+ for (const edge of allFriends.edges) {
446
+ if (!edge.node) continue;
447
+
448
+ const node = edge.node;
449
+ friends.push({
450
+ userID: node.id,
451
+ name: node.name,
452
+ shortName: node.short_name,
453
+ gender: node.gender,
454
+ profilePicture: node.profile_picture?.uri,
455
+ profileUrl: node.url,
456
+ friendshipStatus: node.friendship_status,
457
+ subscribeStatus: node.subscribe_status,
458
+ socialContext: node.social_context?.text,
459
+ mutualFriends:
460
+ node.social_context_top_mutual_friends || [],
461
+ });
462
+ }
463
+ }
464
+
465
+ // Attach pagination and count info
466
+ (friends as any).totalCount = friendCount;
467
+ (friends as any).pageInfo = allFriends.page_info || null;
468
+ }
469
+ }
470
+ }
471
+ }
197
472
  }
198
- },
199
- /**
200
- * Sends a friend request to a user.
201
- * @async
202
- * @param {string} userID The ID of the user to send the friend request to.
203
- * @returns {Promise<Object>} A promise that resolves to the API response data on success.
204
- * @throws {Error} If the userID is missing or the API request fails.
205
- */
206
- request: async function(userID) {
207
- try {
208
- if (!userID) throw new Error("userID is required.");
209
- const variables = {
210
- input: {
211
- friend_requestee_ids: [userID],
212
- friending_channel: "FRIENDS_HOME_MAIN",
213
- actor_id: ctx.userID,
214
- client_mutation_id: Math.floor(Math.random() * 10 + 1).toString()
215
- },
216
- scale: 3
217
- };
218
- const form = {
219
- av: ctx.userID,
220
- __user: ctx.userID,
221
- __a: "1",
222
- fb_dtsg: ctx.fb_dtsg,
223
- jazoest: ctx.jazoest,
224
- lsd: ctx.lsd,
225
- fb_api_caller_class: "RelayModern",
226
- fb_api_req_friendly_name: "FriendingCometFriendRequestSendMutation",
227
- variables: JSON.stringify(variables),
228
- doc_id: "23982103144788355"
229
- };
230
- const res = await defaultFuncs.post("https://www.facebook.com/api/graphql/", ctx.jar, form, {});
231
- if (res.data.errors) throw new Error(JSON.stringify(res.data.errors));
232
- return res.data.data;
233
- } catch(err) {
234
- throw err;
473
+ } catch (e) {
474
+ continue;
475
+ }
476
+ }
477
+ } catch (error) {
478
+ utils.error('extractFriendsFromHTML', error);
479
+ }
480
+
481
+ return friends;
482
+ }
483
+
484
+ /**
485
+ * Extracts friending_possibilities data from the friends/requests HTML page.
486
+ * Parses embedded <script type="application/json" data-sjs> tags
487
+ * to find the RelayPrefetchedStreamCache containing friend requests.
488
+ * @private
489
+ */
490
+ function extractFriendRequestsFromHTML(htmlContent) {
491
+ const requests: any[] = [];
492
+
493
+ try {
494
+ const scriptMatches = htmlContent.match(
495
+ /<script type="application\/json"\s+data-content-len="\d+"\s+data-sjs>(.*?)<\/script>/gs,
496
+ );
497
+
498
+ if (!scriptMatches) return requests;
499
+
500
+ for (const scriptMatch of scriptMatches) {
501
+ try {
502
+ const jsonMatch = scriptMatch.match(/<script[^>]*>(.*?)<\/script>/s);
503
+ if (!jsonMatch) continue;
504
+
505
+ const jsonData = JSON.parse(jsonMatch[1]);
506
+
507
+ if (!jsonData.require || !Array.isArray(jsonData.require)) continue;
508
+
509
+ for (const req of jsonData.require) {
510
+ if (!Array.isArray(req)) continue;
511
+
512
+ if (req[0] === 'ScheduledServerJS' && req[1] === 'handle') {
513
+ const payload = req[3];
514
+ if (!payload || !Array.isArray(payload)) continue;
515
+
516
+ for (const item of payload) {
517
+ if (!item.__bbox || !item.__bbox.require) continue;
518
+
519
+ for (const bboxReq of item.__bbox.require) {
520
+ if (!Array.isArray(bboxReq)) continue;
521
+
522
+ if (
523
+ bboxReq[0] === 'RelayPrefetchedStreamCache' &&
524
+ bboxReq[1] === 'next'
525
+ ) {
526
+ const streamData = bboxReq[3];
527
+ if (
528
+ !streamData ||
529
+ !Array.isArray(streamData) ||
530
+ streamData.length < 2
531
+ )
532
+ continue;
533
+
534
+ const cacheKey = streamData[0];
535
+ if (
536
+ typeof cacheKey !== 'string' ||
537
+ !cacheKey.includes(
538
+ 'FriendingCometFriendRequestsRootQuery',
539
+ )
540
+ )
541
+ continue;
542
+
543
+ const bboxData = streamData[1];
544
+ if (
545
+ !bboxData ||
546
+ !bboxData.__bbox ||
547
+ !bboxData.__bbox.result
548
+ )
549
+ continue;
550
+
551
+ const viewer = bboxData.__bbox.result?.data?.viewer;
552
+ if (!viewer || !viewer.friending_possibilities) continue;
553
+
554
+ const friendingPossibilities =
555
+ viewer.friending_possibilities;
556
+ const requestCount = friendingPossibilities.count || 0;
557
+
558
+ if (
559
+ friendingPossibilities.edges &&
560
+ Array.isArray(friendingPossibilities.edges)
561
+ ) {
562
+ for (const edge of friendingPossibilities.edges) {
563
+ if (!edge.node) continue;
564
+
565
+ const node = edge.node;
566
+ requests.push({
567
+ userID: node.id,
568
+ name: node.name,
569
+ profilePicture: node.profile_picture?.uri,
570
+ profileUrl: node.url,
571
+ friendshipStatus: node.friendship_status,
572
+ socialContext: node.social_context?.text,
573
+ mutualFriends:
574
+ node.social_context_top_mutual_friends || [],
575
+ time: edge.time,
576
+ isSeen: edge.is_seen,
577
+ });
578
+ }
579
+ }
580
+
581
+ // Attach count and pagination info
582
+ (requests as any).totalCount = requestCount;
583
+ (requests as any).pageInfo =
584
+ friendingPossibilities.page_info || null;
585
+ }
586
+ }
587
+ }
235
588
  }
589
+ }
590
+ } catch (e) {
591
+ continue;
236
592
  }
593
+ }
594
+ } catch (error) {
595
+ utils.error('extractFriendRequestsFromHTML', error);
237
596
  }
238
- };
239
-
597
+ return requests;
598
+ }
599
+
240
600
  return friendModule;
241
- };
601
+ }