@lazyneoaz/metachat 5.0.0 → 5.1.0

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/dist/index.cjs CHANGED
@@ -25450,7 +25450,11 @@ function looksLikeCheckpoint(res) {
25450
25450
  return true;
25451
25451
  }
25452
25452
  const body = typeof res?.body === "string" ? res.body : "";
25453
- return /"error":1357004|account has been (?:disabled|suspended)|confirm your identity|security check/i.test(body.slice(0, 400));
25453
+ if (!body) return false;
25454
+ if (/"isNotCritical"\s*:\s*1/.test(body.slice(0, 400))) return false;
25455
+ return /account has been (?:disabled|suspended)|confirm your identity|security check|we (?:suspended|disabled) your account/i.test(
25456
+ body.slice(0, 400)
25457
+ );
25454
25458
  }
25455
25459
  async function requestWithRetry(requestFunction, retries = 3, baseDelay = 1e3, ctx) {
25456
25460
  const emit2 = (event, payload) => {
@@ -75554,6 +75558,7 @@ __export(utils_exports2, {
75554
75558
  BASE_COOLDOWN_MS: () => BASE_COOLDOWN_MS,
75555
75559
  BRAND: () => BRAND,
75556
75560
  CHROME_MAJORS: () => CHROME_MAJORS,
75561
+ CORE_REACTIONS: () => CORE_REACTIONS,
75557
75562
  ConfigurationError: () => ConfigurationError,
75558
75563
  DESKTOP_PLATFORMS: () => DESKTOP_PLATFORMS,
75559
75564
  FCAError: () => FCAError,
@@ -75565,6 +75570,7 @@ __export(utils_exports2, {
75565
75570
  NotFoundError: () => NotFoundError,
75566
75571
  PING_GRACE_MS: () => PING_GRACE_MS,
75567
75572
  PermissionError: () => PermissionError,
75573
+ REACTION_ALIASES: () => REACTION_ALIASES,
75568
75574
  RateLimitError: () => RateLimitError,
75569
75575
  RealtimeHealth: () => RealtimeHealth,
75570
75576
  RecoverySupervisor: () => RecoverySupervisor,
@@ -75646,6 +75652,8 @@ __export(utils_exports2, {
75646
75652
  isAuthFailure: () => isAuthFailure,
75647
75653
  isBreakerOpen: () => isBreakerOpen,
75648
75654
  isLoggedOutDocument: () => isLoggedOutDocument,
75655
+ isNonCriticalError: () => isNonCriticalError,
75656
+ isReactionEmoji: () => isReactionEmoji,
75649
75657
  isReadableStream: () => isReadableStream2,
75650
75658
  isStaleTokenError: () => isStaleTokenError,
75651
75659
  json: () => json2,
@@ -75656,6 +75664,7 @@ __export(utils_exports2, {
75656
75664
  meta: () => meta,
75657
75665
  missingSessionCookies: () => missingSessionCookies,
75658
75666
  normalizeBrowserProfile: () => normalizeBrowserProfile,
75667
+ normalizeReaction: () => normalizeReaction,
75659
75668
  noteAction: () => noteAction,
75660
75669
  noteAuthFailure: () => noteAuthFailure,
75661
75670
  noteCheckpoint: () => noteCheckpoint,
@@ -75677,6 +75686,7 @@ __export(utils_exports2, {
75677
75686
  resetBreaker: () => resetBreaker,
75678
75687
  resolveProfile: () => resolveProfile,
75679
75688
  retryDelay: () => retryDelay,
75689
+ retryOnNonCritical: () => retryOnNonCritical,
75680
75690
  safetySnapshot: () => safetySnapshot,
75681
75691
  saveCookies: () => saveCookies,
75682
75692
  scheduleSessionKeepAlive: () => scheduleSessionKeepAlive,
@@ -90753,6 +90763,30 @@ function buildWsTaskContent(ctx, tasks, versionId) {
90753
90763
  var import_form_data3 = __toESM(require_form_data2());
90754
90764
  init_constants();
90755
90765
  init_user_agents();
90766
+
90767
+ // src/utils/transient.ts
90768
+ var NON_CRITICAL_CODES = /* @__PURE__ */ new Set([1357004, 1357001, 1357031]);
90769
+ function isNonCriticalError(value) {
90770
+ if (!value || typeof value !== "object") return false;
90771
+ const body = value.body && typeof value.body === "object" ? value.body : value;
90772
+ if (body.isNotCritical === 1 || body.is_not_critical === 1) return true;
90773
+ const num = Number(body.error ?? body.errorCode ?? body.code);
90774
+ if (NON_CRITICAL_CODES.has(num)) return true;
90775
+ const text3 = String(body.errorDescription || body.errorSummary || body.message || "");
90776
+ return /re-?open(ing)? your browser|try again(?: later)?|temporarily unavailable/i.test(text3);
90777
+ }
90778
+ async function retryOnNonCritical(operation, refresh) {
90779
+ try {
90780
+ const first2 = await operation();
90781
+ if (!isNonCriticalError(first2)) return first2;
90782
+ } catch (error2) {
90783
+ if (!isNonCriticalError(error2)) throw error2;
90784
+ }
90785
+ await refresh();
90786
+ return operation();
90787
+ }
90788
+
90789
+ // src/utils/attachments.ts
90756
90790
  function cleanJsonResponse(value) {
90757
90791
  if (typeof value !== "string") return value;
90758
90792
  const normalized = value.replace(/^for\s*\(;;\);\s*/i, "");
@@ -90845,18 +90879,76 @@ function buildUploadQuery(ctx) {
90845
90879
  return qs2;
90846
90880
  }
90847
90881
  var EXTENSION_BY_MIME = {
90882
+ // video
90848
90883
  "video/mp4": "mp4",
90884
+ "video/quicktime": "mov",
90885
+ "video/webm": "webm",
90886
+ "video/x-matroska": "mkv",
90887
+ "video/x-msvideo": "avi",
90888
+ "video/mpeg": "mpeg",
90889
+ "video/3gpp": "3gp",
90890
+ // audio
90849
90891
  "audio/mp4": "m4a",
90892
+ "audio/x-m4a": "m4a",
90850
90893
  "audio/mpeg": "mp3",
90851
90894
  "audio/mp3": "mp3",
90852
90895
  "audio/aac": "aac",
90853
90896
  "audio/ogg": "ogg",
90897
+ "audio/opus": "opus",
90854
90898
  "audio/wav": "wav",
90899
+ "audio/x-wav": "wav",
90900
+ "audio/webm": "weba",
90901
+ "audio/flac": "flac",
90902
+ "audio/x-flac": "flac",
90903
+ "audio/3gpp": "3gp",
90904
+ // image
90855
90905
  "image/jpeg": "jpg",
90906
+ "image/jpg": "jpg",
90856
90907
  "image/png": "png",
90857
90908
  "image/gif": "gif",
90858
- "image/webp": "webp"
90909
+ "image/webp": "webp",
90910
+ "image/bmp": "bmp",
90911
+ "image/tiff": "tiff",
90912
+ "image/svg+xml": "svg",
90913
+ "image/heic": "heic",
90914
+ "image/heif": "heif",
90915
+ "image/avif": "avif",
90916
+ // documents / archives
90917
+ "application/pdf": "pdf",
90918
+ "application/zip": "zip",
90919
+ "application/x-zip-compressed": "zip",
90920
+ "application/x-rar-compressed": "rar",
90921
+ "application/vnd.rar": "rar",
90922
+ "application/x-7z-compressed": "7z",
90923
+ "application/gzip": "gz",
90924
+ "application/x-tar": "tar",
90925
+ "text/plain": "txt",
90926
+ "text/csv": "csv",
90927
+ "text/html": "html",
90928
+ "text/calendar": "ics",
90929
+ "application/json": "json",
90930
+ "application/xml": "xml",
90931
+ "text/xml": "xml",
90932
+ "application/msword": "doc",
90933
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document": "docx",
90934
+ "application/vnd.ms-excel": "xls",
90935
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "xlsx",
90936
+ "application/vnd.ms-powerpoint": "ppt",
90937
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation": "pptx",
90938
+ "application/vnd.android.package-archive": "apk",
90939
+ "application/x-msdownload": "exe",
90940
+ "application/octet-stream": "bin"
90859
90941
  };
90942
+ function extensionForMime(mime) {
90943
+ if (!mime) return "";
90944
+ const normalized = String(mime).toLowerCase().split(";")[0].trim();
90945
+ if (EXTENSION_BY_MIME[normalized]) return EXTENSION_BY_MIME[normalized];
90946
+ const subtype = normalized.split("/")[1] || "";
90947
+ if (!subtype) return "";
90948
+ if (/\+([a-z0-9]+)$/.test(subtype)) return subtype.replace(/.*\+/, "");
90949
+ const cleaned = subtype.replace(/^vnd\./, "").replace(/^x-/, "").replace(/[-.]/g, "");
90950
+ return /^[a-z0-9]{1,10}$/.test(cleaned) ? cleaned : "";
90951
+ }
90860
90952
  function sniffMime(buffer) {
90861
90953
  if (buffer.length < 12) return "";
90862
90954
  const b = buffer;
@@ -90881,11 +90973,11 @@ function sniffMime(buffer) {
90881
90973
  }
90882
90974
  function normalizeFilename(filename, mime) {
90883
90975
  const base = filename && String(filename).trim() ? String(filename).trim() : `file-${Date.now()}`;
90884
- const ext = EXTENSION_BY_MIME[mime] || "";
90885
- const match = /\.[a-z0-9]{1,5}$/i.exec(base);
90976
+ const ext = extensionForMime(mime);
90977
+ const match = /\.[a-z0-9]{1,6}$/i.exec(base);
90886
90978
  const currentExt = match ? match[0].slice(1).toLowerCase() : "";
90887
90979
  if (ext && currentExt !== ext) {
90888
- const audioOverride = (ext === "m4a" || ext === "mp3" || ext === "aac" || ext === "ogg" || ext === "wav") && (currentExt === "mp4" || currentExt === "m4v" || currentExt === "");
90980
+ const audioOverride = (ext === "m4a" || ext === "mp3" || ext === "aac" || ext === "ogg" || ext === "wav" || ext === "opus" || ext === "flac") && (currentExt === "mp4" || currentExt === "m4v" || currentExt === "");
90889
90981
  if (audioOverride || !currentExt) {
90890
90982
  return currentExt ? `${base.slice(0, base.length - currentExt.length - 1)}.${ext}` : `${base}.${ext}`;
90891
90983
  }
@@ -90895,16 +90987,45 @@ function normalizeFilename(filename, mime) {
90895
90987
  }
90896
90988
  async function uploadMercuryAttachment(ctx, defaultFuncs, attachment) {
90897
90989
  const normalized = await normalizeAttachmentInput(attachment, ctx);
90990
+ const buffer = await readAll(normalized.stream);
90898
90991
  const url2 = "https://www.facebook.com/ajax/mercury/upload.php";
90992
+ let lastError;
90993
+ for (let attempt = 0; attempt < 2; attempt++) {
90994
+ if (attempt > 0) await refreshUploadTokens(ctx, defaultFuncs);
90995
+ await ensureUploadTokens(ctx, defaultFuncs);
90996
+ const formData = new import_form_data3.default();
90997
+ formData.append("farr", stream3.Readable.from(buffer), {
90998
+ filename: normalized.filename,
90999
+ contentType: normalized.contentType || void 0
91000
+ });
91001
+ let data2;
91002
+ try {
91003
+ const res = await defaultFuncs.postMultipart(url2, ctx.jar, formData, buildUploadQuery(ctx));
91004
+ data2 = cleanJsonResponse(res && res.body !== void 0 ? res.body : res);
91005
+ } catch (error2) {
91006
+ lastError = error2;
91007
+ if (attempt === 0 && isNonCriticalError(error2)) continue;
91008
+ throw error2;
91009
+ }
91010
+ if (extractAttachmentIds(data2).length) return finalizeUpload(data2);
91011
+ lastError = buildNoMetadataError(data2);
91012
+ if (attempt === 0 && isNonCriticalError(lastError)) continue;
91013
+ throw lastError;
91014
+ }
91015
+ throw lastError || buildNoMetadataError(null);
91016
+ }
91017
+ function buildNoMetadataError(data2) {
91018
+ const err = new Error("UploadFb returned no metadata/ids");
91019
+ err.code = "NO_METADATA";
91020
+ err.body = data2;
91021
+ return err;
91022
+ }
91023
+ async function refreshUploadTokens(ctx, defaultFuncs) {
91024
+ ctx.lsd = void 0;
91025
+ ctx.master = ctx.master || {};
91026
+ ctx.master.__rev = void 0;
91027
+ if (typeof ctx.__cometTokens === "object") ctx.__cometTokens = void 0;
90899
91028
  await ensureUploadTokens(ctx, defaultFuncs);
90900
- const formData = new import_form_data3.default();
90901
- formData.append("farr", normalized.stream, {
90902
- filename: normalized.filename,
90903
- contentType: normalized.contentType || void 0
90904
- });
90905
- const res = await defaultFuncs.postMultipart(url2, ctx.jar, formData, buildUploadQuery(ctx));
90906
- const data2 = cleanJsonResponse(res && res.body !== void 0 ? res.body : res);
90907
- return finalizeUpload(data2);
90908
91029
  }
90909
91030
  async function normalizeAttachmentInput(attachment, ctx) {
90910
91031
  if (Buffer.isBuffer(attachment)) {
@@ -91011,6 +91132,68 @@ async function uploadGroupImage(ctx, defaultFuncs, image) {
91011
91132
  return cleanJsonResponse(res && res.body !== void 0 ? res.body : res);
91012
91133
  }
91013
91134
 
91135
+ // src/utils/reactions.ts
91136
+ var CORE_REACTIONS = [
91137
+ "\u{1F60D}",
91138
+ "\u{1F606}",
91139
+ "\u{1F62E}",
91140
+ "\u{1F622}",
91141
+ "\u{1F620}",
91142
+ "\u{1F44D}",
91143
+ "\u{1F44E}",
91144
+ "\u2764",
91145
+ "\u{1F497}",
91146
+ ""
91147
+ ];
91148
+ var REACTION_ALIASES = {
91149
+ ":heart_eyes:": "\u{1F60D}",
91150
+ ":love:": "\u{1F60D}",
91151
+ ":laughing:": "\u{1F606}",
91152
+ ":haha:": "\u{1F606}",
91153
+ ":open_mouth:": "\u{1F62E}",
91154
+ ":wow:": "\u{1F62E}",
91155
+ ":cry:": "\u{1F622}",
91156
+ ":sad:": "\u{1F622}",
91157
+ ":angry:": "\u{1F620}",
91158
+ ":thumbsup:": "\u{1F44D}",
91159
+ ":like:": "\u{1F44D}",
91160
+ ":thumbsdown:": "\u{1F44E}",
91161
+ ":dislike:": "\u{1F44E}",
91162
+ ":heart:": "\u2764",
91163
+ ":glowingheart:": "\u{1F497}"
91164
+ };
91165
+ function isReactionEmoji(value) {
91166
+ if (value == null) return false;
91167
+ const text3 = String(value).trim();
91168
+ if (!text3) return false;
91169
+ if (CORE_REACTIONS.indexOf(text3) !== -1) return true;
91170
+ let clusters;
91171
+ try {
91172
+ const seg = new Intl.Segmenter(void 0, { granularity: "grapheme" });
91173
+ clusters = Array.from(seg.segment(text3), (s) => s.segment);
91174
+ } catch {
91175
+ clusters = Array.from(text3);
91176
+ }
91177
+ if (clusters.length !== 1) return false;
91178
+ const only = clusters[0];
91179
+ if (!new RegExp("\\p{Extended_Pictographic}", "u").test(only) && !/\uFE0F|\u20E3/u.test(only)) return false;
91180
+ if (!/[\p{Extended_Pictographic}\uFE0F\u20E3]/u.test(only)) return false;
91181
+ if (/[\u0000-\u001F\u007F]/.test(only)) return false;
91182
+ return true;
91183
+ }
91184
+ function normalizeReaction(value, options = {}) {
91185
+ const raw = value == null ? "" : String(value).trim();
91186
+ if (raw === "") {
91187
+ if (options.allowRemove) return "";
91188
+ throw new Error("Reaction is not a valid emoji.");
91189
+ }
91190
+ const resolved = REACTION_ALIASES[raw] !== void 0 ? REACTION_ALIASES[raw] : raw;
91191
+ if (!options.forceCustom && !isReactionEmoji(resolved)) {
91192
+ throw new Error(`Reaction is not a valid emoji: ${JSON.stringify(raw)}`);
91193
+ }
91194
+ return resolved;
91195
+ }
91196
+
91014
91197
  // src/utils/lsd.ts
91015
91198
  var TOKEN_TTL_MS = 10 * 60 * 1e3;
91016
91199
  var STALE_TOKEN_ERRORS = /* @__PURE__ */ new Set([
@@ -91805,10 +91988,10 @@ function startRecovery(ctx, runtime, options = {}) {
91805
91988
 
91806
91989
  // src/utils/realtime-health.ts
91807
91990
  init_pacing();
91808
- var SILENT_WINDOW_MS = 6 * 60 * 1e3;
91809
- var PING_GRACE_MS = 45 * 1e3;
91991
+ var SILENT_WINDOW_MS = 30 * 60 * 1e3;
91992
+ var PING_GRACE_MS = 60 * 1e3;
91810
91993
  var RealtimeHealth = class {
91811
- constructor(now = () => Date.now()) {
91994
+ constructor(now = () => Date.now(), options = {}) {
91812
91995
  this.now = now;
91813
91996
  this.frames = 0;
91814
91997
  this.lastFrameAt = 0;
@@ -91817,17 +92000,23 @@ var RealtimeHealth = class {
91817
92000
  this.forcedReconnects = 0;
91818
92001
  this.lastPingAt = 0;
91819
92002
  this.startedAt = Date.now();
92003
+ /** Consecutive pings sent without any resulting frame. */
92004
+ this.silentPings = 0;
91820
92005
  this.startedAt = this.now();
92006
+ this.silentWindowMs = options.silentWindowMs ?? SILENT_WINDOW_MS;
92007
+ this.pingGraceMs = options.pingGraceMs ?? PING_GRACE_MS;
91821
92008
  }
91822
92009
  /** Record any inbound frame (message, pong, anything from the broker). */
91823
92010
  noteFrame() {
91824
92011
  this.frames += 1;
91825
92012
  this.lastFrameAt = this.now();
92013
+ this.silentPings = 0;
91826
92014
  }
91827
92015
  /** Record a successful connect. */
91828
92016
  noteConnected() {
91829
92017
  this.connectedAt = this.now();
91830
92018
  this.lastFrameAt = this.now();
92019
+ this.silentPings = 0;
91831
92020
  }
91832
92021
  /** Record that the transport reports itself disconnected. */
91833
92022
  noteDisconnected() {
@@ -91836,6 +92025,7 @@ var RealtimeHealth = class {
91836
92025
  /** Record an active ping being sent. */
91837
92026
  notePing() {
91838
92027
  this.lastPingAt = this.now();
92028
+ this.silentPings += 1;
91839
92029
  }
91840
92030
  sinceLastFrame() {
91841
92031
  const base = this.lastFrameAt || this.connectedAt || this.startedAt;
@@ -91845,8 +92035,8 @@ var RealtimeHealth = class {
91845
92035
  * Decide what to do about the current socket.
91846
92036
  *
91847
92037
  * - `disconnected` — the transport already knows it is down; reconnect.
91848
- * - `dead` — connected on paper, silent past the window, and a ping raised no
91849
- * traffic within the grace period; reconnect.
92038
+ * - `dead` — connected on paper, silent well past the window, and repeated
92039
+ * pings raised no traffic at all; reconnect.
91850
92040
  * - `suspect` — silent past the window but a ping was only just sent; wait.
91851
92041
  * - `quiet` — silent but inside the window; normal.
91852
92042
  * - `healthy` — recent traffic.
@@ -91854,9 +92044,10 @@ var RealtimeHealth = class {
91854
92044
  verdict(connected) {
91855
92045
  if (!connected) return "disconnected";
91856
92046
  const silent = this.sinceLastFrame();
91857
- if (silent < SILENT_WINDOW_MS / 2) return "healthy";
91858
- if (silent < SILENT_WINDOW_MS) return "quiet";
91859
- if (this.lastPingAt && this.now() - this.lastPingAt < PING_GRACE_MS) return "suspect";
92047
+ if (silent < this.silentWindowMs / 2) return "healthy";
92048
+ if (silent < this.silentWindowMs) return "quiet";
92049
+ if (this.lastPingAt && this.now() - this.lastPingAt < this.pingGraceMs) return "suspect";
92050
+ if (this.silentPings < 3) return "suspect";
91860
92051
  return "dead";
91861
92052
  }
91862
92053
  /** A snapshot for diagnostics. */
@@ -91878,8 +92069,11 @@ var RealtimeHealth = class {
91878
92069
  }
91879
92070
  };
91880
92071
  function startRealtimeSupervisor(options) {
91881
- const health = new RealtimeHealth();
91882
- const interval = options.intervalMs ?? 3 * 60 * 1e3;
92072
+ const health = options.health || new RealtimeHealth(void 0, {
92073
+ silentWindowMs: options.silentWindowMs,
92074
+ pingGraceMs: options.pingGraceMs
92075
+ });
92076
+ const interval = options.intervalMs ?? 10 * 60 * 1e3;
91883
92077
  let stopped = false;
91884
92078
  let timer = null;
91885
92079
  const evaluate = () => {
@@ -93596,7 +93790,8 @@ var allowedProperties = {
93596
93790
  emojiSize: true,
93597
93791
  body: true,
93598
93792
  mentions: true,
93599
- location: true
93793
+ location: true,
93794
+ edit: true
93600
93795
  };
93601
93796
  var sendMessage_default = (defaultFuncs, api, ctx) => {
93602
93797
  async function uploadAttachment(attachments) {
@@ -93718,6 +93913,18 @@ var sendMessage_default = (defaultFuncs, api, ctx) => {
93718
93913
  if (msgType === "String") {
93719
93914
  msg = { body: msg };
93720
93915
  }
93916
+ if (Array.isArray(msg.edit) && msg.edit.length > 0) {
93917
+ let body = String(msg.body || "");
93918
+ const edits = [...msg.edit].sort((a, b) => Number(b?.[1] ?? 0) - Number(a?.[1] ?? 0));
93919
+ for (const entry of edits) {
93920
+ const text3 = Array.isArray(entry) ? String(entry[0] ?? "") : String(entry ?? "");
93921
+ const rawOffset = Array.isArray(entry) ? Number(entry[1]) : NaN;
93922
+ const offset = Number.isFinite(rawOffset) ? Math.max(0, Math.min(body.length, rawOffset)) : body.length;
93923
+ body = body.slice(0, offset) + text3 + body.slice(offset);
93924
+ }
93925
+ msg.body = body;
93926
+ delete msg.edit;
93927
+ }
93721
93928
  const finish = (err, data2) => {
93722
93929
  settle2(err, data2);
93723
93930
  return data2;
@@ -93991,45 +94198,6 @@ function sendTypingIndicator_default(defaultFuncs, api, ctx) {
93991
94198
  }
93992
94199
 
93993
94200
  // src/deltas/apis/messaging/setMessageReaction.ts
93994
- var VALID_REACTIONS = [
93995
- "\u{1F60D}",
93996
- // :heart_eyes:
93997
- "\u{1F606}",
93998
- // :laughing:
93999
- "\u{1F62E}",
94000
- // :open_mouth:
94001
- "\u{1F622}",
94002
- // :cry:
94003
- "\u{1F620}",
94004
- // :angry:
94005
- "\u{1F44D}",
94006
- // :thumbsup:
94007
- "\u{1F44E}",
94008
- // :thumbsdown:
94009
- "\u2764",
94010
- // :heart:
94011
- "\u{1F497}",
94012
- // :glowingheart:
94013
- ""
94014
- // remove
94015
- ];
94016
- var ALIASES = {
94017
- ":heart_eyes:": "\u{1F60D}",
94018
- ":love:": "\u{1F60D}",
94019
- ":laughing:": "\u{1F606}",
94020
- ":haha:": "\u{1F606}",
94021
- ":open_mouth:": "\u{1F62E}",
94022
- ":wow:": "\u{1F62E}",
94023
- ":cry:": "\u{1F622}",
94024
- ":sad:": "\u{1F622}",
94025
- ":angry:": "\u{1F620}",
94026
- ":thumbsup:": "\u{1F44D}",
94027
- ":like:": "\u{1F44D}",
94028
- ":thumbsdown:": "\u{1F44E}",
94029
- ":dislike:": "\u{1F44E}",
94030
- ":heart:": "\u2764",
94031
- ":glowingheart:": "\u{1F497}"
94032
- };
94033
94201
  function setMessageReaction_default(defaultFuncs, _api, ctx) {
94034
94202
  return async function setMessageReaction(reaction, messageID, callback, forceCustomReaction) {
94035
94203
  const cb = typeof callback === "function" ? callback : void 0;
@@ -94046,11 +94214,14 @@ function setMessageReaction_default(defaultFuncs, _api, ctx) {
94046
94214
  if (!messageID) {
94047
94215
  throw { error: "setMessageReaction: messageID is required." };
94048
94216
  }
94049
- let value = reaction == null ? "" : String(reaction);
94050
- if (ALIASES[value] !== void 0) {
94051
- value = ALIASES[value];
94052
- } else if (value !== "" && VALID_REACTIONS.indexOf(value) === -1 && !forceCustomReaction) {
94053
- throw { error: "Reaction is not a valid emoji." };
94217
+ let value;
94218
+ try {
94219
+ value = normalizeReaction(reaction, {
94220
+ allowRemove: true,
94221
+ forceCustom: !!forceCustomReaction
94222
+ });
94223
+ } catch (validationError) {
94224
+ throw { error: validationError?.message || "Reaction is not a valid emoji." };
94054
94225
  }
94055
94226
  const variables = {
94056
94227
  data: {
@@ -94842,33 +95013,35 @@ function searchMusic_default(defaultFuncs, _api, ctx) {
94842
95013
  }
94843
95014
  const page = collectTracks(parsed);
94844
95015
  const tracks = page.tracks.slice(0, count);
94845
- if (tracks.length === 0) {
95016
+ if (tracks.length) {
95017
+ const result = tracks.slice();
95018
+ result.tracks = tracks;
95019
+ result.endCursor = page.endCursor;
95020
+ result.hasNextPage = page.hasNextPage;
95021
+ result.query = text3;
95022
+ return result;
95023
+ }
95024
+ for (let retry = 0; retry < 3; retry++) {
94846
95025
  invalidateCometTokens(ctx);
95026
+ await sleep(600 + retry * 900);
94847
95027
  const retried = await attempt(true);
94848
- if (!isStaleTokenError(retried) && !retried.error && !(retried.errors || []).length) {
94849
- const retryPage = collectTracks(retried);
94850
- const retryTracks = retryPage.tracks.slice(0, count);
94851
- if (retryTracks.length) {
94852
- const result2 = retryTracks.slice();
94853
- result2.tracks = retryTracks;
94854
- result2.endCursor = retryPage.endCursor;
94855
- result2.hasNextPage = retryPage.hasNextPage;
94856
- result2.query = text3;
94857
- return result2;
94858
- }
95028
+ if (isStaleTokenError(retried) || retried?.error || (retried?.errors || []).length) continue;
95029
+ const retryPage = collectTracks(retried);
95030
+ const retryTracks = retryPage.tracks.slice(0, count);
95031
+ if (retryTracks.length) {
95032
+ const result = retryTracks.slice();
95033
+ result.tracks = retryTracks;
95034
+ result.endCursor = retryPage.endCursor;
95035
+ result.hasNextPage = retryPage.hasNextPage;
95036
+ result.query = text3;
95037
+ return result;
94859
95038
  }
94860
- throw {
94861
- error: `No music results found for "${text3}". The Facebook music catalog may be unavailable for this account or region.`,
94862
- code: "NO_RESULTS",
94863
- query: text3
94864
- };
94865
95039
  }
94866
- const result = tracks.slice();
94867
- result.tracks = tracks;
94868
- result.endCursor = page.endCursor;
94869
- result.hasNextPage = page.hasNextPage;
94870
- result.query = text3;
94871
- return result;
95040
+ throw {
95041
+ error: `No music results found for "${text3}". The Facebook music catalog may be unavailable for this account or region.`,
95042
+ code: "NO_RESULTS",
95043
+ query: text3
95044
+ };
94872
95045
  }
94873
95046
  return function searchMusic(query = "", options = {}, callback) {
94874
95047
  const selectedOptions = typeof options === "function" ? {} : options || {};
@@ -99025,15 +99198,22 @@ function story_default(defaultFuncs, api, ctx) {
99025
99198
  }
99026
99199
  async function sendStoryReply(storyIdOrUrl, message, isReaction) {
99027
99200
  try {
99028
- const allowedReactions = ["\u2764\uFE0F", "\u{1F44D}", "\u{1F917}", "\u{1F606}", "\u{1F621}", "\u{1F622}", "\u{1F62E}"];
99029
99201
  if (!storyIdOrUrl) throw new Error("Story ID or URL is required.");
99030
99202
  if (!message) throw new Error("A message or reaction is required.");
99203
+ let reactionValue = message;
99204
+ if (isReaction) {
99205
+ try {
99206
+ reactionValue = normalizeReaction(message);
99207
+ } catch (validationError) {
99208
+ throw new Error(validationError?.message || "Reaction is not a valid emoji.");
99209
+ }
99210
+ }
99031
99211
  let storyID = getStoryIDFromURL(storyIdOrUrl);
99032
99212
  if (!storyID) storyID = storyIdOrUrl;
99033
99213
  const variables = {
99034
99214
  input: {
99035
99215
  attribution_id_v2: "StoriesCometSuspenseRoot.react,comet.stories.viewer,via_cold_start",
99036
- message,
99216
+ message: reactionValue,
99037
99217
  story_id: storyID,
99038
99218
  story_reply_type: isReaction ? "LIGHT_WEIGHT" : "TEXT",
99039
99219
  actor_id: ctx.userID,
@@ -99041,12 +99221,9 @@ function story_default(defaultFuncs, api, ctx) {
99041
99221
  }
99042
99222
  };
99043
99223
  if (isReaction) {
99044
- if (!allowedReactions.includes(message)) {
99045
- throw new Error(`Invalid reaction. Please use one of: ${allowedReactions.join(" ")}`);
99046
- }
99047
99224
  variables.input.lightweight_reaction_actions = {
99048
99225
  offsets: [0],
99049
- reaction: message
99226
+ reaction: reactionValue
99050
99227
  };
99051
99228
  }
99052
99229
  const form = {
@@ -99455,14 +99632,32 @@ function getThreadInfo_default(defaultFuncs, api, ctx) {
99455
99632
  throw new Error("getThreadInfo: malformed response from GraphQL.");
99456
99633
  }
99457
99634
  const threadInfos = {};
99458
- for (let i2 = Math.min(resData.length - 2, threadIDs.length - 1); i2 >= 0; i2--) {
99635
+ let matched = 0;
99636
+ for (let i2 = 0; i2 < resData.length; i2++) {
99459
99637
  const res = resData[i2];
99460
- if (!res || res.error_results || !res[Object.keys(res)[0]]?.data) continue;
99461
- const threadInfo = formatThreadGraphQLResponse(res[Object.keys(res)[0]].data);
99638
+ if (!res || typeof res !== "object" || res.error_results) continue;
99639
+ const keys = Object.keys(res);
99640
+ const queryKey = keys.find((k) => /^o\d+$/.test(k));
99641
+ let index3;
99642
+ if (queryKey) {
99643
+ index3 = Number(queryKey.slice(1));
99644
+ } else if (typeof res.__ar === "number" || res.error !== void 0) {
99645
+ continue;
99646
+ } else {
99647
+ index3 = i2;
99648
+ }
99649
+ if (index3 < 0 || index3 >= threadIDs.length) continue;
99650
+ const data2 = res[queryKey || keys[0]];
99651
+ if (!data2 || !data2.data) continue;
99652
+ const threadInfo = formatThreadGraphQLResponse(data2.data);
99462
99653
  if (threadInfo) {
99463
- threadInfos[threadInfo.threadID || threadIDs[threadIDs.length - 1 - i2]] = threadInfo;
99654
+ threadInfos[threadInfo.threadID || threadIDs[index3]] = threadInfo;
99655
+ matched++;
99464
99656
  }
99465
99657
  }
99658
+ if (matched === 0) {
99659
+ throw new Error("getThreadInfo: malformed response from GraphQL.");
99660
+ }
99466
99661
  return Array.isArray(threadID) ? threadInfos : Object.values(threadInfos)[0] || null;
99467
99662
  } catch (err) {
99468
99663
  error("getThreadInfo", err);
@@ -100001,7 +100196,10 @@ var getUserInfo_default = (defaultFuncs, api, ctx) => {
100001
100196
  form[`ids[${i2}]`] = v;
100002
100197
  });
100003
100198
  const getGenderString = (code) => code === 1 ? "male" : code === 2 ? "female" : "no specific gender";
100004
- defaultFuncs.post("https://www.facebook.com/chat/user_info/", ctx.jar, form).then((resData) => parseAndCheckLogin(ctx, defaultFuncs)(resData)).then((resData) => {
100199
+ const performFetch = () => defaultFuncs.post("https://www.facebook.com/chat/user_info/", ctx.jar, form).then((resData) => parseAndCheckLogin(ctx, defaultFuncs)(resData));
100200
+ retryOnNonCritical(performFetch, () => {
100201
+ invalidateCometTokens?.(ctx);
100202
+ }).then((resData) => {
100005
100203
  if (resData?.error && resData?.error !== 3252001) throw resData;
100006
100204
  const retObj = {};
100007
100205
  const profiles = resData?.payload?.profiles;
@@ -101127,6 +101325,9 @@ var listenMqtt_default = (defaultFuncs, api, ctx) => {
101127
101325
  }
101128
101326
  lifecycle.healthSupervisor = startRealtimeSupervisor({
101129
101327
  isConnected: () => generation === lifecycle.generation && client2.connected,
101328
+ // Share the per-connection tracker so this supervisor sees
101329
+ // every inbound frame the listener records.
101330
+ health,
101130
101331
  reconnect: (reason) => {
101131
101332
  if (generation !== lifecycle.generation) return;
101132
101333
  if (lifecycle.stopped || ctx.loggedIn === false) return;
@@ -101509,6 +101710,7 @@ async function loginHelper(credentials, globalOptions, callback, setOptionsFunc,
101509
101710
  }
101510
101711
  if (listenMqtt_default) {
101511
101712
  api["listenMqtt"] = listenMqtt_default(defaultFuncs, api, ctx);
101713
+ api["listen"] = api["listenMqtt"];
101512
101714
  }
101513
101715
  };
101514
101716
  api.getCurrentUserID = () => ctx.userID;
@@ -101833,7 +102035,9 @@ lodash/lodash.js:
101833
102035
  *)
101834
102036
  */
101835
102037
 
102038
+ exports.CORE_REACTIONS = CORE_REACTIONS;
101836
102039
  exports.PING_GRACE_MS = PING_GRACE_MS;
102040
+ exports.REACTION_ALIASES = REACTION_ALIASES;
101837
102041
  exports.RealtimeHealth = RealtimeHealth;
101838
102042
  exports.RecoverySupervisor = RecoverySupervisor;
101839
102043
  exports.SILENT_WINDOW_MS = SILENT_WINDOW_MS;
@@ -101841,6 +102045,7 @@ exports.acquireCookies = acquireCookies;
101841
102045
  exports.applySetCookie = applySetCookie;
101842
102046
  exports.awaitProactiveSlot = awaitProactiveSlot;
101843
102047
  exports.canActProactively = canActProactively;
102048
+ exports.cleanJsonResponse = cleanJsonResponse;
101844
102049
  exports.collectSessionCookies = collectSessionCookies;
101845
102050
  exports.default = index_default;
101846
102051
  exports.ensureCometTokens = ensureCometTokens;
@@ -101858,10 +102063,13 @@ exports.invalidateCometTokens = invalidateCometTokens;
101858
102063
  exports.isAuthFailure = isAuthFailure;
101859
102064
  exports.isBreakerOpen = isBreakerOpen;
101860
102065
  exports.isLoggedOutDocument = isLoggedOutDocument;
102066
+ exports.isNonCriticalError = isNonCriticalError;
102067
+ exports.isReactionEmoji = isReactionEmoji;
101861
102068
  exports.isStaleTokenError = isStaleTokenError;
101862
102069
  exports.login = login;
101863
102070
  exports.missingSessionCookies = missingSessionCookies;
101864
102071
  exports.normalizeBrowserProfile = normalizeBrowserProfile;
102072
+ exports.normalizeReaction = normalizeReaction;
101865
102073
  exports.noteAction = noteAction;
101866
102074
  exports.noteAuthFailure = noteAuthFailure;
101867
102075
  exports.noteCheckpoint = noteCheckpoint;
@@ -101875,12 +102083,15 @@ exports.reserveRequest = reserveRequest;
101875
102083
  exports.resetBreaker = resetBreaker;
101876
102084
  exports.resolveProfile = resolveProfile;
101877
102085
  exports.retryDelay = retryDelay;
102086
+ exports.retryOnNonCritical = retryOnNonCritical;
101878
102087
  exports.safetySnapshot = safetySnapshot;
101879
102088
  exports.sleep = sleep;
101880
102089
  exports.startLiveness = startLiveness;
101881
102090
  exports.startRealtimeSupervisor = startRealtimeSupervisor;
101882
102091
  exports.startRecovery = startRecovery;
101883
102092
  exports.tripBreaker = tripBreaker;
102093
+ exports.uploadGroupImage = uploadGroupImage;
102094
+ exports.uploadMercuryAttachment = uploadMercuryAttachment;
101884
102095
  exports.warmUpSession = warmUpSession;
101885
102096
  exports.withCometTokens = withCometTokens;
101886
102097
  exports.withSessionRetry = withSessionRetry;