@dongdev/fca-unofficial 3.0.28 → 3.0.30
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 -132
- package/DOCS.md +82 -3
- package/README.md +524 -632
- package/func/logAdapter.js +33 -0
- package/index.d.ts +6 -0
- package/module/config.js +11 -1
- package/module/loginHelper.js +63 -7
- package/package.json +88 -81
- package/src/api/action/changeAvatar.js +1 -1
- package/src/api/action/changeBio.js +1 -1
- package/src/api/action/handleFriendRequest.js +1 -1
- package/src/api/action/logout.js +1 -1
- package/src/api/action/refreshFb_dtsg.js +1 -1
- package/src/api/action/setPostReaction.js +1 -1
- package/src/api/action/unfriend.js +1 -1
- package/src/api/http/postFormData.js +1 -1
- package/src/api/messaging/changeArchivedStatus.js +1 -1
- package/src/api/messaging/changeBlockedStatus.js +1 -1
- package/src/api/messaging/changeGroupImage.js +1 -1
- package/src/api/messaging/changeNickname.js +1 -1
- package/src/api/messaging/changeThreadEmoji.js +1 -1
- package/src/api/messaging/createNewGroup.js +1 -1
- package/src/api/messaging/createThemeAI.js +1 -1
- package/src/api/messaging/deleteMessage.js +1 -1
- package/src/api/messaging/deleteThread.js +1 -1
- package/src/api/messaging/getFriendsList.js +1 -1
- package/src/api/messaging/getMessage.js +1 -1
- package/src/api/messaging/getThemePictures.js +1 -1
- package/src/api/messaging/handleMessageRequest.js +1 -1
- package/src/api/messaging/markAsDelivered.js +1 -1
- package/src/api/messaging/markAsRead.js +1 -1
- package/src/api/messaging/markAsReadAll.js +1 -1
- package/src/api/messaging/markAsSeen.js +1 -1
- package/src/api/messaging/muteThread.js +1 -1
- package/src/api/messaging/resolvePhotoUrl.js +1 -1
- package/src/api/messaging/sendMessage.js +1 -1
- package/src/api/messaging/setTitle.js +1 -1
- package/src/api/messaging/unsendMessage.js +1 -1
- package/src/api/messaging/uploadAttachment.js +1 -1
- package/src/api/socket/core/connectMqtt.js +16 -8
- package/src/api/socket/core/emitAuth.js +4 -0
- package/src/api/socket/core/getSeqID.js +6 -8
- package/src/api/socket/core/getTaskResponseData.js +3 -0
- package/src/api/socket/core/parseDelta.js +9 -0
- package/src/api/socket/detail/buildStream.js +11 -4
- package/src/api/socket/detail/constants.js +4 -0
- package/src/api/socket/listenMqtt.js +11 -5
- package/src/api/threads/getThreadHistory.js +1 -1
- package/src/api/threads/getThreadInfo.js +245 -388
- package/src/api/threads/getThreadList.js +1 -1
- package/src/api/threads/getThreadPictures.js +1 -1
- package/src/api/users/getUserID.js +1 -1
- package/src/api/users/getUserInfo.js +80 -8
- package/src/database/models/thread.js +5 -0
- package/src/remote/remoteClient.js +123 -0
- package/src/utils/broadcast.js +51 -0
- package/src/utils/loginParser.js +19 -1
- package/src/utils/request.js +33 -6
- package/.gitattributes +0 -2
- package/Fca_Database/database.sqlite +0 -0
- package/LICENSE-MIT +0 -21
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
const log = require("
|
|
3
|
+
const log = require("../../../func/logAdapter");
|
|
4
4
|
const { parseAndCheckLogin } = require("../../utils/client");
|
|
5
5
|
const { formatID, getType } = require("../../utils/format");
|
|
6
6
|
function createProfileUrl(url, username, id) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
const log = require("
|
|
3
|
+
const log = require("../../../func/logAdapter");
|
|
4
4
|
const { parseAndCheckLogin } = require("../../utils/client");
|
|
5
5
|
module.exports = function(defaultFuncs, api, ctx) {
|
|
6
6
|
return function getThreadPictures(threadID, offset, limit, callback) {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require("fs");
|
|
4
4
|
const path = require("path");
|
|
5
|
-
const log = require("
|
|
5
|
+
const log = require("../../../func/logAdapter");
|
|
6
6
|
const logger = require("../../../func/logger");
|
|
7
7
|
const { parseAndCheckLogin } = require("../../utils/client.js");
|
|
8
8
|
|
|
@@ -118,13 +118,81 @@ function mergeUserEntry(a, b) {
|
|
|
118
118
|
};
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
const queue = [];
|
|
122
|
-
let isProcessingQueue = false;
|
|
123
|
-
const processingUsers = new Set();
|
|
124
|
-
const queuedUsers = new Set();
|
|
125
|
-
const cooldown = new Map();
|
|
126
|
-
|
|
127
121
|
module.exports = function (defaultFuncs, api, ctx) {
|
|
122
|
+
// Read FastConfig-style anti-get-info flag from global config (if available)
|
|
123
|
+
const cfg = global.fca && global.fca.config;
|
|
124
|
+
const antiCfg = cfg && cfg.antiGetInfo;
|
|
125
|
+
const disableAntiUserInfo = !!(antiCfg && antiCfg.AntiGetUserInfo === true);
|
|
126
|
+
|
|
127
|
+
// Lightweight, Horizon-style implementation without database/queue
|
|
128
|
+
if (disableAntiUserInfo) {
|
|
129
|
+
function formatLegacyData(data) {
|
|
130
|
+
const retObj = {};
|
|
131
|
+
for (const prop in data) {
|
|
132
|
+
if (!Object.prototype.hasOwnProperty.call(data, prop)) continue;
|
|
133
|
+
const innerObj = data[prop] || {};
|
|
134
|
+
retObj[prop] = {
|
|
135
|
+
name: innerObj.name || null,
|
|
136
|
+
firstName: innerObj.firstName || null,
|
|
137
|
+
vanity: innerObj.vanity || null,
|
|
138
|
+
thumbSrc: innerObj.thumbSrc || null,
|
|
139
|
+
profileUrl: innerObj.uri || innerObj.profileUrl || null,
|
|
140
|
+
gender: innerObj.gender || null,
|
|
141
|
+
type: innerObj.type || null,
|
|
142
|
+
isFriend: !!innerObj.is_friend,
|
|
143
|
+
isBirthday: !!innerObj.is_birthday
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
return retObj;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return function getUserInfo(idsOrId, callback) {
|
|
150
|
+
let resolveFunc;
|
|
151
|
+
let rejectFunc;
|
|
152
|
+
const returnPromise = new Promise((resolve, reject) => {
|
|
153
|
+
resolveFunc = resolve;
|
|
154
|
+
rejectFunc = reject;
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
if (typeof callback !== "function") {
|
|
158
|
+
callback = (err, userInfo) => {
|
|
159
|
+
if (err) return rejectFunc(err);
|
|
160
|
+
resolveFunc(userInfo);
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const ids = Array.isArray(idsOrId) ? idsOrId : [idsOrId];
|
|
165
|
+
const form = {};
|
|
166
|
+
ids.forEach((v, i) => {
|
|
167
|
+
form[`ids[${i}]`] = v;
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
defaultFuncs
|
|
171
|
+
.post("https://www.facebook.com/chat/user_info/", ctx.jar, form)
|
|
172
|
+
.then(parseAndCheckLogin(ctx, defaultFuncs))
|
|
173
|
+
.then(resData => {
|
|
174
|
+
if (resData.error) throw resData;
|
|
175
|
+
const profiles = resData?.payload?.profiles || {};
|
|
176
|
+
return callback(null, formatLegacyData(profiles));
|
|
177
|
+
})
|
|
178
|
+
.catch(err => {
|
|
179
|
+
log.error(
|
|
180
|
+
"getUserInfo",
|
|
181
|
+
"Lỗi: getUserInfo Có Thể Do Bạn Spam Quá Nhiều !,Hãy Thử Lại !"
|
|
182
|
+
);
|
|
183
|
+
return callback(err);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
return returnPromise;
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const queue = [];
|
|
191
|
+
let isProcessingQueue = false;
|
|
192
|
+
const processingUsers = new Set();
|
|
193
|
+
const queuedUsers = new Set();
|
|
194
|
+
const cooldown = new Map();
|
|
195
|
+
|
|
128
196
|
const dbFiles = fs.readdirSync(path.join(__dirname, "../../database")).filter(f => path.extname(f) === ".js").reduce((acc, file) => {
|
|
129
197
|
acc[path.basename(file, ".js")] = require(path.join(__dirname, "../../database", file))(api);
|
|
130
198
|
return acc;
|
|
@@ -319,7 +387,11 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
319
387
|
}
|
|
320
388
|
return callback(null, ret);
|
|
321
389
|
}).catch(err => {
|
|
322
|
-
|
|
390
|
+
// Horizon-style anti-get-info message to hint rate limiting / spam block
|
|
391
|
+
log.error(
|
|
392
|
+
"getUserInfo",
|
|
393
|
+
"Lỗi: getUserInfo Có Thể Do Bạn Spam Quá Nhiều !,Hãy Thử Lại !"
|
|
394
|
+
);
|
|
323
395
|
callback(err);
|
|
324
396
|
});
|
|
325
397
|
return returnPromise;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const WebSocket = require("ws");
|
|
4
|
+
const logger = require("../../func/logger");
|
|
5
|
+
|
|
6
|
+
function createRemoteClient(api, ctx, cfg) {
|
|
7
|
+
if (!cfg || !cfg.enabled || !cfg.url) return null;
|
|
8
|
+
|
|
9
|
+
const url = String(cfg.url);
|
|
10
|
+
const token = cfg.token ? String(cfg.token) : null;
|
|
11
|
+
const autoReconnect = cfg.autoReconnect !== false;
|
|
12
|
+
const emitter = ctx && ctx._emitter;
|
|
13
|
+
|
|
14
|
+
let ws = null;
|
|
15
|
+
let closed = false;
|
|
16
|
+
let reconnectTimer = null;
|
|
17
|
+
|
|
18
|
+
function log(message, level = "info") {
|
|
19
|
+
logger(`[remote] ${message}`, level);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function scheduleReconnect() {
|
|
23
|
+
if (!autoReconnect || closed) return;
|
|
24
|
+
if (reconnectTimer) return;
|
|
25
|
+
reconnectTimer = setTimeout(() => {
|
|
26
|
+
reconnectTimer = null;
|
|
27
|
+
if (!closed) connect();
|
|
28
|
+
}, 5000);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function safeEmit(event, payload) {
|
|
32
|
+
try {
|
|
33
|
+
if (emitter && typeof emitter.emit === "function") {
|
|
34
|
+
emitter.emit(event, payload);
|
|
35
|
+
}
|
|
36
|
+
} catch { }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function connect() {
|
|
40
|
+
try {
|
|
41
|
+
ws = new WebSocket(url, {
|
|
42
|
+
headers: token ? { Authorization: `Bearer ${token}` } : undefined
|
|
43
|
+
});
|
|
44
|
+
} catch (e) {
|
|
45
|
+
log(`connect error: ${e && e.message ? e.message : String(e)}`, "warn");
|
|
46
|
+
scheduleReconnect();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
ws.on("open", () => {
|
|
51
|
+
log("connected", "info");
|
|
52
|
+
const payload = {
|
|
53
|
+
type: "hello",
|
|
54
|
+
userID: ctx && ctx.userID,
|
|
55
|
+
region: ctx && ctx.region,
|
|
56
|
+
version: require("../../package.json").version
|
|
57
|
+
};
|
|
58
|
+
try {
|
|
59
|
+
ws.send(JSON.stringify(payload));
|
|
60
|
+
} catch { }
|
|
61
|
+
safeEmit("remoteConnected", payload);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
ws.on("message", data => {
|
|
65
|
+
let msg;
|
|
66
|
+
try {
|
|
67
|
+
msg = JSON.parse(data.toString());
|
|
68
|
+
} catch {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (!msg || typeof msg !== "object") return;
|
|
72
|
+
|
|
73
|
+
switch (msg.type) {
|
|
74
|
+
case "ping":
|
|
75
|
+
try {
|
|
76
|
+
ws.send(JSON.stringify({ type: "pong" }));
|
|
77
|
+
} catch { }
|
|
78
|
+
break;
|
|
79
|
+
case "stop":
|
|
80
|
+
safeEmit("remoteStop", msg);
|
|
81
|
+
break;
|
|
82
|
+
case "broadcast":
|
|
83
|
+
safeEmit("remoteBroadcast", msg.payload || {});
|
|
84
|
+
break;
|
|
85
|
+
default:
|
|
86
|
+
safeEmit("remoteMessage", msg);
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
ws.on("close", () => {
|
|
92
|
+
log("disconnected", "warn");
|
|
93
|
+
safeEmit("remoteDisconnected");
|
|
94
|
+
if (!closed) scheduleReconnect();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
ws.on("error", err => {
|
|
98
|
+
log(`error: ${err && err.message ? err.message : String(err)}`, "warn");
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
connect();
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
close() {
|
|
106
|
+
closed = true;
|
|
107
|
+
if (reconnectTimer) {
|
|
108
|
+
clearTimeout(reconnectTimer);
|
|
109
|
+
reconnectTimer = null;
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
113
|
+
ws.close();
|
|
114
|
+
}
|
|
115
|
+
} catch { }
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
module.exports = {
|
|
121
|
+
createRemoteClient
|
|
122
|
+
};
|
|
123
|
+
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const logger = require("../../func/logger");
|
|
4
|
+
|
|
5
|
+
function delay(ms) {
|
|
6
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async function broadcast(api, threadIDs, message, options) {
|
|
10
|
+
const opts = options || {};
|
|
11
|
+
const delayMs = typeof opts.delayMs === "number" ? opts.delayMs : 1000;
|
|
12
|
+
const skipBlocked = opts.skipBlocked !== false;
|
|
13
|
+
const onResult = typeof opts.onResult === "function" ? opts.onResult : null;
|
|
14
|
+
|
|
15
|
+
if (!api || typeof api.sendMessage !== "function") {
|
|
16
|
+
throw new Error("broadcast: api.sendMessage is required.");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const ids = Array.isArray(threadIDs) ? threadIDs : [threadIDs];
|
|
20
|
+
const results = [];
|
|
21
|
+
|
|
22
|
+
for (const id of ids) {
|
|
23
|
+
try {
|
|
24
|
+
const res = await api.sendMessage(message, id);
|
|
25
|
+
const item = { threadID: id, ok: true, res };
|
|
26
|
+
results.push(item);
|
|
27
|
+
if (onResult) onResult(null, item);
|
|
28
|
+
} catch (e) {
|
|
29
|
+
const msg = e && e.error ? e.error : e && e.message ? e.message : String(e);
|
|
30
|
+
logger(`broadcast: failed for ${id}: ${msg}`, "warn");
|
|
31
|
+
const item = { threadID: id, ok: false, error: e };
|
|
32
|
+
results.push(item);
|
|
33
|
+
if (onResult) onResult(e, item);
|
|
34
|
+
|
|
35
|
+
if (
|
|
36
|
+
skipBlocked &&
|
|
37
|
+
/permission|blocked|not allowed|cannot send message|not authorized/i.test(msg)
|
|
38
|
+
) {
|
|
39
|
+
// Skip only this target, continue with others
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (delayMs > 0) {
|
|
43
|
+
await delay(delayMs);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return results;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = broadcast;
|
|
51
|
+
|
package/src/utils/loginParser.js
CHANGED
|
@@ -20,6 +20,13 @@ function makeParsable(html) {
|
|
|
20
20
|
|
|
21
21
|
function parseAndCheckLogin(ctx, http, retryCount = 0) {
|
|
22
22
|
const delay = ms => new Promise(r => setTimeout(r, ms));
|
|
23
|
+
const emit = (event, payload) => {
|
|
24
|
+
try {
|
|
25
|
+
if (ctx && ctx._emitter && typeof ctx._emitter.emit === "function") {
|
|
26
|
+
ctx._emitter.emit(event, payload);
|
|
27
|
+
}
|
|
28
|
+
} catch { }
|
|
29
|
+
};
|
|
23
30
|
const headerOf = (headers, name) => {
|
|
24
31
|
if (!headers) return;
|
|
25
32
|
const k = Object.keys(headers).find(k => k.toLowerCase() === name.toLowerCase());
|
|
@@ -59,11 +66,13 @@ function parseAndCheckLogin(ctx, http, retryCount = 0) {
|
|
|
59
66
|
// Set flag to prevent concurrent auto login attempts
|
|
60
67
|
ctx.auto_login = true;
|
|
61
68
|
logger("Login session expired, attempting auto login...", "warn");
|
|
69
|
+
emit("sessionExpired", { res: resData });
|
|
62
70
|
|
|
63
71
|
try {
|
|
64
72
|
const ok = await ctx.performAutoLogin();
|
|
65
73
|
if (ok) {
|
|
66
74
|
logger("Auto login successful! Retrying request...", "info");
|
|
75
|
+
emit("autoLoginSuccess", { res: resData });
|
|
67
76
|
ctx.auto_login = false;
|
|
68
77
|
|
|
69
78
|
// After successful auto login, retry the original request
|
|
@@ -128,6 +137,7 @@ function parseAndCheckLogin(ctx, http, retryCount = 0) {
|
|
|
128
137
|
const e = new Error("Not logged in. Auto login failed.");
|
|
129
138
|
e.error = "Not logged in.";
|
|
130
139
|
e.res = resData;
|
|
140
|
+
emit("autoLoginFailed", { error: e, res: resData });
|
|
131
141
|
throw e;
|
|
132
142
|
}
|
|
133
143
|
} catch (autoLoginErr) {
|
|
@@ -147,6 +157,7 @@ function parseAndCheckLogin(ctx, http, retryCount = 0) {
|
|
|
147
157
|
e.error = "Not logged in.";
|
|
148
158
|
e.res = resData;
|
|
149
159
|
e.originalError = autoLoginErr;
|
|
160
|
+
emit("autoLoginFailed", { error: e, res: resData });
|
|
150
161
|
throw e;
|
|
151
162
|
}
|
|
152
163
|
};
|
|
@@ -304,7 +315,9 @@ function parseAndCheckLogin(ctx, http, retryCount = 0) {
|
|
|
304
315
|
}
|
|
305
316
|
if (parsed?.error === 1357001) {
|
|
306
317
|
const err = new Error("Facebook blocked the login");
|
|
307
|
-
err.error = "
|
|
318
|
+
err.error = "login_blocked";
|
|
319
|
+
err.res = parsed;
|
|
320
|
+
emit("loginBlocked", { res: parsed });
|
|
308
321
|
throw err;
|
|
309
322
|
}
|
|
310
323
|
const resData = parsed;
|
|
@@ -313,6 +326,7 @@ function parseAndCheckLogin(ctx, http, retryCount = 0) {
|
|
|
313
326
|
resStr.includes("XCheckpointFBScrapingWarningController") ||
|
|
314
327
|
resStr.includes("601051028565049")
|
|
315
328
|
) {
|
|
329
|
+
emit("checkpoint", { type: "scraping_warning", res: resData });
|
|
316
330
|
return await maybeAutoLogin(resData, res?.config);
|
|
317
331
|
}
|
|
318
332
|
if (
|
|
@@ -326,6 +340,8 @@ function parseAndCheckLogin(ctx, http, retryCount = 0) {
|
|
|
326
340
|
const err = new Error("Checkpoint 282 detected");
|
|
327
341
|
err.error = "checkpoint_282";
|
|
328
342
|
err.res = resData;
|
|
343
|
+
emit("checkpoint", { type: "282", res: resData });
|
|
344
|
+
emit("checkpoint_282", { res: resData });
|
|
329
345
|
throw err;
|
|
330
346
|
}
|
|
331
347
|
if (resStr.includes("828281030927956")) {
|
|
@@ -333,6 +349,8 @@ function parseAndCheckLogin(ctx, http, retryCount = 0) {
|
|
|
333
349
|
const err = new Error("Checkpoint 956 detected");
|
|
334
350
|
err.error = "checkpoint_956";
|
|
335
351
|
err.res = resData;
|
|
352
|
+
emit("checkpoint", { type: "956", res: resData });
|
|
353
|
+
emit("checkpoint_956", { res: resData });
|
|
336
354
|
throw err;
|
|
337
355
|
}
|
|
338
356
|
return parsed;
|
package/src/utils/request.js
CHANGED
|
@@ -85,8 +85,15 @@ const client = wrapper(axios.create({
|
|
|
85
85
|
|
|
86
86
|
const delay = ms => new Promise(r => setTimeout(r, ms));
|
|
87
87
|
|
|
88
|
-
async function requestWithRetry(fn, retries = 3, baseDelay = 1000) {
|
|
88
|
+
async function requestWithRetry(fn, retries = 3, baseDelay = 1000, ctx) {
|
|
89
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
|
+
};
|
|
90
97
|
for (let i = 0; i < retries; i++) {
|
|
91
98
|
try {
|
|
92
99
|
return await fn();
|
|
@@ -104,6 +111,11 @@ async function requestWithRetry(fn, retries = 3, baseDelay = 1000) {
|
|
|
104
111
|
|
|
105
112
|
// Don't retry on client errors (4xx) except 429 (rate limit)
|
|
106
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
|
+
}
|
|
107
119
|
if (status >= 400 && status < 500 && status !== 429) {
|
|
108
120
|
return e.response || Promise.reject(e);
|
|
109
121
|
}
|
|
@@ -111,6 +123,21 @@ async function requestWithRetry(fn, retries = 3, baseDelay = 1000) {
|
|
|
111
123
|
if (i === retries - 1) {
|
|
112
124
|
return e.response || Promise.reject(e);
|
|
113
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
|
+
|
|
114
141
|
// Exponential backoff with jitter
|
|
115
142
|
const backoffDelay = Math.min(
|
|
116
143
|
baseDelay * Math.pow(2, i) + Math.floor(Math.random() * 200),
|
|
@@ -158,13 +185,13 @@ function isPairArrayList(arr) {
|
|
|
158
185
|
return Array.isArray(arr) && arr.length > 0 && arr.every(x => Array.isArray(x) && x.length === 2 && typeof x[0] === "string");
|
|
159
186
|
}
|
|
160
187
|
|
|
161
|
-
function cleanGet(url) {
|
|
162
|
-
return requestWithRetry(() => client.get(url, cfg()), 3, 1000);
|
|
188
|
+
function cleanGet(url, ctx) {
|
|
189
|
+
return requestWithRetry(() => client.get(url, cfg()), 3, 1000, ctx);
|
|
163
190
|
}
|
|
164
191
|
|
|
165
192
|
function get(url, reqJar, qs, options, ctx, customHeader) {
|
|
166
193
|
const headers = getHeaders(url, options, ctx, customHeader);
|
|
167
|
-
return requestWithRetry(() => client.get(url, cfg({ reqJar, headers, params: qs })), 3, 1000);
|
|
194
|
+
return requestWithRetry(() => client.get(url, cfg({ reqJar, headers, params: qs })), 3, 1000, ctx);
|
|
168
195
|
}
|
|
169
196
|
|
|
170
197
|
function post(url, reqJar, form, options, ctx, customHeader) {
|
|
@@ -197,7 +224,7 @@ function post(url, reqJar, form, options, ctx, customHeader) {
|
|
|
197
224
|
data = p.toString();
|
|
198
225
|
headers["Content-Type"] = "application/x-www-form-urlencoded";
|
|
199
226
|
}
|
|
200
|
-
return requestWithRetry(() => client.post(url, data, cfg({ reqJar, headers })), 3, 1000);
|
|
227
|
+
return requestWithRetry(() => client.post(url, data, cfg({ reqJar, headers })), 3, 1000, ctx);
|
|
201
228
|
}
|
|
202
229
|
|
|
203
230
|
async function postFormData(url, reqJar, form, qs, options, ctx) {
|
|
@@ -250,7 +277,7 @@ async function postFormData(url, reqJar, form, qs, options, ctx) {
|
|
|
250
277
|
}
|
|
251
278
|
}
|
|
252
279
|
const headers = { ...getHeaders(url, options, ctx), ...fd.getHeaders() };
|
|
253
|
-
return requestWithRetry(() => client.post(url, fd, cfg({ reqJar, headers, params: qs })), 3, 1000);
|
|
280
|
+
return requestWithRetry(() => client.post(url, fd, cfg({ reqJar, headers, params: qs })), 3, 1000, ctx);
|
|
254
281
|
}
|
|
255
282
|
|
|
256
283
|
function makeDefaults(html, userID, ctx) {
|
package/.gitattributes
DELETED
|
Binary file
|
package/LICENSE-MIT
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 DongDev (Donix-VN)
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|