@dongdev/fca-unofficial 4.0.1 → 4.0.3

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/README.md CHANGED
@@ -268,7 +268,9 @@ When `stopOnSignals` is `true`, the bot automatically calls `stop()` on `SIGINT`
268
268
 
269
269
  ## Configuration
270
270
 
271
- Copy the example config and edit it:
271
+ If `fca-config.json` is missing in the process working directory, it is **created automatically** with defaults on first load (or in-memory defaults only if the file cannot be written).
272
+
273
+ You can also copy the example and edit it:
272
274
 
273
275
  ```bash
274
276
  cp fca-config.example.json fca-config.json
package/dist/index.d.mts CHANGED
@@ -313,6 +313,13 @@ interface FcaConfig {
313
313
  token: string;
314
314
  autoReconnect: boolean;
315
315
  };
316
+ /** SQLite thread row cache used by `getThreadInfo`. */
317
+ threadCache: {
318
+ /** Treat DB row as fresh for this many ms (default 15 minutes). */
319
+ maxAgeMs: number;
320
+ /** Clear `data` for all threads on this interval so the next `getThreadInfo` refetches; 0 = off. */
321
+ invalidateIntervalMs: number;
322
+ };
316
323
  [key: string]: Loose;
317
324
  }
318
325
  interface LoadedFcaConfig {
package/dist/index.d.ts CHANGED
@@ -313,6 +313,13 @@ interface FcaConfig {
313
313
  token: string;
314
314
  autoReconnect: boolean;
315
315
  };
316
+ /** SQLite thread row cache used by `getThreadInfo`. */
317
+ threadCache: {
318
+ /** Treat DB row as fresh for this many ms (default 15 minutes). */
319
+ maxAgeMs: number;
320
+ /** Clear `data` for all threads on this interval so the next `getThreadInfo` refetches; 0 = off. */
321
+ invalidateIntervalMs: number;
322
+ };
316
323
  [key: string]: Loose;
317
324
  }
318
325
  interface LoadedFcaConfig {
package/dist/index.js CHANGED
@@ -65,8 +65,7 @@ function makeStyles(theme) {
65
65
  info: (v) => import_picocolors.default.cyan(v),
66
66
  warn: (v) => import_picocolors.default.yellow(v),
67
67
  error: (v) => import_picocolors.default.red(v),
68
- sys: (v) => import_picocolors.default.blue(v),
69
- banner: (v) => import_picocolors.default.white(v)
68
+ sys: (v) => import_picocolors.default.blue(v)
70
69
  };
71
70
  }
72
71
  return {
@@ -75,8 +74,7 @@ function makeStyles(theme) {
75
74
  info: (v) => import_picocolors.default.cyan(v),
76
75
  warn: (v) => import_picocolors.default.yellow(v),
77
76
  error: (v) => import_picocolors.default.red(v),
78
- sys: (v) => import_picocolors.default.blue(v),
79
- banner: (v) => import_picocolors.default.cyan(v)
77
+ sys: (v) => import_picocolors.default.blue(v)
80
78
  };
81
79
  }
82
80
  function parseLabel(message, fallback) {
@@ -109,22 +107,7 @@ function formatSuccessBody(body, grad, fallbackPaint) {
109
107
  }
110
108
  return fallbackPaint(body);
111
109
  }
112
- function donixAsciiBlock() {
113
- return [
114
- "____ ____ ____ ____ ____",
115
- "||D ||||O ||||N ||||I ||||X ||",
116
- "||__||||__||||__||||__||||__||",
117
- "|/__\\||/__\\||/__\\||/__\\||/__\\|"
118
- ].join("\n");
119
- }
120
110
  async function ensureUiLibs() {
121
- if (!boxenLib) {
122
- try {
123
- const boxenMod = await import("boxen");
124
- boxenLib = boxenMod.default ?? boxenMod;
125
- } catch {
126
- }
127
- }
128
111
  if (!oraFactory) {
129
112
  try {
130
113
  const oraMod = await import("ora");
@@ -141,51 +124,6 @@ async function ensureUiLibs() {
141
124
  }
142
125
  }
143
126
  }
144
- function printBootBanner(styles) {
145
- if (didPrintBootBanner) return;
146
- didPrintBootBanner = true;
147
- const version = process.env.npm_package_version || "4.0.1";
148
- const theme = getTheme();
149
- const grad = theme === "cyberpunk" ? loadGradientFns() : null;
150
- if (theme === "cyberpunk" && grad && boxenLib) {
151
- const asciiStyled = grad.cyberpunk(donixAsciiBlock());
152
- const titleLine = `${import_picocolors.default.bold(grad.coolStatus("FCA-UNOFFICIAL"))} ${import_picocolors.default.dim(`v${version}`)}`;
153
- const body2 = `${asciiStyled}
154
- ${titleLine}
155
- ${styles.text("Author:")} ${grad.coolStatus("DongDev (Donix-VN)")}
156
- ${styles.text("Status:")} ${import_picocolors.default.green("Ready to Connect")}`;
157
- writeStdout(
158
- boxenLib(body2, {
159
- padding: 1,
160
- margin: 0,
161
- borderStyle: "double",
162
- borderColor: "cyan"
163
- })
164
- );
165
- return;
166
- }
167
- const art = [
168
- "\u2554\u2566\u2557\u2554\u2550\u2557\u2554\u2557\u2554\u2566\u2550\u2557\u2566 \u2566",
169
- " \u2551\u2551\u2551 \u2551\u2551\u2551\u2551\u2560\u2566\u255D\u255A\u2566\u255D",
170
- "\u2550\u2569\u255D\u255A\u2550\u255D\u255D\u255A\u255D\u2569\u255A\u2550 \u2569 DONIX"
171
- ].join("\n");
172
- const body = `${import_picocolors.default.bold(styles.info("FCA-UNOFFICIAL"))} ${import_picocolors.default.dim(`v${version}`)}
173
- ${styles.text("Author:")} ${styles.info("DongDev (Donix-VN)")}
174
- ${styles.text("Status:")} ${import_picocolors.default.green("Ready to Connect")}
175
- ${styles.banner(art)}`;
176
- if (boxenLib) {
177
- writeStdout(
178
- boxenLib(body, {
179
- padding: 1,
180
- margin: 0,
181
- borderStyle: "round",
182
- borderColor: "cyan"
183
- })
184
- );
185
- return;
186
- }
187
- writeStdout(styles.banner(body));
188
- }
189
127
  function logLine(text, type) {
190
128
  const level = String(type || "info").toLowerCase();
191
129
  const message = String(text ?? "");
@@ -222,14 +160,12 @@ function logLine(text, type) {
222
160
  const bodyOut = grad ? grad.coolStatus(parts.body) : styles.info(parts.body);
223
161
  writeStdout(`${ts} ${labelOut} : ${bodyOut}`);
224
162
  }
225
- var import_picocolors, import_gradient_string, didPrintBootBanner, boxenLib, oraFactory, progressCtor, progressPreset, gradientFns, baseLogger, logger_default;
163
+ var import_picocolors, import_gradient_string, oraFactory, progressCtor, progressPreset, gradientFns, baseLogger, logger_default;
226
164
  var init_logger = __esm({
227
165
  "src/func/logger.ts"() {
228
166
  "use strict";
229
167
  import_picocolors = __toESM(require("picocolors"));
230
168
  import_gradient_string = __toESM(require("gradient-string"));
231
- didPrintBootBanner = false;
232
- boxenLib = null;
233
169
  oraFactory = null;
234
170
  progressCtor = null;
235
171
  progressPreset = null;
@@ -240,8 +176,6 @@ var init_logger = __esm({
240
176
  baseLogger.warn = (text) => baseLogger(text, "warn");
241
177
  baseLogger.error = (text) => baseLogger(text, "error");
242
178
  baseLogger.showBanner = async () => {
243
- await ensureUiLibs();
244
- printBootBanner(makeStyles(getTheme()));
245
179
  };
246
180
  baseLogger.startSpinner = async (text) => {
247
181
  await ensureUiLibs();
@@ -1530,7 +1464,7 @@ function attachThreadUpdater(ctx, models2, logger) {
1530
1464
  await Thread2.create({
1531
1465
  threadID: id,
1532
1466
  messageCount: 1,
1533
- data: { threadID: id }
1467
+ data: null
1534
1468
  });
1535
1469
  } catch {
1536
1470
  }
@@ -15515,6 +15449,7 @@ function resolveConfig(input) {
15515
15449
  config2.mqtt = deepMerge(defaultConfig.mqtt, config2.mqtt || {});
15516
15450
  config2.antiGetInfo = deepMerge(defaultConfig.antiGetInfo, config2.antiGetInfo || {});
15517
15451
  config2.remoteControl = deepMerge(defaultConfig.remoteControl, config2.remoteControl || {});
15452
+ config2.threadCache = deepMerge(defaultConfig.threadCache, config2.threadCache || {});
15518
15453
  config2.checkUpdate = deepMerge(defaultConfig.checkUpdate, config2.checkUpdate || {});
15519
15454
  config2.autoLogin = normalizeBoolean(config2.autoLogin, defaultConfig.autoLogin);
15520
15455
  config2.autoUpdate = normalizeBoolean(rawInput.autoUpdate, defaultConfig.autoUpdate);
@@ -15563,6 +15498,17 @@ function resolveConfig(input) {
15563
15498
  1e3,
15564
15499
  normalizeNumber(config2.checkUpdate.timeoutMs, defaultConfig.checkUpdate.timeoutMs)
15565
15500
  );
15501
+ config2.threadCache.maxAgeMs = Math.max(
15502
+ 0,
15503
+ normalizeNumber(config2.threadCache.maxAgeMs, defaultConfig.threadCache.maxAgeMs)
15504
+ );
15505
+ config2.threadCache.invalidateIntervalMs = Math.max(
15506
+ 0,
15507
+ normalizeNumber(
15508
+ config2.threadCache.invalidateIntervalMs,
15509
+ defaultConfig.threadCache.invalidateIntervalMs
15510
+ )
15511
+ );
15566
15512
  config2.autoUpdate = config2.checkUpdate.enabled;
15567
15513
  return config2;
15568
15514
  }
@@ -15572,6 +15518,21 @@ function getConfigPath() {
15572
15518
  function loadConfig() {
15573
15519
  const configPath = getConfigPath();
15574
15520
  if (!import_node_fs.default.existsSync(configPath)) {
15521
+ try {
15522
+ const resolved = resolveConfig(defaultConfig);
15523
+ const payload = `${JSON.stringify(resolved, null, 2)}
15524
+ `;
15525
+ import_node_fs.default.writeFileSync(configPath, payload, "utf8");
15526
+ logger_default(`Created ${import_node_path.default.basename(configPath)} in ${process.cwd()} (defaults).`, "info");
15527
+ return {
15528
+ config: resolved,
15529
+ configPath,
15530
+ exists: true
15531
+ };
15532
+ } catch (err) {
15533
+ const msg = err && err.message ? err.message : String(err);
15534
+ logger_default(`Could not create fca-config.json (${msg}). Using in-memory defaults.`, "warn");
15535
+ }
15575
15536
  return {
15576
15537
  config: resolveConfig(defaultConfig),
15577
15538
  configPath,
@@ -15621,8 +15582,8 @@ var init_config2 = __esm({
15621
15582
  autoUpdate: true,
15622
15583
  checkUpdate: {
15623
15584
  enabled: true,
15624
- install: false,
15625
- notifyIfCurrent: false,
15585
+ install: true,
15586
+ notifyIfCurrent: true,
15626
15587
  packageName: DEFAULT_PACKAGE_NAME,
15627
15588
  registryUrl: DEFAULT_REGISTRY_URL,
15628
15589
  timeoutMs: 1e4
@@ -15641,6 +15602,10 @@ var init_config2 = __esm({
15641
15602
  url: "",
15642
15603
  token: "",
15643
15604
  autoReconnect: true
15605
+ },
15606
+ threadCache: {
15607
+ maxAgeMs: 9e5,
15608
+ invalidateIntervalMs: 9e5
15644
15609
  }
15645
15610
  };
15646
15611
  }
@@ -15652,7 +15617,7 @@ var init_package = __esm({
15652
15617
  "package.json"() {
15653
15618
  package_default = {
15654
15619
  name: "@dongdev/fca-unofficial",
15655
- version: "4.0.1",
15620
+ version: "4.0.3",
15656
15621
  description: "Unofficial Facebook Chat API for Node.js - Interact with Facebook Messenger programmatically",
15657
15622
  main: "dist/cjs.cjs",
15658
15623
  types: "dist/index.d.ts",
@@ -15721,7 +15686,6 @@ var init_package = __esm({
15721
15686
  axios: "^1.13.5",
15722
15687
  "axios-cookiejar-support": "^5.0.5",
15723
15688
  bluebird: "^3.7.2",
15724
- boxen: "^8.0.1",
15725
15689
  cheerio: "^1.0.0-rc.10",
15726
15690
  "cli-progress": "^3.12.0",
15727
15691
  duplexify: "^4.1.3",
@@ -15796,8 +15760,8 @@ function readUpdateConfig(input) {
15796
15760
  }
15797
15761
  const fallback = {
15798
15762
  enabled: true,
15799
- install: false,
15800
- notifyIfCurrent: false,
15763
+ install: true,
15764
+ notifyIfCurrent: true,
15801
15765
  packageName: package_default.name,
15802
15766
  registryUrl: package_default.publishConfig?.registry || "https://registry.npmjs.org",
15803
15767
  timeoutMs: 1e4
@@ -20759,6 +20723,34 @@ var init_threadData = __esm({
20759
20723
  });
20760
20724
 
20761
20725
  // src/domains/threads/queries/get-thread-info.ts
20726
+ function getThreadCacheMaxAgeMs() {
20727
+ try {
20728
+ const cfg2 = globalThis.fca?.config;
20729
+ const ms = cfg2?.threadCache?.maxAgeMs;
20730
+ if (typeof ms === "number" && Number.isFinite(ms) && ms > 0) {
20731
+ return ms;
20732
+ }
20733
+ } catch {
20734
+ }
20735
+ return DEFAULT_CACHE_MAX_AGE_MS;
20736
+ }
20737
+ function isUsableThreadInfoCache(data) {
20738
+ if (data == null || typeof data !== "object" || Array.isArray(data)) {
20739
+ return false;
20740
+ }
20741
+ const o = data;
20742
+ const keys = Object.keys(o);
20743
+ if (keys.length === 0) {
20744
+ return false;
20745
+ }
20746
+ if (keys.length === 1 && keys[0] === "threadID") {
20747
+ return false;
20748
+ }
20749
+ if (!Array.isArray(o.participantIDs)) {
20750
+ return false;
20751
+ }
20752
+ return true;
20753
+ }
20762
20754
  function formatEventReminders(reminder) {
20763
20755
  return {
20764
20756
  reminderID: reminder.id,
@@ -20877,7 +20869,6 @@ function createGetThreadInfoQuery(deps) {
20877
20869
  const { defaultFuncs, api, ctx, logError: logError2 } = deps;
20878
20870
  const threadData = createThreadData(api);
20879
20871
  const { create, get: get3, update } = threadData || {};
20880
- const FRESH_MS = 10 * 60 * 1e3;
20881
20872
  async function loadFromDb(ids) {
20882
20873
  if (!threadData || typeof get3 !== "function") {
20883
20874
  return { fresh: {}, stale: ids };
@@ -20886,12 +20877,13 @@ function createGetThreadInfoQuery(deps) {
20886
20877
  const stale = [];
20887
20878
  const rows = await Promise.all(ids.map((id) => get3(id).catch(() => null)));
20888
20879
  const now = Date.now();
20880
+ const maxAgeMs = getThreadCacheMaxAgeMs();
20889
20881
  for (let index = 0; index < ids.length; index += 1) {
20890
20882
  const id = ids[index];
20891
20883
  const row = rows[index];
20892
- if (row?.data) {
20884
+ if (row?.data != null && isUsableThreadInfoCache(row.data)) {
20893
20885
  const updatedAt = row.updatedAt ? new Date(row.updatedAt).getTime() : 0;
20894
- if (updatedAt && now - updatedAt <= FRESH_MS) {
20886
+ if (updatedAt && now - updatedAt <= maxAgeMs) {
20895
20887
  fresh[id] = row.data;
20896
20888
  } else {
20897
20889
  stale.push(id);
@@ -20990,12 +20982,14 @@ function createGetThreadInfoQuery(deps) {
20990
20982
  return promise;
20991
20983
  };
20992
20984
  }
20985
+ var DEFAULT_CACHE_MAX_AGE_MS;
20993
20986
  var init_get_thread_info = __esm({
20994
20987
  "src/domains/threads/queries/get-thread-info.ts"() {
20995
20988
  "use strict";
20996
20989
  init_legacy_promise();
20997
20990
  init_graphql();
20998
20991
  init_threadData();
20992
+ DEFAULT_CACHE_MAX_AGE_MS = 15 * 60 * 1e3;
20999
20993
  }
21000
20994
  });
21001
20995
 
@@ -25844,6 +25838,32 @@ var init_mqtt = __esm({
25844
25838
  }
25845
25839
  });
25846
25840
 
25841
+ // src/core/thread-cache-periodic.ts
25842
+ function attachThreadCachePeriodicInvalidate(ctx, models2, _logger, cfg2) {
25843
+ const Thread2 = models2?.Thread;
25844
+ if (!Thread2 || typeof Thread2.update !== "function") {
25845
+ return;
25846
+ }
25847
+ const threadUpdate = Thread2.update;
25848
+ const intervalMs = cfg2.threadCache?.invalidateIntervalMs ?? 0;
25849
+ if (!intervalMs || intervalMs < 6e4) {
25850
+ return;
25851
+ }
25852
+ const prev = ctx._threadCacheInvalidateTimer;
25853
+ if (prev != null && typeof clearInterval === "function") {
25854
+ clearInterval(prev);
25855
+ }
25856
+ ctx._threadCacheInvalidateTimer = setInterval(() => {
25857
+ void threadUpdate({ data: null }, { where: {} }).catch(() => {
25858
+ });
25859
+ }, intervalMs);
25860
+ }
25861
+ var init_thread_cache_periodic = __esm({
25862
+ "src/core/thread-cache-periodic.ts"() {
25863
+ "use strict";
25864
+ }
25865
+ });
25866
+
25847
25867
  // src/core/thread-info-realtime-sync.ts
25848
25868
  function parseRowData(raw) {
25849
25869
  if (raw == null) return null;
@@ -27050,6 +27070,7 @@ function loginHelper(appState, Cookie, email, password, globalOptions, callback)
27050
27070
  }
27051
27071
  const { loaded, skipped, namespaces } = attachLegacyApiSurface(api, defaultFuncs, ctxMain, logger_default);
27052
27072
  attachThreadInfoRealtimeSync(ctxMain, models_default, logger_default, api);
27073
+ attachThreadCachePeriodicInvalidate(ctxMain, models_default, logger_default, config);
27053
27074
  if (typeof ui.runMethodLoadProgress === "function") {
27054
27075
  await ui.runMethodLoadProgress(loaded);
27055
27076
  }
@@ -27097,6 +27118,7 @@ var init_login_helper_impl = __esm({
27097
27118
  init_mqtt();
27098
27119
  init_options2();
27099
27120
  init_request2();
27121
+ init_thread_cache_periodic();
27100
27122
  init_thread_info_realtime_sync();
27101
27123
  init_state();
27102
27124
  import_sequelize4 = require("sequelize");
package/dist/index.mjs CHANGED
@@ -72,8 +72,7 @@ function makeStyles(theme) {
72
72
  info: (v) => pc.cyan(v),
73
73
  warn: (v) => pc.yellow(v),
74
74
  error: (v) => pc.red(v),
75
- sys: (v) => pc.blue(v),
76
- banner: (v) => pc.white(v)
75
+ sys: (v) => pc.blue(v)
77
76
  };
78
77
  }
79
78
  return {
@@ -82,8 +81,7 @@ function makeStyles(theme) {
82
81
  info: (v) => pc.cyan(v),
83
82
  warn: (v) => pc.yellow(v),
84
83
  error: (v) => pc.red(v),
85
- sys: (v) => pc.blue(v),
86
- banner: (v) => pc.cyan(v)
84
+ sys: (v) => pc.blue(v)
87
85
  };
88
86
  }
89
87
  function parseLabel(message, fallback) {
@@ -116,22 +114,7 @@ function formatSuccessBody(body, grad, fallbackPaint) {
116
114
  }
117
115
  return fallbackPaint(body);
118
116
  }
119
- function donixAsciiBlock() {
120
- return [
121
- "____ ____ ____ ____ ____",
122
- "||D ||||O ||||N ||||I ||||X ||",
123
- "||__||||__||||__||||__||||__||",
124
- "|/__\\||/__\\||/__\\||/__\\||/__\\|"
125
- ].join("\n");
126
- }
127
117
  async function ensureUiLibs() {
128
- if (!boxenLib) {
129
- try {
130
- const boxenMod = await import("boxen");
131
- boxenLib = boxenMod.default ?? boxenMod;
132
- } catch {
133
- }
134
- }
135
118
  if (!oraFactory) {
136
119
  try {
137
120
  const oraMod = await import("ora");
@@ -148,51 +131,6 @@ async function ensureUiLibs() {
148
131
  }
149
132
  }
150
133
  }
151
- function printBootBanner(styles) {
152
- if (didPrintBootBanner) return;
153
- didPrintBootBanner = true;
154
- const version = process.env.npm_package_version || "4.0.1";
155
- const theme = getTheme();
156
- const grad = theme === "cyberpunk" ? loadGradientFns() : null;
157
- if (theme === "cyberpunk" && grad && boxenLib) {
158
- const asciiStyled = grad.cyberpunk(donixAsciiBlock());
159
- const titleLine = `${pc.bold(grad.coolStatus("FCA-UNOFFICIAL"))} ${pc.dim(`v${version}`)}`;
160
- const body2 = `${asciiStyled}
161
- ${titleLine}
162
- ${styles.text("Author:")} ${grad.coolStatus("DongDev (Donix-VN)")}
163
- ${styles.text("Status:")} ${pc.green("Ready to Connect")}`;
164
- writeStdout(
165
- boxenLib(body2, {
166
- padding: 1,
167
- margin: 0,
168
- borderStyle: "double",
169
- borderColor: "cyan"
170
- })
171
- );
172
- return;
173
- }
174
- const art = [
175
- "\u2554\u2566\u2557\u2554\u2550\u2557\u2554\u2557\u2554\u2566\u2550\u2557\u2566 \u2566",
176
- " \u2551\u2551\u2551 \u2551\u2551\u2551\u2551\u2560\u2566\u255D\u255A\u2566\u255D",
177
- "\u2550\u2569\u255D\u255A\u2550\u255D\u255D\u255A\u255D\u2569\u255A\u2550 \u2569 DONIX"
178
- ].join("\n");
179
- const body = `${pc.bold(styles.info("FCA-UNOFFICIAL"))} ${pc.dim(`v${version}`)}
180
- ${styles.text("Author:")} ${styles.info("DongDev (Donix-VN)")}
181
- ${styles.text("Status:")} ${pc.green("Ready to Connect")}
182
- ${styles.banner(art)}`;
183
- if (boxenLib) {
184
- writeStdout(
185
- boxenLib(body, {
186
- padding: 1,
187
- margin: 0,
188
- borderStyle: "round",
189
- borderColor: "cyan"
190
- })
191
- );
192
- return;
193
- }
194
- writeStdout(styles.banner(body));
195
- }
196
134
  function logLine(text, type) {
197
135
  const level = String(type || "info").toLowerCase();
198
136
  const message = String(text ?? "");
@@ -229,12 +167,10 @@ function logLine(text, type) {
229
167
  const bodyOut = grad ? grad.coolStatus(parts.body) : styles.info(parts.body);
230
168
  writeStdout(`${ts} ${labelOut} : ${bodyOut}`);
231
169
  }
232
- var didPrintBootBanner, boxenLib, oraFactory, progressCtor, progressPreset, gradientFns, baseLogger, logger_default;
170
+ var oraFactory, progressCtor, progressPreset, gradientFns, baseLogger, logger_default;
233
171
  var init_logger = __esm({
234
172
  "src/func/logger.ts"() {
235
173
  "use strict";
236
- didPrintBootBanner = false;
237
- boxenLib = null;
238
174
  oraFactory = null;
239
175
  progressCtor = null;
240
176
  progressPreset = null;
@@ -245,8 +181,6 @@ var init_logger = __esm({
245
181
  baseLogger.warn = (text) => baseLogger(text, "warn");
246
182
  baseLogger.error = (text) => baseLogger(text, "error");
247
183
  baseLogger.showBanner = async () => {
248
- await ensureUiLibs();
249
- printBootBanner(makeStyles(getTheme()));
250
184
  };
251
185
  baseLogger.startSpinner = async (text) => {
252
186
  await ensureUiLibs();
@@ -1535,7 +1469,7 @@ function attachThreadUpdater(ctx, models2, logger) {
1535
1469
  await Thread2.create({
1536
1470
  threadID: id,
1537
1471
  messageCount: 1,
1538
- data: { threadID: id }
1472
+ data: null
1539
1473
  });
1540
1474
  } catch {
1541
1475
  }
@@ -15521,6 +15455,7 @@ function resolveConfig(input) {
15521
15455
  config2.mqtt = deepMerge(defaultConfig.mqtt, config2.mqtt || {});
15522
15456
  config2.antiGetInfo = deepMerge(defaultConfig.antiGetInfo, config2.antiGetInfo || {});
15523
15457
  config2.remoteControl = deepMerge(defaultConfig.remoteControl, config2.remoteControl || {});
15458
+ config2.threadCache = deepMerge(defaultConfig.threadCache, config2.threadCache || {});
15524
15459
  config2.checkUpdate = deepMerge(defaultConfig.checkUpdate, config2.checkUpdate || {});
15525
15460
  config2.autoLogin = normalizeBoolean(config2.autoLogin, defaultConfig.autoLogin);
15526
15461
  config2.autoUpdate = normalizeBoolean(rawInput.autoUpdate, defaultConfig.autoUpdate);
@@ -15569,6 +15504,17 @@ function resolveConfig(input) {
15569
15504
  1e3,
15570
15505
  normalizeNumber(config2.checkUpdate.timeoutMs, defaultConfig.checkUpdate.timeoutMs)
15571
15506
  );
15507
+ config2.threadCache.maxAgeMs = Math.max(
15508
+ 0,
15509
+ normalizeNumber(config2.threadCache.maxAgeMs, defaultConfig.threadCache.maxAgeMs)
15510
+ );
15511
+ config2.threadCache.invalidateIntervalMs = Math.max(
15512
+ 0,
15513
+ normalizeNumber(
15514
+ config2.threadCache.invalidateIntervalMs,
15515
+ defaultConfig.threadCache.invalidateIntervalMs
15516
+ )
15517
+ );
15572
15518
  config2.autoUpdate = config2.checkUpdate.enabled;
15573
15519
  return config2;
15574
15520
  }
@@ -15578,6 +15524,21 @@ function getConfigPath() {
15578
15524
  function loadConfig() {
15579
15525
  const configPath = getConfigPath();
15580
15526
  if (!fs.existsSync(configPath)) {
15527
+ try {
15528
+ const resolved = resolveConfig(defaultConfig);
15529
+ const payload = `${JSON.stringify(resolved, null, 2)}
15530
+ `;
15531
+ fs.writeFileSync(configPath, payload, "utf8");
15532
+ logger_default(`Created ${path.basename(configPath)} in ${process.cwd()} (defaults).`, "info");
15533
+ return {
15534
+ config: resolved,
15535
+ configPath,
15536
+ exists: true
15537
+ };
15538
+ } catch (err) {
15539
+ const msg = err && err.message ? err.message : String(err);
15540
+ logger_default(`Could not create fca-config.json (${msg}). Using in-memory defaults.`, "warn");
15541
+ }
15581
15542
  return {
15582
15543
  config: resolveConfig(defaultConfig),
15583
15544
  configPath,
@@ -15625,8 +15586,8 @@ var init_config2 = __esm({
15625
15586
  autoUpdate: true,
15626
15587
  checkUpdate: {
15627
15588
  enabled: true,
15628
- install: false,
15629
- notifyIfCurrent: false,
15589
+ install: true,
15590
+ notifyIfCurrent: true,
15630
15591
  packageName: DEFAULT_PACKAGE_NAME,
15631
15592
  registryUrl: DEFAULT_REGISTRY_URL,
15632
15593
  timeoutMs: 1e4
@@ -15645,6 +15606,10 @@ var init_config2 = __esm({
15645
15606
  url: "",
15646
15607
  token: "",
15647
15608
  autoReconnect: true
15609
+ },
15610
+ threadCache: {
15611
+ maxAgeMs: 9e5,
15612
+ invalidateIntervalMs: 9e5
15648
15613
  }
15649
15614
  };
15650
15615
  }
@@ -15656,7 +15621,7 @@ var init_package = __esm({
15656
15621
  "package.json"() {
15657
15622
  package_default = {
15658
15623
  name: "@dongdev/fca-unofficial",
15659
- version: "4.0.1",
15624
+ version: "4.0.3",
15660
15625
  description: "Unofficial Facebook Chat API for Node.js - Interact with Facebook Messenger programmatically",
15661
15626
  main: "dist/cjs.cjs",
15662
15627
  types: "dist/index.d.ts",
@@ -15725,7 +15690,6 @@ var init_package = __esm({
15725
15690
  axios: "^1.13.5",
15726
15691
  "axios-cookiejar-support": "^5.0.5",
15727
15692
  bluebird: "^3.7.2",
15728
- boxen: "^8.0.1",
15729
15693
  cheerio: "^1.0.0-rc.10",
15730
15694
  "cli-progress": "^3.12.0",
15731
15695
  duplexify: "^4.1.3",
@@ -15802,8 +15766,8 @@ function readUpdateConfig(input) {
15802
15766
  }
15803
15767
  const fallback = {
15804
15768
  enabled: true,
15805
- install: false,
15806
- notifyIfCurrent: false,
15769
+ install: true,
15770
+ notifyIfCurrent: true,
15807
15771
  packageName: package_default.name,
15808
15772
  registryUrl: package_default.publishConfig?.registry || "https://registry.npmjs.org",
15809
15773
  timeoutMs: 1e4
@@ -20760,6 +20724,34 @@ var init_threadData = __esm({
20760
20724
  });
20761
20725
 
20762
20726
  // src/domains/threads/queries/get-thread-info.ts
20727
+ function getThreadCacheMaxAgeMs() {
20728
+ try {
20729
+ const cfg2 = globalThis.fca?.config;
20730
+ const ms = cfg2?.threadCache?.maxAgeMs;
20731
+ if (typeof ms === "number" && Number.isFinite(ms) && ms > 0) {
20732
+ return ms;
20733
+ }
20734
+ } catch {
20735
+ }
20736
+ return DEFAULT_CACHE_MAX_AGE_MS;
20737
+ }
20738
+ function isUsableThreadInfoCache(data) {
20739
+ if (data == null || typeof data !== "object" || Array.isArray(data)) {
20740
+ return false;
20741
+ }
20742
+ const o = data;
20743
+ const keys = Object.keys(o);
20744
+ if (keys.length === 0) {
20745
+ return false;
20746
+ }
20747
+ if (keys.length === 1 && keys[0] === "threadID") {
20748
+ return false;
20749
+ }
20750
+ if (!Array.isArray(o.participantIDs)) {
20751
+ return false;
20752
+ }
20753
+ return true;
20754
+ }
20763
20755
  function formatEventReminders(reminder) {
20764
20756
  return {
20765
20757
  reminderID: reminder.id,
@@ -20878,7 +20870,6 @@ function createGetThreadInfoQuery(deps) {
20878
20870
  const { defaultFuncs, api, ctx, logError: logError2 } = deps;
20879
20871
  const threadData = createThreadData(api);
20880
20872
  const { create, get: get3, update } = threadData || {};
20881
- const FRESH_MS = 10 * 60 * 1e3;
20882
20873
  async function loadFromDb(ids) {
20883
20874
  if (!threadData || typeof get3 !== "function") {
20884
20875
  return { fresh: {}, stale: ids };
@@ -20887,12 +20878,13 @@ function createGetThreadInfoQuery(deps) {
20887
20878
  const stale = [];
20888
20879
  const rows = await Promise.all(ids.map((id) => get3(id).catch(() => null)));
20889
20880
  const now = Date.now();
20881
+ const maxAgeMs = getThreadCacheMaxAgeMs();
20890
20882
  for (let index = 0; index < ids.length; index += 1) {
20891
20883
  const id = ids[index];
20892
20884
  const row = rows[index];
20893
- if (row?.data) {
20885
+ if (row?.data != null && isUsableThreadInfoCache(row.data)) {
20894
20886
  const updatedAt = row.updatedAt ? new Date(row.updatedAt).getTime() : 0;
20895
- if (updatedAt && now - updatedAt <= FRESH_MS) {
20887
+ if (updatedAt && now - updatedAt <= maxAgeMs) {
20896
20888
  fresh[id] = row.data;
20897
20889
  } else {
20898
20890
  stale.push(id);
@@ -20991,12 +20983,14 @@ function createGetThreadInfoQuery(deps) {
20991
20983
  return promise;
20992
20984
  };
20993
20985
  }
20986
+ var DEFAULT_CACHE_MAX_AGE_MS;
20994
20987
  var init_get_thread_info = __esm({
20995
20988
  "src/domains/threads/queries/get-thread-info.ts"() {
20996
20989
  "use strict";
20997
20990
  init_legacy_promise();
20998
20991
  init_graphql();
20999
20992
  init_threadData();
20993
+ DEFAULT_CACHE_MAX_AGE_MS = 15 * 60 * 1e3;
21000
20994
  }
21001
20995
  });
21002
20996
 
@@ -25844,6 +25838,32 @@ var init_mqtt = __esm({
25844
25838
  }
25845
25839
  });
25846
25840
 
25841
+ // src/core/thread-cache-periodic.ts
25842
+ function attachThreadCachePeriodicInvalidate(ctx, models2, _logger, cfg2) {
25843
+ const Thread2 = models2?.Thread;
25844
+ if (!Thread2 || typeof Thread2.update !== "function") {
25845
+ return;
25846
+ }
25847
+ const threadUpdate = Thread2.update;
25848
+ const intervalMs = cfg2.threadCache?.invalidateIntervalMs ?? 0;
25849
+ if (!intervalMs || intervalMs < 6e4) {
25850
+ return;
25851
+ }
25852
+ const prev = ctx._threadCacheInvalidateTimer;
25853
+ if (prev != null && typeof clearInterval === "function") {
25854
+ clearInterval(prev);
25855
+ }
25856
+ ctx._threadCacheInvalidateTimer = setInterval(() => {
25857
+ void threadUpdate({ data: null }, { where: {} }).catch(() => {
25858
+ });
25859
+ }, intervalMs);
25860
+ }
25861
+ var init_thread_cache_periodic = __esm({
25862
+ "src/core/thread-cache-periodic.ts"() {
25863
+ "use strict";
25864
+ }
25865
+ });
25866
+
25847
25867
  // src/core/thread-info-realtime-sync.ts
25848
25868
  function parseRowData(raw) {
25849
25869
  if (raw == null) return null;
@@ -27053,6 +27073,7 @@ function loginHelper(appState, Cookie, email, password, globalOptions, callback)
27053
27073
  }
27054
27074
  const { loaded, skipped, namespaces } = attachLegacyApiSurface(api, defaultFuncs, ctxMain, logger_default);
27055
27075
  attachThreadInfoRealtimeSync(ctxMain, models_default, logger_default, api);
27076
+ attachThreadCachePeriodicInvalidate(ctxMain, models_default, logger_default, config);
27056
27077
  if (typeof ui.runMethodLoadProgress === "function") {
27057
27078
  await ui.runMethodLoadProgress(loaded);
27058
27079
  }
@@ -27098,6 +27119,7 @@ var init_login_helper_impl = __esm({
27098
27119
  init_mqtt();
27099
27120
  init_options2();
27100
27121
  init_request2();
27122
+ init_thread_cache_periodic();
27101
27123
  init_thread_info_realtime_sync();
27102
27124
  init_state();
27103
27125
  g = globalThis;
package/docs/DOCS.md CHANGED
@@ -371,7 +371,9 @@ await client.messages.send("Hi from the facade!", threadID);
371
371
 
372
372
  ## 6. Configuration File (`fca-config.json`)
373
373
 
374
- Copy the example and customize:
374
+ On first load, if `fca-config.json` is missing in the process current working directory, the library **creates** it with default values (pretty-printed JSON). If the filesystem is read-only or creation fails, it falls back to in-memory defaults.
375
+
376
+ You can still start from the shipped example:
375
377
 
376
378
  ```bash
377
379
  cp fca-config.example.json fca-config.json
@@ -385,8 +387,8 @@ cp fca-config.example.json fca-config.json
385
387
  {
386
388
  "checkUpdate": {
387
389
  "enabled": true,
388
- "install": false,
389
- "notifyIfCurrent": false,
390
+ "install": true,
391
+ "notifyIfCurrent": true,
390
392
  "packageName": "@dongdev/fca-unofficial",
391
393
  "registryUrl": "https://registry.npmjs.org",
392
394
  "timeoutMs": 10000
@@ -442,7 +444,7 @@ Used by `autoLogin` and `loginViaAPI` for automatic session recovery.
442
444
  }
443
445
  ```
444
446
 
445
- When enabled, `getThreadInfo` and `getUserInfo` use SQLite-backed caching to reduce repeated GraphQL requests to Facebook.
447
+ When **`AntiGetUserInfo`** is `false` (default), `getUserInfo` uses SQLite-backed caching and GraphQL. Set it to `true` to use only the legacy `/chat/user_info/` HTTP flow. **`AntiGetThreadInfo`** is kept for compatibility; core `getThreadInfo` uses the SQLite + GraphQL path regardless of this flag for now.
446
448
 
447
449
  #### `remoteControl`
448
450
 
@@ -2,8 +2,8 @@
2
2
  "autoUpdate": true,
3
3
  "checkUpdate": {
4
4
  "enabled": true,
5
- "install": false,
6
- "notifyIfCurrent": false,
5
+ "install": true,
6
+ "notifyIfCurrent": true,
7
7
  "packageName": "@dongdev/fca-unofficial",
8
8
  "registryUrl": "https://registry.npmjs.org",
9
9
  "timeoutMs": 10000
@@ -29,5 +29,9 @@
29
29
  "url": "",
30
30
  "token": "",
31
31
  "autoReconnect": true
32
+ },
33
+ "threadCache": {
34
+ "maxAgeMs": 900000,
35
+ "invalidateIntervalMs": 900000
32
36
  }
33
37
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dongdev/fca-unofficial",
3
- "version": "4.0.1",
3
+ "version": "4.0.3",
4
4
  "description": "Unofficial Facebook Chat API for Node.js - Interact with Facebook Messenger programmatically",
5
5
  "main": "dist/cjs.cjs",
6
6
  "types": "dist/index.d.ts",
@@ -69,7 +69,6 @@
69
69
  "axios": "^1.13.5",
70
70
  "axios-cookiejar-support": "^5.0.5",
71
71
  "bluebird": "^3.7.2",
72
- "boxen": "^8.0.1",
73
72
  "cheerio": "^1.0.0-rc.10",
74
73
  "cli-progress": "^3.12.0",
75
74
  "duplexify": "^4.1.3",