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.
@@ -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
+ };