@sockethub/platform-metadata 1.0.1-alpha.18 → 1.0.1-alpha.19

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.js CHANGED
@@ -71661,6 +71661,11 @@ function errorMessage(value) {
71661
71661
  }
71662
71662
  return String(value);
71663
71663
  }
71664
+ var EXPECTED_ERROR = Symbol.for("sockethub.expectedError");
71665
+ function markExpectedError(err) {
71666
+ err[EXPECTED_ERROR] = true;
71667
+ return err;
71668
+ }
71664
71669
 
71665
71670
  // ../util/dist/net/index.js
71666
71671
  var import_undici = __toESM(require_undici(), 1);
@@ -71813,7 +71818,7 @@ var import_undici2 = __toESM(require_undici(), 1);
71813
71818
  // package.json
71814
71819
  var package_default = {
71815
71820
  name: "@sockethub/platform-metadata",
71816
- version: "1.0.1-alpha.18",
71821
+ version: "1.0.1-alpha.19",
71817
71822
  description: "Fetch metadata from a given URL",
71818
71823
  author: "Nick Jennings <nick@silverbucket.net>",
71819
71824
  license: "LGPL-3.0+",
@@ -71855,17 +71860,17 @@ var package_default = {
71855
71860
  "clean:deps": "rm -rf node_modules"
71856
71861
  },
71857
71862
  dependencies: {
71858
- "@sockethub/util": "^1.0.0-alpha.11",
71863
+ "@sockethub/util": "^1.0.0-alpha.12",
71859
71864
  "open-graph-scraper": "^6.11.0",
71860
71865
  undici: "^7.16.0"
71861
71866
  },
71862
71867
  devDependencies: {
71863
- "@sockethub/schemas": "^3.0.0-alpha.23"
71868
+ "@sockethub/schemas": "^3.0.0-alpha.24"
71864
71869
  },
71865
71870
  peerDependencies: {
71866
- "@sockethub/server": "^5.0.0-alpha.23"
71871
+ "@sockethub/server": "^5.0.0-alpha.24"
71867
71872
  },
71868
- gitHead: "a6d303097640e82c7d1a7cef6dea2e6dae5f2cac",
71873
+ gitHead: "e778256d7067e2743cd6807a15d39a0cc65a358d",
71869
71874
  types: "./dist/index.d.ts"
71870
71875
  };
71871
71876
 
@@ -71884,6 +71889,15 @@ var YOUTUBE_HOSTS = new Set([
71884
71889
  "m.youtube.com",
71885
71890
  "music.youtube.com"
71886
71891
  ]);
71892
+ var FACEBOOK_HOSTS = new Set([
71893
+ "facebook.com",
71894
+ "www.facebook.com",
71895
+ "m.facebook.com",
71896
+ "web.facebook.com",
71897
+ "mbasic.facebook.com",
71898
+ "fb.com",
71899
+ "www.fb.com"
71900
+ ]);
71887
71901
  var REDDIT_HOSTS = new Set([
71888
71902
  "reddit.com",
71889
71903
  "www.reddit.com",
@@ -71967,6 +71981,19 @@ function youtubeOEmbedImage(embed) {
71967
71981
  }
71968
71982
  ] : undefined;
71969
71983
  }
71984
+ function isFacebookUrl(url) {
71985
+ let parsed;
71986
+ try {
71987
+ parsed = new URL(url);
71988
+ } catch {
71989
+ return false;
71990
+ }
71991
+ const host = parsed.hostname.toLowerCase();
71992
+ return FACEBOOK_HOSTS.has(host) || host === "fb.watch";
71993
+ }
71994
+ function stripFacebookEngagement(title) {
71995
+ return title?.replace(/^[^|]*\p{Nd}[^|]*·[^|]*\|\s*/u, "");
71996
+ }
71970
71997
  function isRedditUrl(url) {
71971
71998
  let parsed;
71972
71999
  try {
@@ -71993,6 +72020,19 @@ function resolveRedditJson(url) {
71993
72020
  json.searchParams.set("raw_json", "1");
71994
72021
  return json.href;
71995
72022
  }
72023
+ function resolveRedditMedia(url) {
72024
+ let parsed;
72025
+ try {
72026
+ parsed = new URL(url);
72027
+ } catch {
72028
+ return null;
72029
+ }
72030
+ if (!REDDIT_HOSTS.has(parsed.hostname.toLowerCase()))
72031
+ return null;
72032
+ if (parsed.pathname.replace(/\/+$/, "") !== "/media")
72033
+ return null;
72034
+ return redditMediaUrl(parsed.searchParams.get("url"), new Set(["i.redd.it", "preview.redd.it", "external-preview.redd.it"]));
72035
+ }
71996
72036
  function parseRedditPost(value) {
71997
72037
  if (!Array.isArray(value))
71998
72038
  return null;
@@ -72008,19 +72048,51 @@ function parseRedditPost(value) {
72008
72048
  }
72009
72049
  return data;
72010
72050
  }
72051
+ function redditMediaUrl(value, allowedHosts) {
72052
+ if (typeof value !== "string")
72053
+ return null;
72054
+ try {
72055
+ const url = new URL(value);
72056
+ return url.protocol === "https:" && allowedHosts.has(url.hostname.toLowerCase()) ? url.href : null;
72057
+ } catch {
72058
+ return null;
72059
+ }
72060
+ }
72061
+ function finiteNonNegative(value) {
72062
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
72063
+ }
72011
72064
  function redditPostImage(post) {
72012
72065
  for (const candidate of [post.url_overridden_by_dest, post.url]) {
72013
- if (!candidate)
72014
- continue;
72015
- try {
72016
- const url = new URL(candidate);
72017
- if (["i.redd.it", "preview.redd.it"].includes(url.hostname.toLowerCase())) {
72018
- return [{ url: url.href }];
72066
+ const url = redditMediaUrl(candidate, new Set(["i.redd.it", "preview.redd.it"]));
72067
+ if (url)
72068
+ return [{ url }];
72069
+ }
72070
+ const source = post.preview?.images?.[0]?.source;
72071
+ const previewUrl = redditMediaUrl(source?.url, new Set(["preview.redd.it", "external-preview.redd.it"]));
72072
+ if (previewUrl) {
72073
+ return [
72074
+ {
72075
+ url: previewUrl,
72076
+ width: finiteNonNegative(source?.width),
72077
+ height: finiteNonNegative(source?.height)
72019
72078
  }
72020
- } catch {}
72079
+ ];
72021
72080
  }
72022
72081
  return;
72023
72082
  }
72083
+ function redditPostVideo(post) {
72084
+ const video = post.secure_media?.reddit_video;
72085
+ const url = redditMediaUrl(video?.fallback_url, new Set(["v.redd.it"]));
72086
+ if (!url)
72087
+ return;
72088
+ return {
72089
+ url,
72090
+ thumbnail: redditPostImage(post)?.[0]?.url,
72091
+ width: finiteNonNegative(video?.width),
72092
+ height: finiteNonNegative(video?.height),
72093
+ duration: finiteNonNegative(video?.duration)
72094
+ };
72095
+ }
72024
72096
  function parseRedditOEmbed(value) {
72025
72097
  if (!value || typeof value !== "object" || Array.isArray(value))
72026
72098
  return null;
@@ -72336,6 +72408,29 @@ class Metadata {
72336
72408
  this.scrape(job, cb);
72337
72409
  }
72338
72410
  async fetchReddit(job, cb) {
72411
+ const mediaImage = resolveRedditMedia(job.actor.id);
72412
+ if (mediaImage) {
72413
+ const imagePath = new URL(mediaImage).pathname;
72414
+ let title;
72415
+ try {
72416
+ title = decodeURIComponent(imagePath).slice(1);
72417
+ } catch {
72418
+ title = imagePath.slice(1);
72419
+ }
72420
+ job.actor.name = "reddit";
72421
+ job.object = {
72422
+ type: "page",
72423
+ title,
72424
+ name: "reddit",
72425
+ description: "",
72426
+ image: [{ url: mediaImage }],
72427
+ url: job.actor.id,
72428
+ favicon: "/favicon.ico"
72429
+ };
72430
+ this.log.debug(`reddit media link unwrapped for ${job.actor.id}`);
72431
+ cb(null, job);
72432
+ return;
72433
+ }
72339
72434
  const jsonUrl = resolveRedditJson(job.actor.id);
72340
72435
  const embedPromise = withDeadline(this.fetchRedditEmbed(job.actor.id), REDDIT_JSON_TIMEOUT_MS).catch(() => {
72341
72436
  return;
@@ -72353,6 +72448,7 @@ class Metadata {
72353
72448
  name: "reddit",
72354
72449
  description: normalizeDescription(post.selftext ?? ""),
72355
72450
  image: redditPostImage(post),
72451
+ video: redditPostVideo(post),
72356
72452
  url: job.actor.id,
72357
72453
  favicon: "/favicon.ico"
72358
72454
  };
@@ -72378,7 +72474,7 @@ class Metadata {
72378
72474
  cb(null, job);
72379
72475
  return;
72380
72476
  }
72381
- cb(new Error(`No Reddit metadata available for ${job.actor.id}`));
72477
+ cb(markExpectedError(new Error(`No Reddit metadata available for ${job.actor.id}`)));
72382
72478
  }
72383
72479
  fetchRedditJson(url) {
72384
72480
  return new Promise((resolve, reject) => {
@@ -72441,7 +72537,7 @@ class Metadata {
72441
72537
  }
72442
72538
  }
72443
72539
  scrape(job, cb, redditEmbed, scrapeUrl = job.actor.id, youtubeEmbed) {
72444
- const useCompatUserAgent = isRedditUrl(job.actor.id) || Boolean(resolveYouTubeOEmbed(job.actor.id));
72540
+ const useCompatUserAgent = isRedditUrl(job.actor.id) || isFacebookUrl(job.actor.id) || Boolean(resolveYouTubeOEmbed(job.actor.id));
72445
72541
  const userAgent = useCompatUserAgent ? this.compatUserAgent() : this.userAgent();
72446
72542
  const dispatcher = this.getDispatcher();
72447
72543
  this.log.debug(`scrape started for ${job.actor.id} via ${scrapeUrl}`);
@@ -72481,18 +72577,24 @@ class Metadata {
72481
72577
  const { result } = data;
72482
72578
  this.log.debug(`scrape completed for ${job.actor.id}`);
72483
72579
  const reddit = isRedditUrl(job.actor.id);
72580
+ const facebook = isFacebookUrl(job.actor.id);
72484
72581
  const embed = reddit ? await redditEmbed : undefined;
72485
72582
  const youtube = await youtubeEmbed;
72486
72583
  if (!reddit)
72487
72584
  job.actor.id = result.ogUrl || job.actor.id;
72488
- job.actor.name = reddit ? embed?.provider_name ?? "reddit" : result.ogSiteName ?? job.actor.name ?? "";
72585
+ const siteName = result.ogSiteName ?? (facebook ? "Facebook" : undefined);
72586
+ const scrapedImages = facebook ? result.ogImage?.map((image) => ({
72587
+ ...image,
72588
+ alt: stripFacebookEngagement(image.alt)
72589
+ })) : result.ogImage;
72590
+ job.actor.name = reddit ? embed?.provider_name ?? "reddit" : siteName ?? job.actor.name ?? "";
72489
72591
  job.object = {
72490
72592
  type: "page",
72491
72593
  language: result.ogLocale,
72492
- title: embed?.title ?? youtube?.title ?? result.ogTitle,
72493
- name: embed?.provider_name ?? youtube?.provider_name ?? result.ogSiteName,
72594
+ title: embed?.title ?? youtube?.title ?? (facebook ? stripFacebookEngagement(result.ogTitle) : result.ogTitle),
72595
+ name: embed?.provider_name ?? youtube?.provider_name ?? siteName,
72494
72596
  description: normalizeDescription(result.ogDescription || ""),
72495
- image: reddit ? redditPostImages(result.ogImage) ?? redditOEmbedImage(embed ?? {}) : result.ogImage?.length ? result.ogImage : youtubeOEmbedImage(youtube),
72597
+ image: reddit ? redditPostImages(result.ogImage) ?? redditOEmbedImage(embed ?? {}) : scrapedImages?.length ? scrapedImages : youtubeOEmbedImage(youtube),
72496
72598
  url: reddit ? job.actor.id : result.ogUrl,
72497
72599
  favicon: result.favicon || "/favicon.ico",
72498
72600
  charset: result.charset
@@ -72535,7 +72637,7 @@ class Metadata {
72535
72637
  cb(null, job);
72536
72638
  return;
72537
72639
  }
72538
- cb(err);
72640
+ cb(markExpectedError(new Error(`metadata scrape failed for ${job.actor.id}: ${err.message}`)));
72539
72641
  });
72540
72642
  }
72541
72643
  cleanup(cb) {
@@ -72551,4 +72653,4 @@ export {
72551
72653
  withDeadline
72552
72654
  };
72553
72655
 
72554
- //# debugId=98F3FC3874006CC764756E2164756E21
72656
+ //# debugId=136FC9DCA9AFCE9364756E2164756E21