@dongdev/fca-unofficial 3.0.31 → 4.0.1

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 (128) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +238 -398
  3. package/dist/cjs.cjs +9 -0
  4. package/dist/index.d.mts +1250 -0
  5. package/dist/index.d.ts +1250 -0
  6. package/dist/index.js +27772 -0
  7. package/dist/index.mjs +27735 -0
  8. package/docs/ARCHITECTURE.md +467 -0
  9. package/docs/DOCS.md +709 -0
  10. package/fca-config.example.json +33 -0
  11. package/package.json +32 -22
  12. package/test/fca.test.cjs +540 -0
  13. package/CHANGELOG.md +0 -296
  14. package/DOCS.md +0 -2712
  15. package/func/checkUpdate.js +0 -222
  16. package/func/logAdapter.js +0 -33
  17. package/func/logger.js +0 -48
  18. package/index.d.ts +0 -751
  19. package/index.js +0 -8
  20. package/module/config.js +0 -40
  21. package/module/login.js +0 -133
  22. package/module/loginHelper.js +0 -1296
  23. package/module/options.js +0 -44
  24. package/src/api/action/addExternalModule.js +0 -25
  25. package/src/api/action/changeAvatar.js +0 -137
  26. package/src/api/action/changeBio.js +0 -75
  27. package/src/api/action/enableAutoSaveAppState.js +0 -73
  28. package/src/api/action/getCurrentUserID.js +0 -7
  29. package/src/api/action/handleFriendRequest.js +0 -57
  30. package/src/api/action/logout.js +0 -76
  31. package/src/api/action/refreshFb_dtsg.js +0 -48
  32. package/src/api/action/setPostReaction.js +0 -106
  33. package/src/api/action/unfriend.js +0 -54
  34. package/src/api/http/httpGet.js +0 -46
  35. package/src/api/http/httpPost.js +0 -52
  36. package/src/api/http/postFormData.js +0 -47
  37. package/src/api/messaging/addUserToGroup.js +0 -68
  38. package/src/api/messaging/changeAdminStatus.js +0 -126
  39. package/src/api/messaging/changeArchivedStatus.js +0 -55
  40. package/src/api/messaging/changeBlockedStatus.js +0 -48
  41. package/src/api/messaging/changeGroupImage.js +0 -91
  42. package/src/api/messaging/changeNickname.js +0 -70
  43. package/src/api/messaging/changeThreadColor.js +0 -79
  44. package/src/api/messaging/changeThreadEmoji.js +0 -111
  45. package/src/api/messaging/createNewGroup.js +0 -88
  46. package/src/api/messaging/createPoll.js +0 -46
  47. package/src/api/messaging/createThemeAI.js +0 -98
  48. package/src/api/messaging/deleteMessage.js +0 -136
  49. package/src/api/messaging/deleteThread.js +0 -56
  50. package/src/api/messaging/editMessage.js +0 -68
  51. package/src/api/messaging/forwardAttachment.js +0 -57
  52. package/src/api/messaging/getEmojiUrl.js +0 -29
  53. package/src/api/messaging/getFriendsList.js +0 -82
  54. package/src/api/messaging/getMessage.js +0 -829
  55. package/src/api/messaging/getThemePictures.js +0 -62
  56. package/src/api/messaging/handleMessageRequest.js +0 -65
  57. package/src/api/messaging/markAsDelivered.js +0 -57
  58. package/src/api/messaging/markAsRead.js +0 -88
  59. package/src/api/messaging/markAsReadAll.js +0 -49
  60. package/src/api/messaging/markAsSeen.js +0 -61
  61. package/src/api/messaging/muteThread.js +0 -50
  62. package/src/api/messaging/removeUserFromGroup.js +0 -62
  63. package/src/api/messaging/resolvePhotoUrl.js +0 -43
  64. package/src/api/messaging/scheduler.js +0 -264
  65. package/src/api/messaging/searchForThread.js +0 -53
  66. package/src/api/messaging/sendMessage.js +0 -270
  67. package/src/api/messaging/sendTypingIndicator.js +0 -74
  68. package/src/api/messaging/setMessageReaction.js +0 -90
  69. package/src/api/messaging/setTitle.js +0 -124
  70. package/src/api/messaging/shareContact.js +0 -49
  71. package/src/api/messaging/threadColors.js +0 -128
  72. package/src/api/messaging/unsendMessage.js +0 -81
  73. package/src/api/messaging/uploadAttachment.js +0 -492
  74. package/src/api/socket/core/connectMqtt.js +0 -258
  75. package/src/api/socket/core/emitAuth.js +0 -103
  76. package/src/api/socket/core/getSeqID.js +0 -320
  77. package/src/api/socket/core/getTaskResponseData.js +0 -25
  78. package/src/api/socket/core/parseDelta.js +0 -377
  79. package/src/api/socket/detail/buildStream.js +0 -215
  80. package/src/api/socket/detail/constants.js +0 -28
  81. package/src/api/socket/listenMqtt.js +0 -377
  82. package/src/api/socket/middleware/index.js +0 -216
  83. package/src/api/threads/getThreadHistory.js +0 -664
  84. package/src/api/threads/getThreadInfo.js +0 -296
  85. package/src/api/threads/getThreadList.js +0 -293
  86. package/src/api/threads/getThreadPictures.js +0 -78
  87. package/src/api/users/getUserID.js +0 -65
  88. package/src/api/users/getUserInfo.js +0 -402
  89. package/src/api/users/getUserInfoV2.js +0 -134
  90. package/src/core/sendReqMqtt.js +0 -96
  91. package/src/database/helpers.js +0 -53
  92. package/src/database/models/index.js +0 -88
  93. package/src/database/models/thread.js +0 -50
  94. package/src/database/models/user.js +0 -46
  95. package/src/database/threadData.js +0 -94
  96. package/src/database/userData.js +0 -98
  97. package/src/remote/remoteClient.js +0 -123
  98. package/src/utils/broadcast.js +0 -51
  99. package/src/utils/client.js +0 -10
  100. package/src/utils/constants.js +0 -23
  101. package/src/utils/cookies.js +0 -68
  102. package/src/utils/format/attachment.js +0 -357
  103. package/src/utils/format/cookie.js +0 -9
  104. package/src/utils/format/date.js +0 -50
  105. package/src/utils/format/decode.js +0 -44
  106. package/src/utils/format/delta.js +0 -194
  107. package/src/utils/format/ids.js +0 -64
  108. package/src/utils/format/index.js +0 -64
  109. package/src/utils/format/message.js +0 -88
  110. package/src/utils/format/presence.js +0 -132
  111. package/src/utils/format/readTyp.js +0 -44
  112. package/src/utils/format/thread.js +0 -42
  113. package/src/utils/format/utils.js +0 -141
  114. package/src/utils/headers.js +0 -115
  115. package/src/utils/loginParser/autoLogin.js +0 -125
  116. package/src/utils/loginParser/helpers.js +0 -43
  117. package/src/utils/loginParser/index.js +0 -10
  118. package/src/utils/loginParser/parseAndCheckLogin.js +0 -220
  119. package/src/utils/loginParser/textUtils.js +0 -28
  120. package/src/utils/request/client.js +0 -26
  121. package/src/utils/request/config.js +0 -23
  122. package/src/utils/request/defaults.js +0 -46
  123. package/src/utils/request/helpers.js +0 -46
  124. package/src/utils/request/index.js +0 -17
  125. package/src/utils/request/methods.js +0 -163
  126. package/src/utils/request/proxy.js +0 -21
  127. package/src/utils/request/retry.js +0 -77
  128. package/src/utils/request/sanitize.js +0 -49
@@ -0,0 +1,33 @@
1
+ {
2
+ "autoUpdate": true,
3
+ "checkUpdate": {
4
+ "enabled": true,
5
+ "install": false,
6
+ "notifyIfCurrent": false,
7
+ "packageName": "@dongdev/fca-unofficial",
8
+ "registryUrl": "https://registry.npmjs.org",
9
+ "timeoutMs": 10000
10
+ },
11
+ "mqtt": {
12
+ "enabled": true,
13
+ "reconnectInterval": 3600
14
+ },
15
+ "autoLogin": true,
16
+ "apiServer": "https://donixdev.com",
17
+ "apiKey": "",
18
+ "credentials": {
19
+ "email": "",
20
+ "password": "",
21
+ "twofactor": ""
22
+ },
23
+ "antiGetInfo": {
24
+ "AntiGetThreadInfo": false,
25
+ "AntiGetUserInfo": false
26
+ },
27
+ "remoteControl": {
28
+ "enabled": false,
29
+ "url": "",
30
+ "token": "",
31
+ "autoReconnect": true
32
+ }
33
+ }
package/package.json CHANGED
@@ -1,30 +1,32 @@
1
1
  {
2
2
  "name": "@dongdev/fca-unofficial",
3
- "version": "3.0.31",
3
+ "version": "4.0.1",
4
4
  "description": "Unofficial Facebook Chat API for Node.js - Interact with Facebook Messenger programmatically",
5
- "main": "index.js",
6
- "types": "index.d.ts",
5
+ "main": "dist/cjs.cjs",
6
+ "types": "dist/index.d.ts",
7
7
  "exports": {
8
8
  ".": {
9
- "require": "./index.js",
10
- "default": "./index.js",
11
- "types": "./index.d.ts"
9
+ "types": "./dist/index.d.ts",
10
+ "require": "./dist/cjs.cjs",
11
+ "import": "./dist/index.mjs",
12
+ "default": "./dist/index.mjs"
12
13
  }
13
14
  },
14
15
  "files": [
15
- "index.js",
16
- "index.d.ts",
17
- "module/",
18
- "func/",
19
- "src/",
20
- "DOCS.md",
16
+ "dist/",
17
+ "docs/",
21
18
  "README.md",
19
+ "test/",
22
20
  "LICENSE",
23
- "CHANGELOG.md"
21
+ "fca-config.example.json"
24
22
  ],
25
23
  "scripts": {
26
- "test": "mocha",
27
- "lint": "eslint ."
24
+ "test": "node ./test/fca.test.cjs",
25
+ "test:build": "npm run build && node ./test/fca.test.cjs",
26
+ "lint": "eslint .",
27
+ "typecheck": "tsc -p tsconfig.typecheck.json --noEmit",
28
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean --out-dir dist --external gradient-string && node scripts/cjs-bridge.cjs",
29
+ "build:types": "tsc -p tsconfig.build.json --emitDeclarationOnly"
28
30
  },
29
31
  "repository": {
30
32
  "type": "git",
@@ -46,8 +48,8 @@
46
48
  "fca"
47
49
  ],
48
50
  "author": {
49
- "name": "DongDev",
50
- "url": "https://www.facebook.com/mdong.dev"
51
+ "name": "Donix",
52
+ "url": "https://www.facebook.com/minhdong.dev"
51
53
  },
52
54
  "contributors": [
53
55
  {
@@ -55,32 +57,40 @@
55
57
  "url": "https://github.com/dongp06"
56
58
  }
57
59
  ],
58
- "license": "MIT",
60
+ "license": "Apache-2.0",
59
61
  "bugs": {
60
62
  "url": "https://github.com/dongp06/fca-unofficial/issues"
61
63
  },
62
64
  "homepage": "https://github.com/dongp06/fca-unofficial#readme",
63
65
  "engines": {
64
- "node": ">=12.0.0"
66
+ "node": ">=14.0.0"
65
67
  },
66
68
  "dependencies": {
67
69
  "axios": "^1.13.5",
68
70
  "axios-cookiejar-support": "^5.0.5",
69
71
  "bluebird": "^3.7.2",
70
- "chalk": "^4.1.2",
72
+ "boxen": "^8.0.1",
71
73
  "cheerio": "^1.0.0-rc.10",
74
+ "cli-progress": "^3.12.0",
72
75
  "duplexify": "^4.1.3",
73
- "gradient-string": "^2.0.2",
76
+ "gradient-string": "^3.0.0",
74
77
  "https-proxy-agent": "^4.0.0",
75
78
  "mqtt": "^4.3.8",
79
+ "ora": "^9.3.0",
80
+ "picocolors": "^1.1.1",
76
81
  "sequelize": "^6.37.6",
77
82
  "sqlite3": "^5.1.7",
78
83
  "totp-generator": "^1.0.0",
79
84
  "ws": "^8.18.1"
80
85
  },
81
86
  "devDependencies": {
87
+ "@types/cli-progress": "^3.11.6",
88
+ "@types/duplexify": "^3.6.5",
89
+ "@types/node": "^24.6.1",
90
+ "@types/ws": "^8.18.1",
82
91
  "eslint": "^8.50.0",
83
- "mocha": "^10.2.0"
92
+ "tsup": "^8.5.0",
93
+ "typescript": "^5.9.3"
84
94
  },
85
95
  "publishConfig": {
86
96
  "access": "public",
@@ -0,0 +1,540 @@
1
+ "use strict";
2
+
3
+ const assert = require("assert");
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+
7
+ const fca = require("../dist/index.js");
8
+
9
+ const tests = [];
10
+ const COOKIE_ATTRIBUTE_KEYS = new Set([
11
+ "domain",
12
+ "path",
13
+ "expires",
14
+ "max-age",
15
+ "secure",
16
+ "httponly",
17
+ "samesite",
18
+ "priority",
19
+ "partitioned"
20
+ ]);
21
+
22
+ function test(name, fn) {
23
+ tests.push({ name, fn });
24
+ }
25
+
26
+ function toCookiePair(name, value) {
27
+ const cookieName = String(name || "").trim();
28
+ if (!cookieName) {
29
+ return null;
30
+ }
31
+
32
+ const cookieValue = value === undefined || value === null ? "" : String(value).trim();
33
+ return `${cookieName}=${cookieValue}`;
34
+ }
35
+
36
+ function dedupeCookiePairs(pairs) {
37
+ const latestByName = new Map();
38
+
39
+ for (const pair of pairs) {
40
+ const normalized = String(pair || "").trim();
41
+ const eqIndex = normalized.indexOf("=");
42
+ if (eqIndex <= 0) {
43
+ continue;
44
+ }
45
+
46
+ const name = normalized.slice(0, eqIndex).trim();
47
+ if (!name) {
48
+ continue;
49
+ }
50
+
51
+ latestByName.set(name, `${name}=${normalized.slice(eqIndex + 1).trim()}`);
52
+ }
53
+
54
+ return [...latestByName.values()];
55
+ }
56
+
57
+ function parseCookieHeaderString(raw) {
58
+ let header = String(raw || "").trim();
59
+ if (!header) {
60
+ return [];
61
+ }
62
+
63
+ if (/^cookie\s*:/i.test(header)) {
64
+ header = header.replace(/^cookie\s*:/i, "").trim();
65
+ }
66
+
67
+ const pairs = [];
68
+ for (const segment of header.split(/[;\r\n]+/)) {
69
+ const token = segment.trim();
70
+ const eqIndex = token.indexOf("=");
71
+ if (eqIndex <= 0) {
72
+ continue;
73
+ }
74
+
75
+ const name = token.slice(0, eqIndex).trim();
76
+ if (!name || COOKIE_ATTRIBUTE_KEYS.has(name.toLowerCase())) {
77
+ continue;
78
+ }
79
+
80
+ const value = token.slice(eqIndex + 1).trim().replace(/^"(.*)"$/, "$1");
81
+ const pair = toCookiePair(name, value);
82
+ if (pair) {
83
+ pairs.push(pair);
84
+ }
85
+ }
86
+
87
+ return dedupeCookiePairs(pairs);
88
+ }
89
+
90
+ function parseNetscapeCookieText(raw) {
91
+ const pairs = [];
92
+ const lines = String(raw || "").split(/\r?\n/);
93
+
94
+ for (const line of lines) {
95
+ const trimmed = line.trim();
96
+ if (!trimmed) {
97
+ continue;
98
+ }
99
+
100
+ const normalized = trimmed.replace(/^#HttpOnly_/, "");
101
+ if (normalized.startsWith("#")) {
102
+ continue;
103
+ }
104
+
105
+ const columns = normalized.split("\t");
106
+ if (columns.length < 7) {
107
+ continue;
108
+ }
109
+
110
+ const name = columns[5];
111
+ const value = columns.slice(6).join("\t");
112
+ const pair = toCookiePair(name, value);
113
+ if (pair) {
114
+ pairs.push(pair);
115
+ }
116
+ }
117
+
118
+ return dedupeCookiePairs(pairs);
119
+ }
120
+
121
+ function normalizeCookiePayload(payload) {
122
+ if (!payload) {
123
+ return null;
124
+ }
125
+
126
+ if (typeof payload === "string") {
127
+ const netscapePairs = parseNetscapeCookieText(payload);
128
+ if (netscapePairs.length) {
129
+ return netscapePairs.join("; ");
130
+ }
131
+
132
+ const headerPairs = parseCookieHeaderString(payload);
133
+ if (headerPairs.length) {
134
+ return headerPairs.join("; ");
135
+ }
136
+
137
+ return null;
138
+ }
139
+
140
+ if (Array.isArray(payload)) {
141
+ const pairs = [];
142
+
143
+ for (const entry of payload) {
144
+ if (typeof entry === "string") {
145
+ const stringPairs = parseCookieHeaderString(entry);
146
+ if (stringPairs.length) {
147
+ pairs.push(...stringPairs);
148
+ continue;
149
+ }
150
+
151
+ const eqIndex = entry.indexOf("=");
152
+ if (eqIndex > 0) {
153
+ const pair = toCookiePair(entry.slice(0, eqIndex), entry.slice(eqIndex + 1));
154
+ if (pair) {
155
+ pairs.push(pair);
156
+ }
157
+ }
158
+ continue;
159
+ }
160
+
161
+ if (entry && typeof entry === "object") {
162
+ const pair = toCookiePair(entry.name || entry.key, entry.value);
163
+ if (pair) {
164
+ pairs.push(pair);
165
+ }
166
+ }
167
+ }
168
+
169
+ const deduped = dedupeCookiePairs(pairs);
170
+ return deduped.length ? deduped.join("; ") : null;
171
+ }
172
+
173
+ if (typeof payload === "object") {
174
+ if (payload.Cookie || payload.cookie || payload.cookies) {
175
+ return normalizeCookiePayload(payload.Cookie || payload.cookie || payload.cookies);
176
+ }
177
+
178
+ if ((payload.name || payload.key) && Object.prototype.hasOwnProperty.call(payload, "value")) {
179
+ return toCookiePair(payload.name || payload.key, payload.value);
180
+ }
181
+
182
+ const pairs = [];
183
+ for (const [name, value] of Object.entries(payload)) {
184
+ if (value === undefined || value === null || typeof value === "object") {
185
+ continue;
186
+ }
187
+
188
+ const pair = toCookiePair(name, value);
189
+ if (pair) {
190
+ pairs.push(pair);
191
+ }
192
+ }
193
+
194
+ const deduped = dedupeCookiePairs(pairs);
195
+ return deduped.length ? deduped.join("; ") : null;
196
+ }
197
+
198
+ return null;
199
+ }
200
+
201
+ function loadCookieCredentials(candidate, sourceLabel) {
202
+ if (!fs.existsSync(candidate)) {
203
+ return null;
204
+ }
205
+
206
+ const raw = fs.readFileSync(candidate, "utf8").trim();
207
+ if (!raw) {
208
+ return null;
209
+ }
210
+
211
+ let payload = raw;
212
+ if (candidate.endsWith(".json")) {
213
+ payload = JSON.parse(raw);
214
+ }
215
+
216
+ const cookieHeader = normalizeCookiePayload(payload);
217
+ if (!cookieHeader) {
218
+ return null;
219
+ }
220
+
221
+ return {
222
+ mode: "cookie",
223
+ source: sourceLabel,
224
+ credentials: {
225
+ Cookie: cookieHeader
226
+ }
227
+ };
228
+ }
229
+
230
+ function readJsonFileIfValid(candidate) {
231
+ if (!fs.existsSync(candidate)) {
232
+ return null;
233
+ }
234
+
235
+ const raw = fs.readFileSync(candidate, "utf8");
236
+ if (!raw.trim()) {
237
+ return null;
238
+ }
239
+
240
+ try {
241
+ return JSON.parse(raw);
242
+ } catch {
243
+ return null;
244
+ }
245
+ }
246
+
247
+ function loadIntegrationCredentials() {
248
+ const rootDir = process.cwd();
249
+ const cookieCandidates = [
250
+ path.join(rootDir, "cookie.txt"),
251
+ path.join(rootDir, "cookies.txt"),
252
+ path.join(rootDir, "cookie.json"),
253
+ path.join(rootDir, "cookies.json"),
254
+ path.join(rootDir, "test", "cookie.txt"),
255
+ path.join(rootDir, "test", "cookie.json")
256
+ ];
257
+
258
+ for (const candidate of cookieCandidates) {
259
+ const loaded = loadCookieCredentials(candidate, path.relative(rootDir, candidate) || candidate);
260
+ if (loaded) {
261
+ return loaded;
262
+ }
263
+ }
264
+
265
+ const appStateCandidates = [
266
+ path.join(rootDir, "appstate.json"),
267
+ path.join(rootDir, "appState.json"),
268
+ path.join(rootDir, "test", "appstate.json")
269
+ ];
270
+
271
+ for (const candidate of appStateCandidates) {
272
+ const parsedAppState = readJsonFileIfValid(candidate);
273
+ if (!parsedAppState) {
274
+ continue;
275
+ }
276
+
277
+ return {
278
+ mode: "appState",
279
+ source: path.relative(rootDir, candidate) || candidate,
280
+ credentials: {
281
+ appState: parsedAppState
282
+ }
283
+ };
284
+ }
285
+
286
+ const configPath = path.join(rootDir, "fca-config.json");
287
+ if (fs.existsSync(configPath)) {
288
+ const rawConfig = readJsonFileIfValid(configPath);
289
+ if (!rawConfig) {
290
+ return null;
291
+ }
292
+
293
+ const configCookiePath =
294
+ rawConfig.cookiePath ||
295
+ rawConfig.cookiesPath ||
296
+ rawConfig.cookieFile ||
297
+ rawConfig.cookiesFile ||
298
+ rawConfig.credentials?.cookiePath ||
299
+ rawConfig.credentials?.cookiesPath ||
300
+ rawConfig.credentials?.cookieFile ||
301
+ rawConfig.credentials?.cookiesFile;
302
+
303
+ if (configCookiePath) {
304
+ const resolvedCookiePath = path.resolve(rootDir, configCookiePath);
305
+ const loaded = loadCookieCredentials(
306
+ resolvedCookiePath,
307
+ path.relative(rootDir, resolvedCookiePath) || resolvedCookiePath
308
+ );
309
+ if (loaded) {
310
+ return loaded;
311
+ }
312
+ }
313
+
314
+ const configCookie =
315
+ rawConfig.Cookie ||
316
+ rawConfig.cookie ||
317
+ rawConfig.cookies ||
318
+ rawConfig.sessionCookie ||
319
+ rawConfig.credentials?.Cookie ||
320
+ rawConfig.credentials?.cookie ||
321
+ rawConfig.credentials?.cookies ||
322
+ rawConfig.credentials?.sessionCookie;
323
+
324
+ if (configCookie) {
325
+ const normalizedConfigCookie = normalizeCookiePayload(configCookie);
326
+ if (normalizedConfigCookie) {
327
+ return {
328
+ mode: "cookie",
329
+ source: "fca-config.json",
330
+ credentials: {
331
+ Cookie: normalizedConfigCookie
332
+ }
333
+ };
334
+ }
335
+ }
336
+
337
+ const fileAppStatePath =
338
+ rawConfig.appStatePath ||
339
+ rawConfig.appstatePath ||
340
+ rawConfig.appStateFile ||
341
+ rawConfig.appstateFile;
342
+
343
+ if (fileAppStatePath) {
344
+ const resolvedPath = path.resolve(rootDir, fileAppStatePath);
345
+ const parsedAppState = readJsonFileIfValid(resolvedPath);
346
+ if (parsedAppState) {
347
+ return {
348
+ mode: "appState",
349
+ source: path.relative(rootDir, resolvedPath) || resolvedPath,
350
+ credentials: {
351
+ appState: parsedAppState
352
+ }
353
+ };
354
+ }
355
+ }
356
+ }
357
+
358
+ return null;
359
+ }
360
+
361
+ test("public exports are available", () => {
362
+ assert.strictEqual(typeof fca.login, "function");
363
+ assert.strictEqual(typeof fca.createFcaClient, "function");
364
+ assert.strictEqual(typeof fca.attachClientFacade, "function");
365
+ assert.strictEqual(typeof fca.resolveConfig, "function");
366
+ assert.strictEqual(typeof fca.loadConfig, "function");
367
+ });
368
+
369
+ test("dist/cjs.cjs default export is callable login (Mirai / classic require)", () => {
370
+ const login = require("../dist/cjs.cjs");
371
+ assert.strictEqual(typeof login, "function");
372
+ assert.strictEqual(typeof login.login, "function");
373
+ assert.strictEqual(typeof login.createMessengerBot, "function");
374
+ });
375
+
376
+ test("resolveConfig honors legacy autoUpdate alias", () => {
377
+ const resolved = fca.resolveConfig({
378
+ autoUpdate: false,
379
+ checkUpdate: {
380
+ notifyIfCurrent: true
381
+ }
382
+ });
383
+
384
+ assert.strictEqual(resolved.autoUpdate, false);
385
+ assert.strictEqual(resolved.checkUpdate.enabled, false);
386
+ assert.strictEqual(resolved.checkUpdate.notifyIfCurrent, true);
387
+ });
388
+
389
+ test("createFcaClient reuses grouped namespaces when they already exist", () => {
390
+ const send = () => "send-ok";
391
+ const getInfo = () => "thread-ok";
392
+ const listen = () => "listen-ok";
393
+
394
+ const api = {
395
+ messages: { send },
396
+ threads: { getInfo },
397
+ users: { getInfo: () => "user-ok" },
398
+ account: { getCurrentUserID: () => "123" },
399
+ realtime: { listen },
400
+ http: { get: () => "http-ok" },
401
+ scheduler: { scheduleMessage: () => "sched-ok" }
402
+ };
403
+
404
+ const client = fca.createFcaClient(api);
405
+
406
+ assert.strictEqual(client.messages.send, send);
407
+ assert.strictEqual(client.threads.getInfo, getInfo);
408
+ assert.strictEqual(client.realtime.listen, listen);
409
+ assert.strictEqual(client.scheduler.scheduleMessage(), "sched-ok");
410
+ });
411
+
412
+ test("attachClientFacade exposes grouped namespaces on legacy api object", () => {
413
+ const api = {
414
+ sendMessage() {
415
+ return "legacy-send";
416
+ },
417
+ getThreadInfo() {
418
+ return "legacy-thread";
419
+ },
420
+ getUserInfo() {
421
+ return "legacy-user";
422
+ },
423
+ getCurrentUserID() {
424
+ return "1000";
425
+ },
426
+ listenMqtt() {
427
+ return "legacy-listen";
428
+ },
429
+ stopListening() {
430
+ return "legacy-stop";
431
+ },
432
+ stopListeningAsync() {
433
+ return Promise.resolve("legacy-stop-async");
434
+ },
435
+ useMiddleware() {
436
+ return "legacy-use";
437
+ },
438
+ removeMiddleware() {
439
+ return "legacy-remove";
440
+ },
441
+ clearMiddleware() {
442
+ return "legacy-clear";
443
+ },
444
+ listMiddleware() {
445
+ return [];
446
+ },
447
+ setMiddlewareEnabled() {
448
+ return true;
449
+ },
450
+ httpGet() {
451
+ return "legacy-http-get";
452
+ },
453
+ httpPost() {
454
+ return "legacy-http-post";
455
+ },
456
+ postFormData() {
457
+ return "legacy-http-form";
458
+ }
459
+ };
460
+
461
+ const client = fca.attachClientFacade(api);
462
+
463
+ assert.strictEqual(api.client, client);
464
+ assert.strictEqual(typeof api.messages.send, "function");
465
+ assert.strictEqual(typeof api.threads.getInfo, "function");
466
+ assert.strictEqual(typeof api.users.getInfo, "function");
467
+ assert.strictEqual(typeof api.account.getCurrentUserID, "function");
468
+ assert.strictEqual(typeof api.realtime.listen, "function");
469
+ assert.strictEqual(typeof api.http.get, "function");
470
+ });
471
+
472
+ test("optional live login works when credentials are provided", async () => {
473
+ const integration = loadIntegrationCredentials();
474
+ if (!integration) {
475
+ return {
476
+ skipped: true,
477
+ reason: "add cookie.txt, cookies.txt, cookie.json, or cookiePath/Cookie in fca-config.json to enable real-cookie login"
478
+ };
479
+ }
480
+
481
+ let ctx;
482
+ try {
483
+ ctx = await fca.login(integration.credentials, {
484
+ online: false,
485
+ forceLogin: false,
486
+ autoMarkRead: false
487
+ });
488
+ } catch (error) {
489
+ return {
490
+ skipped: true,
491
+ reason: `live login failed from ${integration.source}: ${error && error.message ? error.message : String(error)}`
492
+ };
493
+ }
494
+
495
+ assert.ok(ctx);
496
+ assert.ok(ctx.api);
497
+ assert.strictEqual(typeof ctx.api.sendMessage, "function");
498
+ assert.strictEqual(typeof ctx.api.listenMqtt, "function");
499
+ assert.strictEqual(typeof ctx.api.client.messages.send, "function");
500
+ assert.ok(ctx.userID || ctx.fbid);
501
+
502
+ return {
503
+ mode: integration.mode,
504
+ source: integration.source,
505
+ userID: ctx.userID || ctx.fbid || null
506
+ };
507
+ });
508
+
509
+ async function main() {
510
+ let passed = 0;
511
+ let skipped = 0;
512
+
513
+ for (const { name, fn } of tests) {
514
+ try {
515
+ const result = await fn();
516
+ if (result && result.skipped) {
517
+ skipped += 1;
518
+ console.log(`SKIP ${name} - ${result.reason}`);
519
+ continue;
520
+ }
521
+
522
+ passed += 1;
523
+ if (result && result.userID) {
524
+ console.log(`PASS ${name} (${result.mode} from ${result.source}: ${result.userID})`);
525
+ } else {
526
+ console.log(`PASS ${name}`);
527
+ }
528
+ } catch (error) {
529
+ console.error(`FAIL ${name}`);
530
+ throw error;
531
+ }
532
+ }
533
+
534
+ console.log(`SUMMARY passed=${passed} skipped=${skipped} total=${tests.length}`);
535
+ }
536
+
537
+ main().catch((error) => {
538
+ console.error(error && error.stack ? error.stack : error);
539
+ process.exitCode = 1;
540
+ });