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,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
const STORE_DIR = process.cwd();
|
|
7
|
+
const STORE_FILE = path.join(STORE_DIR, ".phantom_session.json");
|
|
8
|
+
|
|
9
|
+
function _safeRead(filePath) {
|
|
10
|
+
try {
|
|
11
|
+
if (!fs.existsSync(filePath)) return null;
|
|
12
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
13
|
+
} catch (_) { return null; }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function _safeWrite(filePath, data) {
|
|
17
|
+
try {
|
|
18
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf8");
|
|
19
|
+
return true;
|
|
20
|
+
} catch (_) { return false; }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function backupAppStateSQL(jar, userID) {
|
|
24
|
+
try {
|
|
25
|
+
const fbCookies = jar.getCookiesSync("https://www.facebook.com");
|
|
26
|
+
const msgCookies = jar.getCookiesSync("https://www.messenger.com");
|
|
27
|
+
|
|
28
|
+
const seen = new Set();
|
|
29
|
+
const cookies = [];
|
|
30
|
+
for (const c of [...fbCookies, ...msgCookies]) {
|
|
31
|
+
if (seen.has(c.key)) continue;
|
|
32
|
+
seen.add(c.key);
|
|
33
|
+
cookies.push({
|
|
34
|
+
key: c.key,
|
|
35
|
+
value: c.value,
|
|
36
|
+
domain: c.domain,
|
|
37
|
+
path: c.path || "/",
|
|
38
|
+
secure: c.secure ?? true,
|
|
39
|
+
httpOnly: c.httpOnly ?? false,
|
|
40
|
+
expires: c.expires instanceof Date ? c.expires.getTime() : null,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!cookies.length) return false;
|
|
45
|
+
|
|
46
|
+
const existing = _safeRead(STORE_FILE) || { sessions: {} };
|
|
47
|
+
if (!existing.sessions) existing.sessions = {};
|
|
48
|
+
|
|
49
|
+
existing.sessions[userID] = {
|
|
50
|
+
userID,
|
|
51
|
+
cookies,
|
|
52
|
+
savedAt: Date.now(),
|
|
53
|
+
savedDate: new Date().toISOString(),
|
|
54
|
+
version: 2,
|
|
55
|
+
};
|
|
56
|
+
existing.lastUserID = userID;
|
|
57
|
+
existing.updatedAt = Date.now();
|
|
58
|
+
|
|
59
|
+
return _safeWrite(STORE_FILE, existing);
|
|
60
|
+
} catch (_) { return false; }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function hydrateJarFromDB(jar, userID) {
|
|
64
|
+
try {
|
|
65
|
+
const data = _safeRead(STORE_FILE);
|
|
66
|
+
if (!data) return false;
|
|
67
|
+
|
|
68
|
+
const targetUserID = userID || data.lastUserID;
|
|
69
|
+
const session = data.sessions?.[targetUserID];
|
|
70
|
+
|
|
71
|
+
let cookies = session?.cookies;
|
|
72
|
+
if (!cookies && Array.isArray(data.cookies)) cookies = data.cookies;
|
|
73
|
+
if (!Array.isArray(cookies) || !cookies.length) return false;
|
|
74
|
+
|
|
75
|
+
const { Cookie } = require("tough-cookie");
|
|
76
|
+
for (const c of cookies) {
|
|
77
|
+
try {
|
|
78
|
+
if (c.expires && typeof c.expires === "number" && Date.now() > c.expires) continue;
|
|
79
|
+
const domain = c.domain || ".facebook.com";
|
|
80
|
+
const domainUrl = `https://${domain.replace(/^\./, "")}`;
|
|
81
|
+
const cookie = new Cookie({
|
|
82
|
+
key: c.key,
|
|
83
|
+
value: c.value,
|
|
84
|
+
domain,
|
|
85
|
+
path: c.path || "/",
|
|
86
|
+
secure: c.secure ?? true,
|
|
87
|
+
httpOnly: c.httpOnly ?? false,
|
|
88
|
+
});
|
|
89
|
+
jar.setCookieSync(cookie, domainUrl);
|
|
90
|
+
if (domain.includes("facebook")) {
|
|
91
|
+
jar.setCookieSync(new Cookie({ ...cookie, domain: ".messenger.com" }), "https://www.messenger.com");
|
|
92
|
+
}
|
|
93
|
+
} catch (_) {}
|
|
94
|
+
}
|
|
95
|
+
return true;
|
|
96
|
+
} catch (_) { return false; }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function clearSessionStore(userID = null) {
|
|
100
|
+
try {
|
|
101
|
+
if (!fs.existsSync(STORE_FILE)) return;
|
|
102
|
+
if (!userID) {
|
|
103
|
+
fs.unlinkSync(STORE_FILE);
|
|
104
|
+
} else {
|
|
105
|
+
const data = _safeRead(STORE_FILE);
|
|
106
|
+
if (data?.sessions?.[userID]) {
|
|
107
|
+
delete data.sessions[userID];
|
|
108
|
+
_safeWrite(STORE_FILE, data);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
} catch (_) {}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function listSessions() {
|
|
115
|
+
const data = _safeRead(STORE_FILE);
|
|
116
|
+
if (!data?.sessions) return [];
|
|
117
|
+
return Object.values(data.sessions).map(s => ({
|
|
118
|
+
userID: s.userID,
|
|
119
|
+
savedAt: s.savedAt,
|
|
120
|
+
cookies: s.cookies?.length || 0,
|
|
121
|
+
}));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module.exports = {
|
|
125
|
+
backupAppStateSQL,
|
|
126
|
+
hydrateJarFromDB,
|
|
127
|
+
clearSessionStore,
|
|
128
|
+
listSessions,
|
|
129
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
module.exports = function createThreadData(api) {
|
|
4
|
+
const _store = new Map();
|
|
5
|
+
return {
|
|
6
|
+
get(threadID) { return _store.get(String(threadID)) || null; },
|
|
7
|
+
set(threadID, data) { _store.set(String(threadID), data); return data; },
|
|
8
|
+
delete(threadID) { _store.delete(String(threadID)); },
|
|
9
|
+
has(threadID) { return _store.has(String(threadID)); },
|
|
10
|
+
clear() { _store.clear(); },
|
|
11
|
+
all() { return [..._store.entries()]; },
|
|
12
|
+
size() { return _store.size; },
|
|
13
|
+
toArray() { return [..._store.values()]; },
|
|
14
|
+
getIDs() { return [..._store.keys()]; },
|
|
15
|
+
update(threadID, patch) {
|
|
16
|
+
const existing = _store.get(String(threadID)) || {};
|
|
17
|
+
const updated = { ...existing, ...patch, updatedAt: Date.now() };
|
|
18
|
+
_store.set(String(threadID), updated);
|
|
19
|
+
return updated;
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
module.exports = function createUserData(api) {
|
|
4
|
+
const _store = new Map();
|
|
5
|
+
return {
|
|
6
|
+
get(userID) { return _store.get(String(userID)) || null; },
|
|
7
|
+
set(userID, data) { _store.set(String(userID), data); return data; },
|
|
8
|
+
delete(userID) { _store.delete(String(userID)); },
|
|
9
|
+
has(userID) { return _store.has(String(userID)); },
|
|
10
|
+
clear() { _store.clear(); },
|
|
11
|
+
all() { return [..._store.entries()]; },
|
|
12
|
+
size() { return _store.size; },
|
|
13
|
+
toArray() { return [..._store.values()]; },
|
|
14
|
+
getIDs() { return [..._store.keys()]; },
|
|
15
|
+
update(userID, patch) {
|
|
16
|
+
const existing = _store.get(String(userID)) || {};
|
|
17
|
+
const updated = { ...existing, ...patch, updatedAt: Date.now() };
|
|
18
|
+
_store.set(String(userID), updated);
|
|
19
|
+
return updated;
|
|
20
|
+
},
|
|
21
|
+
findByName(name) {
|
|
22
|
+
const q = (name || "").toLowerCase();
|
|
23
|
+
return [..._store.values()].filter(u => (u.name || "").toLowerCase().includes(q));
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
};
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const utils = require('../datastore/models/matrix/tools');
|
|
4
|
+
|
|
5
|
+
const MODULE_REGISTRY = new Map();
|
|
6
|
+
const MODULE_AUDIT_LOG = [];
|
|
7
|
+
const MAX_AUDIT_LOG = 500;
|
|
8
|
+
const DEPENDENCY_GRAPH = new Map();
|
|
9
|
+
const MIDDLEWARE_PIPELINES = new Map();
|
|
10
|
+
const MODULE_VERSIONS = new Map();
|
|
11
|
+
const MODULE_HOOKS = new Map();
|
|
12
|
+
|
|
13
|
+
function recordAudit(action, moduleName, meta = {}) {
|
|
14
|
+
const entry = { action, moduleName, ts: Date.now(), ...meta };
|
|
15
|
+
MODULE_AUDIT_LOG.unshift(entry);
|
|
16
|
+
if (MODULE_AUDIT_LOG.length > MAX_AUDIT_LOG) MODULE_AUDIT_LOG.length = MAX_AUDIT_LOG;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function detectCircularDependency(name, deps, visited = new Set()) {
|
|
20
|
+
if (visited.has(name)) return true;
|
|
21
|
+
visited.add(name);
|
|
22
|
+
const childDeps = DEPENDENCY_GRAPH.get(name) || [];
|
|
23
|
+
for (const dep of childDeps) {
|
|
24
|
+
if (deps.includes(dep) || detectCircularDependency(dep, deps, new Set(visited))) return true;
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function buildMiddlewarePipeline(middlewares) {
|
|
30
|
+
return function pipeline(fn) {
|
|
31
|
+
return async function pipelined(...args) {
|
|
32
|
+
let i = 0;
|
|
33
|
+
const next = async (...nextArgs) => {
|
|
34
|
+
if (i < middlewares.length) {
|
|
35
|
+
const mid = middlewares[i++];
|
|
36
|
+
return mid(...(nextArgs.length ? nextArgs : args), next);
|
|
37
|
+
}
|
|
38
|
+
return fn(...(nextArgs.length ? nextArgs : args));
|
|
39
|
+
};
|
|
40
|
+
return next(...args);
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = function (defaultFuncs, api, ctx) {
|
|
46
|
+
|
|
47
|
+
function validateModule(moduleObj) {
|
|
48
|
+
if (!moduleObj || typeof moduleObj !== 'object' || Array.isArray(moduleObj)) {
|
|
49
|
+
throw new TypeError(`addExternalModule: moduleObj must be a plain object, got ${utils.getType(moduleObj)}`);
|
|
50
|
+
}
|
|
51
|
+
const keys = Object.keys(moduleObj);
|
|
52
|
+
if (keys.length === 0) throw new Error('addExternalModule: moduleObj must have at least one key');
|
|
53
|
+
|
|
54
|
+
const errors = [];
|
|
55
|
+
for (const key of keys) {
|
|
56
|
+
const t = utils.getType(moduleObj[key]);
|
|
57
|
+
if (t !== 'Function' && t !== 'AsyncFunction') {
|
|
58
|
+
errors.push(`"${key}" must be a Function (got ${t})`);
|
|
59
|
+
}
|
|
60
|
+
if (/^(constructor|prototype|__proto__|toString|valueOf|hasOwnProperty)$/.test(key)) {
|
|
61
|
+
errors.push(`"${key}" is a reserved property name`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (errors.length) throw new Error(`addExternalModule: invalid entries — ${errors.join('; ')}`);
|
|
65
|
+
return keys;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function checkConflicts(keys, { overwrite = false, namespace = null } = {}) {
|
|
69
|
+
const conflicts = [];
|
|
70
|
+
const targetObj = namespace ? (api[namespace] || {}) : api;
|
|
71
|
+
for (const key of keys) {
|
|
72
|
+
if (Object.prototype.hasOwnProperty.call(targetObj, key) && !overwrite) {
|
|
73
|
+
conflicts.push(key);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (conflicts.length) {
|
|
77
|
+
throw new Error(`addExternalModule: conflicting API keys (use overwrite:true to force): ${conflicts.join(', ')}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function applyNamespace(namespace, keys, builtFunctions) {
|
|
82
|
+
if (namespace) {
|
|
83
|
+
if (!api[namespace] || typeof api[namespace] !== 'object') api[namespace] = {};
|
|
84
|
+
for (const key of keys) api[namespace][key] = builtFunctions[key];
|
|
85
|
+
recordAudit('namespace_attach', namespace, { keys });
|
|
86
|
+
} else {
|
|
87
|
+
for (const key of keys) api[key] = builtFunctions[key];
|
|
88
|
+
recordAudit('global_attach', 'root', { keys });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function wrapWithTelemetry(name, fn) {
|
|
93
|
+
return async function telemetried(...args) {
|
|
94
|
+
const start = Date.now();
|
|
95
|
+
try {
|
|
96
|
+
const result = await fn(...args);
|
|
97
|
+
utils.log('addExternalModule', `[${name}] executed in ${Date.now() - start}ms`);
|
|
98
|
+
return result;
|
|
99
|
+
} catch (err) {
|
|
100
|
+
utils.error('addExternalModule', `[${name}] failed after ${Date.now() - start}ms:`, err.message || err);
|
|
101
|
+
throw err;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function wrapWithMiddleware(name, fn, middlewares) {
|
|
107
|
+
if (!middlewares || !middlewares.length) return fn;
|
|
108
|
+
const pipeline = buildMiddlewarePipeline(middlewares);
|
|
109
|
+
return pipeline(fn);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return function addExternalModule(moduleObj, options = {}) {
|
|
113
|
+
const {
|
|
114
|
+
overwrite = false,
|
|
115
|
+
namespace = null,
|
|
116
|
+
dependencies = [],
|
|
117
|
+
version = '1.0.0',
|
|
118
|
+
middlewares = [],
|
|
119
|
+
lazy = false,
|
|
120
|
+
description = '',
|
|
121
|
+
tags = []
|
|
122
|
+
} = typeof options === 'string' ? { namespace: options } : options;
|
|
123
|
+
|
|
124
|
+
const keys = validateModule(moduleObj);
|
|
125
|
+
|
|
126
|
+
if (dependencies.length) {
|
|
127
|
+
for (const dep of dependencies) {
|
|
128
|
+
if (!MODULE_REGISTRY.has(dep) && !api[dep]) {
|
|
129
|
+
throw new Error(`addExternalModule: unresolved dependency "${dep}" — install it first`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (detectCircularDependency(keys[0], dependencies)) {
|
|
133
|
+
throw new Error(`addExternalModule: circular dependency detected for ${keys.join(', ')}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
checkConflicts(keys, { overwrite, namespace });
|
|
138
|
+
|
|
139
|
+
const builtFunctions = {};
|
|
140
|
+
const moduleName = namespace || keys.join('+');
|
|
141
|
+
|
|
142
|
+
for (const key of keys) {
|
|
143
|
+
const rawFn = moduleObj[key](defaultFuncs, api, ctx);
|
|
144
|
+
const t = utils.getType(rawFn);
|
|
145
|
+
if (t !== 'Function' && t !== 'AsyncFunction') {
|
|
146
|
+
throw new TypeError(`addExternalModule: factory for "${key}" must return a function, got ${t}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let wrappedFn = rawFn;
|
|
150
|
+
|
|
151
|
+
if (middlewares.length) wrappedFn = wrapWithMiddleware(key, wrappedFn, middlewares);
|
|
152
|
+
wrappedFn = wrapWithTelemetry(key, wrappedFn);
|
|
153
|
+
|
|
154
|
+
if (lazy) {
|
|
155
|
+
let cached = null;
|
|
156
|
+
const lazyFn = wrappedFn;
|
|
157
|
+
wrappedFn = function lazyWrapper(...args) {
|
|
158
|
+
if (!cached) cached = lazyFn;
|
|
159
|
+
return cached(...args);
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
builtFunctions[key] = wrappedFn;
|
|
164
|
+
|
|
165
|
+
const fullKey = namespace ? `${namespace}.${key}` : key;
|
|
166
|
+
MODULE_REGISTRY.set(fullKey, {
|
|
167
|
+
name: fullKey,
|
|
168
|
+
version,
|
|
169
|
+
description,
|
|
170
|
+
tags,
|
|
171
|
+
dependencies,
|
|
172
|
+
installedAt: Date.now(),
|
|
173
|
+
fn: wrappedFn
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (dependencies.length) DEPENDENCY_GRAPH.set(moduleName, dependencies);
|
|
178
|
+
if (middlewares.length) MIDDLEWARE_PIPELINES.set(moduleName, middlewares);
|
|
179
|
+
MODULE_VERSIONS.set(moduleName, version);
|
|
180
|
+
|
|
181
|
+
if (MODULE_HOOKS.has('beforeAttach')) {
|
|
182
|
+
for (const hook of MODULE_HOOKS.get('beforeAttach')) {
|
|
183
|
+
try { hook({ moduleName, keys, version }); } catch (_) {}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
applyNamespace(namespace, keys, builtFunctions);
|
|
188
|
+
|
|
189
|
+
if (MODULE_HOOKS.has('afterAttach')) {
|
|
190
|
+
for (const hook of MODULE_HOOKS.get('afterAttach')) {
|
|
191
|
+
try { hook({ moduleName, keys, version }); } catch (_) {}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
utils.log('addExternalModule', `Installed [${moduleName}] v${version} — keys: ${keys.join(', ')}${namespace ? ` (namespace: ${namespace})` : ''}`);
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
success: true,
|
|
199
|
+
installedKeys: keys,
|
|
200
|
+
namespace: namespace || null,
|
|
201
|
+
version,
|
|
202
|
+
moduleName,
|
|
203
|
+
removeModule() {
|
|
204
|
+
const target = namespace ? api[namespace] : api;
|
|
205
|
+
for (const key of keys) {
|
|
206
|
+
if (target) delete target[key];
|
|
207
|
+
MODULE_REGISTRY.delete(namespace ? `${namespace}.${key}` : key);
|
|
208
|
+
}
|
|
209
|
+
DEPENDENCY_GRAPH.delete(moduleName);
|
|
210
|
+
MODULE_VERSIONS.delete(moduleName);
|
|
211
|
+
recordAudit('remove', moduleName);
|
|
212
|
+
utils.log('addExternalModule', `Removed [${moduleName}]`);
|
|
213
|
+
return { success: true, removedKeys: keys };
|
|
214
|
+
},
|
|
215
|
+
updateModule(newModuleObj, newOpts = {}) {
|
|
216
|
+
return addExternalModule(newModuleObj, { ...options, ...newOpts, overwrite: true });
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
Object.assign(module.exports, {
|
|
222
|
+
getRegistry: () => Object.fromEntries(MODULE_REGISTRY),
|
|
223
|
+
getAuditLog: (limit = 50) => MODULE_AUDIT_LOG.slice(0, limit),
|
|
224
|
+
getDependencyGraph: () => Object.fromEntries(DEPENDENCY_GRAPH),
|
|
225
|
+
getVersions: () => Object.fromEntries(MODULE_VERSIONS),
|
|
226
|
+
isInstalled: (key) => MODULE_REGISTRY.has(key),
|
|
227
|
+
addHook(event, fn) {
|
|
228
|
+
if (!MODULE_HOOKS.has(event)) MODULE_HOOKS.set(event, []);
|
|
229
|
+
MODULE_HOOKS.get(event).push(fn);
|
|
230
|
+
},
|
|
231
|
+
clearRegistry() {
|
|
232
|
+
MODULE_REGISTRY.clear();
|
|
233
|
+
DEPENDENCY_GRAPH.clear();
|
|
234
|
+
MODULE_VERSIONS.clear();
|
|
235
|
+
MODULE_AUDIT_LOG.length = 0;
|
|
236
|
+
utils.log('addExternalModule', 'Registry cleared');
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const utils = require('../datastore/models/matrix/tools');
|
|
4
|
+
const { globalShield } = require('../datastore/models/matrix/ghost');
|
|
5
|
+
|
|
6
|
+
const CACHE = new Map();
|
|
7
|
+
const CACHE_TTL = 5 * 60 * 1000;
|
|
8
|
+
|
|
9
|
+
function cacheSet(key, val) { CACHE.set(key, { val, ts: Date.now() }); }
|
|
10
|
+
function cacheGet(key) {
|
|
11
|
+
const e = CACHE.get(key);
|
|
12
|
+
if (!e) return null;
|
|
13
|
+
if (Date.now() - e.ts > CACHE_TTL) { CACHE.delete(key); return null; }
|
|
14
|
+
return e.val;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function retryOp(fn, retries = 3, base = 600) {
|
|
18
|
+
for (let i = 0; i < retries; i++) {
|
|
19
|
+
try { return await fn(); } catch (err) {
|
|
20
|
+
if (i === retries - 1) throw err;
|
|
21
|
+
await new Promise(r => setTimeout(r, base * Math.pow(2, i) + Math.random() * 300));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function mqttAddUsers(ctx, userIDs, threadID) {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
const reqID = ++ctx.wsReqNumber;
|
|
29
|
+
const taskID = ++ctx.wsTaskNumber;
|
|
30
|
+
|
|
31
|
+
const payload = {
|
|
32
|
+
epoch_id: utils.generateOfflineThreadingID(),
|
|
33
|
+
tasks: [{
|
|
34
|
+
failure_count: null,
|
|
35
|
+
label: "23",
|
|
36
|
+
payload: JSON.stringify({
|
|
37
|
+
thread_key: threadID,
|
|
38
|
+
contact_ids: userIDs.map(id => String(id)),
|
|
39
|
+
sync_group: 1
|
|
40
|
+
}),
|
|
41
|
+
queue_name: threadID.toString(),
|
|
42
|
+
task_id: taskID
|
|
43
|
+
}],
|
|
44
|
+
version_id: "24502707779384158"
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const form = JSON.stringify({
|
|
48
|
+
app_id: "772021112871879",
|
|
49
|
+
payload: JSON.stringify(payload),
|
|
50
|
+
request_id: reqID,
|
|
51
|
+
type: 3
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
let handled = false;
|
|
55
|
+
const handleRes = (topic, message) => {
|
|
56
|
+
if (topic !== "/ls_resp" || handled) return;
|
|
57
|
+
let j;
|
|
58
|
+
try { j = JSON.parse(message.toString()); j.payload = JSON.parse(j.payload); } catch { return; }
|
|
59
|
+
if (j.request_id !== reqID) return;
|
|
60
|
+
handled = true;
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
ctx.mqttClient.removeListener("message", handleRes);
|
|
63
|
+
resolve({ success: true, addedUsers: userIDs, threadID, response: j.payload });
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const timer = setTimeout(() => {
|
|
67
|
+
if (!handled) {
|
|
68
|
+
handled = true;
|
|
69
|
+
ctx.mqttClient.removeListener("message", handleRes);
|
|
70
|
+
reject(new Error("MQTT timeout waiting for addUserToGroup response"));
|
|
71
|
+
}
|
|
72
|
+
}, 30000);
|
|
73
|
+
|
|
74
|
+
ctx.mqttClient.on("message", handleRes);
|
|
75
|
+
ctx.mqttClient.publish("/ls_req", form, { qos: 1, retain: false }, (err) => {
|
|
76
|
+
if (err && !handled) {
|
|
77
|
+
handled = true;
|
|
78
|
+
clearTimeout(timer);
|
|
79
|
+
ctx.mqttClient.removeListener("message", handleRes);
|
|
80
|
+
reject(err);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function httpAddUsers(defaultFuncs, ctx, userIDs, threadID) {
|
|
87
|
+
const form = {
|
|
88
|
+
fb_api_caller_class: "RelayModern",
|
|
89
|
+
fb_api_req_friendly_name: "MessengerAddMembersMutation",
|
|
90
|
+
doc_id: "6564756290218688",
|
|
91
|
+
variables: JSON.stringify({
|
|
92
|
+
input: {
|
|
93
|
+
thread_fbid: threadID,
|
|
94
|
+
actor_id: ctx.userID,
|
|
95
|
+
client_mutation_id: String(Math.round(Math.random() * 10000)),
|
|
96
|
+
user_ids: userIDs.map(String)
|
|
97
|
+
}
|
|
98
|
+
}),
|
|
99
|
+
av: ctx.userID
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const res = await defaultFuncs.post("https://www.facebook.com/api/graphql/", ctx.jar, form)
|
|
103
|
+
.then(utils.parseAndCheckLogin(ctx, defaultFuncs));
|
|
104
|
+
|
|
105
|
+
if (res && res.errors && res.errors.length) throw new Error(JSON.stringify(res.errors));
|
|
106
|
+
return { success: true, addedUsers: userIDs, threadID, method: "graphql" };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
module.exports = (defaultFuncs, api, ctx) => {
|
|
110
|
+
return async function addUserToGroup(userID, threadID, callback) {
|
|
111
|
+
let resolveFunc, rejectFunc;
|
|
112
|
+
const returnPromise = new Promise((resolve, reject) => {
|
|
113
|
+
resolveFunc = resolve;
|
|
114
|
+
rejectFunc = reject;
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
if (typeof callback !== "function") {
|
|
118
|
+
callback = (err, result) => {
|
|
119
|
+
if (err) return rejectFunc(err);
|
|
120
|
+
resolveFunc(result);
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
if (!threadID) throw new Error("addUserToGroup: threadID is required");
|
|
126
|
+
|
|
127
|
+
const t = utils.getType(threadID);
|
|
128
|
+
if (t !== "Number" && t !== "String") throw new Error("addUserToGroup: threadID must be a Number or String");
|
|
129
|
+
|
|
130
|
+
const userIDs = Array.isArray(userID) ? userID : [userID];
|
|
131
|
+
if (userIDs.length === 0) throw new Error("addUserToGroup: at least one userID is required");
|
|
132
|
+
|
|
133
|
+
const cacheKey = `addUser_${threadID}_${userIDs.sort().join(",")}`;
|
|
134
|
+
const cached = cacheGet(cacheKey);
|
|
135
|
+
if (cached) return callback(null, cached);
|
|
136
|
+
|
|
137
|
+
await globalShield.addSmartDelay();
|
|
138
|
+
|
|
139
|
+
let result;
|
|
140
|
+
|
|
141
|
+
if (ctx.mqttClient) {
|
|
142
|
+
try {
|
|
143
|
+
result = await retryOp(() => mqttAddUsers(ctx, userIDs, threadID));
|
|
144
|
+
} catch (mqttErr) {
|
|
145
|
+
utils.warn("addUserToGroup", "MQTT failed, falling back to HTTP:", mqttErr.message);
|
|
146
|
+
result = await retryOp(() => httpAddUsers(defaultFuncs, ctx, userIDs, threadID));
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
result = await retryOp(() => httpAddUsers(defaultFuncs, ctx, userIDs, threadID));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
cacheSet(cacheKey, result);
|
|
153
|
+
callback(null, result);
|
|
154
|
+
} catch (err) {
|
|
155
|
+
utils.error("addUserToGroup", err);
|
|
156
|
+
callback(err);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return returnPromise;
|
|
160
|
+
};
|
|
161
|
+
};
|