@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.
Files changed (100) hide show
  1. package/CHANGELOG.md +232 -132
  2. package/DOCS.md +82 -3
  3. package/README.md +524 -632
  4. package/func/logAdapter.js +33 -0
  5. package/index.d.ts +6 -0
  6. package/module/config.js +11 -1
  7. package/module/loginHelper.js +63 -4
  8. package/package.json +89 -81
  9. package/src/api/action/changeAvatar.js +1 -1
  10. package/src/api/action/changeBio.js +1 -1
  11. package/src/api/action/handleFriendRequest.js +1 -1
  12. package/src/api/action/logout.js +1 -1
  13. package/src/api/action/refreshFb_dtsg.js +1 -1
  14. package/src/api/action/setPostReaction.js +1 -1
  15. package/src/api/action/unfriend.js +1 -1
  16. package/src/api/http/httpGet.js +2 -2
  17. package/src/api/http/postFormData.js +1 -1
  18. package/src/api/messaging/addUserToGroup.js +1 -1
  19. package/src/api/messaging/changeArchivedStatus.js +1 -1
  20. package/src/api/messaging/changeBlockedStatus.js +1 -1
  21. package/src/api/messaging/changeGroupImage.js +2 -2
  22. package/src/api/messaging/changeNickname.js +2 -2
  23. package/src/api/messaging/changeThreadColor.js +1 -1
  24. package/src/api/messaging/changeThreadEmoji.js +1 -1
  25. package/src/api/messaging/createNewGroup.js +1 -1
  26. package/src/api/messaging/createThemeAI.js +1 -1
  27. package/src/api/messaging/deleteMessage.js +1 -1
  28. package/src/api/messaging/deleteThread.js +1 -1
  29. package/src/api/messaging/editMessage.js +1 -1
  30. package/src/api/messaging/getFriendsList.js +1 -1
  31. package/src/api/messaging/getMessage.js +1 -1
  32. package/src/api/messaging/getThemePictures.js +1 -1
  33. package/src/api/messaging/handleMessageRequest.js +1 -1
  34. package/src/api/messaging/markAsDelivered.js +1 -1
  35. package/src/api/messaging/markAsRead.js +1 -1
  36. package/src/api/messaging/markAsReadAll.js +1 -1
  37. package/src/api/messaging/markAsSeen.js +1 -1
  38. package/src/api/messaging/muteThread.js +1 -1
  39. package/src/api/messaging/resolvePhotoUrl.js +1 -1
  40. package/src/api/messaging/searchForThread.js +2 -1
  41. package/src/api/messaging/sendMessage.js +1 -1
  42. package/src/api/messaging/sendTypingIndicator.js +1 -1
  43. package/src/api/messaging/setMessageReaction.js +3 -4
  44. package/src/api/messaging/setTitle.js +1 -1
  45. package/src/api/messaging/unsendMessage.js +2 -2
  46. package/src/api/messaging/uploadAttachment.js +1 -1
  47. package/src/api/socket/core/connectMqtt.js +16 -8
  48. package/src/api/socket/core/emitAuth.js +4 -0
  49. package/src/api/socket/core/getSeqID.js +6 -8
  50. package/src/api/socket/core/getTaskResponseData.js +3 -0
  51. package/src/api/socket/core/parseDelta.js +9 -0
  52. package/src/api/socket/detail/buildStream.js +11 -4
  53. package/src/api/socket/detail/constants.js +4 -0
  54. package/src/api/socket/listenMqtt.js +11 -5
  55. package/src/api/threads/getThreadHistory.js +1 -1
  56. package/src/api/threads/getThreadInfo.js +246 -388
  57. package/src/api/threads/getThreadList.js +1 -1
  58. package/src/api/threads/getThreadPictures.js +1 -1
  59. package/src/api/users/getUserID.js +1 -1
  60. package/src/api/users/getUserInfo.js +87 -12
  61. package/src/database/helpers.js +53 -0
  62. package/src/database/models/index.js +2 -1
  63. package/src/database/models/thread.js +5 -0
  64. package/src/database/threadData.js +49 -53
  65. package/src/database/userData.js +46 -37
  66. package/src/remote/remoteClient.js +123 -0
  67. package/src/utils/broadcast.js +51 -0
  68. package/src/utils/format/attachment.js +357 -0
  69. package/src/utils/format/cookie.js +9 -0
  70. package/src/utils/format/date.js +50 -0
  71. package/src/utils/format/decode.js +44 -0
  72. package/src/utils/format/delta.js +194 -0
  73. package/src/utils/format/ids.js +64 -0
  74. package/src/utils/format/index.js +64 -0
  75. package/src/utils/format/message.js +88 -0
  76. package/src/utils/format/presence.js +132 -0
  77. package/src/utils/format/readTyp.js +44 -0
  78. package/src/utils/format/thread.js +42 -0
  79. package/src/utils/format/utils.js +141 -0
  80. package/src/utils/loginParser/autoLogin.js +125 -0
  81. package/src/utils/loginParser/helpers.js +43 -0
  82. package/src/utils/loginParser/index.js +10 -0
  83. package/src/utils/loginParser/parseAndCheckLogin.js +220 -0
  84. package/src/utils/loginParser/textUtils.js +28 -0
  85. package/src/utils/request/client.js +26 -0
  86. package/src/utils/request/config.js +23 -0
  87. package/src/utils/request/defaults.js +46 -0
  88. package/src/utils/request/helpers.js +46 -0
  89. package/src/utils/request/index.js +17 -0
  90. package/src/utils/request/methods.js +163 -0
  91. package/src/utils/request/proxy.js +21 -0
  92. package/src/utils/request/retry.js +77 -0
  93. package/src/utils/request/sanitize.js +49 -0
  94. package/.gitattributes +0 -2
  95. package/Fca_Database/database.sqlite +0 -0
  96. package/LICENSE-MIT +0 -21
  97. package/src/utils/format.js +0 -1174
  98. package/src/utils/loginParser.js +0 -347
  99. package/src/utils/messageFormat.js +0 -1173
  100. package/src/utils/request.js +0 -305
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- const log = require("npmlog");
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("npmlog");
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) {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- const log = require("npmlog");
3
+ const log = require("../../../func/logAdapter");
4
4
  const { formatID } = require("../../utils/format");
5
5
  function formatData(data) {
6
6
  return {
@@ -2,7 +2,7 @@
2
2
 
3
3
  const fs = require("fs");
4
4
  const path = require("path");
5
- const log = require("npmlog");
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
- const dbFiles = fs.readdirSync(path.join(__dirname, "../../database")).filter(f => path.extname(f) === ".js").reduce((acc, file) => {
129
- acc[path.basename(file, ".js")] = require(path.join(__dirname, "../../database", file))(api);
130
- return acc;
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
- log.error("getUserInfo", "Error: " + (err?.message || "Unknown"));
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
  };
@@ -16,6 +16,11 @@ module.exports = function(sequelize) {
16
16
  allowNull: false,
17
17
  unique: true
18
18
  },
19
+ messageCount: {
20
+ type: DataTypes.INTEGER,
21
+ allowNull: false,
22
+ defaultValue: 0
23
+ },
19
24
  data: {
20
25
  type: DataTypes.TEXT,
21
26
  allowNull: true,
@@ -1,97 +1,93 @@
1
- const { Thread } = require("./models");
1
+ "use strict";
2
2
 
3
- const validateThreadID = threadID => {
4
- if (typeof threadID !== "string" && typeof threadID !== "number") {
5
- throw new Error("Invalid threadID: must be a string or number.");
6
- }
7
- return String(threadID);
8
- };
9
- const validateData = data => {
10
- if (!data || typeof data !== "object" || Array.isArray(data)) {
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
- module.exports = function(bot) {
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
- return { thread: thread.get(), created: false };
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 (error) {
26
- throw new Error(`Failed to create thread: ${error.message}`);
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 = validateThreadID(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 (error) {
36
- throw new Error(`Failed to get thread: ${error.message}`);
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 = validateThreadID(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
- } catch (error) {
54
- throw new Error(`Failed to update thread: ${error.message}`);
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
- if (!threadID) {
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 (error) {
73
- throw new Error(`Failed to delete thread: ${error.message}`);
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 (error) {
80
- throw new Error(`Failed to delete all threads: ${error.message}`);
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
- typeof keys === "string"
87
- ? [keys]
88
- : Array.isArray(keys)
89
- ? keys
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
  };
@@ -1,88 +1,97 @@
1
- const { User } = require("./models");
1
+ "use strict";
2
2
 
3
- const validateUserID = userID => {
4
- if (typeof userID !== "string" && typeof userID !== "number") {
5
- throw new Error("Invalid userID: must be a string or number.");
6
- }
7
- return String(userID);
8
- };
9
- const validateData = data => {
10
- if (!data || typeof data !== "object" || Array.isArray(data)) {
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
+ 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 = validateUserID(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 (error) {
27
- throw new Error(`Failed to create user: ${error.message}`);
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 = validateUserID(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 (error) {
37
- throw new Error(`Failed to get user: ${error.message}`);
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 = validateUserID(userID);
51
+ userID = validateId(userID, ID_FIELD);
44
52
  validateData(data);
45
- const payload = Object.prototype.hasOwnProperty.call(data, "data") ? data : { data };
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
- } catch (error) {
55
- throw new Error(`Failed to update user: ${error.message}`);
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
- if (!userID) throw new Error("userID is required and cannot be undefined");
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 (error) {
67
- throw new Error(`Failed to delete user: ${error.message}`);
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 (error) {
75
- throw new Error(`Failed to delete all users: ${error.message}`);
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 = typeof keys === "string" ? [keys] : Array.isArray(keys) ? keys : undefined;
82
- const users = await User.findAll({ attributes });
83
- return users.map(u => u.get());
84
- } catch (error) {
85
- throw new Error(`Failed to get all users: ${error.message}`);
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
+