@lazyneoaz/metachat 6.0.2 → 6.0.4

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 (3) hide show
  1. package/dist/index.cjs +136 -58
  2. package/dist/index.mjs +136 -58
  3. package/package.json +2 -2
package/dist/index.cjs CHANGED
@@ -70169,6 +70169,7 @@ __export(utils_exports2, {
70169
70169
  getType: () => getType,
70170
70170
  getWebSocketHeaders: () => getWebSocketHeaders,
70171
70171
  hasAuthenticatedSession: () => hasAuthenticatedSession,
70172
+ hasCometTokens: () => hasCometTokens,
70172
70173
  humanDelay: () => humanDelay,
70173
70174
  info: () => info,
70174
70175
  invalidateCometTokens: () => invalidateCometTokens,
@@ -85863,6 +85864,12 @@ function tokenPairFresh(state) {
85863
85864
  state.lsd && state.fb_dtsg && state.fetchedAt && Date.now() - state.fetchedAt < TOKEN_TTL_MS
85864
85865
  );
85865
85866
  }
85867
+ function applyCached(ctx, state) {
85868
+ if (!state.lsd || !state.fb_dtsg) return;
85869
+ ctx.lsd = state.lsd;
85870
+ ctx.fb_dtsg = state.fb_dtsg;
85871
+ ctx.jazoest = recomputeJazoest(state.fb_dtsg);
85872
+ }
85866
85873
  function htmlOf(res) {
85867
85874
  if (typeof res === "string") return res;
85868
85875
  if (res && typeof res.body === "string") return res.body;
@@ -85872,20 +85879,28 @@ function htmlOf(res) {
85872
85879
  async function ensureCometTokens(ctx, defaultFuncs, force = false) {
85873
85880
  const state = tokenState(ctx);
85874
85881
  if (!force && tokenPairFresh(state)) {
85875
- ctx.lsd = state.lsd;
85876
- ctx.fb_dtsg = state.fb_dtsg;
85882
+ applyCached(ctx, state);
85877
85883
  return;
85878
85884
  }
85879
85885
  if (!force && state.failures > 0) {
85880
85886
  const backoff = Math.min(1e3 * Math.pow(2, state.failures), MAX_BACKOFF_MS);
85881
85887
  if (state.attemptedAt && Date.now() - state.attemptedAt < backoff) {
85882
- if (state.lsd && state.fb_dtsg) {
85883
- ctx.lsd = state.lsd;
85884
- ctx.fb_dtsg = state.fb_dtsg;
85885
- }
85888
+ applyCached(ctx, state);
85886
85889
  return;
85887
85890
  }
85888
85891
  }
85892
+ if (!state.inflight) {
85893
+ state.inflight = scrapeTokens(ctx, defaultFuncs, state).finally(() => {
85894
+ state.inflight = null;
85895
+ });
85896
+ }
85897
+ try {
85898
+ await state.inflight;
85899
+ } catch {
85900
+ }
85901
+ applyCached(ctx, state);
85902
+ }
85903
+ async function scrapeTokens(ctx, defaultFuncs, state) {
85889
85904
  state.attemptedAt = Date.now();
85890
85905
  try {
85891
85906
  const res = await defaultFuncs.get("https://www.facebook.com/", ctx.jar);
@@ -85909,13 +85924,14 @@ async function ensureCometTokens(ctx, defaultFuncs, force = false) {
85909
85924
  if (spin.__rev) ctx.revision = spin.__rev;
85910
85925
  const asbd = extractAsbdId(html3);
85911
85926
  if (asbd) ctx.asbdId = asbd;
85912
- ctx.lsd = lsd;
85913
- ctx.fb_dtsg = dtsg;
85914
- ctx.jazoest = recomputeJazoest(dtsg);
85915
85927
  } catch {
85916
85928
  state.failures++;
85917
85929
  }
85918
85930
  }
85931
+ function hasCometTokens(ctx) {
85932
+ const state = ctx.__cometTokens || {};
85933
+ return Boolean(state.lsd && state.fb_dtsg);
85934
+ }
85919
85935
  function invalidateCometTokens(ctx) {
85920
85936
  const state = tokenState(ctx);
85921
85937
  state.lsd = void 0;
@@ -90173,10 +90189,11 @@ function collectTracks(payload) {
90173
90189
  if (Array.isArray(node.sub_items)) candidates.push(...node.sub_items);
90174
90190
  for (const candidate of candidates) {
90175
90191
  const track = unwrap2(candidate);
90176
- if (track && track.id && !seen.has(track.id)) {
90177
- seen.add(track.id);
90178
- tracks.push(track);
90179
- }
90192
+ if (!track) continue;
90193
+ const key = track.id || track.audioUrl;
90194
+ if (!key || seen.has(key)) continue;
90195
+ seen.add(key);
90196
+ tracks.push(track);
90180
90197
  }
90181
90198
  }
90182
90199
  const pageInfo = container?.items?.page_info;
@@ -90188,10 +90205,24 @@ function collectTracks(payload) {
90188
90205
  }
90189
90206
 
90190
90207
  // src/deltas/apis/music/searchMusic.ts
90208
+ var MUSIC_CACHE_TTL_MS = 5 * 60 * 1e3;
90191
90209
  function nextRequestId(ctx) {
90192
90210
  ctx.__reqCounter = (typeof ctx.__reqCounter === "number" ? ctx.__reqCounter : 0) + 1;
90193
90211
  return ctx.__reqCounter.toString(36);
90194
90212
  }
90213
+ var MUSIC_MIN_INTERVAL_MS = 1100;
90214
+ async function paceMusic(ctx) {
90215
+ const configured = Number(ctx?.globalOptions?.musicMinIntervalMs);
90216
+ const min = Number.isFinite(configured) && configured >= 0 ? configured : MUSIC_MIN_INTERVAL_MS;
90217
+ const last2 = typeof ctx.__musicLastCallAt === "number" ? ctx.__musicLastCallAt : 0;
90218
+ const wait = last2 + min - Date.now();
90219
+ if (wait > 0) await sleep(wait);
90220
+ ctx.__musicLastCallAt = Date.now();
90221
+ }
90222
+ function cacheResult(ctx, key, result) {
90223
+ const cache = ctx.__musicCache || (ctx.__musicCache = {});
90224
+ cache[key] = { at: Date.now(), result };
90225
+ }
90195
90226
  function normalizeError(error2) {
90196
90227
  if (!error2 || typeof error2 !== "object") {
90197
90228
  const message2 = String(error2);
@@ -90213,6 +90244,13 @@ function normalizeError(error2) {
90213
90244
  }
90214
90245
  function searchMusic_default(defaultFuncs, _api, ctx) {
90215
90246
  async function runSearch(text3, count, selectedOptions, callback) {
90247
+ const cacheKey = `${selectedOptions.product || PRODUCT}|${count}|${text3}|${selectedOptions.cursor || ""}`;
90248
+ if (!selectedOptions.noCache) {
90249
+ const cache = ctx.__musicCache || (ctx.__musicCache = {});
90250
+ const hit = cache[cacheKey];
90251
+ if (hit && Date.now() - hit.at < MUSIC_CACHE_TTL_MS) return hit.result;
90252
+ }
90253
+ await paceMusic(ctx);
90216
90254
  const params = { first: count };
90217
90255
  if (text3) params.search_text = text3;
90218
90256
  if (selectedOptions.cursor) params.after = String(selectedOptions.cursor);
@@ -90248,7 +90286,6 @@ function searchMusic_default(defaultFuncs, _api, ctx) {
90248
90286
  };
90249
90287
  let parsed = await attempt(false);
90250
90288
  if (isStaleTokenError(parsed)) {
90251
- invalidateCometTokens(ctx);
90252
90289
  parsed = await attempt(true);
90253
90290
  }
90254
90291
  if (!parsed) throw { error: "searchMusic returned empty object.", code: "BAD_RESPONSE" };
@@ -90286,12 +90323,12 @@ function searchMusic_default(defaultFuncs, _api, ctx) {
90286
90323
  result.endCursor = page.endCursor;
90287
90324
  result.hasNextPage = page.hasNextPage;
90288
90325
  result.query = text3;
90326
+ cacheResult(ctx, cacheKey, result);
90289
90327
  return result;
90290
90328
  }
90291
90329
  for (let retry = 0; retry < 3; retry++) {
90292
- invalidateCometTokens(ctx);
90293
- await sleep(600 + retry * 900);
90294
- const retried = await attempt(true);
90330
+ await sleep(1200 + retry * 1300 + Math.floor(Math.random() * 400));
90331
+ const retried = await attempt(retry === 2 || !hasCometTokens(ctx));
90295
90332
  if (isStaleTokenError(retried) || retried?.error || (retried?.errors || []).length) continue;
90296
90333
  const retryPage = collectTracks(retried);
90297
90334
  const retryTracks = retryPage.tracks.slice(0, count);
@@ -90301,9 +90338,17 @@ function searchMusic_default(defaultFuncs, _api, ctx) {
90301
90338
  result.endCursor = retryPage.endCursor;
90302
90339
  result.hasNextPage = retryPage.hasNextPage;
90303
90340
  result.query = text3;
90341
+ cacheResult(ctx, cacheKey, result);
90304
90342
  return result;
90305
90343
  }
90306
90344
  }
90345
+ if (!hasCometTokens(ctx)) {
90346
+ throw {
90347
+ error: "Facebook did not return a music search document token. The session or network is unhealthy; retry shortly.",
90348
+ code: "TOKEN_UNAVAILABLE",
90349
+ query: text3
90350
+ };
90351
+ }
90307
90352
  throw {
90308
90353
  error: `No music results found for "${text3}". The Facebook music catalog may be unavailable for this account or region.`,
90309
90354
  code: "NO_RESULTS",
@@ -92047,7 +92092,7 @@ function getThreadHistory_default(defaultFuncs, api, ctx) {
92047
92092
  function formatEventReminders(reminder) {
92048
92093
  return {
92049
92094
  reminderID: reminder.id,
92050
- eventCreatorID: reminder.lightweight_event_creator.id,
92095
+ eventCreatorID: reminder.lightweight_event_creator?.id,
92051
92096
  time: reminder.time,
92052
92097
  eventType: String(reminder.lightweight_event_type || "").toLowerCase(),
92053
92098
  locationName: reminder.location_name,
@@ -92083,21 +92128,21 @@ function formatThreadGraphQLResponse(data2) {
92083
92128
  return {
92084
92129
  threadID,
92085
92130
  threadName: messageThread.name,
92086
- participantIDs: messageThread.all_participants.edges.map(
92087
- (d) => d.node.messaging_actor.id
92131
+ participantIDs: (messageThread.all_participants?.edges || []).map(
92132
+ (d) => d?.node?.messaging_actor?.id
92088
92133
  ),
92089
- userInfo: messageThread.all_participants.edges.map((d) => ({
92090
- id: d.node.messaging_actor.id,
92091
- name: d.node.messaging_actor.name,
92092
- firstName: d.node.messaging_actor.short_name,
92093
- vanity: d.node.messaging_actor.username,
92094
- url: d.node.messaging_actor.url,
92095
- thumbSrc: d.node.messaging_actor.big_image_src.uri,
92096
- profileUrl: d.node.messaging_actor.big_image_src.uri,
92097
- gender: d.node.messaging_actor.gender,
92098
- type: d.node.messaging_actor.__typename,
92099
- isFriend: d.node.messaging_actor.is_viewer_friend,
92100
- isBirthday: !!d.node.messaging_actor.is_birthday
92134
+ userInfo: (messageThread.all_participants?.edges || []).map((d) => ({
92135
+ id: d?.node?.messaging_actor?.id,
92136
+ name: d?.node?.messaging_actor?.name,
92137
+ firstName: d?.node?.messaging_actor?.short_name,
92138
+ vanity: d?.node?.messaging_actor?.username,
92139
+ url: d?.node?.messaging_actor?.url,
92140
+ thumbSrc: d?.node?.messaging_actor?.big_image_src?.uri,
92141
+ profileUrl: d?.node?.messaging_actor?.big_image_src?.uri,
92142
+ gender: d?.node?.messaging_actor?.gender,
92143
+ type: d?.node?.messaging_actor?.__typename,
92144
+ isFriend: d?.node?.messaging_actor?.is_viewer_friend,
92145
+ isBirthday: !!d?.node?.messaging_actor?.is_birthday
92101
92146
  })),
92102
92147
  unreadCount: messageThread.unread_count,
92103
92148
  messageCount: messageThread.messages_count,
@@ -92108,7 +92153,7 @@ function formatThreadGraphQLResponse(data2) {
92108
92153
  isArchived: messageThread.has_viewer_archived,
92109
92154
  folder: messageThread.folder,
92110
92155
  cannotReplyReason: messageThread.cannot_reply_reason,
92111
- eventReminders: messageThread.event_reminders ? messageThread.event_reminders.nodes.map(formatEventReminders) : null,
92156
+ eventReminders: messageThread.event_reminders ? (messageThread.event_reminders.nodes || []).map(formatEventReminders) : null,
92112
92157
  emoji: messageThread.customization_info ? messageThread.customization_info.emoji : null,
92113
92158
  color: messageThread.customization_info && messageThread.customization_info.outgoing_bubble_color ? messageThread.customization_info.outgoing_bubble_color.slice(2) : null,
92114
92159
  threadTheme: messageThread.thread_theme,
@@ -92154,7 +92199,8 @@ function formatThreadGraphQLResponse(data2) {
92154
92199
  };
92155
92200
  }
92156
92201
  function getThreadInfo_default(defaultFuncs, api, ctx) {
92157
- return async function getThreadInfo(threadID) {
92202
+ return async function getThreadInfo(threadID, callback = void 0) {
92203
+ const cb = typeof callback === "function" ? callback : void 0;
92158
92204
  const threadIDs = Array.isArray(threadID) ? threadID : [threadID];
92159
92205
  let form = {};
92160
92206
  threadIDs.forEach((t, i2) => {
@@ -92205,9 +92251,23 @@ function getThreadInfo_default(defaultFuncs, api, ctx) {
92205
92251
  if (matched === 0) {
92206
92252
  throw new Error("getThreadInfo: malformed response from GraphQL.");
92207
92253
  }
92208
- return Array.isArray(threadID) ? threadInfos : Object.values(threadInfos)[0] || null;
92254
+ const out = Array.isArray(threadID) ? threadInfos : Object.values(threadInfos)[0] || null;
92255
+ if (cb) {
92256
+ try {
92257
+ cb(null, out);
92258
+ } catch (_) {
92259
+ }
92260
+ }
92261
+ return out;
92209
92262
  } catch (err) {
92210
92263
  error("getThreadInfo", err);
92264
+ if (cb) {
92265
+ try {
92266
+ cb(err);
92267
+ } catch (_) {
92268
+ }
92269
+ return void 0;
92270
+ }
92211
92271
  throw err;
92212
92272
  }
92213
92273
  };
@@ -92217,7 +92277,7 @@ function getThreadInfo_default(defaultFuncs, api, ctx) {
92217
92277
  function formatEventReminders2(reminder) {
92218
92278
  return {
92219
92279
  reminderID: reminder.id,
92220
- eventCreatorID: reminder.lightweight_event_creator.id,
92280
+ eventCreatorID: reminder.lightweight_event_creator?.id,
92221
92281
  time: reminder.time,
92222
92282
  eventType: String(reminder.lightweight_event_type || "").toLowerCase(),
92223
92283
  locationName: reminder.location_name,
@@ -92231,7 +92291,7 @@ function formatEventReminders2(reminder) {
92231
92291
  secondsToNotifyBefore: reminder.seconds_to_notify_before,
92232
92292
  allowsRsvp: reminder.allows_rsvp,
92233
92293
  relatedEvent: reminder.related_event,
92234
- members: reminder.event_reminder_members.edges.map(function(member) {
92294
+ members: (reminder.event_reminder_members?.edges || []).map(function(member) {
92235
92295
  return {
92236
92296
  memberID: member.node.id,
92237
92297
  state: String(member.guest_list_state || "").toLowerCase()
@@ -92250,21 +92310,21 @@ function formatThreadGraphQLResponse2(messageThread) {
92250
92310
  return {
92251
92311
  threadID,
92252
92312
  threadName: messageThread.name,
92253
- participantIDs: messageThread.all_participants.edges.map(
92254
- (d) => d.node.messaging_actor.id
92313
+ participantIDs: (messageThread.all_participants?.edges || []).map(
92314
+ (d) => d?.node?.messaging_actor?.id
92255
92315
  ),
92256
- userInfo: messageThread.all_participants.edges.map((d) => ({
92257
- id: d.node.messaging_actor.id,
92258
- name: d.node.messaging_actor.name,
92259
- firstName: d.node.messaging_actor.short_name,
92260
- vanity: d.node.messaging_actor.username,
92261
- url: d.node.messaging_actor.url,
92262
- thumbSrc: d.node.messaging_actor.big_image_src.uri,
92263
- profileUrl: d.node.messaging_actor.big_image_src.uri,
92264
- gender: d.node.messaging_actor.gender,
92265
- type: d.node.messaging_actor.__typename,
92266
- isFriend: d.node.messaging_actor.is_viewer_friend,
92267
- isBirthday: !!d.node.messaging_actor.is_birthday
92316
+ userInfo: (messageThread.all_participants?.edges || []).map((d) => ({
92317
+ id: d?.node?.messaging_actor?.id,
92318
+ name: d?.node?.messaging_actor?.name,
92319
+ firstName: d?.node?.messaging_actor?.short_name,
92320
+ vanity: d?.node?.messaging_actor?.username,
92321
+ url: d?.node?.messaging_actor?.url,
92322
+ thumbSrc: d?.node?.messaging_actor?.big_image_src?.uri,
92323
+ profileUrl: d?.node?.messaging_actor?.big_image_src?.uri,
92324
+ gender: d?.node?.messaging_actor?.gender,
92325
+ type: d?.node?.messaging_actor?.__typename,
92326
+ isFriend: d?.node?.messaging_actor?.is_viewer_friend,
92327
+ isBirthday: !!d?.node?.messaging_actor?.is_birthday
92268
92328
  })),
92269
92329
  unreadCount: messageThread.unread_count,
92270
92330
  messageCount: messageThread.messages_count,
@@ -92275,7 +92335,7 @@ function formatThreadGraphQLResponse2(messageThread) {
92275
92335
  isArchived: messageThread.has_viewer_archived,
92276
92336
  folder: messageThread.folder,
92277
92337
  cannotReplyReason: messageThread.cannot_reply_reason,
92278
- eventReminders: messageThread.event_reminders ? messageThread.event_reminders.nodes.map(formatEventReminders2) : null,
92338
+ eventReminders: messageThread.event_reminders ? (messageThread.event_reminders.nodes || []).map(formatEventReminders2) : null,
92279
92339
  emoji: messageThread.customization_info ? messageThread.customization_info.emoji : null,
92280
92340
  color: messageThread.customization_info && messageThread.customization_info.outgoing_bubble_color ? messageThread.customization_info.outgoing_bubble_color.slice(2) : null,
92281
92341
  threadTheme: messageThread.thread_theme,
@@ -92321,18 +92381,29 @@ function formatThreadGraphQLResponse2(messageThread) {
92321
92381
  };
92322
92382
  }
92323
92383
  function getThreadList_default(defaultFuncs, api, ctx) {
92324
- return async function getThreadList(limit, timestamp = null, tags = ["INBOX"]) {
92384
+ return async function getThreadList(limit, timestamp = null, tags = ["INBOX"], callback = void 0) {
92385
+ const cb = typeof callback === "function" ? callback : void 0;
92386
+ const fail = (err) => {
92387
+ if (cb) {
92388
+ try {
92389
+ cb(err);
92390
+ } catch (_) {
92391
+ }
92392
+ return void 0;
92393
+ }
92394
+ throw err;
92395
+ };
92325
92396
  if (getType(limit) !== "Number" || !Number.isInteger(limit) || limit <= 0) {
92326
- throw new Error("getThreadList: limit must be a positive integer.");
92397
+ return fail(new Error("getThreadList: limit must be a positive integer."));
92327
92398
  }
92328
92399
  if (getType(timestamp) !== "Null" && (getType(timestamp) !== "Number" || !Number.isInteger(timestamp))) {
92329
- throw new Error("getThreadList: timestamp must be an integer or null.");
92400
+ return fail(new Error("getThreadList: timestamp must be an integer or null."));
92330
92401
  }
92331
92402
  if (getType(tags) === "String") {
92332
92403
  tags = [tags];
92333
92404
  }
92334
92405
  if (getType(tags) !== "Array") {
92335
- throw new Error("getThreadList: tags must be an array.");
92406
+ return fail(new Error("getThreadList: tags must be an array."));
92336
92407
  }
92337
92408
  const form = {
92338
92409
  av: ctx.i_userID || ctx.userID,
@@ -92366,10 +92437,17 @@ function getThreadList_default(defaultFuncs, api, ctx) {
92366
92437
  if (timestamp !== null && timestamp !== void 0) {
92367
92438
  nodes.shift();
92368
92439
  }
92369
- return nodes.map(formatThreadGraphQLResponse2).filter(Boolean);
92440
+ const list = nodes.map(formatThreadGraphQLResponse2).filter(Boolean);
92441
+ if (cb) {
92442
+ try {
92443
+ cb(null, list);
92444
+ } catch (_) {
92445
+ }
92446
+ }
92447
+ return list;
92370
92448
  } catch (err) {
92371
92449
  error("getThreadList", err);
92372
- throw err;
92450
+ return fail(err);
92373
92451
  }
92374
92452
  };
92375
92453
  }
package/dist/index.mjs CHANGED
@@ -70137,6 +70137,7 @@ __export(utils_exports2, {
70137
70137
  getType: () => getType,
70138
70138
  getWebSocketHeaders: () => getWebSocketHeaders,
70139
70139
  hasAuthenticatedSession: () => hasAuthenticatedSession,
70140
+ hasCometTokens: () => hasCometTokens,
70140
70141
  humanDelay: () => humanDelay,
70141
70142
  info: () => info,
70142
70143
  invalidateCometTokens: () => invalidateCometTokens,
@@ -85831,6 +85832,12 @@ function tokenPairFresh(state) {
85831
85832
  state.lsd && state.fb_dtsg && state.fetchedAt && Date.now() - state.fetchedAt < TOKEN_TTL_MS
85832
85833
  );
85833
85834
  }
85835
+ function applyCached(ctx, state) {
85836
+ if (!state.lsd || !state.fb_dtsg) return;
85837
+ ctx.lsd = state.lsd;
85838
+ ctx.fb_dtsg = state.fb_dtsg;
85839
+ ctx.jazoest = recomputeJazoest(state.fb_dtsg);
85840
+ }
85834
85841
  function htmlOf(res) {
85835
85842
  if (typeof res === "string") return res;
85836
85843
  if (res && typeof res.body === "string") return res.body;
@@ -85840,20 +85847,28 @@ function htmlOf(res) {
85840
85847
  async function ensureCometTokens(ctx, defaultFuncs, force = false) {
85841
85848
  const state = tokenState(ctx);
85842
85849
  if (!force && tokenPairFresh(state)) {
85843
- ctx.lsd = state.lsd;
85844
- ctx.fb_dtsg = state.fb_dtsg;
85850
+ applyCached(ctx, state);
85845
85851
  return;
85846
85852
  }
85847
85853
  if (!force && state.failures > 0) {
85848
85854
  const backoff = Math.min(1e3 * Math.pow(2, state.failures), MAX_BACKOFF_MS);
85849
85855
  if (state.attemptedAt && Date.now() - state.attemptedAt < backoff) {
85850
- if (state.lsd && state.fb_dtsg) {
85851
- ctx.lsd = state.lsd;
85852
- ctx.fb_dtsg = state.fb_dtsg;
85853
- }
85856
+ applyCached(ctx, state);
85854
85857
  return;
85855
85858
  }
85856
85859
  }
85860
+ if (!state.inflight) {
85861
+ state.inflight = scrapeTokens(ctx, defaultFuncs, state).finally(() => {
85862
+ state.inflight = null;
85863
+ });
85864
+ }
85865
+ try {
85866
+ await state.inflight;
85867
+ } catch {
85868
+ }
85869
+ applyCached(ctx, state);
85870
+ }
85871
+ async function scrapeTokens(ctx, defaultFuncs, state) {
85857
85872
  state.attemptedAt = Date.now();
85858
85873
  try {
85859
85874
  const res = await defaultFuncs.get("https://www.facebook.com/", ctx.jar);
@@ -85877,13 +85892,14 @@ async function ensureCometTokens(ctx, defaultFuncs, force = false) {
85877
85892
  if (spin.__rev) ctx.revision = spin.__rev;
85878
85893
  const asbd = extractAsbdId(html3);
85879
85894
  if (asbd) ctx.asbdId = asbd;
85880
- ctx.lsd = lsd;
85881
- ctx.fb_dtsg = dtsg;
85882
- ctx.jazoest = recomputeJazoest(dtsg);
85883
85895
  } catch {
85884
85896
  state.failures++;
85885
85897
  }
85886
85898
  }
85899
+ function hasCometTokens(ctx) {
85900
+ const state = ctx.__cometTokens || {};
85901
+ return Boolean(state.lsd && state.fb_dtsg);
85902
+ }
85887
85903
  function invalidateCometTokens(ctx) {
85888
85904
  const state = tokenState(ctx);
85889
85905
  state.lsd = void 0;
@@ -90141,10 +90157,11 @@ function collectTracks(payload) {
90141
90157
  if (Array.isArray(node.sub_items)) candidates.push(...node.sub_items);
90142
90158
  for (const candidate of candidates) {
90143
90159
  const track = unwrap2(candidate);
90144
- if (track && track.id && !seen.has(track.id)) {
90145
- seen.add(track.id);
90146
- tracks.push(track);
90147
- }
90160
+ if (!track) continue;
90161
+ const key = track.id || track.audioUrl;
90162
+ if (!key || seen.has(key)) continue;
90163
+ seen.add(key);
90164
+ tracks.push(track);
90148
90165
  }
90149
90166
  }
90150
90167
  const pageInfo = container?.items?.page_info;
@@ -90156,10 +90173,24 @@ function collectTracks(payload) {
90156
90173
  }
90157
90174
 
90158
90175
  // src/deltas/apis/music/searchMusic.ts
90176
+ var MUSIC_CACHE_TTL_MS = 5 * 60 * 1e3;
90159
90177
  function nextRequestId(ctx) {
90160
90178
  ctx.__reqCounter = (typeof ctx.__reqCounter === "number" ? ctx.__reqCounter : 0) + 1;
90161
90179
  return ctx.__reqCounter.toString(36);
90162
90180
  }
90181
+ var MUSIC_MIN_INTERVAL_MS = 1100;
90182
+ async function paceMusic(ctx) {
90183
+ const configured = Number(ctx?.globalOptions?.musicMinIntervalMs);
90184
+ const min = Number.isFinite(configured) && configured >= 0 ? configured : MUSIC_MIN_INTERVAL_MS;
90185
+ const last2 = typeof ctx.__musicLastCallAt === "number" ? ctx.__musicLastCallAt : 0;
90186
+ const wait = last2 + min - Date.now();
90187
+ if (wait > 0) await sleep(wait);
90188
+ ctx.__musicLastCallAt = Date.now();
90189
+ }
90190
+ function cacheResult(ctx, key, result) {
90191
+ const cache = ctx.__musicCache || (ctx.__musicCache = {});
90192
+ cache[key] = { at: Date.now(), result };
90193
+ }
90163
90194
  function normalizeError(error2) {
90164
90195
  if (!error2 || typeof error2 !== "object") {
90165
90196
  const message2 = String(error2);
@@ -90181,6 +90212,13 @@ function normalizeError(error2) {
90181
90212
  }
90182
90213
  function searchMusic_default(defaultFuncs, _api, ctx) {
90183
90214
  async function runSearch(text3, count, selectedOptions, callback) {
90215
+ const cacheKey = `${selectedOptions.product || PRODUCT}|${count}|${text3}|${selectedOptions.cursor || ""}`;
90216
+ if (!selectedOptions.noCache) {
90217
+ const cache = ctx.__musicCache || (ctx.__musicCache = {});
90218
+ const hit = cache[cacheKey];
90219
+ if (hit && Date.now() - hit.at < MUSIC_CACHE_TTL_MS) return hit.result;
90220
+ }
90221
+ await paceMusic(ctx);
90184
90222
  const params = { first: count };
90185
90223
  if (text3) params.search_text = text3;
90186
90224
  if (selectedOptions.cursor) params.after = String(selectedOptions.cursor);
@@ -90216,7 +90254,6 @@ function searchMusic_default(defaultFuncs, _api, ctx) {
90216
90254
  };
90217
90255
  let parsed = await attempt(false);
90218
90256
  if (isStaleTokenError(parsed)) {
90219
- invalidateCometTokens(ctx);
90220
90257
  parsed = await attempt(true);
90221
90258
  }
90222
90259
  if (!parsed) throw { error: "searchMusic returned empty object.", code: "BAD_RESPONSE" };
@@ -90254,12 +90291,12 @@ function searchMusic_default(defaultFuncs, _api, ctx) {
90254
90291
  result.endCursor = page.endCursor;
90255
90292
  result.hasNextPage = page.hasNextPage;
90256
90293
  result.query = text3;
90294
+ cacheResult(ctx, cacheKey, result);
90257
90295
  return result;
90258
90296
  }
90259
90297
  for (let retry = 0; retry < 3; retry++) {
90260
- invalidateCometTokens(ctx);
90261
- await sleep(600 + retry * 900);
90262
- const retried = await attempt(true);
90298
+ await sleep(1200 + retry * 1300 + Math.floor(Math.random() * 400));
90299
+ const retried = await attempt(retry === 2 || !hasCometTokens(ctx));
90263
90300
  if (isStaleTokenError(retried) || retried?.error || (retried?.errors || []).length) continue;
90264
90301
  const retryPage = collectTracks(retried);
90265
90302
  const retryTracks = retryPage.tracks.slice(0, count);
@@ -90269,9 +90306,17 @@ function searchMusic_default(defaultFuncs, _api, ctx) {
90269
90306
  result.endCursor = retryPage.endCursor;
90270
90307
  result.hasNextPage = retryPage.hasNextPage;
90271
90308
  result.query = text3;
90309
+ cacheResult(ctx, cacheKey, result);
90272
90310
  return result;
90273
90311
  }
90274
90312
  }
90313
+ if (!hasCometTokens(ctx)) {
90314
+ throw {
90315
+ error: "Facebook did not return a music search document token. The session or network is unhealthy; retry shortly.",
90316
+ code: "TOKEN_UNAVAILABLE",
90317
+ query: text3
90318
+ };
90319
+ }
90275
90320
  throw {
90276
90321
  error: `No music results found for "${text3}". The Facebook music catalog may be unavailable for this account or region.`,
90277
90322
  code: "NO_RESULTS",
@@ -92015,7 +92060,7 @@ function getThreadHistory_default(defaultFuncs, api, ctx) {
92015
92060
  function formatEventReminders(reminder) {
92016
92061
  return {
92017
92062
  reminderID: reminder.id,
92018
- eventCreatorID: reminder.lightweight_event_creator.id,
92063
+ eventCreatorID: reminder.lightweight_event_creator?.id,
92019
92064
  time: reminder.time,
92020
92065
  eventType: String(reminder.lightweight_event_type || "").toLowerCase(),
92021
92066
  locationName: reminder.location_name,
@@ -92051,21 +92096,21 @@ function formatThreadGraphQLResponse(data2) {
92051
92096
  return {
92052
92097
  threadID,
92053
92098
  threadName: messageThread.name,
92054
- participantIDs: messageThread.all_participants.edges.map(
92055
- (d) => d.node.messaging_actor.id
92099
+ participantIDs: (messageThread.all_participants?.edges || []).map(
92100
+ (d) => d?.node?.messaging_actor?.id
92056
92101
  ),
92057
- userInfo: messageThread.all_participants.edges.map((d) => ({
92058
- id: d.node.messaging_actor.id,
92059
- name: d.node.messaging_actor.name,
92060
- firstName: d.node.messaging_actor.short_name,
92061
- vanity: d.node.messaging_actor.username,
92062
- url: d.node.messaging_actor.url,
92063
- thumbSrc: d.node.messaging_actor.big_image_src.uri,
92064
- profileUrl: d.node.messaging_actor.big_image_src.uri,
92065
- gender: d.node.messaging_actor.gender,
92066
- type: d.node.messaging_actor.__typename,
92067
- isFriend: d.node.messaging_actor.is_viewer_friend,
92068
- isBirthday: !!d.node.messaging_actor.is_birthday
92102
+ userInfo: (messageThread.all_participants?.edges || []).map((d) => ({
92103
+ id: d?.node?.messaging_actor?.id,
92104
+ name: d?.node?.messaging_actor?.name,
92105
+ firstName: d?.node?.messaging_actor?.short_name,
92106
+ vanity: d?.node?.messaging_actor?.username,
92107
+ url: d?.node?.messaging_actor?.url,
92108
+ thumbSrc: d?.node?.messaging_actor?.big_image_src?.uri,
92109
+ profileUrl: d?.node?.messaging_actor?.big_image_src?.uri,
92110
+ gender: d?.node?.messaging_actor?.gender,
92111
+ type: d?.node?.messaging_actor?.__typename,
92112
+ isFriend: d?.node?.messaging_actor?.is_viewer_friend,
92113
+ isBirthday: !!d?.node?.messaging_actor?.is_birthday
92069
92114
  })),
92070
92115
  unreadCount: messageThread.unread_count,
92071
92116
  messageCount: messageThread.messages_count,
@@ -92076,7 +92121,7 @@ function formatThreadGraphQLResponse(data2) {
92076
92121
  isArchived: messageThread.has_viewer_archived,
92077
92122
  folder: messageThread.folder,
92078
92123
  cannotReplyReason: messageThread.cannot_reply_reason,
92079
- eventReminders: messageThread.event_reminders ? messageThread.event_reminders.nodes.map(formatEventReminders) : null,
92124
+ eventReminders: messageThread.event_reminders ? (messageThread.event_reminders.nodes || []).map(formatEventReminders) : null,
92080
92125
  emoji: messageThread.customization_info ? messageThread.customization_info.emoji : null,
92081
92126
  color: messageThread.customization_info && messageThread.customization_info.outgoing_bubble_color ? messageThread.customization_info.outgoing_bubble_color.slice(2) : null,
92082
92127
  threadTheme: messageThread.thread_theme,
@@ -92122,7 +92167,8 @@ function formatThreadGraphQLResponse(data2) {
92122
92167
  };
92123
92168
  }
92124
92169
  function getThreadInfo_default(defaultFuncs, api, ctx) {
92125
- return async function getThreadInfo(threadID) {
92170
+ return async function getThreadInfo(threadID, callback = void 0) {
92171
+ const cb = typeof callback === "function" ? callback : void 0;
92126
92172
  const threadIDs = Array.isArray(threadID) ? threadID : [threadID];
92127
92173
  let form = {};
92128
92174
  threadIDs.forEach((t, i2) => {
@@ -92173,9 +92219,23 @@ function getThreadInfo_default(defaultFuncs, api, ctx) {
92173
92219
  if (matched === 0) {
92174
92220
  throw new Error("getThreadInfo: malformed response from GraphQL.");
92175
92221
  }
92176
- return Array.isArray(threadID) ? threadInfos : Object.values(threadInfos)[0] || null;
92222
+ const out = Array.isArray(threadID) ? threadInfos : Object.values(threadInfos)[0] || null;
92223
+ if (cb) {
92224
+ try {
92225
+ cb(null, out);
92226
+ } catch (_) {
92227
+ }
92228
+ }
92229
+ return out;
92177
92230
  } catch (err) {
92178
92231
  error("getThreadInfo", err);
92232
+ if (cb) {
92233
+ try {
92234
+ cb(err);
92235
+ } catch (_) {
92236
+ }
92237
+ return void 0;
92238
+ }
92179
92239
  throw err;
92180
92240
  }
92181
92241
  };
@@ -92185,7 +92245,7 @@ function getThreadInfo_default(defaultFuncs, api, ctx) {
92185
92245
  function formatEventReminders2(reminder) {
92186
92246
  return {
92187
92247
  reminderID: reminder.id,
92188
- eventCreatorID: reminder.lightweight_event_creator.id,
92248
+ eventCreatorID: reminder.lightweight_event_creator?.id,
92189
92249
  time: reminder.time,
92190
92250
  eventType: String(reminder.lightweight_event_type || "").toLowerCase(),
92191
92251
  locationName: reminder.location_name,
@@ -92199,7 +92259,7 @@ function formatEventReminders2(reminder) {
92199
92259
  secondsToNotifyBefore: reminder.seconds_to_notify_before,
92200
92260
  allowsRsvp: reminder.allows_rsvp,
92201
92261
  relatedEvent: reminder.related_event,
92202
- members: reminder.event_reminder_members.edges.map(function(member) {
92262
+ members: (reminder.event_reminder_members?.edges || []).map(function(member) {
92203
92263
  return {
92204
92264
  memberID: member.node.id,
92205
92265
  state: String(member.guest_list_state || "").toLowerCase()
@@ -92218,21 +92278,21 @@ function formatThreadGraphQLResponse2(messageThread) {
92218
92278
  return {
92219
92279
  threadID,
92220
92280
  threadName: messageThread.name,
92221
- participantIDs: messageThread.all_participants.edges.map(
92222
- (d) => d.node.messaging_actor.id
92281
+ participantIDs: (messageThread.all_participants?.edges || []).map(
92282
+ (d) => d?.node?.messaging_actor?.id
92223
92283
  ),
92224
- userInfo: messageThread.all_participants.edges.map((d) => ({
92225
- id: d.node.messaging_actor.id,
92226
- name: d.node.messaging_actor.name,
92227
- firstName: d.node.messaging_actor.short_name,
92228
- vanity: d.node.messaging_actor.username,
92229
- url: d.node.messaging_actor.url,
92230
- thumbSrc: d.node.messaging_actor.big_image_src.uri,
92231
- profileUrl: d.node.messaging_actor.big_image_src.uri,
92232
- gender: d.node.messaging_actor.gender,
92233
- type: d.node.messaging_actor.__typename,
92234
- isFriend: d.node.messaging_actor.is_viewer_friend,
92235
- isBirthday: !!d.node.messaging_actor.is_birthday
92284
+ userInfo: (messageThread.all_participants?.edges || []).map((d) => ({
92285
+ id: d?.node?.messaging_actor?.id,
92286
+ name: d?.node?.messaging_actor?.name,
92287
+ firstName: d?.node?.messaging_actor?.short_name,
92288
+ vanity: d?.node?.messaging_actor?.username,
92289
+ url: d?.node?.messaging_actor?.url,
92290
+ thumbSrc: d?.node?.messaging_actor?.big_image_src?.uri,
92291
+ profileUrl: d?.node?.messaging_actor?.big_image_src?.uri,
92292
+ gender: d?.node?.messaging_actor?.gender,
92293
+ type: d?.node?.messaging_actor?.__typename,
92294
+ isFriend: d?.node?.messaging_actor?.is_viewer_friend,
92295
+ isBirthday: !!d?.node?.messaging_actor?.is_birthday
92236
92296
  })),
92237
92297
  unreadCount: messageThread.unread_count,
92238
92298
  messageCount: messageThread.messages_count,
@@ -92243,7 +92303,7 @@ function formatThreadGraphQLResponse2(messageThread) {
92243
92303
  isArchived: messageThread.has_viewer_archived,
92244
92304
  folder: messageThread.folder,
92245
92305
  cannotReplyReason: messageThread.cannot_reply_reason,
92246
- eventReminders: messageThread.event_reminders ? messageThread.event_reminders.nodes.map(formatEventReminders2) : null,
92306
+ eventReminders: messageThread.event_reminders ? (messageThread.event_reminders.nodes || []).map(formatEventReminders2) : null,
92247
92307
  emoji: messageThread.customization_info ? messageThread.customization_info.emoji : null,
92248
92308
  color: messageThread.customization_info && messageThread.customization_info.outgoing_bubble_color ? messageThread.customization_info.outgoing_bubble_color.slice(2) : null,
92249
92309
  threadTheme: messageThread.thread_theme,
@@ -92289,18 +92349,29 @@ function formatThreadGraphQLResponse2(messageThread) {
92289
92349
  };
92290
92350
  }
92291
92351
  function getThreadList_default(defaultFuncs, api, ctx) {
92292
- return async function getThreadList(limit, timestamp = null, tags = ["INBOX"]) {
92352
+ return async function getThreadList(limit, timestamp = null, tags = ["INBOX"], callback = void 0) {
92353
+ const cb = typeof callback === "function" ? callback : void 0;
92354
+ const fail = (err) => {
92355
+ if (cb) {
92356
+ try {
92357
+ cb(err);
92358
+ } catch (_) {
92359
+ }
92360
+ return void 0;
92361
+ }
92362
+ throw err;
92363
+ };
92293
92364
  if (getType(limit) !== "Number" || !Number.isInteger(limit) || limit <= 0) {
92294
- throw new Error("getThreadList: limit must be a positive integer.");
92365
+ return fail(new Error("getThreadList: limit must be a positive integer."));
92295
92366
  }
92296
92367
  if (getType(timestamp) !== "Null" && (getType(timestamp) !== "Number" || !Number.isInteger(timestamp))) {
92297
- throw new Error("getThreadList: timestamp must be an integer or null.");
92368
+ return fail(new Error("getThreadList: timestamp must be an integer or null."));
92298
92369
  }
92299
92370
  if (getType(tags) === "String") {
92300
92371
  tags = [tags];
92301
92372
  }
92302
92373
  if (getType(tags) !== "Array") {
92303
- throw new Error("getThreadList: tags must be an array.");
92374
+ return fail(new Error("getThreadList: tags must be an array."));
92304
92375
  }
92305
92376
  const form = {
92306
92377
  av: ctx.i_userID || ctx.userID,
@@ -92334,10 +92405,17 @@ function getThreadList_default(defaultFuncs, api, ctx) {
92334
92405
  if (timestamp !== null && timestamp !== void 0) {
92335
92406
  nodes.shift();
92336
92407
  }
92337
- return nodes.map(formatThreadGraphQLResponse2).filter(Boolean);
92408
+ const list = nodes.map(formatThreadGraphQLResponse2).filter(Boolean);
92409
+ if (cb) {
92410
+ try {
92411
+ cb(null, list);
92412
+ } catch (_) {
92413
+ }
92414
+ }
92415
+ return list;
92338
92416
  } catch (err) {
92339
92417
  error("getThreadList", err);
92340
- throw err;
92418
+ return fail(err);
92341
92419
  }
92342
92420
  };
92343
92421
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyneoaz/metachat",
3
- "version": "6.0.2",
3
+ "version": "6.0.4",
4
4
  "description": "A modern TypeScript Facebook Chat API client for building reliable Messenger bots, with built-in anti-detection protection, automatic cookie refresh, music search, and AI theme support.",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -57,7 +57,7 @@
57
57
  "prepublishOnly": "npm run build",
58
58
  "lint": "npm run typecheck",
59
59
  "format": "prettier --write \"src/**/*.ts\" \"index.ts\"",
60
- "test": "npm run build && node test/smoke.test.cjs && node test/smoke.test.mjs && node test/music-search.test.cjs && node test/mqtt-lifecycle.test.cjs && node test/session-reliability.test.cjs && node test/anti-detection.test.cjs && node test/account-safety.test.cjs && node test/liveness.test.cjs && node test/recovery.test.cjs && node test/cookie-acquisition.test.cjs && node test/realtime-health.test.cjs && node test/attachment-resilience.test.cjs && node test/reaction-validation.test.cjs && node test/api-surface-fixes.test.cjs && node test/threadinfo-regression.test.cjs && node test/new-modules.test.cjs && node test/mention-parsing.test.cjs",
60
+ "test": "npm run build && node test/smoke.test.cjs && node test/smoke.test.mjs && node test/music-search.test.cjs && node test/mqtt-lifecycle.test.cjs && node test/session-reliability.test.cjs && node test/anti-detection.test.cjs && node test/account-safety.test.cjs && node test/liveness.test.cjs && node test/recovery.test.cjs && node test/cookie-acquisition.test.cjs && node test/realtime-health.test.cjs && node test/attachment-resilience.test.cjs && node test/reaction-validation.test.cjs && node test/api-surface-fixes.test.cjs && node test/threadinfo-regression.test.cjs && node test/new-modules.test.cjs && node test/mention-parsing.test.cjs && node test/pending-support.test.cjs && node test/music-token-resilience.test.cjs",
61
61
  "audit": "npm audit",
62
62
  "audit:fix": "npm audit fix"
63
63
  },