Hexo 博客与 Waline 评论区实现外链跳转中间页
240804
- 新增 Waline 评论区中间页的实现
- 加入 @清羽飞扬 的插件方案
原文章发表于 240223
今年年初站长收到了 wxb 的温暖来电😨…自此以后对博客安全额外关注。好巧不巧那时在 友链朋友圈 刷到类似文章,于是立刻开展学习并全站自查外链安全性。
本文主要构成:
- Waline 评论区中间页的实现(新增):
- 方案 1:官方插件实现。但是有点小问题,建议直接看第二个方案。
- 方案 2:使用博主自写插件,流畅配合 Hexo 中其他中间页插件,同时支持昵称中的外链插入中间页。
- Hexo 博客外链跳转中间页的实现:
- 方案 1: @清羽飞扬 的插件方案。
- 方案 2【过期方案】:博主原本的过渡方案。
Waline 评论区实现外链跳转中间页
当然,如果 Waline 是其他部署方式,那么安装插件的方法其实大差不差,可继续阅读参考。
Waline 评论区独立于 Hexo 博客,是一个另外的系统,因此 Hexo 的各种渲染插件管理不到 Waline 的内容。但是我们可以通过安装 Waline 评论区的插件实现评论中的外链拦截。
这里介绍两种方案:
- 方案 1:使用官方插件。但是中间页链接无法定制,不够灵活,且存在小 Bug。
- 方案 2:使用博主本人基于官方插件二次改造的插件。其特点是黑白名单逻辑优化,可定制中间页链接,昵称外链匹配,并修复一些原官方插件中的 Bug。
想立刻上手可以直接看方案 2!
方案一:使用 Waline 官方提供的插件 @waline-plugins/link-interceptor
虽然 Waline 的插件不多,但是 Waline 维护者 lizheming 还是写了个相关的插件:plugins/packages/link-interceptor at master · walinejs/plugins (github.com)
本方案将介绍一种繁琐的但是又利于管理的安装 waline 插件的方法,以理解整个插件安装流程。在后续方案 2 中会介绍另一种十分轻松的偷懒方法,简化整个 waline 插件安装过程。
安装
首先从 GitHub 中 git clone
你的 Waline 后端到本地,然后进入你的 Waline 后端项目,输入以下命令安装 npm 包:
1 | npm i @waline-plugins/link-interceptor |
按照官方 Demo 的做法,在你的 Waline 后端项目的 index.js
中添加对应代码:
1 | const Application = require('@waline/vercel'); |
interceptorTemplate
选项不写的话,官方默认的 HTML 网页模板为:
1 |
|
部署与测试
将下面的文件加到 .gitignore
文件中:
- 刚才安装插件新增的
node_modules
文件夹 - 新增的
package-lock.json
然后 git add
以下文件(也就是说只有以下两个文件发生了改动):
- 你刚刚改动的
index.js
- 下载插件后自动变化的
package.json
最后 git commit
并上传 git push
。上传成功后 Vercel 会自动重新部署后端。
你可以发表评论进行测试。比如 blackList
中包含 jd.com
,那么评论内容出现相关域名链接时将替换链接为插件指定的中间页地址,并展示指定模板。
效果如下(使用默认模板):
上面的默认模板显示后,紧接着自动跳转到目标链接。
官方插件中存在的问题
黑白名单判断逻辑
该插件的黑白名单判断逻辑:
1 | function isValidUrl(url) { |
也就是说它是黑名单优先的:
- 如果网址不在黑名单,那就立马通过验证。
- 只有在黑名单且不在白名单才进行中间页拦截。
此外,没有域名通配符逻辑。这样的效果似乎不是很好。
中间页模板(已修复)
此问题当天 PR 当天就修复,赞赞赞!
另外,官方插件中的模板功能并不生效。因为官方插件的函数 outputHtml
写错位置了(已提 PR ,目前成功合并)。
昵称中的外链
官方插件并不过滤昵称中的外链。
不支持带有 #
符号的外链识别
看了下代码,感觉是偷懒…因为它编写的正则表达式有意的排除了带 #
的链接,简化后续域名判断。但是修复这个问题其实并不难(或者存在我没考虑的的情况?),暂且理解为官方为了省事暂时不变写这段逻辑。
方案二:使用博主自写插件 waline-link-interceptor
针对官方插件中存在的不足,我对官方插件的逻辑进行改进:
修复官方插件中的中间页模板不生效的问题(官方已在 PR #13 同步修复);- 黑名单、白名单匹配规则更改;
- 可以自定义跳转的中间页,以联合 Hexo 中的相关插件;
- 【v0.1.1 新增】除了评论内容中的外链筛选外,可以以同样的方式为头像旁边的昵称中的外链应用中间页。
- 【v0.1.2 修复】修复原官方插件中,不对带有
#
链接进行处理的错误逻辑
直接改动 GitHub 仓库完成插件的安装和使用
上一小节在介绍方案 1 时,安装 waline 插件时又要 git clone
、npm install
的,可能会特别麻烦。这里介绍直接在 GitHub 仓库改文件的方法,因为两者的本质是一样的。
进入你 GitHub 中的 Waline 后端项目,修改 package.json
文件,添加依赖:
1 | { |
一定要注意:
编辑 Waline 后端项目的 index.js
:
1 | const Application = require('@waline/vercel'); |
这个插件的最基本的用法和上面方案 1 中官方插件的用法完全一样。不同的是,黑白名单的逻辑稍有变更:
- 如果只填写白名单,那么不在白名单内的域名及其子域名,均将重定向至拦截页;
- 如果只填写黑名单,那么黑名单中的域名及其子域名,均将重定向至拦截页。其余链接放行;
- 如果黑白名单都填写,那么只拦截在黑名单中但不在白名单中的域名及其子域名。
将这些修改 commit 并 push 到远程仓库的 main 分支后之后,Waline 会自动部署,部署完成后即可看到效果。
关于 Vercel 中的 Production 分支详见官方文档:Deploying Git Repositories with Vercel
进阶使用 - 重定向至指定的链接
有时候,我们并不想使用 Waline 默认提供的跳转页模板(可以说官方这个模板十分简陋了),又或者不想自己手动通过 interceptorTemplate
字段写中间页模板。如果我们已经有了现成的中间页网址,可不可以让外链跳转到指定的地址呢?答案是肯定的,这个插件提供修改中间页链接的可选项。
1 | module.exports = Application({ |
示例 1:简单示例
比如我想让中间页地址形式为:
1 | https://example.com/go.html?u=https://external-link.com |
其中,example.com 表示主站地址,external-link.com 表示外部站点地址。那么, waline-link-interceptor
插件参数填写如下:
1 | redirectUrl: `https://example.com/go.html`, |
也就是说,外链地址中问号 ?
的前面归 redirectUrl
管,问号后面的归 encodeFunc
管。
比如,像知乎中间页链接就是这种简单的形式(来自评论区小伙伴提供的思路,这种方式有点说不出的妙🤭):
1 | https://link.zhihu.com/?target=https://uuanqin.top/ |
对应的 waline-link-interceptor
配置为:
1 | redirectUrl: `https://link.zhihu.com/`, |
示例 2:配合 Hexo 中间页插件 hexo-safego
如果你装了后文提到 Hexo 插件 hexo-safego
,且假设你的 hexo-safego
的相关配置(在 _config.yml
中)为:
1 | # see https://blog.qyliu.top/posts/1dfd1f41/ |
也就是说其中间页地址形式为:
1 | https://example.com/go.html?u=xxxxxxxxxxxxxxxxx |
那么你在插件 waline-link-interceptor
的配置中可以这样填写:
1 | redirectUrl: "https://example.com/go.html", |
这样就能 Waline 评论区 + Hexo 文章统一的中间页跳转啦🥳!
Hexo 博客文章或页面的外链跳转
方案一:直接使用跳转插件 hexo-safego
详见友链用户 @清羽飞扬 的文章:安全跳转页面·插件版。
效果如下:
方案二【过期】:魔改既有插件 hexo-external-link
- 本小节为今年(2024)年初事态紧急时探索的方案;
- 作为临时过渡措施,本方案已经完成了它的使命;
- 虽然整体的实现并不优雅,但直至目前该方案仍可继续正常工作。
本小节提供一个在 Hexo 实现的简单的方法,涉及到对插件源代码的修改(不优雅),但过渡时期可暂时容忍这一不足。我觉得整体思路是有参考性的。
插件安装与调整
插件的原理是识别出所有的外部链接,并修改其 URL 的格式使之成为内部链接。URL 指向一个中间页(没错,就是我以前嗤之以鼻的「互联网壁垒」)。你可以选择其他方式完成这件事情(比如 Hugo 方案参考友链用户 @大大的蜗牛 的文章: Hugo 外部链接跳转提示页面)
插件 hvnobug/hexo-external-link (github.com) 所做的就是这件事情。但是唯一缺点就是最终转换的链接不可定制,于是我就手动修改了一下源代码——即修改博客目录下 blogs\node_modules\hexo-filter-links\lib\filter.js
文件:
1 | - NewhrefStr = 'href="' + config.url + '/go/#' + NewhrefStr + '"'; |
这样链接就变成了我想要的样子:/pages/go.html#
+ 编码为 Base64 的外部链接地址。
建立中间页
从上面转换后的中间连接可以知道,我设置的中间页位置为 /pages/go.html
。其实我只是在 sourse/pages
目录下新建了 go.md
文件而已,Hexo 会自动渲染成 HTML。我这样做只是为了和某些页面统一而已。你也可以自己另外设计独立的页面。
go.md
基本内容如下(省略了样式代码,保留主要的 HTML,看思路即可):
1 | --- |
不太建议全站启用外链中间页,只对文章内容启用中间页即可。
后记
附一份常用白名单,涵盖程序员常用外链(云服务平台、博客网站、笔记网站等):
1 | allowList: |
如果平时不喜欢各大网站中间页可以考虑安装这个脚本:Open the F**king URL Right Now (greasyfork.org)。或者尝试安装这个插件(我没试过):Skip Redirect (google.com)
很多时候,在网上读到一篇好文章,我们被作者思想的深度,逻辑论证的缜密,内容的旁征博引所折服,这时我们往往也想去作者给出的相关链接上看看是怎样的奇文给了作者启发,写出了这样一篇振聋发聩的好文,于是点击链接,但是很多时候会突然跳出来一行字,要用户手动复制该链接贴到地址栏访问才行,被网站这样一折腾,刚刚提起来的求知欲一下子烟消云散了。
一次两次也倒罢了,但最近此风甚长,还有愈演愈烈之势,好好的超链接变成了一个个的『超不链接』,其他网站为了留存用户,延长用户停留时间,不惜以暴制暴,进行恶性竞争,结果就是互联网的用户体验变成了各大公司利益的牺牲品,在夹缝中艰难地生存,昔日的信息高速公路上就这样被人为地挖了许多坑。
为了把互联网体验变回我们熟悉的样子,为了让超链接能痛快地把用户送到终点,就写了这样一个简单的脚本…
——OldPanda ,摘自中间页跳转脚本 Open the F**king URL Right Now 的简介
有朋友问,为何要增设这样一个看似多余的步骤?“直接浏览不更直接吗?何必增添一个看似拖沓的跳转环节?”还有人,将其误解为纯粹的技术冗余,误以为这是效仿某些平台所设置的“用户不便”之举…Hexo-SafeGo 插件的核心目的,并非直接进行安全检测,而是作为一种自我保护机制,默默守护着网站的声誉与访问者的信任。现在的插件并没有能力能够主动扫描并消除网络中的所有威胁,但却能有效避免自身网站因缺少必要的安全协议而被浏览器标记为“不安全”,这一小步跳跃,实则是维护网站形象与信誉的一大步。
此插件的实施,更像是一份无形的“免责声明”,它向访问者无声宣告:作为网站管理者,我已经采取措施确保连接的安全性,即便遭遇外部链接可能带来的不确定性,也已事先提醒,尽到了告知的责任。在这个信息错综复杂的时代,这样的透明度与责任感显得尤为重要。
—— @清羽飞扬,摘自中间页插件 hexo-safego 的介绍文章
在编写 waline 插件时,真的如同盲人摸象,全靠猜测和 log 输出,好在需要修改的地方不复杂。
本文经历了过期又翻新的过程。讯飞星火参与正则代码编写,所以这种苦力活最适合交给 AI 做。
最后建议各位博主不要过度依赖各类插件,定期清理文章或评论区的可疑链接才是治本措施。
本文参考
- Hexo的外链转内链插件 - 春风吹 - 浅秋枫影的博客 (cuojue.org)
- Hugo 外部链接跳转提示页面-大大的小蜗牛 (eallion.com)
- HUGO 外链跳转到中间页 - 空白Koobai
- js实现倒计时跳转页面_在网页中显示倒计时读秒效果,比如543210当数据小于0时指定跳转到其他页面-CSDN博客
- NodeJS:字符串和base64相互转换_nodejs string to base64-CSDN博客
- 使用 JavaScript 进行 Base64 编码与解码_js base64解码-CSDN博客
- JavaScript中 切割截取字符串的几种方法_js 截取字符串-CSDN博客
- Document: getElementsByClassName() method - Web APIs | MDN (mozilla.org)