@dongdev/fca-unofficial 3.0.29 → 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 +232 -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 -4
- package/package.json +89 -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/httpGet.js +2 -2
- package/src/api/http/postFormData.js +1 -1
- package/src/api/messaging/addUserToGroup.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 +2 -2
- package/src/api/messaging/changeNickname.js +2 -2
- package/src/api/messaging/changeThreadColor.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/editMessage.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/searchForThread.js +2 -1
- package/src/api/messaging/sendMessage.js +1 -1
- package/src/api/messaging/sendTypingIndicator.js +1 -1
- package/src/api/messaging/setMessageReaction.js +3 -4
- package/src/api/messaging/setTitle.js +1 -1
- package/src/api/messaging/unsendMessage.js +2 -2
- 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 +246 -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 +87 -12
- package/src/database/helpers.js +53 -0
- package/src/database/models/index.js +2 -1
- package/src/database/models/thread.js +5 -0
- package/src/database/threadData.js +49 -53
- package/src/database/userData.js +46 -37
- package/src/remote/remoteClient.js +123 -0
- package/src/utils/broadcast.js +51 -0
- 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/.gitattributes +0 -2
- package/Fca_Database/database.sqlite +0 -0
- package/LICENSE-MIT +0 -21
- package/src/utils/format.js +0 -1174
- package/src/utils/loginParser.js +0 -347
- package/src/utils/messageFormat.js +0 -1173
- package/src/utils/request.js +0 -305
|
@@ -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,17 +118,88 @@ 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) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
+
|
|
196
|
+
const dbFiles = fs.readdirSync(path.join(__dirname, "../../database"))
|
|
197
|
+
.filter(f => path.extname(f) === ".js")
|
|
198
|
+
.reduce((acc, file) => {
|
|
199
|
+
const mod = require(path.join(__dirname, "../../database", file));
|
|
200
|
+
acc[path.basename(file, ".js")] = typeof mod === "function" ? mod(api) : mod;
|
|
201
|
+
return acc;
|
|
202
|
+
}, {});
|
|
132
203
|
const { userData } = dbFiles;
|
|
133
204
|
const { create, get, update, getAll } = userData;
|
|
134
205
|
|
|
@@ -319,7 +390,11 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
319
390
|
}
|
|
320
391
|
return callback(null, ret);
|
|
321
392
|
}).catch(err => {
|
|
322
|
-
|
|
393
|
+
// Horizon-style anti-get-info message to hint rate limiting / spam block
|
|
394
|
+
log.error(
|
|
395
|
+
"getUserInfo",
|
|
396
|
+
"Lỗi: getUserInfo Có Thể Do Bạn Spam Quá Nhiều !,Hãy Thử Lại !"
|
|
397
|
+
);
|
|
323
398
|
callback(err);
|
|
324
399
|
});
|
|
325
400
|
return returnPromise;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared helpers for database layer (userData, threadData).
|
|
5
|
+
* Keeps validation and payload normalization in one place.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const DB_NOT_INIT = "Database not initialized";
|
|
9
|
+
|
|
10
|
+
function validateId(value, fieldName = "id") {
|
|
11
|
+
if (value == null) {
|
|
12
|
+
throw new Error(`${fieldName} is required and cannot be undefined`);
|
|
13
|
+
}
|
|
14
|
+
if (typeof value !== "string" && typeof value !== "number") {
|
|
15
|
+
throw new Error(`Invalid ${fieldName}: must be a string or number`);
|
|
16
|
+
}
|
|
17
|
+
return String(value);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function validateData(data) {
|
|
21
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
22
|
+
throw new Error("Invalid data: must be a non-empty object");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {string|string[]|null} keys - "userID" | ["userID","data"] | null
|
|
28
|
+
* @returns {string[]|undefined}
|
|
29
|
+
*/
|
|
30
|
+
function normalizeAttributes(keys) {
|
|
31
|
+
if (keys == null) return undefined;
|
|
32
|
+
return typeof keys === "string" ? [keys] : Array.isArray(keys) ? keys : undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Normalize payload: accept either { data } or raw object.
|
|
37
|
+
*/
|
|
38
|
+
function normalizePayload(data, key = "data") {
|
|
39
|
+
return Object.prototype.hasOwnProperty.call(data, key) ? data : { [key]: data };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function wrapError(message, cause) {
|
|
43
|
+
return new Error(`${message}: ${cause && cause.message ? cause.message : cause}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = {
|
|
47
|
+
DB_NOT_INIT,
|
|
48
|
+
validateId,
|
|
49
|
+
validateData,
|
|
50
|
+
normalizeAttributes,
|
|
51
|
+
normalizePayload,
|
|
52
|
+
wrapError
|
|
53
|
+
};
|
|
@@ -63,6 +63,7 @@ try {
|
|
|
63
63
|
|
|
64
64
|
models.sequelize = sequelize;
|
|
65
65
|
models.Sequelize = Sequelize;
|
|
66
|
+
models.isReady = true;
|
|
66
67
|
models.syncAll = async () => {
|
|
67
68
|
try {
|
|
68
69
|
if (!sequelize) {
|
|
@@ -75,10 +76,10 @@ try {
|
|
|
75
76
|
}
|
|
76
77
|
};
|
|
77
78
|
} catch (initError) {
|
|
78
|
-
// If initialization fails completely, still export a valid structure
|
|
79
79
|
console.error("Database initialization error:", initError && initError.message ? initError.message : String(initError));
|
|
80
80
|
models.sequelize = null;
|
|
81
81
|
models.Sequelize = Sequelize;
|
|
82
|
+
models.isReady = false;
|
|
82
83
|
models.syncAll = async () => {
|
|
83
84
|
throw new Error("Database not initialized");
|
|
84
85
|
};
|
|
@@ -1,97 +1,93 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
throw new Error("Invalid data: must be a non-empty object.");
|
|
12
|
-
}
|
|
13
|
-
};
|
|
3
|
+
const models = require("./models");
|
|
4
|
+
const {
|
|
5
|
+
DB_NOT_INIT,
|
|
6
|
+
validateId,
|
|
7
|
+
validateData,
|
|
8
|
+
normalizeAttributes,
|
|
9
|
+
wrapError
|
|
10
|
+
} = require("./helpers");
|
|
14
11
|
|
|
15
|
-
|
|
12
|
+
const Thread = models.Thread;
|
|
13
|
+
const ID_FIELD = "threadID";
|
|
14
|
+
|
|
15
|
+
module.exports = function (bot) {
|
|
16
16
|
return {
|
|
17
17
|
async create(threadID, data) {
|
|
18
|
+
if (!Thread) {
|
|
19
|
+
return { thread: { threadID: validateId(threadID, ID_FIELD), ...(data || {}) }, created: true };
|
|
20
|
+
}
|
|
18
21
|
try {
|
|
22
|
+
threadID = validateId(threadID, ID_FIELD);
|
|
19
23
|
let thread = await Thread.findOne({ where: { threadID } });
|
|
20
|
-
if (thread) {
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
thread = await Thread.create({ threadID, ...data });
|
|
24
|
+
if (thread) return { thread: thread.get(), created: false };
|
|
25
|
+
thread = await Thread.create({ threadID, ...(data || {}) });
|
|
24
26
|
return { thread: thread.get(), created: true };
|
|
25
|
-
} catch (
|
|
26
|
-
throw
|
|
27
|
+
} catch (err) {
|
|
28
|
+
throw wrapError("Failed to create thread", err);
|
|
27
29
|
}
|
|
28
30
|
},
|
|
29
31
|
|
|
30
32
|
async get(threadID) {
|
|
33
|
+
if (!Thread) return null;
|
|
31
34
|
try {
|
|
32
|
-
threadID =
|
|
35
|
+
threadID = validateId(threadID, ID_FIELD);
|
|
33
36
|
const thread = await Thread.findOne({ where: { threadID } });
|
|
34
37
|
return thread ? thread.get() : null;
|
|
35
|
-
} catch (
|
|
36
|
-
throw
|
|
38
|
+
} catch (err) {
|
|
39
|
+
throw wrapError("Failed to get thread", err);
|
|
37
40
|
}
|
|
38
41
|
},
|
|
39
42
|
|
|
40
43
|
async update(threadID, data) {
|
|
44
|
+
if (!Thread) {
|
|
45
|
+
return { thread: { threadID: validateId(threadID, ID_FIELD), ...(data || {}) }, created: false };
|
|
46
|
+
}
|
|
41
47
|
try {
|
|
42
|
-
threadID =
|
|
48
|
+
threadID = validateId(threadID, ID_FIELD);
|
|
43
49
|
validateData(data);
|
|
44
50
|
const thread = await Thread.findOne({ where: { threadID } });
|
|
45
|
-
|
|
46
51
|
if (thread) {
|
|
47
52
|
await thread.update(data);
|
|
48
53
|
return { thread: thread.get(), created: false };
|
|
49
|
-
} else {
|
|
50
|
-
const newThread = await Thread.create({ ...data, threadID });
|
|
51
|
-
return { thread: newThread.get(), created: true };
|
|
52
54
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
const newThread = await Thread.create({ ...data, threadID });
|
|
56
|
+
return { thread: newThread.get(), created: true };
|
|
57
|
+
} catch (err) {
|
|
58
|
+
throw wrapError("Failed to update thread", err);
|
|
55
59
|
}
|
|
56
60
|
},
|
|
57
61
|
|
|
58
62
|
async del(threadID) {
|
|
63
|
+
if (!Thread) throw new Error(DB_NOT_INIT);
|
|
59
64
|
try {
|
|
60
|
-
|
|
61
|
-
throw new Error("threadID is required and cannot be undefined");
|
|
62
|
-
}
|
|
63
|
-
threadID = validateThreadID(threadID);
|
|
64
|
-
if (!threadID) {
|
|
65
|
-
throw new Error("Invalid threadID");
|
|
66
|
-
}
|
|
65
|
+
threadID = validateId(threadID, ID_FIELD);
|
|
67
66
|
const result = await Thread.destroy({ where: { threadID } });
|
|
68
|
-
if (result === 0)
|
|
69
|
-
throw new Error("No thread found with the specified threadID");
|
|
70
|
-
}
|
|
67
|
+
if (result === 0) throw new Error("No thread found with the specified threadID");
|
|
71
68
|
return result;
|
|
72
|
-
} catch (
|
|
73
|
-
throw
|
|
69
|
+
} catch (err) {
|
|
70
|
+
throw wrapError("Failed to delete thread", err);
|
|
74
71
|
}
|
|
75
72
|
},
|
|
73
|
+
|
|
76
74
|
async delAll() {
|
|
75
|
+
if (!Thread) return 0;
|
|
77
76
|
try {
|
|
78
77
|
return await Thread.destroy({ where: {} });
|
|
79
|
-
} catch (
|
|
80
|
-
throw
|
|
78
|
+
} catch (err) {
|
|
79
|
+
throw wrapError("Failed to delete all threads", err);
|
|
81
80
|
}
|
|
82
81
|
},
|
|
82
|
+
|
|
83
83
|
async getAll(keys = null) {
|
|
84
|
+
if (!Thread) return [];
|
|
84
85
|
try {
|
|
85
|
-
const attributes =
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
: undefined;
|
|
91
|
-
const threads = await Thread.findAll({ attributes });
|
|
92
|
-
return threads.map(thread => thread.get());
|
|
93
|
-
} catch (error) {
|
|
94
|
-
throw new Error(`Failed to get all threads: ${error.message}`);
|
|
86
|
+
const attributes = normalizeAttributes(keys);
|
|
87
|
+
const rows = await Thread.findAll({ attributes });
|
|
88
|
+
return rows.map((t) => t.get());
|
|
89
|
+
} catch (err) {
|
|
90
|
+
throw wrapError("Failed to get all threads", err);
|
|
95
91
|
}
|
|
96
92
|
}
|
|
97
93
|
};
|
package/src/database/userData.js
CHANGED
|
@@ -1,88 +1,97 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
3
|
+
const models = require("./models");
|
|
4
|
+
const {
|
|
5
|
+
DB_NOT_INIT,
|
|
6
|
+
validateId,
|
|
7
|
+
validateData,
|
|
8
|
+
normalizeAttributes,
|
|
9
|
+
normalizePayload,
|
|
10
|
+
wrapError
|
|
11
|
+
} = require("./helpers");
|
|
12
|
+
|
|
13
|
+
const User = models.User;
|
|
14
|
+
const ID_FIELD = "userID";
|
|
15
|
+
|
|
16
|
+
function stubUser(userID, data) {
|
|
17
|
+
return { user: { userID, ...normalizePayload(data || {}, "data") }, created: true };
|
|
18
|
+
}
|
|
14
19
|
|
|
15
20
|
module.exports = function (bot) {
|
|
16
21
|
return {
|
|
17
22
|
async create(userID, data) {
|
|
23
|
+
if (!User) return stubUser(validateId(userID, ID_FIELD), data);
|
|
18
24
|
try {
|
|
19
|
-
userID =
|
|
25
|
+
userID = validateId(userID, ID_FIELD);
|
|
20
26
|
validateData(data);
|
|
27
|
+
const payload = normalizePayload(data, "data");
|
|
21
28
|
let user = await User.findOne({ where: { userID } });
|
|
22
29
|
if (user) return { user: user.get(), created: false };
|
|
23
|
-
const payload = Object.prototype.hasOwnProperty.call(data, "data") ? data : { data };
|
|
24
30
|
user = await User.create({ userID, ...payload });
|
|
25
31
|
return { user: user.get(), created: true };
|
|
26
|
-
} catch (
|
|
27
|
-
throw
|
|
32
|
+
} catch (err) {
|
|
33
|
+
throw wrapError("Failed to create user", err);
|
|
28
34
|
}
|
|
29
35
|
},
|
|
30
36
|
|
|
31
37
|
async get(userID) {
|
|
38
|
+
if (!User) return null;
|
|
32
39
|
try {
|
|
33
|
-
userID =
|
|
40
|
+
userID = validateId(userID, ID_FIELD);
|
|
34
41
|
const user = await User.findOne({ where: { userID } });
|
|
35
42
|
return user ? user.get() : null;
|
|
36
|
-
} catch (
|
|
37
|
-
throw
|
|
43
|
+
} catch (err) {
|
|
44
|
+
throw wrapError("Failed to get user", err);
|
|
38
45
|
}
|
|
39
46
|
},
|
|
40
47
|
|
|
41
48
|
async update(userID, data) {
|
|
49
|
+
if (!User) return { user: { userID: validateId(userID, ID_FIELD), ...normalizePayload(data || {}, "data") }, created: false };
|
|
42
50
|
try {
|
|
43
|
-
userID =
|
|
51
|
+
userID = validateId(userID, ID_FIELD);
|
|
44
52
|
validateData(data);
|
|
45
|
-
const payload =
|
|
53
|
+
const payload = normalizePayload(data, "data");
|
|
46
54
|
const user = await User.findOne({ where: { userID } });
|
|
47
55
|
if (user) {
|
|
48
56
|
await user.update(payload);
|
|
49
57
|
return { user: user.get(), created: false };
|
|
50
|
-
} else {
|
|
51
|
-
const newUser = await User.create({ userID, ...payload });
|
|
52
|
-
return { user: newUser.get(), created: true };
|
|
53
58
|
}
|
|
54
|
-
|
|
55
|
-
|
|
59
|
+
const newUser = await User.create({ userID, ...payload });
|
|
60
|
+
return { user: newUser.get(), created: true };
|
|
61
|
+
} catch (err) {
|
|
62
|
+
throw wrapError("Failed to update user", err);
|
|
56
63
|
}
|
|
57
64
|
},
|
|
58
65
|
|
|
59
66
|
async del(userID) {
|
|
67
|
+
if (!User) throw new Error(DB_NOT_INIT);
|
|
60
68
|
try {
|
|
61
|
-
|
|
62
|
-
userID = validateUserID(userID);
|
|
69
|
+
userID = validateId(userID, ID_FIELD);
|
|
63
70
|
const result = await User.destroy({ where: { userID } });
|
|
64
71
|
if (result === 0) throw new Error("No user found with the specified userID");
|
|
65
72
|
return result;
|
|
66
|
-
} catch (
|
|
67
|
-
throw
|
|
73
|
+
} catch (err) {
|
|
74
|
+
throw wrapError("Failed to delete user", err);
|
|
68
75
|
}
|
|
69
76
|
},
|
|
70
77
|
|
|
71
78
|
async delAll() {
|
|
79
|
+
if (!User) return 0;
|
|
72
80
|
try {
|
|
73
81
|
return await User.destroy({ where: {} });
|
|
74
|
-
} catch (
|
|
75
|
-
throw
|
|
82
|
+
} catch (err) {
|
|
83
|
+
throw wrapError("Failed to delete all users", err);
|
|
76
84
|
}
|
|
77
85
|
},
|
|
78
86
|
|
|
79
87
|
async getAll(keys = null) {
|
|
88
|
+
if (!User) return [];
|
|
80
89
|
try {
|
|
81
|
-
const attributes =
|
|
82
|
-
const
|
|
83
|
-
return
|
|
84
|
-
} catch (
|
|
85
|
-
throw
|
|
90
|
+
const attributes = normalizeAttributes(keys);
|
|
91
|
+
const rows = await User.findAll({ attributes });
|
|
92
|
+
return rows.map((u) => u.get());
|
|
93
|
+
} catch (err) {
|
|
94
|
+
throw wrapError("Failed to get all users", err);
|
|
86
95
|
}
|
|
87
96
|
}
|
|
88
97
|
};
|
|
@@ -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
|
+
|