@sockethub/platform-metadata 1.0.1-beta.4 → 1.0.1-beta.5
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 +12 -2
- package/dist/index.js +63 -13
- package/dist/index.js.map +4 -4
- package/dist/resolvers.d.ts +42 -10
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -98,6 +98,11 @@ The platform extracts the following metadata when available:
|
|
|
98
98
|
flattened to one string, or the virtual location URL), `url`, and `status`
|
|
99
99
|
(`scheduled`, `cancelled`, `postponed`, `rescheduled`). Only present when
|
|
100
100
|
both a name and a start date are declared.
|
|
101
|
+
* **Description fallback**: pages with no meta description use a JSON-LD
|
|
102
|
+
`description`, else a `headline` not already in the title (Wikipedia's
|
|
103
|
+
short description, e.g. "1979 studio album by Brian Eno").
|
|
104
|
+
* **Non-HTML links** (PDFs and other files) return a page titled with the
|
|
105
|
+
file name instead of an error.
|
|
101
106
|
|
|
102
107
|
## Site-Specific Handling
|
|
103
108
|
|
|
@@ -108,7 +113,9 @@ so those URLs are resolved through preview services built for this purpose:
|
|
|
108
113
|
are fetched from the [FxTwitter API](https://github.com/FxEmbed/FxEmbed)
|
|
109
114
|
instead of scraped — X serves only a generic site banner to scrapers. The
|
|
110
115
|
response carries the post's text, its actual photo (or video + poster
|
|
111
|
-
thumbnail), and the canonical `x.com` URL.
|
|
116
|
+
thumbnail), and the canonical `x.com` URL. Posts without their own media
|
|
117
|
+
use the quoted post's media, then the link card image, then the author's
|
|
118
|
+
avatar. If the API is unavailable or the
|
|
112
119
|
post can't be resolved, the platform falls back to the regular scrape
|
|
113
120
|
(which still yields the post text).
|
|
114
121
|
* **Reddit post URLs** use `old.reddit.com`'s structured JSON response, avoiding
|
|
@@ -127,7 +134,10 @@ so those URLs are resolved through preview services built for this purpose:
|
|
|
127
134
|
* **Amazon product URLs** (storefronts such as `amazon.de` and `amazon.com`,
|
|
128
135
|
plus `amzn.to`/`a.co` short links) are scraped with WhatsApp's link-preview
|
|
129
136
|
user agent. Amazon answers other crawlers with a captcha or a generic
|
|
130
|
-
"Amazon" share card instead of the product's title and image.
|
|
137
|
+
"Amazon" share card instead of the product's title and image. Mobile-app
|
|
138
|
+
links (`/gp/aw/d/<ASIN>`) are scraped as `/dp/<ASIN>`, which is the only
|
|
139
|
+
form that gets the product card. When Amazon serves its full desktop page
|
|
140
|
+
instead, the image comes from the page's main product image.
|
|
131
141
|
* **Facebook engagement prefixes**: video titles and image alt text often
|
|
132
142
|
start with a localized view or reaction count, such as
|
|
133
143
|
`2.2M views · 21K reactions | …` or `347K views | …`. When the prefix is
|
package/dist/index.js
CHANGED
|
@@ -70777,7 +70777,7 @@ var import_undici3 = __toESM(require_undici(), 1);
|
|
|
70777
70777
|
// package.json
|
|
70778
70778
|
var package_default = {
|
|
70779
70779
|
name: "@sockethub/platform-metadata",
|
|
70780
|
-
version: "1.0.1-beta.
|
|
70780
|
+
version: "1.0.1-beta.5",
|
|
70781
70781
|
description: "Fetch metadata from a given URL",
|
|
70782
70782
|
author: "Nick Jennings <nick@silverbucket.net>",
|
|
70783
70783
|
license: "LGPL-3.0+",
|
|
@@ -70819,17 +70819,17 @@ var package_default = {
|
|
|
70819
70819
|
"clean:deps": "rm -rf node_modules"
|
|
70820
70820
|
},
|
|
70821
70821
|
dependencies: {
|
|
70822
|
-
"@sockethub/util": "^1.0.0-beta.
|
|
70822
|
+
"@sockethub/util": "^1.0.0-beta.5",
|
|
70823
70823
|
"open-graph-scraper": "^6.11.0",
|
|
70824
70824
|
undici: "^7.30.0"
|
|
70825
70825
|
},
|
|
70826
70826
|
devDependencies: {
|
|
70827
|
-
"@sockethub/schemas": "^3.0.0-beta.
|
|
70827
|
+
"@sockethub/schemas": "^3.0.0-beta.5"
|
|
70828
70828
|
},
|
|
70829
70829
|
peerDependencies: {
|
|
70830
|
-
"@sockethub/server": "^5.0.0-beta.
|
|
70830
|
+
"@sockethub/server": "^5.0.0-beta.5"
|
|
70831
70831
|
},
|
|
70832
|
-
gitHead: "
|
|
70832
|
+
gitHead: "cc4ffdd9714ebbd8372a88c4f2d795974e04029e",
|
|
70833
70833
|
types: "./dist/index.d.ts"
|
|
70834
70834
|
};
|
|
70835
70835
|
|
|
@@ -71112,6 +71112,30 @@ function isAmazonUrl(url) {
|
|
|
71112
71112
|
return true;
|
|
71113
71113
|
return host.startsWith("amazon.") && AMAZON_STORE_TLDS.has(host.slice(7));
|
|
71114
71114
|
}
|
|
71115
|
+
function resolveAmazonAppLink(url) {
|
|
71116
|
+
if (!isAmazonUrl(url))
|
|
71117
|
+
return null;
|
|
71118
|
+
const parsed = new URL(url);
|
|
71119
|
+
const asin = parsed.pathname.match(/^\/gp\/aw\/d\/([A-Z0-9]{10})(?:\/|$)/i)?.[1];
|
|
71120
|
+
return asin ? `${parsed.origin}/dp/${asin}` : null;
|
|
71121
|
+
}
|
|
71122
|
+
function extractAmazonImage(html) {
|
|
71123
|
+
const tag = html?.match(/<img[^>]*\bid="landingImage"[^>]*>/)?.[0];
|
|
71124
|
+
return tag?.match(/\bdata-old-hires="(https:[^"]+)"/)?.[1];
|
|
71125
|
+
}
|
|
71126
|
+
function schemaOrgSummary(jsonLD, title = "") {
|
|
71127
|
+
if (!Array.isArray(jsonLD))
|
|
71128
|
+
return;
|
|
71129
|
+
const items = jsonLD.flatMap((item) => item?.["@graph"] ?? item);
|
|
71130
|
+
const description = items.find((item) => typeof item?.description === "string" && item.description.trim())?.description;
|
|
71131
|
+
if (description)
|
|
71132
|
+
return description;
|
|
71133
|
+
for (const item of items) {
|
|
71134
|
+
const headline = typeof item?.headline === "string" ? item.headline.trim() : "";
|
|
71135
|
+
if (headline && !title.includes(headline))
|
|
71136
|
+
return headline;
|
|
71137
|
+
}
|
|
71138
|
+
}
|
|
71115
71139
|
function stripFacebookEngagement(title) {
|
|
71116
71140
|
const bar = title?.indexOf("|") ?? -1;
|
|
71117
71141
|
if (title === undefined || bar === -1) {
|
|
@@ -71338,10 +71362,12 @@ function tweetToPageObject(status) {
|
|
|
71338
71362
|
|
|
71339
71363
|
`) : undefined;
|
|
71340
71364
|
const title = typeof article?.title === "string" && article.title || (author.name && author.screen_name ? `${author.name} (@${author.screen_name}) on X` : author.name ?? "Post on X");
|
|
71341
|
-
const
|
|
71342
|
-
const
|
|
71365
|
+
const media = tweet.media?.photos?.length || tweet.media?.videos?.length ? tweet.media : tweet.quote?.media;
|
|
71366
|
+
const photo = media?.photos?.[0];
|
|
71367
|
+
const video = media?.videos?.[0];
|
|
71343
71368
|
const articleImage = article?.cover_media?.media_info;
|
|
71344
|
-
const
|
|
71369
|
+
const card = tweet.card?.image;
|
|
71370
|
+
const imageUrl = articleImage?.original_img_url ?? photo?.url ?? video?.thumbnail_url ?? card?.url ?? author.avatar_url;
|
|
71345
71371
|
const page = {
|
|
71346
71372
|
type: "page",
|
|
71347
71373
|
title,
|
|
@@ -71354,8 +71380,8 @@ function tweetToPageObject(status) {
|
|
|
71354
71380
|
page.image = [
|
|
71355
71381
|
{
|
|
71356
71382
|
url: imageUrl,
|
|
71357
|
-
width: articleImage?.original_img_width ?? photo?.width ?? video?.width,
|
|
71358
|
-
height: articleImage?.original_img_height ?? photo?.height ?? video?.height
|
|
71383
|
+
width: articleImage?.original_img_width ?? photo?.width ?? video?.width ?? card?.width,
|
|
71384
|
+
height: articleImage?.original_img_height ?? photo?.height ?? video?.height ?? card?.height
|
|
71359
71385
|
}
|
|
71360
71386
|
];
|
|
71361
71387
|
}
|
|
@@ -71610,7 +71636,8 @@ class Metadata {
|
|
|
71610
71636
|
this.fetchDirectImage(job, image, cb);
|
|
71611
71637
|
return;
|
|
71612
71638
|
}
|
|
71613
|
-
|
|
71639
|
+
const amazonUrl = resolveAmazonAppLink(job.actor.id);
|
|
71640
|
+
this.scrape(job, cb, undefined, amazonUrl ?? job.actor.id);
|
|
71614
71641
|
}
|
|
71615
71642
|
async fetchDirectImage(job, candidate, cb, timeoutMs = DIRECT_IMAGE_PROBE_TIMEOUT_MS) {
|
|
71616
71643
|
try {
|
|
@@ -71935,6 +71962,10 @@ class Metadata {
|
|
|
71935
71962
|
}
|
|
71936
71963
|
}
|
|
71937
71964
|
}
|
|
71965
|
+
const amazonImage = amazon && !result.ogImage?.[0]?.url?.includes("/images/I/") && extractAmazonImage(data.html);
|
|
71966
|
+
if (amazonImage) {
|
|
71967
|
+
result = { ...result, ogImage: [{ url: amazonImage }] };
|
|
71968
|
+
}
|
|
71938
71969
|
const embed = reddit ? await redditEmbed : undefined;
|
|
71939
71970
|
const youtube = await youtubeEmbed;
|
|
71940
71971
|
if (!reddit)
|
|
@@ -71950,7 +71981,7 @@ class Metadata {
|
|
|
71950
71981
|
language: result.ogLocale,
|
|
71951
71982
|
title: embed?.title ?? youtube?.title ?? (facebook ? stripFacebookEngagement(result.ogTitle) : result.ogTitle),
|
|
71952
71983
|
name: embed?.provider_name ?? youtube?.provider_name ?? siteName,
|
|
71953
|
-
description: normalizeDescription(youtubeDescription || result.ogDescription || ""),
|
|
71984
|
+
description: normalizeDescription(youtubeDescription || result.ogDescription || schemaOrgSummary(result.jsonLD, result.ogTitle) || ""),
|
|
71954
71985
|
image: reddit ? redditPostImages(result.ogImage) ?? redditOEmbedImage(embed ?? {}) : scrapedImages?.length ? scrapedImages : youtubeOEmbedImage(youtube),
|
|
71955
71986
|
url: job.actor.id,
|
|
71956
71987
|
favicon: result.favicon || "/favicon.ico",
|
|
@@ -71995,6 +72026,25 @@ class Metadata {
|
|
|
71995
72026
|
cb(null, job);
|
|
71996
72027
|
return;
|
|
71997
72028
|
}
|
|
72029
|
+
let filename = "";
|
|
72030
|
+
if (/must scrape an html page/i.test(err.message)) {
|
|
72031
|
+
try {
|
|
72032
|
+
filename = decodeURIComponent(new URL(job.actor.id).pathname.split("/").at(-1) || "");
|
|
72033
|
+
} catch {
|
|
72034
|
+
filename = job.actor.id.split(/[?#]/, 1)[0].split("/").at(-1) || "";
|
|
72035
|
+
}
|
|
72036
|
+
}
|
|
72037
|
+
if (filename) {
|
|
72038
|
+
job.object = {
|
|
72039
|
+
type: "page",
|
|
72040
|
+
title: filename,
|
|
72041
|
+
description: "",
|
|
72042
|
+
url: job.actor.id,
|
|
72043
|
+
favicon: "/favicon.ico"
|
|
72044
|
+
};
|
|
72045
|
+
cb(null, job);
|
|
72046
|
+
return;
|
|
72047
|
+
}
|
|
71998
72048
|
cb(markExpectedError(new Error(`metadata scrape failed for ${job.actor.id}: ${err.message}`)));
|
|
71999
72049
|
});
|
|
72000
72050
|
}
|
|
@@ -72011,4 +72061,4 @@ export {
|
|
|
72011
72061
|
withDeadline
|
|
72012
72062
|
};
|
|
72013
72063
|
|
|
72014
|
-
//# debugId=
|
|
72064
|
+
//# debugId=256C32E14BF4C7F264756E2164756E21
|