@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,61 @@
|
|
|
1
|
+
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const utils = require('../utils');
|
|
5
|
+
|
|
6
|
+
function isCallable(func) {
|
|
7
|
+
try {
|
|
8
|
+
Reflect.apply(func, null, []);
|
|
9
|
+
return true;
|
|
10
|
+
} catch (error) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = function (defaultFuncs, api, ctx) {
|
|
16
|
+
return function setMessageReactionMqtt(reaction, messageID, threadID) {
|
|
17
|
+
if (!ctx.mqttClient) {
|
|
18
|
+
throw new Error('Not connected to MQTT');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
ctx.wsReqNumber += 1;
|
|
22
|
+
ctx.wsTaskNumber += 1;
|
|
23
|
+
|
|
24
|
+
const taskPayload = {
|
|
25
|
+
thread_key: threadID,
|
|
26
|
+
timestamp_ms: Date.now(),
|
|
27
|
+
message_id: messageID,
|
|
28
|
+
reaction,
|
|
29
|
+
actor_id: ctx.userID,
|
|
30
|
+
reaction_style: null,
|
|
31
|
+
sync_group: 1,
|
|
32
|
+
send_attribution: Math.random() < 0.5 ? 65537 : 524289
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const task = {
|
|
36
|
+
failure_count: null,
|
|
37
|
+
label: '29',
|
|
38
|
+
payload: JSON.stringify(taskPayload),
|
|
39
|
+
queue_name: JSON.stringify(['reaction', messageID]),
|
|
40
|
+
task_id: ctx.wsTaskNumber
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const content = {
|
|
44
|
+
app_id: '2220391788200892',
|
|
45
|
+
payload: JSON.stringify({
|
|
46
|
+
data_trace_id: null,
|
|
47
|
+
epoch_id: parseInt(utils.generateOfflineThreadingID()),
|
|
48
|
+
tasks: [task],
|
|
49
|
+
version_id: '7158486590867448',
|
|
50
|
+
}),
|
|
51
|
+
request_id: ctx.wsReqNumber,
|
|
52
|
+
type: 3,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/*if (isCallable(callback)) {
|
|
56
|
+
ctx.reqCallbacks[ctx.wsReqNumber] = callback;
|
|
57
|
+
}*/
|
|
58
|
+
|
|
59
|
+
ctx.mqttClient.publish('/ls_req', JSON.stringify(content), { qos: 1, retain: false });
|
|
60
|
+
};
|
|
61
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const utils = require("../utils");
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Adds or removes a reaction on a Facebook post (not a Messenger message).
|
|
7
|
+
*
|
|
8
|
+
* For reacting to Messenger messages use api.setMessageReaction() instead.
|
|
9
|
+
*
|
|
10
|
+
* @param {string} postID The Facebook post/object ID.
|
|
11
|
+
* @param {string|number} type Reaction type:
|
|
12
|
+
* - Named: "like" | "heart" | "love" | "haha" | "wow" | "sad" | "angry" | "unlike"
|
|
13
|
+
* - Numeric: 0=unlike, 1=like, 2=heart, 4=haha, 3=wow, 7=sad, 8=angry, 16=love
|
|
14
|
+
* @param {Function} [callback]
|
|
15
|
+
* @returns {Promise}
|
|
16
|
+
*/
|
|
17
|
+
module.exports = function (defaultFuncs, api, ctx) {
|
|
18
|
+
const REACTION_MAP = {
|
|
19
|
+
unlike: 0,
|
|
20
|
+
like: 1,
|
|
21
|
+
heart: 2,
|
|
22
|
+
love: 16,
|
|
23
|
+
haha: 4,
|
|
24
|
+
wow: 3,
|
|
25
|
+
sad: 7,
|
|
26
|
+
angry: 8,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
return function setPostReaction(postID, type, callback) {
|
|
30
|
+
let reactionType = type;
|
|
31
|
+
let cb = callback;
|
|
32
|
+
|
|
33
|
+
// Allow omitting type to default to "unlike"
|
|
34
|
+
if (!cb && (utils.getType(type) === "Function" || utils.getType(type) === "AsyncFunction")) {
|
|
35
|
+
cb = type;
|
|
36
|
+
reactionType = 0;
|
|
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 (!postID) return cb(new Error("postID is required"));
|
|
49
|
+
|
|
50
|
+
// Resolve named type
|
|
51
|
+
if (utils.getType(reactionType) === "String") {
|
|
52
|
+
const key = String(reactionType).toLowerCase();
|
|
53
|
+
if (key in REACTION_MAP) {
|
|
54
|
+
reactionType = REACTION_MAP[key];
|
|
55
|
+
} else {
|
|
56
|
+
return cb(new Error(`Unknown reaction type "${type}". Valid types: ${Object.keys(REACTION_MAP).join(", ")}`));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (typeof reactionType !== "number") {
|
|
61
|
+
return cb(new Error("setPostReaction: reaction type must be a string or number"));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const feedbackID = Buffer.from(`feedback:${postID}`).toString("base64");
|
|
65
|
+
|
|
66
|
+
const form = {
|
|
67
|
+
av: ctx.userID,
|
|
68
|
+
__user: ctx.userID,
|
|
69
|
+
__a: 1,
|
|
70
|
+
__req: utils.getSignatureID(),
|
|
71
|
+
fb_dtsg: ctx.fb_dtsg,
|
|
72
|
+
lsd: ctx.lsd || ctx.fb_dtsg,
|
|
73
|
+
jazoest: ctx.jazoest,
|
|
74
|
+
fb_api_caller_class: "RelayModern",
|
|
75
|
+
fb_api_req_friendly_name: "CometUFIFeedbackReactMutation",
|
|
76
|
+
doc_id: "4769042373179384",
|
|
77
|
+
variables: JSON.stringify({
|
|
78
|
+
input: {
|
|
79
|
+
actor_id: ctx.userID,
|
|
80
|
+
feedback_id: feedbackID,
|
|
81
|
+
feedback_reaction: reactionType,
|
|
82
|
+
feedback_source: "OBJECT",
|
|
83
|
+
is_tracking_encrypted: true,
|
|
84
|
+
tracking: [],
|
|
85
|
+
session_id: utils.getSignatureID() + "-" + Date.now(),
|
|
86
|
+
client_mutation_id: Math.floor(Math.random() * 20).toString(),
|
|
87
|
+
},
|
|
88
|
+
useDefaultActor: false,
|
|
89
|
+
scale: 3,
|
|
90
|
+
}),
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
defaultFuncs
|
|
94
|
+
.post("https://www.facebook.com/api/graphql/", ctx.jar, form)
|
|
95
|
+
.then(utils.parseAndCheckLogin(ctx, defaultFuncs))
|
|
96
|
+
.then((resData) => {
|
|
97
|
+
if (resData && resData.errors) throw resData;
|
|
98
|
+
|
|
99
|
+
const fbReact = resData && resData.data && resData.data.feedback_react;
|
|
100
|
+
const result = {
|
|
101
|
+
postID,
|
|
102
|
+
reactionType,
|
|
103
|
+
success: true,
|
|
104
|
+
reaction_count: fbReact && fbReact.feedback
|
|
105
|
+
? fbReact.feedback.reaction_count
|
|
106
|
+
: null,
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
cb(null, result);
|
|
110
|
+
})
|
|
111
|
+
.catch((err) => {
|
|
112
|
+
utils.error("setPostReaction", err.message || err);
|
|
113
|
+
cb(err instanceof Error ? err : new Error(String(err.message || err)));
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return promise;
|
|
117
|
+
};
|
|
118
|
+
};
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const utils = require("../utils");
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Sets the visual theme of a Messenger thread.
|
|
7
|
+
*
|
|
8
|
+
* Accepts:
|
|
9
|
+
* - A numeric theme ID string ("196241301102133")
|
|
10
|
+
* - A color alias ("blue", "purple", "green", …)
|
|
11
|
+
* - A theme name substring ("Citrus", "Tie-Dye", …) — matched against live theme list
|
|
12
|
+
* - An object ({ themeId, emoji })
|
|
13
|
+
*
|
|
14
|
+
* Strategy: tries the legacy GraphQL batch endpoint first (fastest), then falls
|
|
15
|
+
* back to the modern Relay mutation, then the MQTT layer.
|
|
16
|
+
*/
|
|
17
|
+
module.exports = function (defaultFuncs, api, ctx) {
|
|
18
|
+
return function setThreadTheme(threadID, themeData, callback) {
|
|
19
|
+
let resolveFunc, rejectFunc;
|
|
20
|
+
const promise = new Promise((resolve, reject) => {
|
|
21
|
+
resolveFunc = resolve;
|
|
22
|
+
rejectFunc = reject;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
if (!callback) {
|
|
26
|
+
callback = function (err, data) {
|
|
27
|
+
if (err) return rejectFunc(err);
|
|
28
|
+
resolveFunc(data);
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!threadID) {
|
|
33
|
+
return callback(new Error("threadID is required"));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ── Hardcoded colour aliases (fast path — no API round trip needed) ──────
|
|
37
|
+
const PALETTE = {
|
|
38
|
+
blue: "196241301102133",
|
|
39
|
+
purple: "370940413392601",
|
|
40
|
+
green: "169463077092846",
|
|
41
|
+
pink: "230032715012014",
|
|
42
|
+
orange: "175615189761153",
|
|
43
|
+
red: "2136751179887052",
|
|
44
|
+
yellow: "2058653964378557",
|
|
45
|
+
teal: "417639218648241",
|
|
46
|
+
black: "539927563794799",
|
|
47
|
+
white: "2873642392710980",
|
|
48
|
+
default: "196241301102133",
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
(async function worker() {
|
|
52
|
+
try {
|
|
53
|
+
const now = Date.now();
|
|
54
|
+
let availableThemes = [];
|
|
55
|
+
|
|
56
|
+
// Attempt to fetch the live theme list for name-based resolution
|
|
57
|
+
try {
|
|
58
|
+
const themeQueryForm = {
|
|
59
|
+
av: ctx.userID,
|
|
60
|
+
__user: ctx.userID,
|
|
61
|
+
__a: 1,
|
|
62
|
+
__req: utils.getSignatureID(),
|
|
63
|
+
dpr: 1,
|
|
64
|
+
fb_dtsg: ctx.fb_dtsg,
|
|
65
|
+
jazoest: ctx.jazoest,
|
|
66
|
+
lsd: ctx.lsd || ctx.fb_dtsg,
|
|
67
|
+
fb_api_caller_class: "RelayModern",
|
|
68
|
+
fb_api_req_friendly_name: "MWPThreadThemeQuery_AllThemesQuery",
|
|
69
|
+
variables: JSON.stringify({ version: "default" }),
|
|
70
|
+
server_timestamps: true,
|
|
71
|
+
doc_id: "24474714052117636",
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const themeRes = await defaultFuncs
|
|
75
|
+
.post("https://www.facebook.com/api/graphql/", ctx.jar, themeQueryForm)
|
|
76
|
+
.then(utils.parseAndCheckLogin(ctx, defaultFuncs));
|
|
77
|
+
|
|
78
|
+
if (themeRes && themeRes.data && themeRes.data.messenger_thread_themes) {
|
|
79
|
+
availableThemes = themeRes.data.messenger_thread_themes;
|
|
80
|
+
}
|
|
81
|
+
} catch (_) {
|
|
82
|
+
utils.warn("setThreadTheme", "Could not fetch live theme list; using aliases only");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ── Resolve the theme ID ──────────────────────────────────────────
|
|
86
|
+
let chosenThemeId = null;
|
|
87
|
+
let chosenEmoji = "👍";
|
|
88
|
+
|
|
89
|
+
if (typeof themeData === "string") {
|
|
90
|
+
const s = themeData.trim();
|
|
91
|
+
|
|
92
|
+
if (/^\d+$/.test(s)) {
|
|
93
|
+
chosenThemeId = s;
|
|
94
|
+
} else {
|
|
95
|
+
// Try live list first (name substring match)
|
|
96
|
+
const norm = s.toLowerCase();
|
|
97
|
+
const found = availableThemes.find(t =>
|
|
98
|
+
(t.accessibility_label || '').toLowerCase().includes(norm) ||
|
|
99
|
+
(t.name || '').toLowerCase().includes(norm)
|
|
100
|
+
);
|
|
101
|
+
if (found) {
|
|
102
|
+
chosenThemeId = found.id;
|
|
103
|
+
} else {
|
|
104
|
+
chosenThemeId = PALETTE[norm] || PALETTE.default;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
} else if (typeof themeData === "object" && themeData !== null) {
|
|
108
|
+
chosenThemeId = themeData.themeId || themeData.theme_id || themeData.id || null;
|
|
109
|
+
chosenEmoji = themeData.emoji || themeData.customEmoji || chosenEmoji;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!chosenThemeId) chosenThemeId = PALETTE.default;
|
|
113
|
+
|
|
114
|
+
// ── Strategy 1: legacy GraphQL batch ─────────────────────────────
|
|
115
|
+
try {
|
|
116
|
+
const legacyBody = {
|
|
117
|
+
dpr: 1,
|
|
118
|
+
queries: JSON.stringify({
|
|
119
|
+
o0: {
|
|
120
|
+
doc_id: "1727493033983591",
|
|
121
|
+
query_params: {
|
|
122
|
+
data: {
|
|
123
|
+
actor_id: ctx.userID,
|
|
124
|
+
client_mutation_id: "0",
|
|
125
|
+
source: "SETTINGS",
|
|
126
|
+
theme_id: chosenThemeId,
|
|
127
|
+
thread_id: threadID,
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
}),
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const legacyResp = await defaultFuncs
|
|
135
|
+
.post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, legacyBody)
|
|
136
|
+
.then(utils.parseAndCheckLogin(ctx, defaultFuncs));
|
|
137
|
+
|
|
138
|
+
if (legacyResp && !(legacyResp[0] && legacyResp[0].o0 && legacyResp[0].o0.errors)) {
|
|
139
|
+
return callback(null, {
|
|
140
|
+
threadID,
|
|
141
|
+
themeId: chosenThemeId,
|
|
142
|
+
customEmoji: chosenEmoji,
|
|
143
|
+
timestamp: now,
|
|
144
|
+
success: true,
|
|
145
|
+
method: "legacy",
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
} catch (_) {
|
|
149
|
+
utils.warn("setThreadTheme", "Legacy batch approach failed; trying Relay mutation");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ── Strategy 2: Relay Modern mutation ────────────────────────────
|
|
153
|
+
const mutationBody = {
|
|
154
|
+
av: ctx.userID,
|
|
155
|
+
__user: ctx.userID,
|
|
156
|
+
__a: 1,
|
|
157
|
+
__req: utils.getSignatureID(),
|
|
158
|
+
dpr: 1,
|
|
159
|
+
fb_dtsg: ctx.fb_dtsg,
|
|
160
|
+
jazoest: ctx.jazoest,
|
|
161
|
+
lsd: ctx.lsd || ctx.fb_dtsg,
|
|
162
|
+
fb_api_caller_class: "RelayModern",
|
|
163
|
+
fb_api_req_friendly_name: "MessengerThreadThemeUpdateMutation",
|
|
164
|
+
variables: JSON.stringify({
|
|
165
|
+
input: {
|
|
166
|
+
actor_id: ctx.userID,
|
|
167
|
+
client_mutation_id: Math.floor(Math.random() * 10000).toString(),
|
|
168
|
+
source: "SETTINGS",
|
|
169
|
+
thread_id: String(threadID),
|
|
170
|
+
theme_id: String(chosenThemeId),
|
|
171
|
+
custom_emoji: chosenEmoji,
|
|
172
|
+
},
|
|
173
|
+
}),
|
|
174
|
+
server_timestamps: true,
|
|
175
|
+
doc_id: "9734829906576883",
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const gqlResult = await defaultFuncs
|
|
179
|
+
.post("https://www.facebook.com/api/graphql/", ctx.jar, mutationBody)
|
|
180
|
+
.then(utils.parseAndCheckLogin(ctx, defaultFuncs));
|
|
181
|
+
|
|
182
|
+
if (gqlResult && gqlResult.errors && gqlResult.errors.length > 0) {
|
|
183
|
+
throw new Error("GraphQL Error: " + JSON.stringify(gqlResult.errors));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ── Strategy 3: MQTT fallback ─────────────────────────────────────
|
|
187
|
+
if (ctx.mqttClient) {
|
|
188
|
+
try {
|
|
189
|
+
await api.setThreadThemeMqtt(threadID, chosenThemeId);
|
|
190
|
+
} catch (_) {}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return callback(null, {
|
|
194
|
+
threadID,
|
|
195
|
+
themeId: chosenThemeId,
|
|
196
|
+
customEmoji: chosenEmoji,
|
|
197
|
+
timestamp: now,
|
|
198
|
+
success: true,
|
|
199
|
+
method: "graphql",
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
} catch (err) {
|
|
203
|
+
utils.error("setThreadTheme", err.message || err);
|
|
204
|
+
return callback(err instanceof Error ? err : new Error(String(err.message || err)));
|
|
205
|
+
}
|
|
206
|
+
})();
|
|
207
|
+
|
|
208
|
+
return promise;
|
|
209
|
+
};
|
|
210
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const utils = require('../utils');
|
|
4
|
+
|
|
5
|
+
module.exports = function (defaultFuncs, api, ctx) {
|
|
6
|
+
return function setThreadThemeMqtt(threadID, themeFBID, callback) {
|
|
7
|
+
if (!ctx.mqttClient) {
|
|
8
|
+
throw new Error('Not connected to MQTT');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
ctx.wsReqNumber += 1;
|
|
12
|
+
let baseTaskNumber = ++ctx.wsTaskNumber;
|
|
13
|
+
|
|
14
|
+
const makeTask = (label, queueName, extraPayload = {}) => ({
|
|
15
|
+
failure_count: null,
|
|
16
|
+
label: String(label),
|
|
17
|
+
payload: JSON.stringify({
|
|
18
|
+
thread_key: threadID,
|
|
19
|
+
theme_fbid: themeFBID,
|
|
20
|
+
sync_group: 1,
|
|
21
|
+
...extraPayload,
|
|
22
|
+
}),
|
|
23
|
+
queue_name: typeof queueName === 'string' ? queueName : JSON.stringify(queueName),
|
|
24
|
+
task_id: baseTaskNumber++,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const messages = [
|
|
28
|
+
{
|
|
29
|
+
label: 1013,
|
|
30
|
+
queue: ['ai_generated_theme', String(threadID)],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
label: 1037,
|
|
34
|
+
queue: ['msgr_custom_thread_theme', String(threadID)],
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
label: 1028,
|
|
38
|
+
queue: ['thread_theme_writer', String(threadID)],
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
label: 43,
|
|
42
|
+
queue: 'thread_theme',
|
|
43
|
+
extra: { source: null, payload: null },
|
|
44
|
+
},
|
|
45
|
+
].map(({ label, queue, extra }) => {
|
|
46
|
+
ctx.wsReqNumber += 1;
|
|
47
|
+
return {
|
|
48
|
+
app_id: '772021112871879',
|
|
49
|
+
payload: JSON.stringify({
|
|
50
|
+
epoch_id: parseInt(utils.generateOfflineThreadingID()),
|
|
51
|
+
tasks: [makeTask(label, queue, extra)],
|
|
52
|
+
version_id: '24227364673632991',
|
|
53
|
+
}),
|
|
54
|
+
request_id: ctx.wsReqNumber,
|
|
55
|
+
type: 3,
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Return promise if no callback provided
|
|
60
|
+
if (!callback) {
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
try {
|
|
63
|
+
messages.forEach((msg, idx) => {
|
|
64
|
+
ctx.mqttClient.publish(
|
|
65
|
+
'/ls_req',
|
|
66
|
+
JSON.stringify(msg),
|
|
67
|
+
{ qos: 1, retain: false },
|
|
68
|
+
idx === messages.length - 1 ? (err) => {
|
|
69
|
+
if (err) reject(err);
|
|
70
|
+
else resolve();
|
|
71
|
+
} : undefined
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
} catch (err) {
|
|
75
|
+
reject(err);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Callback mode
|
|
81
|
+
try {
|
|
82
|
+
messages.forEach((msg, idx) => {
|
|
83
|
+
ctx.mqttClient.publish(
|
|
84
|
+
'/ls_req',
|
|
85
|
+
JSON.stringify(msg),
|
|
86
|
+
{ qos: 1, retain: false },
|
|
87
|
+
idx === messages.length - 1 ? callback : undefined
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
} catch (err) {
|
|
91
|
+
callback(err);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Changes the title of a group thread.
|
|
5
|
+
*
|
|
6
|
+
* This is an alias for `api.gcname()` using the same MQTT implementation,
|
|
7
|
+
* provided for API compatibility with standard FCA libraries.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* await api.setTitle("My Group Name", threadID);
|
|
11
|
+
*
|
|
12
|
+
* @param {string} newTitle The new name for the group.
|
|
13
|
+
* @param {string} threadID The group thread ID.
|
|
14
|
+
* @param {Function} [callback]
|
|
15
|
+
* @returns {Promise}
|
|
16
|
+
*/
|
|
17
|
+
module.exports = function (defaultFuncs, api, ctx) {
|
|
18
|
+
return function setTitle(newTitle, threadID, callback) {
|
|
19
|
+
if (!api.gcname || typeof api.gcname !== "function") {
|
|
20
|
+
const err = new Error("setTitle requires the gcname module to be loaded.");
|
|
21
|
+
if (callback) return callback(err);
|
|
22
|
+
return Promise.reject(err);
|
|
23
|
+
}
|
|
24
|
+
return api.gcname(newTitle, threadID, callback);
|
|
25
|
+
};
|
|
26
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const utils = require('../utils');
|
|
4
|
+
|
|
5
|
+
function formatPreviewResult(data) {
|
|
6
|
+
if (data.errors) {
|
|
7
|
+
throw data.errors[0];
|
|
8
|
+
}
|
|
9
|
+
const previewData = data.data?.xma_preview_data;
|
|
10
|
+
if (!previewData) {
|
|
11
|
+
throw { error: "Could not generate a preview for this post." };
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
postID: previewData.post_id,
|
|
15
|
+
header: previewData.header_title,
|
|
16
|
+
subtitle: previewData.subtitle_text,
|
|
17
|
+
title: previewData.title_text,
|
|
18
|
+
previewImage: previewData.preview_url,
|
|
19
|
+
favicon: previewData.favicon_url,
|
|
20
|
+
headerImage: previewData.header_image_url
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = function(defaultFuncs, api, ctx) {
|
|
25
|
+
return async function getPostPreview(postID, callback) {
|
|
26
|
+
let resolveFunc, rejectFunc;
|
|
27
|
+
const returnPromise = new Promise((resolve, reject) => {
|
|
28
|
+
resolveFunc = resolve;
|
|
29
|
+
rejectFunc = reject;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const cb = (err, data) => {
|
|
33
|
+
if (callback) callback(err, data);
|
|
34
|
+
if (err) return rejectFunc(err);
|
|
35
|
+
resolveFunc(data);
|
|
36
|
+
};
|
|
37
|
+
if (!postID) {
|
|
38
|
+
cb({ error: "A postID is required to generate a preview." });
|
|
39
|
+
return returnPromise;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const variables = {
|
|
43
|
+
shareable_id: postID.toString(),
|
|
44
|
+
scale: 3,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Use configurable doc_id or default (may be outdated)
|
|
48
|
+
// To update:
|
|
49
|
+
// 1. Open Facebook Messenger in browser
|
|
50
|
+
// 2. Open DevTools (F12) → Network tab → Filter by "graphql"
|
|
51
|
+
// 3. Trigger a share/preview action
|
|
52
|
+
// 4. Look for CometXMAProxyShareablePreviewQuery request
|
|
53
|
+
// 5. Copy the doc_id value from the request payload
|
|
54
|
+
// 6. Set ctx.options.sharePreviewDocId = 'NEW_DOC_ID' when logging in
|
|
55
|
+
//
|
|
56
|
+
// Known doc_ids (may expire):
|
|
57
|
+
// - 28939050904374351 (expired as of Nov 2024)
|
|
58
|
+
const docId = ctx.options?.sharePreviewDocId || '28939050904374351';
|
|
59
|
+
|
|
60
|
+
const form = {
|
|
61
|
+
fb_api_caller_class: 'RelayModern',
|
|
62
|
+
fb_api_req_friendly_name: 'CometXMAProxyShareablePreviewQuery',
|
|
63
|
+
variables: JSON.stringify(variables),
|
|
64
|
+
doc_id: docId
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const resData = await defaultFuncs
|
|
69
|
+
.post("https://www.facebook.com/api/graphql/", ctx.jar, form)
|
|
70
|
+
.then(utils.parseAndCheckLogin(ctx, defaultFuncs));
|
|
71
|
+
|
|
72
|
+
// Check for persisted query not found error (case-insensitive)
|
|
73
|
+
if (resData?.errors) {
|
|
74
|
+
const persistedQueryError = resData.errors.find(e => {
|
|
75
|
+
const msg = (e.message || '').toLowerCase();
|
|
76
|
+
return msg.includes('persistedquerynotfound') ||
|
|
77
|
+
msg.includes('document') && msg.includes('not found') ||
|
|
78
|
+
msg.includes('persisted query');
|
|
79
|
+
});
|
|
80
|
+
if (persistedQueryError) {
|
|
81
|
+
const error = {
|
|
82
|
+
error: "Facebook GraphQL doc_id expired. Please update ctx.options.sharePreviewDocId",
|
|
83
|
+
details: "Capture the current doc_id from Messenger web traffic (inspect CometXMAProxyShareablePreviewQuery request)",
|
|
84
|
+
currentDocId: docId,
|
|
85
|
+
fbError: persistedQueryError.message
|
|
86
|
+
};
|
|
87
|
+
utils.error("getPostPreview", error);
|
|
88
|
+
cb(error);
|
|
89
|
+
return returnPromise;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const result = formatPreviewResult(resData);
|
|
94
|
+
cb(null, result);
|
|
95
|
+
return returnPromise;
|
|
96
|
+
} catch (err) {
|
|
97
|
+
// Add helpful context for common failure modes
|
|
98
|
+
if (err.message?.includes('UNHANDLED_REJECTION') || err.message?.includes('not found')) {
|
|
99
|
+
err.hint = "The GraphQL doc_id may be outdated. Set ctx.options.sharePreviewDocId with current value from Messenger traffic.";
|
|
100
|
+
}
|
|
101
|
+
utils.error("getPostPreview", err);
|
|
102
|
+
cb(err);
|
|
103
|
+
return returnPromise;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/* eslint-disable linebreak-style */
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const utils = require('../utils');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @module shareContact
|
|
8
|
+
* @param {Object} defaultFuncs - The default functions provided by the API.
|
|
9
|
+
* @param {Object} api - The full API object.
|
|
10
|
+
* @param {Object} ctx - The context object.
|
|
11
|
+
* @returns {function(text: string, senderID: string, threadID: string, callback: Function): void} - A function to share a contact.
|
|
12
|
+
*/
|
|
13
|
+
module.exports = function(defaultFuncs, api, ctx) {
|
|
14
|
+
/**
|
|
15
|
+
* Shares a user's contact information into a specific thread via MQTT.
|
|
16
|
+
* @param {string} [text] - An optional message to send along with the contact card.
|
|
17
|
+
* @param {string} senderID - The Facebook user ID of the contact you want to share.
|
|
18
|
+
* @param {string} threadID - The ID of the thread where the contact will be shared.
|
|
19
|
+
* @param {Function} [callback] - An optional callback function to be executed.
|
|
20
|
+
*/
|
|
21
|
+
return function shareContact(text, senderID, threadID, callback) {
|
|
22
|
+
if (!ctx.mqttClient) {
|
|
23
|
+
throw new Error('Not connected to MQTT');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
ctx.wsReqNumber ??= 0;
|
|
27
|
+
ctx.wsTaskNumber ??= 0;
|
|
28
|
+
|
|
29
|
+
ctx.wsReqNumber += 1;
|
|
30
|
+
ctx.wsTaskNumber += 1;
|
|
31
|
+
|
|
32
|
+
const queryPayload = {
|
|
33
|
+
contact_id: senderID,
|
|
34
|
+
sync_group: 1,
|
|
35
|
+
text: text || "",
|
|
36
|
+
thread_id: threadID
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const query = {
|
|
40
|
+
failure_count: null,
|
|
41
|
+
label: '359',
|
|
42
|
+
payload: JSON.stringify(queryPayload),
|
|
43
|
+
queue_name: 'messenger_contact_sharing',
|
|
44
|
+
task_id: Math.random() * 1001 << 0,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const context = {
|
|
48
|
+
app_id: '2220391788200892',
|
|
49
|
+
payload: {
|
|
50
|
+
tasks: [query],
|
|
51
|
+
epoch_id: utils.generateOfflineThreadingID(),
|
|
52
|
+
version_id: '7214102258676893',
|
|
53
|
+
},
|
|
54
|
+
request_id: ctx.wsReqNumber,
|
|
55
|
+
type: 3,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
context.payload = JSON.stringify(context.payload);
|
|
59
|
+
|
|
60
|
+
if (typeof callback === 'function') {
|
|
61
|
+
ctx.callback_Task[ctx.wsReqNumber] = { callback, type: "shareContact" };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
ctx.mqttClient.publish('/ls_req', JSON.stringify(context), { qos: 1, retain: false });
|
|
65
|
+
};
|
|
66
|
+
};
|