@lazyneoaz/testfca 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/CHANGELOG.md +229 -0
- package/COOKIE_LOGIN.md +208 -0
- package/LICENSE +3 -0
- package/README.md +492 -0
- package/index.js +2 -0
- package/package.json +120 -0
- package/scripts/build-go.mjs +54 -0
- package/scripts/detect-platform.mjs +36 -0
- package/scripts/download-prebuilt.mjs +119 -0
- package/scripts/package.mjs +6 -0
- package/scripts/postinstall.mjs +113 -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 +52 -0
- package/src/apis/deleteThread.js +52 -0
- package/src/apis/e2ee.js +170 -0
- package/src/apis/editMessage.js +78 -0
- package/src/apis/emoji.js +124 -0
- package/src/apis/fetchThemeData.js +82 -0
- package/src/apis/follow.js +81 -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/getFriendsList.js +79 -0
- package/src/apis/getMessage.js +423 -0
- package/src/apis/getTheme.js +95 -0
- package/src/apis/getThemeInfo.js +116 -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/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 +1236 -0
- package/src/apis/listenSpeed.js +179 -0
- package/src/apis/logout.js +93 -0
- package/src/apis/markAsDelivered.js +47 -0
- package/src/apis/markAsRead.js +115 -0
- package/src/apis/markAsReadAll.js +40 -0
- package/src/apis/markAsSeen.js +70 -0
- package/src/apis/mqttDeltaValue.js +250 -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 +180 -0
- package/src/apis/realtime.js +182 -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/sendMessage.js +346 -0
- package/src/apis/sendMessageMqtt.js +248 -0
- package/src/apis/sendTypingIndicator.js +105 -0
- package/src/apis/setMessageReaction.js +38 -0
- package/src/apis/setMessageReactionMqtt.js +61 -0
- package/src/apis/setThreadTheme.js +260 -0
- package/src/apis/setThreadThemeMqtt.js +94 -0
- package/src/apis/share.js +107 -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 +25 -0
- package/src/database/appStateBackup.js +298 -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/e2ee/bridge.js +275 -0
- package/src/e2ee/index.js +60 -0
- package/src/engine/client.js +95 -0
- package/src/engine/models/buildAPI.js +152 -0
- package/src/engine/models/loginHelper.js +574 -0
- package/src/engine/models/setOptions.js +88 -0
- package/src/types/index.d.ts +574 -0
- package/src/utils/antiSuspension.js +529 -0
- package/src/utils/auth-helpers.js +149 -0
- package/src/utils/autoReLogin.js +336 -0
- package/src/utils/axios.js +436 -0
- package/src/utils/cache.js +54 -0
- package/src/utils/clients.js +282 -0
- package/src/utils/constants.js +410 -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 +1373 -0
- package/src/utils/headers.js +235 -0
- package/src/utils/index.js +153 -0
- package/src/utils/monitoring.js +333 -0
- package/src/utils/rateLimiter.js +319 -0
- package/src/utils/tokenRefresh.js +680 -0
- package/src/utils/user-agents.js +238 -0
- package/src/utils/validation.js +157 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { createWriteStream } from "node:fs";
|
|
2
|
+
import { mkdir, rename, unlink } from "node:fs/promises";
|
|
3
|
+
import https from "node:https";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
import { detectPlatform } from "./detect-platform.mjs";
|
|
8
|
+
import { packageJson as pkg } from "./package.mjs";
|
|
9
|
+
|
|
10
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
|
|
12
|
+
function defaultRepoSlug() {
|
|
13
|
+
const repo = pkg.repository;
|
|
14
|
+
if (typeof repo === "string") {
|
|
15
|
+
const m = repo.match(/github:(.+)/i);
|
|
16
|
+
if (m) return m[1];
|
|
17
|
+
if (/^[\w-]+\/[\w.-]+$/.test(repo)) return repo;
|
|
18
|
+
} else if (repo && typeof repo === "object" && repo.url) {
|
|
19
|
+
const m = repo.url.match(/github\.com[:/]+([^#]+?)(?:\.git)?$/i);
|
|
20
|
+
if (m) return m[1];
|
|
21
|
+
}
|
|
22
|
+
return "NeoKEX/nkxfca";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function buildBaseURL() {
|
|
26
|
+
const repo = defaultRepoSlug();
|
|
27
|
+
const versionTag = `v${pkg.version}`;
|
|
28
|
+
const baseURL = `https://github.com/${repo}/releases/download/${versionTag}`;
|
|
29
|
+
return baseURL.replace(/\/$/, "");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function httpGet(url, redirectCount = 0) {
|
|
33
|
+
console.log(`[${pkg.name}] HTTP GET: ${url}${redirectCount > 0 ? ` (redirect #${redirectCount})` : ""}`);
|
|
34
|
+
const reqStart = Date.now();
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
https
|
|
37
|
+
.get(url, res => {
|
|
38
|
+
console.log(`[${pkg.name}] Response: HTTP ${res.statusCode} in ${Date.now() - reqStart}ms`);
|
|
39
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
40
|
+
console.log(`[${pkg.name}] Following redirect to: ${res.headers.location}`);
|
|
41
|
+
return resolve(httpGet(res.headers.location, redirectCount + 1));
|
|
42
|
+
}
|
|
43
|
+
if (res.statusCode !== 200) {
|
|
44
|
+
return reject(new Error(`HTTP ${res.statusCode} for ${url}`));
|
|
45
|
+
}
|
|
46
|
+
resolve(res);
|
|
47
|
+
})
|
|
48
|
+
.on("error", err => {
|
|
49
|
+
console.log(`[${pkg.name}] HTTP error after ${Date.now() - reqStart}ms:`, err?.message);
|
|
50
|
+
reject(err);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function downloadTo(url, dstPath) {
|
|
56
|
+
console.log(`[${pkg.name}] Downloading to: ${dstPath}`);
|
|
57
|
+
await mkdir(dirname(dstPath), { recursive: true });
|
|
58
|
+
const tmp = `${dstPath}.download`;
|
|
59
|
+
try {
|
|
60
|
+
await unlink(tmp);
|
|
61
|
+
} catch {
|
|
62
|
+
//
|
|
63
|
+
}
|
|
64
|
+
const res = await httpGet(url);
|
|
65
|
+
console.log(`[${pkg.name}] Starting file write...`);
|
|
66
|
+
const writeStart = Date.now();
|
|
67
|
+
let bytesWritten = 0;
|
|
68
|
+
await new Promise((resolve, reject) => {
|
|
69
|
+
const out = createWriteStream(tmp);
|
|
70
|
+
res.on("data", chunk => {
|
|
71
|
+
bytesWritten += chunk.length;
|
|
72
|
+
});
|
|
73
|
+
res.pipe(out);
|
|
74
|
+
res.on("error", reject);
|
|
75
|
+
out.on("error", reject);
|
|
76
|
+
out.on("finish", () => {
|
|
77
|
+
console.log(`[${pkg.name}] File write completed: ${bytesWritten} bytes in ${Date.now() - writeStart}ms`);
|
|
78
|
+
res.destroy();
|
|
79
|
+
resolve();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
await rename(tmp, dstPath);
|
|
83
|
+
console.log(`[${pkg.name}] File renamed to final destination`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function downloadPrebuilt() {
|
|
87
|
+
console.log(`[${pkg.name}] downloadPrebuilt() started`);
|
|
88
|
+
const { triplet, ext } = detectPlatform();
|
|
89
|
+
const baseURL = buildBaseURL();
|
|
90
|
+
const filename = `messagix-${triplet}.${ext}`;
|
|
91
|
+
const url = `${baseURL}/${filename}`;
|
|
92
|
+
console.log(`[${pkg.name}] Target URL: ${url}`);
|
|
93
|
+
|
|
94
|
+
const out = join(__dirname, "..", "build", `messagix.${ext}`);
|
|
95
|
+
const totalStart = Date.now();
|
|
96
|
+
try {
|
|
97
|
+
await downloadTo(url, out);
|
|
98
|
+
console.log(`[${pkg.name}] Downloaded prebuilt from ${url} in ${Date.now() - totalStart}ms`);
|
|
99
|
+
return true;
|
|
100
|
+
} catch (err) {
|
|
101
|
+
console.warn(
|
|
102
|
+
`[${pkg.name}] No remote prebuilt found at ${url} (after ${Date.now() - totalStart}ms):`,
|
|
103
|
+
err?.message || String(err),
|
|
104
|
+
);
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const isMain = process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1];
|
|
110
|
+
if (isMain) {
|
|
111
|
+
downloadPrebuilt()
|
|
112
|
+
.then(ok => {
|
|
113
|
+
if (!ok) process.exit(1);
|
|
114
|
+
})
|
|
115
|
+
.catch(err => {
|
|
116
|
+
console.error(`[${pkg.name}] download-prebuilt failed:`, err?.message || String(err));
|
|
117
|
+
process.exit(1);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
export const packageJson = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { copyFile, mkdir, rm } from "node:fs/promises";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
import { detectPlatform } from "./detect-platform.mjs";
|
|
8
|
+
import { downloadPrebuilt } from "./download-prebuilt.mjs";
|
|
9
|
+
import { packageJson as pkg } from "./package.mjs";
|
|
10
|
+
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
|
|
13
|
+
async function copyIfExists(src, dst) {
|
|
14
|
+
try {
|
|
15
|
+
await mkdir(dirname(dst), { recursive: true });
|
|
16
|
+
await copyFile(src, dst);
|
|
17
|
+
return true;
|
|
18
|
+
} catch (err) {
|
|
19
|
+
if (err?.code === "ENOENT") return false;
|
|
20
|
+
throw err;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function run() {
|
|
25
|
+
console.log(`[${pkg.name}] postinstall started`);
|
|
26
|
+
const startTime = Date.now();
|
|
27
|
+
|
|
28
|
+
if (process.env.MESSAGIX_SKIP_POSTINSTALL === "true") {
|
|
29
|
+
console.log(`[${pkg.name}] Skipping postinstall (MESSAGIX_SKIP_POSTINSTALL=true)`);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log(`[${pkg.name}] Detecting platform...`);
|
|
34
|
+
const { triplet, ext } = detectPlatform();
|
|
35
|
+
console.log(`[${pkg.name}] Platform: ${triplet}, ext: ${ext}`);
|
|
36
|
+
|
|
37
|
+
const buildOut = join(__dirname, "..", "build", `messagix.${ext}`);
|
|
38
|
+
console.log(`[${pkg.name}] Build output path: ${buildOut}`);
|
|
39
|
+
|
|
40
|
+
if (existsSync(buildOut)) {
|
|
41
|
+
console.log(`[${pkg.name}] Native library already present at build/messagix.${ext}`);
|
|
42
|
+
console.log(`[${pkg.name}] postinstall completed in ${Date.now() - startTime}ms`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 1) Prefer local prebuilt shipped in npm tarball
|
|
47
|
+
const prebuiltDir = join(__dirname, "..", "prebuilt", triplet);
|
|
48
|
+
const prebuilt = join(prebuiltDir, `messagix.${ext}`);
|
|
49
|
+
console.log(`[${pkg.name}] Checking local prebuilt at: ${prebuilt}`);
|
|
50
|
+
if (await copyIfExists(prebuilt, buildOut)) {
|
|
51
|
+
console.log(`[${pkg.name}] Using local prebuilt for ${triplet}`);
|
|
52
|
+
if (process.env.MESSAGIX_KEEP_PREBUILT !== "true") {
|
|
53
|
+
try {
|
|
54
|
+
await rm(prebuiltDir, { recursive: true, force: true });
|
|
55
|
+
} catch {
|
|
56
|
+
//
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
console.log(`[${pkg.name}] postinstall completed in ${Date.now() - startTime}ms`);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
console.log(`[${pkg.name}] No local prebuilt found`);
|
|
63
|
+
|
|
64
|
+
// 2) Try remote prebuilt from GitHub Releases
|
|
65
|
+
console.log(`[${pkg.name}] Attempting to download remote prebuilt...`);
|
|
66
|
+
const downloadStart = Date.now();
|
|
67
|
+
try {
|
|
68
|
+
if (await downloadPrebuilt()) {
|
|
69
|
+
console.log(`[${pkg.name}] Download completed in ${Date.now() - downloadStart}ms`);
|
|
70
|
+
console.log(`[${pkg.name}] postinstall completed in ${Date.now() - startTime}ms`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
} catch (err) {
|
|
74
|
+
console.log(
|
|
75
|
+
`[${pkg.name}] Download failed after ${Date.now() - downloadStart}ms:`,
|
|
76
|
+
err?.message || String(err),
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 3) No prebuilt available. Try local build if allowed
|
|
81
|
+
if (process.env.MESSAGIX_BUILD_FROM_SOURCE === "true") {
|
|
82
|
+
console.log(`[${pkg.name}] No prebuilt found. Attempting local build...`);
|
|
83
|
+
try {
|
|
84
|
+
const res = spawnSync(process.platform === "win32" ? "npm.cmd" : "npm", ["run", "build:go"], {
|
|
85
|
+
cwd: join(__dirname, ".."),
|
|
86
|
+
stdio: "inherit",
|
|
87
|
+
env: process.env,
|
|
88
|
+
});
|
|
89
|
+
if (res.status === 0 && existsSync(buildOut)) {
|
|
90
|
+
console.log(`[${pkg.name}] postinstall completed in ${Date.now() - startTime}ms`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
console.warn(`[${pkg.name}] Local build did not produce the native library.`);
|
|
94
|
+
} catch (err) {
|
|
95
|
+
console.warn(`[${pkg.name}] Local build failed:`, err?.message || String(err));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
console.warn(`[${pkg.name}] No prebuilt found (local/remote) and no local build completed.`);
|
|
100
|
+
console.warn(`[${pkg.name}] Expected triplet: ${triplet}, file: messagix-${triplet}.${ext}`);
|
|
101
|
+
console.warn(
|
|
102
|
+
`[${pkg.name}] You can:\n` +
|
|
103
|
+
" - set MESSAGIX_BUILD_FROM_SOURCE=true and re-run install\n" +
|
|
104
|
+
" - or build manually with: npm run build:go",
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
run()
|
|
109
|
+
.then(() => process.exit(0))
|
|
110
|
+
.catch(err => {
|
|
111
|
+
console.error(`[${pkg.name}] postinstall failed:`, err?.message || String(err));
|
|
112
|
+
process.exit(1);
|
|
113
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const utils = require('../utils');
|
|
3
|
+
|
|
4
|
+
module.exports = function (defaultFuncs, api, ctx) {
|
|
5
|
+
return function addExternalModule(moduleObj) {
|
|
6
|
+
if (utils.getType(moduleObj) == "Object") {
|
|
7
|
+
for (const apiName in moduleObj) {
|
|
8
|
+
if (utils.getType(moduleObj[apiName]) == "Function") {
|
|
9
|
+
api[apiName] = moduleObj[apiName](defaultFuncs, api, ctx);
|
|
10
|
+
} else {
|
|
11
|
+
throw new Error(
|
|
12
|
+
`Item "${apiName}" in moduleObj must be a function, not ${utils.getType(
|
|
13
|
+
moduleObj[apiName],
|
|
14
|
+
)}!`,
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
} else {
|
|
19
|
+
throw new Error(
|
|
20
|
+
`moduleObj must be an object, not ${utils.getType(moduleObj)}!`,
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const utils = require('../utils');
|
|
4
|
+
|
|
5
|
+
module.exports = (defaultFuncs, api, ctx) => {
|
|
6
|
+
return async function addUserToGroup(userID, threadID, callback) {
|
|
7
|
+
let resolveFunc = () => {};
|
|
8
|
+
let rejectFunc = () => {};
|
|
9
|
+
const returnPromise = new Promise((resolve, reject) => {
|
|
10
|
+
resolveFunc = resolve;
|
|
11
|
+
rejectFunc = reject;
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
if (!callback) {
|
|
15
|
+
callback = (err, result) => {
|
|
16
|
+
if (err) return rejectFunc(err);
|
|
17
|
+
resolveFunc(result);
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
if (!ctx.mqttClient) {
|
|
23
|
+
throw new Error("Not connected to MQTT. Please use listenMqtt first.");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (utils.getType(threadID) !== "Number" && utils.getType(threadID) !== "String") {
|
|
27
|
+
throw new Error("ThreadID should be of type Number or String");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (utils.getType(userID) !== "Array") {
|
|
31
|
+
userID = [userID];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const reqID = ++ctx.wsReqNumber;
|
|
35
|
+
const taskID = ++ctx.wsTaskNumber;
|
|
36
|
+
|
|
37
|
+
const payload = {
|
|
38
|
+
epoch_id: utils.generateOfflineThreadingID(),
|
|
39
|
+
tasks: [
|
|
40
|
+
{
|
|
41
|
+
failure_count: null,
|
|
42
|
+
label: "23",
|
|
43
|
+
payload: JSON.stringify({
|
|
44
|
+
thread_key: threadID,
|
|
45
|
+
contact_ids: userID,
|
|
46
|
+
sync_group: 1
|
|
47
|
+
}),
|
|
48
|
+
queue_name: threadID.toString(),
|
|
49
|
+
task_id: taskID
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
version_id: "24502707779384158"
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const form = JSON.stringify({
|
|
56
|
+
app_id: "772021112871879",
|
|
57
|
+
payload: JSON.stringify(payload),
|
|
58
|
+
request_id: reqID,
|
|
59
|
+
type: 3
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
let responseHandled = false;
|
|
63
|
+
const handleRes = (topic, message) => {
|
|
64
|
+
if (topic !== "/ls_resp" || responseHandled) return;
|
|
65
|
+
let jsonMsg;
|
|
66
|
+
try {
|
|
67
|
+
jsonMsg = JSON.parse(message.toString());
|
|
68
|
+
jsonMsg.payload = JSON.parse(jsonMsg.payload);
|
|
69
|
+
} catch {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (jsonMsg.request_id !== reqID) return;
|
|
73
|
+
responseHandled = true;
|
|
74
|
+
clearTimeout(timeout);
|
|
75
|
+
ctx.mqttClient.removeListener("message", handleRes);
|
|
76
|
+
callback(null, { success: true, response: jsonMsg.payload });
|
|
77
|
+
resolveFunc({ success: true, response: jsonMsg.payload });
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const timeout = setTimeout(() => {
|
|
81
|
+
if (!responseHandled) {
|
|
82
|
+
responseHandled = true;
|
|
83
|
+
ctx.mqttClient.removeListener("message", handleRes);
|
|
84
|
+
const err = new Error("MQTT request timeout");
|
|
85
|
+
callback(err);
|
|
86
|
+
rejectFunc(err);
|
|
87
|
+
}
|
|
88
|
+
}, 30000);
|
|
89
|
+
|
|
90
|
+
ctx.mqttClient.on("message", handleRes);
|
|
91
|
+
ctx.mqttClient.publish("/ls_req", form, { qos: 1, retain: false }, (err) => {
|
|
92
|
+
if (err && !responseHandled) {
|
|
93
|
+
responseHandled = true;
|
|
94
|
+
clearTimeout(timeout);
|
|
95
|
+
ctx.mqttClient.removeListener("message", handleRes);
|
|
96
|
+
callback(err);
|
|
97
|
+
rejectFunc(err);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
} catch (err) {
|
|
101
|
+
utils.error("addUserToGroup", err);
|
|
102
|
+
callback(err);
|
|
103
|
+
rejectFunc(err);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return returnPromise;
|
|
107
|
+
};
|
|
108
|
+
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const utils = require('../utils');
|
|
4
|
+
|
|
5
|
+
function generateOfflineThreadingID() {
|
|
6
|
+
return Date.now().toString() + Math.floor(Math.random() * 1000000).toString();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function getType(obj) {
|
|
10
|
+
return Object.prototype.toString.call(obj).slice(8, -1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports = function (defaultFuncs, api, ctx) {
|
|
14
|
+
return function changeAdminStatus(threadID, adminID, adminStatus, callback) {
|
|
15
|
+
let resolveFunc = function() {};
|
|
16
|
+
let rejectFunc = function() {};
|
|
17
|
+
const returnPromise = new Promise(function(resolve, reject) {
|
|
18
|
+
resolveFunc = resolve;
|
|
19
|
+
rejectFunc = reject;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
if (!callback) {
|
|
23
|
+
callback = function(err, data) {
|
|
24
|
+
if (err) return rejectFunc(err);
|
|
25
|
+
resolveFunc(data);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (getType(threadID) !== "String") {
|
|
30
|
+
return callback({ error: "changeAdminStatus: threadID must be a string" });
|
|
31
|
+
}
|
|
32
|
+
if (getType(adminID) !== "String" && getType(adminID) !== "Array") {
|
|
33
|
+
return callback({ error: "changeAdminStatus: adminID must be a string or an array" });
|
|
34
|
+
}
|
|
35
|
+
if (getType(adminStatus) !== "Boolean") {
|
|
36
|
+
return callback({ error: "changeAdminStatus: adminStatus must be true or false" });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (ctx.mqttClient) {
|
|
40
|
+
const tasks = [];
|
|
41
|
+
const isAdmin = adminStatus ? 1 : 0;
|
|
42
|
+
const epochID = generateOfflineThreadingID();
|
|
43
|
+
|
|
44
|
+
if (getType(adminID) === "Array") {
|
|
45
|
+
adminID.forEach((id, index) => {
|
|
46
|
+
tasks.push({
|
|
47
|
+
failure_count: null,
|
|
48
|
+
label: "25",
|
|
49
|
+
payload: JSON.stringify({
|
|
50
|
+
thread_key: threadID,
|
|
51
|
+
contact_id: id,
|
|
52
|
+
is_admin: isAdmin
|
|
53
|
+
}),
|
|
54
|
+
queue_name: "admin_status",
|
|
55
|
+
task_id: index + 1
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
} else {
|
|
59
|
+
tasks.push({
|
|
60
|
+
failure_count: null,
|
|
61
|
+
label: "25",
|
|
62
|
+
payload: JSON.stringify({
|
|
63
|
+
thread_key: threadID,
|
|
64
|
+
contact_id: adminID,
|
|
65
|
+
is_admin: isAdmin
|
|
66
|
+
}),
|
|
67
|
+
queue_name: "admin_status",
|
|
68
|
+
task_id: 1
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let count_req = 0;
|
|
73
|
+
const form = JSON.stringify({
|
|
74
|
+
app_id: "2220391788200892",
|
|
75
|
+
payload: JSON.stringify({
|
|
76
|
+
epoch_id: epochID,
|
|
77
|
+
tasks: tasks,
|
|
78
|
+
version_id: "8798795233522156"
|
|
79
|
+
}),
|
|
80
|
+
request_id: ++count_req,
|
|
81
|
+
type: 3
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
ctx.mqttClient.publish("/ls_req", form, {}, (err, _packet) => {
|
|
85
|
+
if (err) {
|
|
86
|
+
utils.error("changeAdminStatus (MQTT)", err);
|
|
87
|
+
return callback(err);
|
|
88
|
+
} else {
|
|
89
|
+
utils.log("Admin status changed successfully via MQTT");
|
|
90
|
+
return callback(null, { success: true });
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
} else {
|
|
94
|
+
utils.warn("MQTT client not available, using HTTP fallback for changeAdminStatus");
|
|
95
|
+
const tasks = [];
|
|
96
|
+
const epochID = generateOfflineThreadingID();
|
|
97
|
+
|
|
98
|
+
if (getType(adminID) === "Array") {
|
|
99
|
+
adminID.forEach((id, index) => {
|
|
100
|
+
tasks.push({
|
|
101
|
+
label: '25',
|
|
102
|
+
payload: JSON.stringify({ thread_key: threadID, contact_id: id, is_admin: adminStatus }),
|
|
103
|
+
queue_name: 'admin_status',
|
|
104
|
+
task_id: index + 1,
|
|
105
|
+
failure_count: null
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
} else {
|
|
109
|
+
tasks.push({
|
|
110
|
+
label: '25',
|
|
111
|
+
payload: JSON.stringify({ thread_key: threadID, contact_id: adminID, is_admin: adminStatus }),
|
|
112
|
+
queue_name: 'admin_status',
|
|
113
|
+
task_id: 1,
|
|
114
|
+
failure_count: null
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const form = {
|
|
119
|
+
fb_dtsg: ctx.fb_dtsg,
|
|
120
|
+
request_id: 1,
|
|
121
|
+
type: 3,
|
|
122
|
+
payload: {
|
|
123
|
+
version_id: '3816854585040595',
|
|
124
|
+
tasks: tasks,
|
|
125
|
+
epoch_id: epochID,
|
|
126
|
+
data_trace_id: null
|
|
127
|
+
},
|
|
128
|
+
app_id: '772021112871879'
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
form.payload = JSON.stringify(form.payload);
|
|
132
|
+
|
|
133
|
+
defaultFuncs
|
|
134
|
+
.post("https://www.facebook.com/api/graphqlbatch/", ctx.jar, form)
|
|
135
|
+
.then(utils.parseAndCheckLogin(ctx, defaultFuncs))
|
|
136
|
+
.then(() => {
|
|
137
|
+
utils.log("Admin status changed successfully via HTTP");
|
|
138
|
+
callback(null, { success: true });
|
|
139
|
+
})
|
|
140
|
+
.catch(err => {
|
|
141
|
+
utils.error("changeAdminStatus (HTTP)", err);
|
|
142
|
+
callback(err);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return returnPromise;
|
|
147
|
+
};
|
|
148
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const utils = require('../utils');
|
|
4
|
+
|
|
5
|
+
module.exports = (defaultFuncs, api, ctx) => {
|
|
6
|
+
return async function changeArchivedStatus(threadIDs, archive, callback) {
|
|
7
|
+
let resolveFunc = () => {};
|
|
8
|
+
let rejectFunc = () => {};
|
|
9
|
+
const returnPromise = new Promise((resolve, reject) => {
|
|
10
|
+
resolveFunc = resolve;
|
|
11
|
+
rejectFunc = reject;
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
if (!callback) {
|
|
15
|
+
callback = (err, result) => {
|
|
16
|
+
if (err) return rejectFunc(err);
|
|
17
|
+
resolveFunc(result);
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
if (utils.getType(archive) === "Function") {
|
|
23
|
+
callback = archive;
|
|
24
|
+
archive = true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (utils.getType(archive) !== "Boolean") {
|
|
28
|
+
throw new Error("archive parameter must be a boolean");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!Array.isArray(threadIDs)) {
|
|
32
|
+
threadIDs = [threadIDs];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const form = {
|
|
36
|
+
should_archive: archive
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
threadIDs.forEach(id => {
|
|
40
|
+
form[`thread_fbids[${id}]`] = true;
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const res = await defaultFuncs.post(
|
|
44
|
+
"https://www.facebook.com/ajax/mercury/change_archived_status.php",
|
|
45
|
+
ctx.jar,
|
|
46
|
+
form
|
|
47
|
+
).then(utils.parseAndCheckLogin(ctx, defaultFuncs));
|
|
48
|
+
|
|
49
|
+
if (res && res.error) {
|
|
50
|
+
throw res;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
callback(null, { success: true });
|
|
54
|
+
} catch (err) {
|
|
55
|
+
utils.error("changeArchivedStatus", err);
|
|
56
|
+
callback(err);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return returnPromise;
|
|
60
|
+
};
|
|
61
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const utils = require('../utils');
|
|
4
|
+
|
|
5
|
+
module.exports = (defaultFuncs, api, ctx) => {
|
|
6
|
+
async function handleUpload(image) {
|
|
7
|
+
const form = {
|
|
8
|
+
profile_id: ctx.userID,
|
|
9
|
+
photo_source: 57,
|
|
10
|
+
av: ctx.userID,
|
|
11
|
+
file: image
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
return defaultFuncs.postFormData(
|
|
15
|
+
"https://www.facebook.com/profile/picture/upload/",
|
|
16
|
+
ctx.jar,
|
|
17
|
+
form,
|
|
18
|
+
{}
|
|
19
|
+
).then(utils.parseAndCheckLogin(ctx, defaultFuncs))
|
|
20
|
+
.then(resData => {
|
|
21
|
+
if (resData.error) throw resData;
|
|
22
|
+
return resData;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return async function changeAvatar(image, caption, timestamp, callback) {
|
|
27
|
+
let resolveFunc = () => {};
|
|
28
|
+
let rejectFunc = () => {};
|
|
29
|
+
const returnPromise = new Promise((resolve, reject) => {
|
|
30
|
+
resolveFunc = resolve;
|
|
31
|
+
rejectFunc = reject;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (!timestamp && utils.getType(caption) === "Number") {
|
|
35
|
+
timestamp = caption;
|
|
36
|
+
caption = "";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!timestamp && !callback && (utils.getType(caption) === "Function")) {
|
|
40
|
+
callback = caption;
|
|
41
|
+
caption = "";
|
|
42
|
+
timestamp = null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!callback) {
|
|
46
|
+
callback = (err, data) => {
|
|
47
|
+
if (err) return rejectFunc(err);
|
|
48
|
+
resolveFunc(data);
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
if (!utils.isReadableStream(image)) {
|
|
54
|
+
throw new Error("Image is not a readable stream");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const payload = await handleUpload(image);
|
|
58
|
+
|
|
59
|
+
const form = {
|
|
60
|
+
av: ctx.i_userID || ctx.userID,
|
|
61
|
+
fb_api_req_friendly_name: "ProfileCometProfilePictureSetMutation",
|
|
62
|
+
fb_api_caller_class: "RelayModern",
|
|
63
|
+
doc_id: "5066134240065849",
|
|
64
|
+
variables: JSON.stringify({
|
|
65
|
+
input: {
|
|
66
|
+
caption: caption || "",
|
|
67
|
+
existing_photo_id: payload.payload.fbid,
|
|
68
|
+
expiration_time: timestamp,
|
|
69
|
+
profile_id: ctx.i_userID || ctx.userID,
|
|
70
|
+
profile_pic_method: "EXISTING",
|
|
71
|
+
profile_pic_source: "TIMELINE",
|
|
72
|
+
scaled_crop_rect: {
|
|
73
|
+
height: 1,
|
|
74
|
+
width: 1,
|
|
75
|
+
x: 0,
|
|
76
|
+
y: 0
|
|
77
|
+
},
|
|
78
|
+
skip_cropping: true,
|
|
79
|
+
actor_id: ctx.i_userID || ctx.userID,
|
|
80
|
+
client_mutation_id: Math.round(Math.random() * 19).toString()
|
|
81
|
+
},
|
|
82
|
+
isPage: false,
|
|
83
|
+
isProfile: true,
|
|
84
|
+
scale: 3
|
|
85
|
+
})
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const res = await defaultFuncs.post("https://www.facebook.com/api/graphql/", ctx.jar, form)
|
|
89
|
+
.then(utils.parseAndCheckLogin(ctx, defaultFuncs));
|
|
90
|
+
|
|
91
|
+
if (res.errors) {
|
|
92
|
+
throw res;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
callback(null, res[0].data.profile_picture_set);
|
|
96
|
+
} catch (err) {
|
|
97
|
+
utils.error("changeAvatar", err);
|
|
98
|
+
callback(err);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return returnPromise;
|
|
102
|
+
};
|
|
103
|
+
};
|