@dongdev/fca-unofficial 3.0.30 → 3.0.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/package.json +2 -1
  3. package/src/api/http/httpGet.js +2 -2
  4. package/src/api/messaging/addUserToGroup.js +1 -1
  5. package/src/api/messaging/changeGroupImage.js +1 -1
  6. package/src/api/messaging/changeNickname.js +1 -1
  7. package/src/api/messaging/changeThreadColor.js +1 -1
  8. package/src/api/messaging/editMessage.js +1 -1
  9. package/src/api/messaging/searchForThread.js +2 -1
  10. package/src/api/messaging/sendTypingIndicator.js +1 -1
  11. package/src/api/messaging/setMessageReaction.js +3 -4
  12. package/src/api/messaging/unsendMessage.js +1 -1
  13. package/src/api/threads/getThreadInfo.js +2 -1
  14. package/src/api/users/getUserInfo.js +7 -4
  15. package/src/database/helpers.js +53 -0
  16. package/src/database/models/index.js +2 -1
  17. package/src/database/threadData.js +49 -53
  18. package/src/database/userData.js +46 -37
  19. package/src/utils/format/attachment.js +357 -0
  20. package/src/utils/format/cookie.js +9 -0
  21. package/src/utils/format/date.js +50 -0
  22. package/src/utils/format/decode.js +44 -0
  23. package/src/utils/format/delta.js +194 -0
  24. package/src/utils/format/ids.js +64 -0
  25. package/src/utils/format/index.js +64 -0
  26. package/src/utils/format/message.js +88 -0
  27. package/src/utils/format/presence.js +132 -0
  28. package/src/utils/format/readTyp.js +44 -0
  29. package/src/utils/format/thread.js +42 -0
  30. package/src/utils/format/utils.js +141 -0
  31. package/src/utils/loginParser/autoLogin.js +125 -0
  32. package/src/utils/loginParser/helpers.js +43 -0
  33. package/src/utils/loginParser/index.js +10 -0
  34. package/src/utils/loginParser/parseAndCheckLogin.js +220 -0
  35. package/src/utils/loginParser/textUtils.js +28 -0
  36. package/src/utils/request/client.js +26 -0
  37. package/src/utils/request/config.js +23 -0
  38. package/src/utils/request/defaults.js +46 -0
  39. package/src/utils/request/helpers.js +46 -0
  40. package/src/utils/request/index.js +17 -0
  41. package/src/utils/request/methods.js +163 -0
  42. package/src/utils/request/proxy.js +21 -0
  43. package/src/utils/request/retry.js +77 -0
  44. package/src/utils/request/sanitize.js +49 -0
  45. package/src/utils/format.js +0 -1174
  46. package/src/utils/loginParser.js +0 -365
  47. package/src/utils/messageFormat.js +0 -1173
  48. package/src/utils/request.js +0 -332
package/CHANGELOG.md CHANGED
@@ -291,3 +291,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
291
291
  [3.0.28]: https://github.com/Donix-VN/fca-unofficial/compare/v3.0.27...v3.0.28
292
292
  [3.0.27]: https://github.com/Donix-VN/fca-unofficial/compare/v3.0.25...v3.0.27
293
293
  [1.0.10]: https://github.com/Donix-VN/fca-unofficial/releases/tag/v1.0.10
294
+
295
+ ## v3.0.30 - 2026-03-07
296
+ - Hotfix / auto bump
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dongdev/fca-unofficial",
3
- "version": "3.0.30",
3
+ "version": "3.0.31",
4
4
  "description": "Unofficial Facebook Chat API for Node.js - Interact with Facebook Messenger programmatically",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -74,6 +74,7 @@
74
74
  "https-proxy-agent": "^4.0.0",
75
75
  "mqtt": "^4.3.8",
76
76
  "sequelize": "^6.37.6",
77
+ "sqlite3": "^5.1.7",
77
78
  "totp-generator": "^1.0.0",
78
79
  "ws": "^8.18.1"
79
80
  },
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
 
3
- const { getType } = require("../../utils/format.js");
4
- const { get } = require("../../utils/request.js");
3
+ const { getType } = require("../../utils/format");
4
+ const { get } = require("../../utils/request");
5
5
 
6
6
  const httpGetFactory = function (defaultFuncs, api, ctx) {
7
7
  return function httpGet(url, form, callback, notAPI) {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- const { generateOfflineThreadingID, getType } = require("../../utils/format.js");
3
+ const { generateOfflineThreadingID, getType } = require("../../utils/format");
4
4
  module.exports = function (defaultFuncs, api, ctx) {
5
5
  return function addUserToGroup(userID, threadID, callback) {
6
6
  return new Promise((resolve, reject) => {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- const { generateOfflineThreadingID } = require("../../utils/format.js");
3
+ const { generateOfflineThreadingID } = require("../../utils/format");
4
4
  const log = require("../../../func/logAdapter");
5
5
 
6
6
  module.exports = function (defaultFuncs, api, ctx) {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- const { generateOfflineThreadingID } = require("../../utils/format.js");
3
+ const { generateOfflineThreadingID } = require("../../utils/format");
4
4
  const log = require("../../../func/logAdapter");
5
5
 
6
6
  module.exports = function (defaultFuncs, api, ctx) {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- const { generateOfflineThreadingID } = require("../../utils/format.js");
3
+ const { generateOfflineThreadingID } = require("../../utils/format");
4
4
  module.exports = (defaultFuncs, api, ctx) => {
5
5
  return async (color, threadID, callback) => {
6
6
  let reqID = ++ctx.wsReqNumber;
@@ -1,6 +1,6 @@
1
1
  "use_strict";
2
2
 
3
- const { generateOfflineThreadingID } = require("../../utils/format.js");
3
+ const { generateOfflineThreadingID } = require("../../utils/format");
4
4
  module.exports = (defaultFuncs, api, ctx) => {
5
5
  return (text, messageID, callback) => {
6
6
  let reqID = ctx.wsReqNumber + 1;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
  const { parseAndCheckLogin } = require("../../utils/client");
4
+ const { formatThread } = require("../../utils/format");
4
5
  module.exports = function(defaultFuncs, api, ctx) {
5
6
  return function searchForThread(name, callback) {
6
7
  let resolveFunc = function() {};
@@ -43,7 +44,7 @@ module.exports = function(defaultFuncs, api, ctx) {
43
44
  }
44
45
  return callback(
45
46
  null,
46
- resData.payload.mercury_payload.threads.map(utils.formatThread)
47
+ resData.payload.mercury_payload.threads.map(formatThread)
47
48
  );
48
49
  });
49
50
 
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- const { getType } = require("../../utils/format.js");
2
+ const { getType } = require("../../utils/format");
3
3
  module.exports = function (defaultFuncs, api, ctx) {
4
4
  return function sendTyping(threadID, isTyping, options, callback) {
5
5
  var resolveFunc = function () { };
@@ -49,16 +49,15 @@ module.exports = function (defaultFuncs, api, ctx) {
49
49
  failure_count: null,
50
50
  label: "29",
51
51
  payload: JSON.stringify(taskPayload),
52
- queue_name: JSON.stringify(["reaction", messageID]),
52
+ queue_name: "reaction:" + messageID,
53
53
  task_id: taskID,
54
54
  };
55
55
  const mqttForm = {
56
- app_id: "772021112871879",
56
+ app_id: "2220391788200892",
57
57
  payload: JSON.stringify({
58
- data_trace_id: null,
59
58
  epoch_id: parseInt(generateOfflineThreadingID()),
60
59
  tasks: [task],
61
- version_id: "25376272951962053"
60
+ version_id: "24585299697835063"
62
61
  }),
63
62
  request_id: reqID,
64
63
  type: 3
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- const { generateOfflineThreadingID } = require("../../utils/format.js");
3
+ const { generateOfflineThreadingID } = require("../../utils/format");
4
4
  const log = require("../../../func/logAdapter");
5
5
 
6
6
  module.exports = function (defaultFuncs, api, ctx) {
@@ -137,7 +137,8 @@ module.exports = function (defaultFuncs, api, ctx) {
137
137
  const dbFiles = fs.readdirSync(path.join(__dirname, "../../database"))
138
138
  .filter(f => path.extname(f) === ".js")
139
139
  .reduce((acc, file) => {
140
- acc[path.basename(file, ".js")] = require(path.join(__dirname, "../../database", file))(api);
140
+ const mod = require(path.join(__dirname, "../../database", file));
141
+ acc[path.basename(file, ".js")] = typeof mod === "function" ? mod(api) : mod;
141
142
  return acc;
142
143
  }, {});
143
144
 
@@ -193,10 +193,13 @@ module.exports = function (defaultFuncs, api, ctx) {
193
193
  const queuedUsers = new Set();
194
194
  const cooldown = new Map();
195
195
 
196
- const dbFiles = fs.readdirSync(path.join(__dirname, "../../database")).filter(f => path.extname(f) === ".js").reduce((acc, file) => {
197
- acc[path.basename(file, ".js")] = require(path.join(__dirname, "../../database", file))(api);
198
- return acc;
199
- }, {});
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
+ }, {});
200
203
  const { userData } = dbFiles;
201
204
  const { create, get, update, getAll } = userData;
202
205
 
@@ -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
- 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
  };