bb-fca 2.0.17 → 2.0.18
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/posting/replyComment.js +227 -0
- package/dist/deltas/apis/posting/replyComment.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/types/deltas/apis/posting/replyComment.d.ts +28 -0
- package/package.json +1 -1
- package/src/deltas/apis/posting/replyComment.ts +261 -0
- package/src/types/index.d.ts +15 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = default_1;
|
|
4
|
+
const utils = require("../../../utils");
|
|
5
|
+
/**
|
|
6
|
+
* Extracts the numeric post ID from a Facebook post URL by fetching the page HTML.
|
|
7
|
+
* Supports formats: full URL, /posts/pfbid..., /posts/numericID, permalink.php, etc.
|
|
8
|
+
* @param {object} defaultFuncs - The default functions for making API requests.
|
|
9
|
+
* @param {object} ctx - The context object.
|
|
10
|
+
* @param {string} postUrl - The Facebook post URL.
|
|
11
|
+
* @returns {Promise<string>} The numeric post ID.
|
|
12
|
+
*/
|
|
13
|
+
async function resolvePostID(defaultFuncs, ctx, postUrl) {
|
|
14
|
+
// Build fetch URL
|
|
15
|
+
let fetchUrl;
|
|
16
|
+
if (postUrl.startsWith('http://') || postUrl.startsWith('https://')) {
|
|
17
|
+
fetchUrl = postUrl;
|
|
18
|
+
}
|
|
19
|
+
else if (postUrl.startsWith('/')) {
|
|
20
|
+
fetchUrl = `https://www.facebook.com${postUrl}`;
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
fetchUrl = `https://www.facebook.com/${postUrl}`;
|
|
24
|
+
}
|
|
25
|
+
const resData = await utils.get(fetchUrl, ctx.jar, {}, ctx.globalOptions, ctx);
|
|
26
|
+
const html = resData.body.toString();
|
|
27
|
+
// Try to find post_id from the HTML JSON data
|
|
28
|
+
const scriptMatches = html.match(/<script type="application\/json"\s+data-content-len="\d+"\s+data-sjs>(.*?)<\/script>/gs);
|
|
29
|
+
if (!scriptMatches) {
|
|
30
|
+
throw new Error('Could not parse post HTML to find post ID.');
|
|
31
|
+
}
|
|
32
|
+
let postID = null;
|
|
33
|
+
// Deep search helper
|
|
34
|
+
function deepFind(obj, key) {
|
|
35
|
+
if (!obj || typeof obj !== 'object')
|
|
36
|
+
return null;
|
|
37
|
+
for (const k of Object.keys(obj)) {
|
|
38
|
+
if (k === key && typeof obj[k] === 'string' && obj[k].length > 0) {
|
|
39
|
+
return obj[k];
|
|
40
|
+
}
|
|
41
|
+
const found = deepFind(obj[k], key);
|
|
42
|
+
if (found)
|
|
43
|
+
return found;
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
for (const scriptMatch of scriptMatches) {
|
|
48
|
+
try {
|
|
49
|
+
const jsonMatch = scriptMatch.match(/<script[^>]*>(.*?)<\/script>/s);
|
|
50
|
+
if (!jsonMatch)
|
|
51
|
+
continue;
|
|
52
|
+
const jsonData = JSON.parse(jsonMatch[1]);
|
|
53
|
+
const found = deepFind(jsonData, 'post_id');
|
|
54
|
+
if (found) {
|
|
55
|
+
postID = found;
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (!postID) {
|
|
64
|
+
// Fallback: try to extract from URL patterns
|
|
65
|
+
// e.g. /posts/3228319350779313:415318807385347
|
|
66
|
+
const urlMatch = postUrl.match(/posts\/(\d+)/);
|
|
67
|
+
if (urlMatch) {
|
|
68
|
+
postID = urlMatch[1];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (!postID) {
|
|
72
|
+
// Fallback: try story_fbid from permalink.php
|
|
73
|
+
const storyMatch = postUrl.match(/story_fbid=(\d+)/);
|
|
74
|
+
if (storyMatch) {
|
|
75
|
+
postID = storyMatch[1];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (!postID) {
|
|
79
|
+
throw new Error('Could not resolve post ID from the provided URL.');
|
|
80
|
+
}
|
|
81
|
+
return postID;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Submits the reply comment to the GraphQL endpoint.
|
|
85
|
+
* @param {object} defaultFuncs - The default functions.
|
|
86
|
+
* @param {object} ctx - The context object.
|
|
87
|
+
* @param {object} variables - The fully constructed variables object.
|
|
88
|
+
* @returns {Promise<object>} A promise that resolves with the comment info.
|
|
89
|
+
*/
|
|
90
|
+
async function submitReply(defaultFuncs, ctx, variables) {
|
|
91
|
+
const res = await defaultFuncs
|
|
92
|
+
.post('https://www.facebook.com/api/graphql/', ctx.jar, {
|
|
93
|
+
fb_api_caller_class: 'RelayModern',
|
|
94
|
+
fb_api_req_friendly_name: 'useCometUFICreateCommentMutation',
|
|
95
|
+
variables: JSON.stringify(variables),
|
|
96
|
+
server_timestamps: true,
|
|
97
|
+
doc_id: '26613344231661138',
|
|
98
|
+
})
|
|
99
|
+
.then(utils.parseAndCheckLogin(ctx, defaultFuncs));
|
|
100
|
+
if (res.errors) {
|
|
101
|
+
throw res;
|
|
102
|
+
}
|
|
103
|
+
const commentEdge = res.data?.comment_create?.feedback_comment_edge;
|
|
104
|
+
if (!commentEdge) {
|
|
105
|
+
throw new Error('Unexpected response structure: missing comment_create.feedback_comment_edge');
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
id: commentEdge.node.id,
|
|
109
|
+
url: commentEdge.node.feedback?.url || null,
|
|
110
|
+
count: res.data.comment_create.feedback?.total_comment_count || 0,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Creates a reply to a comment on a Facebook post.
|
|
115
|
+
*
|
|
116
|
+
* @param {string} postUrl - The URL of the Facebook post (e.g. https://www.facebook.com/user/posts/123456789).
|
|
117
|
+
* @param {string} commentID - The numeric ID of the comment to reply to.
|
|
118
|
+
* @param {string} text - The reply text content.
|
|
119
|
+
* @param {function} [callback] - Optional callback function (err, result).
|
|
120
|
+
* @returns {Promise<object>} A promise that resolves with the new reply's information:
|
|
121
|
+
* - id: The GraphQL ID of the new reply.
|
|
122
|
+
* - url: The URL of the reply (if available).
|
|
123
|
+
* - count: The total comment count on the post.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* // Using async/await
|
|
127
|
+
* const result = await api.replyComment(
|
|
128
|
+
* 'https://www.facebook.com/user/posts/3228319350779313',
|
|
129
|
+
* '3873188569648912',
|
|
130
|
+
* 'Cảm ơn bạn!'
|
|
131
|
+
* );
|
|
132
|
+
* console.log(result.id);
|
|
133
|
+
*
|
|
134
|
+
* // Using callback
|
|
135
|
+
* api.replyComment(url, commentId, 'Hello!', (err, result) => {
|
|
136
|
+
* if (err) console.error(err);
|
|
137
|
+
* else console.log(result);
|
|
138
|
+
* });
|
|
139
|
+
*/
|
|
140
|
+
function default_1(defaultFuncs, api, ctx) {
|
|
141
|
+
return async function replyComment(postUrl, commentID, text, callback) {
|
|
142
|
+
let cb;
|
|
143
|
+
const returnPromise = new Promise((resolve, reject) => {
|
|
144
|
+
cb = (error, info) => (info ? resolve(info) : reject(error));
|
|
145
|
+
});
|
|
146
|
+
if (typeof callback === 'function') {
|
|
147
|
+
cb = callback;
|
|
148
|
+
}
|
|
149
|
+
// ── Validate inputs ──────────────────────────────────────────────
|
|
150
|
+
if (!postUrl || typeof postUrl !== 'string') {
|
|
151
|
+
const error = 'postUrl must be a non-empty string (Facebook post URL).';
|
|
152
|
+
utils.error('replyComment', error);
|
|
153
|
+
return cb(error);
|
|
154
|
+
}
|
|
155
|
+
if (!commentID || typeof commentID !== 'string') {
|
|
156
|
+
const error = 'commentID must be a non-empty string (numeric comment ID).';
|
|
157
|
+
utils.error('replyComment', error);
|
|
158
|
+
return cb(error);
|
|
159
|
+
}
|
|
160
|
+
if (!text || typeof text !== 'string') {
|
|
161
|
+
const error = 'text must be a non-empty string (reply content).';
|
|
162
|
+
utils.error('replyComment', error);
|
|
163
|
+
return cb(error);
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
// ── Step 1: Resolve the numeric post ID from the URL ─────────
|
|
167
|
+
const postID = await resolvePostID(defaultFuncs, ctx, postUrl);
|
|
168
|
+
utils.log('replyComment', `Resolved postID: ${postID}`);
|
|
169
|
+
// ── Step 2: Build the correct base64 IDs ─────────────────────
|
|
170
|
+
// feedback_id = base64("feedback:{postID}_{commentID}")
|
|
171
|
+
const feedbackIdRaw = `feedback:${postID}_${commentID}`;
|
|
172
|
+
const feedbackIdBase64 = Buffer.from(feedbackIdRaw).toString('base64');
|
|
173
|
+
// reply_comment_parent_fbid = base64("comment:{postID}_{commentID}")
|
|
174
|
+
const replyParentRaw = `comment:${postID}_${commentID}`;
|
|
175
|
+
const replyParentBase64 = Buffer.from(replyParentRaw).toString('base64');
|
|
176
|
+
// ── Step 3: Build the variables payload ──────────────────────
|
|
177
|
+
const variables = {
|
|
178
|
+
feedLocation: 'POST_PERMALINK_DIALOG',
|
|
179
|
+
feedbackSource: 2,
|
|
180
|
+
groupID: null,
|
|
181
|
+
input: {
|
|
182
|
+
actor_id: ctx.userID,
|
|
183
|
+
client_mutation_id: Math.round(Math.random() * 19).toString(),
|
|
184
|
+
attachments: null,
|
|
185
|
+
feedback_id: feedbackIdBase64,
|
|
186
|
+
formatting_style: null,
|
|
187
|
+
message: {
|
|
188
|
+
ranges: [],
|
|
189
|
+
text: text,
|
|
190
|
+
},
|
|
191
|
+
reply_comment_parent_fbid: replyParentBase64,
|
|
192
|
+
reply_target_clicked: true,
|
|
193
|
+
attribution_id_v2: `CometSinglePostDialogRoot.react,comet.post.single_dialog,via_cold_start,${Date.now()},846664,,,`,
|
|
194
|
+
vod_video_timestamp: null,
|
|
195
|
+
is_tracking_encrypted: true,
|
|
196
|
+
tracking: [],
|
|
197
|
+
feedback_source: 'OBJECT',
|
|
198
|
+
idempotence_token: 'client:' + utils.getGUID(),
|
|
199
|
+
session_id: utils.getGUID(),
|
|
200
|
+
downstream_share_session_id: utils.getGUID(),
|
|
201
|
+
downstream_share_session_origin_uri: postUrl,
|
|
202
|
+
downstream_share_session_start_time: Date.now().toString(),
|
|
203
|
+
},
|
|
204
|
+
inviteShortLinkKey: null,
|
|
205
|
+
renderLocation: null,
|
|
206
|
+
scale: 1,
|
|
207
|
+
useDefaultActor: false,
|
|
208
|
+
focusCommentID: null,
|
|
209
|
+
__relay_internal__pv__groups_comet_use_glvrelayprovider: false,
|
|
210
|
+
__relay_internal__pv__CometUFICommentActionLinksRewriteEnabledrelayprovider: false,
|
|
211
|
+
__relay_internal__pv__CometUFICommentAvatarStickerAnimatedImagerelayprovider: false,
|
|
212
|
+
__relay_internal__pv__IsWorkUserrelayprovider: false,
|
|
213
|
+
__relay_internal__pv__CometUFICommentAutoTranslationTyperelayprovider: 'ORIGINAL',
|
|
214
|
+
};
|
|
215
|
+
// ── Step 4: Submit the reply ──────────────────────────────────
|
|
216
|
+
const info = await submitReply(defaultFuncs, ctx, variables);
|
|
217
|
+
utils.log('replyComment', `Reply created successfully: ${info.id}`);
|
|
218
|
+
cb(null, info);
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
utils.error('replyComment', err);
|
|
222
|
+
cb(err);
|
|
223
|
+
}
|
|
224
|
+
return returnPromise;
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
//# sourceMappingURL=replyComment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replyComment.js","sourceRoot":"","sources":["../../../../src/deltas/apis/posting/replyComment.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -705,6 +705,21 @@ export interface API {
|
|
|
705
705
|
callback?: Callback<CommentResult>,
|
|
706
706
|
): Promise<CommentResult>;
|
|
707
707
|
|
|
708
|
+
/**
|
|
709
|
+
* Reply to a comment on a Facebook post.
|
|
710
|
+
* @param postUrl - The URL of the Facebook post.
|
|
711
|
+
* @param commentID - The numeric ID of the comment to reply to.
|
|
712
|
+
* @param text - The reply text content.
|
|
713
|
+
* @param callback - Optional callback function.
|
|
714
|
+
* @returns A promise that resolves with the new reply's information.
|
|
715
|
+
*/
|
|
716
|
+
replyComment(
|
|
717
|
+
postUrl: string,
|
|
718
|
+
commentID: string,
|
|
719
|
+
text: string,
|
|
720
|
+
callback?: Callback<CommentResult>,
|
|
721
|
+
): Promise<CommentResult>;
|
|
722
|
+
|
|
708
723
|
/** Share a Facebook post. */
|
|
709
724
|
share(
|
|
710
725
|
text: string,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a reply to a comment on a Facebook post.
|
|
3
|
+
*
|
|
4
|
+
* @param {string} postUrl - The URL of the Facebook post (e.g. https://www.facebook.com/user/posts/123456789).
|
|
5
|
+
* @param {string} commentID - The numeric ID of the comment to reply to.
|
|
6
|
+
* @param {string} text - The reply text content.
|
|
7
|
+
* @param {function} [callback] - Optional callback function (err, result).
|
|
8
|
+
* @returns {Promise<object>} A promise that resolves with the new reply's information:
|
|
9
|
+
* - id: The GraphQL ID of the new reply.
|
|
10
|
+
* - url: The URL of the reply (if available).
|
|
11
|
+
* - count: The total comment count on the post.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* // Using async/await
|
|
15
|
+
* const result = await api.replyComment(
|
|
16
|
+
* 'https://www.facebook.com/user/posts/3228319350779313',
|
|
17
|
+
* '3873188569648912',
|
|
18
|
+
* 'Cảm ơn bạn!'
|
|
19
|
+
* );
|
|
20
|
+
* console.log(result.id);
|
|
21
|
+
*
|
|
22
|
+
* // Using callback
|
|
23
|
+
* api.replyComment(url, commentId, 'Hello!', (err, result) => {
|
|
24
|
+
* if (err) console.error(err);
|
|
25
|
+
* else console.log(result);
|
|
26
|
+
* });
|
|
27
|
+
*/
|
|
28
|
+
export default function (defaultFuncs: any, api: any, ctx: any): (postUrl: string, commentID: string, text: string, callback?: Function) => Promise<any>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bb-fca",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.18",
|
|
4
4
|
"description": "BB-FCA is a powerful and user-friendly Facebook Chat API wrapper for Node.js, designed to simplify the process of creating chatbots and automating interactions on Facebook Messenger. With BB-FCA, developers can easily send messages, manage conversations, and interact with the Facebook Messenger platform using a simple and intuitive API.",
|
|
5
5
|
"main": "dist/core/client.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import utils = require('../../../utils');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extracts the numeric post ID from a Facebook post URL by fetching the page HTML.
|
|
5
|
+
* Supports formats: full URL, /posts/pfbid..., /posts/numericID, permalink.php, etc.
|
|
6
|
+
* @param {object} defaultFuncs - The default functions for making API requests.
|
|
7
|
+
* @param {object} ctx - The context object.
|
|
8
|
+
* @param {string} postUrl - The Facebook post URL.
|
|
9
|
+
* @returns {Promise<string>} The numeric post ID.
|
|
10
|
+
*/
|
|
11
|
+
async function resolvePostID(
|
|
12
|
+
defaultFuncs,
|
|
13
|
+
ctx,
|
|
14
|
+
postUrl: string,
|
|
15
|
+
): Promise<string> {
|
|
16
|
+
// Build fetch URL
|
|
17
|
+
let fetchUrl: string;
|
|
18
|
+
if (postUrl.startsWith('http://') || postUrl.startsWith('https://')) {
|
|
19
|
+
fetchUrl = postUrl;
|
|
20
|
+
} else if (postUrl.startsWith('/')) {
|
|
21
|
+
fetchUrl = `https://www.facebook.com${postUrl}`;
|
|
22
|
+
} else {
|
|
23
|
+
fetchUrl = `https://www.facebook.com/${postUrl}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const resData = await utils.get(
|
|
27
|
+
fetchUrl,
|
|
28
|
+
ctx.jar,
|
|
29
|
+
{},
|
|
30
|
+
ctx.globalOptions,
|
|
31
|
+
ctx,
|
|
32
|
+
);
|
|
33
|
+
const html = resData.body.toString();
|
|
34
|
+
|
|
35
|
+
// Try to find post_id from the HTML JSON data
|
|
36
|
+
const scriptMatches = html.match(
|
|
37
|
+
/<script type="application\/json"\s+data-content-len="\d+"\s+data-sjs>(.*?)<\/script>/gs,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
if (!scriptMatches) {
|
|
41
|
+
throw new Error('Could not parse post HTML to find post ID.');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let postID: string | null = null;
|
|
45
|
+
|
|
46
|
+
// Deep search helper
|
|
47
|
+
function deepFind(obj: any, key: string): string | null {
|
|
48
|
+
if (!obj || typeof obj !== 'object') return null;
|
|
49
|
+
for (const k of Object.keys(obj)) {
|
|
50
|
+
if (k === key && typeof obj[k] === 'string' && obj[k].length > 0) {
|
|
51
|
+
return obj[k];
|
|
52
|
+
}
|
|
53
|
+
const found = deepFind(obj[k], key);
|
|
54
|
+
if (found) return found;
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
for (const scriptMatch of scriptMatches) {
|
|
60
|
+
try {
|
|
61
|
+
const jsonMatch = scriptMatch.match(/<script[^>]*>(.*?)<\/script>/s);
|
|
62
|
+
if (!jsonMatch) continue;
|
|
63
|
+
const jsonData = JSON.parse(jsonMatch[1]);
|
|
64
|
+
const found = deepFind(jsonData, 'post_id');
|
|
65
|
+
if (found) {
|
|
66
|
+
postID = found;
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
} catch (e) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!postID) {
|
|
75
|
+
// Fallback: try to extract from URL patterns
|
|
76
|
+
// e.g. /posts/3228319350779313:415318807385347
|
|
77
|
+
const urlMatch = postUrl.match(/posts\/(\d+)/);
|
|
78
|
+
if (urlMatch) {
|
|
79
|
+
postID = urlMatch[1];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!postID) {
|
|
84
|
+
// Fallback: try story_fbid from permalink.php
|
|
85
|
+
const storyMatch = postUrl.match(/story_fbid=(\d+)/);
|
|
86
|
+
if (storyMatch) {
|
|
87
|
+
postID = storyMatch[1];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!postID) {
|
|
92
|
+
throw new Error('Could not resolve post ID from the provided URL.');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return postID;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Submits the reply comment to the GraphQL endpoint.
|
|
100
|
+
* @param {object} defaultFuncs - The default functions.
|
|
101
|
+
* @param {object} ctx - The context object.
|
|
102
|
+
* @param {object} variables - The fully constructed variables object.
|
|
103
|
+
* @returns {Promise<object>} A promise that resolves with the comment info.
|
|
104
|
+
*/
|
|
105
|
+
async function submitReply(defaultFuncs, ctx, variables) {
|
|
106
|
+
const res = await defaultFuncs
|
|
107
|
+
.post('https://www.facebook.com/api/graphql/', ctx.jar, {
|
|
108
|
+
fb_api_caller_class: 'RelayModern',
|
|
109
|
+
fb_api_req_friendly_name: 'useCometUFICreateCommentMutation',
|
|
110
|
+
variables: JSON.stringify(variables),
|
|
111
|
+
server_timestamps: true,
|
|
112
|
+
doc_id: '26613344231661138',
|
|
113
|
+
})
|
|
114
|
+
.then(utils.parseAndCheckLogin(ctx, defaultFuncs));
|
|
115
|
+
|
|
116
|
+
if (res.errors) {
|
|
117
|
+
throw res;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const commentEdge = res.data?.comment_create?.feedback_comment_edge;
|
|
121
|
+
if (!commentEdge) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
'Unexpected response structure: missing comment_create.feedback_comment_edge',
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
id: commentEdge.node.id,
|
|
129
|
+
url: commentEdge.node.feedback?.url || null,
|
|
130
|
+
count: res.data.comment_create.feedback?.total_comment_count || 0,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Creates a reply to a comment on a Facebook post.
|
|
136
|
+
*
|
|
137
|
+
* @param {string} postUrl - The URL of the Facebook post (e.g. https://www.facebook.com/user/posts/123456789).
|
|
138
|
+
* @param {string} commentID - The numeric ID of the comment to reply to.
|
|
139
|
+
* @param {string} text - The reply text content.
|
|
140
|
+
* @param {function} [callback] - Optional callback function (err, result).
|
|
141
|
+
* @returns {Promise<object>} A promise that resolves with the new reply's information:
|
|
142
|
+
* - id: The GraphQL ID of the new reply.
|
|
143
|
+
* - url: The URL of the reply (if available).
|
|
144
|
+
* - count: The total comment count on the post.
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* // Using async/await
|
|
148
|
+
* const result = await api.replyComment(
|
|
149
|
+
* 'https://www.facebook.com/user/posts/3228319350779313',
|
|
150
|
+
* '3873188569648912',
|
|
151
|
+
* 'Cảm ơn bạn!'
|
|
152
|
+
* );
|
|
153
|
+
* console.log(result.id);
|
|
154
|
+
*
|
|
155
|
+
* // Using callback
|
|
156
|
+
* api.replyComment(url, commentId, 'Hello!', (err, result) => {
|
|
157
|
+
* if (err) console.error(err);
|
|
158
|
+
* else console.log(result);
|
|
159
|
+
* });
|
|
160
|
+
*/
|
|
161
|
+
export default function(defaultFuncs: any, api: any, ctx: any) {
|
|
162
|
+
return async function replyComment(
|
|
163
|
+
postUrl: string,
|
|
164
|
+
commentID: string,
|
|
165
|
+
text: string,
|
|
166
|
+
callback?: Function,
|
|
167
|
+
) {
|
|
168
|
+
let cb: (error: any, info?: any) => void;
|
|
169
|
+
const returnPromise = new Promise<any>((resolve, reject) => {
|
|
170
|
+
cb = (error, info) => (info ? resolve(info) : reject(error));
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
if (typeof callback === 'function') {
|
|
174
|
+
cb = callback as any;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ── Validate inputs ──────────────────────────────────────────────
|
|
178
|
+
if (!postUrl || typeof postUrl !== 'string') {
|
|
179
|
+
const error = 'postUrl must be a non-empty string (Facebook post URL).';
|
|
180
|
+
utils.error('replyComment', error);
|
|
181
|
+
return cb(error);
|
|
182
|
+
}
|
|
183
|
+
if (!commentID || typeof commentID !== 'string') {
|
|
184
|
+
const error =
|
|
185
|
+
'commentID must be a non-empty string (numeric comment ID).';
|
|
186
|
+
utils.error('replyComment', error);
|
|
187
|
+
return cb(error);
|
|
188
|
+
}
|
|
189
|
+
if (!text || typeof text !== 'string') {
|
|
190
|
+
const error = 'text must be a non-empty string (reply content).';
|
|
191
|
+
utils.error('replyComment', error);
|
|
192
|
+
return cb(error);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
// ── Step 1: Resolve the numeric post ID from the URL ─────────
|
|
197
|
+
const postID = await resolvePostID(defaultFuncs, ctx, postUrl);
|
|
198
|
+
utils.log('replyComment', `Resolved postID: ${postID}`);
|
|
199
|
+
|
|
200
|
+
// ── Step 2: Build the correct base64 IDs ─────────────────────
|
|
201
|
+
// feedback_id = base64("feedback:{postID}_{commentID}")
|
|
202
|
+
const feedbackIdRaw = `feedback:${postID}_${commentID}`;
|
|
203
|
+
const feedbackIdBase64 = Buffer.from(feedbackIdRaw).toString('base64');
|
|
204
|
+
|
|
205
|
+
// reply_comment_parent_fbid = base64("comment:{postID}_{commentID}")
|
|
206
|
+
const replyParentRaw = `comment:${postID}_${commentID}`;
|
|
207
|
+
const replyParentBase64 = Buffer.from(replyParentRaw).toString('base64');
|
|
208
|
+
|
|
209
|
+
// ── Step 3: Build the variables payload ──────────────────────
|
|
210
|
+
const variables = {
|
|
211
|
+
feedLocation: 'POST_PERMALINK_DIALOG',
|
|
212
|
+
feedbackSource: 2,
|
|
213
|
+
groupID: null,
|
|
214
|
+
input: {
|
|
215
|
+
actor_id: ctx.userID,
|
|
216
|
+
client_mutation_id: Math.round(Math.random() * 19).toString(),
|
|
217
|
+
attachments: null,
|
|
218
|
+
feedback_id: feedbackIdBase64,
|
|
219
|
+
formatting_style: null,
|
|
220
|
+
message: {
|
|
221
|
+
ranges: [],
|
|
222
|
+
text: text,
|
|
223
|
+
},
|
|
224
|
+
reply_comment_parent_fbid: replyParentBase64,
|
|
225
|
+
reply_target_clicked: true,
|
|
226
|
+
attribution_id_v2: `CometSinglePostDialogRoot.react,comet.post.single_dialog,via_cold_start,${Date.now()},846664,,,`,
|
|
227
|
+
vod_video_timestamp: null,
|
|
228
|
+
is_tracking_encrypted: true,
|
|
229
|
+
tracking: [],
|
|
230
|
+
feedback_source: 'OBJECT',
|
|
231
|
+
idempotence_token: 'client:' + utils.getGUID(),
|
|
232
|
+
session_id: utils.getGUID(),
|
|
233
|
+
downstream_share_session_id: utils.getGUID(),
|
|
234
|
+
downstream_share_session_origin_uri: postUrl,
|
|
235
|
+
downstream_share_session_start_time: Date.now().toString(),
|
|
236
|
+
},
|
|
237
|
+
inviteShortLinkKey: null,
|
|
238
|
+
renderLocation: null,
|
|
239
|
+
scale: 1,
|
|
240
|
+
useDefaultActor: false,
|
|
241
|
+
focusCommentID: null,
|
|
242
|
+
__relay_internal__pv__groups_comet_use_glvrelayprovider: false,
|
|
243
|
+
__relay_internal__pv__CometUFICommentActionLinksRewriteEnabledrelayprovider: false,
|
|
244
|
+
__relay_internal__pv__CometUFICommentAvatarStickerAnimatedImagerelayprovider: false,
|
|
245
|
+
__relay_internal__pv__IsWorkUserrelayprovider: false,
|
|
246
|
+
__relay_internal__pv__CometUFICommentAutoTranslationTyperelayprovider:
|
|
247
|
+
'ORIGINAL',
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
// ── Step 4: Submit the reply ──────────────────────────────────
|
|
251
|
+
const info = await submitReply(defaultFuncs, ctx, variables);
|
|
252
|
+
utils.log('replyComment', `Reply created successfully: ${info.id}`);
|
|
253
|
+
cb(null, info);
|
|
254
|
+
} catch (err) {
|
|
255
|
+
utils.error('replyComment', err);
|
|
256
|
+
cb(err);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return returnPromise;
|
|
260
|
+
};
|
|
261
|
+
}
|
package/src/types/index.d.ts
CHANGED
|
@@ -705,6 +705,21 @@ export interface API {
|
|
|
705
705
|
callback?: Callback<CommentResult>,
|
|
706
706
|
): Promise<CommentResult>;
|
|
707
707
|
|
|
708
|
+
/**
|
|
709
|
+
* Reply to a comment on a Facebook post.
|
|
710
|
+
* @param postUrl - The URL of the Facebook post.
|
|
711
|
+
* @param commentID - The numeric ID of the comment to reply to.
|
|
712
|
+
* @param text - The reply text content.
|
|
713
|
+
* @param callback - Optional callback function.
|
|
714
|
+
* @returns A promise that resolves with the new reply's information.
|
|
715
|
+
*/
|
|
716
|
+
replyComment(
|
|
717
|
+
postUrl: string,
|
|
718
|
+
commentID: string,
|
|
719
|
+
text: string,
|
|
720
|
+
callback?: Callback<CommentResult>,
|
|
721
|
+
): Promise<CommentResult>;
|
|
722
|
+
|
|
708
723
|
/** Share a Facebook post. */
|
|
709
724
|
share(
|
|
710
725
|
text: string,
|