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.
@@ -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
+ }