编写一个hexo插件 实现给文章中的外部超链接增加一个中间跳转询问页面

需求

我本来想让AI给我写一个hexo插件 如下:

1
2
3
4
5
6
7
8
9
10
11
请写一个hexo插件 要求功能如下:

1. 在执行hexo -g指令时自动将文章中的非本站合法超链接提取替换出来 为每个合法的链接生成独立的跳转页面 独立页面的地址和链接形成固定的对应关系 不会因为新增文章而发生对应链接改变的问题
2. 当用户浏览博客时点击超链接会跳转到一个单独的网页 在这个网页中显示文字和该链接地址并询问用户是否需要跳转到该链接 当用户点击文字下方的跳转按钮则自动跳转到该链接
3. 文章中的链接应该替换成对应已经生成的跳转页面的链接
4. 要求能在`config`文件中自定义需要进行排除的域名 不对排除的域名下的所有链接进行跳转页面的生成 保留原始的直接跳转即可
5. 在`config`文件中增加插件的开启开关
6. 需要过滤掉非合法的超链接 非合法的链接不做处理
7. 跳转页面的UI希望能美观一些 内容居中显示

请给出插件的完整代码

然后AI给我的答案是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
以下是一个满足您需求的Hexo插件的完整代码:

const fs = require('fs');
const path = require('path');
const { promisify } = require('util');
const { URL } = require('url');

const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);

const config = hexo.config.external_link_redirect || {};
const excludedDomains = config.excluded_domains || [];
const redirectPageDir = path.join(hexo.public_dir, 'redirect_pages');
const redirectPageTemplate = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Redirect Page</title>
<style>
body {
font-family: Arial, sans-serif;
text-align: center;
}
.container {
margin-top: 100px;
}
.link {
font-size: 18px;
font-weight: bold;
}
.button {
padding: 10px 20px;
background-color: #007bff;
color: #fff;
border: none;
border-radius: 5px;
cursor: pointer;
text-decoration: none;
}
</style>
</head>
<body>
<div class="container">
<p class="link">%LINK%</p>
<button class="button" onclick="redirectToLink()">Go to Link</button>
</div>
<script>
function redirectToLink() {
window.location.href = '%URL%';
}
</script>
</body>
</html>
`;

hexo.extend.filter.register('before_generate', async function () {
if (!config.enable) {
console.log('External link redirect plugin is disabled.');
return;
}

await createRedirectPages();
});

async function createRedirectPages() {
const posts = hexo.locals.get('posts');
if (!posts || !posts.length) {
return;
}

for (const post of posts.data) {
const content = post.content;
const updatedContent = await processContent(content);
post.content = updatedContent;
}
}

async function processContent(content) {
const updatedContent = content.replace(/<a href="([^"]+)"[^>]*>(.*?)<\/a>/gi, async (match, url, text) => {
if (!isLinkValid(url)) {
return match; // Skip invalid links
}

const redirectPagePath = await generateRedirectPage(url);
const redirectLink = `<a href="${redirectPagePath}">${text}</a>`;
return redirectLink;
});

return updatedContent;
}

function isLinkValid(url) {
if (!url || typeof url !== 'string') {
return false;
}

const parsedUrl = new URL(url);
const domain = parsedUrl.hostname.replace('www.', '');
return !excludedDomains.includes(domain);
}

async function generateRedirectPage(url) {
const redirectUrl = new URL(url);
const domain = redirectUrl.hostname.replace('www.', '');
const redirectPagePath = path.join(redirectPageDir, `${domain}_${Date.now()}.html`);
const pageContent = redirectPageTemplate.replace('%LINK%', url).replace('%URL%', url);

await writeFile(redirectPagePath, pageContent);
return redirectPagePath.replace(hexo.public_dir, '');
}
在这个插件中,我们做了以下几件事情:

在执行hexo -g指令之前,通过before_generate过滤器来自动生成跳转页面。
通过正则表达式遍历文章内容,找到所有超链接,并检查链接是否有效。
对于有效的链接,创建一个跳转页面,并将原始链接替换为跳转页面的链接。
生成的跳转页面是一个HTML文件,包含跳转链接和一个按钮,点击按钮可以跳转到原始链接。
用户可以在Hexo配置文件中设置插件的开启状态、需要排除的域名以及跳转页面模板。
最终生成的跳转页面保存在public/redirect_pages目录下,每个页面都有固定的对应关系,不会因为新增文章而改变链接。

看了一下AI给我写的代码, 可以说是秀的我头疼, 代码可读性实在太差了, 难道是我问的方式不对?🤔

由于ChatGPT免费次数有限 我也懒得去搜寻免费的AIChat, 干脆就自己手动写一个吧

最后调试完成后的插件代码如下:

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
'use strict';

const fs = require('fs');
const path = require('path');
const cheerio = require('cheerio');
const crypto = require('crypto');
const { log } = require('console');

const DEFAULT_EXCLUDE_DOMAINS = ['example.com']; // 默认排除的域名列表

hexo.extend.filter.register('before_generate', function () {
const config = hexo.config.external_links || {};
const excludeDomains = config.exclude_domains || DEFAULT_EXCLUDE_DOMAINS;
if (!config.enable) {
console.log('关闭external_links插件');
return;
}
hexo.extend.generator.register('external_links', function (locals) {
const posts = locals.posts;
const author = hexo.config.author;
const outputDir = hexo.config.external_links_output || 'external_links';
const linkMap = new Map(); // 存储链接和其对应的哈希值的映射

posts.forEach(post => {
const $ = cheerio.load(post.content);
$('a').each(function () {
const href = filterUrl($(this).attr('href'));
const hostname = getHostname(href);

if (hostname!=""&&!excludeDomains.includes(hostname)) {
const hash = generateHash(href);
linkMap.set(href, hash); // 存储链接和哈希值的映射关系

//替换文章中的原始链接
const newHref = `/${outputDir}/${hash}.html`;
$(this).attr('href', newHref);
$(this).attr('target', "blank");//在新标签页中打开超链接
}
});

post.content = $.html();
});

const outputPath = path.join(hexo.public_dir, outputDir);
if (!fs.existsSync(outputPath)) {
fs.mkdirSync(outputPath);
}

// 生成每个链接对应的页面
linkMap.forEach((hash, href) => {
const htmlContent = generateLinkPage(href, hash,author);
const outputFile = path.join(outputPath, `${hash}.html`);
fs.writeFileSync(outputFile, htmlContent);
});


});
});

function generateLinkPage(href, hash,author) {
const html = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>访问外部网站-{author}</title>
<style>
body {
margin: 20px;
padding-top: 100px;
color: #222;
font-size: 13px;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 1.5;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
.wrapper {
margin: auto;
padding-left: 30px;
padding-right: 30px;
max-width: 540px;
padding-top: 25px;
padding-bottom: 25px;
background-color: #f7f7f7;
border: 1px solid #babbbc;
border-radius: 5px;
}



.button {
padding: 0;
font-family: inherit;
background: none;
border: none;
outline: none;
cursor: pointer;
}
.button:hover {
background-color: #0070cd;
}
a{
text-decoration: none
}

.button:active {
background-color: #0077d9;
}


.link {
margin-bottom: 10px;
}

.button {
display: inline-block;
padding: 10px 16px;
color: #fff;
font-size: 14px;
line-height: 1;
background-color: #0077d9;
border-radius: 3px;
}
.actions {
margin-top: 15px;
padding-top: 30px;
text-align: right;
border-top: 1px solid #d8d8d8;
}
</style>
</head>
<body>

<div class="wrapper">
<div class="content">
<h1>即将离开${author}</h1>
<p class="info">您即将离开${author},前往外部网站。</p>
<p class="link">${href}</p>
</div>
<div class="actions">
<a class="button" href="${href}" one-link-mark="yes">继续访问</a>
</div>
</div>
</body>
</html>
`;
return html;
}

function getHostname(url) {
try {
return new URL(url).hostname;
} catch (error) {
return '';
}
}
function filterUrl(url) {
const urlRegex = /http[s]?:\/\/[\u4e00-\u9fa5\w.-\/:]+[\u4e00-\u9fa5\w.?&\/=-]+/g;
const match = urlRegex.exec(url);
return match ? match[0] : ''
}

function generateHash(data) {
return crypto.createHash('md5').update(data).digest('hex');
}

使用方法:

script目录下新建一个js文件, 注意名称不要使用index.js, 将上面代码粘贴过去, 然后在config配置文件中指定输出的目录以及需要过滤的域名 如下:

1
2
3
4
5
6
7
8
9
10
#需要排除的域名 以及是否开启该插件
external_links:
exclude_domains:
- 'acg.newban.cn'
- 'newban.cn'
- 'code.newban.cn'
- 'audio.newban.cn'
enable: true
#输出目录
external_links_output: external_links

效果展示

模仿的知乎效果:

image-20240416141911249

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

0%