
很多时候我们都在会在一些大牌网站上看到这个功能,特别是腾讯云的技术论坛,或者csdn的博客页面,在我们跳转到文章中链接的文章地址的时候,会给我们一个非常不错的跳转提示。有人说这个功能没啥用,也有人说会增加网站博客浏览者的心情,可靠,值得信赖,所以想着实现这个跳转提示的功能,幸好看到这篇文章所以记录下来,这是一个给Hexo博客实现给文章中的外部超链接增加一个中间跳转询问页面的插件,这个插件本来是用ai写的代码,写作要求如下
1 2 3 4 5 6 7
| 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
| 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
| '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
|
实现之后的效果应该是这个样子

这是原作者的创作链接:编写一个hexo插件 实现给文章中的外部超链接增加一个中间跳转询问页面
可惜,本人能力有限,并没有实现作者的意图,而且添加代码之后,给自己的博客生成预览添了毛病,所以这是一篇纯粹的记录帖子,谁有这么高的水平实现了麻烦告诉我一下。