fca-phantom 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 +58 -0
- package/README.md +534 -0
- package/index.js +35 -0
- package/package.json +101 -0
- package/phantom/core/builder/bootstrap.js +334 -0
- package/phantom/core/builder/config.js +78 -0
- package/phantom/core/builder/forge.js +113 -0
- package/phantom/core/builder/ignite.js +386 -0
- package/phantom/core/builder/options.js +61 -0
- package/phantom/core/engine.js +71 -0
- package/phantom/core/reactor.js +2 -0
- package/phantom/datastore/appState.js +2 -0
- package/phantom/datastore/appStateBackup.js +34 -0
- package/phantom/datastore/models/cipher/e2ee.js +48 -0
- package/phantom/datastore/models/cipher/vault.js +153 -0
- package/phantom/datastore/models/index.js +3 -0
- package/phantom/datastore/models/matrix/auth.js +151 -0
- package/phantom/datastore/models/matrix/cache.js +3 -0
- package/phantom/datastore/models/matrix/checker.js +2 -0
- package/phantom/datastore/models/matrix/clients.js +2 -0
- package/phantom/datastore/models/matrix/constants.js +2 -0
- package/phantom/datastore/models/matrix/credentials.js +2 -0
- package/phantom/datastore/models/matrix/cycle.js +2 -0
- package/phantom/datastore/models/matrix/gate.js +282 -0
- package/phantom/datastore/models/matrix/ghost.js +332 -0
- package/phantom/datastore/models/matrix/headers.js +193 -0
- package/phantom/datastore/models/matrix/heartbeat.js +298 -0
- package/phantom/datastore/models/matrix/identity.js +235 -0
- package/phantom/datastore/models/matrix/logger.js +271 -0
- package/phantom/datastore/models/matrix/monitor.js +2 -0
- package/phantom/datastore/models/matrix/net.js +316 -0
- package/phantom/datastore/models/matrix/response.js +193 -0
- package/phantom/datastore/models/matrix/revive.js +255 -0
- package/phantom/datastore/models/matrix/signals.js +2 -0
- package/phantom/datastore/models/matrix/store.js +263 -0
- package/phantom/datastore/models/matrix/telemetry.js +272 -0
- package/phantom/datastore/models/matrix/tools.js +93 -0
- package/phantom/datastore/models/matrix/transform/cookieParser.js +2 -0
- package/phantom/datastore/models/matrix/transform/cookies.js +114 -0
- package/phantom/datastore/models/matrix/transform/index.js +203 -0
- package/phantom/datastore/models/matrix/validator.js +157 -0
- package/phantom/datastore/models/types/index.d.ts +498 -0
- package/phantom/datastore/schema.js +167 -0
- package/phantom/datastore/session.js +129 -0
- package/phantom/datastore/threads.js +22 -0
- package/phantom/datastore/users.js +26 -0
- package/phantom/dispatch/addExternalModule.js +239 -0
- package/phantom/dispatch/addUserToGroup.js +161 -0
- package/phantom/dispatch/changeAdminStatus.js +142 -0
- package/phantom/dispatch/changeArchivedStatus.js +135 -0
- package/phantom/dispatch/changeAvatar.js +123 -0
- package/phantom/dispatch/changeBio.js +86 -0
- package/phantom/dispatch/changeBlockedStatus.js +86 -0
- package/phantom/dispatch/changeGroupImage.js +145 -0
- package/phantom/dispatch/changeThreadColor.js +172 -0
- package/phantom/dispatch/changeThreadEmoji.js +130 -0
- package/phantom/dispatch/comment.js +136 -0
- package/phantom/dispatch/createAITheme.js +333 -0
- package/phantom/dispatch/createNewGroup.js +99 -0
- package/phantom/dispatch/createPoll.js +148 -0
- package/phantom/dispatch/deleteMessage.js +131 -0
- package/phantom/dispatch/deleteThread.js +155 -0
- package/phantom/dispatch/e2ee.js +101 -0
- package/phantom/dispatch/editMessage.js +158 -0
- package/phantom/dispatch/emoji.js +143 -0
- package/phantom/dispatch/fetchThemeData.js +233 -0
- package/phantom/dispatch/follow.js +111 -0
- package/phantom/dispatch/forwardMessage.js +110 -0
- package/phantom/dispatch/friend.js +189 -0
- package/phantom/dispatch/gcmember.js +138 -0
- package/phantom/dispatch/gcname.js +131 -0
- package/phantom/dispatch/gcrule.js +111 -0
- package/phantom/dispatch/getAccess.js +109 -0
- package/phantom/dispatch/getBotInfo.js +81 -0
- package/phantom/dispatch/getBotInitialData.js +110 -0
- package/phantom/dispatch/getFriendsList.js +118 -0
- package/phantom/dispatch/getMessage.js +199 -0
- package/phantom/dispatch/getTheme.js +199 -0
- package/phantom/dispatch/getThemeInfo.js +160 -0
- package/phantom/dispatch/getThreadHistory.js +139 -0
- package/phantom/dispatch/getThreadInfo.js +153 -0
- package/phantom/dispatch/getThreadList.js +132 -0
- package/phantom/dispatch/getThreadPictures.js +93 -0
- package/phantom/dispatch/getUserID.js +147 -0
- package/phantom/dispatch/getUserInfo.js +513 -0
- package/phantom/dispatch/getUserInfoV2.js +146 -0
- package/phantom/dispatch/handleMessageRequest.js +50 -0
- package/phantom/dispatch/httpGet.js +63 -0
- package/phantom/dispatch/httpPost.js +89 -0
- package/phantom/dispatch/httpPostFormData.js +69 -0
- package/phantom/dispatch/listenMqtt.js +1236 -0
- package/phantom/dispatch/listenSpeed.js +179 -0
- package/phantom/dispatch/logout.js +93 -0
- package/phantom/dispatch/markAsDelivered.js +92 -0
- package/phantom/dispatch/markAsRead.js +119 -0
- package/phantom/dispatch/markAsReadAll.js +215 -0
- package/phantom/dispatch/markAsSeen.js +70 -0
- package/phantom/dispatch/mqttDeltaValue.js +278 -0
- package/phantom/dispatch/muteThread.js +253 -0
- package/phantom/dispatch/nickname.js +132 -0
- package/phantom/dispatch/notes.js +263 -0
- package/phantom/dispatch/pinMessage.js +238 -0
- package/phantom/dispatch/produceMetaTheme.js +335 -0
- package/phantom/dispatch/realtime.js +291 -0
- package/phantom/dispatch/removeUserFromGroup.js +248 -0
- package/phantom/dispatch/resolvePhotoUrl.js +217 -0
- package/phantom/dispatch/searchForThread.js +258 -0
- package/phantom/dispatch/sendMessage.js +354 -0
- package/phantom/dispatch/sendMessageMqtt.js +249 -0
- package/phantom/dispatch/sendTypingIndicator.js +206 -0
- package/phantom/dispatch/setMessageReaction.js +188 -0
- package/phantom/dispatch/setMessageReactionMqtt.js +248 -0
- package/phantom/dispatch/setThreadTheme.js +330 -0
- package/phantom/dispatch/setThreadThemeMqtt.js +207 -0
- package/phantom/dispatch/share.js +200 -0
- package/phantom/dispatch/shareContact.js +216 -0
- package/phantom/dispatch/stickers.js +395 -0
- package/phantom/dispatch/story.js +240 -0
- package/phantom/dispatch/theme.js +296 -0
- package/phantom/dispatch/unfriend.js +199 -0
- package/phantom/dispatch/unsendMessage.js +124 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const utils = require('../datastore/models/matrix/tools');
|
|
4
|
+
|
|
5
|
+
async function retryOp(fn, retries = 3, base = 500) {
|
|
6
|
+
for (let i = 0; i < retries; i++) {
|
|
7
|
+
try { return await fn(); } catch (err) {
|
|
8
|
+
if (i === retries - 1) throw err;
|
|
9
|
+
await new Promise(r => setTimeout(r, base * Math.pow(2, i) + Math.random() * 200));
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function mqttGcMember(ctx, action, users, threadID) {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
if (!ctx.mqttClient) return reject(new Error("MQTT not connected"));
|
|
17
|
+
|
|
18
|
+
const reqID = ++ctx.wsReqNumber;
|
|
19
|
+
const taskID = ++ctx.wsTaskNumber;
|
|
20
|
+
|
|
21
|
+
let queryPayload, query;
|
|
22
|
+
if (action === "add") {
|
|
23
|
+
queryPayload = {
|
|
24
|
+
thread_key: parseInt(threadID),
|
|
25
|
+
contact_ids: users.map(id => parseInt(id)),
|
|
26
|
+
sync_group: 1
|
|
27
|
+
};
|
|
28
|
+
query = {
|
|
29
|
+
failure_count: null,
|
|
30
|
+
label: "23",
|
|
31
|
+
payload: JSON.stringify(queryPayload),
|
|
32
|
+
queue_name: threadID,
|
|
33
|
+
task_id: taskID
|
|
34
|
+
};
|
|
35
|
+
} else {
|
|
36
|
+
queryPayload = { thread_id: threadID, contact_id: users[0], sync_group: 1 };
|
|
37
|
+
query = {
|
|
38
|
+
failure_count: null,
|
|
39
|
+
label: "140",
|
|
40
|
+
payload: JSON.stringify(queryPayload),
|
|
41
|
+
queue_name: "remove_participant_v2",
|
|
42
|
+
task_id: taskID
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const context = JSON.stringify({
|
|
47
|
+
app_id: ctx.appID || "2220391788200892",
|
|
48
|
+
payload: JSON.stringify({
|
|
49
|
+
epoch_id: parseInt(utils.generateOfflineThreadingID()),
|
|
50
|
+
tasks: [query],
|
|
51
|
+
version_id: "24631415369801570"
|
|
52
|
+
}),
|
|
53
|
+
request_id: reqID,
|
|
54
|
+
type: 3
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
ctx.mqttClient.publish("/ls_req", context, { qos: 1, retain: false }, (err) => {
|
|
58
|
+
if (err) return reject(err);
|
|
59
|
+
resolve({
|
|
60
|
+
type: "gc_member_update",
|
|
61
|
+
threadID,
|
|
62
|
+
userIDs: users,
|
|
63
|
+
action,
|
|
64
|
+
senderID: ctx.userID,
|
|
65
|
+
BotID: ctx.userID,
|
|
66
|
+
timestamp: Date.now(),
|
|
67
|
+
method: "mqtt"
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = (defaultFuncs, api, ctx) => {
|
|
74
|
+
return async function gcmember(action, userIDs, threadID, callback) {
|
|
75
|
+
let _callback;
|
|
76
|
+
if (typeof threadID === "function") { _callback = threadID; threadID = null; }
|
|
77
|
+
else if (typeof callback === "function") { _callback = callback; }
|
|
78
|
+
|
|
79
|
+
let resolvePromise, rejectPromise;
|
|
80
|
+
const returnPromise = new Promise((resolve, reject) => {
|
|
81
|
+
resolvePromise = resolve;
|
|
82
|
+
rejectPromise = reject;
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
if (typeof _callback !== "function") {
|
|
86
|
+
_callback = (err, data) => {
|
|
87
|
+
if (err) return rejectPromise(err);
|
|
88
|
+
resolvePromise(data);
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
const validActions = ["add", "remove"];
|
|
94
|
+
action = String(action || "").toLowerCase();
|
|
95
|
+
|
|
96
|
+
if (!validActions.includes(action))
|
|
97
|
+
return _callback(null, { type: "error_gc", error: `Invalid action. Must be: ${validActions.join(", ")}` });
|
|
98
|
+
if (!userIDs || (Array.isArray(userIDs) && userIDs.length === 0))
|
|
99
|
+
return _callback(null, { type: "error_gc", error: "userIDs is required" });
|
|
100
|
+
if (!threadID)
|
|
101
|
+
return _callback(null, { type: "error_gc", error: "threadID is required" });
|
|
102
|
+
if (!ctx.mqttClient)
|
|
103
|
+
return _callback(null, { type: "error_gc", error: "Not connected to MQTT" });
|
|
104
|
+
|
|
105
|
+
const users = Array.isArray(userIDs) ? userIDs.map(String) : [String(userIDs)];
|
|
106
|
+
|
|
107
|
+
let threadInfo;
|
|
108
|
+
try { threadInfo = await api.getThreadInfo(threadID); } catch {}
|
|
109
|
+
|
|
110
|
+
if (threadInfo) {
|
|
111
|
+
if (threadInfo.isGroup === false)
|
|
112
|
+
return _callback(null, { type: "error_gc", error: "Only for group chats" });
|
|
113
|
+
|
|
114
|
+
const currentMembers = threadInfo.participantIDs || [];
|
|
115
|
+
|
|
116
|
+
if (action === "add") {
|
|
117
|
+
const alreadyIn = users.filter(id => currentMembers.includes(id));
|
|
118
|
+
if (alreadyIn.length === users.length)
|
|
119
|
+
return _callback(null, { type: "error_gc", error: "All specified users are already in the group" });
|
|
120
|
+
const toAdd = users.filter(id => !currentMembers.includes(id));
|
|
121
|
+
const result = await retryOp(() => mqttGcMember(ctx, "add", toAdd, String(threadID)));
|
|
122
|
+
return _callback(null, result);
|
|
123
|
+
} else {
|
|
124
|
+
const userToRemove = users[0];
|
|
125
|
+
if (!currentMembers.includes(userToRemove))
|
|
126
|
+
return _callback(null, { type: "error_gc", error: `User ${userToRemove} not in group` });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const result = await retryOp(() => mqttGcMember(ctx, action, users, String(threadID)));
|
|
131
|
+
_callback(null, result);
|
|
132
|
+
} catch (err) {
|
|
133
|
+
_callback(null, { type: "error_gc", error: err.message || "Unknown error" });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return returnPromise;
|
|
137
|
+
};
|
|
138
|
+
};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const utils = require('../datastore/models/matrix/tools');
|
|
4
|
+
|
|
5
|
+
async function retryOp(fn, retries = 3, base = 500) {
|
|
6
|
+
for (let i = 0; i < retries; i++) {
|
|
7
|
+
try { return await fn(); } catch (err) {
|
|
8
|
+
if (i === retries - 1) throw err;
|
|
9
|
+
await new Promise(r => setTimeout(r, base * Math.pow(2, i) + Math.random() * 200));
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function mqttSetName(ctx, newName, threadID, initiatorID) {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
if (!ctx.mqttClient) return reject(new Error("MQTT not connected"));
|
|
17
|
+
|
|
18
|
+
const reqID = ++ctx.wsReqNumber;
|
|
19
|
+
const taskID = ++ctx.wsTaskNumber;
|
|
20
|
+
|
|
21
|
+
const context = JSON.stringify({
|
|
22
|
+
app_id: ctx.appID || "2220391788200892",
|
|
23
|
+
payload: JSON.stringify({
|
|
24
|
+
epoch_id: parseInt(utils.generateOfflineThreadingID()),
|
|
25
|
+
tasks: [{
|
|
26
|
+
failure_count: null,
|
|
27
|
+
label: "32",
|
|
28
|
+
payload: JSON.stringify({
|
|
29
|
+
thread_key: String(threadID),
|
|
30
|
+
thread_name: newName,
|
|
31
|
+
sync_group: 1
|
|
32
|
+
}),
|
|
33
|
+
queue_name: String(threadID),
|
|
34
|
+
task_id: taskID
|
|
35
|
+
}],
|
|
36
|
+
version_id: "24631415369801570"
|
|
37
|
+
}),
|
|
38
|
+
request_id: reqID,
|
|
39
|
+
type: 3
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
ctx.mqttClient.publish("/ls_req", context, { qos: 1, retain: false }, (err) => {
|
|
43
|
+
if (err) return reject(new Error(`MQTT publish failed: ${err.message}`));
|
|
44
|
+
resolve({
|
|
45
|
+
type: "thread_name_update",
|
|
46
|
+
threadID,
|
|
47
|
+
newName,
|
|
48
|
+
senderID: initiatorID,
|
|
49
|
+
BotID: ctx.userID,
|
|
50
|
+
timestamp: Date.now(),
|
|
51
|
+
method: "mqtt"
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function httpSetName(defaultFuncs, ctx, newName, threadID) {
|
|
58
|
+
const form = {
|
|
59
|
+
fb_api_caller_class: "RelayModern",
|
|
60
|
+
fb_api_req_friendly_name: "MessengerUpdateThreadNameMutation",
|
|
61
|
+
doc_id: "4648919335181350",
|
|
62
|
+
variables: JSON.stringify({
|
|
63
|
+
input: {
|
|
64
|
+
thread_id: String(threadID),
|
|
65
|
+
name: newName,
|
|
66
|
+
actor_id: ctx.userID,
|
|
67
|
+
client_mutation_id: String(Math.round(Math.random() * 10000))
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
};
|
|
71
|
+
const res = await defaultFuncs.post("https://www.facebook.com/api/graphql/", ctx.jar, form)
|
|
72
|
+
.then(utils.parseAndCheckLogin(ctx, defaultFuncs));
|
|
73
|
+
if (res.errors) throw new Error(JSON.stringify(res.errors));
|
|
74
|
+
return { type: "thread_name_update", threadID, newName, method: "http", timestamp: Date.now() };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = (defaultFuncs, api, ctx) => {
|
|
78
|
+
return function gcname(newName, threadID, callback, initiatorID) {
|
|
79
|
+
let _callback, _initiatorID;
|
|
80
|
+
let resolveFunc, rejectFunc;
|
|
81
|
+
const returnPromise = new Promise((resolve, reject) => {
|
|
82
|
+
resolveFunc = resolve;
|
|
83
|
+
rejectFunc = reject;
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const cbType = utils.getType(callback);
|
|
87
|
+
if (cbType === "Function" || cbType === "AsyncFunction") {
|
|
88
|
+
_callback = callback;
|
|
89
|
+
_initiatorID = initiatorID;
|
|
90
|
+
} else if (utils.getType(threadID) === "Function" || utils.getType(threadID) === "AsyncFunction") {
|
|
91
|
+
_callback = threadID;
|
|
92
|
+
threadID = null;
|
|
93
|
+
_initiatorID = callback;
|
|
94
|
+
} else if (cbType === "String") {
|
|
95
|
+
_initiatorID = callback;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
_initiatorID = _initiatorID || ctx.userID;
|
|
99
|
+
threadID = threadID || ctx.threadID;
|
|
100
|
+
|
|
101
|
+
const finalCb = (err, data) => {
|
|
102
|
+
if (typeof _callback === "function") _callback(err, data);
|
|
103
|
+
if (err) rejectFunc(err); else resolveFunc(data);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
if (!threadID) return finalCb(new Error("gcname: threadID is required")), returnPromise;
|
|
107
|
+
if (typeof newName !== "string") return finalCb(new Error("gcname: newName must be a string")), returnPromise;
|
|
108
|
+
|
|
109
|
+
(async () => {
|
|
110
|
+
try {
|
|
111
|
+
let result;
|
|
112
|
+
if (ctx.mqttClient) {
|
|
113
|
+
try {
|
|
114
|
+
result = await retryOp(() => mqttSetName(ctx, newName, String(threadID), _initiatorID));
|
|
115
|
+
} catch (mqttErr) {
|
|
116
|
+
utils.warn("gcname", "MQTT failed, using HTTP:", mqttErr.message);
|
|
117
|
+
result = await retryOp(() => httpSetName(defaultFuncs, ctx, newName, threadID));
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
result = await retryOp(() => httpSetName(defaultFuncs, ctx, newName, threadID));
|
|
121
|
+
}
|
|
122
|
+
finalCb(null, result);
|
|
123
|
+
} catch (err) {
|
|
124
|
+
utils.error("gcname", err);
|
|
125
|
+
finalCb(err);
|
|
126
|
+
}
|
|
127
|
+
})();
|
|
128
|
+
|
|
129
|
+
return returnPromise;
|
|
130
|
+
};
|
|
131
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const utils = require('../datastore/models/matrix/tools');
|
|
4
|
+
|
|
5
|
+
async function retryOp(fn, retries = 3, base = 500) {
|
|
6
|
+
for (let i = 0; i < retries; i++) {
|
|
7
|
+
try { return await fn(); } catch (err) {
|
|
8
|
+
if (i === retries - 1) throw err;
|
|
9
|
+
await new Promise(r => setTimeout(r, base * Math.pow(2, i) + Math.random() * 200));
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function mqttSetAdmin(ctx, userID, threadID, isAdmin) {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
if (!ctx.mqttClient) return reject(new Error("MQTT not connected"));
|
|
17
|
+
|
|
18
|
+
const reqID = ++ctx.wsReqNumber;
|
|
19
|
+
const taskID = ++ctx.wsTaskNumber;
|
|
20
|
+
|
|
21
|
+
const context = JSON.stringify({
|
|
22
|
+
app_id: ctx.appID || "2220391788200892",
|
|
23
|
+
payload: JSON.stringify({
|
|
24
|
+
epoch_id: parseInt(utils.generateOfflineThreadingID()),
|
|
25
|
+
tasks: [{
|
|
26
|
+
failure_count: null,
|
|
27
|
+
label: "25",
|
|
28
|
+
payload: JSON.stringify({
|
|
29
|
+
thread_key: parseInt(threadID),
|
|
30
|
+
contact_id: parseInt(userID),
|
|
31
|
+
is_admin: isAdmin ? 1 : 0
|
|
32
|
+
}),
|
|
33
|
+
queue_name: "admin_status",
|
|
34
|
+
task_id: taskID
|
|
35
|
+
}],
|
|
36
|
+
version_id: "24631415369801570"
|
|
37
|
+
}),
|
|
38
|
+
request_id: reqID,
|
|
39
|
+
type: 3
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
ctx.mqttClient.publish("/ls_req", context, { qos: 1, retain: false }, (err) => {
|
|
43
|
+
if (err) return reject(err);
|
|
44
|
+
resolve({
|
|
45
|
+
type: "gc_rule_update",
|
|
46
|
+
threadID,
|
|
47
|
+
userID,
|
|
48
|
+
action: isAdmin ? "admin" : "unadmin",
|
|
49
|
+
senderID: ctx.userID,
|
|
50
|
+
BotID: ctx.userID,
|
|
51
|
+
timestamp: Date.now(),
|
|
52
|
+
method: "mqtt"
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = (defaultFuncs, api, ctx) => {
|
|
59
|
+
return async function gcrule(action, userID, threadID, callback) {
|
|
60
|
+
let _callback;
|
|
61
|
+
if (typeof threadID === "function") { _callback = threadID; threadID = null; }
|
|
62
|
+
else if (typeof callback === "function") { _callback = callback; }
|
|
63
|
+
|
|
64
|
+
let resolvePromise, rejectPromise;
|
|
65
|
+
const returnPromise = new Promise((resolve, reject) => {
|
|
66
|
+
resolvePromise = resolve;
|
|
67
|
+
rejectPromise = reject;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
if (typeof _callback !== "function") {
|
|
71
|
+
_callback = (err, data) => {
|
|
72
|
+
if (err) return rejectPromise(err);
|
|
73
|
+
resolvePromise(data);
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const validActions = ["admin", "unadmin"];
|
|
79
|
+
action = String(action || "").toLowerCase();
|
|
80
|
+
|
|
81
|
+
if (!validActions.includes(action))
|
|
82
|
+
return _callback(null, { type: "error_gc_rule", error: `Invalid action. Must be: ${validActions.join(", ")}` });
|
|
83
|
+
if (!userID) return _callback(null, { type: "error_gc_rule", error: "userID is required" });
|
|
84
|
+
if (!threadID) return _callback(null, { type: "error_gc_rule", error: "threadID is required" });
|
|
85
|
+
if (!ctx.mqttClient) return _callback(null, { type: "error_gc_rule", error: "Not connected to MQTT" });
|
|
86
|
+
|
|
87
|
+
let threadInfo;
|
|
88
|
+
try { threadInfo = await api.getThreadInfo(threadID); } catch {}
|
|
89
|
+
|
|
90
|
+
if (threadInfo) {
|
|
91
|
+
if (threadInfo.isGroup === false)
|
|
92
|
+
return _callback(null, { type: "error_gc_rule", error: "Only for group chats" });
|
|
93
|
+
|
|
94
|
+
const adminIDs = threadInfo.adminIDs || [];
|
|
95
|
+
const isCurrentlyAdmin = adminIDs.some(a => String(a.id) === String(userID));
|
|
96
|
+
|
|
97
|
+
if (action === "admin" && isCurrentlyAdmin)
|
|
98
|
+
return _callback(null, { type: "error_gc_rule", error: "User is already an admin" });
|
|
99
|
+
if (action === "unadmin" && !isCurrentlyAdmin)
|
|
100
|
+
return _callback(null, { type: "error_gc_rule", error: "User is not an admin" });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const result = await retryOp(() => mqttSetAdmin(ctx, String(userID), String(threadID), action === "admin"));
|
|
104
|
+
_callback(null, result);
|
|
105
|
+
} catch (err) {
|
|
106
|
+
_callback(null, { type: "error_gc_rule", error: err.message || "Unknown error" });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return returnPromise;
|
|
110
|
+
};
|
|
111
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const utils = require("../datastore/models/matrix/tools");
|
|
4
|
+
|
|
5
|
+
async function retryOp(fn, retries = 3, base = 800) {
|
|
6
|
+
for (let i = 0; i < retries; i++) {
|
|
7
|
+
try { return await fn(); } catch (err) {
|
|
8
|
+
if (i === retries - 1) throw err;
|
|
9
|
+
if (err && err.continue) throw err;
|
|
10
|
+
await new Promise(r => setTimeout(r, base * Math.pow(2, i) + Math.random() * 300));
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = function (defaultFuncs, api, ctx) {
|
|
16
|
+
return function getAccess(authCode, callback) {
|
|
17
|
+
const BASE_URL = "https://business.facebook.com/";
|
|
18
|
+
const REFERER = BASE_URL + "security/twofactor/reauth/?twofac_next=" + encodeURIComponent(BASE_URL + "content_management") + "&type=avoid_bypass&app_id=0&save_device=0";
|
|
19
|
+
|
|
20
|
+
let resolveFunc, rejectFunc;
|
|
21
|
+
const returnPromise = new Promise((resolve, reject) => {
|
|
22
|
+
resolveFunc = resolve;
|
|
23
|
+
rejectFunc = reject;
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
if (typeof authCode === "function") { callback = authCode; authCode = ""; }
|
|
27
|
+
|
|
28
|
+
const cb = (err, token) => {
|
|
29
|
+
if (typeof callback === "function") callback(err, token);
|
|
30
|
+
if (err) rejectFunc(err); else resolveFunc(token);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
if (ctx.access_token) {
|
|
34
|
+
cb(null, ctx.access_token);
|
|
35
|
+
return returnPromise;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
(async () => {
|
|
39
|
+
try {
|
|
40
|
+
const pageRes = await retryOp(() =>
|
|
41
|
+
utils.get(BASE_URL + "content_management", ctx.jar, null, ctx.globalOptions, null, {
|
|
42
|
+
noRef: true,
|
|
43
|
+
Origin: BASE_URL
|
|
44
|
+
})
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const html = pageRes.body;
|
|
48
|
+
const lsd = utils.getFrom(html, '[\"LSD\",[],{\"token\":\"', '\"}');
|
|
49
|
+
if (!lsd) throw new Error("getAccess: could not extract LSD token");
|
|
50
|
+
|
|
51
|
+
function submitCode(code) {
|
|
52
|
+
let pResolve, pReject;
|
|
53
|
+
const p = new Promise((res, rej) => { pResolve = res; pReject = rej; });
|
|
54
|
+
|
|
55
|
+
if (typeof code !== "string" || code.length !== 6 || isNaN(code)) {
|
|
56
|
+
const contErr = { error: "submitCode", lerror: "code must be a 6-digit string", continue: submitCode };
|
|
57
|
+
pReject(contErr);
|
|
58
|
+
cb(contErr);
|
|
59
|
+
return p;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
defaultFuncs.post(
|
|
63
|
+
BASE_URL + "security/twofactor/reauth/enter/",
|
|
64
|
+
ctx.jar,
|
|
65
|
+
{ approvals_code: code, save_device: true, lsd },
|
|
66
|
+
ctx.globalOptions, null,
|
|
67
|
+
{ Referer: REFERER, Origin: BASE_URL }
|
|
68
|
+
).then(res => {
|
|
69
|
+
const body = res.body || "";
|
|
70
|
+
let payload;
|
|
71
|
+
try { payload = JSON.parse(body.split(";").pop() || "{}").payload; } catch {}
|
|
72
|
+
if (payload && !payload.codeConfirmed) {
|
|
73
|
+
throw { error: "submitCode", lerror: payload.message, continue: submitCode };
|
|
74
|
+
}
|
|
75
|
+
}).then(() =>
|
|
76
|
+
retryOp(() => utils.get(BASE_URL + "content_management", ctx.jar, null, ctx.globalOptions, null, { noRef: true }))
|
|
77
|
+
).then(res => {
|
|
78
|
+
const pageHtml = res.body;
|
|
79
|
+
const tokenMatch = /"accessToken":"(\S+)","clientID":/g.exec(pageHtml);
|
|
80
|
+
if (!tokenMatch) throw { error: "token-undefined", htmlData: pageHtml.substring(0, 500) };
|
|
81
|
+
ctx.access_token = tokenMatch[1];
|
|
82
|
+
pResolve(tokenMatch[1]);
|
|
83
|
+
cb(null, tokenMatch[1]);
|
|
84
|
+
}).catch(err => {
|
|
85
|
+
utils.error("getAccess.submitCode", err.error || err);
|
|
86
|
+
pReject(err);
|
|
87
|
+
cb(err);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return p;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const code = String(authCode || "");
|
|
94
|
+
if (code.length === 6 && !isNaN(code)) {
|
|
95
|
+
await submitCode(code);
|
|
96
|
+
} else if (typeof callback === "function") {
|
|
97
|
+
cb({ error: "submitCode", continue: submitCode });
|
|
98
|
+
} else {
|
|
99
|
+
throw { error: "getAccess: provide a 6-digit auth code or a callback for interactive entry" };
|
|
100
|
+
}
|
|
101
|
+
} catch (err) {
|
|
102
|
+
utils.error("getAccess", typeof callback === "function" ? (err.error || err) : err);
|
|
103
|
+
cb(err);
|
|
104
|
+
}
|
|
105
|
+
})();
|
|
106
|
+
|
|
107
|
+
return returnPromise;
|
|
108
|
+
};
|
|
109
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const utils = require('../datastore/models/matrix/tools');
|
|
4
|
+
|
|
5
|
+
const BOT_INFO_CACHE = new Map();
|
|
6
|
+
|
|
7
|
+
module.exports = (defaultFuncs, api, ctx) => {
|
|
8
|
+
return function getBotInfo(netData) {
|
|
9
|
+
if (!netData || !Array.isArray(netData)) {
|
|
10
|
+
utils.error("getBotInfo", "netData must be a valid array");
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const cacheKey = `botinfo_${ctx.userID}`;
|
|
15
|
+
const cached = BOT_INFO_CACHE.get(cacheKey);
|
|
16
|
+
if (cached && (Date.now() - cached.ts < 10 * 60 * 1000)) return cached.info;
|
|
17
|
+
|
|
18
|
+
const findConfig = (key) => {
|
|
19
|
+
for (const scriptData of netData) {
|
|
20
|
+
if (!scriptData.require) continue;
|
|
21
|
+
for (const req of scriptData.require) {
|
|
22
|
+
if (Array.isArray(req)) {
|
|
23
|
+
if (req[0] === key && req[2]) return req[2];
|
|
24
|
+
if (req[3] && req[3][0]?.__bbox?.define) {
|
|
25
|
+
for (const def of req[3][0].__bbox.define) {
|
|
26
|
+
if (Array.isArray(def) && def[0]?.endsWith(key) && def[2]) return def[2];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const findMultiConfig = (...keys) => {
|
|
36
|
+
for (const key of keys) {
|
|
37
|
+
const result = findConfig(key);
|
|
38
|
+
if (result) return result;
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const currentUserData = findConfig("CurrentUserInitialData");
|
|
44
|
+
const dtsgInitialData = findMultiConfig("DTSGInitialData", "DTSGInitData");
|
|
45
|
+
const lsdData = findConfig("LSD");
|
|
46
|
+
const mqttData = findConfig("MqttWebDeviceID");
|
|
47
|
+
const siteData = findConfig("SiteData");
|
|
48
|
+
const cryptoData = findConfig("CryptoConfig");
|
|
49
|
+
|
|
50
|
+
if (!currentUserData || !dtsgInitialData) {
|
|
51
|
+
utils.error("getBotInfo", "Critical data missing (CurrentUserInitialData or DTSGInitialData)");
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const botInfo = {
|
|
56
|
+
name: currentUserData.NAME,
|
|
57
|
+
firstName: currentUserData.SHORT_NAME,
|
|
58
|
+
uid: currentUserData.USER_ID,
|
|
59
|
+
appID: currentUserData.APP_ID,
|
|
60
|
+
isBusiness: !!currentUserData.IS_BUSINESS_PERSON_ACCOUNT,
|
|
61
|
+
isPageAdmin: !!currentUserData.IS_PAGES_CREATOR,
|
|
62
|
+
locale: currentUserData.LOCALE || "en_US",
|
|
63
|
+
dtsgToken: dtsgInitialData.token,
|
|
64
|
+
dtsgAsyncToken: dtsgInitialData.async_get_token,
|
|
65
|
+
lsdToken: lsdData?.token,
|
|
66
|
+
mqttDeviceID: mqttData?.clientID,
|
|
67
|
+
siteRevision: siteData?.client_revision,
|
|
68
|
+
spinT: siteData?.spin_t,
|
|
69
|
+
cryptoKey: cryptoData?.key,
|
|
70
|
+
timestamp: Date.now(),
|
|
71
|
+
|
|
72
|
+
getCtx: (key) => ctx[key],
|
|
73
|
+
getOptions: (key) => ctx.globalOptions?.[key],
|
|
74
|
+
getRegion: () => ctx.region,
|
|
75
|
+
getRaw: (key) => findConfig(key)
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
BOT_INFO_CACHE.set(cacheKey, { info: botInfo, ts: Date.now() });
|
|
79
|
+
return botInfo;
|
|
80
|
+
};
|
|
81
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const utils = require('../datastore/models/matrix/tools');
|
|
4
|
+
|
|
5
|
+
const INIT_DATA_CACHE = new Map();
|
|
6
|
+
const CACHE_TTL = 5 * 60 * 1000;
|
|
7
|
+
|
|
8
|
+
async function retryOp(fn, retries = 3, base = 700) {
|
|
9
|
+
for (let i = 0; i < retries; i++) {
|
|
10
|
+
try { return await fn(); } catch (err) {
|
|
11
|
+
if (i === retries - 1) throw err;
|
|
12
|
+
await new Promise(r => setTimeout(r, base * Math.pow(2, i) + Math.random() * 300));
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function parseInitialData(html) {
|
|
18
|
+
const extractors = [
|
|
19
|
+
{ regex: /"CurrentUserInitialData",\[\],\{(.*?)\},(.*?)\]/, parse: (m) => JSON.parse(`{${m[1]}}`) },
|
|
20
|
+
{ regex: /\"USER_ID\":\"(\d+)\"/, parse: (m) => ({ USER_ID: m[1] }) }
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
for (const { regex, parse } of extractors) {
|
|
24
|
+
const match = html.match(regex);
|
|
25
|
+
if (match) {
|
|
26
|
+
try { return parse(match); } catch {}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const jsonBlobs = [];
|
|
31
|
+
const jsonRegex = /\{[^{}]*"USER_ID"[^{}]*\}/g;
|
|
32
|
+
let m;
|
|
33
|
+
while ((m = jsonRegex.exec(html)) !== null) {
|
|
34
|
+
try { jsonBlobs.push(JSON.parse(m[0])); } catch {}
|
|
35
|
+
}
|
|
36
|
+
return jsonBlobs[0] || null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = (defaultFuncs, api, ctx) => {
|
|
40
|
+
return async function getBotInitialData(callback) {
|
|
41
|
+
let resolveFunc, rejectFunc;
|
|
42
|
+
const returnPromise = new Promise((resolve, reject) => {
|
|
43
|
+
resolveFunc = resolve;
|
|
44
|
+
rejectFunc = reject;
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (typeof callback !== "function") {
|
|
48
|
+
callback = (err, data) => {
|
|
49
|
+
if (err) return rejectFunc(err);
|
|
50
|
+
resolveFunc(data);
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const cacheKey = `botInitialData_${ctx.userID}`;
|
|
56
|
+
const cached = INIT_DATA_CACHE.get(cacheKey);
|
|
57
|
+
if (cached && (Date.now() - cached.ts < CACHE_TTL)) {
|
|
58
|
+
return callback(null, cached.data);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
utils.log("getBotInitialData: Fetching account info...");
|
|
62
|
+
|
|
63
|
+
const urls = [
|
|
64
|
+
`https://www.facebook.com/profile.php?id=${ctx.userID}`,
|
|
65
|
+
`https://www.facebook.com/me`
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
let html = null;
|
|
69
|
+
for (const url of urls) {
|
|
70
|
+
try {
|
|
71
|
+
await new Promise((res, rej) => {
|
|
72
|
+
api.httpGet(url, null, { customUserAgent: utils.windowsUserAgent }, (err, data) => {
|
|
73
|
+
if (err) return rej(err);
|
|
74
|
+
html = data;
|
|
75
|
+
res();
|
|
76
|
+
}, true);
|
|
77
|
+
});
|
|
78
|
+
if (html) break;
|
|
79
|
+
} catch {}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!html) throw new Error("getBotInitialData: failed to fetch profile page");
|
|
83
|
+
|
|
84
|
+
const parsed = parseInitialData(html);
|
|
85
|
+
if (!parsed) {
|
|
86
|
+
return callback(null, { error: "Could not parse initial data. Rate-limited or session expired." });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const result = {
|
|
90
|
+
...parsed,
|
|
91
|
+
name: parsed.NAME,
|
|
92
|
+
uid: parsed.USER_ID,
|
|
93
|
+
firstName: parsed.SHORT_NAME,
|
|
94
|
+
isBusiness: !!parsed.IS_BUSINESS_PERSON_ACCOUNT,
|
|
95
|
+
locale: parsed.LOCALE,
|
|
96
|
+
fetchedAt: Date.now()
|
|
97
|
+
};
|
|
98
|
+
delete result.NAME;
|
|
99
|
+
delete result.USER_ID;
|
|
100
|
+
|
|
101
|
+
INIT_DATA_CACHE.set(cacheKey, { data: result, ts: Date.now() });
|
|
102
|
+
callback(null, result);
|
|
103
|
+
} catch (err) {
|
|
104
|
+
utils.error("getBotInitialData", err);
|
|
105
|
+
callback(err);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return returnPromise;
|
|
109
|
+
};
|
|
110
|
+
};
|