bb-fca 2.0.4 → 2.0.7
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/dist/deltas/apis/create.js +22 -0
- package/dist/deltas/apis/create.js.map +1 -1
- package/dist/deltas/apis/posting/group.js +607 -0
- package/dist/deltas/apis/posting/group.js.map +1 -0
- package/dist/deltas/apis/posting/post.js +346 -1
- package/dist/deltas/apis/posting/post.js.map +1 -1
- package/dist/deltas/apis/posting/story.js +147 -0
- package/dist/deltas/apis/posting/story.js.map +1 -1
- package/dist/deltas/apis/threads/searchGroup.js +159 -0
- package/dist/deltas/apis/threads/searchGroup.js.map +1 -0
- package/dist/deltas/apis/users/searchGroups.js +221 -0
- package/dist/deltas/apis/users/searchGroups.js.map +1 -0
- package/dist/index.d.ts +66 -0
- package/dist/types/deltas/apis/create.d.ts +22 -0
- package/dist/types/deltas/apis/posting/group.d.ts +90 -0
- package/dist/types/deltas/apis/posting/post.d.ts +7 -0
- package/dist/types/deltas/apis/posting/story.d.ts +21 -0
- package/dist/types/deltas/apis/threads/searchGroup.d.ts +15 -0
- package/dist/types/deltas/apis/users/searchGroups.d.ts +17 -0
- package/dist/types/utils/axios.d.ts +1 -0
- package/dist/types/utils/index.d.ts +1 -0
- package/dist/utils/axios.js +12 -0
- package/dist/utils/axios.js.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
- package/request.txt +60 -0
- package/src/deltas/apis/create.ts +25 -0
- package/src/deltas/apis/posting/group.ts +754 -0
- package/src/deltas/apis/posting/post.ts +439 -1
- package/src/deltas/apis/posting/story.ts +147 -0
- package/src/types/index.d.ts +66 -0
- package/src/utils/axios.ts +19 -0
- package/src/utils/index.ts +1 -0
- package/LICENSE-MIT +0 -21
- package/examples/post.example.js +0 -149
- package/friend.html +0 -534
- package/proflie.html +0 -527
|
@@ -25,6 +25,28 @@ function default_1(defaultFuncs, api, ctx) {
|
|
|
25
25
|
* @returns {Promise<object>} The server's response.
|
|
26
26
|
*/
|
|
27
27
|
deletePost: postModule.delete,
|
|
28
|
+
/**
|
|
29
|
+
* Gets comments from a Facebook post.
|
|
30
|
+
* @param {string|object} postID - The post's story_fbid (string) or options object with story_fbid and id.
|
|
31
|
+
* @param {Function} [callback] - Optional callback function.
|
|
32
|
+
* @returns {Promise<object[]>} Array of comment objects.
|
|
33
|
+
*/
|
|
34
|
+
getComments: postModule.getComments,
|
|
35
|
+
/**
|
|
36
|
+
* Uploads a photo to Facebook for later use in posts.
|
|
37
|
+
* @param {string} photoPath - Local file path to the photo.
|
|
38
|
+
* @param {Function} [callback] - Optional callback function.
|
|
39
|
+
* @returns {Promise<object>} Upload result with photoID.
|
|
40
|
+
*/
|
|
41
|
+
uploadPhoto: postModule.uploadPhoto,
|
|
42
|
+
/**
|
|
43
|
+
* Uploads a video to Facebook for later use in posts.
|
|
44
|
+
* Uses the 7-step resumable upload flow.
|
|
45
|
+
* @param {string} videoPath - Local file path to the video.
|
|
46
|
+
* @param {Function} [callback] - Optional callback function.
|
|
47
|
+
* @returns {Promise<object>} Upload result with videoID.
|
|
48
|
+
*/
|
|
49
|
+
uploadVideo: postModule.uploadVideo,
|
|
28
50
|
};
|
|
29
51
|
}
|
|
30
52
|
//# sourceMappingURL=create.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create.js","sourceRoot":"","sources":["../../../src/deltas/apis/create.ts"],"names":[],"mappings":";;AAKA,
|
|
1
|
+
{"version":3,"file":"create.js","sourceRoot":"","sources":["../../../src/deltas/apis/create.ts"],"names":[],"mappings":";;AAKA,4BA+CC;AApDD;;;;GAIG;AACH,mBAAwB,YAAiB,EAAE,GAAQ,EAAE,GAAQ;IAC3D,MAAM,UAAU,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IAE7E,OAAO;QACL;;;;;;;WAOG;QACH,OAAO,EAAE,UAAU,CAAC,MAAM;QAE1B;;;;;WAKG;QACH,UAAU,EAAE,UAAU,CAAC,MAAM;QAE7B;;;;;WAKG;QACH,WAAW,EAAE,UAAU,CAAC,WAAW;QAEnC;;;;;WAKG;QACH,WAAW,EAAE,UAAU,CAAC,WAAW;QAEnC;;;;;;WAMG;QACH,WAAW,EAAE,UAAU,CAAC,WAAW;KACpC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = default_1;
|
|
4
|
+
const utils = require("../../../utils");
|
|
5
|
+
/**
|
|
6
|
+
* @namespace api.group
|
|
7
|
+
* @description A module for joining, leaving, and listing Facebook groups.
|
|
8
|
+
* @license Ex-it
|
|
9
|
+
*/
|
|
10
|
+
function default_1(defaultFuncs, api, ctx) {
|
|
11
|
+
/**
|
|
12
|
+
* Deep-searches an object/array tree for the first value matching a predicate.
|
|
13
|
+
*/
|
|
14
|
+
function deepFind(obj, predicate, visited = new WeakSet()) {
|
|
15
|
+
if (obj === null || obj === undefined)
|
|
16
|
+
return null;
|
|
17
|
+
if (typeof obj === 'object') {
|
|
18
|
+
if (visited.has(obj))
|
|
19
|
+
return null;
|
|
20
|
+
visited.add(obj);
|
|
21
|
+
}
|
|
22
|
+
if (typeof obj === 'object') {
|
|
23
|
+
for (const key in obj) {
|
|
24
|
+
if (!Object.prototype.hasOwnProperty.call(obj, key))
|
|
25
|
+
continue;
|
|
26
|
+
const val = obj[key];
|
|
27
|
+
if (predicate(val, key))
|
|
28
|
+
return val;
|
|
29
|
+
const found = deepFind(val, predicate, visited);
|
|
30
|
+
if (found !== null)
|
|
31
|
+
return found;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Deep-searches for all values matching a predicate and collects them.
|
|
38
|
+
*/
|
|
39
|
+
function deepFindAll(obj, predicate, results = [], visited = new WeakSet()) {
|
|
40
|
+
if (obj === null || obj === undefined)
|
|
41
|
+
return results;
|
|
42
|
+
if (typeof obj === 'object') {
|
|
43
|
+
if (visited.has(obj))
|
|
44
|
+
return results;
|
|
45
|
+
visited.add(obj);
|
|
46
|
+
}
|
|
47
|
+
if (typeof obj === 'object') {
|
|
48
|
+
for (const key in obj) {
|
|
49
|
+
if (!Object.prototype.hasOwnProperty.call(obj, key))
|
|
50
|
+
continue;
|
|
51
|
+
const val = obj[key];
|
|
52
|
+
if (predicate(val, key))
|
|
53
|
+
results.push(val);
|
|
54
|
+
deepFindAll(val, predicate, results, visited);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return results;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Maps a raw edge node from the joined groups list to a GroupEntry.
|
|
61
|
+
*/
|
|
62
|
+
function mapEdgeToGroup(edge) {
|
|
63
|
+
const node = edge?.node || edge;
|
|
64
|
+
return {
|
|
65
|
+
id: node?.id || '',
|
|
66
|
+
name: node?.name || '',
|
|
67
|
+
imageUri: node?.profile_picture?.uri ||
|
|
68
|
+
node?.group_header_profile_picture?.uri ||
|
|
69
|
+
node?.image?.uri ||
|
|
70
|
+
'',
|
|
71
|
+
url: node?.url || '',
|
|
72
|
+
memberCount: node?.group_member_count_info_text?.text ||
|
|
73
|
+
node?.member_count_text?.text ||
|
|
74
|
+
'',
|
|
75
|
+
privacy: node?.group_visibility || node?.privacy || '',
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Extracts the initial joined groups data from the embedded JSON data on the groups/joins page.
|
|
80
|
+
*/
|
|
81
|
+
function extractInitialGroupsData(allJsonData) {
|
|
82
|
+
// Look for tab_groups_list with edges and page_info
|
|
83
|
+
const tabGroupsList = deepFind(allJsonData, (val, key) => key === 'tab_groups_list' &&
|
|
84
|
+
val &&
|
|
85
|
+
typeof val === 'object' &&
|
|
86
|
+
Array.isArray(val.edges) &&
|
|
87
|
+
val.page_info);
|
|
88
|
+
if (tabGroupsList) {
|
|
89
|
+
const edges = tabGroupsList.edges || [];
|
|
90
|
+
const pageInfo = tabGroupsList.page_info || {};
|
|
91
|
+
return {
|
|
92
|
+
groups: edges.map(mapEdgeToGroup),
|
|
93
|
+
endCursor: pageInfo.end_cursor || null,
|
|
94
|
+
hasNextPage: pageInfo.has_next_page || false,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
// Fallback: look for groups_tab or joined_groups patterns
|
|
98
|
+
const groupsList = deepFind(allJsonData, (val, _key) => val &&
|
|
99
|
+
typeof val === 'object' &&
|
|
100
|
+
Array.isArray(val.edges) &&
|
|
101
|
+
val.page_info &&
|
|
102
|
+
val.edges.length > 0 &&
|
|
103
|
+
val.edges[0]?.node?.group_visibility !== undefined);
|
|
104
|
+
if (groupsList) {
|
|
105
|
+
const edges = groupsList.edges || [];
|
|
106
|
+
const pageInfo = groupsList.page_info || {};
|
|
107
|
+
return {
|
|
108
|
+
groups: edges.map(mapEdgeToGroup),
|
|
109
|
+
endCursor: pageInfo.end_cursor || null,
|
|
110
|
+
hasNextPage: pageInfo.has_next_page || false,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Extracts the viewer ID needed for GraphQL pagination from embedded JSON.
|
|
117
|
+
*/
|
|
118
|
+
function extractViewerId(allJsonData) {
|
|
119
|
+
const viewer = deepFind(allJsonData, (val, key) => key === 'viewer' &&
|
|
120
|
+
val &&
|
|
121
|
+
typeof val === 'object' &&
|
|
122
|
+
typeof val.id === 'string');
|
|
123
|
+
return viewer?.id || null;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Builds the GraphQL POST form for groups pagination.
|
|
127
|
+
*/
|
|
128
|
+
function buildGroupsGraphQLForm(variables) {
|
|
129
|
+
return {
|
|
130
|
+
av: ctx.userID,
|
|
131
|
+
__user: ctx.userID,
|
|
132
|
+
__a: '1',
|
|
133
|
+
fb_dtsg: ctx.fb_dtsg,
|
|
134
|
+
jazoest: ctx.jazoest,
|
|
135
|
+
lsd: ctx.lsd,
|
|
136
|
+
fb_api_caller_class: 'RelayModern',
|
|
137
|
+
fb_api_req_friendly_name: 'GroupsCometJoinedGroupsListPaginationQuery',
|
|
138
|
+
variables: JSON.stringify(variables),
|
|
139
|
+
server_timestamps: 'true',
|
|
140
|
+
doc_id: '8957838874246460',
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function parseResponseBody(body) {
|
|
144
|
+
if (typeof body === 'object' && body !== null && !Buffer.isBuffer(body)) {
|
|
145
|
+
return body;
|
|
146
|
+
}
|
|
147
|
+
const raw = typeof body === 'string' ? body : body.toString();
|
|
148
|
+
const normalized = raw.replace(/^for \(;;\);\s*/, '');
|
|
149
|
+
try {
|
|
150
|
+
return JSON.parse(normalized);
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
const lines = normalized.split('\n').filter(Boolean);
|
|
154
|
+
return JSON.parse(lines[0]);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function parseLocalizedCount(text) {
|
|
158
|
+
const match = text.match(/([\d.,]+)\s*([kKmMbB])?/);
|
|
159
|
+
if (!match) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
const base = Number((match[1] || '').replace(/,/g, '.'));
|
|
163
|
+
if (Number.isNaN(base)) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
const unit = (match[2] || '').toLowerCase();
|
|
167
|
+
if (unit === 'k') {
|
|
168
|
+
return Math.round(base * 1000);
|
|
169
|
+
}
|
|
170
|
+
if (unit === 'm') {
|
|
171
|
+
return Math.round(base * 1000000);
|
|
172
|
+
}
|
|
173
|
+
if (unit === 'b') {
|
|
174
|
+
return Math.round(base * 1000000000);
|
|
175
|
+
}
|
|
176
|
+
return Math.round(base);
|
|
177
|
+
}
|
|
178
|
+
function mapSearchGroupEdge(edge) {
|
|
179
|
+
const viewModel = edge?.rendering_strategy?.view_model;
|
|
180
|
+
const profile = viewModel?.profile || {};
|
|
181
|
+
const primaryCTAProfile = viewModel?.ctas?.primary?.[0]?.profile || {};
|
|
182
|
+
const summaryText = viewModel?.primary_snippet_text_with_entities?.text || null;
|
|
183
|
+
const summaryParts = summaryText
|
|
184
|
+
? summaryText
|
|
185
|
+
.split('·')
|
|
186
|
+
.map((part) => part.trim())
|
|
187
|
+
.filter(Boolean)
|
|
188
|
+
: [];
|
|
189
|
+
const memberPart = summaryParts.find((part) => /member|thành viên|membro|mitglieder|membres/i.test(part)) || null;
|
|
190
|
+
return {
|
|
191
|
+
groupID: profile.id || null,
|
|
192
|
+
name: profile.name || null,
|
|
193
|
+
url: profile.url || primaryCTAProfile.url || null,
|
|
194
|
+
profileUrl: profile.profile_url || profile.url || primaryCTAProfile.url || null,
|
|
195
|
+
imageSrc: profile.profile_picture?.uri || null,
|
|
196
|
+
summary: summaryText,
|
|
197
|
+
privacy: summaryParts[0] || null,
|
|
198
|
+
memberText: memberPart,
|
|
199
|
+
memberCount: memberPart ? parseLocalizedCount(memberPart) : null,
|
|
200
|
+
joinState: primaryCTAProfile.viewer_join_state || null,
|
|
201
|
+
hasMembershipQuestions: typeof primaryCTAProfile.has_membership_questions === 'boolean'
|
|
202
|
+
? primaryCTAProfile.has_membership_questions
|
|
203
|
+
: null,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function createBsid() {
|
|
207
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
208
|
+
const r = Math.random() * 16;
|
|
209
|
+
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
|
210
|
+
return Math.floor(v).toString(16);
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
const groupModule = {
|
|
214
|
+
/**
|
|
215
|
+
* Joins a Facebook group.
|
|
216
|
+
* @async
|
|
217
|
+
* @param {string} groupID The ID of the group to join.
|
|
218
|
+
* @returns {Promise<Object>} A promise that resolves to the API response data on success.
|
|
219
|
+
* @throws {Error} If the groupID is missing or the API request fails.
|
|
220
|
+
*/
|
|
221
|
+
join: async function (groupID, inviteShortLinkKey) {
|
|
222
|
+
if (!groupID)
|
|
223
|
+
throw new Error('groupID is required.');
|
|
224
|
+
const actorId = ctx.i_userID || ctx.userID;
|
|
225
|
+
const variables = {
|
|
226
|
+
feedType: 'DISCUSSION',
|
|
227
|
+
groupID: groupID,
|
|
228
|
+
input: {
|
|
229
|
+
action_source: 'GROUP_MALL',
|
|
230
|
+
attribution_id_v2: 'CometGroupDiscussionRoot.react,comet.group,via_cold_start,' +
|
|
231
|
+
Date.now() +
|
|
232
|
+
',874165,2361831622,,',
|
|
233
|
+
group_id: groupID,
|
|
234
|
+
group_share_tracking_params: {
|
|
235
|
+
app_id: '2220391788200892',
|
|
236
|
+
exp_id: 'null',
|
|
237
|
+
is_from_share: false,
|
|
238
|
+
},
|
|
239
|
+
actor_id: actorId,
|
|
240
|
+
client_mutation_id: '1',
|
|
241
|
+
},
|
|
242
|
+
inviteShortLinkKey: inviteShortLinkKey ?? null,
|
|
243
|
+
isChainingRecommendationUnit: false,
|
|
244
|
+
scale: 1,
|
|
245
|
+
source: 'GROUP_MALL',
|
|
246
|
+
renderLocation: 'group_mall',
|
|
247
|
+
__relay_internal__pv__groups_comet_use_glvrelayprovider: false,
|
|
248
|
+
__relay_internal__pv__GroupsCometGYSJUnifiedUnitCardImageHeightrelayprovider: 150,
|
|
249
|
+
__relay_internal__pv__GroupsCometGroupChatLazyLoadLastMessageSnippetrelayprovider: false,
|
|
250
|
+
};
|
|
251
|
+
const form = {
|
|
252
|
+
av: actorId,
|
|
253
|
+
__aaid: '0',
|
|
254
|
+
__user: actorId,
|
|
255
|
+
__a: '1',
|
|
256
|
+
__req: '18',
|
|
257
|
+
__hs: '20530.HCSV2:comet_pkg.2.1...0',
|
|
258
|
+
dpr: '1',
|
|
259
|
+
__ccg: 'GOOD',
|
|
260
|
+
__comet_req: '15',
|
|
261
|
+
fb_dtsg: ctx.fb_dtsg,
|
|
262
|
+
jazoest: ctx.jazoest,
|
|
263
|
+
lsd: ctx.lsd,
|
|
264
|
+
__spin_r: '1035418183',
|
|
265
|
+
__spin_b: 'trunk',
|
|
266
|
+
__spin_t: Math.floor(Date.now() / 1000).toString(),
|
|
267
|
+
__crn: 'comet.fbweb.CometGroupDiscussionRoute',
|
|
268
|
+
fb_api_caller_class: 'RelayModern',
|
|
269
|
+
fb_api_req_friendly_name: 'GroupCometJoinForumMutation',
|
|
270
|
+
variables: JSON.stringify(variables),
|
|
271
|
+
server_timestamps: 'true',
|
|
272
|
+
doc_id: '26180016991684264',
|
|
273
|
+
};
|
|
274
|
+
const customHeader = {
|
|
275
|
+
'x-fb-friendly-name': 'GroupCometJoinForumMutation',
|
|
276
|
+
'x-fb-lsd': ctx.lsd || '',
|
|
277
|
+
'x-asbd-id': '359341',
|
|
278
|
+
origin: 'https://www.facebook.com',
|
|
279
|
+
referer: `https://www.facebook.com/groups/${groupID}`,
|
|
280
|
+
};
|
|
281
|
+
const res = await utils.post('https://www.facebook.com/api/graphql/', ctx.jar, form, ctx.globalOptions, ctx, customHeader);
|
|
282
|
+
const data = parseResponseBody(res.body);
|
|
283
|
+
if (data?.errors)
|
|
284
|
+
throw new Error(JSON.stringify(data.errors));
|
|
285
|
+
return data?.data ?? data;
|
|
286
|
+
},
|
|
287
|
+
/**
|
|
288
|
+
* Leaves a Facebook group.
|
|
289
|
+
* @async
|
|
290
|
+
* @param {string} groupID The ID of the group to leave.
|
|
291
|
+
* @returns {Promise<Object>} A promise that resolves to the API response data on success.
|
|
292
|
+
* @throws {Error} If the groupID is missing or the API request fails.
|
|
293
|
+
*/
|
|
294
|
+
leave: async function (groupID) {
|
|
295
|
+
if (!groupID)
|
|
296
|
+
throw new Error('groupID is required.');
|
|
297
|
+
const actorId = ctx.i_userID || ctx.userID;
|
|
298
|
+
const variables = {
|
|
299
|
+
input: {
|
|
300
|
+
attribution_id_v2: 'CometGroupDiscussionRoot.react,comet.group,via_cold_start,' +
|
|
301
|
+
Date.now() +
|
|
302
|
+
',243486,2361831622,,',
|
|
303
|
+
group_id: groupID,
|
|
304
|
+
actor_id: actorId,
|
|
305
|
+
client_mutation_id: '1',
|
|
306
|
+
},
|
|
307
|
+
inviteShortLinkKey: null,
|
|
308
|
+
isChainingRecommendationUnit: false,
|
|
309
|
+
ordering: ['viewer_added'],
|
|
310
|
+
scale: 1,
|
|
311
|
+
groupID: groupID,
|
|
312
|
+
__relay_internal__pv__GroupsCometGYSJUnifiedUnitCardImageHeightrelayprovider: 150,
|
|
313
|
+
__relay_internal__pv__GroupsCometGroupChatLazyLoadLastMessageSnippetrelayprovider: false,
|
|
314
|
+
};
|
|
315
|
+
const form = {
|
|
316
|
+
av: actorId,
|
|
317
|
+
__aaid: '0',
|
|
318
|
+
__user: actorId,
|
|
319
|
+
__a: '1',
|
|
320
|
+
__req: '1f',
|
|
321
|
+
__hs: '20530.HCSV2:comet_pkg.2.1...0',
|
|
322
|
+
dpr: '1',
|
|
323
|
+
__ccg: 'EXCELLENT',
|
|
324
|
+
__comet_req: '15',
|
|
325
|
+
fb_dtsg: ctx.fb_dtsg,
|
|
326
|
+
jazoest: ctx.jazoest,
|
|
327
|
+
lsd: ctx.lsd,
|
|
328
|
+
__spin_r: '1035418183',
|
|
329
|
+
__spin_b: 'trunk',
|
|
330
|
+
__spin_t: Math.floor(Date.now() / 1000).toString(),
|
|
331
|
+
__crn: 'comet.fbweb.CometGroupDiscussionRoute',
|
|
332
|
+
fb_api_caller_class: 'RelayModern',
|
|
333
|
+
fb_api_req_friendly_name: 'GroupCometLeaveForumMutation',
|
|
334
|
+
variables: JSON.stringify(variables),
|
|
335
|
+
server_timestamps: 'true',
|
|
336
|
+
doc_id: '26077711195170900',
|
|
337
|
+
};
|
|
338
|
+
const customHeader = {
|
|
339
|
+
'x-fb-friendly-name': 'GroupCometLeaveForumMutation',
|
|
340
|
+
'x-fb-lsd': ctx.lsd || '',
|
|
341
|
+
'x-asbd-id': '359341',
|
|
342
|
+
origin: 'https://www.facebook.com',
|
|
343
|
+
referer: `https://www.facebook.com/groups/${groupID}`,
|
|
344
|
+
};
|
|
345
|
+
const res = await utils.post('https://www.facebook.com/api/graphql/', ctx.jar, form, ctx.globalOptions, ctx, customHeader);
|
|
346
|
+
const data = parseResponseBody(res.body);
|
|
347
|
+
if (data?.errors)
|
|
348
|
+
throw new Error(JSON.stringify(data.errors));
|
|
349
|
+
return data?.data ?? data;
|
|
350
|
+
},
|
|
351
|
+
/**
|
|
352
|
+
* Invites friends to a Facebook group.
|
|
353
|
+
* @async
|
|
354
|
+
* @param {string} groupID The ID of the group to invite friends to.
|
|
355
|
+
* @param {string[]} userIDs Array of user IDs to invite.
|
|
356
|
+
* @returns {Promise<Object>} A promise that resolves to the API response data on success.
|
|
357
|
+
* @throws {Error} If the groupID or userIDs are missing or the API request fails.
|
|
358
|
+
*/
|
|
359
|
+
inviteFriends: async function (groupID, userIDs) {
|
|
360
|
+
if (!groupID)
|
|
361
|
+
throw new Error('groupID is required.');
|
|
362
|
+
if (!userIDs?.length)
|
|
363
|
+
throw new Error('userIDs is required and must not be empty.');
|
|
364
|
+
const actorId = ctx.i_userID || ctx.userID;
|
|
365
|
+
const variables = {
|
|
366
|
+
input: {
|
|
367
|
+
attribution_id_v2: 'CometGroupDiscussionRoot.react,comet.group,via_cold_start,' +
|
|
368
|
+
Date.now() +
|
|
369
|
+
',829526,2361831622,,',
|
|
370
|
+
email_addresses: [],
|
|
371
|
+
group_id: groupID,
|
|
372
|
+
source: 'comet_invite_friends',
|
|
373
|
+
user_ids: userIDs,
|
|
374
|
+
actor_id: actorId,
|
|
375
|
+
client_mutation_id: '2',
|
|
376
|
+
},
|
|
377
|
+
groupID: groupID,
|
|
378
|
+
};
|
|
379
|
+
const form = {
|
|
380
|
+
av: actorId,
|
|
381
|
+
__aaid: '0',
|
|
382
|
+
__user: actorId,
|
|
383
|
+
__a: '1',
|
|
384
|
+
__req: '1a',
|
|
385
|
+
__hs: '20530.HCSV2:comet_pkg.2.1...0',
|
|
386
|
+
dpr: '1',
|
|
387
|
+
__ccg: 'EXCELLENT',
|
|
388
|
+
__comet_req: '15',
|
|
389
|
+
fb_dtsg: ctx.fb_dtsg,
|
|
390
|
+
jazoest: ctx.jazoest,
|
|
391
|
+
lsd: ctx.lsd,
|
|
392
|
+
__spin_r: '1035418183',
|
|
393
|
+
__spin_b: 'trunk',
|
|
394
|
+
__spin_t: Math.floor(Date.now() / 1000).toString(),
|
|
395
|
+
__crn: 'comet.fbweb.CometGroupDiscussionRoute',
|
|
396
|
+
fb_api_caller_class: 'RelayModern',
|
|
397
|
+
fb_api_req_friendly_name: 'useGroupAddMembersMutation',
|
|
398
|
+
variables: JSON.stringify(variables),
|
|
399
|
+
server_timestamps: 'true',
|
|
400
|
+
doc_id: '25892331960437758',
|
|
401
|
+
};
|
|
402
|
+
const customHeader = {
|
|
403
|
+
'x-fb-friendly-name': 'useGroupAddMembersMutation',
|
|
404
|
+
'x-fb-lsd': ctx.lsd || '',
|
|
405
|
+
'x-asbd-id': '359341',
|
|
406
|
+
origin: 'https://www.facebook.com',
|
|
407
|
+
referer: `https://www.facebook.com/groups/${groupID}`,
|
|
408
|
+
};
|
|
409
|
+
const res = await utils.post('https://www.facebook.com/api/graphql/', ctx.jar, form, ctx.globalOptions, ctx, customHeader);
|
|
410
|
+
const data = parseResponseBody(res.body);
|
|
411
|
+
if (data?.errors)
|
|
412
|
+
throw new Error(JSON.stringify(data.errors));
|
|
413
|
+
return data?.data ?? data;
|
|
414
|
+
},
|
|
415
|
+
/**
|
|
416
|
+
* Fetches the logged-in user's joined Facebook groups with cursor-based pagination.
|
|
417
|
+
*
|
|
418
|
+
* @param {string|null} [cursor=null] Pagination cursor from a previous result's `endCursor`.
|
|
419
|
+
* Pass `null` (or omit) for the first page.
|
|
420
|
+
* @param {number} [count=10] Number of groups to fetch per page (used for pagination requests).
|
|
421
|
+
* @returns {Promise<JoinedGroupsResult>} Groups list and pagination info.
|
|
422
|
+
*
|
|
423
|
+
* @example
|
|
424
|
+
* // Page 1
|
|
425
|
+
* const page1 = await api.group.getJoinedGroups();
|
|
426
|
+
* console.log(page1.groups); // Array of GroupEntry
|
|
427
|
+
* console.log(page1.endCursor); // cursor string for next page
|
|
428
|
+
*
|
|
429
|
+
* // Page 2+
|
|
430
|
+
* const page2 = await api.group.getJoinedGroups(page1.endCursor);
|
|
431
|
+
*/
|
|
432
|
+
getJoinedGroups: async function (cursor = null, count = 10) {
|
|
433
|
+
// First page: fetch HTML and extract embedded data
|
|
434
|
+
if (cursor === null) {
|
|
435
|
+
const groupsPageUrl = 'https://www.facebook.com/groups/joins/';
|
|
436
|
+
const allJsonData = await utils.json(groupsPageUrl, ctx.jar, null, ctx.globalOptions, ctx);
|
|
437
|
+
if (!allJsonData || allJsonData.length === 0) {
|
|
438
|
+
throw new Error('Could not fetch joined groups page data.');
|
|
439
|
+
}
|
|
440
|
+
const initialData = extractInitialGroupsData(allJsonData);
|
|
441
|
+
if (initialData) {
|
|
442
|
+
return initialData;
|
|
443
|
+
}
|
|
444
|
+
// Fallthrough: HTML extraction failed — use GraphQL with null cursor
|
|
445
|
+
}
|
|
446
|
+
// GraphQL pagination request (page 2+ or first-page fallback)
|
|
447
|
+
const variables = {
|
|
448
|
+
count,
|
|
449
|
+
cursor,
|
|
450
|
+
scale: 1,
|
|
451
|
+
id: ctx.userID,
|
|
452
|
+
};
|
|
453
|
+
const form = buildGroupsGraphQLForm(variables);
|
|
454
|
+
const res = await utils.post('https://www.facebook.com/api/graphql/', ctx.jar, form, ctx.globalOptions, ctx);
|
|
455
|
+
let data;
|
|
456
|
+
if (typeof res.body === 'object' && res.body !== null) {
|
|
457
|
+
data = res.body;
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
const body = typeof res.body === 'string' ? res.body : String(res.body);
|
|
461
|
+
try {
|
|
462
|
+
data = JSON.parse(body);
|
|
463
|
+
}
|
|
464
|
+
catch {
|
|
465
|
+
const lines = body.split('\n').filter(Boolean);
|
|
466
|
+
data = JSON.parse(lines[0]);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
if (data.errors)
|
|
470
|
+
throw new Error(JSON.stringify(data.errors));
|
|
471
|
+
// Navigate the GraphQL response to find groups edges
|
|
472
|
+
const tabGroupsList = data?.data?.node?.tab_groups_list ||
|
|
473
|
+
data?.data?.viewer?.tab_groups_list;
|
|
474
|
+
const edges = tabGroupsList?.edges || [];
|
|
475
|
+
const pageInfo = tabGroupsList?.page_info || {};
|
|
476
|
+
return {
|
|
477
|
+
groups: edges.map(mapEdgeToGroup),
|
|
478
|
+
endCursor: pageInfo.end_cursor || null,
|
|
479
|
+
hasNextPage: pageInfo.has_next_page || false,
|
|
480
|
+
};
|
|
481
|
+
},
|
|
482
|
+
/**
|
|
483
|
+
* Search Facebook groups by keyword.
|
|
484
|
+
*
|
|
485
|
+
* @param {string} keyword Search keyword.
|
|
486
|
+
* @param {{ limit?: number; cursor?: string | null; locale?: string }} [options]
|
|
487
|
+
* @returns {Promise<{ groups: any[]; cursor: string | null; hasNextPage: boolean }>} Search result and pagination info.
|
|
488
|
+
*/
|
|
489
|
+
searchGroup: async function (keyword, options = {}) {
|
|
490
|
+
if (!keyword || typeof keyword !== 'string') {
|
|
491
|
+
throw new Error('searchGroup: keyword must be a non-empty string.');
|
|
492
|
+
}
|
|
493
|
+
const limit = typeof options.limit === 'number' && Number.isInteger(options.limit)
|
|
494
|
+
? options.limit
|
|
495
|
+
: 10;
|
|
496
|
+
if (limit <= 0) {
|
|
497
|
+
throw new Error('searchGroup: options.limit must be a positive integer.');
|
|
498
|
+
}
|
|
499
|
+
const locale = options.locale || 'vi_VN';
|
|
500
|
+
const bsid = createBsid();
|
|
501
|
+
const tsid = Math.random().toString();
|
|
502
|
+
const variables = {
|
|
503
|
+
allow_streaming: false,
|
|
504
|
+
args: {
|
|
505
|
+
callsite: 'comet:groups_search',
|
|
506
|
+
config: {
|
|
507
|
+
exact_match: false,
|
|
508
|
+
high_confidence_config: null,
|
|
509
|
+
intercept_config: null,
|
|
510
|
+
sts_disambiguation: null,
|
|
511
|
+
watch_config: null,
|
|
512
|
+
},
|
|
513
|
+
context: {
|
|
514
|
+
bsid,
|
|
515
|
+
tsid,
|
|
516
|
+
},
|
|
517
|
+
experience: {
|
|
518
|
+
client_defined_experiences: ['ADS_PARALLEL_FETCH'],
|
|
519
|
+
encoded_server_defined_params: null,
|
|
520
|
+
fbid: null,
|
|
521
|
+
type: 'GROUPS_TAB_GLOBAL',
|
|
522
|
+
},
|
|
523
|
+
filters: [],
|
|
524
|
+
text: keyword,
|
|
525
|
+
},
|
|
526
|
+
count: limit,
|
|
527
|
+
cursor: options.cursor ?? null,
|
|
528
|
+
feedLocation: 'SEARCH',
|
|
529
|
+
feedbackSource: 23,
|
|
530
|
+
fetch_filters: true,
|
|
531
|
+
focusCommentID: null,
|
|
532
|
+
locale: null,
|
|
533
|
+
privacySelectorRenderLocation: 'COMET_STREAM',
|
|
534
|
+
referringStoryRenderLocation: null,
|
|
535
|
+
renderLocation: 'search_results_page',
|
|
536
|
+
scale: 1,
|
|
537
|
+
stream_initial_count: 0,
|
|
538
|
+
useDefaultActor: false,
|
|
539
|
+
__relay_internal__pv__GHLShouldChangeAdIdFieldNamerelayprovider: true,
|
|
540
|
+
__relay_internal__pv__GHLShouldChangeSponsoredDataFieldNamerelayprovider: true,
|
|
541
|
+
__relay_internal__pv__CometFeedStory_enable_post_permalink_white_space_clickrelayprovider: false,
|
|
542
|
+
__relay_internal__pv__CometUFICommentActionLinksRewriteEnabledrelayprovider: false,
|
|
543
|
+
__relay_internal__pv__CometUFICommentAvatarStickerAnimatedImagerelayprovider: false,
|
|
544
|
+
__relay_internal__pv__IsWorkUserrelayprovider: false,
|
|
545
|
+
__relay_internal__pv__TestPilotShouldIncludeDemoAdUseCaserelayprovider: false,
|
|
546
|
+
__relay_internal__pv__FBReels_deprecate_short_form_video_context_gkrelayprovider: true,
|
|
547
|
+
__relay_internal__pv__FBReels_enable_view_dubbed_audio_type_gkrelayprovider: true,
|
|
548
|
+
__relay_internal__pv__CometImmersivePhotoCanUserDisable3DMotionrelayprovider: false,
|
|
549
|
+
__relay_internal__pv__WorkCometIsEmployeeGKProviderrelayprovider: false,
|
|
550
|
+
__relay_internal__pv__IsMergQAPollsrelayprovider: false,
|
|
551
|
+
__relay_internal__pv__FBReelsMediaFooter_comet_enable_reels_ads_gkrelayprovider: true,
|
|
552
|
+
__relay_internal__pv__CometUFIReactionsEnableShortNamerelayprovider: false,
|
|
553
|
+
__relay_internal__pv__CometUFICommentAutoTranslationTyperelayprovider: 'ORIGINAL',
|
|
554
|
+
__relay_internal__pv__CometUFIShareActionMigrationrelayprovider: true,
|
|
555
|
+
__relay_internal__pv__CometUFISingleLineUFIrelayprovider: false,
|
|
556
|
+
__relay_internal__pv__CometUFI_dedicated_comment_routable_dialog_gkrelayprovider: true,
|
|
557
|
+
__relay_internal__pv__FBReelsIFUTileContent_reelsIFUPlayOnHoverrelayprovider: true,
|
|
558
|
+
__relay_internal__pv__GroupsCometGYSJFeedItemHeightrelayprovider: 150,
|
|
559
|
+
__relay_internal__pv__ShouldEnableBakedInTextStoriesrelayprovider: false,
|
|
560
|
+
__relay_internal__pv__StoriesShouldIncludeFbNotesrelayprovider: false,
|
|
561
|
+
};
|
|
562
|
+
const form = {
|
|
563
|
+
av: ctx.i_userID || ctx.userID,
|
|
564
|
+
__user: ctx.i_userID || ctx.userID,
|
|
565
|
+
__a: '1',
|
|
566
|
+
fb_dtsg: ctx.fb_dtsg,
|
|
567
|
+
jazoest: ctx.jazoest,
|
|
568
|
+
lsd: ctx.lsd || '',
|
|
569
|
+
locale,
|
|
570
|
+
fb_api_caller_class: 'RelayModern',
|
|
571
|
+
fb_api_req_friendly_name: 'SearchCometResultsPaginatedResultsQuery',
|
|
572
|
+
server_timestamps: 'true',
|
|
573
|
+
variables: JSON.stringify(variables),
|
|
574
|
+
doc_id: '26131941349797315',
|
|
575
|
+
};
|
|
576
|
+
const customHeader = {
|
|
577
|
+
'x-fb-friendly-name': 'SearchCometResultsPaginatedResultsQuery',
|
|
578
|
+
'x-fb-lsd': ctx.lsd || '',
|
|
579
|
+
'x-asbd-id': '359341',
|
|
580
|
+
origin: 'https://www.facebook.com',
|
|
581
|
+
referer: `https://www.facebook.com/groups/search/groups_home?q=${encodeURIComponent(keyword)}&locale=${encodeURIComponent(locale)}`,
|
|
582
|
+
};
|
|
583
|
+
const res = await utils.post('https://www.facebook.com/api/graphql/', ctx.jar, form, ctx.globalOptions, ctx, customHeader);
|
|
584
|
+
const data = parseResponseBody(res.body);
|
|
585
|
+
if (data?.errors) {
|
|
586
|
+
throw new Error(JSON.stringify(data.errors));
|
|
587
|
+
}
|
|
588
|
+
const results = data?.data?.serpResponse?.results;
|
|
589
|
+
const edges = Array.isArray(results?.edges) ? results.edges : [];
|
|
590
|
+
const pageInfo = results?.page_info || {};
|
|
591
|
+
return {
|
|
592
|
+
groups: edges.map(mapSearchGroupEdge),
|
|
593
|
+
cursor: pageInfo.end_cursor || null,
|
|
594
|
+
hasNextPage: Boolean(pageInfo.has_next_page),
|
|
595
|
+
};
|
|
596
|
+
},
|
|
597
|
+
/**
|
|
598
|
+
* Alias for searchGroup.
|
|
599
|
+
* @see searchGroup
|
|
600
|
+
*/
|
|
601
|
+
searchGroups: async function (keyword, options = {}) {
|
|
602
|
+
return groupModule.searchGroup(keyword, options);
|
|
603
|
+
},
|
|
604
|
+
};
|
|
605
|
+
return groupModule;
|
|
606
|
+
}
|
|
607
|
+
//# sourceMappingURL=group.js.map
|