@lazyneoaz/nkxchat 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/LICENSE +3 -0
- package/README.md +199 -0
- package/examples/login-with-cookies.js +102 -0
- package/examples/verify.js +70 -0
- package/index.js +2 -0
- package/package.json +84 -0
- package/src/apis/addExternalModule.js +24 -0
- package/src/apis/addUserToGroup.js +108 -0
- package/src/apis/changeAdminStatus.js +148 -0
- package/src/apis/changeArchivedStatus.js +61 -0
- package/src/apis/changeAvatar.js +103 -0
- package/src/apis/changeBio.js +69 -0
- package/src/apis/changeBlockedStatus.js +54 -0
- package/src/apis/changeGroupImage.js +136 -0
- package/src/apis/changeThreadColor.js +116 -0
- package/src/apis/changeThreadEmoji.js +53 -0
- package/src/apis/comment.js +207 -0
- package/src/apis/createAITheme.js +129 -0
- package/src/apis/createNewGroup.js +79 -0
- package/src/apis/createPoll.js +73 -0
- package/src/apis/deleteMessage.js +44 -0
- package/src/apis/deleteThread.js +52 -0
- package/src/apis/editMessage.js +70 -0
- package/src/apis/emoji.js +124 -0
- package/src/apis/enableAutoSaveAppState.js +69 -0
- package/src/apis/fetchThemeData.js +113 -0
- package/src/apis/follow.js +81 -0
- package/src/apis/forwardAttachment.js +178 -0
- package/src/apis/forwardMessage.js +52 -0
- package/src/apis/friend.js +243 -0
- package/src/apis/gcmember.js +122 -0
- package/src/apis/gcname.js +123 -0
- package/src/apis/gcrule.js +119 -0
- package/src/apis/getAccess.js +111 -0
- package/src/apis/getBotInfo.js +88 -0
- package/src/apis/getBotInitialData.js +43 -0
- package/src/apis/getEmojiUrl.js +40 -0
- package/src/apis/getFriendsList.js +79 -0
- package/src/apis/getMessage.js +423 -0
- package/src/apis/getTheme.js +123 -0
- package/src/apis/getThemeInfo.js +116 -0
- package/src/apis/getThemePictures.js +87 -0
- package/src/apis/getThreadColors.js +119 -0
- package/src/apis/getThreadHistory.js +239 -0
- package/src/apis/getThreadInfo.js +267 -0
- package/src/apis/getThreadList.js +232 -0
- package/src/apis/getThreadPictures.js +58 -0
- package/src/apis/getUserID.js +117 -0
- package/src/apis/getUserInfo.js +513 -0
- package/src/apis/getUserInfoV2.js +146 -0
- package/src/apis/handleFriendRequest.js +66 -0
- package/src/apis/handleMessageRequest.js +50 -0
- package/src/apis/httpGet.js +63 -0
- package/src/apis/httpPost.js +89 -0
- package/src/apis/httpPostFormData.js +69 -0
- package/src/apis/listenMqtt.js +924 -0
- package/src/apis/listenSpeed.js +178 -0
- package/src/apis/logout.js +63 -0
- package/src/apis/markAsDelivered.js +47 -0
- package/src/apis/markAsRead.js +95 -0
- package/src/apis/markAsReadAll.js +40 -0
- package/src/apis/markAsSeen.js +70 -0
- package/src/apis/mqttDeltaValue.js +252 -0
- package/src/apis/muteThread.js +45 -0
- package/src/apis/nickname.js +132 -0
- package/src/apis/notes.js +163 -0
- package/src/apis/pinMessage.js +150 -0
- package/src/apis/produceMetaTheme.js +160 -0
- package/src/apis/realtime.js +182 -0
- package/src/apis/refreshFb_dtsg.js +94 -0
- package/src/apis/removeUserFromGroup.js +117 -0
- package/src/apis/resolvePhotoUrl.js +58 -0
- package/src/apis/searchForThread.js +154 -0
- package/src/apis/sendEffect.js +306 -0
- package/src/apis/sendMessage.js +353 -0
- package/src/apis/sendMessageMqtt.js +255 -0
- package/src/apis/sendTypingIndicator.js +40 -0
- package/src/apis/setMessageReaction.js +27 -0
- package/src/apis/setMessageReactionMqtt.js +61 -0
- package/src/apis/setPostReaction.js +118 -0
- package/src/apis/setThreadTheme.js +210 -0
- package/src/apis/setThreadThemeMqtt.js +94 -0
- package/src/apis/setTitle.js +26 -0
- package/src/apis/share.js +106 -0
- package/src/apis/shareContact.js +66 -0
- package/src/apis/stickers.js +257 -0
- package/src/apis/story.js +181 -0
- package/src/apis/theme.js +233 -0
- package/src/apis/unfriend.js +47 -0
- package/src/apis/unsendMessage.js +17 -0
- package/src/apis/uploadAttachment.js +87 -0
- package/src/database/appStateBackup.js +189 -0
- package/src/database/models/index.js +56 -0
- package/src/database/models/thread.js +31 -0
- package/src/database/models/user.js +32 -0
- package/src/database/threadData.js +101 -0
- package/src/database/userData.js +90 -0
- package/src/engine/client.js +92 -0
- package/src/engine/models/buildAPI.js +118 -0
- package/src/engine/models/loginHelper.js +492 -0
- package/src/engine/models/setOptions.js +88 -0
- package/src/types/index.d.ts +498 -0
- package/src/utils/antiSuspension.js +516 -0
- package/src/utils/auth-helpers.js +149 -0
- package/src/utils/autoReLogin.js +237 -0
- package/src/utils/axios.js +368 -0
- package/src/utils/cache.js +54 -0
- package/src/utils/clients.js +279 -0
- package/src/utils/constants.js +525 -0
- package/src/utils/formatters/data/formatAttachment.js +370 -0
- package/src/utils/formatters/data/formatDelta.js +109 -0
- package/src/utils/formatters/index.js +159 -0
- package/src/utils/formatters/value/formatCookie.js +91 -0
- package/src/utils/formatters/value/formatDate.js +36 -0
- package/src/utils/formatters/value/formatID.js +16 -0
- package/src/utils/formatters.js +1369 -0
- package/src/utils/headers.js +235 -0
- package/src/utils/index.js +152 -0
- package/src/utils/monitoring.js +333 -0
- package/src/utils/rateLimiter.js +251 -0
- package/src/utils/tokenRefresh.js +285 -0
- package/src/utils/user-agents.js +238 -0
- package/src/utils/validation.js +157 -0
|
@@ -0,0 +1,124 @@
|
|
|
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 custom emoji for a specific Facebook thread via MQTT.
|
|
10
|
+
*
|
|
11
|
+
* @param {string} emoji The emoji character to set as the custom emoji (e.g., "👍", "❤️").
|
|
12
|
+
* @param {string} threadID The ID of the thread where the emoji will be set.
|
|
13
|
+
* @param {Function} [callback] Optional callback function to be invoked upon completion.
|
|
14
|
+
* @param {string} [initiatorID] The ID of the user who initiated the emoji 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 emoji(emoji, 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 set an emoji."));
|
|
67
|
+
}
|
|
68
|
+
if (!emoji) {
|
|
69
|
+
return _callback(new Error("An emoji character is required."));
|
|
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
|
+
custom_emoji: emoji,
|
|
82
|
+
avatar_sticker_instruction_key_id: null,
|
|
83
|
+
sync_group: 1,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const query = {
|
|
87
|
+
failure_count: null,
|
|
88
|
+
label: '100003',
|
|
89
|
+
payload: JSON.stringify(queryPayload),
|
|
90
|
+
queue_name: 'thread_quick_reaction',
|
|
91
|
+
task_id: ctx.wsTaskNumber,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const context = {
|
|
95
|
+
app_id: ctx.appID,
|
|
96
|
+
payload: {
|
|
97
|
+
epoch_id: parseInt(utils.generateOfflineThreadingID()),
|
|
98
|
+
tasks: [query],
|
|
99
|
+
version_id: '24631415369801570',
|
|
100
|
+
},
|
|
101
|
+
request_id: ctx.wsReqNumber,
|
|
102
|
+
type: 3,
|
|
103
|
+
};
|
|
104
|
+
context.payload = JSON.stringify(context.payload);
|
|
105
|
+
|
|
106
|
+
ctx.mqttClient.publish('/ls_req', JSON.stringify(context), { qos: 1, retain: false }, (err) => {
|
|
107
|
+
if (err) {
|
|
108
|
+
return _callback(new Error(`MQTT publish failed for emoji: ${err.message || err}`));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const emojiChangeEvent = {
|
|
112
|
+
type: "thread_emoji_update",
|
|
113
|
+
threadID: threadID,
|
|
114
|
+
newEmoji: emoji,
|
|
115
|
+
senderID: _initiatorID,
|
|
116
|
+
BotID: ctx.userID,
|
|
117
|
+
timestamp: Date.now(),
|
|
118
|
+
};
|
|
119
|
+
_callback(null, emojiChangeEvent);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
return returnPromise;
|
|
123
|
+
};
|
|
124
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Enables automatic periodic saving of the current appState (session cookies)
|
|
8
|
+
* to a JSON file. Returns a `disable()` function to cancel auto-saving.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* const disable = api.enableAutoSaveAppState({ filePath: "./appstate.json" });
|
|
12
|
+
* // Later:
|
|
13
|
+
* disable();
|
|
14
|
+
*
|
|
15
|
+
* @param {object} [options]
|
|
16
|
+
* @param {string} [options.filePath="./appstate.json"] Path to save the appState JSON.
|
|
17
|
+
* @param {number} [options.interval=600000] Save interval in ms (default: 10 min).
|
|
18
|
+
* @param {boolean} [options.saveOnLogin=true] Save once immediately on call.
|
|
19
|
+
* @returns {Function} `disable()` — stops the auto-save timer.
|
|
20
|
+
*/
|
|
21
|
+
module.exports = function (defaultFuncs, api, ctx) {
|
|
22
|
+
return function enableAutoSaveAppState(options) {
|
|
23
|
+
options = options || {};
|
|
24
|
+
const filePath = options.filePath || path.join(process.cwd(), "appstate.json");
|
|
25
|
+
const interval = options.interval || 10 * 60 * 1000;
|
|
26
|
+
const saveOnLogin = options.saveOnLogin !== false;
|
|
27
|
+
|
|
28
|
+
function saveAppState() {
|
|
29
|
+
try {
|
|
30
|
+
const appState = api.getAppState ? api.getAppState() : null;
|
|
31
|
+
if (!appState || (Array.isArray(appState) && appState.length === 0)) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const payload = Array.isArray(appState) ? appState : (appState.appState || appState);
|
|
35
|
+
fs.writeFileSync(filePath, JSON.stringify(payload, null, 2), "utf8");
|
|
36
|
+
} catch (err) {
|
|
37
|
+
// Silently ignore write errors (e.g. read-only FS in some environments)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let immediateTimer = null;
|
|
42
|
+
if (saveOnLogin) {
|
|
43
|
+
immediateTimer = setTimeout(() => {
|
|
44
|
+
saveAppState();
|
|
45
|
+
immediateTimer = null;
|
|
46
|
+
}, 2000);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const timerId = setInterval(saveAppState, interval);
|
|
50
|
+
|
|
51
|
+
// Track timers on ctx so they can be cleaned up on logout
|
|
52
|
+
if (!ctx._autoSaveIntervals) ctx._autoSaveIntervals = [];
|
|
53
|
+
ctx._autoSaveIntervals.push(timerId);
|
|
54
|
+
|
|
55
|
+
function disable() {
|
|
56
|
+
if (immediateTimer) {
|
|
57
|
+
clearTimeout(immediateTimer);
|
|
58
|
+
immediateTimer = null;
|
|
59
|
+
}
|
|
60
|
+
clearInterval(timerId);
|
|
61
|
+
if (ctx._autoSaveIntervals) {
|
|
62
|
+
const i = ctx._autoSaveIntervals.indexOf(timerId);
|
|
63
|
+
if (i !== -1) ctx._autoSaveIntervals.splice(i, 1);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return disable;
|
|
68
|
+
};
|
|
69
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const utils = require("../utils");
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Fetches detailed color data for a single theme by its numeric ID.
|
|
7
|
+
* Uses the MWPThreadThemeProviderQuery GraphQL endpoint.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} themeID Numeric theme ID (e.g. "196241301102133")
|
|
10
|
+
* @param {Function} [callback]
|
|
11
|
+
* @returns {Promise<ThemeData>}
|
|
12
|
+
*/
|
|
13
|
+
module.exports = function (defaultFuncs, api, ctx) {
|
|
14
|
+
return function fetchThemeData(themeID, callback) {
|
|
15
|
+
let resolveFunc, rejectFunc;
|
|
16
|
+
const promise = new Promise((resolve, reject) => {
|
|
17
|
+
resolveFunc = resolve;
|
|
18
|
+
rejectFunc = reject;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const done = callback || function (err, data) {
|
|
22
|
+
if (err) return rejectFunc(err);
|
|
23
|
+
resolveFunc(data);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
if (!themeID) {
|
|
27
|
+
done(new Error("Theme ID is a required parameter"));
|
|
28
|
+
return promise;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const payload = {
|
|
32
|
+
av: ctx.userID,
|
|
33
|
+
__user: ctx.userID,
|
|
34
|
+
__a: 1,
|
|
35
|
+
__req: utils.getSignatureID(),
|
|
36
|
+
fb_dtsg: ctx.fb_dtsg,
|
|
37
|
+
lsd: ctx.lsd || ctx.fb_dtsg,
|
|
38
|
+
jazoest: ctx.jazoest,
|
|
39
|
+
fb_api_caller_class: "RelayModern",
|
|
40
|
+
fb_api_req_friendly_name: "MWPThreadThemeProviderQuery",
|
|
41
|
+
variables: JSON.stringify({ id: String(themeID) }),
|
|
42
|
+
server_timestamps: true,
|
|
43
|
+
doc_id: "9734829906576883",
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
defaultFuncs
|
|
47
|
+
.post("https://www.facebook.com/api/graphql/", ctx.jar, payload)
|
|
48
|
+
.then(utils.parseAndCheckLogin(ctx, defaultFuncs))
|
|
49
|
+
.then(async (res) => {
|
|
50
|
+
if (res.errors) throw new Error(JSON.stringify(res.errors));
|
|
51
|
+
|
|
52
|
+
const data = res && res.data && res.data.messenger_thread_theme;
|
|
53
|
+
if (!data) throw new Error("Theme data could not be located in the response");
|
|
54
|
+
|
|
55
|
+
const gradientColors = data.gradient_colors || (data.fallback_color ? [data.fallback_color] : []);
|
|
56
|
+
const bgGradient = data.background_gradient_colors || gradientColors;
|
|
57
|
+
const inboundGradient = data.inbound_message_gradient_colors || gradientColors;
|
|
58
|
+
|
|
59
|
+
const output = {
|
|
60
|
+
id: data.id,
|
|
61
|
+
name: data.accessibility_label || data.name || '',
|
|
62
|
+
description: data.description || '',
|
|
63
|
+
|
|
64
|
+
// ── Colors ─────────────────────────────────────────────
|
|
65
|
+
primary_color: gradientColors[0] || data.fallback_color || null,
|
|
66
|
+
fallback_color: data.fallback_color || null,
|
|
67
|
+
gradient_colors: gradientColors,
|
|
68
|
+
background_gradient_colors: bgGradient,
|
|
69
|
+
inbound_message_gradient_colors: inboundGradient,
|
|
70
|
+
|
|
71
|
+
message_text_color: data.message_text_color || null,
|
|
72
|
+
inbound_message_text_color: data.inbound_message_text_color || null,
|
|
73
|
+
composer_background_color: data.composer_background_color || null,
|
|
74
|
+
composer_input_background_color:data.composer_input_background_color|| null,
|
|
75
|
+
composer_tint_color: data.composer_tint_color || null,
|
|
76
|
+
title_bar_background_color: data.title_bar_background_color || null,
|
|
77
|
+
title_bar_text_color: data.title_bar_text_color || null,
|
|
78
|
+
title_bar_button_tint_color: data.title_bar_button_tint_color || null,
|
|
79
|
+
title_bar_attribution_color: data.title_bar_attribution_color || null,
|
|
80
|
+
primary_button_background_color:data.primary_button_background_color|| null,
|
|
81
|
+
hot_like_color: data.hot_like_color || null,
|
|
82
|
+
reaction_pill_background_color: data.reaction_pill_background_color || null,
|
|
83
|
+
secondary_text_color: data.secondary_text_color || null,
|
|
84
|
+
tertiary_text_color: data.tertiary_text_color || null,
|
|
85
|
+
app_color_mode: data.app_color_mode || null,
|
|
86
|
+
|
|
87
|
+
// ── Assets ─────────────────────────────────────────────
|
|
88
|
+
backgroundImage: data.background_asset ? (data.background_asset.image || {}).uri || null : null,
|
|
89
|
+
iconImage: data.icon_asset ? (data.icon_asset.image || {}).uri || null : null,
|
|
90
|
+
|
|
91
|
+
// ── Alternatives (dark mode, etc.) ──────────────────────
|
|
92
|
+
alternative_themes: Array.isArray(data.alternative_themes)
|
|
93
|
+
? data.alternative_themes.map(a => ({
|
|
94
|
+
id: a.id,
|
|
95
|
+
name: a.accessibility_label || a.name || '',
|
|
96
|
+
backgroundImage: a.background_asset ? (a.background_asset.image || {}).uri || null : null,
|
|
97
|
+
iconImage: a.icon_asset ? (a.icon_asset.image || {}).uri || null : null,
|
|
98
|
+
gradient_colors: a.gradient_colors || [],
|
|
99
|
+
fallback_color: a.fallback_color || null,
|
|
100
|
+
}))
|
|
101
|
+
: [],
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
done(null, output);
|
|
105
|
+
})
|
|
106
|
+
.catch((err) => {
|
|
107
|
+
utils.error("fetchThemeData", err.message || err);
|
|
108
|
+
done(err instanceof Error ? err : new Error(String(err.message || err)));
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return promise;
|
|
112
|
+
};
|
|
113
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Author @YanMaglinte
|
|
5
|
+
* https://github.com/YANDEVA
|
|
6
|
+
* * Example:
|
|
7
|
+
* api.follow("100090794779367", true);
|
|
8
|
+
* jsdocs @ChoruOfficial
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {object} defaultFuncs The default functions for making API requests.
|
|
13
|
+
* @param {object} api The full API object.
|
|
14
|
+
* @param {object} ctx The context object.
|
|
15
|
+
* @returns {function(senderID: string, boolean: boolean, callback?: (err: any, data?: any) => void): void} The follow function.
|
|
16
|
+
*/
|
|
17
|
+
module.exports = function (defaultFuncs, api, ctx) {
|
|
18
|
+
/**
|
|
19
|
+
* Follows or unfollows a user on Facebook.
|
|
20
|
+
* @param {string} senderID The ID of the user to follow or unfollow.
|
|
21
|
+
* @param {boolean} boolean Set to `true` to follow the user, `false` to unfollow.
|
|
22
|
+
* @param {(err: any, data?: any) => void} [callback] An optional callback function.
|
|
23
|
+
*/
|
|
24
|
+
return function follow(senderID, boolean, callback) {
|
|
25
|
+
let form;
|
|
26
|
+
if (boolean) {
|
|
27
|
+
form = {
|
|
28
|
+
av: ctx.userID,
|
|
29
|
+
fb_api_req_friendly_name: "CometUserFollowMutation",
|
|
30
|
+
fb_api_caller_class: "RelayModern",
|
|
31
|
+
doc_id: "25472099855769847",
|
|
32
|
+
variables: JSON.stringify({
|
|
33
|
+
input: {
|
|
34
|
+
attribution_id_v2:
|
|
35
|
+
"ProfileCometTimelineListViewRoot.react,comet.profile.timeline.list,via_cold_start,1717249218695,723451,250100865708545,,",
|
|
36
|
+
is_tracking_encrypted: true,
|
|
37
|
+
subscribe_location: "PROFILE",
|
|
38
|
+
subscribee_id: senderID,
|
|
39
|
+
tracking: null,
|
|
40
|
+
actor_id: ctx.userID,
|
|
41
|
+
client_mutation_id: "1",
|
|
42
|
+
},
|
|
43
|
+
scale: 1,
|
|
44
|
+
}),
|
|
45
|
+
};
|
|
46
|
+
} else {
|
|
47
|
+
form = {
|
|
48
|
+
av: ctx.userID,
|
|
49
|
+
fb_api_req_friendly_name: "CometUserUnfollowMutation",
|
|
50
|
+
fb_api_caller_class: "RelayModern",
|
|
51
|
+
doc_id: "25472099855769847",
|
|
52
|
+
variables: JSON.stringify({
|
|
53
|
+
action_render_location: "WWW_COMET_FRIEND_MENU",
|
|
54
|
+
input: {
|
|
55
|
+
attribution_id_v2:
|
|
56
|
+
"ProfileCometTimelineListViewRoot.react,comet.profile.timeline.list,tap_search_bar,1717294006136,602597,250100865708545,,",
|
|
57
|
+
is_tracking_encrypted: true,
|
|
58
|
+
subscribe_location: "PROFILE",
|
|
59
|
+
tracking: null,
|
|
60
|
+
unsubscribee_id: senderID,
|
|
61
|
+
actor_id: ctx.userID,
|
|
62
|
+
client_mutation_id: "10",
|
|
63
|
+
},
|
|
64
|
+
scale: 1,
|
|
65
|
+
}),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
api.httpPost("https://www.facebook.com/api/graphql/", form, (err, data) => {
|
|
70
|
+
if (err) {
|
|
71
|
+
if (typeof callback === "function") {
|
|
72
|
+
callback(err);
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
if (typeof callback === "function") {
|
|
76
|
+
callback(null, data);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const utils = require("../utils");
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Forwards one or more attachment IDs to a thread via MQTT.
|
|
7
|
+
*
|
|
8
|
+
* Attachment IDs can be obtained from api.uploadAttachment() or from the
|
|
9
|
+
* attachments field of a received message event.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* // Forward a photo attachment to another thread
|
|
13
|
+
* await api.forwardAttachment(["1234567890"], targetThreadID);
|
|
14
|
+
*
|
|
15
|
+
* @param {string|string[]} attachmentIDs One or more Facebook attachment IDs.
|
|
16
|
+
* @param {string} threadID Target thread to send the attachment(s) to.
|
|
17
|
+
* @param {string} [body] Optional text body to accompany the attachment.
|
|
18
|
+
* @param {Function} [callback]
|
|
19
|
+
* @returns {Promise<{ messageID, threadID }>}
|
|
20
|
+
*/
|
|
21
|
+
module.exports = function (defaultFuncs, api, ctx) {
|
|
22
|
+
return function forwardAttachment(attachmentIDs, threadID, body, callback) {
|
|
23
|
+
// Allow omitting body
|
|
24
|
+
if (typeof body === "function") {
|
|
25
|
+
callback = body;
|
|
26
|
+
body = "";
|
|
27
|
+
}
|
|
28
|
+
body = body || "";
|
|
29
|
+
|
|
30
|
+
let resolveFunc, rejectFunc;
|
|
31
|
+
const promise = new Promise((resolve, reject) => {
|
|
32
|
+
resolveFunc = resolve;
|
|
33
|
+
rejectFunc = reject;
|
|
34
|
+
});
|
|
35
|
+
if (!callback) {
|
|
36
|
+
callback = (err, data) => { if (err) return rejectFunc(err); resolveFunc(data); };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!attachmentIDs) return callback(new Error("attachmentIDs is required"));
|
|
40
|
+
if (!threadID) return callback(new Error("threadID is required"));
|
|
41
|
+
|
|
42
|
+
const ids = Array.isArray(attachmentIDs) ? attachmentIDs : [attachmentIDs];
|
|
43
|
+
|
|
44
|
+
// ── Try MQTT first (preferred) ────────────────────────────────────────
|
|
45
|
+
if (ctx.mqttClient) {
|
|
46
|
+
const otid = utils.generateOfflineThreadingID();
|
|
47
|
+
const epoch_id = utils.generateOfflineThreadingID();
|
|
48
|
+
const timestamp = Date.now();
|
|
49
|
+
const tid = String(threadID);
|
|
50
|
+
const reqID = ++ctx.wsReqNumber;
|
|
51
|
+
|
|
52
|
+
const sendPayload = {
|
|
53
|
+
thread_id: tid,
|
|
54
|
+
otid: otid.toString(),
|
|
55
|
+
source: 2097153,
|
|
56
|
+
send_type: 3, // attachment
|
|
57
|
+
sync_group: 1,
|
|
58
|
+
mark_thread_read: 1,
|
|
59
|
+
text: body || null,
|
|
60
|
+
initiating_source: 0,
|
|
61
|
+
skip_url_preview_gen: 0,
|
|
62
|
+
attachment_fbids: ids,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const content = {
|
|
66
|
+
app_id: "2220391788200892",
|
|
67
|
+
payload: JSON.stringify({
|
|
68
|
+
tasks: [{
|
|
69
|
+
label: "46",
|
|
70
|
+
payload: JSON.stringify(sendPayload),
|
|
71
|
+
queue_name: tid,
|
|
72
|
+
task_id: ++ctx.wsTaskNumber,
|
|
73
|
+
failure_count: null,
|
|
74
|
+
}],
|
|
75
|
+
epoch_id,
|
|
76
|
+
version_id: "6120284488008082",
|
|
77
|
+
data_trace_id: null,
|
|
78
|
+
}),
|
|
79
|
+
request_id: reqID,
|
|
80
|
+
type: 3,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
let done = false;
|
|
84
|
+
const timer = setTimeout(() => {
|
|
85
|
+
if (done) return;
|
|
86
|
+
done = true;
|
|
87
|
+
ctx.mqttClient.removeListener("message", onMsg);
|
|
88
|
+
callback({ error: "Timeout waiting for MQTT ACK" });
|
|
89
|
+
}, 15000);
|
|
90
|
+
|
|
91
|
+
const onMsg = (topic, raw) => {
|
|
92
|
+
if (topic !== "/ls_resp") return;
|
|
93
|
+
let parsed;
|
|
94
|
+
try { parsed = JSON.parse(raw.toString()); parsed.payload = JSON.parse(parsed.payload); } catch { return; }
|
|
95
|
+
if (parsed.request_id !== reqID || done) return;
|
|
96
|
+
done = true;
|
|
97
|
+
clearTimeout(timer);
|
|
98
|
+
ctx.mqttClient.removeListener("message", onMsg);
|
|
99
|
+
callback(null, { threadID, messageID: otid.toString(), timestamp, method: "mqtt" });
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
ctx.mqttClient.on("message", onMsg);
|
|
103
|
+
ctx.mqttClient.publish("/ls_req", JSON.stringify(content), { qos: 1, retain: false }, (err) => {
|
|
104
|
+
if (err && !done) {
|
|
105
|
+
done = true;
|
|
106
|
+
clearTimeout(timer);
|
|
107
|
+
ctx.mqttClient.removeListener("message", onMsg);
|
|
108
|
+
callback(err);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
return promise;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ── HTTP fallback ─────────────────────────────────────────────────────
|
|
116
|
+
const messageAndOTID = utils.generateOfflineThreadingID();
|
|
117
|
+
const form = {
|
|
118
|
+
client: "mercury",
|
|
119
|
+
action_type: "ma-type:user-generated-message",
|
|
120
|
+
author: "fbid:" + ctx.userID,
|
|
121
|
+
timestamp: Date.now(),
|
|
122
|
+
timestamp_absolute: "Today",
|
|
123
|
+
timestamp_relative: utils.generateTimestampRelative(),
|
|
124
|
+
timestamp_time_passed:"0",
|
|
125
|
+
is_unread: false,
|
|
126
|
+
is_cleared: false,
|
|
127
|
+
is_forward: true,
|
|
128
|
+
is_filtered_content: false,
|
|
129
|
+
is_filtered_content_bh: false,
|
|
130
|
+
is_filtered_content_account: false,
|
|
131
|
+
is_filtered_content_quasar: false,
|
|
132
|
+
is_filtered_content_invalid_app: false,
|
|
133
|
+
is_spoof_warning: false,
|
|
134
|
+
source: "source:chat:web",
|
|
135
|
+
"source_tags[0]": "source:chat",
|
|
136
|
+
body: body,
|
|
137
|
+
html_body: false,
|
|
138
|
+
ui_push_phase: "V3",
|
|
139
|
+
status: "0",
|
|
140
|
+
offline_threading_id: messageAndOTID,
|
|
141
|
+
message_id: messageAndOTID,
|
|
142
|
+
threading_id: utils.generateThreadingID(ctx.clientID),
|
|
143
|
+
"ephemeral_ttl_mode:":"0",
|
|
144
|
+
manual_retry_cnt: "0",
|
|
145
|
+
has_attachment: true,
|
|
146
|
+
signatureID: utils.getSignatureID(),
|
|
147
|
+
thread_fbid: String(threadID),
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// Add attachment IDs to form
|
|
151
|
+
ids.forEach((id, i) => {
|
|
152
|
+
form[`image_ids[${i}]`] = id;
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
defaultFuncs
|
|
156
|
+
.post("https://www.facebook.com/messaging/send/", ctx.jar, form, { ...ctx, requestThreadID: String(threadID) })
|
|
157
|
+
.then(utils.parseAndCheckLogin(ctx, defaultFuncs))
|
|
158
|
+
.then((resData) => {
|
|
159
|
+
if (!resData) throw new Error("Empty response from messaging/send");
|
|
160
|
+
if (resData.error) throw new Error(JSON.stringify(resData));
|
|
161
|
+
|
|
162
|
+
const actions = (resData.payload && resData.payload.actions) || [];
|
|
163
|
+
const msgInfo = actions.reduce((p, v) => ({
|
|
164
|
+
threadID: v.thread_fbid || p.threadID,
|
|
165
|
+
messageID: v.message_id || p.messageID,
|
|
166
|
+
timestamp: v.timestamp || p.timestamp,
|
|
167
|
+
}), { threadID, messageID: messageAndOTID, timestamp: Date.now() });
|
|
168
|
+
|
|
169
|
+
callback(null, { ...msgInfo, method: "http" });
|
|
170
|
+
})
|
|
171
|
+
.catch((err) => {
|
|
172
|
+
utils.error("forwardAttachment", err.message || err);
|
|
173
|
+
callback(err instanceof Error ? err : new Error(String(err.message || err)));
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
return promise;
|
|
177
|
+
};
|
|
178
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const utils = require('../utils');
|
|
4
|
+
|
|
5
|
+
module.exports = (defaultFuncs, api, ctx) => {
|
|
6
|
+
return async function forwardMessage(messageID, threadIDs, callback) {
|
|
7
|
+
let resolveFunc = () => {};
|
|
8
|
+
let rejectFunc = () => {};
|
|
9
|
+
const returnPromise = new Promise((resolve, reject) => {
|
|
10
|
+
resolveFunc = resolve;
|
|
11
|
+
rejectFunc = reject;
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
if (!callback) {
|
|
15
|
+
callback = (err, result) => {
|
|
16
|
+
if (err) return rejectFunc(err);
|
|
17
|
+
resolveFunc(result);
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
if (!Array.isArray(threadIDs)) {
|
|
23
|
+
threadIDs = [threadIDs];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const form = {
|
|
27
|
+
message_id: messageID
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
threadIDs.forEach(id => {
|
|
31
|
+
form[`recipient_ids[${id}]`] = id;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const res = await defaultFuncs.post(
|
|
35
|
+
"https://www.facebook.com/ajax/mercury/forward_message.php",
|
|
36
|
+
ctx.jar,
|
|
37
|
+
form
|
|
38
|
+
).then(utils.parseAndCheckLogin(ctx, defaultFuncs));
|
|
39
|
+
|
|
40
|
+
if (res && res.error) {
|
|
41
|
+
throw res;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
callback(null, { success: true, forwardedTo: threadIDs });
|
|
45
|
+
} catch (err) {
|
|
46
|
+
utils.error("forwardMessage", err);
|
|
47
|
+
callback(err);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return returnPromise;
|
|
51
|
+
};
|
|
52
|
+
};
|