@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,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const utils = require('../utils');
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const _ = require("lodash");
|
|
7
|
+
const deepdash = require("deepdash");
|
|
8
|
+
const { JSONPath } = require("jsonpath-plus");
|
|
9
|
+
|
|
10
|
+
deepdash(_);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Extract and save the full lightspeed_web_request object from thread data.
|
|
14
|
+
* Also search for specific pinned message commands.
|
|
15
|
+
* @param {Object} allJsonData - Raw JSON response from Facebook.
|
|
16
|
+
* @param {Object} options - Options for debug output.
|
|
17
|
+
* @returns {Object|null} The full lightspeed_web_request object or null.
|
|
18
|
+
*/
|
|
19
|
+
function extractAndSearchLightspeedRequest(allJsonData, options = {}) {
|
|
20
|
+
const { saveDebugFile = false, debugFilePath } = options;
|
|
21
|
+
|
|
22
|
+
let outputFile = null;
|
|
23
|
+
if (saveDebugFile && debugFilePath) {
|
|
24
|
+
outputFile = debugFilePath;
|
|
25
|
+
} else if (saveDebugFile) {
|
|
26
|
+
// Only save to current working directory if explicitly requested
|
|
27
|
+
outputFile = require('path').join(process.cwd(), "fca_neo_pro_lightspeed_debug.json");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const lightReq = _.get(allJsonData, "__bbox.result.data.viewer.lightspeed_web_request");
|
|
31
|
+
if (!lightReq) {
|
|
32
|
+
utils.warn("pin.js: lightspeed_web_request not found.");
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
fs.writeFileSync(outputFile, JSON.stringify(lightReq, null, 2), "utf8");
|
|
39
|
+
utils.log(`pin.js: Saved lightspeed_web_request to ${outputFile}`);
|
|
40
|
+
} catch (err) {
|
|
41
|
+
utils.error("pin.js: Failed to write lightspeed_web_request.json", err);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const matches = JSONPath({
|
|
47
|
+
path: `$..[?(@ === "setPinnedMessage" || @ === "deleteThenInsertMessage")]`,
|
|
48
|
+
json: lightReq
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
utils.log(`pin.js: Found ${matches.length} matching command(s).`);
|
|
52
|
+
matches.forEach((match, idx) => {
|
|
53
|
+
utils.log(`📌 Match ${idx + 1}:`, match);
|
|
54
|
+
});
|
|
55
|
+
} catch (err) {
|
|
56
|
+
utils.error("pin.js: JSONPath search failed.", err);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return lightReq;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = function (defaultFuncs, api, ctx) {
|
|
63
|
+
return async function pin(action, threadID, messageID) {
|
|
64
|
+
if (action === "list") {
|
|
65
|
+
if (!threadID) throw new Error('Action "list" requires threadID.');
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const url = `https://www.facebook.com/messages/t/${threadID}/`;
|
|
69
|
+
const allJsonData = await utils.json(url, ctx.jar, null, ctx.globalOptions, ctx);
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
const lightReq = extractAndSearchLightspeedRequest(allJsonData, { saveDebugFile: false });
|
|
73
|
+
if (!lightReq || !lightReq.pin_status) {
|
|
74
|
+
utils.warn("pin.js: No pinned messages found or pin_status missing.");
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
return lightReq;
|
|
78
|
+
} catch (err) {
|
|
79
|
+
utils.error(`pin.js: Failed to process "list" for thread ${threadID}`, err);
|
|
80
|
+
throw err;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!ctx.mqttClient) throw new Error("MQTT not connected.");
|
|
85
|
+
if (!threadID || !messageID) throw new Error(`"${action}" requires threadID and messageID.`);
|
|
86
|
+
|
|
87
|
+
const epoch_id = parseInt(utils.generateOfflineThreadingID());
|
|
88
|
+
const version_id = "9523201934447612";
|
|
89
|
+
const app_id = "2220391788200892";
|
|
90
|
+
|
|
91
|
+
const createMqttRequest = (tasks, increment = 0) => ({
|
|
92
|
+
app_id,
|
|
93
|
+
payload: JSON.stringify({ epoch_id: epoch_id + increment, tasks, version_id }),
|
|
94
|
+
request_id: (ctx.wsReqNumber = (ctx.wsReqNumber || 0) + 1),
|
|
95
|
+
type: 3
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const publishMqtt = (content) =>
|
|
99
|
+
new Promise((resolve, reject) => {
|
|
100
|
+
ctx.mqttClient.publish("/ls_req", JSON.stringify(content), { qos: 1, retain: false }, (err) => {
|
|
101
|
+
if (err) reject(err);
|
|
102
|
+
else resolve({ success: true, request_id: content.request_id });
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (action === "pin") {
|
|
107
|
+
const pinTask = {
|
|
108
|
+
label: "430",
|
|
109
|
+
payload: JSON.stringify({ thread_key: threadID, message_id: messageID, timestamp_ms: Date.now() }),
|
|
110
|
+
queue_name: `pin_msg_v2_${threadID}`,
|
|
111
|
+
task_id: (ctx.wsTaskNumber = (ctx.wsTaskNumber || 0) + 1)
|
|
112
|
+
};
|
|
113
|
+
const setSearchTask = {
|
|
114
|
+
label: "751",
|
|
115
|
+
payload: JSON.stringify({ thread_key: threadID, message_id: messageID, pinned_message_state: 1 }),
|
|
116
|
+
queue_name: "set_pinned_message_search",
|
|
117
|
+
task_id: (ctx.wsTaskNumber = (ctx.wsTaskNumber || 0) + 1)
|
|
118
|
+
};
|
|
119
|
+
const req1 = createMqttRequest([pinTask], 0);
|
|
120
|
+
const req2 = createMqttRequest([setSearchTask], 1);
|
|
121
|
+
return Promise.all([publishMqtt(req1), publishMqtt(req2)]);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (action === "unpin") {
|
|
125
|
+
const setSearchTask1 = {
|
|
126
|
+
label: "751",
|
|
127
|
+
payload: JSON.stringify({ thread_key: threadID, message_id: messageID, pinned_message_state: 0 }),
|
|
128
|
+
queue_name: "set_pinned_message_search",
|
|
129
|
+
task_id: (ctx.wsTaskNumber = (ctx.wsTaskNumber || 0) + 1)
|
|
130
|
+
};
|
|
131
|
+
const unpinTask = {
|
|
132
|
+
label: "431",
|
|
133
|
+
payload: JSON.stringify({ thread_key: threadID, message_id: messageID, timestamp_ms: Date.now() }),
|
|
134
|
+
queue_name: `unpin_msg_v2_${threadID}`,
|
|
135
|
+
task_id: (ctx.wsTaskNumber = (ctx.wsTaskNumber || 0) + 1)
|
|
136
|
+
};
|
|
137
|
+
const setSearchTask2 = {
|
|
138
|
+
label: "751",
|
|
139
|
+
payload: JSON.stringify({ thread_key: threadID, message_id: messageID, pinned_message_state: 0 }),
|
|
140
|
+
queue_name: "set_pinned_message_search",
|
|
141
|
+
task_id: (ctx.wsTaskNumber = (ctx.wsTaskNumber || 0) + 1)
|
|
142
|
+
};
|
|
143
|
+
await publishMqtt(createMqttRequest([setSearchTask1], 0));
|
|
144
|
+
await publishMqtt(createMqttRequest([unpinTask], 1));
|
|
145
|
+
return publishMqtt(createMqttRequest([setSearchTask2], 2));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
throw new Error(`Invalid action: "${action}". Use "pin", "unpin", or "list".`);
|
|
149
|
+
};
|
|
150
|
+
};
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const utils = require("../utils");
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generates custom Meta AI chat themes from a text prompt.
|
|
7
|
+
* Wraps the `useGenerateAIThemeMutation` GraphQL mutation.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} prompt Natural-language description of the desired theme
|
|
10
|
+
* @param {object} [opts] { numThemes: 1-5, imageUrl? }
|
|
11
|
+
* @param {Function}[callback]
|
|
12
|
+
* @returns {Promise}
|
|
13
|
+
*/
|
|
14
|
+
module.exports = function (defaultFuncs, api, ctx) {
|
|
15
|
+
return function produceMetaTheme(prompt, opts, callback) {
|
|
16
|
+
let resolveFunc, rejectFunc;
|
|
17
|
+
const promise = new Promise((resolve, reject) => {
|
|
18
|
+
resolveFunc = resolve;
|
|
19
|
+
rejectFunc = reject;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
if (typeof opts === "function") {
|
|
23
|
+
callback = opts;
|
|
24
|
+
opts = {};
|
|
25
|
+
}
|
|
26
|
+
opts = opts || {};
|
|
27
|
+
if (typeof callback !== "function") {
|
|
28
|
+
callback = (err, data) => {
|
|
29
|
+
if (err) return rejectFunc(err);
|
|
30
|
+
resolveFunc(data);
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!prompt || typeof prompt !== "string") {
|
|
35
|
+
callback(new Error("Prompt is required and must be a string"));
|
|
36
|
+
return promise;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
|
|
40
|
+
const randId = () => Math.floor(Math.random() * 10).toString();
|
|
41
|
+
|
|
42
|
+
const makeInput = () => {
|
|
43
|
+
const desired = ("numThemes" in opts) ? Number(opts.numThemes) : 1;
|
|
44
|
+
const safeCount = clamp(Number.isFinite(desired) ? desired : 1, 1, 5);
|
|
45
|
+
const body = {
|
|
46
|
+
client_mutation_id: randId(),
|
|
47
|
+
actor_id: ctx.userID,
|
|
48
|
+
bypass_cache: true,
|
|
49
|
+
caller: "MESSENGER",
|
|
50
|
+
num_themes: safeCount,
|
|
51
|
+
prompt,
|
|
52
|
+
};
|
|
53
|
+
if (opts.imageUrl) body.image_url = opts.imageUrl;
|
|
54
|
+
return body;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const constructForm = (input) => ({
|
|
58
|
+
av: ctx.userID,
|
|
59
|
+
__user: ctx.userID,
|
|
60
|
+
__a: 1,
|
|
61
|
+
__req: utils.getSignatureID(),
|
|
62
|
+
dpr: 1,
|
|
63
|
+
fb_dtsg: ctx.fb_dtsg,
|
|
64
|
+
jazoest: ctx.jazoest,
|
|
65
|
+
lsd: ctx.lsd || ctx.fb_dtsg,
|
|
66
|
+
fb_api_caller_class: "RelayModern",
|
|
67
|
+
fb_api_req_friendly_name: "useGenerateAIThemeMutation",
|
|
68
|
+
variables: JSON.stringify({ input }),
|
|
69
|
+
server_timestamps: true,
|
|
70
|
+
doc_id: "23873748445608673",
|
|
71
|
+
fb_api_analytics_tags: JSON.stringify(["qpl_active_flow_ids=25309433,521485406"]),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const normalizeTheme = (t, idx) => ({
|
|
75
|
+
success: true,
|
|
76
|
+
themeId: t.id,
|
|
77
|
+
name: t.accessibility_label || t.name || '',
|
|
78
|
+
description: t.description || '',
|
|
79
|
+
serialNumber: idx + 1,
|
|
80
|
+
colors: {
|
|
81
|
+
primary: (t.gradient_colors || [])[0] || t.fallback_color || null,
|
|
82
|
+
fallback: t.fallback_color,
|
|
83
|
+
gradient: t.gradient_colors || [],
|
|
84
|
+
backgroundGradient: t.background_gradient_colors || t.gradient_colors || [],
|
|
85
|
+
composerBackground: t.composer_background_color,
|
|
86
|
+
composerTint: t.composer_tint_color,
|
|
87
|
+
titleBarBackground: t.title_bar_background_color,
|
|
88
|
+
titleBarText: t.title_bar_text_color,
|
|
89
|
+
titleBarButton: t.title_bar_button_tint_color,
|
|
90
|
+
messageText: t.message_text_color,
|
|
91
|
+
inboundGradient: t.inbound_message_gradient_colors || t.gradient_colors || [],
|
|
92
|
+
primaryButton: t.primary_button_background_color,
|
|
93
|
+
hotLike: t.hot_like_color,
|
|
94
|
+
},
|
|
95
|
+
backgroundImage: t.background_asset ? (t.background_asset.image || {}).uri || null : null,
|
|
96
|
+
iconImage: t.icon_asset ? (t.icon_asset.image || {}).uri || null : null,
|
|
97
|
+
alternativeThemes: Array.isArray(t.alternative_themes)
|
|
98
|
+
? t.alternative_themes.map(a => ({
|
|
99
|
+
id: a.id,
|
|
100
|
+
name: a.accessibility_label || a.name || '',
|
|
101
|
+
backgroundImage: a.background_asset ? (a.background_asset.image || {}).uri || null : null,
|
|
102
|
+
iconImage: a.icon_asset ? (a.icon_asset.image || {}).uri || null : null,
|
|
103
|
+
gradient_colors: a.gradient_colors || [],
|
|
104
|
+
fallback_color: a.fallback_color || null,
|
|
105
|
+
}))
|
|
106
|
+
: [],
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const KNOWN_ERRORS = [
|
|
110
|
+
{ test: (e) => /not authorized/i.test(e && e.message || ''), msg: "This account doesn't have permission to create AI themes." },
|
|
111
|
+
{ test: (e) => /rate limit/i.test(e && e.message || ''), msg: "Too many requests. Please wait before retrying." },
|
|
112
|
+
{ test: (e) => /invalid/i.test(e && e.message || ''), msg: "Invalid request parameters. Please review your input." },
|
|
113
|
+
{ test: (e) => !!(e && e.statusCode === 403), msg: "Access forbidden. Your account may not support AI theme generation." },
|
|
114
|
+
{ test: (e) => !!(e && e.statusCode === 429), msg: "Rate limit reached. Please wait before retrying." },
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
const friendlyError = (err) => {
|
|
118
|
+
for (const { test, msg } of KNOWN_ERRORS) {
|
|
119
|
+
if (test(err)) return msg;
|
|
120
|
+
}
|
|
121
|
+
return "Something went wrong while generating your theme.";
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
(async function run() {
|
|
125
|
+
try {
|
|
126
|
+
const formData = constructForm(makeInput());
|
|
127
|
+
const raw = await defaultFuncs.post("https://www.facebook.com/api/graphql/", ctx.jar, formData);
|
|
128
|
+
const checked = await utils.parseAndCheckLogin(ctx, defaultFuncs)(raw);
|
|
129
|
+
|
|
130
|
+
if (checked.errors) throw checked.errors;
|
|
131
|
+
|
|
132
|
+
const payload = checked && checked.data && checked.data.xfb_generate_ai_themes_from_prompt;
|
|
133
|
+
if (!payload) throw new Error("Invalid response from AI theme generation");
|
|
134
|
+
|
|
135
|
+
if (!payload.success || !Array.isArray(payload.themes) || payload.themes.length === 0) {
|
|
136
|
+
throw new Error("No themes generated for the given prompt");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const normalized = payload.themes.map(normalizeTheme);
|
|
140
|
+
const out = {
|
|
141
|
+
success: true,
|
|
142
|
+
count: normalized.length,
|
|
143
|
+
themes: normalized,
|
|
144
|
+
...normalized[0],
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
callback(null, out);
|
|
148
|
+
} catch (err) {
|
|
149
|
+
utils.error("produceMetaTheme", err.message || err);
|
|
150
|
+
callback({
|
|
151
|
+
error: friendlyError(err),
|
|
152
|
+
originalError: (err && (err.message || err)) || String(err),
|
|
153
|
+
statusCode: err && err.statusCode ? err.statusCode : null,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
})();
|
|
157
|
+
|
|
158
|
+
return promise;
|
|
159
|
+
};
|
|
160
|
+
};
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { WebSocket } = require("undici");
|
|
4
|
+
const EventEmitter = require("events");
|
|
5
|
+
const utils = require('../utils');
|
|
6
|
+
const HttpsProxyAgent = require("https-proxy-agent");
|
|
7
|
+
|
|
8
|
+
function formatNotification(data) {
|
|
9
|
+
if (!data.data || !data.data.viewer) return null;
|
|
10
|
+
const notifEdge = data.data.viewer.notifications_page?.edges?.[1]?.node?.notif;
|
|
11
|
+
if (!notifEdge) return null;
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
type: "notification",
|
|
15
|
+
notifID: notifEdge.notif_id,
|
|
16
|
+
body: notifEdge.body?.text,
|
|
17
|
+
senderID: Object.keys(notifEdge.tracking.from_uids || {})[0],
|
|
18
|
+
url: notifEdge.url,
|
|
19
|
+
timestamp: notifEdge.creation_time.timestamp,
|
|
20
|
+
seenState: notifEdge.seen_state,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = function (defaultFuncs, api, ctx) {
|
|
25
|
+
return function listenRealtime() {
|
|
26
|
+
const emitter = new EventEmitter();
|
|
27
|
+
let ws;
|
|
28
|
+
let reconnectTimeout;
|
|
29
|
+
let keepAliveInterval;
|
|
30
|
+
let stopped = false;
|
|
31
|
+
let reconnectAttempts = 0;
|
|
32
|
+
|
|
33
|
+
const subscriptions = [
|
|
34
|
+
'{"x-dgw-app-XRSS-method":"Falco","x-dgw-app-xrs-body":"true","x-dgw-app-XRS-Accept-Ack":"RSAck","x-dgw-app-XRSS-http_referer":"https://www.facebook.com/friends"}',
|
|
35
|
+
'{"x-dgw-app-XRSS-method":"FBGQLS:USER_ACTIVITY_UPDATE_SUBSCRIBE","x-dgw-app-XRSS-doc_id":"9525970914181809","x-dgw-app-XRSS-routing_hint":"UserActivitySubscription","x-dgw-app-xrs-body":"true","x-dgw-app-XRS-Accept-Ack":"RSAck","x-dgw-app-XRSS-http_referer":"https://www.facebook.com/friends"}',
|
|
36
|
+
'{"x-dgw-app-XRSS-method":"FBGQLS:ACTOR_GATEWAY_EXPERIENCE_SUBSCRIBE","x-dgw-app-XRSS-doc_id":"24191710730466150","x-dgw-app-XRSS-routing_hint":"CometActorGatewayExperienceSubscription","x-dgw-app-xrs-body":"true","x-dgw-app-XRS-Accept-Ack":"RSAck","x-dgw-app-XRSS-http_referer":"https://www.facebook.com/friends"}',
|
|
37
|
+
`{"x-dgw-app-XRSS-method":"FBLQ:comet_notifications_live_query_experimental","x-dgw-app-XRSS-doc_id":"9784489068321501","x-dgw-app-XRSS-actor_id":"${ctx.userID}","x-dgw-app-XRSS-page_id":"${ctx.userID}","x-dgw-app-XRSS-request_stream_retry":"false","x-dgw-app-xrs-body":"true","x-dgw-app-XRS-Accept-Ack":"RSAck","x-dgw-app-XRSS-http_referer":"https://www.facebook.com/friends"}`,
|
|
38
|
+
'{"x-dgw-app-XRSS-method":"FBGQLS:FRIEND_REQUEST_CONFIRM_SUBSCRIBE","x-dgw-app-XRSS-doc_id":"9687616244672204","x-dgw-app-XRSS-routing_hint":"FriendingCometFriendRequestConfirmSubscription","x-dgw-app-xrs-body":"true","x-dgw-app-XRS-Accept-Ack":"RSAck","x-dgw-app-XRSS-http_referer":"https://www.facebook.com/friends"}',
|
|
39
|
+
'{"x-dgw-app-XRSS-method":"FBGQLS:FRIEND_REQUEST_RECEIVE_SUBSCRIBE","x-dgw-app-XRSS-doc_id":"24047008371656912","x-dgw-app-XRSS-routing_hint":"FriendingCometFriendRequestReceiveSubscription","x-dgw-app-xrs-body":"true","x-dgw-app-XRS-Accept-Ack":"RSAck","x-dgw-app-XRSS-http_referer":"https://www.facebook.com/friends"}',
|
|
40
|
+
'{"x-dgw-app-XRSS-method":"FBGQLS:RTWEB_CALL_BLOCKED_SETTING_SUBSCRIBE","x-dgw-app-XRSS-doc_id":"24429620016626810","x-dgw-app-XRSS-routing_hint":"RTWebCallBlockedSettingSubscription_CallBlockSettingSubscription","x-dgw-app-xrs-body":"true","x-dgw-app-XRS-Accept-Ack":"RSAck","x-dgw-app-XRSS-http_referer":"https://www.facebook.com/friends"}',
|
|
41
|
+
'{"x-dgw-app-XRSS-method":"PresenceUnifiedJSON","x-dgw-app-xrs-body":"true","x-dgw-app-XRS-Accept-Ack":"RSAck","x-dgw-app-XRSS-http_referer":"https://www.facebook.com/friends"}',
|
|
42
|
+
'{"x-dgw-app-XRSS-method":"FBGQLS:MESSENGER_CHAT_TABS_NOTIFICATION_SUBSCRIBE","x-dgw-app-XRSS-doc_id":"23885219097739619","x-dgw-app-XRSS-routing_hint":"MWChatTabsNotificationSubscription_MessengerChatTabsNotificationSubscription","x-dgw-app-xrs-body":"true","x-dgw-app-XRS-Accept-Ack":"RSAck","x-dgw-app-XRSS-http_referer":"https://www.facebook.com/friends"}',
|
|
43
|
+
'{"x-dgw-app-XRSS-method":"FBGQLS:BATCH_NOTIFICATION_STATE_CHANGE_SUBSCRIBE","x-dgw-app-XRSS-doc_id":"30300156509571373","x-dgw-app-XRSS-routing_hint":"CometBatchNotificationsStateChangeSubscription","x-dgw-app-xrs-body":"true","x-dgw-app-XRS-Accept-Ack":"RSAck","x-dgw-app-XRSS-http_referer":"https://www.facebook.com/friends"}',
|
|
44
|
+
'{"x-dgw-app-XRSS-method":"FBGQLS:NOTIFICATION_STATE_CHANGE_SUBSCRIBE","x-dgw-app-XRSS-doc_id":"23864641996495578","x-dgw-app-XRSS-routing_hint":"CometNotificationsStateChangeSubscription","x-dgw-app-xrs-body":"true","x-dgw-app-XRS-Accept-Ack":"RSAck","x-dgw-app-XRSS-http_referer":"https://www.facebook.com/friends"}',
|
|
45
|
+
'{"x-dgw-app-XRSS-method":"FBGQLS:NOTIFICATION_STATE_CHANGE_SUBSCRIBE","x-dgw-app-XRSS-doc_id":"9754477301332178","x-dgw-app-XRSS-routing_hint":"CometFriendNotificationsStateChangeSubscription","x-dgw-app-xrs-body":"true","x-dgw-app-XRS-Accept-Ack":"RSAck","x-dgw-app-XRSS-http_referer":"https://www.facebook.com/friends"}'
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
async function handleMessage(data) {
|
|
49
|
+
try {
|
|
50
|
+
const text = await data.text();
|
|
51
|
+
const jsonStart = text.indexOf("{");
|
|
52
|
+
if (jsonStart !== -1) {
|
|
53
|
+
const jsonData = JSON.parse(text.substring(jsonStart));
|
|
54
|
+
if (jsonData.code === 200) {
|
|
55
|
+
utils.log("Realtime subscription ready");
|
|
56
|
+
emitter.emit("success", jsonData);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const formattedNotif = formatNotification(jsonData);
|
|
61
|
+
if (formattedNotif) {
|
|
62
|
+
emitter.emit("notification", formattedNotif);
|
|
63
|
+
} else {
|
|
64
|
+
emitter.emit("payload", jsonData);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
} catch (err) {
|
|
68
|
+
utils.error("Realtime message parse error:", err);
|
|
69
|
+
emitter.emit("error", err);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function scheduleReconnect() {
|
|
74
|
+
if (stopped) return;
|
|
75
|
+
const base = 1000;
|
|
76
|
+
const max = 30000;
|
|
77
|
+
reconnectAttempts += 1;
|
|
78
|
+
const backoff = Math.min(max, base * Math.pow(2, Math.min(reconnectAttempts, 5)));
|
|
79
|
+
const jitter = Math.floor(Math.random() * 1000);
|
|
80
|
+
const delay = backoff + jitter;
|
|
81
|
+
clearTimeout(reconnectTimeout);
|
|
82
|
+
reconnectTimeout = setTimeout(connect, delay);
|
|
83
|
+
utils.warn("Realtime reconnect scheduled in", delay + "ms");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function connect() {
|
|
87
|
+
try {
|
|
88
|
+
if (stopped) return;
|
|
89
|
+
const queryParams = new URLSearchParams({
|
|
90
|
+
"x-dgw-appid": "2220391788200892",
|
|
91
|
+
"x-dgw-appversion": "0",
|
|
92
|
+
"x-dgw-authtype": "1:0",
|
|
93
|
+
"x-dgw-version": "5",
|
|
94
|
+
"x-dgw-uuid": ctx.userID,
|
|
95
|
+
"x-dgw-tier": "prod",
|
|
96
|
+
"x-dgw-deviceid": ctx.clientID,
|
|
97
|
+
"x-dgw-app-stream-group": "group1"
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const url = `wss://gateway.facebook.com/ws/realtime?${queryParams.toString()}`;
|
|
101
|
+
const cookies = ctx.jar.getCookiesSync("https://www.facebook.com").join("; ");
|
|
102
|
+
|
|
103
|
+
const baseHeaders = {
|
|
104
|
+
"Cookie": cookies,
|
|
105
|
+
"Origin": "https://www.facebook.com",
|
|
106
|
+
"User-Agent": ctx.globalOptions.userAgent,
|
|
107
|
+
"Referer": "https://www.facebook.com",
|
|
108
|
+
"Host": new URL(url).hostname,
|
|
109
|
+
"Accept-Encoding": "gzip, deflate, br",
|
|
110
|
+
"Accept-Language": "en-US,en;q=0.9"
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const wsOptions = { headers: baseHeaders };
|
|
114
|
+
if (ctx.globalOptions.proxy) {
|
|
115
|
+
wsOptions.agent = new HttpsProxyAgent(ctx.globalOptions.proxy);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
ws = new WebSocket(url, wsOptions);
|
|
119
|
+
|
|
120
|
+
ws.onopen = () => {
|
|
121
|
+
reconnectAttempts = 0;
|
|
122
|
+
utils.log("Realtime connected");
|
|
123
|
+
subscriptions.forEach((payload, index) => {
|
|
124
|
+
const prefix = Buffer.from([14, index, 0, payload.length]);
|
|
125
|
+
const suffix = Buffer.from([0, 0]);
|
|
126
|
+
const fullMessage = Buffer.concat([prefix, Buffer.from(payload), suffix]);
|
|
127
|
+
ws.send(fullMessage);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
keepAliveInterval = setInterval(() => {
|
|
131
|
+
if (ws.readyState === ws.OPEN) {
|
|
132
|
+
ws.send("ping");
|
|
133
|
+
}
|
|
134
|
+
}, 10000);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
ws.onmessage = (event) => {
|
|
138
|
+
if (event.data instanceof Blob) {
|
|
139
|
+
handleMessage(event.data);
|
|
140
|
+
} else if (typeof event.data === "string") {
|
|
141
|
+
handleMessage(new Blob([event.data]));
|
|
142
|
+
} else if (event.data instanceof ArrayBuffer) {
|
|
143
|
+
handleMessage(new Blob([event.data]));
|
|
144
|
+
} else {
|
|
145
|
+
utils.warn("Realtime unknown message type:", typeof event.data);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
ws.onerror = (err) => {
|
|
150
|
+
if (stopped) return;
|
|
151
|
+
utils.error("Realtime socket error:", err.message || err);
|
|
152
|
+
emitter.emit("error", err);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
ws.onclose = () => {
|
|
156
|
+
if (stopped) return;
|
|
157
|
+
utils.warn("Realtime socket closed");
|
|
158
|
+
clearInterval(keepAliveInterval);
|
|
159
|
+
scheduleReconnect();
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
} catch (err) {
|
|
163
|
+
if (stopped) return;
|
|
164
|
+
utils.error("Realtime connection error:", err.message);
|
|
165
|
+
emitter.emit("error", err);
|
|
166
|
+
clearInterval(keepAliveInterval);
|
|
167
|
+
scheduleReconnect();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
connect();
|
|
172
|
+
|
|
173
|
+
emitter.stop = () => {
|
|
174
|
+
stopped = true;
|
|
175
|
+
clearInterval(keepAliveInterval);
|
|
176
|
+
clearTimeout(reconnectTimeout);
|
|
177
|
+
if (ws) ws.close();
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
return emitter;
|
|
181
|
+
};
|
|
182
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const utils = require("../utils");
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Refreshes the `fb_dtsg` and `jazoest` security tokens in the current session.
|
|
7
|
+
*
|
|
8
|
+
* Two modes:
|
|
9
|
+
* 1. Called with no arguments (or just a callback) — fetches a fresh token by
|
|
10
|
+
* loading the Facebook homepage and parsing the token from the HTML.
|
|
11
|
+
* 2. Called with a payload object — directly assigns the provided fields to ctx.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* // Auto-refresh from Facebook:
|
|
15
|
+
* await api.refreshFb_dtsg();
|
|
16
|
+
*
|
|
17
|
+
* // Manual override:
|
|
18
|
+
* await api.refreshFb_dtsg({ fb_dtsg: "newToken", jazoest: "12345" });
|
|
19
|
+
*
|
|
20
|
+
* @param {object|Function} [payload] Object with { fb_dtsg?, jazoest?, lsd? }, or callback.
|
|
21
|
+
* @param {Function} [callback]
|
|
22
|
+
* @returns {Promise<{ data: object, message: string }>}
|
|
23
|
+
*/
|
|
24
|
+
module.exports = function (defaultFuncs, api, ctx) {
|
|
25
|
+
return function refreshFb_dtsg(payload, callback) {
|
|
26
|
+
let opts = payload;
|
|
27
|
+
let cb = callback;
|
|
28
|
+
|
|
29
|
+
if (typeof payload === "function") {
|
|
30
|
+
cb = payload;
|
|
31
|
+
opts = {};
|
|
32
|
+
}
|
|
33
|
+
if (!opts) opts = {};
|
|
34
|
+
|
|
35
|
+
if (utils.getType(opts) !== "Object") {
|
|
36
|
+
throw new Error("The first parameter must be an object or a callback function");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let resolveFunc, rejectFunc;
|
|
40
|
+
const promise = new Promise((resolve, reject) => {
|
|
41
|
+
resolveFunc = resolve;
|
|
42
|
+
rejectFunc = reject;
|
|
43
|
+
});
|
|
44
|
+
if (!cb) {
|
|
45
|
+
cb = (err, data) => { if (err) return rejectFunc(err); resolveFunc(data); };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (Object.keys(opts).length === 0) {
|
|
49
|
+
// ── Auto-refresh: parse from Facebook HTML ────────────────────
|
|
50
|
+
defaultFuncs
|
|
51
|
+
.get("https://www.facebook.com/", ctx.jar, { noRef: true })
|
|
52
|
+
.then((res) => {
|
|
53
|
+
const html = typeof res === "string" ? res : (res && res.body ? res.body : (res && res.data ? res.data : ""));
|
|
54
|
+
|
|
55
|
+
const fb_dtsg = utils.getFrom(html, '["DTSGInitData",[],{"token":"', '","') ||
|
|
56
|
+
utils.getFrom(html, '"fb_dtsg":{"token":"', '"') ||
|
|
57
|
+
utils.getFrom(html, 'name="fb_dtsg" value="', '"');
|
|
58
|
+
const jazoest = utils.getFrom(html, "jazoest=", '"') ||
|
|
59
|
+
utils.getFrom(html, '"jazoest":"', '"');
|
|
60
|
+
|
|
61
|
+
if (!fb_dtsg) throw new Error("Could not find fb_dtsg in HTML. Session may have expired.");
|
|
62
|
+
|
|
63
|
+
const updated = { fb_dtsg };
|
|
64
|
+
if (jazoest) updated.jazoest = jazoest;
|
|
65
|
+
|
|
66
|
+
Object.assign(ctx, updated);
|
|
67
|
+
|
|
68
|
+
const result = {
|
|
69
|
+
data: updated,
|
|
70
|
+
message: "Refreshed fb_dtsg" + (jazoest ? " and jazoest" : ""),
|
|
71
|
+
};
|
|
72
|
+
cb(null, result);
|
|
73
|
+
})
|
|
74
|
+
.catch((err) => {
|
|
75
|
+
utils.error("refreshFb_dtsg", err.message || err);
|
|
76
|
+
cb(err instanceof Error ? err : new Error(String(err.message || err)));
|
|
77
|
+
});
|
|
78
|
+
} else {
|
|
79
|
+
// ── Manual override: apply provided fields directly ───────────
|
|
80
|
+
const allowed = ["fb_dtsg", "jazoest", "lsd", "clientID", "userID"];
|
|
81
|
+
const filtered = {};
|
|
82
|
+
for (const key of allowed) {
|
|
83
|
+
if (key in opts) filtered[key] = opts[key];
|
|
84
|
+
}
|
|
85
|
+
Object.assign(ctx, filtered);
|
|
86
|
+
cb(null, {
|
|
87
|
+
data: filtered,
|
|
88
|
+
message: `Updated: ${Object.keys(filtered).join(", ")}`,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return promise;
|
|
93
|
+
};
|
|
94
|
+
};
|