bb-fca 2.0.6 → 2.0.8

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.
@@ -289,14 +289,25 @@ export default function(defaultFuncs: any, api: any, ctx: any) {
289
289
  }
290
290
 
291
291
  /**
292
- * Gets comments from a Facebook post.
293
- * @param {string|object} postID - The post ID (string) or options object.
294
- * @param {string} postID.story_fbid - The story FBID from the permalink URL.
295
- * @param {string} postID.id - The account ID from the permalink URL.
292
+ * Gets comments from a Facebook post, with pagination support.
293
+ * @param {string|object} postID - The post URL (string) or options object.
294
+ * @param {string} [postID.url] - Direct Facebook post URL.
295
+ * @param {string} [postID.story_fbid] - The story FBID.
296
+ * @param {string} [postID.id] - The account ID.
297
+ * @param {string} [postID.after] - Cursor string to fetch the next page.
298
+ * @param {string} [postID.feedback_id] - Feedback ID required for pagination (returned in page_info).
296
299
  * @param {Function} [callback] - Optional callback function.
297
- * @returns {Promise<Array>} Array of comments with author info, text, timestamps, etc.
300
+ * @returns {Promise<{comments: Array, page_info: object}>} Object with comments array and pagination info.
298
301
  */
299
- async function getPostComments(postID, callback) {
302
+ async function getPostComments(postID, optionsOrCallback?, callback?) {
303
+ // Support: getComments(url), getComments(url, cb), getComments(url, opts), getComments(url, opts, cb)
304
+ let paginationOpts: any = null;
305
+ if (typeof optionsOrCallback === 'function') {
306
+ callback = optionsOrCallback;
307
+ } else if (typeof optionsOrCallback === 'object' && optionsOrCallback !== null) {
308
+ paginationOpts = optionsOrCallback;
309
+ }
310
+
300
311
  let resolveFunc: Function = function() {};
301
312
  let rejectFunc: Function = function() {};
302
313
 
@@ -313,37 +324,161 @@ export default function(defaultFuncs: any, api: any, ctx: any) {
313
324
  };
314
325
 
315
326
  try {
316
- let story_fbid, accountID;
327
+ // ── Pagination mode: fetch next page via GraphQL ──────────────────
328
+ // Merge pagination from either postID (object) or second-arg options
329
+ const afterCursor: string | undefined =
330
+ paginationOpts?.after
331
+ || (typeof postID === 'object' && postID !== null ? postID.after : undefined);
332
+ const feedbackId: string | undefined =
333
+ paginationOpts?.feedback_id
334
+ || (typeof postID === 'object' && postID !== null ? postID.feedback_id : undefined);
335
+
336
+ if (afterCursor && feedbackId) {
337
+ // Use GraphQL to fetch next page of comments using the cursor
338
+ // feedbackId is the GraphQL ID (base64 of "feedback:<numeric_id>")
339
+ const variables: Record<string, any> = {
340
+ commentsAfterCount: -1,
341
+ commentsAfterCursor: afterCursor,
342
+ commentsBeforeCount: null,
343
+ commentsBeforeCursor: null,
344
+ commentsIntentToken: null,
345
+ feedLocation: 'POST_PERMALINK_DIALOG',
346
+ focusCommentID: null,
347
+ scale: 1,
348
+ useDefaultActor: false,
349
+ id: feedbackId,
350
+ __relay_internal__pv__CometUFICommentAutoTranslationTyperelayprovider: 'ORIGINAL',
351
+ __relay_internal__pv__CometUFICommentAvatarStickerAnimatedImagerelayprovider: false,
352
+ __relay_internal__pv__CometUFICommentActionLinksRewriteEnabledrelayprovider: false,
353
+ __relay_internal__pv__IsWorkUserrelayprovider: false,
354
+ };
355
+
356
+ const form = {
357
+ av: ctx.userID,
358
+ fb_api_caller_class: 'RelayModern',
359
+ fb_api_req_friendly_name: 'CommentsListComponentsPaginationQuery',
360
+ variables: JSON.stringify(variables),
361
+ doc_id: '35385759421023325',
362
+ server_timestamps: 'true',
363
+ };
364
+
365
+ const gqlRes = await defaultFuncs.post(
366
+ 'https://www.facebook.com/api/graphql/',
367
+ ctx.jar,
368
+ form,
369
+ ctx.globalOptions,
370
+ ctx,
371
+ );
372
+
373
+ let gqlData: any;
374
+ if (typeof gqlRes.body === 'object' && gqlRes.body !== null && !Buffer.isBuffer(gqlRes.body)) {
375
+ gqlData = gqlRes.body;
376
+ } else {
377
+ const body = gqlRes.body.toString();
378
+ try {
379
+ gqlData = JSON.parse(body);
380
+ } catch (e) {
381
+ // Multi-line JSON stream — take first line
382
+ gqlData = JSON.parse(body.split('\n')[0]);
383
+ }
384
+ }
385
+
386
+ // Navigate to comments in GraphQL response
387
+ const commentsConnection =
388
+ gqlData?.data?.node?.comment_rendering_instance_for_feed_location
389
+ ?.comments ??
390
+ gqlData?.data?.feedback?.comment_rendering_instance_for_feed_location
391
+ ?.comments ??
392
+ null;
393
+
394
+ const result: any[] = [];
395
+ let page_info: any = null;
396
+
397
+ if (commentsConnection) {
398
+ page_info = commentsConnection.page_info || null;
399
+ (commentsConnection.edges || []).forEach((edge: any) => {
400
+ if (!edge?.node) return;
401
+ const c: any = {
402
+ id: edge.node.legacy_fbid,
403
+ graphql_id: edge.node.id,
404
+ text: edge.node.body?.text || edge.node.preferred_body?.text || '',
405
+ created_time: edge.node.created_time,
406
+ author: {
407
+ id: edge.node.author?.id,
408
+ name: edge.node.author?.name,
409
+ avatar: edge.node.author?.profile_picture_depth_0?.uri,
410
+ },
411
+ reply_count: edge.node.feedback?.replies_fields?.count || 0,
412
+ total_reply_count: edge.node.feedback?.replies_fields?.total_count || 0,
413
+ depth: edge.node.depth || 0,
414
+ attachments: edge.node.attachments || [],
415
+ };
416
+ if (edge.node.feedback?.replies_connection?.edges) {
417
+ c.replies = edge.node.feedback.replies_connection.edges
418
+ .map((re: any) => {
419
+ if (!re?.node) return null;
420
+ return {
421
+ id: re.node.legacy_fbid,
422
+ text: re.node.body?.text || re.node.preferred_body?.text || '',
423
+ created_time: re.node.created_time,
424
+ author: {
425
+ id: re.node.author?.id,
426
+ name: re.node.author?.name,
427
+ avatar: re.node.author?.profile_picture_depth_0?.uri,
428
+ },
429
+ };
430
+ })
431
+ .filter((r: any) => r !== null);
432
+ }
433
+ result.push(c);
434
+ });
435
+ }
436
+
437
+ callback(null, { comments: result, page_info });
438
+ return returnPromise;
439
+ }
440
+
441
+ // ── Initial load mode: fetch HTML and parse ───────────────────────
442
+ let url: string;
317
443
 
318
- // Handle both string and object input
319
444
  if (typeof postID === 'string') {
320
- // If it's a simple post ID, construct URL with user's ID
321
- story_fbid = postID;
322
- accountID = ctx.userID;
323
- } else if (typeof postID === 'object') {
324
- story_fbid = postID.story_fbid;
325
- accountID = postID.id || ctx.userID;
445
+ if (postID.startsWith('https://') || postID.startsWith('http://')) {
446
+ url = postID;
447
+ } else if (postID.startsWith('/')) {
448
+ url = `https://www.facebook.com${postID}`;
449
+ } else if (/^\d+$/.test(postID)) {
450
+ url = `https://www.facebook.com/permalink.php?story_fbid=${postID}&id=${ctx.userID}`;
451
+ } else {
452
+ url = `https://www.facebook.com/permalink.php?story_fbid=${postID}&id=${ctx.userID}`;
453
+ }
454
+ } else if (typeof postID === 'object' && postID !== null) {
455
+ const story_fbid = postID.story_fbid;
456
+ const accountID = postID.id || ctx.userID;
457
+ const postUrl = postID.url;
458
+
459
+ if (postUrl) {
460
+ url = postUrl.startsWith('http')
461
+ ? postUrl
462
+ : `https://www.facebook.com${postUrl}`;
463
+ } else if (story_fbid) {
464
+ url = `https://www.facebook.com/permalink.php?story_fbid=${story_fbid}&id=${accountID}`;
465
+ } else {
466
+ throw new Error('Object input must have story_fbid or url field.');
467
+ }
326
468
  } else {
327
469
  throw new Error(
328
- 'Invalid input: expected string or object with story_fbid and id.',
470
+ 'Invalid input: expected string (URL or postID) or object with story_fbid/url.',
329
471
  );
330
472
  }
331
473
 
332
- if (!story_fbid) {
333
- throw new Error('Post ID or story_fbid is required.');
334
- }
335
-
336
- // Construct the permalink URL
337
- const url = `https://www.facebook.com/permalink.php?story_fbid=${story_fbid}&id=${accountID}`;
338
-
339
474
  // Make GET request to fetch the page HTML with cookies
340
475
  const resData = await utils.get(url, ctx.jar, {}, ctx.globalOptions, ctx);
341
476
  const html = resData.body.toString();
342
477
 
343
- // Extract comments using new logic
344
- const commentList = extractCommentsFromHTML(html);
478
+ // Extract comments and page_info from HTML
479
+ const { comments, page_info } = extractCommentsFromHTML(html);
345
480
 
346
- callback(null, commentList);
481
+ callback(null, { comments, page_info });
347
482
  } catch (err) {
348
483
  utils.error('getPostComments', err);
349
484
  callback(err);
@@ -370,9 +505,11 @@ export default function(defaultFuncs: any, api: any, ctx: any) {
370
505
  /**
371
506
  * Extract comments from HTML using new parsing logic
372
507
  * @private
508
+ * @returns {{ comments: any[], page_info: any | null }}
373
509
  */
374
- function extractCommentsFromHTML(htmlContent) {
375
- const comments = [];
510
+ function extractCommentsFromHTML(htmlContent: string): { comments: any[]; page_info: any | null } {
511
+ const comments: any[] = [];
512
+ let page_info: any = null;
376
513
 
377
514
  try {
378
515
  // Find all script tags containing JSON data
@@ -381,7 +518,7 @@ export default function(defaultFuncs: any, api: any, ctx: any) {
381
518
  );
382
519
 
383
520
  if (!scriptMatches) {
384
- return comments;
521
+ return { comments, page_info };
385
522
  }
386
523
 
387
524
  // Loop through each script tag
@@ -442,8 +579,29 @@ export default function(defaultFuncs: any, api: any, ctx: any) {
442
579
  commentData.edges &&
443
580
  Array.isArray(commentData.edges)
444
581
  ) {
582
+ // Extract page_info (cursor pagination info)
583
+ if (commentData.page_info && !page_info) {
584
+ page_info = {
585
+ end_cursor: commentData.page_info.end_cursor || null,
586
+ has_next_page: commentData.page_info.has_next_page || false,
587
+ has_previous_page: commentData.page_info.has_previous_page || false,
588
+ start_cursor: commentData.page_info.start_cursor || null,
589
+ };
590
+ // Also try to capture feedback_id for GraphQL pagination
591
+ try {
592
+ const feedback =
593
+ result.data.node_v2?.comet_sections?.feedback?.story
594
+ ?.story_ufi_container?.story?.feedback_context
595
+ ?.feedback_target_with_context?.comment_list_renderer
596
+ ?.feedback;
597
+ if (feedback?.id && !page_info.feedback_id) {
598
+ page_info.feedback_id = feedback.id;
599
+ }
600
+ } catch (_) {}
601
+ }
602
+
445
603
  // Extract comments
446
- commentData.edges.forEach((edge) => {
604
+ commentData.edges.forEach((edge: any) => {
447
605
  if (!edge.node) return;
448
606
 
449
607
  const comment: any = {
@@ -472,7 +630,7 @@ export default function(defaultFuncs: any, api: any, ctx: any) {
472
630
  // Get replies if available
473
631
  if (edge.node.feedback?.replies_connection?.edges) {
474
632
  comment.replies = edge.node.feedback.replies_connection.edges
475
- .map((replyEdge) => {
633
+ .map((replyEdge: any) => {
476
634
  if (!replyEdge.node) return null;
477
635
  return {
478
636
  id: replyEdge.node.legacy_fbid,
@@ -490,7 +648,7 @@ export default function(defaultFuncs: any, api: any, ctx: any) {
490
648
  },
491
649
  };
492
650
  })
493
- .filter((r) => r !== null);
651
+ .filter((r: any) => r !== null);
494
652
  }
495
653
 
496
654
  comments.push(comment);
@@ -510,7 +668,7 @@ export default function(defaultFuncs: any, api: any, ctx: any) {
510
668
  utils.error('extractCommentsFromHTML', error);
511
669
  }
512
670
 
513
- return comments;
671
+ return { comments, page_info };
514
672
  }
515
673
 
516
674
  /**
@@ -88,8 +88,30 @@ export interface PostComment {
88
88
  }
89
89
 
90
90
  export interface GetPostCommentsOptions {
91
- story_fbid: string;
91
+ /** Numeric story_fbid (used with id to construct permalink.php URL) */
92
+ story_fbid?: string;
93
+ /** Facebook user/page ID (used with story_fbid) */
92
94
  id?: string;
95
+ /** Direct Facebook post URL (e.g. https://www.facebook.com/user/posts/pfbid...) */
96
+ url?: string;
97
+ /** Cursor for fetching next page of comments (from page_info.end_cursor) */
98
+ after?: string;
99
+ /** GraphQL feedback ID for pagination (from page_info.feedback_id) */
100
+ feedback_id?: string;
101
+ }
102
+
103
+ export interface PageInfo {
104
+ end_cursor: string | null;
105
+ has_next_page: boolean;
106
+ has_previous_page?: boolean;
107
+ start_cursor?: string | null;
108
+ /** GraphQL feedback ID needed for pagination queries */
109
+ feedback_id?: string;
110
+ }
111
+
112
+ export interface GetPostCommentsResult {
113
+ comments: PostComment[];
114
+ page_info: PageInfo | null;
93
115
  }
94
116
 
95
117
  export interface ShareResult {
@@ -374,6 +396,56 @@ export interface ThreadInfo {
374
396
  };
375
397
  }
376
398
 
399
+ export interface SearchGroupOptions {
400
+ limit?: number;
401
+ cursor?: string | null;
402
+ locale?: string;
403
+ }
404
+
405
+ export interface SearchGroupItem {
406
+ groupID: string | null;
407
+ name: string | null;
408
+ url: string | null;
409
+ profileUrl: string | null;
410
+ imageSrc: string | null;
411
+ summary: string | null;
412
+ privacy: string | null;
413
+ memberText: string | null;
414
+ memberCount: number | null;
415
+ joinState: string | null;
416
+ hasMembershipQuestions: boolean | null;
417
+ }
418
+
419
+ export interface SearchGroupResult {
420
+ groups: SearchGroupItem[];
421
+ cursor: string | null;
422
+ hasNextPage: boolean;
423
+ }
424
+
425
+ export interface GroupModule {
426
+ join(groupID: string): Promise<any>;
427
+ leave(groupID: string): Promise<any>;
428
+ getJoinedGroups(
429
+ cursor?: string | null,
430
+ count?: number,
431
+ ): Promise<{
432
+ groups: Array<{
433
+ id: string;
434
+ name: string;
435
+ imageUri: string;
436
+ url: string;
437
+ memberCount: string;
438
+ privacy: string;
439
+ }>;
440
+ endCursor: string | null;
441
+ hasNextPage: boolean;
442
+ }>;
443
+ searchGroup(
444
+ keyword: string,
445
+ options?: SearchGroupOptions,
446
+ ): Promise<SearchGroupResult>;
447
+ }
448
+
377
449
  export interface MessageObject {
378
450
  body?: string;
379
451
  attachment?: ReadStream[];
@@ -609,8 +681,9 @@ export interface API {
609
681
  ): Promise<DeletePostResult>;
610
682
  getComments(
611
683
  postID: string | GetPostCommentsOptions,
612
- callback?: Callback<PostComment[]>,
613
- ): Promise<PostComment[]>;
684
+ optionsOrCallback?: GetPostCommentsOptions | Callback<GetPostCommentsResult>,
685
+ callback?: Callback<GetPostCommentsResult>,
686
+ ): Promise<GetPostCommentsResult>;
614
687
  uploadPhoto(
615
688
  photoPath: string,
616
689
  callback?: Callback<UploadPhotoResult>,
@@ -852,6 +925,9 @@ export interface API {
852
925
  /** Add an external module to extend the API. */
853
926
  addExternalModule(moduleObj: Record<string, Function>): void;
854
927
 
928
+ /** Group-related API methods. */
929
+ group: GroupModule;
930
+
855
931
  /** Allow accessing dynamically loaded methods. */
856
932
  [key: string]: any;
857
933
  }
package/task.txt ADDED
@@ -0,0 +1,24 @@
1
+ phân tích cách lấy bình luận sau bằng cách gọi API GET /tuyetcollection/posts/pfbid02jnmdLKUc4Trmxrh86Mzsqj1cnh72vrecnjQHWvUVxLTmiUv6VrFRoTCLEUnHu2rgl HTTP/2
2
+ Host: www.facebook.com
3
+ Cookie: datr=eRioaetI9Tgtc9AgD3mzsOn9; ps_l=1; ps_n=1; sb=yxmoaT1mPQz9xGiZp2QIQntV; ar_debug=1; c_user=61588408996667; wd=958x944; presence=C%7B%22t3%22%3A%5B%5D%2C%22utc3%22%3A1773923504664%2C%22v%22%3A1%7D; fr=14IeYIDrQiJPtK2MI.AWcoZQVO9ti_wNuGf5SwJ2YjisY_-NDBa_RECyRt39--PgA1IH0.Bpu-zR..AAA.0.0.Bpu-zR.AWfs1v74VdJlfYmKCJaGYQlSLv8; xs=40%3AKL_HAZmsZj9pwA%3A2%3A1773922444%3A-1%3A-1%3A%3AAczxyRuNYHr1YUWyu5hzhU_ojR4q0GgEo1ZvwAcW4w
4
+ Dpr: 1
5
+ Viewport-Width: 958
6
+ Sec-Ch-Ua: "Not_A Brand";v="99", "Chromium";v="142"
7
+ Sec-Ch-Ua-Mobile: ?0
8
+ Sec-Ch-Ua-Platform: "Windows"
9
+ Sec-Ch-Ua-Platform-Version: ""
10
+ Sec-Ch-Ua-Model: ""
11
+ Sec-Ch-Ua-Full-Version-List:
12
+ Sec-Ch-Prefers-Color-Scheme: light
13
+ Accept-Language: en-US,en;q=0.9
14
+ Upgrade-Insecure-Requests: 1
15
+ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36
16
+ Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
17
+ Sec-Fetch-Site: none
18
+ Sec-Fetch-Mode: navigate
19
+ Sec-Fetch-User: ?1
20
+ Sec-Fetch-Dest: document
21
+ Accept-Encoding: gzip, deflate, br
22
+ Priority: u=0, i
23
+
24
+ sau ghi gọi xong được trả về file a.html. lấy bình từ đoạn này "comments":{"created_comment_insertion_position":"TOP","filtering_footer_string":"\u0110\u00e3 ch\u1ecdn ch\u1ebf \u0111\u1ed9 Ph\u00f9 h\u1ee3p nh\u1ea5t n\u00ean m\u1ed9t s\u1ed1 b\u00ecnh lu\u1eadn c\u00f3 th\u1ec3 b\u1ecb l\u1ecdc ra.","count":838,"page_size":10,"total_count":989,"is_not_behind_the_fold":null,"behind_the_fold_subtext":null,"edges":[{"node":{"id":"Y29tbWVudDoxNTAwODE2NjI0NzQ3MDQ5XzEzMDA2MTQ3NTg1NTM1MDk=","feedback":{
package/LICENSE-MIT DELETED
@@ -1,21 +0,0 @@
1
- The MIT License (MIT)
2
-
3
- Copyright (c) 2015 Avery, Benjamin, David, Maude
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in
13
- all copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- THE SOFTWARE.
@@ -1,189 +0,0 @@
1
- /**
2
- * Example usage of post API functions
3
- * This demonstrates how to create, delete posts, upload photos, and get comments
4
- */
5
-
6
- const login = require('../module');
7
-
8
- // Your Facebook credentials
9
- const credentials = {
10
- appState: [] // Add your appState here
11
- };
12
-
13
- login(credentials, (err, api) => {
14
- if (err) {
15
- console.error('Login failed:', err);
16
- return;
17
- }
18
-
19
- console.log('Logged in successfully!');
20
-
21
- // Example 1: Create a post
22
- api.post.create({
23
- message: 'Hello from bb-fca!',
24
- privacy: 'SELF' // Options: 'EVERYONE', 'FRIENDS', 'SELF'
25
- }, (err, result) => {
26
- if (err) {
27
- console.error('Error creating post:', err);
28
- return;
29
- }
30
-
31
- console.log('Post created successfully!');
32
- console.log('Post ID:', result.postID);
33
- console.log('Success:', result.success);
34
- });
35
-
36
- // Example 2: Upload a photo
37
- api.post.uploadPhoto('./path/to/your/photo.png', (err, result) => {
38
- if (err) {
39
- console.error('Error uploading photo:', err);
40
- return;
41
- }
42
-
43
- console.log('Photo uploaded successfully!');
44
- console.log('Photo ID:', result.photoID);
45
- console.log('Upload ID:', result.uploadID);
46
- console.log('Success:', result.success);
47
- });
48
-
49
- // Example 3: Upload photo with Promise
50
- api.post.uploadPhoto('./path/to/your/photo.jpg')
51
- .then(result => {
52
- console.log('Photo uploaded:', result.photoID);
53
- })
54
- .catch(err => {
55
- console.error('Upload failed:', err);
56
- });
57
-
58
- // Example 3.1: Create a post with photo attachments
59
- // First upload the photo, then create post with photo ID
60
- api.post.uploadPhoto('./path/to/your/photo.png')
61
- .then(uploadResult => {
62
- return api.post.create({
63
- message: 'Check out this photo!',
64
- privacy: 'SELF',
65
- photos: [uploadResult.photoID] // Attach the uploaded photo
66
- });
67
- })
68
- .then(createResult => {
69
- console.log('Post with photo created successfully!');
70
- console.log('Post ID:', createResult.postID);
71
- })
72
- .catch(err => {
73
- console.error('Error:', err);
74
- });
75
-
76
- // Example 3.2: Create a post with multiple photos
77
- Promise.all([
78
- api.post.uploadPhoto('./photo1.jpg'),
79
- api.post.uploadPhoto('./photo2.jpg'),
80
- api.post.uploadPhoto('./photo3.jpg')
81
- ])
82
- .then(results => {
83
- const photoIDs = results.map(r => r.photoID);
84
- return api.post.create({
85
- message: 'Multiple photos!',
86
- privacy: 'FRIENDS',
87
- photos: photoIDs
88
- });
89
- })
90
- .then(result => {
91
- console.log('Post with multiple photos created!');
92
- console.log('Post ID:', result.postID);
93
- })
94
- .catch(err => {
95
- console.error('Error:', err);
96
- });
97
-
98
- // Example 4: Get comments from a post
99
- // URL: https://www.facebook.com/permalink.php?story_fbid=pfbid02WHHgPgDR9VuDfXiUR5KCseuh9f2NVmfwddGEgUuYKxrkJpFtqfUKbwcCBV7qAJxel&id=61588408996667
100
- api.post.getComments({
101
- story_fbid: 'pfbid02WHHgPgDR9VuDfXiUR5KCseuh9f2NVmfwddGEgUuYKxrkJpFtqfUKbwcCBV7qAJxel',
102
- id: '61588408996667'
103
- }, (err, comments) => {
104
- if (err) {
105
- console.error('Error getting comments:', err);
106
- return;
107
- }
108
-
109
- console.log(`Found ${comments.length} comments:`);
110
- comments.forEach((comment, index) => {
111
- console.log(`\n--- Comment ${index + 1} ---`);
112
- console.log(`ID: ${comment.id}`);
113
- console.log(`GraphQL ID: ${comment.graphql_id}`);
114
- console.log(`Text: ${comment.text}`);
115
- console.log(`Author: ${comment.author.name} (ID: ${comment.author.id})`);
116
- console.log(`Created: ${new Date(comment.created_time * 1000).toLocaleString('vi-VN')}`);
117
- console.log(`Replies: ${comment.reply_count}/${comment.total_reply_count}`);
118
- console.log(`Depth: ${comment.depth}`);
119
-
120
- if (comment.replies && comment.replies.length > 0) {
121
- console.log(` Nested replies:`);
122
- comment.replies.forEach((reply, i) => {
123
- console.log(` └─ [${i + 1}] ${reply.author.name}: ${reply.text}`);
124
- });
125
- }
126
- });
127
- });
128
-
129
- // Example 5: Get comments using just the story_fbid (will use current user's ID)
130
- api.post.getComments('pfbid02WHHgPgDR9VuDfXiUR5KCseuh9f2NVmfwddGEgUuYKxrkJpFtqfUKbwcCBV7qAJxel')
131
- .then(comments => {
132
- console.log(`\nUsing Promise: Found ${comments.length} comments`);
133
- })
134
- .catch(err => {
135
- console.error('Error:', err);
136
- });
137
-
138
- // Example 7: Upload a video
139
- api.post.uploadVideo('./path/to/your/video.mp4', (err, result) => {
140
- if (err) {
141
- console.error('Error uploading video:', err);
142
- return;
143
- }
144
-
145
- console.log('Video uploaded successfully!');
146
- console.log('Video ID:', result.videoID);
147
- console.log('Upload ID:', result.uploadID);
148
- console.log('Upload Session ID:', result.uploadSessionID);
149
- console.log('Success:', result.success);
150
- });
151
-
152
- // Example 8: Upload video with Promise
153
- api.post.uploadVideo('./path/to/your/video.mp4')
154
- .then(result => {
155
- console.log('Video uploaded:', result.videoID);
156
- })
157
- .catch(err => {
158
- console.error('Video upload failed:', err);
159
- });
160
-
161
- // Example 9: Create a post with a video attachment
162
- api.post.uploadVideo('./path/to/your/video.mp4')
163
- .then(uploadResult => {
164
- return api.post.create({
165
- message: 'Check out this video!',
166
- privacy: 'SELF',
167
- videos: [uploadResult.videoID]
168
- });
169
- })
170
- .then(createResult => {
171
- console.log('Post with video created successfully!');
172
- console.log('Post ID:', createResult.postID);
173
- })
174
- .catch(err => {
175
- console.error('Error:', err);
176
- });
177
-
178
- // Example 10: Delete a post
179
- api.post.delete('post_id_here', (err, result) => {
180
- if (err) {
181
- console.error('Error deleting post:', err);
182
- return;
183
- }
184
-
185
- console.log('Post deleted successfully!');
186
- console.log('Deleted Post ID:', result.postID);
187
- console.log('Success:', result.success);
188
- });
189
- });