surya-sahil-fca 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +175 -0
- package/DOCS.md +2636 -0
- package/LICENSE-MIT +21 -0
- package/README.md +107 -0
- package/func/checkUpdate.js +222 -0
- package/func/logger.js +48 -0
- package/index.d.ts +746 -0
- package/index.js +8 -0
- package/module/config.js +34 -0
- package/module/login.js +126 -0
- package/module/loginHelper.js +747 -0
- package/module/options.js +45 -0
- package/package.json +82 -0
- package/src/api/messaging/changeGroupImage.js +90 -0
- package/src/api/messaging/changeNickname.js +70 -0
- package/src/api/messaging/changeThreadName.js +123 -0
- package/src/api/messaging/createCommentPost.js +207 -0
- package/src/api/messaging/sendMessage.js +272 -0
- package/src/api/messaging/sendTypingIndicator.js +67 -0
- package/src/api/messaging/setMessageReaction.js +76 -0
- package/src/api/messaging/setTitle.js +119 -0
- package/src/api/messaging/stickers.js +257 -0
- package/src/core/sendReqMqtt.js +96 -0
- package/src/database/models/index.js +49 -0
- package/src/database/models/thread.js +31 -0
- package/src/database/models/user.js +32 -0
- package/src/database/threadData.js +98 -0
- package/src/database/userData.js +89 -0
- package/src/utils/client.js +320 -0
- package/src/utils/constants.js +23 -0
- package/src/utils/format.js +1115 -0
- package/src/utils/headers.js +115 -0
- package/src/utils/request.js +305 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const { getType } = require("../src/utils/format");
|
|
2
|
+
const { setProxy } = require("../src/utils/request");
|
|
3
|
+
const logger = require("../func/logger");
|
|
4
|
+
const Boolean_Option = [
|
|
5
|
+
"online",
|
|
6
|
+
"selfListen",
|
|
7
|
+
"listenEvents",
|
|
8
|
+
"updatePresence",
|
|
9
|
+
"forceLogin",
|
|
10
|
+
"autoMarkDelivery",
|
|
11
|
+
"autoMarkRead",
|
|
12
|
+
"listenTyping",
|
|
13
|
+
"autoReconnect",
|
|
14
|
+
"emitReady",
|
|
15
|
+
"selfListenEvent"
|
|
16
|
+
];
|
|
17
|
+
function setOptions(globalOptions, options) {
|
|
18
|
+
for (const key of Object.keys(options || {})) {
|
|
19
|
+
if (Boolean_Option.includes(key)) {
|
|
20
|
+
globalOptions[key] = Boolean(options[key]);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
switch (key) {
|
|
24
|
+
case "userAgent": {
|
|
25
|
+
globalOptions.userAgent = options.userAgent || "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36";
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
case "proxy": {
|
|
29
|
+
if (typeof options.proxy !== "string") {
|
|
30
|
+
delete globalOptions.proxy;
|
|
31
|
+
setProxy();
|
|
32
|
+
} else {
|
|
33
|
+
globalOptions.proxy = options.proxy;
|
|
34
|
+
setProxy(globalOptions.proxy);
|
|
35
|
+
}
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
default: {
|
|
39
|
+
logger("setOptions Unrecognized option given to setOptions: " + key, "warn");
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
module.exports = { setOptions, Boolean_Option };
|
package/package.json
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "surya-sahil-fca",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Facebook Chat API - Streamlined version with essential features",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"types": "index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"require": "./index.js",
|
|
10
|
+
"default": "./index.js",
|
|
11
|
+
"types": "./index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"test": "mocha",
|
|
16
|
+
"lint": "eslint ."
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/surya-sahil/fca.git"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"facebook",
|
|
24
|
+
"chat",
|
|
25
|
+
"api",
|
|
26
|
+
"messenger",
|
|
27
|
+
"bot",
|
|
28
|
+
"unofficial",
|
|
29
|
+
"automation",
|
|
30
|
+
"facebook-api",
|
|
31
|
+
"facebook-chat",
|
|
32
|
+
"facebook-messenger",
|
|
33
|
+
"chatbot",
|
|
34
|
+
"nodejs",
|
|
35
|
+
"fca"
|
|
36
|
+
],
|
|
37
|
+
"author": {
|
|
38
|
+
"name": "Surya Sahil",
|
|
39
|
+
"url": "https://github.com/surya-sahil"
|
|
40
|
+
},
|
|
41
|
+
"contributors": [
|
|
42
|
+
{
|
|
43
|
+
"name": "Surya Sahil",
|
|
44
|
+
"url": "https://github.com/surya-sahil"
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://github.com/surya-sahil/fca/issues"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://github.com/surya-sahil/fca#readme",
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=12.0.0"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"axios": "latest",
|
|
57
|
+
"axios-cookiejar-support": "^5.0.5",
|
|
58
|
+
"bluebird": "^3.7.2",
|
|
59
|
+
"chalk": "^4.1.2",
|
|
60
|
+
"cheerio": "^1.0.0-rc.10",
|
|
61
|
+
"duplexify": "^4.1.3",
|
|
62
|
+
"gradient-string": "^2.0.2",
|
|
63
|
+
"https-proxy-agent": "^4.0.0",
|
|
64
|
+
"mqtt": "^4.3.8",
|
|
65
|
+
"npmlog": "^1.2.0",
|
|
66
|
+
"request": "^2.53.0",
|
|
67
|
+
"sequelize": "^6.37.6",
|
|
68
|
+
"sqlite3": "^5.1.7",
|
|
69
|
+
"totp-generator": "^1.0.0",
|
|
70
|
+
"ws": "^8.18.1"
|
|
71
|
+
},
|
|
72
|
+
"devDependencies": {
|
|
73
|
+
"eslint": "^8.50.0",
|
|
74
|
+
"mocha": "^10.2.0"
|
|
75
|
+
},
|
|
76
|
+
"optionalDependencies": {},
|
|
77
|
+
"peerDependencies": {},
|
|
78
|
+
"publishConfig": {
|
|
79
|
+
"access": "public",
|
|
80
|
+
"registry": "https://registry.npmjs.org/"
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { generateOfflineThreadingID } = require("../../utils/format.js");
|
|
4
|
+
const log = require("npmlog");
|
|
5
|
+
|
|
6
|
+
module.exports = function (defaultFuncs, api, ctx) {
|
|
7
|
+
function handleUpload(image) {
|
|
8
|
+
const form = {
|
|
9
|
+
images_only: "true",
|
|
10
|
+
"attachment[]": image
|
|
11
|
+
};
|
|
12
|
+
return defaultFuncs
|
|
13
|
+
.postFormData("https://upload.facebook.com/ajax/mercury/upload.php", ctx.jar, form, {})
|
|
14
|
+
.then(parseAndCheckLogin(ctx, defaultFuncs))
|
|
15
|
+
.then(resData => {
|
|
16
|
+
if (resData.error) throw resData;
|
|
17
|
+
return resData.payload.metadata[0];
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
return function changeGroupImage(image, threadID, callback) {
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
if (!ctx.mqttClient) {
|
|
23
|
+
const err = new Error("Not connected to MQTT");
|
|
24
|
+
callback?.(err);
|
|
25
|
+
return reject(err);
|
|
26
|
+
}
|
|
27
|
+
if (!threadID || typeof threadID !== "string") {
|
|
28
|
+
const err = new Error("Invalid threadID");
|
|
29
|
+
callback?.(err);
|
|
30
|
+
return reject(err);
|
|
31
|
+
}
|
|
32
|
+
const reqID = ++ctx.wsReqNumber;
|
|
33
|
+
const taskID = ++ctx.wsTaskNumber;
|
|
34
|
+
const onResponse = (topic, message) => {
|
|
35
|
+
if (topic !== "/ls_resp") return;
|
|
36
|
+
let jsonMsg;
|
|
37
|
+
try {
|
|
38
|
+
jsonMsg = JSON.parse(message.toString());
|
|
39
|
+
jsonMsg.payload = JSON.parse(jsonMsg.payload);
|
|
40
|
+
} catch (err) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (jsonMsg.request_id !== reqID) return;
|
|
44
|
+
ctx.mqttClient.removeListener("message", onResponse);
|
|
45
|
+
callback?.(null, { success: true, response: jsonMsg.payload });
|
|
46
|
+
return resolve({ success: true, response: jsonMsg.payload });
|
|
47
|
+
};
|
|
48
|
+
ctx.mqttClient.on("message", onResponse);
|
|
49
|
+
handleUpload(image)
|
|
50
|
+
.then(payload => {
|
|
51
|
+
const imageID = payload.image_id;
|
|
52
|
+
const taskPayload = {
|
|
53
|
+
thread_key: threadID,
|
|
54
|
+
image_id: imageID,
|
|
55
|
+
sync_group: 1
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const mqttPayload = {
|
|
59
|
+
epoch_id: generateOfflineThreadingID(),
|
|
60
|
+
tasks: [
|
|
61
|
+
{
|
|
62
|
+
failure_count: null,
|
|
63
|
+
label: "37",
|
|
64
|
+
payload: JSON.stringify(taskPayload),
|
|
65
|
+
queue_name: "thread_image",
|
|
66
|
+
task_id: taskID
|
|
67
|
+
}
|
|
68
|
+
],
|
|
69
|
+
version_id: "8798795233522156"
|
|
70
|
+
};
|
|
71
|
+
const request = {
|
|
72
|
+
app_id: "2220391788200892",
|
|
73
|
+
payload: JSON.stringify(mqttPayload),
|
|
74
|
+
request_id: reqID,
|
|
75
|
+
type: 3
|
|
76
|
+
};
|
|
77
|
+
ctx.mqttClient.publish("/ls_req", JSON.stringify(request), {
|
|
78
|
+
qos: 1,
|
|
79
|
+
retain: false
|
|
80
|
+
});
|
|
81
|
+
})
|
|
82
|
+
.catch(err => {
|
|
83
|
+
ctx.mqttClient.removeListener("message", onResponse);
|
|
84
|
+
log.error("changeGroupImageMqtt", err);
|
|
85
|
+
callback?.(err);
|
|
86
|
+
reject(err);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { generateOfflineThreadingID } = require("../../utils/format.js");
|
|
4
|
+
const log = require("npmlog");
|
|
5
|
+
|
|
6
|
+
module.exports = function (defaultFuncs, api, ctx) {
|
|
7
|
+
return function changeNickname(nickname, threadID, participantID, callback) {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
if (!ctx.mqttClient) {
|
|
10
|
+
const err = new Error("Not connected to MQTT");
|
|
11
|
+
callback?.(err);
|
|
12
|
+
return reject(err);
|
|
13
|
+
}
|
|
14
|
+
if (!threadID || !participantID) {
|
|
15
|
+
const err = new Error("Missing required parameters");
|
|
16
|
+
callback?.(err);
|
|
17
|
+
return reject(err);
|
|
18
|
+
}
|
|
19
|
+
const reqID = ++ctx.wsReqNumber;
|
|
20
|
+
const taskID = ++ctx.wsTaskNumber;
|
|
21
|
+
const payload = {
|
|
22
|
+
epoch_id: generateOfflineThreadingID(),
|
|
23
|
+
tasks: [
|
|
24
|
+
{
|
|
25
|
+
failure_count: null,
|
|
26
|
+
label: "44",
|
|
27
|
+
payload: JSON.stringify({
|
|
28
|
+
thread_key: threadID,
|
|
29
|
+
contact_id: participantID,
|
|
30
|
+
nickname: nickname || "",
|
|
31
|
+
sync_group: 1
|
|
32
|
+
}),
|
|
33
|
+
queue_name: "thread_participant_nickname",
|
|
34
|
+
task_id: taskID
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
version_id: "8798795233522156"
|
|
38
|
+
};
|
|
39
|
+
const request = {
|
|
40
|
+
app_id: "2220391788200892",
|
|
41
|
+
payload: JSON.stringify(payload),
|
|
42
|
+
request_id: reqID,
|
|
43
|
+
type: 3
|
|
44
|
+
};
|
|
45
|
+
const onResponse = (topic, message) => {
|
|
46
|
+
if (topic !== "/ls_resp") return;
|
|
47
|
+
let jsonMsg;
|
|
48
|
+
try {
|
|
49
|
+
jsonMsg = JSON.parse(message.toString());
|
|
50
|
+
jsonMsg.payload = JSON.parse(jsonMsg.payload);
|
|
51
|
+
} catch (err) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (jsonMsg.request_id !== reqID) return;
|
|
55
|
+
ctx.mqttClient.removeListener("message", onResponse);
|
|
56
|
+
callback?.(null, { success: true, response: jsonMsg.payload });
|
|
57
|
+
return resolve({ success: true, response: jsonMsg.payload });
|
|
58
|
+
};
|
|
59
|
+
ctx.mqttClient.on("message", onResponse);
|
|
60
|
+
ctx.mqttClient.publish("/ls_req", JSON.stringify(request), { qos: 1, retain: false }, (err) => {
|
|
61
|
+
if (err) {
|
|
62
|
+
ctx.mqttClient.removeListener("message", onResponse);
|
|
63
|
+
log.error("changeNicknameMqtt", err);
|
|
64
|
+
callback?.(err);
|
|
65
|
+
return reject(err);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const utils = require('../../../utils');
|
|
4
|
+
|
|
5
|
+
module.exports = function (defaultFuncs, api, ctx) {
|
|
6
|
+
/**
|
|
7
|
+
* Made by Choru Official
|
|
8
|
+
* Mqtt
|
|
9
|
+
* Sets the name of a group chat thread via MQTT.
|
|
10
|
+
*
|
|
11
|
+
* @param {string} newName The new name for the group chat.
|
|
12
|
+
* @param {string} threadID The ID of the group chat thread.
|
|
13
|
+
* @param {Function} [callback] Optional callback function to be invoked upon completion.
|
|
14
|
+
* @param {string} [initiatorID] The ID of the user who initiated the group name change (e.g., from event.senderID).
|
|
15
|
+
* @returns {Promise<object>} A promise that resolves with a structured event object on success or rejects on error.
|
|
16
|
+
*/
|
|
17
|
+
return function gcname(newName, threadID, callback, initiatorID) {
|
|
18
|
+
let _callback;
|
|
19
|
+
let _initiatorID;
|
|
20
|
+
|
|
21
|
+
let _resolvePromise;
|
|
22
|
+
let _rejectPromise;
|
|
23
|
+
const returnPromise = new Promise((resolve, reject) => {
|
|
24
|
+
_resolvePromise = resolve;
|
|
25
|
+
_rejectPromise = reject;
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
if (utils.getType(callback) === "Function" || utils.getType(callback) === "AsyncFunction") {
|
|
29
|
+
_callback = callback;
|
|
30
|
+
_initiatorID = initiatorID;
|
|
31
|
+
} else if (utils.getType(threadID) === "Function" || utils.getType(threadID) === "AsyncFunction") {
|
|
32
|
+
_callback = threadID;
|
|
33
|
+
threadID = null;
|
|
34
|
+
_initiatorID = callback;
|
|
35
|
+
} else if (utils.getType(callback) === "string") {
|
|
36
|
+
_initiatorID = callback;
|
|
37
|
+
_callback = undefined;
|
|
38
|
+
} else {
|
|
39
|
+
_callback = undefined;
|
|
40
|
+
_initiatorID = undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!_callback) {
|
|
44
|
+
_callback = function (__err, __data) {
|
|
45
|
+
if (__err) _rejectPromise(__err);
|
|
46
|
+
else _resolvePromise(__data);
|
|
47
|
+
};
|
|
48
|
+
} else {
|
|
49
|
+
const originalCallback = _callback;
|
|
50
|
+
_callback = function(__err, __data) {
|
|
51
|
+
if (__err) {
|
|
52
|
+
originalCallback(__err);
|
|
53
|
+
_rejectPromise(__err);
|
|
54
|
+
} else {
|
|
55
|
+
originalCallback(null, __data);
|
|
56
|
+
_resolvePromise(__data);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
_initiatorID = _initiatorID || ctx.userID;
|
|
62
|
+
|
|
63
|
+
threadID = threadID || ctx.threadID;
|
|
64
|
+
|
|
65
|
+
if (!threadID) {
|
|
66
|
+
return _callback(new Error("threadID is required to change the group chat name."));
|
|
67
|
+
}
|
|
68
|
+
if (typeof newName !== 'string') {
|
|
69
|
+
return _callback(new Error("newName must be a string."));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!ctx.mqttClient) {
|
|
73
|
+
return _callback(new Error("Not connected to MQTT"));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
ctx.wsReqNumber += 1;
|
|
77
|
+
ctx.wsTaskNumber += 1;
|
|
78
|
+
|
|
79
|
+
const queryPayload = {
|
|
80
|
+
thread_key: threadID.toString(),
|
|
81
|
+
thread_name: newName,
|
|
82
|
+
sync_group: 1,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const query = {
|
|
86
|
+
failure_count: null,
|
|
87
|
+
label: '32',
|
|
88
|
+
payload: JSON.stringify(queryPayload),
|
|
89
|
+
queue_name: threadID.toString(),
|
|
90
|
+
task_id: ctx.wsTaskNumber,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const context = {
|
|
94
|
+
app_id: ctx.appID,
|
|
95
|
+
payload: {
|
|
96
|
+
epoch_id: parseInt(utils.generateOfflineThreadingID()),
|
|
97
|
+
tasks: [query],
|
|
98
|
+
version_id: '24631415369801570',
|
|
99
|
+
},
|
|
100
|
+
request_id: ctx.wsReqNumber,
|
|
101
|
+
type: 3,
|
|
102
|
+
};
|
|
103
|
+
context.payload = JSON.stringify(context.payload);
|
|
104
|
+
|
|
105
|
+
ctx.mqttClient.publish('/ls_req', JSON.stringify(context), { qos: 1, retain: false }, (err) => {
|
|
106
|
+
if (err) {
|
|
107
|
+
return _callback(new Error(`MQTT publish failed for gcname: ${err.message || err}`));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const gcnameChangeEvent = {
|
|
111
|
+
type: "thread_name_update",
|
|
112
|
+
threadID: threadID,
|
|
113
|
+
newName: newName,
|
|
114
|
+
senderID: _initiatorID,
|
|
115
|
+
BotID: ctx.userID,
|
|
116
|
+
timestamp: Date.now(),
|
|
117
|
+
};
|
|
118
|
+
_callback(null, gcnameChangeEvent);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return returnPromise;
|
|
122
|
+
};
|
|
123
|
+
};
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const utils = require('../../../utils');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Handles the upload of attachments (images/videos) for a comment.
|
|
7
|
+
* @param {object} defaultFuncs - The default functions for making API requests.
|
|
8
|
+
* @param {object} ctx - The context object.
|
|
9
|
+
* @param {object} msg - The message object, containing attachments.
|
|
10
|
+
* @param {object} form - The main form object to be populated.
|
|
11
|
+
* @returns {Promise<void>}
|
|
12
|
+
*/
|
|
13
|
+
async function handleUpload(defaultFuncs, ctx, msg, form) {
|
|
14
|
+
if (!msg.attachments || msg.attachments.length === 0) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const uploads = msg.attachments.map(item => {
|
|
19
|
+
if (!utils.isReadableStream(item)) {
|
|
20
|
+
throw new Error('Attachments must be a readable stream.');
|
|
21
|
+
}
|
|
22
|
+
return defaultFuncs
|
|
23
|
+
.postFormData('https://www.facebook.com/ajax/ufi/upload/', ctx.jar, {
|
|
24
|
+
profile_id: ctx.userID,
|
|
25
|
+
source: 19,
|
|
26
|
+
target_id: ctx.userID,
|
|
27
|
+
file: item
|
|
28
|
+
})
|
|
29
|
+
.then(utils.parseAndCheckLogin(ctx, defaultFuncs))
|
|
30
|
+
.then(res => {
|
|
31
|
+
if (res.error || !res.payload?.fbid) {
|
|
32
|
+
throw res;
|
|
33
|
+
}
|
|
34
|
+
return { media: { id: res.payload.fbid } };
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const results = await Promise.all(uploads);
|
|
39
|
+
form.input.attachments.push(...results);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Handles a URL attachment for a comment.
|
|
44
|
+
* @param {object} msg - The message object.
|
|
45
|
+
* @param {object} form - The main form object.
|
|
46
|
+
*/
|
|
47
|
+
function handleURL(msg, form) {
|
|
48
|
+
if (typeof msg.url === 'string') {
|
|
49
|
+
form.input.attachments.push({
|
|
50
|
+
link: {
|
|
51
|
+
external: {
|
|
52
|
+
url: msg.url
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Handles mentions in the comment body.
|
|
61
|
+
* @param {object} msg - The message object.
|
|
62
|
+
* @param {object} form - The main form object.
|
|
63
|
+
*/
|
|
64
|
+
function handleMentions(msg, form) {
|
|
65
|
+
if (!msg.mentions) return;
|
|
66
|
+
|
|
67
|
+
for (const item of msg.mentions) {
|
|
68
|
+
const { tag, id, fromIndex } = item;
|
|
69
|
+
if (typeof tag !== 'string' || !id) {
|
|
70
|
+
utils.warn('createCommentPost', 'Mentions must have a string "tag" and an "id".');
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const offset = msg.body.indexOf(tag, fromIndex || 0);
|
|
74
|
+
if (offset < 0) {
|
|
75
|
+
utils.warn('createCommentPost', `Mention for "${tag}" not found in message string.`);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
form.input.message.ranges.push({
|
|
79
|
+
entity: { id },
|
|
80
|
+
length: tag.length,
|
|
81
|
+
offset
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Handles a sticker attachment for a comment.
|
|
88
|
+
* @param {object} msg - The message object.
|
|
89
|
+
* @param {object} form - The main form object.
|
|
90
|
+
*/
|
|
91
|
+
function handleSticker(msg, form) {
|
|
92
|
+
if (msg.sticker) {
|
|
93
|
+
form.input.attachments.push({
|
|
94
|
+
media: {
|
|
95
|
+
id: String(msg.sticker)
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Submits the final comment form to the GraphQL endpoint.
|
|
103
|
+
* @param {object} defaultFuncs - The default functions.
|
|
104
|
+
* @param {object} ctx - The context object.
|
|
105
|
+
* @param {object} form - The fully constructed form object.
|
|
106
|
+
* @returns {Promise<object>} A promise that resolves with the comment info.
|
|
107
|
+
*/
|
|
108
|
+
async function createContent(defaultFuncs, ctx, form) {
|
|
109
|
+
const res = await defaultFuncs
|
|
110
|
+
.post('https://www.facebook.com/api/graphql/', ctx.jar, {
|
|
111
|
+
fb_api_caller_class: 'RelayModern',
|
|
112
|
+
fb_api_req_friendly_name: 'useCometUFICreateCommentMutation',
|
|
113
|
+
variables: JSON.stringify(form),
|
|
114
|
+
server_timestamps: true,
|
|
115
|
+
doc_id: 6993516810709754
|
|
116
|
+
})
|
|
117
|
+
.then(utils.parseAndCheckLogin(ctx, defaultFuncs));
|
|
118
|
+
|
|
119
|
+
if (res.errors) {
|
|
120
|
+
throw res;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const commentEdge = res.data.comment_create.feedback_comment_edge;
|
|
124
|
+
return {
|
|
125
|
+
id: commentEdge.node.id,
|
|
126
|
+
url: commentEdge.node.feedback.url,
|
|
127
|
+
count: res.data.comment_create.feedback.total_comment_count
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Creates a comment on a Facebook post. Can also reply to an existing comment.
|
|
133
|
+
* @param {string|object} msg - The message to post. Can be a string or an object with `body`, `attachments`, `mentions`, etc.
|
|
134
|
+
* @param {string} postID - The ID of the post to comment on.
|
|
135
|
+
* @param {string} [replyCommentID] - (Optional) The ID of the comment to reply to.
|
|
136
|
+
* @param {function} [callback] - (Optional) A callback function.
|
|
137
|
+
* @returns {Promise<object>} A promise that resolves with the new comment's information.
|
|
138
|
+
*/
|
|
139
|
+
module.exports = function(defaultFuncs, api, ctx) {
|
|
140
|
+
return async function createCommentPost(msg, postID, replyCommentID, callback) {
|
|
141
|
+
let cb;
|
|
142
|
+
const returnPromise = new Promise((resolve, reject) => {
|
|
143
|
+
cb = (error, info) => info ? resolve(info) : reject(error);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (typeof replyCommentID === 'function') {
|
|
147
|
+
callback = replyCommentID;
|
|
148
|
+
replyCommentID = null;
|
|
149
|
+
}
|
|
150
|
+
if (typeof callback === 'function') {
|
|
151
|
+
cb = callback;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (typeof msg !== 'string' && typeof msg !== 'object') {
|
|
155
|
+
const error = 'Message must be a string or an object.';
|
|
156
|
+
utils.error('createCommentPost', error);
|
|
157
|
+
return cb(error);
|
|
158
|
+
}
|
|
159
|
+
if (typeof postID !== 'string') {
|
|
160
|
+
const error = 'postID must be a string.';
|
|
161
|
+
utils.error('createCommentPost', error);
|
|
162
|
+
return cb(error);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
let messageObject = typeof msg === 'string' ? { body: msg } : { ...msg };
|
|
166
|
+
messageObject.mentions = messageObject.mentions || [];
|
|
167
|
+
messageObject.attachments = messageObject.attachments || [];
|
|
168
|
+
|
|
169
|
+
const form = {
|
|
170
|
+
feedLocation: 'NEWSFEED',
|
|
171
|
+
feedbackSource: 1,
|
|
172
|
+
groupID: null,
|
|
173
|
+
input: {
|
|
174
|
+
client_mutation_id: Math.round(Math.random() * 19).toString(),
|
|
175
|
+
actor_id: ctx.userID,
|
|
176
|
+
attachments: [],
|
|
177
|
+
feedback_id: Buffer.from('feedback:' + postID).toString('base64'),
|
|
178
|
+
message: {
|
|
179
|
+
ranges: [],
|
|
180
|
+
text: messageObject.body || ''
|
|
181
|
+
},
|
|
182
|
+
reply_comment_parent_fbid: replyCommentID || null,
|
|
183
|
+
is_tracking_encrypted: true,
|
|
184
|
+
tracking: [],
|
|
185
|
+
feedback_source: 'NEWS_FEED',
|
|
186
|
+
idempotence_token: 'client:' + utils.getGUID(),
|
|
187
|
+
session_id: utils.getGUID()
|
|
188
|
+
},
|
|
189
|
+
scale: 1,
|
|
190
|
+
useDefaultActor: false
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
await handleUpload(defaultFuncs, ctx, messageObject, form);
|
|
195
|
+
handleURL(messageObject, form);
|
|
196
|
+
handleMentions(messageObject, form);
|
|
197
|
+
handleSticker(messageObject, form);
|
|
198
|
+
const info = await createContent(defaultFuncs, ctx, form);
|
|
199
|
+
cb(null, info);
|
|
200
|
+
} catch (err) {
|
|
201
|
+
utils.error('createCommentPost', err);
|
|
202
|
+
cb(err);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return returnPromise;
|
|
206
|
+
};
|
|
207
|
+
};
|