@dongdev/fca-unofficial 3.0.30 → 3.0.31
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 +3 -0
- package/package.json +2 -1
- package/src/api/http/httpGet.js +2 -2
- package/src/api/messaging/addUserToGroup.js +1 -1
- package/src/api/messaging/changeGroupImage.js +1 -1
- package/src/api/messaging/changeNickname.js +1 -1
- package/src/api/messaging/changeThreadColor.js +1 -1
- package/src/api/messaging/editMessage.js +1 -1
- package/src/api/messaging/searchForThread.js +2 -1
- package/src/api/messaging/sendTypingIndicator.js +1 -1
- package/src/api/messaging/setMessageReaction.js +3 -4
- package/src/api/messaging/unsendMessage.js +1 -1
- package/src/api/threads/getThreadInfo.js +2 -1
- package/src/api/users/getUserInfo.js +7 -4
- package/src/database/helpers.js +53 -0
- package/src/database/models/index.js +2 -1
- package/src/database/threadData.js +49 -53
- package/src/database/userData.js +46 -37
- package/src/utils/format/attachment.js +357 -0
- package/src/utils/format/cookie.js +9 -0
- package/src/utils/format/date.js +50 -0
- package/src/utils/format/decode.js +44 -0
- package/src/utils/format/delta.js +194 -0
- package/src/utils/format/ids.js +64 -0
- package/src/utils/format/index.js +64 -0
- package/src/utils/format/message.js +88 -0
- package/src/utils/format/presence.js +132 -0
- package/src/utils/format/readTyp.js +44 -0
- package/src/utils/format/thread.js +42 -0
- package/src/utils/format/utils.js +141 -0
- package/src/utils/loginParser/autoLogin.js +125 -0
- package/src/utils/loginParser/helpers.js +43 -0
- package/src/utils/loginParser/index.js +10 -0
- package/src/utils/loginParser/parseAndCheckLogin.js +220 -0
- package/src/utils/loginParser/textUtils.js +28 -0
- package/src/utils/request/client.js +26 -0
- package/src/utils/request/config.js +23 -0
- package/src/utils/request/defaults.js +46 -0
- package/src/utils/request/helpers.js +46 -0
- package/src/utils/request/index.js +17 -0
- package/src/utils/request/methods.js +163 -0
- package/src/utils/request/proxy.js +21 -0
- package/src/utils/request/retry.js +77 -0
- package/src/utils/request/sanitize.js +49 -0
- package/src/utils/format.js +0 -1174
- package/src/utils/loginParser.js +0 -365
- package/src/utils/messageFormat.js +0 -1173
- package/src/utils/request.js +0 -332
package/src/utils/request.js
DELETED
|
@@ -1,332 +0,0 @@
|
|
|
1
|
-
const axios = require("axios");
|
|
2
|
-
const { CookieJar } = require("tough-cookie");
|
|
3
|
-
const { wrapper } = require("axios-cookiejar-support");
|
|
4
|
-
const FormData = require("form-data");
|
|
5
|
-
const { HttpsProxyAgent } = require("https-proxy-agent");
|
|
6
|
-
const { Readable } = require("stream");
|
|
7
|
-
|
|
8
|
-
const headersMod = require("./headers");
|
|
9
|
-
const getHeaders = headersMod.getHeaders || headersMod;
|
|
10
|
-
const formatMod = require("./format");
|
|
11
|
-
const getType = formatMod.getType || formatMod;
|
|
12
|
-
const constMod = require("./constants");
|
|
13
|
-
const getFrom = constMod.getFrom || constMod;
|
|
14
|
-
|
|
15
|
-
// Sanitize header value to remove invalid characters
|
|
16
|
-
function sanitizeHeaderValue(value) {
|
|
17
|
-
if (value === null || value === undefined) return "";
|
|
18
|
-
const str = String(value);
|
|
19
|
-
// Remove invalid characters for HTTP headers:
|
|
20
|
-
// - Control characters (0x00-0x1F, except HTAB 0x09)
|
|
21
|
-
// - DEL character (0x7F)
|
|
22
|
-
// - Newlines and carriage returns
|
|
23
|
-
return str.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F\r\n]/g, "").trim();
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Sanitize header name to ensure it's valid
|
|
27
|
-
function sanitizeHeaderName(name) {
|
|
28
|
-
if (!name || typeof name !== "string") return "";
|
|
29
|
-
// Remove invalid characters for HTTP header names
|
|
30
|
-
return name.replace(/[^\x21-\x7E]/g, "").trim();
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Sanitize all headers in an object
|
|
34
|
-
function sanitizeHeaders(headers) {
|
|
35
|
-
if (!headers || typeof headers !== "object") return {};
|
|
36
|
-
const sanitized = {};
|
|
37
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
38
|
-
const sanitizedKey = sanitizeHeaderName(key);
|
|
39
|
-
if (!sanitizedKey) continue;
|
|
40
|
-
|
|
41
|
-
// Handle arrays - skip them entirely
|
|
42
|
-
if (Array.isArray(value)) continue;
|
|
43
|
-
|
|
44
|
-
// Handle objects - skip them
|
|
45
|
-
if (value !== null && typeof value === "object") continue;
|
|
46
|
-
|
|
47
|
-
// Handle functions - skip them
|
|
48
|
-
if (typeof value === "function") continue;
|
|
49
|
-
|
|
50
|
-
// Check if string value looks like a stringified array (e.g., "["performAutoLogin"]")
|
|
51
|
-
if (typeof value === "string") {
|
|
52
|
-
const trimmed = value.trim();
|
|
53
|
-
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
54
|
-
// Try to parse as JSON array - if successful, skip this header
|
|
55
|
-
try {
|
|
56
|
-
const parsed = JSON.parse(trimmed);
|
|
57
|
-
if (Array.isArray(parsed)) {
|
|
58
|
-
continue; // Skip stringified arrays
|
|
59
|
-
}
|
|
60
|
-
} catch {
|
|
61
|
-
// Not valid JSON array, continue with normal sanitization
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Sanitize the value
|
|
67
|
-
const sanitizedValue = sanitizeHeaderValue(value);
|
|
68
|
-
if (sanitizedValue !== "") {
|
|
69
|
-
sanitized[sanitizedKey] = sanitizedValue;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return sanitized;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const jar = new CookieJar();
|
|
76
|
-
const client = wrapper(axios.create({
|
|
77
|
-
jar,
|
|
78
|
-
withCredentials: true,
|
|
79
|
-
timeout: 60000,
|
|
80
|
-
validateStatus: s => s >= 200 && s < 600,
|
|
81
|
-
maxRedirects: 5,
|
|
82
|
-
maxContentLength: Infinity,
|
|
83
|
-
maxBodyLength: Infinity
|
|
84
|
-
}));
|
|
85
|
-
|
|
86
|
-
const delay = ms => new Promise(r => setTimeout(r, ms));
|
|
87
|
-
|
|
88
|
-
async function requestWithRetry(fn, retries = 3, baseDelay = 1000, ctx) {
|
|
89
|
-
let lastError;
|
|
90
|
-
const emit = (event, payload) => {
|
|
91
|
-
try {
|
|
92
|
-
if (ctx && ctx._emitter && typeof ctx._emitter.emit === "function") {
|
|
93
|
-
ctx._emitter.emit(event, payload);
|
|
94
|
-
}
|
|
95
|
-
} catch { }
|
|
96
|
-
};
|
|
97
|
-
for (let i = 0; i < retries; i++) {
|
|
98
|
-
try {
|
|
99
|
-
return await fn();
|
|
100
|
-
} catch (e) {
|
|
101
|
-
lastError = e;
|
|
102
|
-
|
|
103
|
-
// Handle ERR_INVALID_CHAR and other header errors - don't retry, return error immediately
|
|
104
|
-
if (e?.code === "ERR_INVALID_CHAR" || (e?.message && e.message.includes("Invalid character in header"))) {
|
|
105
|
-
const err = new Error("Invalid header content detected. Request aborted to prevent crash.");
|
|
106
|
-
err.error = "Invalid header content";
|
|
107
|
-
err.originalError = e;
|
|
108
|
-
err.code = "ERR_INVALID_CHAR";
|
|
109
|
-
return Promise.reject(err);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Don't retry on client errors (4xx) except 429 (rate limit)
|
|
113
|
-
const status = e?.response?.status || e?.statusCode || 0;
|
|
114
|
-
const url = e?.config?.url || "";
|
|
115
|
-
const method = String(e?.config?.method || "").toUpperCase();
|
|
116
|
-
if (status === 429) {
|
|
117
|
-
emit("rateLimit", { status, url, method });
|
|
118
|
-
}
|
|
119
|
-
if (status >= 400 && status < 500 && status !== 429) {
|
|
120
|
-
return e.response || Promise.reject(e);
|
|
121
|
-
}
|
|
122
|
-
// Don't retry on last attempt
|
|
123
|
-
if (i === retries - 1) {
|
|
124
|
-
return e.response || Promise.reject(e);
|
|
125
|
-
}
|
|
126
|
-
// Network errors (no status code)
|
|
127
|
-
const netCode = e?.code || "";
|
|
128
|
-
const msg = e && e.message ? e.message : String(e || "");
|
|
129
|
-
if (
|
|
130
|
-
!status &&
|
|
131
|
-
(netCode === "UND_ERR_CONNECT_TIMEOUT" ||
|
|
132
|
-
netCode === "ETIMEDOUT" ||
|
|
133
|
-
netCode === "ECONNRESET" ||
|
|
134
|
-
netCode === "ECONNREFUSED" ||
|
|
135
|
-
netCode === "ENOTFOUND" ||
|
|
136
|
-
/timeout|connect timeout|network error|fetch failed/i.test(msg))
|
|
137
|
-
) {
|
|
138
|
-
emit("networkError", { code: netCode, message: msg, url, method });
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Exponential backoff with jitter
|
|
142
|
-
const backoffDelay = Math.min(
|
|
143
|
-
baseDelay * Math.pow(2, i) + Math.floor(Math.random() * 200),
|
|
144
|
-
30000 // Max 30 seconds
|
|
145
|
-
);
|
|
146
|
-
await delay(backoffDelay);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
// Return error instead of throwing to prevent uncaught exception
|
|
150
|
-
const finalError = lastError || new Error("Request failed after retries");
|
|
151
|
-
return Promise.reject(finalError);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function cfg(base = {}) {
|
|
155
|
-
const { reqJar, headers, params, agent, timeout } = base;
|
|
156
|
-
return {
|
|
157
|
-
headers: sanitizeHeaders(headers),
|
|
158
|
-
params,
|
|
159
|
-
jar: reqJar || jar,
|
|
160
|
-
withCredentials: true,
|
|
161
|
-
timeout: timeout || 60000,
|
|
162
|
-
httpAgent: agent || client.defaults.httpAgent,
|
|
163
|
-
httpsAgent: agent || client.defaults.httpsAgent,
|
|
164
|
-
proxy: false,
|
|
165
|
-
validateStatus: s => s >= 200 && s < 600
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function toStringVal(v) {
|
|
170
|
-
if (v === undefined || v === null) return "";
|
|
171
|
-
if (typeof v === "bigint") return v.toString();
|
|
172
|
-
if (typeof v === "boolean") return v ? "true" : "false";
|
|
173
|
-
return String(v);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function isStream(v) {
|
|
177
|
-
return v && typeof v === "object" && typeof v.pipe === "function" && typeof v.on === "function";
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function isBlobLike(v) {
|
|
181
|
-
return v && typeof v.arrayBuffer === "function" && (typeof v.type === "string" || typeof v.name === "string");
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function isPairArrayList(arr) {
|
|
185
|
-
return Array.isArray(arr) && arr.length > 0 && arr.every(x => Array.isArray(x) && x.length === 2 && typeof x[0] === "string");
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function cleanGet(url, ctx) {
|
|
189
|
-
return requestWithRetry(() => client.get(url, cfg()), 3, 1000, ctx);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function get(url, reqJar, qs, options, ctx, customHeader) {
|
|
193
|
-
const headers = getHeaders(url, options, ctx, customHeader);
|
|
194
|
-
return requestWithRetry(() => client.get(url, cfg({ reqJar, headers, params: qs })), 3, 1000, ctx);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function post(url, reqJar, form, options, ctx, customHeader) {
|
|
198
|
-
const headers = getHeaders(url, options, ctx, customHeader);
|
|
199
|
-
const ct = String(headers["Content-Type"] || headers["content-type"] || "application/x-www-form-urlencoded").toLowerCase();
|
|
200
|
-
let data;
|
|
201
|
-
if (ct.includes("json")) {
|
|
202
|
-
data = JSON.stringify(form || {});
|
|
203
|
-
headers["Content-Type"] = "application/json";
|
|
204
|
-
} else {
|
|
205
|
-
const p = new URLSearchParams();
|
|
206
|
-
if (form && typeof form === "object") {
|
|
207
|
-
for (const k of Object.keys(form)) {
|
|
208
|
-
let v = form[k];
|
|
209
|
-
if (isPairArrayList(v)) {
|
|
210
|
-
for (const [kk, vv] of v) p.append(`${k}[${kk}]`, toStringVal(vv));
|
|
211
|
-
continue;
|
|
212
|
-
}
|
|
213
|
-
if (Array.isArray(v)) {
|
|
214
|
-
for (const x of v) {
|
|
215
|
-
if (Array.isArray(x) && x.length === 2 && typeof x[1] !== "object") p.append(k, toStringVal(x[1]));
|
|
216
|
-
else p.append(k, toStringVal(x));
|
|
217
|
-
}
|
|
218
|
-
continue;
|
|
219
|
-
}
|
|
220
|
-
if (getType(v) === "Object") v = JSON.stringify(v);
|
|
221
|
-
p.append(k, toStringVal(v));
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
data = p.toString();
|
|
225
|
-
headers["Content-Type"] = "application/x-www-form-urlencoded";
|
|
226
|
-
}
|
|
227
|
-
return requestWithRetry(() => client.post(url, data, cfg({ reqJar, headers })), 3, 1000, ctx);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
async function postFormData(url, reqJar, form, qs, options, ctx) {
|
|
231
|
-
const fd = new FormData();
|
|
232
|
-
if (form && typeof form === "object") {
|
|
233
|
-
for (const k of Object.keys(form)) {
|
|
234
|
-
const v = form[k];
|
|
235
|
-
if (v === undefined || v === null) continue;
|
|
236
|
-
if (isPairArrayList(v)) {
|
|
237
|
-
for (const [kk, vv] of v) fd.append(`${k}[${kk}]`, typeof vv === "object" && !Buffer.isBuffer(vv) && !isStream(vv) ? JSON.stringify(vv) : vv);
|
|
238
|
-
continue;
|
|
239
|
-
}
|
|
240
|
-
if (Array.isArray(v)) {
|
|
241
|
-
for (const x of v) {
|
|
242
|
-
if (Array.isArray(x) && x.length === 2 && x[1] && typeof x[1] === "object" && !Buffer.isBuffer(x[1]) && !isStream(x[1])) {
|
|
243
|
-
fd.append(k, x[0], x[1]);
|
|
244
|
-
} else if (Array.isArray(x) && x.length === 2 && typeof x[1] !== "object") {
|
|
245
|
-
fd.append(k, toStringVal(x[1]));
|
|
246
|
-
} else if (x && typeof x === "object" && "value" in x && "options" in x) {
|
|
247
|
-
fd.append(k, x.value, x.options || {});
|
|
248
|
-
} else if (isStream(x) || Buffer.isBuffer(x) || typeof x === "string") {
|
|
249
|
-
fd.append(k, x);
|
|
250
|
-
} else if (isBlobLike(x)) {
|
|
251
|
-
const buf = Buffer.from(await x.arrayBuffer());
|
|
252
|
-
fd.append(k, buf, { filename: x.name || k, contentType: x.type || undefined });
|
|
253
|
-
} else {
|
|
254
|
-
fd.append(k, JSON.stringify(x));
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
continue;
|
|
258
|
-
}
|
|
259
|
-
if (v && typeof v === "object" && "value" in v && "options" in v) {
|
|
260
|
-
fd.append(k, v.value, v.options || {});
|
|
261
|
-
continue;
|
|
262
|
-
}
|
|
263
|
-
if (isStream(v) || Buffer.isBuffer(v) || typeof v === "string") {
|
|
264
|
-
fd.append(k, v);
|
|
265
|
-
continue;
|
|
266
|
-
}
|
|
267
|
-
if (isBlobLike(v)) {
|
|
268
|
-
const buf = Buffer.from(await v.arrayBuffer());
|
|
269
|
-
fd.append(k, buf, { filename: v.name || k, contentType: v.type || undefined });
|
|
270
|
-
continue;
|
|
271
|
-
}
|
|
272
|
-
if (typeof v === "number" || typeof v === "boolean") {
|
|
273
|
-
fd.append(k, toStringVal(v));
|
|
274
|
-
continue;
|
|
275
|
-
}
|
|
276
|
-
fd.append(k, JSON.stringify(v));
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
const headers = { ...getHeaders(url, options, ctx), ...fd.getHeaders() };
|
|
280
|
-
return requestWithRetry(() => client.post(url, fd, cfg({ reqJar, headers, params: qs })), 3, 1000, ctx);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function makeDefaults(html, userID, ctx) {
|
|
284
|
-
let reqCounter = 1;
|
|
285
|
-
const revision = getFrom(html || "", 'revision":', ",") || getFrom(html || "", '"client_revision":', ",") || "";
|
|
286
|
-
function mergeWithDefaults(obj) {
|
|
287
|
-
const base = {
|
|
288
|
-
av: userID,
|
|
289
|
-
__user: userID,
|
|
290
|
-
__req: (reqCounter++).toString(36),
|
|
291
|
-
__rev: revision,
|
|
292
|
-
__a: 1
|
|
293
|
-
};
|
|
294
|
-
if (ctx?.fb_dtsg) base.fb_dtsg = ctx.fb_dtsg;
|
|
295
|
-
if (ctx?.jazoest) base.jazoest = ctx.jazoest;
|
|
296
|
-
if (!obj) return base;
|
|
297
|
-
for (const k of Object.keys(obj)) if (!(k in base)) base[k] = obj[k];
|
|
298
|
-
return base;
|
|
299
|
-
}
|
|
300
|
-
return {
|
|
301
|
-
get: (url, j, qs, ctxx, customHeader = {}) =>
|
|
302
|
-
get(url, j, mergeWithDefaults(qs), ctx?.globalOptions, ctxx || ctx, customHeader),
|
|
303
|
-
post: (url, j, form, ctxx, customHeader = {}) =>
|
|
304
|
-
post(url, j, mergeWithDefaults(form), ctx?.globalOptions, ctxx || ctx, customHeader),
|
|
305
|
-
postFormData: (url, j, form, qs, ctxx) =>
|
|
306
|
-
postFormData(url, j, mergeWithDefaults(form), mergeWithDefaults(qs), ctx?.globalOptions, ctxx || ctx)
|
|
307
|
-
};
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
function setProxy(proxyUrl) {
|
|
311
|
-
if (!proxyUrl) {
|
|
312
|
-
client.defaults.httpAgent = undefined;
|
|
313
|
-
client.defaults.httpsAgent = undefined;
|
|
314
|
-
client.defaults.proxy = false;
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
const agent = new HttpsProxyAgent(proxyUrl);
|
|
318
|
-
client.defaults.httpAgent = agent;
|
|
319
|
-
client.defaults.httpsAgent = agent;
|
|
320
|
-
client.defaults.proxy = false;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
module.exports = {
|
|
324
|
-
cleanGet,
|
|
325
|
-
get,
|
|
326
|
-
post,
|
|
327
|
-
postFormData,
|
|
328
|
-
jar,
|
|
329
|
-
setProxy,
|
|
330
|
-
makeDefaults,
|
|
331
|
-
client
|
|
332
|
-
};
|