metanova 0.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/LICENSE +21 -0
- package/README.md +263 -0
- package/USAGE_GUIDE.md +829 -0
- package/dist/index.cjs +3756 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +493 -0
- package/dist/index.d.ts +493 -0
- package/dist/index.js +3674 -0
- package/dist/index.js.map +1 -0
- package/examples/behance.mjs +23 -0
- package/examples/commonjs.cjs +12 -0
- package/examples/custom-adapter.mjs +41 -0
- package/examples/custom-plugin.mjs +26 -0
- package/examples/diagnostics.mjs +17 -0
- package/examples/live-fetch.mjs +21 -0
- package/examples/parse-html.mjs +15 -0
- package/examples/pinterest.mjs +22 -0
- package/examples/preview-card.mjs +11 -0
- package/examples/quick-start.mjs +24 -0
- package/examples/reddit.mjs +23 -0
- package/examples/social-links.mjs +28 -0
- package/examples/social-preview.mjs +21 -0
- package/examples/youtube-playlist.mjs +19 -0
- package/examples/youtube-video.mjs +22 -0
- package/examples/youtube.mjs +22 -0
- package/package.json +70 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createPreviewCard, parseMetadata } from "metanova";
|
|
2
|
+
|
|
3
|
+
const html = `
|
|
4
|
+
<script id="__NEXT_DATA__" type="application/json">
|
|
5
|
+
{
|
|
6
|
+
"props": {
|
|
7
|
+
"pageProps": {
|
|
8
|
+
"project": {
|
|
9
|
+
"projectTitle": "Identity system for MetaNova",
|
|
10
|
+
"description": "A Behance-style project page powered by Next.js data.",
|
|
11
|
+
"owners": [{ "name": "Creative Lab" }],
|
|
12
|
+
"publishedTime": "2026-06-04T11:00:00Z",
|
|
13
|
+
"coverImage": { "url": "https://mir-s3-cdn-cf.behance.net/project-cover.jpg", "width": 1400, "height": 1000 }
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
</script>
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
const metadata = parseMetadata(html, "https://www.behance.net/gallery/123456789/metanova");
|
|
22
|
+
|
|
23
|
+
console.log(createPreviewCard(metadata));
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const { parseMetadata } = require("metanova");
|
|
2
|
+
|
|
3
|
+
const metadata = parseMetadata(
|
|
4
|
+
`<title>CommonJS example</title><meta name="description" content="Loaded with require.">`,
|
|
5
|
+
"https://example.com/commonjs"
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
console.log({
|
|
9
|
+
title: metadata.title,
|
|
10
|
+
type: metadata.type,
|
|
11
|
+
confidence: metadata.confidence
|
|
12
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { parseMetadata } from "metanova";
|
|
2
|
+
|
|
3
|
+
const docsAdapter = {
|
|
4
|
+
name: "docsAdapter",
|
|
5
|
+
detect(url) {
|
|
6
|
+
return url.hostname === "docs.example.com";
|
|
7
|
+
},
|
|
8
|
+
extract({ raw }) {
|
|
9
|
+
return {
|
|
10
|
+
source: "docsAdapter",
|
|
11
|
+
title: raw.openGraph.title,
|
|
12
|
+
description: raw.openGraph.description,
|
|
13
|
+
images: raw.openGraph.images,
|
|
14
|
+
platform: "Example Docs"
|
|
15
|
+
};
|
|
16
|
+
},
|
|
17
|
+
normalize(rawData) {
|
|
18
|
+
return {
|
|
19
|
+
...rawData,
|
|
20
|
+
source: "docsAdapter",
|
|
21
|
+
type: "article",
|
|
22
|
+
siteName: rawData.platform
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const metadata = parseMetadata(`
|
|
28
|
+
<meta property="og:title" content="Adapter example">
|
|
29
|
+
<meta property="og:description" content="Custom site-specific behavior.">
|
|
30
|
+
<meta property="og:image" content="/adapter.jpg">
|
|
31
|
+
<meta property="og:image:width" content="1200">
|
|
32
|
+
<meta property="og:image:height" content="630">
|
|
33
|
+
`, "https://docs.example.com/guides/adapter", {
|
|
34
|
+
adapters: [docsAdapter]
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
console.log({
|
|
38
|
+
type: metadata.type,
|
|
39
|
+
siteName: metadata.siteName,
|
|
40
|
+
title: metadata.title
|
|
41
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { parseMetadata } from "metanova";
|
|
2
|
+
|
|
3
|
+
const docsPlugin = {
|
|
4
|
+
name: "docs-plugin",
|
|
5
|
+
setup(api) {
|
|
6
|
+
api.addExtractor("docs-meta", ({ $ }) => ({
|
|
7
|
+
source: "docs-meta",
|
|
8
|
+
title: $("meta[name='doc:title']").attr("content"),
|
|
9
|
+
siteName: "Docs"
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
api.addImageScorer((image) => (image.url.includes("/hero/") ? 12 : 0));
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const metadata = parseMetadata(
|
|
17
|
+
`<meta name="doc:title" content="Plugin powered docs"><img src="/hero/docs.jpg" width="1200" height="630">`,
|
|
18
|
+
"https://docs.example.com/guide",
|
|
19
|
+
{ plugins: [docsPlugin] }
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
console.log({
|
|
23
|
+
title: metadata.title,
|
|
24
|
+
siteName: metadata.siteName,
|
|
25
|
+
bestImage: metadata.bestImage
|
|
26
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { parseMetadata } from "metanova";
|
|
2
|
+
|
|
3
|
+
const metadata = parseMetadata(`
|
|
4
|
+
<meta property="og:title" content="Diagnostics example">
|
|
5
|
+
<meta property="og:description" content="Inspect trace, confidence, completeness, and image scoring.">
|
|
6
|
+
<meta property="og:image" content="/social-preview.jpg">
|
|
7
|
+
<meta property="og:image:width" content="1200">
|
|
8
|
+
<meta property="og:image:height" content="630">
|
|
9
|
+
`, "https://example.com/post");
|
|
10
|
+
|
|
11
|
+
console.log({
|
|
12
|
+
confidence: metadata.confidence,
|
|
13
|
+
completeness: metadata.completeness,
|
|
14
|
+
bestImage: metadata.bestImage,
|
|
15
|
+
selectedImageReason: metadata.diagnostics.selectedImageReason,
|
|
16
|
+
trace: metadata.diagnostics.trace
|
|
17
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createPreviewCard, fetchMetadata } from "metanova";
|
|
2
|
+
|
|
3
|
+
const url = process.argv[2];
|
|
4
|
+
|
|
5
|
+
if (!url) {
|
|
6
|
+
console.error("Usage: node examples/live-fetch.mjs <url>");
|
|
7
|
+
process.exitCode = 1;
|
|
8
|
+
} else {
|
|
9
|
+
const metadata = await fetchMetadata(url, {
|
|
10
|
+
timeoutMs: 15000,
|
|
11
|
+
maxBytes: 4_000_000
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
console.log(createPreviewCard(metadata));
|
|
15
|
+
console.log({
|
|
16
|
+
confidence: metadata.confidence,
|
|
17
|
+
completeness: metadata.completeness,
|
|
18
|
+
reliability: metadata.reliability,
|
|
19
|
+
diagnostics: metadata.diagnostics
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { parseMetadata } from "metanova";
|
|
2
|
+
|
|
3
|
+
const html = `
|
|
4
|
+
<html>
|
|
5
|
+
<head>
|
|
6
|
+
<title>Parsed HTML</title>
|
|
7
|
+
<meta name="description" content="Parse already-downloaded HTML.">
|
|
8
|
+
<img src="/inline.jpg" width="800" height="450">
|
|
9
|
+
</head>
|
|
10
|
+
</html>
|
|
11
|
+
`;
|
|
12
|
+
|
|
13
|
+
const metadata = parseMetadata(html, "https://example.com/articles/parsed-html");
|
|
14
|
+
|
|
15
|
+
console.log(metadata);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createPreviewCard, parseMetadata } from "metanova";
|
|
2
|
+
|
|
3
|
+
const html = `
|
|
4
|
+
<meta property="og:site_name" content="Pinterest">
|
|
5
|
+
<script id="__PWS_DATA__" type="application/json">
|
|
6
|
+
{
|
|
7
|
+
"props": {
|
|
8
|
+
"pin": {
|
|
9
|
+
"pinTitle": "A clean product moodboard",
|
|
10
|
+
"description": "Pinterest-style embedded pin payload.",
|
|
11
|
+
"pinner": { "name": "Design Studio" },
|
|
12
|
+
"createdAt": "2026-06-04T10:00:00Z",
|
|
13
|
+
"images": [{ "url": "https://i.pinimg.com/originals/pin-cover.jpg", "width": 1200, "height": 1800 }]
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
</script>
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
const metadata = parseMetadata(html, "https://www.pinterest.com/pin/123456789/");
|
|
21
|
+
|
|
22
|
+
console.log(createPreviewCard(metadata));
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createPreviewCard, parseMetadata } from "metanova";
|
|
2
|
+
|
|
3
|
+
const metadata = parseMetadata(`
|
|
4
|
+
<meta property="og:title" content="Preview Card">
|
|
5
|
+
<meta property="og:description" content="Small JSON for bots and apps.">
|
|
6
|
+
<meta property="og:image" content="/preview-card.jpg">
|
|
7
|
+
<meta property="og:image:width" content="1200">
|
|
8
|
+
<meta property="og:image:height" content="630">
|
|
9
|
+
`, "https://example.com/preview-card");
|
|
10
|
+
|
|
11
|
+
console.log(createPreviewCard(metadata));
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { createPreviewCard, fetchMetadata } from "metanova";
|
|
2
|
+
|
|
3
|
+
const html = `
|
|
4
|
+
<html>
|
|
5
|
+
<head>
|
|
6
|
+
<meta property="og:title" content="MetaNova Quick Start">
|
|
7
|
+
<meta property="og:description" content="A fast metadata extraction example.">
|
|
8
|
+
<meta property="og:image" content="http://127.0.0.1/cover.jpg">
|
|
9
|
+
<meta property="og:image:width" content="1200">
|
|
10
|
+
<meta property="og:image:height" content="630">
|
|
11
|
+
<link rel="canonical" href="http://127.0.0.1/posts/quick-start">
|
|
12
|
+
</head>
|
|
13
|
+
</html>
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
const metadata = await fetchMetadata("http://127.0.0.1/posts/quick-start", {
|
|
17
|
+
allowLocalhost: true,
|
|
18
|
+
fetch: async () => new Response(html, {
|
|
19
|
+
status: 200,
|
|
20
|
+
headers: { "content-type": "text/html; charset=utf-8" }
|
|
21
|
+
})
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
console.log(createPreviewCard(metadata));
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createPreviewCard, parseMetadata } from "metanova";
|
|
2
|
+
|
|
3
|
+
const html = `
|
|
4
|
+
<title>Fallback Reddit title</title>
|
|
5
|
+
<meta property="og:site_name" content="Reddit">
|
|
6
|
+
<script>
|
|
7
|
+
window.__INITIAL_STATE__ = {
|
|
8
|
+
"post": {
|
|
9
|
+
"title": "MetaNova real world extraction",
|
|
10
|
+
"description": "A Reddit-style post with useful embedded data.",
|
|
11
|
+
"author": { "name": "u/metanova" },
|
|
12
|
+
"createdAt": "2026-06-04T09:00:00Z",
|
|
13
|
+
"previewImage": "https://preview.redd.it/metanova-card.jpg",
|
|
14
|
+
"media": { "videoUrl": "https://v.redd.it/metanova/DASH_720.mp4" }
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
</script>
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
const metadata = parseMetadata(html, "https://www.reddit.com/r/typescript/comments/abc123/metanova/");
|
|
21
|
+
|
|
22
|
+
console.log(createPreviewCard(metadata));
|
|
23
|
+
console.log(metadata.diagnostics.trace);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { parseMetadata } from "metanova";
|
|
2
|
+
|
|
3
|
+
const html = `
|
|
4
|
+
<meta property="og:title" content="Platform post">
|
|
5
|
+
<meta property="og:description" content="A public social or media page.">
|
|
6
|
+
<meta property="og:image" content="/cover.jpg">
|
|
7
|
+
<meta property="og:image:width" content="1200">
|
|
8
|
+
<meta property="og:image:height" content="630">
|
|
9
|
+
`;
|
|
10
|
+
|
|
11
|
+
const urls = [
|
|
12
|
+
"https://www.reddit.com/r/typescript/comments/abc123/metanova/",
|
|
13
|
+
"https://www.pinterest.com/pin/123456789/",
|
|
14
|
+
"https://www.behance.net/gallery/123456789/project",
|
|
15
|
+
"https://youtu.be/dQw4w9WgXcQ",
|
|
16
|
+
"https://x.com/example/status/1234567890",
|
|
17
|
+
"https://www.instagram.com/p/ABC123/"
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
for (const url of urls) {
|
|
21
|
+
const metadata = parseMetadata(html, url);
|
|
22
|
+
console.log({
|
|
23
|
+
url,
|
|
24
|
+
type: metadata.type,
|
|
25
|
+
siteName: metadata.siteName,
|
|
26
|
+
bestImage: metadata.bestImage
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createPreviewCard, fetchMetadata } from "metanova";
|
|
2
|
+
|
|
3
|
+
const url = process.argv[2];
|
|
4
|
+
|
|
5
|
+
if (!url) {
|
|
6
|
+
console.error("Usage: node examples/social-preview.mjs <social-url>");
|
|
7
|
+
process.exitCode = 1;
|
|
8
|
+
} else {
|
|
9
|
+
const metadata = await fetchMetadata(url, {
|
|
10
|
+
timeoutMs: 15000,
|
|
11
|
+
maxBytes: 4_000_000
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
console.log(createPreviewCard(metadata));
|
|
15
|
+
console.log({
|
|
16
|
+
sources: metadata.sources,
|
|
17
|
+
adapter: metadata.diagnostics.adapter,
|
|
18
|
+
trace: metadata.diagnostics.trace,
|
|
19
|
+
selectedImageReason: metadata.diagnostics.selectedImageReason
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { fetchMetadata } from "metanova";
|
|
2
|
+
|
|
3
|
+
const url = process.argv[2];
|
|
4
|
+
|
|
5
|
+
if (!url) {
|
|
6
|
+
console.error("Usage: node examples/youtube-playlist.mjs <youtube-playlist-or-watch-url>");
|
|
7
|
+
process.exitCode = 1;
|
|
8
|
+
} else {
|
|
9
|
+
const metadata = await fetchMetadata(url, {
|
|
10
|
+
timeoutMs: 15000,
|
|
11
|
+
maxBytes: 5_000_000
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
console.log({
|
|
15
|
+
type: metadata.type,
|
|
16
|
+
playlist: metadata.playlist,
|
|
17
|
+
diagnostics: metadata.diagnostics
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { fetchMetadata } from "metanova";
|
|
2
|
+
|
|
3
|
+
const url = process.argv[2];
|
|
4
|
+
|
|
5
|
+
if (!url) {
|
|
6
|
+
console.error("Usage: node examples/youtube-video.mjs <youtube-video-url>");
|
|
7
|
+
process.exitCode = 1;
|
|
8
|
+
} else {
|
|
9
|
+
const metadata = await fetchMetadata(url, {
|
|
10
|
+
timeoutMs: 15000,
|
|
11
|
+
maxBytes: 5_000_000
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
console.log({
|
|
15
|
+
title: metadata.title,
|
|
16
|
+
channel: metadata.video?.channel ?? metadata.author,
|
|
17
|
+
video: metadata.video,
|
|
18
|
+
bestImage: metadata.bestImage,
|
|
19
|
+
confidence: metadata.confidence,
|
|
20
|
+
diagnostics: metadata.diagnostics
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createPreviewCard, parseMetadata } from "metanova";
|
|
2
|
+
|
|
3
|
+
const html = `
|
|
4
|
+
<meta property="og:site_name" content="YouTube">
|
|
5
|
+
<script>
|
|
6
|
+
window.__INITIAL_STATE__ = {
|
|
7
|
+
"videoDetails": {
|
|
8
|
+
"title": "Building MetaNova",
|
|
9
|
+
"description": "A YouTube-style payload without relying only on OG tags.",
|
|
10
|
+
"ownerChannelName": "MetaNova Labs",
|
|
11
|
+
"uploadDate": "2026-06-04T12:00:00Z",
|
|
12
|
+
"contentUrl": "https://www.youtube.com/embed/dQw4w9WgXcQ",
|
|
13
|
+
"thumbnail": { "url": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg", "width": 1280, "height": 720 }
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
</script>
|
|
17
|
+
`;
|
|
18
|
+
|
|
19
|
+
const metadata = parseMetadata(html, "https://youtu.be/dQw4w9WgXcQ");
|
|
20
|
+
|
|
21
|
+
console.log(createPreviewCard(metadata));
|
|
22
|
+
console.log(metadata.videos);
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "metanova",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A modular TypeScript metadata extraction and normalization library for web pages and public URLs.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md",
|
|
19
|
+
"USAGE_GUIDE.md",
|
|
20
|
+
"LICENSE",
|
|
21
|
+
"examples"
|
|
22
|
+
],
|
|
23
|
+
"sideEffects": false,
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --sourcemap --clean",
|
|
26
|
+
"typecheck": "tsc --noEmit",
|
|
27
|
+
"test": "vitest run",
|
|
28
|
+
"lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\" \"examples/**/*.mjs\" \"examples/**/*.cjs\""
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"metadata",
|
|
32
|
+
"open-graph",
|
|
33
|
+
"twitter-cards",
|
|
34
|
+
"json-ld",
|
|
35
|
+
"schema.org",
|
|
36
|
+
"oembed",
|
|
37
|
+
"link-preview",
|
|
38
|
+
"scraping",
|
|
39
|
+
"preview-card",
|
|
40
|
+
"social-media",
|
|
41
|
+
"nextjs",
|
|
42
|
+
"metadata-extraction",
|
|
43
|
+
"ssrf-protection"
|
|
44
|
+
],
|
|
45
|
+
"author": "MetaNova Contributors",
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "git+https://github.com/xredspinel-coder/MetaNova.git"
|
|
49
|
+
},
|
|
50
|
+
"homepage": "https://github.com/xredspinel-coder/MetaNova#readme",
|
|
51
|
+
"bugs": {
|
|
52
|
+
"url": "https://github.com/xredspinel-coder/MetaNova/issues"
|
|
53
|
+
},
|
|
54
|
+
"license": "MIT",
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=18.18"
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"cheerio": "^1.1.2"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@eslint/js": "^9.39.1",
|
|
63
|
+
"@types/node": "^24.10.1",
|
|
64
|
+
"eslint": "^9.39.1",
|
|
65
|
+
"tsup": "^8.5.0",
|
|
66
|
+
"typescript": "^5.9.3",
|
|
67
|
+
"typescript-eslint": "^8.46.4",
|
|
68
|
+
"vitest": "^4.0.8"
|
|
69
|
+
}
|
|
70
|
+
}
|