@dongdev/fca-unofficial 2.0.32 → 3.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 +3 -0
- package/LICENSE-MIT +1 -1
- package/README.md +402 -95
- package/func/checkUpdate.js +0 -1
- package/index.d.ts +685 -607
- package/package.json +1 -1
- package/src/api/messaging/createThemeAI.js +98 -0
- package/src/api/messaging/sendMessage.js +3 -4
- package/src/api/socket/core/connectMqtt.js +58 -11
- package/src/api/socket/core/emitAuth.js +39 -9
- package/src/api/socket/core/parseDelta.js +13 -4
- package/src/api/socket/listenMqtt.js +79 -10
- package/src/utils/client.js +98 -25
- package/src/utils/request.js +30 -12
package/package.json
CHANGED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const log = require("npmlog");
|
|
4
|
+
const { getType } = require("../../utils/format");
|
|
5
|
+
const { parseAndCheckLogin } = require("../../utils/client");
|
|
6
|
+
|
|
7
|
+
module.exports = function (defaultFuncs, api, ctx) {
|
|
8
|
+
return function createThemeAI(prompt, callback) {
|
|
9
|
+
let resolveFunc = function () { };
|
|
10
|
+
let rejectFunc = function () { };
|
|
11
|
+
const returnPromise = new Promise(function (resolve, reject) {
|
|
12
|
+
resolveFunc = resolve;
|
|
13
|
+
rejectFunc = reject;
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
if (!callback) {
|
|
17
|
+
callback = function (err, data) {
|
|
18
|
+
if (err) return rejectFunc(err);
|
|
19
|
+
resolveFunc(data);
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (getType(prompt) !== "String") {
|
|
24
|
+
return callback({ error: "Invalid prompt. Please provide a string." });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!prompt || prompt.trim().length === 0) {
|
|
28
|
+
return callback({ error: "Prompt cannot be empty." });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const form = {
|
|
32
|
+
av: ctx.userID,
|
|
33
|
+
fb_api_caller_class: "RelayModern",
|
|
34
|
+
fb_api_req_friendly_name: "useGenerateAIThemeMutation",
|
|
35
|
+
doc_id: "23873748445608673",
|
|
36
|
+
variables: JSON.stringify({
|
|
37
|
+
input: {
|
|
38
|
+
client_mutation_id: Math.round(Math.random() * 19).toString(),
|
|
39
|
+
actor_id: ctx.userID,
|
|
40
|
+
bypass_cache: true,
|
|
41
|
+
caller: "MESSENGER",
|
|
42
|
+
num_themes: 1,
|
|
43
|
+
prompt: prompt.trim()
|
|
44
|
+
}
|
|
45
|
+
}),
|
|
46
|
+
server_timestamps: true
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
defaultFuncs
|
|
50
|
+
.post("https://www.facebook.com/api/graphql/", ctx.jar, form)
|
|
51
|
+
.then(parseAndCheckLogin(ctx, defaultFuncs))
|
|
52
|
+
.then(function (resData) {
|
|
53
|
+
if (resData.errors) {
|
|
54
|
+
throw resData;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!resData.data || !resData.data.xfb_generate_ai_themes_from_prompt) {
|
|
58
|
+
throw {
|
|
59
|
+
error: "Invalid response from Facebook API",
|
|
60
|
+
res: resData
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const themes = resData.data.xfb_generate_ai_themes_from_prompt.themes;
|
|
65
|
+
if (!themes || !Array.isArray(themes) || themes.length === 0) {
|
|
66
|
+
throw {
|
|
67
|
+
error: "No themes generated",
|
|
68
|
+
res: resData
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const theme = themes[0];
|
|
73
|
+
if (!theme || !theme.id || !theme.background_asset) {
|
|
74
|
+
throw {
|
|
75
|
+
error: "Invalid theme data",
|
|
76
|
+
res: resData
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
callback(null, {
|
|
81
|
+
id: theme.id,
|
|
82
|
+
accessibility_label: theme.accessibility_label || null,
|
|
83
|
+
background_asset: {
|
|
84
|
+
id: theme.background_asset.id,
|
|
85
|
+
image: {
|
|
86
|
+
url: theme.background_asset.image?.uri || null
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
})
|
|
91
|
+
.catch(function (err) {
|
|
92
|
+
log.error("createThemeAI", err);
|
|
93
|
+
return callback(err);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return returnPromise;
|
|
97
|
+
};
|
|
98
|
+
};
|
|
@@ -56,13 +56,12 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
function publishWithAck(content, text, reqID, callback) {
|
|
59
|
-
const mqttClient = ctx.mqttClient;
|
|
60
59
|
return new Promise((resolve, reject) => {
|
|
61
60
|
let done = false;
|
|
62
61
|
const cleanup = () => {
|
|
63
62
|
if (done) return;
|
|
64
63
|
done = true;
|
|
65
|
-
mqttClient.removeListener("message", handleRes);
|
|
64
|
+
ctx.mqttClient.removeListener("message", handleRes);
|
|
66
65
|
};
|
|
67
66
|
const handleRes = (topic, message) => {
|
|
68
67
|
if (topic !== "/ls_resp") return;
|
|
@@ -80,8 +79,8 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
80
79
|
callback && callback(undefined, bodies);
|
|
81
80
|
resolve(bodies);
|
|
82
81
|
};
|
|
83
|
-
mqttClient.on("message", handleRes);
|
|
84
|
-
mqttClient.publish("/ls_req", JSON.stringify(content), { qos: 1, retain: false }, err => {
|
|
82
|
+
ctx.mqttClient.on("message", handleRes);
|
|
83
|
+
ctx.mqttClient.publish("/ls_req", JSON.stringify(content), { qos: 1, retain: false }, err => {
|
|
85
84
|
if (err) {
|
|
86
85
|
cleanup();
|
|
87
86
|
callback && callback(err);
|
|
@@ -12,11 +12,20 @@ module.exports = function createListenMqtt(deps) {
|
|
|
12
12
|
function scheduleReconnect(delayMs) {
|
|
13
13
|
const d = (ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || 2000;
|
|
14
14
|
const ms = typeof delayMs === "number" ? delayMs : d;
|
|
15
|
-
if (ctx._reconnectTimer)
|
|
15
|
+
if (ctx._reconnectTimer) {
|
|
16
|
+
logger("mqtt reconnect already scheduled", "warn");
|
|
17
|
+
return; // debounce
|
|
18
|
+
}
|
|
19
|
+
if (ctx._ending) {
|
|
20
|
+
logger("mqtt reconnect skipped - ending", "warn");
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
16
23
|
logger(`mqtt will reconnect in ${ms}ms`, "warn");
|
|
17
24
|
ctx._reconnectTimer = setTimeout(() => {
|
|
18
25
|
ctx._reconnectTimer = null;
|
|
19
|
-
|
|
26
|
+
if (!ctx._ending) {
|
|
27
|
+
listenMqtt(defaultFuncs, api, ctx, globalCallback);
|
|
28
|
+
}
|
|
20
29
|
}, ms);
|
|
21
30
|
}
|
|
22
31
|
function isEndingLikeError(msg) {
|
|
@@ -88,20 +97,29 @@ module.exports = function createListenMqtt(deps) {
|
|
|
88
97
|
}
|
|
89
98
|
|
|
90
99
|
if (/Not logged in|Not logged in.|blocked the login|401|403/i.test(msg)) {
|
|
91
|
-
try {
|
|
100
|
+
try {
|
|
101
|
+
if (mqttClient && mqttClient.connected) {
|
|
102
|
+
mqttClient.end(true);
|
|
103
|
+
}
|
|
104
|
+
} catch (_) { }
|
|
92
105
|
return emitAuth(ctx, api, globalCallback,
|
|
93
106
|
/blocked/i.test(msg) ? "login_blocked" : "not_logged_in",
|
|
94
107
|
msg
|
|
95
108
|
);
|
|
96
109
|
}
|
|
97
110
|
logger(`mqtt error: ${msg}`, "error");
|
|
98
|
-
try {
|
|
111
|
+
try {
|
|
112
|
+
if (mqttClient && mqttClient.connected) {
|
|
113
|
+
mqttClient.end(true);
|
|
114
|
+
}
|
|
115
|
+
} catch (_) { }
|
|
99
116
|
if (ctx._ending || ctx._cycling) return;
|
|
100
117
|
|
|
101
|
-
if (ctx.globalOptions.autoReconnect) {
|
|
118
|
+
if (ctx.globalOptions.autoReconnect && !ctx._ending) {
|
|
102
119
|
const d = (ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || 2000;
|
|
103
120
|
logger(`mqtt autoReconnect listenMqtt() in ${d}ms`, "warn");
|
|
104
|
-
|
|
121
|
+
// Use scheduleReconnect to prevent multiple reconnections
|
|
122
|
+
scheduleReconnect(d);
|
|
105
123
|
} else {
|
|
106
124
|
globalCallback({ type: "stop_listen", error: msg || "Connection refused" }, null);
|
|
107
125
|
}
|
|
@@ -128,8 +146,16 @@ module.exports = function createListenMqtt(deps) {
|
|
|
128
146
|
mqttClient.publish("/set_client_settings", JSON.stringify({ make_user_available_when_in_foreground: true }), { qos: 1 });
|
|
129
147
|
const d = (ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || 2000;
|
|
130
148
|
const rTimeout = setTimeout(function () {
|
|
149
|
+
if (ctx._ending) {
|
|
150
|
+
logger("mqtt t_ms timeout skipped - ending", "warn");
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
131
153
|
logger(`mqtt t_ms timeout, cycling in ${d}ms`, "warn");
|
|
132
|
-
try {
|
|
154
|
+
try {
|
|
155
|
+
if (mqttClient && mqttClient.connected) {
|
|
156
|
+
mqttClient.end(true);
|
|
157
|
+
}
|
|
158
|
+
} catch (_) { }
|
|
133
159
|
scheduleReconnect(d);
|
|
134
160
|
}, 5000);
|
|
135
161
|
|
|
@@ -141,9 +167,15 @@ module.exports = function createListenMqtt(deps) {
|
|
|
141
167
|
});
|
|
142
168
|
|
|
143
169
|
mqttClient.on("message", function (topic, message) {
|
|
170
|
+
if (ctx._ending) return; // Ignore messages if ending
|
|
144
171
|
try {
|
|
145
172
|
let jsonMessage = Buffer.isBuffer(message) ? Buffer.from(message).toString() : message;
|
|
146
|
-
try {
|
|
173
|
+
try {
|
|
174
|
+
jsonMessage = JSON.parse(jsonMessage);
|
|
175
|
+
} catch (parseErr) {
|
|
176
|
+
logger(`mqtt message parse error for topic ${topic}: ${parseErr && parseErr.message ? parseErr.message : String(parseErr)}`, "warn");
|
|
177
|
+
jsonMessage = {};
|
|
178
|
+
}
|
|
147
179
|
|
|
148
180
|
if (jsonMessage.type === "jewel_requests_add") {
|
|
149
181
|
globalCallback(null, { type: "friend_request_received", actorFbId: jsonMessage.from.toString(), timestamp: Date.now().toString() });
|
|
@@ -186,11 +218,26 @@ module.exports = function createListenMqtt(deps) {
|
|
|
186
218
|
}
|
|
187
219
|
}
|
|
188
220
|
} catch (ex) {
|
|
189
|
-
|
|
221
|
+
const errMsg = ex && ex.message ? ex.message : String(ex || "Unknown error");
|
|
222
|
+
logger(`mqtt message handler error: ${errMsg}`, "error");
|
|
223
|
+
// Don't crash on message parsing errors, just log and continue
|
|
190
224
|
}
|
|
191
225
|
});
|
|
192
226
|
|
|
193
|
-
mqttClient.on("close", function () {
|
|
194
|
-
|
|
227
|
+
mqttClient.on("close", function () {
|
|
228
|
+
if (ctx._ending || ctx._cycling) {
|
|
229
|
+
logger("mqtt close expected", "info");
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
logger("mqtt connection closed", "warn");
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
mqttClient.on("disconnect", () => {
|
|
236
|
+
if (ctx._ending || ctx._cycling) {
|
|
237
|
+
logger("mqtt disconnect expected", "info");
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
logger("mqtt disconnected", "warn");
|
|
241
|
+
});
|
|
195
242
|
};
|
|
196
243
|
};
|
|
@@ -1,9 +1,35 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
module.exports = function createEmitAuth({ logger }) {
|
|
3
3
|
return function emitAuth(ctx, api, globalCallback, reason, detail) {
|
|
4
|
-
|
|
5
|
-
try {
|
|
6
|
-
|
|
4
|
+
// Clean up all timers
|
|
5
|
+
try {
|
|
6
|
+
if (ctx._autoCycleTimer) {
|
|
7
|
+
clearInterval(ctx._autoCycleTimer);
|
|
8
|
+
ctx._autoCycleTimer = null;
|
|
9
|
+
}
|
|
10
|
+
} catch (_) { }
|
|
11
|
+
try {
|
|
12
|
+
if (ctx._reconnectTimer) {
|
|
13
|
+
clearTimeout(ctx._reconnectTimer);
|
|
14
|
+
ctx._reconnectTimer = null;
|
|
15
|
+
}
|
|
16
|
+
} catch (_) { }
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
ctx._ending = true;
|
|
20
|
+
ctx._cycling = false;
|
|
21
|
+
} catch (_) { }
|
|
22
|
+
|
|
23
|
+
// Clean up MQTT client
|
|
24
|
+
try {
|
|
25
|
+
if (ctx.mqttClient) {
|
|
26
|
+
ctx.mqttClient.removeAllListeners();
|
|
27
|
+
if (ctx.mqttClient.connected) {
|
|
28
|
+
ctx.mqttClient.end(true);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
} catch (_) { }
|
|
32
|
+
|
|
7
33
|
ctx.mqttClient = undefined;
|
|
8
34
|
ctx.loggedIn = false;
|
|
9
35
|
|
|
@@ -11,12 +37,16 @@ module.exports = function createEmitAuth({ logger }) {
|
|
|
11
37
|
logger(`auth change -> ${reason}: ${msg}`, "error");
|
|
12
38
|
|
|
13
39
|
if (typeof globalCallback === "function") {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
40
|
+
try {
|
|
41
|
+
globalCallback({
|
|
42
|
+
type: "account_inactive",
|
|
43
|
+
reason,
|
|
44
|
+
error: msg,
|
|
45
|
+
timestamp: Date.now()
|
|
46
|
+
}, null);
|
|
47
|
+
} catch (cbErr) {
|
|
48
|
+
logger(`emitAuth callback error: ${cbErr && cbErr.message ? cbErr.message : String(cbErr)}`, "error");
|
|
49
|
+
}
|
|
20
50
|
}
|
|
21
51
|
};
|
|
22
52
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const { formatDeltaEvent, formatMessage, _formatAttachment, formatDeltaMessage, formatDeltaReadReceipt, formatID, getType, decodeClientPayload } = require("../../../utils/format");
|
|
3
|
+
const logger = require("../../../func/logger");
|
|
3
4
|
module.exports = function createParseDelta(deps) {
|
|
4
5
|
const { markDelivery, parseAndCheckLogin } = deps;
|
|
5
6
|
return function parseDelta(defaultFuncs, api, ctx, globalCallback, { delta }) {
|
|
@@ -21,9 +22,11 @@ module.exports = function createParseDelta(deps) {
|
|
|
21
22
|
}
|
|
22
23
|
} else {
|
|
23
24
|
const attachment = delta.attachments[i];
|
|
24
|
-
if (attachment.mercury.attach_type === "photo") {
|
|
25
|
+
if (attachment && attachment.mercury && attachment.mercury.attach_type === "photo") {
|
|
25
26
|
api.resolvePhotoUrl(attachment.fbid, (err, url) => {
|
|
26
|
-
if (!err
|
|
27
|
+
if (!err && attachment.mercury && attachment.mercury.metadata) {
|
|
28
|
+
attachment.mercury.metadata.url = url;
|
|
29
|
+
}
|
|
27
30
|
resolveAttachmentUrl(i + 1);
|
|
28
31
|
});
|
|
29
32
|
} else {
|
|
@@ -166,7 +169,10 @@ module.exports = function createParseDelta(deps) {
|
|
|
166
169
|
mentions: mobj,
|
|
167
170
|
timestamp: parseInt(fetchData.timestamp_precise)
|
|
168
171
|
};
|
|
169
|
-
}).catch(err => {
|
|
172
|
+
}).catch(err => {
|
|
173
|
+
const errMsg = err && err.message ? err.message : String(err || "Unknown error");
|
|
174
|
+
logger(`parseDelta message_reply fetch error: ${errMsg}`, "warn");
|
|
175
|
+
}).finally(() => {
|
|
170
176
|
if (ctx.globalOptions.autoMarkDelivery) {
|
|
171
177
|
markDelivery(ctx, api, callbackToReturn.threadID, callbackToReturn.messageID);
|
|
172
178
|
}
|
|
@@ -313,7 +319,10 @@ module.exports = function createParseDelta(deps) {
|
|
|
313
319
|
} else {
|
|
314
320
|
return;
|
|
315
321
|
}
|
|
316
|
-
}).catch(err => {
|
|
322
|
+
}).catch(err => {
|
|
323
|
+
const errMsg = err && err.message ? err.message : String(err || "Unknown error");
|
|
324
|
+
logger(`parseDelta ForcedFetch error: ${errMsg}`, "warn");
|
|
325
|
+
});
|
|
317
326
|
}
|
|
318
327
|
break;
|
|
319
328
|
}
|
|
@@ -61,6 +61,10 @@ module.exports = function (defaultFuncs, api, ctx, opts) {
|
|
|
61
61
|
let conf = mqttConf(ctx, opts);
|
|
62
62
|
|
|
63
63
|
function getSeqIDWrapper() {
|
|
64
|
+
if (ctx._ending && !ctx._cycling) {
|
|
65
|
+
logger("mqtt getSeqID skipped - ending", "warn");
|
|
66
|
+
return Promise.resolve();
|
|
67
|
+
}
|
|
64
68
|
const form = {
|
|
65
69
|
av: ctx.globalOptions.pageID,
|
|
66
70
|
queries: JSON.stringify({
|
|
@@ -75,8 +79,25 @@ module.exports = function (defaultFuncs, api, ctx, opts) {
|
|
|
75
79
|
};
|
|
76
80
|
logger("mqtt getSeqID call", "info");
|
|
77
81
|
return getSeqIDFactory(defaultFuncs, api, ctx, globalCallback, form)
|
|
78
|
-
.then(() => {
|
|
79
|
-
|
|
82
|
+
.then(() => {
|
|
83
|
+
logger("mqtt getSeqID done", "info");
|
|
84
|
+
ctx._cycling = false;
|
|
85
|
+
})
|
|
86
|
+
.catch(e => {
|
|
87
|
+
ctx._cycling = false;
|
|
88
|
+
const errMsg = e && e.message ? e.message : String(e || "Unknown error");
|
|
89
|
+
logger(`mqtt getSeqID error: ${errMsg}`, "error");
|
|
90
|
+
// Don't reconnect if we're ending
|
|
91
|
+
if (ctx._ending) return;
|
|
92
|
+
// Retry after delay if autoReconnect is enabled
|
|
93
|
+
if (ctx.globalOptions.autoReconnect) {
|
|
94
|
+
const d = conf.reconnectDelayMs;
|
|
95
|
+
logger(`mqtt getSeqID will retry in ${d}ms`, "warn");
|
|
96
|
+
setTimeout(() => {
|
|
97
|
+
if (!ctx._ending) getSeqIDWrapper();
|
|
98
|
+
}, d);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
80
101
|
}
|
|
81
102
|
|
|
82
103
|
function isConnected() {
|
|
@@ -84,30 +105,70 @@ module.exports = function (defaultFuncs, api, ctx, opts) {
|
|
|
84
105
|
}
|
|
85
106
|
|
|
86
107
|
function unsubAll(cb) {
|
|
87
|
-
if (!isConnected())
|
|
108
|
+
if (!isConnected()) {
|
|
109
|
+
if (cb) setTimeout(cb, 0);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
88
112
|
let pending = topics.length;
|
|
89
|
-
if (!pending)
|
|
113
|
+
if (!pending) {
|
|
114
|
+
if (cb) setTimeout(cb, 0);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
90
117
|
let fired = false;
|
|
118
|
+
const timeout = setTimeout(() => {
|
|
119
|
+
if (!fired) {
|
|
120
|
+
fired = true;
|
|
121
|
+
logger("unsubAll timeout, proceeding anyway", "warn");
|
|
122
|
+
if (cb) cb();
|
|
123
|
+
}
|
|
124
|
+
}, 5000); // 5 second timeout
|
|
125
|
+
|
|
91
126
|
topics.forEach(t => {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
127
|
+
try {
|
|
128
|
+
ctx.mqttClient.unsubscribe(t, () => {
|
|
129
|
+
if (--pending === 0 && !fired) {
|
|
130
|
+
clearTimeout(timeout);
|
|
131
|
+
fired = true;
|
|
132
|
+
if (cb) cb();
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
} catch (err) {
|
|
136
|
+
logger(`unsubAll error for topic ${t}: ${err && err.message ? err.message : String(err)}`, "warn");
|
|
137
|
+
if (--pending === 0 && !fired) {
|
|
138
|
+
clearTimeout(timeout);
|
|
139
|
+
fired = true;
|
|
140
|
+
if (cb) cb();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
95
143
|
});
|
|
96
144
|
}
|
|
97
145
|
|
|
98
146
|
function endQuietly(next) {
|
|
99
147
|
const finish = () => {
|
|
100
|
-
try {
|
|
148
|
+
try {
|
|
149
|
+
if (ctx.mqttClient) {
|
|
150
|
+
ctx.mqttClient.removeAllListeners();
|
|
151
|
+
}
|
|
152
|
+
} catch (_) { }
|
|
101
153
|
ctx.mqttClient = undefined;
|
|
102
154
|
ctx.lastSeqId = null;
|
|
103
155
|
ctx.syncToken = undefined;
|
|
104
156
|
ctx.t_mqttCalled = false;
|
|
105
157
|
ctx._ending = false;
|
|
158
|
+
ctx._cycling = false;
|
|
159
|
+
if (ctx._reconnectTimer) {
|
|
160
|
+
clearTimeout(ctx._reconnectTimer);
|
|
161
|
+
ctx._reconnectTimer = null;
|
|
162
|
+
}
|
|
106
163
|
next && next();
|
|
107
164
|
};
|
|
108
165
|
try {
|
|
109
166
|
if (ctx.mqttClient) {
|
|
110
|
-
if (isConnected()) {
|
|
167
|
+
if (isConnected()) {
|
|
168
|
+
try {
|
|
169
|
+
ctx.mqttClient.publish("/browser_close", "{}", { qos: 0 });
|
|
170
|
+
} catch (_) { }
|
|
171
|
+
}
|
|
111
172
|
ctx.mqttClient.end(true, finish);
|
|
112
173
|
} else finish();
|
|
113
174
|
} catch (_) { finish(); }
|
|
@@ -120,7 +181,10 @@ module.exports = function (defaultFuncs, api, ctx, opts) {
|
|
|
120
181
|
}
|
|
121
182
|
|
|
122
183
|
function forceCycle() {
|
|
123
|
-
if (ctx._cycling)
|
|
184
|
+
if (ctx._cycling) {
|
|
185
|
+
logger("mqtt force cycle already in progress", "warn");
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
124
188
|
ctx._cycling = true;
|
|
125
189
|
ctx._ending = true;
|
|
126
190
|
logger("mqtt force cycle begin", "warn");
|
|
@@ -140,6 +204,11 @@ module.exports = function (defaultFuncs, api, ctx, opts) {
|
|
|
140
204
|
logger("mqtt auto-cycle cleared", "info");
|
|
141
205
|
}
|
|
142
206
|
|
|
207
|
+
if (ctx._reconnectTimer) {
|
|
208
|
+
clearTimeout(ctx._reconnectTimer);
|
|
209
|
+
ctx._reconnectTimer = null;
|
|
210
|
+
}
|
|
211
|
+
|
|
143
212
|
ctx._ending = true;
|
|
144
213
|
unsubAll(() => endQuietly(() => {
|
|
145
214
|
logger("mqtt stopped", "info");
|
package/src/utils/client.js
CHANGED
|
@@ -85,31 +85,85 @@ function parseAndCheckLogin(ctx, http, retryCount = 0) {
|
|
|
85
85
|
return `${n}=${v}; Domain=.${service}.com; Path=/; Secure`;
|
|
86
86
|
};
|
|
87
87
|
|
|
88
|
-
const maybeAutoLogin = async (resData) => {
|
|
88
|
+
const maybeAutoLogin = async (resData, resConfig) => {
|
|
89
|
+
// Prevent infinite loop if auto login is already in progress
|
|
89
90
|
if (ctx.auto_login) {
|
|
90
|
-
const e = new Error("Not logged in.");
|
|
91
|
+
const e = new Error("Not logged in. Auto login already in progress.");
|
|
91
92
|
e.error = "Not logged in.";
|
|
92
93
|
e.res = resData;
|
|
93
94
|
throw e;
|
|
94
95
|
}
|
|
96
|
+
// Check if performAutoLogin function exists
|
|
95
97
|
if (typeof ctx.performAutoLogin !== "function") {
|
|
96
|
-
const e = new Error("Not logged in.");
|
|
98
|
+
const e = new Error("Not logged in. Auto login function not available.");
|
|
97
99
|
e.error = "Not logged in.";
|
|
98
100
|
e.res = resData;
|
|
99
101
|
throw e;
|
|
100
102
|
}
|
|
103
|
+
// Set flag to prevent concurrent auto login attempts
|
|
101
104
|
ctx.auto_login = true;
|
|
102
|
-
logger("Login session expired", "warn");
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
105
|
+
logger("Login session expired, attempting auto login...", "warn");
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const ok = await ctx.performAutoLogin();
|
|
109
|
+
if (ok) {
|
|
110
|
+
logger("Auto login successful! Retrying request...", "info");
|
|
111
|
+
ctx.auto_login = false;
|
|
112
|
+
|
|
113
|
+
// After successful auto login, retry the original request
|
|
114
|
+
if (resConfig) {
|
|
115
|
+
const url = buildUrl(resConfig);
|
|
116
|
+
const method = String(resConfig?.method || "GET").toUpperCase();
|
|
117
|
+
const ctype = String(headerOf(resConfig?.headers, "content-type") || "").toLowerCase();
|
|
118
|
+
const isMultipart = ctype.includes("multipart/form-data");
|
|
119
|
+
const payload = resConfig?.data;
|
|
120
|
+
const params = resConfig?.params;
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
let newData;
|
|
124
|
+
if (method === "GET") {
|
|
125
|
+
newData = await http.get(url, ctx.jar, params || null, ctx.globalOptions, ctx);
|
|
126
|
+
} else if (isMultipart) {
|
|
127
|
+
newData = await http.postFormData(url, ctx.jar, payload, params, ctx.globalOptions, ctx);
|
|
128
|
+
} else {
|
|
129
|
+
newData = await http.post(url, ctx.jar, payload, ctx.globalOptions, ctx);
|
|
130
|
+
}
|
|
131
|
+
// Retry parsing with the new response
|
|
132
|
+
return await parseAndCheckLogin(ctx, http, retryCount)(newData);
|
|
133
|
+
} catch (retryErr) {
|
|
134
|
+
logger(`Auto login retry failed: ${retryErr && retryErr.message ? retryErr.message : String(retryErr)}`, "error");
|
|
135
|
+
const e = new Error("Not logged in. Auto login retry failed.");
|
|
136
|
+
e.error = "Not logged in.";
|
|
137
|
+
e.res = resData;
|
|
138
|
+
e.originalError = retryErr;
|
|
139
|
+
throw e;
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
// No config available, can't retry
|
|
143
|
+
const e = new Error("Not logged in. Auto login successful but cannot retry request.");
|
|
144
|
+
e.error = "Not logged in.";
|
|
145
|
+
e.res = resData;
|
|
146
|
+
throw e;
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
ctx.auto_login = false;
|
|
150
|
+
const e = new Error("Not logged in. Auto login failed.");
|
|
151
|
+
e.error = "Not logged in.";
|
|
152
|
+
e.res = resData;
|
|
153
|
+
throw e;
|
|
154
|
+
}
|
|
155
|
+
} catch (autoLoginErr) {
|
|
109
156
|
ctx.auto_login = false;
|
|
110
|
-
|
|
157
|
+
// If error already has the right format, rethrow it
|
|
158
|
+
if (autoLoginErr.error === "Not logged in.") {
|
|
159
|
+
throw autoLoginErr;
|
|
160
|
+
}
|
|
161
|
+
// Otherwise, wrap it
|
|
162
|
+
logger(`Auto login error: ${autoLoginErr && autoLoginErr.message ? autoLoginErr.message : String(autoLoginErr)}`, "error");
|
|
163
|
+
const e = new Error("Not logged in. Auto login error.");
|
|
111
164
|
e.error = "Not logged in.";
|
|
112
165
|
e.res = resData;
|
|
166
|
+
e.originalError = autoLoginErr;
|
|
113
167
|
throw e;
|
|
114
168
|
}
|
|
115
169
|
};
|
|
@@ -121,9 +175,15 @@ function parseAndCheckLogin(ctx, http, retryCount = 0) {
|
|
|
121
175
|
err.statusCode = status;
|
|
122
176
|
err.res = res?.data;
|
|
123
177
|
err.error = "Request retry failed. Check the `res` and `statusCode` property on this error.";
|
|
178
|
+
logger(`parseAndCheckLogin: Max retries (5) reached for status ${status}`, "error");
|
|
124
179
|
throw err;
|
|
125
180
|
}
|
|
126
|
-
|
|
181
|
+
// Exponential backoff with jitter
|
|
182
|
+
const retryTime = Math.min(
|
|
183
|
+
Math.floor(Math.random() * (1000 * Math.pow(2, retryCount))) + 1000,
|
|
184
|
+
10000 // Max 10 seconds
|
|
185
|
+
);
|
|
186
|
+
logger(`parseAndCheckLogin: Retrying request (attempt ${retryCount + 1}/5) after ${retryTime}ms for status ${status}`, "warn");
|
|
127
187
|
await delay(retryTime);
|
|
128
188
|
const url = buildUrl(res?.config);
|
|
129
189
|
const method = String(res?.config?.method || "GET").toUpperCase();
|
|
@@ -132,16 +192,22 @@ function parseAndCheckLogin(ctx, http, retryCount = 0) {
|
|
|
132
192
|
const payload = res?.config?.data;
|
|
133
193
|
const params = res?.config?.params;
|
|
134
194
|
retryCount += 1;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
195
|
+
try {
|
|
196
|
+
if (method === "GET") {
|
|
197
|
+
const newData = await http.get(url, ctx.jar, params || null, ctx.globalOptions, ctx);
|
|
198
|
+
return await parseAndCheckLogin(ctx, http, retryCount)(newData);
|
|
199
|
+
}
|
|
200
|
+
if (isMultipart) {
|
|
201
|
+
const newData = await http.postFormData(url, ctx.jar, payload, params, ctx.globalOptions, ctx);
|
|
202
|
+
return await parseAndCheckLogin(ctx, http, retryCount)(newData);
|
|
203
|
+
} else {
|
|
204
|
+
const newData = await http.post(url, ctx.jar, payload, ctx.globalOptions, ctx);
|
|
205
|
+
return await parseAndCheckLogin(ctx, http, retryCount)(newData);
|
|
206
|
+
}
|
|
207
|
+
} catch (retryErr) {
|
|
208
|
+
if (retryCount >= 5) throw retryErr;
|
|
209
|
+
// Continue retry loop
|
|
210
|
+
return await parseAndCheckLogin(ctx, http, retryCount)(res);
|
|
145
211
|
}
|
|
146
212
|
}
|
|
147
213
|
if (status === 404) return;
|
|
@@ -192,17 +258,24 @@ function parseAndCheckLogin(ctx, http, retryCount = 0) {
|
|
|
192
258
|
const resData = parsed;
|
|
193
259
|
const resStr = JSON.stringify(resData);
|
|
194
260
|
if (resStr.includes("XCheckpointFBScrapingWarningController") || resStr.includes("601051028565049")) {
|
|
195
|
-
await maybeAutoLogin(resData);
|
|
261
|
+
return await maybeAutoLogin(resData, res?.config);
|
|
196
262
|
}
|
|
197
263
|
if (resStr.includes("https://www.facebook.com/login.php?") || String(parsed?.redirect || "").includes("login.php?")) {
|
|
198
|
-
await maybeAutoLogin(resData);
|
|
264
|
+
return await maybeAutoLogin(resData, res?.config);
|
|
199
265
|
}
|
|
200
266
|
if (resStr.includes("1501092823525282")) {
|
|
201
267
|
logger("Bot checkpoint 282 detected, please check the account!", "error");
|
|
202
|
-
|
|
268
|
+
const err = new Error("Checkpoint 282 detected");
|
|
269
|
+
err.error = "checkpoint_282";
|
|
270
|
+
err.res = resData;
|
|
271
|
+
throw err;
|
|
203
272
|
}
|
|
204
273
|
if (resStr.includes("828281030927956")) {
|
|
205
274
|
logger("Bot checkpoint 956 detected, please check the account!", "error");
|
|
275
|
+
const err = new Error("Checkpoint 956 detected");
|
|
276
|
+
err.error = "checkpoint_956";
|
|
277
|
+
err.res = resData;
|
|
278
|
+
throw err;
|
|
206
279
|
}
|
|
207
280
|
return parsed;
|
|
208
281
|
};
|