rehype-og-card 0.2.1 → 0.3.1
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 +29 -15
- package/dist/index.d.ts +1 -1
- package/dist/index.js +27 -5
- package/dist/openGraphUtil.d.ts +31 -0
- package/dist/openGraphUtil.js +91 -0
- package/dist/types.d.ts +57 -0
- package/dist/types.js +1 -0
- package/dist/util/cache.d.ts +13 -0
- package/dist/util/cache.js +52 -0
- package/dist/util.d.ts +23 -75
- package/dist/util.js +30 -94
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,20 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
Rehype plugin to convert text links to link cards inspired by [remark-link-card](https://github.com/gladevise/remark-link-card). Bare links are converted to link cards, but not links in lists or non-bare links.
|
|
4
4
|
|
|
5
|
-
| Features | remark-link-card |
|
|
6
|
-
| :--------------------------------------------- | :---------------------------------------------------------------------------------------: |
|
|
7
|
-
| Plugin type | Remark |
|
|
8
|
-
| TypeScript support | ❌ |
|
|
9
|
-
| Convert bare links to link cards | ✅ |
|
|
10
|
-
| Shorten URLs displayed on link cards | ✅ |
|
|
11
|
-
| Server-side cache for images | ✅ |
|
|
12
|
-
| Cache images with long URLs | ❌ ([implemented](https://github.com/gladevise/remark-link-card/pull/16) but not released) |
|
|
13
|
-
| Build cache for Open Graph metadata and images | ❌ |
|
|
14
|
-
| Exclude links in lists from conversion | ❌ |
|
|
15
|
-
| Exclude specific domains from conversion | ❌ |
|
|
16
|
-
| Lazy loading images | ❌ |
|
|
17
|
-
| Async decoding images | ❌ |
|
|
18
|
-
| Customize the crawler's user agent string | ❌ |
|
|
5
|
+
| Features | remark-link-card | rehype-og-card |
|
|
6
|
+
| :--------------------------------------------- | :---------------------------------------------------------------------------------------: | :------------: |
|
|
7
|
+
| Plugin type | Remark | Rehype |
|
|
8
|
+
| TypeScript support | ❌ | ✅ |
|
|
9
|
+
| Convert bare links to link cards | ✅ | ✅ |
|
|
10
|
+
| Shorten URLs displayed on link cards | ✅ | ✅ |
|
|
11
|
+
| Server-side cache for images | ✅ | ✅ |
|
|
12
|
+
| Cache images with long URLs | ❌ ([implemented](https://github.com/gladevise/remark-link-card/pull/16) but not released) | ✅ |
|
|
13
|
+
| Build cache for Open Graph metadata and images | ❌ | ✅ (image only) |
|
|
14
|
+
| Exclude links in lists from conversion | ❌ | ✅ |
|
|
15
|
+
| Exclude specific domains from conversion | ❌ | ✅ |
|
|
16
|
+
| Lazy loading images | ❌ | ✅ |
|
|
17
|
+
| Async decoding images | ❌ | ✅ |
|
|
18
|
+
| Customize the crawler's user agent string | ❌ | ✅ |
|
|
19
19
|
|
|
20
20
|
## Installation
|
|
21
21
|
|
|
@@ -101,6 +101,20 @@ The following links will NOT be converted to link cards:
|
|
|
101
101
|
|
|
102
102
|
All options are optional.
|
|
103
103
|
|
|
104
|
+
### `buildCache`
|
|
105
|
+
|
|
106
|
+
Whether to cache the OG metadata and images for the next build. `buildCache` option requires `serverCache` option to be enabled.
|
|
107
|
+
|
|
108
|
+
- Type: `boolean`
|
|
109
|
+
- Default: `false`
|
|
110
|
+
|
|
111
|
+
### `buildCachePath`
|
|
112
|
+
|
|
113
|
+
Path to save the build cache. For Astro projects, specify the `./node_modules/.astro` directory.
|
|
114
|
+
|
|
115
|
+
- Type: `string`
|
|
116
|
+
- Default: `"./.cache/og-card"`
|
|
117
|
+
|
|
104
118
|
### `crawlerUserAgent`
|
|
105
119
|
|
|
106
120
|
User agent to use for fetching the OG metadata and images.
|
|
@@ -145,7 +159,7 @@ Whether to cache the OG data on the server.
|
|
|
145
159
|
|
|
146
160
|
### `serverCachePath`
|
|
147
161
|
|
|
148
|
-
Path to save the server cache.
|
|
162
|
+
Path to save the server cache. Specify the directory whose contents will be copied to the root. For example, for an Astro project, specify the `public` directory.
|
|
149
163
|
|
|
150
164
|
- Type: `string`
|
|
151
165
|
- Default: `"./public"`
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -7,11 +7,15 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
import { convertTextToAnchorElement,
|
|
10
|
+
import { convertTextToAnchorElement, downloadImage, isAnchorElement, isTextNode, isValidURL } from "./util.js";
|
|
11
|
+
import { createOGCard, getOGData } from "./openGraphUtil.js";
|
|
12
|
+
import { restoreBuildCache, saveBuildCache } from "./util/cache.js";
|
|
11
13
|
import { isElement } from "hast-util-is-element";
|
|
12
14
|
import path from "path";
|
|
13
15
|
import { visitParents } from "unist-util-visit-parents";
|
|
14
16
|
const DEFAULT_OPTIONS = {
|
|
17
|
+
buildCache: false,
|
|
18
|
+
buildCachePath: "./node_modules/.cache",
|
|
15
19
|
crawlerUserAgent:
|
|
16
20
|
// eslint-disable-next-line max-len
|
|
17
21
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
|
|
@@ -31,13 +35,20 @@ const DEFAULT_OPTIONS = {
|
|
|
31
35
|
// eslint-disable-next-line max-lines-per-function
|
|
32
36
|
const rehypeOGCard = (options) => {
|
|
33
37
|
const mergedOptions = Object.assign(Object.assign({}, DEFAULT_OPTIONS), options);
|
|
38
|
+
if (mergedOptions.buildCache && !mergedOptions.serverCache) {
|
|
39
|
+
throw new Error("[rehype-og-card] `buildCache` option requires `serverCache` option to be enabled.");
|
|
40
|
+
}
|
|
34
41
|
mergedOptions.serverCachePath = path.posix.join(mergedOptions.serverCachePath, "./rehype-og-card/");
|
|
42
|
+
mergedOptions.buildCachePath = path.posix.join(mergedOptions.buildCachePath, "./rehype-og-card/");
|
|
35
43
|
/**
|
|
36
44
|
* Transform function to create OG card from bare links.
|
|
37
45
|
* @param tree Root node of the HAST tree.
|
|
38
46
|
*/
|
|
39
47
|
// eslint-disable-next-line max-lines-per-function
|
|
40
48
|
const transform = (tree) => __awaiter(void 0, void 0, void 0, function* () {
|
|
49
|
+
if (mergedOptions.buildCache) {
|
|
50
|
+
yield restoreBuildCache(mergedOptions.buildCachePath, mergedOptions.serverCachePath);
|
|
51
|
+
}
|
|
41
52
|
const linkCardPromises = [];
|
|
42
53
|
// eslint-disable-next-line max-statements, max-lines-per-function
|
|
43
54
|
visitParents(tree, ["element", "text"], (node, ancestors) => {
|
|
@@ -80,14 +91,22 @@ const rehypeOGCard = (options) => {
|
|
|
80
91
|
if (!OGData)
|
|
81
92
|
return;
|
|
82
93
|
if (OGData.OGImageURL && mergedOptions.serverCache) {
|
|
83
|
-
const filename = yield downloadImage(
|
|
84
|
-
|
|
94
|
+
const filename = yield downloadImage({
|
|
95
|
+
directly: mergedOptions.serverCachePath,
|
|
96
|
+
url: OGData.OGImageURL,
|
|
97
|
+
userAgent: mergedOptions.crawlerUserAgent
|
|
98
|
+
});
|
|
99
|
+
if (filename && mergedOptions.serverCache) {
|
|
85
100
|
OGData.OGImageURL = path.posix.join("/rehype-og-card", filename);
|
|
86
101
|
}
|
|
87
102
|
}
|
|
88
103
|
if (OGData.faviconURL && mergedOptions.serverCache) {
|
|
89
|
-
const filename = yield downloadImage(
|
|
90
|
-
|
|
104
|
+
const filename = yield downloadImage({
|
|
105
|
+
directly: mergedOptions.serverCachePath,
|
|
106
|
+
url: OGData.faviconURL,
|
|
107
|
+
userAgent: mergedOptions.crawlerUserAgent
|
|
108
|
+
});
|
|
109
|
+
if (filename && mergedOptions.serverCache) {
|
|
91
110
|
OGData.faviconURL = path.posix.join("/rehype-og-card", filename);
|
|
92
111
|
}
|
|
93
112
|
}
|
|
@@ -107,6 +126,9 @@ const rehypeOGCard = (options) => {
|
|
|
107
126
|
linkCardPromises.push(linkCardPromise());
|
|
108
127
|
});
|
|
109
128
|
yield Promise.all(linkCardPromises);
|
|
129
|
+
if (mergedOptions.buildCache) {
|
|
130
|
+
yield saveBuildCache(mergedOptions.serverCachePath, mergedOptions.buildCachePath);
|
|
131
|
+
}
|
|
110
132
|
});
|
|
111
133
|
return transform;
|
|
112
134
|
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Element } from "hast";
|
|
2
|
+
import { RehypeOGCardOptions } from "./types.js";
|
|
3
|
+
interface OGCardData {
|
|
4
|
+
url: string;
|
|
5
|
+
title: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
displayURL: string;
|
|
8
|
+
faviconURL?: string;
|
|
9
|
+
OGImageURL?: string;
|
|
10
|
+
OGImageAlt?: string;
|
|
11
|
+
OGImageWidth?: number;
|
|
12
|
+
OGImageHeight?: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Get OG card data from given URL.
|
|
16
|
+
* @param url URL to get OG card data.
|
|
17
|
+
* @param userAgent User agent to use for fetching.
|
|
18
|
+
* @returns OG card data.
|
|
19
|
+
*/
|
|
20
|
+
declare const getOGData: (url: string, userAgent: string) => Promise<OGCardData | null>;
|
|
21
|
+
/**
|
|
22
|
+
* Create OG card element from given data.
|
|
23
|
+
* @param data Data to create OG card.
|
|
24
|
+
* @param options Options to create OG card.
|
|
25
|
+
* @returns OG card element.
|
|
26
|
+
*/
|
|
27
|
+
declare const createOGCard: (data: OGCardData, options: Required<{
|
|
28
|
+
loading: RehypeOGCardOptions["loading"];
|
|
29
|
+
decoding: RehypeOGCardOptions["decoding"];
|
|
30
|
+
}>) => Element;
|
|
31
|
+
export { OGCardData, createOGCard, getOGData };
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { h } from "hastscript";
|
|
11
|
+
import scraper from "open-graph-scraper";
|
|
12
|
+
/**
|
|
13
|
+
* Get OG card data from given URL.
|
|
14
|
+
* @param url URL to get OG card data.
|
|
15
|
+
* @param userAgent User agent to use for fetching.
|
|
16
|
+
* @returns OG card data.
|
|
17
|
+
*/
|
|
18
|
+
const getOGData = (url, userAgent) => __awaiter(void 0, void 0, void 0, function* () {
|
|
19
|
+
try {
|
|
20
|
+
const { result } = yield scraper({
|
|
21
|
+
fetchOptions: {
|
|
22
|
+
headers: {
|
|
23
|
+
"user-agent": userAgent
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
url
|
|
27
|
+
});
|
|
28
|
+
const OGImage = result.ogImage ? result.ogImage[0] : null;
|
|
29
|
+
const faviconURL = result.favicon
|
|
30
|
+
? `https://www.google.com/s2/favicons?domain=${new URL(url).hostname}`
|
|
31
|
+
: // eslint-disable-next-line no-undefined
|
|
32
|
+
undefined;
|
|
33
|
+
return {
|
|
34
|
+
OGImageAlt: OGImage === null || OGImage === void 0 ? void 0 : OGImage.alt,
|
|
35
|
+
OGImageHeight: OGImage === null || OGImage === void 0 ? void 0 : OGImage.height,
|
|
36
|
+
OGImageURL: OGImage === null || OGImage === void 0 ? void 0 : OGImage.url,
|
|
37
|
+
OGImageWidth: OGImage === null || OGImage === void 0 ? void 0 : OGImage.width,
|
|
38
|
+
description: result.ogDescription,
|
|
39
|
+
displayURL: url,
|
|
40
|
+
faviconURL,
|
|
41
|
+
title: result.ogTitle || url,
|
|
42
|
+
url
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
// eslint-disable-next-line no-console
|
|
47
|
+
console.error("[rehype-og-card] Error fetching OG data:", error);
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
/**
|
|
52
|
+
* Create OG card element from given data.
|
|
53
|
+
* @param data Data to create OG card.
|
|
54
|
+
* @param options Options to create OG card.
|
|
55
|
+
* @returns OG card element.
|
|
56
|
+
*/
|
|
57
|
+
const createOGCard = (data, options) => {
|
|
58
|
+
const card = h("div.og-card-container", [
|
|
59
|
+
h("a", { href: data.url }, [
|
|
60
|
+
h("div.og-card-info", [
|
|
61
|
+
h("div.og-card-title", data.title),
|
|
62
|
+
data.description ? h("div.og-card-description", data.description) : null,
|
|
63
|
+
h("div.og-card-url-container", [
|
|
64
|
+
h("img.og-card-favicon", {
|
|
65
|
+
alt: "favicon",
|
|
66
|
+
decoding: options.decoding,
|
|
67
|
+
height: 16,
|
|
68
|
+
loading: options.loading,
|
|
69
|
+
src: data.faviconURL,
|
|
70
|
+
width: 16
|
|
71
|
+
}),
|
|
72
|
+
h("span.og-card-url", data.displayURL)
|
|
73
|
+
])
|
|
74
|
+
]),
|
|
75
|
+
data.OGImageURL
|
|
76
|
+
? h("div.og-card-image-container", [
|
|
77
|
+
h("img.og-card-image", {
|
|
78
|
+
alt: data.OGImageAlt || data.OGImageURL,
|
|
79
|
+
decoding: options.decoding,
|
|
80
|
+
height: data.OGImageHeight,
|
|
81
|
+
loading: options.loading,
|
|
82
|
+
src: data.OGImageURL,
|
|
83
|
+
width: data.OGImageWidth
|
|
84
|
+
})
|
|
85
|
+
])
|
|
86
|
+
: null
|
|
87
|
+
])
|
|
88
|
+
]);
|
|
89
|
+
return card;
|
|
90
|
+
};
|
|
91
|
+
export { createOGCard, getOGData };
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
interface RehypeOGCardOptions {
|
|
2
|
+
/**
|
|
3
|
+
* Domains to exclude from conversion.
|
|
4
|
+
*/
|
|
5
|
+
excludeDomains?: string[];
|
|
6
|
+
/**
|
|
7
|
+
* `decoding` attribute of the image element.
|
|
8
|
+
* @default "async"
|
|
9
|
+
*/
|
|
10
|
+
decoding?: HTMLImageElement["decoding"];
|
|
11
|
+
/**
|
|
12
|
+
* `loading` attribute of the image element.
|
|
13
|
+
* @default "lazy"
|
|
14
|
+
*/
|
|
15
|
+
loading?: HTMLImageElement["loading"];
|
|
16
|
+
/**
|
|
17
|
+
* Whether to shorten the displayed URL.
|
|
18
|
+
* @default true
|
|
19
|
+
*/
|
|
20
|
+
shortenURL?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Whether to enable converting the links
|
|
23
|
+
* that have the same URL and text (e.g., `[https://example.com](https://example.com)`).
|
|
24
|
+
* @default false
|
|
25
|
+
*/
|
|
26
|
+
enableSameTextURLConversion?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Whether to cache the OG data on the server.
|
|
29
|
+
* @default true
|
|
30
|
+
*/
|
|
31
|
+
serverCache?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Path to save the server cache.
|
|
34
|
+
* Specify the directory whose contents will be copied to the root.
|
|
35
|
+
* For example, for an Astro project, specify the `public` directory.
|
|
36
|
+
* @default "./public"
|
|
37
|
+
*/
|
|
38
|
+
serverCachePath?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Whether to cache the OG metadata and images for the next build.
|
|
41
|
+
* `buildCache` option requires `serverCache` option to be enabled.
|
|
42
|
+
* @default false
|
|
43
|
+
*/
|
|
44
|
+
buildCache?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Path to save the build cache.
|
|
47
|
+
* For Astro projects, specify the `./node_modules/.astro` directory.
|
|
48
|
+
* @default "./node_modules/.cache"
|
|
49
|
+
*/
|
|
50
|
+
buildCachePath?: string;
|
|
51
|
+
/**
|
|
52
|
+
* User agent to use for fetching the OG metadata and images.
|
|
53
|
+
* @default "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
|
|
54
|
+
*/
|
|
55
|
+
crawlerUserAgent?: string;
|
|
56
|
+
}
|
|
57
|
+
export { RehypeOGCardOptions };
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Save build cache if server cache exists.
|
|
3
|
+
* @param serverCachePath Server cache path.
|
|
4
|
+
* @param buildCachePath Build cache path.
|
|
5
|
+
*/
|
|
6
|
+
declare const saveBuildCache: (serverCachePath: string, buildCachePath: string) => Promise<void>;
|
|
7
|
+
/**
|
|
8
|
+
* Restore build cache if it exists.
|
|
9
|
+
* @param buildCachePath Build cache path.
|
|
10
|
+
* @param serverCachePath Server cache path.
|
|
11
|
+
*/
|
|
12
|
+
declare const restoreBuildCache: (buildCachePath: string, serverCachePath: string) => Promise<void>;
|
|
13
|
+
export { saveBuildCache, restoreBuildCache };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { checkFileExists } from "../util.js";
|
|
11
|
+
import fs from "fs/promises";
|
|
12
|
+
import path from "path";
|
|
13
|
+
/**
|
|
14
|
+
* Copy directory from source to destination.
|
|
15
|
+
* @param source Source directory.
|
|
16
|
+
* @param destination Destination directory.
|
|
17
|
+
*/
|
|
18
|
+
// eslint-disable-next-line max-statements
|
|
19
|
+
const copyDirectory = (source, destination) => __awaiter(void 0, void 0, void 0, function* () {
|
|
20
|
+
const sourceExists = yield checkFileExists(source);
|
|
21
|
+
if (!sourceExists)
|
|
22
|
+
return;
|
|
23
|
+
const destinationExists = yield checkFileExists(destination);
|
|
24
|
+
if (!destinationExists) {
|
|
25
|
+
yield fs.mkdir(destination, { recursive: true });
|
|
26
|
+
}
|
|
27
|
+
const files = yield fs.readdir(source);
|
|
28
|
+
const promises = [];
|
|
29
|
+
for (const file of files) {
|
|
30
|
+
const sourceFile = path.join(source, file);
|
|
31
|
+
const destFile = path.join(destination, file);
|
|
32
|
+
promises.push(fs.copyFile(sourceFile, destFile));
|
|
33
|
+
}
|
|
34
|
+
yield Promise.all(promises);
|
|
35
|
+
});
|
|
36
|
+
/**
|
|
37
|
+
* Save build cache if server cache exists.
|
|
38
|
+
* @param serverCachePath Server cache path.
|
|
39
|
+
* @param buildCachePath Build cache path.
|
|
40
|
+
*/
|
|
41
|
+
const saveBuildCache = (serverCachePath, buildCachePath) => __awaiter(void 0, void 0, void 0, function* () {
|
|
42
|
+
yield copyDirectory(serverCachePath, buildCachePath);
|
|
43
|
+
});
|
|
44
|
+
/**
|
|
45
|
+
* Restore build cache if it exists.
|
|
46
|
+
* @param buildCachePath Build cache path.
|
|
47
|
+
* @param serverCachePath Server cache path.
|
|
48
|
+
*/
|
|
49
|
+
const restoreBuildCache = (buildCachePath, serverCachePath) => __awaiter(void 0, void 0, void 0, function* () {
|
|
50
|
+
yield copyDirectory(buildCachePath, serverCachePath);
|
|
51
|
+
});
|
|
52
|
+
export { saveBuildCache, restoreBuildCache };
|
package/dist/util.d.ts
CHANGED
|
@@ -1,46 +1,4 @@
|
|
|
1
1
|
import type { Element, ElementContent, Text } from "hast";
|
|
2
|
-
interface RehypeOGCardOptions {
|
|
3
|
-
/**
|
|
4
|
-
* Domains to exclude from conversion.
|
|
5
|
-
*/
|
|
6
|
-
excludeDomains?: string[];
|
|
7
|
-
/**
|
|
8
|
-
* `decoding` attribute of the image element.
|
|
9
|
-
* @default "async"
|
|
10
|
-
*/
|
|
11
|
-
decoding?: HTMLImageElement["decoding"];
|
|
12
|
-
/**
|
|
13
|
-
* `loading` attribute of the image element.
|
|
14
|
-
* @default "lazy"
|
|
15
|
-
*/
|
|
16
|
-
loading?: HTMLImageElement["loading"];
|
|
17
|
-
/**
|
|
18
|
-
* Whether to shorten the displayed URL.
|
|
19
|
-
* @default true
|
|
20
|
-
*/
|
|
21
|
-
shortenURL?: boolean;
|
|
22
|
-
/**
|
|
23
|
-
* Whether to enable converting the links
|
|
24
|
-
* that have the same URL and text (e.g., `[https://example.com](https://example.com)`).
|
|
25
|
-
* @default false
|
|
26
|
-
*/
|
|
27
|
-
enableSameTextURLConversion?: boolean;
|
|
28
|
-
/**
|
|
29
|
-
* Whether to cache the OG data on the server.
|
|
30
|
-
* @default true
|
|
31
|
-
*/
|
|
32
|
-
serverCache?: boolean;
|
|
33
|
-
/**
|
|
34
|
-
* Path to save the server cache.
|
|
35
|
-
* @default "./public"
|
|
36
|
-
*/
|
|
37
|
-
serverCachePath?: string;
|
|
38
|
-
/**
|
|
39
|
-
* User agent to use for fetching the OG metadata and images.
|
|
40
|
-
* @default "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
|
|
41
|
-
*/
|
|
42
|
-
crawlerUserAgent?: string;
|
|
43
|
-
}
|
|
44
2
|
interface AnchorElement extends Element {
|
|
45
3
|
tagName: "a";
|
|
46
4
|
properties: {
|
|
@@ -65,46 +23,36 @@ declare const isAnchorElement: (node: unknown) => node is AnchorElement;
|
|
|
65
23
|
* @returns `true` if the URL is valid, `false` otherwise.
|
|
66
24
|
*/
|
|
67
25
|
declare const isValidURL: (url: string) => boolean;
|
|
68
|
-
interface OGCardData {
|
|
69
|
-
url: string;
|
|
70
|
-
title: string;
|
|
71
|
-
description?: string;
|
|
72
|
-
displayURL: string;
|
|
73
|
-
faviconURL?: string;
|
|
74
|
-
OGImageURL?: string;
|
|
75
|
-
OGImageAlt?: string;
|
|
76
|
-
OGImageWidth?: number;
|
|
77
|
-
OGImageHeight?: number;
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Get OG card data from given URL.
|
|
81
|
-
* @param url URL to get OG card data.
|
|
82
|
-
* @param userAgent User agent to use for fetching.
|
|
83
|
-
* @returns OG card data.
|
|
84
|
-
*/
|
|
85
|
-
declare const getOGData: (url: string, userAgent: string) => Promise<OGCardData | null>;
|
|
86
|
-
/**
|
|
87
|
-
* Create OG card element from given data.
|
|
88
|
-
* @param data Data to create OG card.
|
|
89
|
-
* @param options Options to create OG card.
|
|
90
|
-
* @returns OG card element.
|
|
91
|
-
*/
|
|
92
|
-
declare const createOGCard: (data: OGCardData, options: Required<{
|
|
93
|
-
loading: RehypeOGCardOptions["loading"];
|
|
94
|
-
decoding: RehypeOGCardOptions["decoding"];
|
|
95
|
-
}>) => Element;
|
|
96
26
|
/**
|
|
97
27
|
* Convert text node to anchor element. **This function does not check if the text is a valid URL.**
|
|
98
28
|
* @param text Text node to convert.
|
|
99
29
|
* @returns Anchor element.
|
|
100
30
|
*/
|
|
101
31
|
declare const convertTextToAnchorElement: (text: Text) => AnchorElement;
|
|
32
|
+
/**
|
|
33
|
+
* Check if the file exists.
|
|
34
|
+
* @param filePath Path to check.
|
|
35
|
+
* @returns `true` if the file exists, `false` otherwise.
|
|
36
|
+
*/
|
|
37
|
+
declare const checkFileExists: (filePath: string) => Promise<boolean>;
|
|
38
|
+
interface DownloadImageOptions {
|
|
39
|
+
/**
|
|
40
|
+
* URL to download image.
|
|
41
|
+
*/
|
|
42
|
+
url: string;
|
|
43
|
+
/**
|
|
44
|
+
* Directory to save the downloaded image.
|
|
45
|
+
*/
|
|
46
|
+
directly: string;
|
|
47
|
+
/**
|
|
48
|
+
* User agent to use for fetching the image.
|
|
49
|
+
*/
|
|
50
|
+
userAgent: string;
|
|
51
|
+
}
|
|
102
52
|
/**
|
|
103
53
|
* Download image from given URL.
|
|
104
|
-
* @param
|
|
105
|
-
* @param directly Path to save the image.
|
|
106
|
-
* @param userAgent User agent to use for fetching.
|
|
54
|
+
* @param options Options to download image.
|
|
107
55
|
* @returns Path to the downloaded image.
|
|
108
56
|
*/
|
|
109
|
-
declare const downloadImage: (
|
|
110
|
-
export { AnchorElement,
|
|
57
|
+
declare const downloadImage: (options: DownloadImageOptions) => Promise<string | null>;
|
|
58
|
+
export { AnchorElement, isTextNode, isAnchorElement, isValidURL, convertTextToAnchorElement, checkFileExists, downloadImage };
|
package/dist/util.js
CHANGED
|
@@ -9,10 +9,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
};
|
|
10
10
|
import { createHash } from "crypto";
|
|
11
11
|
import fs from "fs/promises";
|
|
12
|
-
import { h } from "hastscript";
|
|
13
12
|
import { isElement } from "hast-util-is-element";
|
|
14
13
|
import path from "path";
|
|
15
|
-
import scraper from "open-graph-scraper";
|
|
16
14
|
/**
|
|
17
15
|
* Check if the node is a text node.
|
|
18
16
|
* @param node Node to check.
|
|
@@ -40,85 +38,6 @@ const isValidURL = (url) => {
|
|
|
40
38
|
return false;
|
|
41
39
|
}
|
|
42
40
|
};
|
|
43
|
-
/**
|
|
44
|
-
* Get OG card data from given URL.
|
|
45
|
-
* @param url URL to get OG card data.
|
|
46
|
-
* @param userAgent User agent to use for fetching.
|
|
47
|
-
* @returns OG card data.
|
|
48
|
-
*/
|
|
49
|
-
const getOGData = (url, userAgent) => __awaiter(void 0, void 0, void 0, function* () {
|
|
50
|
-
try {
|
|
51
|
-
const { result } = yield scraper({
|
|
52
|
-
fetchOptions: {
|
|
53
|
-
headers: {
|
|
54
|
-
"user-agent": userAgent
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
url
|
|
58
|
-
});
|
|
59
|
-
const OGImage = result.ogImage ? result.ogImage[0] : null;
|
|
60
|
-
const faviconURL = result.favicon
|
|
61
|
-
? `https://www.google.com/s2/favicons?domain=${new URL(url).hostname}`
|
|
62
|
-
: // eslint-disable-next-line no-undefined
|
|
63
|
-
undefined;
|
|
64
|
-
return {
|
|
65
|
-
OGImageAlt: OGImage === null || OGImage === void 0 ? void 0 : OGImage.alt,
|
|
66
|
-
OGImageHeight: OGImage === null || OGImage === void 0 ? void 0 : OGImage.height,
|
|
67
|
-
OGImageURL: OGImage === null || OGImage === void 0 ? void 0 : OGImage.url,
|
|
68
|
-
OGImageWidth: OGImage === null || OGImage === void 0 ? void 0 : OGImage.width,
|
|
69
|
-
description: result.ogDescription,
|
|
70
|
-
displayURL: url,
|
|
71
|
-
faviconURL,
|
|
72
|
-
title: result.ogTitle || url,
|
|
73
|
-
url
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
catch (error) {
|
|
77
|
-
// eslint-disable-next-line no-console
|
|
78
|
-
console.error("[rehype-og-card] Error fetching OG data:", error);
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
/**
|
|
83
|
-
* Create OG card element from given data.
|
|
84
|
-
* @param data Data to create OG card.
|
|
85
|
-
* @param options Options to create OG card.
|
|
86
|
-
* @returns OG card element.
|
|
87
|
-
*/
|
|
88
|
-
const createOGCard = (data, options) => {
|
|
89
|
-
const card = h("div.og-card-container", [
|
|
90
|
-
h("a", { href: data.url }, [
|
|
91
|
-
h("div.og-card-info", [
|
|
92
|
-
h("div.og-card-title", data.title),
|
|
93
|
-
data.description ? h("div.og-card-description", data.description) : null,
|
|
94
|
-
h("div.og-card-url-container", [
|
|
95
|
-
h("img.og-card-favicon", {
|
|
96
|
-
alt: "favicon",
|
|
97
|
-
decoding: options.decoding,
|
|
98
|
-
height: 16,
|
|
99
|
-
loading: options.loading,
|
|
100
|
-
src: data.faviconURL,
|
|
101
|
-
width: 16
|
|
102
|
-
}),
|
|
103
|
-
h("span.og-card-url", data.displayURL)
|
|
104
|
-
])
|
|
105
|
-
]),
|
|
106
|
-
data.OGImageURL
|
|
107
|
-
? h("div.og-card-image-container", [
|
|
108
|
-
h("img.og-card-image", {
|
|
109
|
-
alt: data.OGImageAlt || data.OGImageURL,
|
|
110
|
-
decoding: options.decoding,
|
|
111
|
-
height: data.OGImageHeight,
|
|
112
|
-
loading: options.loading,
|
|
113
|
-
src: data.OGImageURL,
|
|
114
|
-
width: data.OGImageWidth
|
|
115
|
-
})
|
|
116
|
-
])
|
|
117
|
-
: null
|
|
118
|
-
])
|
|
119
|
-
]);
|
|
120
|
-
return card;
|
|
121
|
-
};
|
|
122
41
|
/**
|
|
123
42
|
* Convert text node to anchor element. **This function does not check if the text is a valid URL.**
|
|
124
43
|
* @param text Text node to convert.
|
|
@@ -132,32 +51,49 @@ const convertTextToAnchorElement = (text) => ({
|
|
|
132
51
|
tagName: "a",
|
|
133
52
|
type: "element"
|
|
134
53
|
});
|
|
54
|
+
/**
|
|
55
|
+
* Check if the file exists.
|
|
56
|
+
* @param filePath Path to check.
|
|
57
|
+
* @returns `true` if the file exists, `false` otherwise.
|
|
58
|
+
*/
|
|
59
|
+
const checkFileExists = (filePath) => __awaiter(void 0, void 0, void 0, function* () {
|
|
60
|
+
try {
|
|
61
|
+
yield fs.access(filePath);
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
catch (_a) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
135
68
|
/**
|
|
136
69
|
* Download image from given URL.
|
|
137
|
-
* @param
|
|
138
|
-
* @param directly Path to save the image.
|
|
139
|
-
* @param userAgent User agent to use for fetching.
|
|
70
|
+
* @param options Options to download image.
|
|
140
71
|
* @returns Path to the downloaded image.
|
|
141
72
|
*/
|
|
142
73
|
// eslint-disable-next-line max-statements
|
|
143
|
-
const downloadImage = (
|
|
144
|
-
if (!isValidURL(url)) {
|
|
74
|
+
const downloadImage = (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
75
|
+
if (!isValidURL(options.url)) {
|
|
145
76
|
// eslint-disable-next-line no-console
|
|
146
|
-
console.error("[rehype-og-card] Invalid thumbnail URL:", url);
|
|
77
|
+
console.error("[rehype-og-card] Invalid thumbnail URL:", options.url);
|
|
147
78
|
return null;
|
|
148
79
|
}
|
|
149
80
|
try {
|
|
150
|
-
const
|
|
81
|
+
const hash = createHash("sha256").update(options.url).digest("hex");
|
|
82
|
+
const filename = hash + path.extname(new URL(options.url).pathname);
|
|
83
|
+
const savePath = path.posix.join(options.directly, filename);
|
|
84
|
+
// If the file already exists, return the filename.
|
|
85
|
+
const fileExists = yield checkFileExists(savePath);
|
|
86
|
+
if (fileExists) {
|
|
87
|
+
return filename;
|
|
88
|
+
}
|
|
89
|
+
const response = yield fetch(options.url, {
|
|
151
90
|
headers: {
|
|
152
|
-
"user-agent": userAgent
|
|
91
|
+
"user-agent": options.userAgent
|
|
153
92
|
}
|
|
154
93
|
});
|
|
155
94
|
const arrayBuffer = yield response.arrayBuffer();
|
|
156
95
|
const buffer = Buffer.from(arrayBuffer);
|
|
157
|
-
|
|
158
|
-
const filename = hash + path.extname(new URL(url).pathname);
|
|
159
|
-
const savePath = path.posix.join(directly, filename);
|
|
160
|
-
yield fs.mkdir(directly, { recursive: true });
|
|
96
|
+
yield fs.mkdir(options.directly, { recursive: true });
|
|
161
97
|
yield fs.writeFile(savePath, buffer);
|
|
162
98
|
return filename;
|
|
163
99
|
}
|
|
@@ -167,4 +103,4 @@ const downloadImage = (url, directly, userAgent) => __awaiter(void 0, void 0, vo
|
|
|
167
103
|
return null;
|
|
168
104
|
}
|
|
169
105
|
});
|
|
170
|
-
export { isTextNode, isAnchorElement, isValidURL,
|
|
106
|
+
export { isTextNode, isAnchorElement, isValidURL, convertTextToAnchorElement, checkFileExists, downloadImage };
|