碎碎念

原本的安全跳转页面糟糕的一塌糊涂,因为当时水平有限,所以只能在别人的基础上修改,导致很多地方都不兼容,比如最简单的fancybox我都没有办法排除,会导致无法点击图片进行放大查看,除此之外无法排除友链页面,也无法排除友情链接的跳转卡片,兼容性也很差,群友想要使用我也没法提供解决方案,很是头疼,所以经过整整一个月的酝酿,我胡汉三又回来啦!此次重构大大简化了代码结构,并解决了前面的问题,为了测试稳定性,我还特意悄悄地上线了六天,好像也没人提出什么bug(也有可能是我的人气太少了呜呜呜),这才正式写出该重制版教程,给予需要的朋友以启发。

功能介绍

  1. 设置替换白名单:按照揽星给出的建议,可以自定义替换白名单匹配,如友链文章引用,好友引用等无需替换,其他链接替换;
  2. 设置页面白名单:如仅匹配文章页面的链接;
  3. 设置元素白名单:如仅匹配id="article-container"的内容;
  4. 设置跳转白名单:如知乎等,使用跳转页面,但显示为安全,可以自动跳转;
  • 注:该教程理论上适用于全部Hexo架构博客,请按照要求修改代码即可,该教程需要有一定的前端水平,如果有问题可以发到评论区,我会尽量解答。

功能实现

这里我还是使用原廿壴博客提供的跳转页模板,但是相关跳转页逻辑完全重构,模板原链接如下:

页面模板

首先需要在source文件夹下创建go.html,写入以下内容:

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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
---
layout: false
---
<!DOCTYPE html>
<html data-user-color-scheme="light">

<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no" />
<title>
安全中心 | LiuShen's Blog
</title>
<link rel="icon" class="icon-favicon" href="/" />
<link rel="stylesheet" href="https://lib.baomitu.com/twitter-bootstrap/4.6.1/css/bootstrap.min.css" />
<link rel="stylesheet" href="https://at.alicdn.com/t/font_1736178_lbnruvf0jn.css" />
<style type="text/css">
/* // 向上渐隐显示(主内容使用) */
@-webkit-keyframes fadeInUp {
0% {
opacity: 0;
transform: translateY(24px);
}

100% {
opacity: 1;
transform: translateY(-80px);
}
}

@keyframes fadeInUp {
0% {
opacity: 0;
-webkit-transform: translateY(24px);
-ms-transform: translateY(24px);
transform: translateY(24px);
}

100% {
opacity: 1;
-webkit-transform: translateY(-80px);
-ms-transform: translateY(-80px);
transform: translateY(-80px);
}
}

/* // 向上渐隐显示(成功错误提示) */
@-webkit-keyframes alertFadeInUp {
0% {
opacity: 0;
transform: translateY(24px);
}

75% {
opacity: 1;
transform: translateY(0);
}

100% {
opacity: 0;
}
}

@keyframes alertFadeInUp {
0% {
opacity: 0;
-webkit-transform: translateY(24px);
-ms-transform: translateY(24px);
transform: translateY(24px);
}

75% {
opacity: 1;
-webkit-transform: translateY(0);
-ms-transform: translateY(0);
transform: translateY(0);
}

100% {
opacity: 0;
}
}

@-webkit-keyframes fadeOutUp {
0% {
opacity: 1;
}

to {
opacity: 0;
transform: translate3d(0, -350%, 0);
}
}

@keyframes fadeOutUp {
0% {
opacity: 1;
}

to {
opacity: 0;
-webkit-transform: translate3d(0, -350%, 0);
transform: translate3d(0, -350%, 0);
}
}

:root {
--blue: #007bff;
--indigo: #6610f2;
--purple: #6f42c1;
--pink: #e83e8c;
--red: #dc3545;
--orange: #fd7e14;
--yellow: #ffc107;
--green: #28a745;
--teal: #20c997;
--cyan: #17a2b8;
--white: #fff;
--gray: #6c757d;
--gray-dark: #343a40;
--primary: #007bff;
--secondary: #6c757d;
--success: #28a745;
--info: #17a2b8;
--warning: #ffc107;
--danger: #dc3545;
--light: #f8f9fa;
--dark: #343a40;
--breakpoint-xs: 0;
--breakpoint-sm: 576px;
--breakpoint-md: 768px;
--breakpoint-lg: 992px;
--breakpoint-xl: 1200px;
--font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI",
"PingFang SC", Roboto, "Helvetica Neue", Arial, "Noto Sans",
"Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
"Segoe UI Symbol", "Noto Color Emoji";
--font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas,
"Liberation Mono", "Courier New", monospace;
}

[data-user-color-scheme="dark"] {
--body-bg-color: #22272e;
--board-bg-color: #2b313a;
--text-color: #adbac7;
--sec-text-color: #b3bac1;
--post-text-color: #adbac7;
--post-heading-color: #adbac7;
--post-link-color: #34a3ff;
--link-hover-color: #30a9de;
--link-hover-bg-color: #22272e;
--line-color: #adbac7;
--navbar-bg-color: #22272e;
--navbar-text-color: #cbd4dc;
--subtitle-color: #cbd4dc;
--scrollbar-color: #30a9de;
--scrollbar-hover-color: #34a3ff;
--button-bg-color: transparent;
--button-hover-bg-color: #46647e;
--highlight-bg-color: #2d333b;
--inlinecode-bg-color: rgba(99, 110, 123, 0.4);
}

::-webkit-scrollbar {
width: 6px;
height: 6px;
}

::-webkit-scrollbar-corner {
background-color: transparent;
}

::-webkit-scrollbar-thumb {
background-color: var(--scrollbar-color);
border-radius: 6px;
}

html {
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: transparent;
}

html,
body {
/* background: #f3f4f5; */
/* font-family: PingFang SC, Hiragino Sans GB, Arial, Microsoft YaHei,
Verdana, Roboto, Noto, Helvetica Neue, sans-serif; */
font-family: var(--font-family-sans-serif);
padding: 0;
margin: 0;
background-color: var(--body-bg-color);
color: var(--text-color);
transition: color 0.2s ease-in-out, background-color 0.2s ease-in-out;
height: 100%;
}

body {
font-size: 1rem;
}

p,
div {
padding: 0;
margin: 0;
}

a {
text-decoration: none;
transition: color 0.2s ease-in-out, background-color 0.2s ease-in-out;
}

body a:hover {
color: var(--link-hover-color);
text-decoration: none;
}

.go-page {
height: 100%;
}

.content {
/* padding-top: 220px; */
width: 450px;
margin: auto;
word-break: break-all;
height: 100%;
}

.content .logo-img {
margin-bottom: 20px;
text-align: center;
padding-top: 220px;
}

.content .logo-img p:first-child {
font-size: 22px;
}

.content .logo-img img {
display: block;
width: 175px;
height: 48px;
margin: auto;
margin-bottom: 16px;
}

.content .loading-item {
background: #fff;
padding: 24px;
border-radius: 12px;
border: 1px solid #e1e1e1;
margin-bottom: 10px;
}

/* 绿色 */
.content .tip1 {
background: #f0f9ea;
}

/* 黄色 */
.content .tip2 {
background: #fdf5e6;
}

/* 红色 */
.content .tip3 {
background: #fef0f0;
}

.content .icon-snapchat-fill {
font-size: 20px;
color: #fc5531;
border: 1px solid #fc5531;
border-radius: 50%;
width: 32px;
text-align: center;
margin-right: 5px;
}

.content .tip1 .icon-snapchat-fill {
color: var(--post-link-color);
border-color: var(--post-link-color);
}

.content .loading-text {
font-size: 16px;
font-weight: 600;
color: #222226;
line-height: 22px;
/* margin-left: 12px; */
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.content .flex {
display: flex;
align-items: center;
}

.content .flex-end {
display: flex;
justify-content: flex-end;
}

/* #267dcc 蓝色 */
.content .loading-color1 {
color: var(--post-link-color);
}

.content .loading-color2 {
color: #fc5531;
}

.content .loading-tip {
padding: 12px;
margin-bottom: 16px;
border-radius: 4px;
}

.content .loading-topic {
font-size: 14px;
color: #222226;
line-height: 24px;
margin-bottom: 24px;
}

.loading-topic .flex {
flex-direction: column;
}

.content .loading-img {
width: 24px;
height: 24px;
}

/* #fc5531; #fc5531*/
.content .loading-btn {
font-size: 14px;
color: var(--post-link-color);
border: 1px solid var(--post-link-color);
display: inline-block;
box-sizing: border-box;
padding: 6px 18px;
border-radius: 18px;
margin-left: 8px;
}

.content .loading-btn:hover {
color: var(--link-hover-color);
border-color: var(--link-hover-color);
}

.content .loading-btn-github {
width: 121px;
background: #fc5531;
color: #fff;
}

.hidden {
display: none;
}

.form-control.hidden {
display: none !important;
}

.mp-img-box {
text-align: center;
margin-bottom: 10px;
}

.mp-img {
max-width: 400px;
width: 100%;
box-shadow: 5px 5px 15px rgb(0 0 0 / 8%);
margin-bottom: 5px;
}

.fadeInUp {
-webkit-animation-name: fadeInUp;
animation-name: fadeInUp;
}

.alertFadeInUp {
-webkit-animation-name: alertFadeInUp;
animation-name: alertFadeInUp;
-webkit-animation-duration: 3s;
animation-duration: 3s;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
}

.fadeOutUp {
-webkit-animation-name: fadeOutUp;
animation-name: fadeOutUp;
}

.fade-animate {
-webkit-animation-duration: 1s;
animation-duration: 1s;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
-webkit-animation-delay: 1s;
animation-delay: 1s;
}

.go-alert {
margin: 0 auto;
width: 110px;
position: absolute;
left: 46%;
top: 5%;
opacity: 0;
text-align: center;
}

.footer {
text-align: center;
position: relative;
margin-bottom: 20px;
}

.footer a {
color: var(--text-color);
}

.flex-box {
display: flex;
height: 100vh;
flex-direction: column;
}

.flex-contain {
flex: 1;
}

.flex-footer {
height: 24px;
}

@media (max-width: 767.98px) {
.content {
width: 94%;
}

.content .logo-img {
padding-top: 120px;
}
}
</style>
</head>

<body class="web-font">
<div id="goPage" class="go-page">
<div class="alert alert-danger go-alert hidden" role="alert">
验证失败
</div>

<div class="content">
<div class="flex-box">
<div class="flex-contain">
<div class="logo-img">
<p class="blog-name">LiuShen's Blog</p>
<p class="blog-description"></p>
</div>

<!-- 加载ing... -->
<div class="loading-item loading-safe flex">
<i class="iconfont icon-snapchat-fill"></i>
<div class="loading-text">链接安全性检验中 请稍后...</div>
</div>

<div class="go-box"></div>
</div>
<div class="footer flex-footer">
©2021-2024
<a href="https://www.qyliu.top" class="blog-name"><span>LiuShen's Blog</span></a>
版权所有
</div>
</div>
</div>
</div>
<!-- goPage end -->

<script src="https://lib.baomitu.com/jquery/3.6.0/jquery.min.js"></script>
<script src="https://lib.baomitu.com/twitter-bootstrap/4.6.1/js/bootstrap.min.js"></script>
<script type="module">
// 请根据自己博客修改
const config = {
// 标题
title:
"安全中心 | LiuShen's Blog",
// 地址栏图标
iconFavicon: "https://cdn.qyliu.top/i/2024/03/21/65fc56832e37d.png",
// 二维码地址
// mpImgSrc: "/img/wxgzh.webp",
// 博客名称
blogName: "LiuShen's Blog",
// 博客描述
blogDescription: "柳影曳曳,清酒孤灯,扬笔撒墨,心境如霜",
// 白名单
safeUrl: [
// 平台 常用平台不用改哈
"github.com",
"gitee.com",
"csdn.net",
"zhihu.com",
"pan.baidu.com",
"baike.baidu.com",
"hexo.io",
"leancloud.cn",
"nodejs.cn",
"jsdelivr.com",
"ohmyposh.dev",
"nerdfonts.com",
"douban.com",
"waline.js.org",
"developer.mozilla.org",
"qyliu.top",

// 好友博客 增加自己的博客友链
],
tipsTextError: "链接错误,关闭页面返回本站",
// tipsTextDownload:
// "从廿壴(ganxb2)微信公众号获取暗号≖‿≖✧ o‿≖✧(๑•̀ㅂ•́)و✧",
// "(๑•̀ㅂ•́)و✧“博客”微信公众号关注走一波o‿≖✧",
tipsTextDanger: "该网址未在确认的安全范围内",
tipsTextSuccess: "该网址在确认的安全范围内",
textDanger:
"您即将离开博客去往如下网址,请注意您的账号隐私安全和财产安全:",
textSuccess: "您即将离开博客去往如下网址",
// 后续改成leancloud获取(下载验证码)
// wpValidate: "9498",
};
// 获取地址
const getQueryString = (name, type) => {
// 构造一个含有目标参数的正则表达式对象
let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"),
regDown = new RegExp("&type=" + type),
// 匹配地址参数
r = window.location.search.substr(1).match(reg),
d = window.location.search.substr(1).match(regDown),
isDownload = false;

// 反编译回原地址 取第3个值,不然就返回 Null
if (r !== null) {
// 如果d不为空,则显示下载提示
if (d !== null) {
isDownload = true;
}
return { url: decodeURIComponent(r[2]), isDownload: isDownload };
}
return null;
};

// xss攻击(绑定值时使用)
const xssCheck = (str, reg) => {
return str
? str.replace(
reg || /[&<">'](?:(amp|lt|quot|gt|#39|nbsp|#\d+);)?/g,
function (a, b) {
if (b) {
return a;
} else {
return {
"<": "&lt;",
"&": "&amp;",
'"': "&quot;",
">": "&gt;",
"'": "&#39;",
}[a];
}
}
)
: "";
};

// 其他地址校验白名单
const othersValidate = (config, getLinkUrl) => {
let isSafeUrl = false,
safeUrl = config.safeUrl,
url = xssCheck(getLinkUrl.url);
console.log("shuchuchuchcu", safeUrl)
console.log("shuchuchuchcu", url)

if (safeUrl.length !== 0) {
for (let i = 0; i < safeUrl.length; i++) {
const ele = safeUrl[i];
if (url.includes(ele)
|| url.includes(ele + '/')
|| url.includes('https://' + ele)
|| url.includes('https://' + ele + '/')
|| url.includes('http://' + ele)
|| url.includes('http://' + ele + '/')) {
isSafeUrl = true;
break;
}
}
}
return isSafeUrl;
};

// 模版基础配置初始
const goInit = (config) => {
// $(function () {
const tplConfig = {
loadingType: "loading-error",
tipType: "tip3",
tipsText: config.tipsTextError,
loadingTopicText: config.textDanger,
loadingColorType: "loading-color2",
goUrl: "/",
},
getLinkUrl = getQueryString("goUrl", "goDown"),
loadingSafe = document.querySelector(".loading-safe"),
goBox = document.querySelector(".go-box"),
title = document.querySelector("title"),
iconFavicon = document.querySelector(".icon-favicon"),
blogName = document.querySelectorAll(".blog-name"),
blogDescription = document.querySelector(".blog-description");

// 初始化:标题,favicon,博客名称,博客描述
title.textContent = config.title;
iconFavicon.setAttribute("href", config.iconFavicon);
blogName.forEach((element) => {
element.textContent = config.blogName;
});
blogDescription.textContent = config.blogDescription;

// 根据地址栏参数判断是下载地址还是纯外链,外链则直接修改a标签按钮url,用户点击跳转
if (getLinkUrl) {
// 可参考csdn加入后端请求验证地址是否白名单再进一步给出不同场景状态:是白名单,则绿+蓝,否则黄+红
const isSafeUrl = othersValidate(config, getLinkUrl);
tplConfig.loadingType = "loading-others";
tplConfig.goUrl = xssCheck(getLinkUrl.url);

if (isSafeUrl) {
tplConfig.tipType = "tip1";
tplConfig.tipsText = config.tipsTextSuccess;
tplConfig.loadingTopicText = config.textSuccess;
tplConfig.loadingColorType = "loading-color1";
// 白名单链接直接跳转
setTimeout(() => {
const goUrlBtn = document.querySelector(".go-url-btn");
goUrlBtn.click();
}, 2000);

} else {
tplConfig.tipType = "tip2";
tplConfig.tipsText = config.tipsTextDanger;
tplConfig.loadingTopicText = config.textDanger;
tplConfig.loadingColorType = "loading-color2";
}
}
else {
// 错误
tplConfig.tipType = "tip2";
tplConfig.tipsText = config.tipsTextError;
}

const othersTpl = `
<div class="loading-topic">
<span
>${tplConfig.loadingTopicText}</span
>
<a class="${tplConfig.loadingColorType} go-url">${tplConfig.goUrl}</a>
</div>
<div class="flex-end">
<a rel="noopener external nofollow noreferrer" class="loading-btn go-url-btn" href="${tplConfig.goUrl}" target="_self">继续</a>
</div>
`;
const tpl = `
<div class="loading-item ${tplConfig.loadingType} hidden">
<div class="flex loading-tip ${tplConfig.tipType}">
<i class="iconfont icon-snapchat-fill ${tplConfig.loadingType === "loading-download" && "hidden"
}"></i>
<div class="loading-text">
${tplConfig.tipsText}
</div>
</div>
${tplConfig.loadingType === "loading-others"
? othersTpl
// : tplConfig.loadingType === "loading-download"
// ? downloadTpl
: ""
}
</div>
`;

// tpl渲染
goBox.innerHTML = tpl;
const loadingItem = document.querySelector(".go-box .loading-item");
loadingSafe.classList.add("fadeOutUp", "fade-animate");
loadingItem.classList.remove("hidden");
loadingItem.classList.add("fadeInUp", "fade-animate");
};
goInit(config);
</script>
</body>

</html>

以上代码可能需要修改的部分只有一个地方,白名单,不过这里的白名单都是通用的,可以不进行修改,这里的白名单为跳转白名单,详情请看功能介绍,下面是页面展示:

跳转白名单展示

JS链接替换

下面就是我重构的内容,使用JS脚本,将能匹配上的链接进行替换,请在自定义JS代码部分添加以下内容:

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
function updateLinks() {
// 定义白名单数组
var whitelist = [
'qyliu.top', // 添加您不想替换链接的域名或路径片段
'zouht.com',
'akilar.top',
……
];

var containerArticle = document.getElementById("article-container");
if (containerArticle) {
var links = containerArticle.getElementsByTagName("a");
for (var i = 0; i < links.length; i++) {
var link = links[i];
var hasFancybox = link.hasAttribute("data-fancybox");
var isSafeGo = link.href.startsWith('/go.html');

// 使用 Array.prototype.some() 来检查链接的 href 是否包含白名单中的某个元素
var isWhitelisted = whitelist.some(function(whitelistedItem) {
return link.href.includes(whitelistedItem);
});

// 如果没有特定属性且链接没有安全跳转,且链接不在白名单中
if (!hasFancybox && !isSafeGo && !isWhitelisted) {
var originalUrl = link.href;
link.href = "/go.html?goUrl=" + encodeURIComponent(originalUrl) + "&type=goDown";
}
}
}
}

// 在 PJAX 完成时调用函数
document.addEventListener('pjax:complete', function() {
// 检查当前路径是否以 "/posts/" 开头
if (window.location.pathname.startsWith('/posts/')) {
updateLinks();
console.log('pjax||文章页面,准备替换安全链接');
} else {
console.log('pjax||非文章页面无需替换安全链接');
}
});

// 在页面加载完成后调用函数
window.addEventListener('load', function() {
// 检查当前路径是否以 "/posts/" 开头
if (window.location.pathname.startsWith('/posts/')) {
updateLinks();
console.log('load||文章页面,准备替换安全链接');
} else {
console.log('load||非文章页面无需替换安全链接');
}
});

这里需要修改的部分主要有:

  1. 第三行替换白名单,这些网站将默认为安全网站,不会被重定向到安全跳转页面,可以将友链的根域名放到这里,如果在链接中匹配到元素,将不进行替换。

  2. 第十行元素白名单:填写你想替换的页面的某个部分的ID或者类名,查找方式如下:

    元素白名单查找方式

  3. 第十六行元素黑名单:比如fancybox,fancybox是图片点击后放大预览的插件,如果链接替换了的话会导致无法正常放大,显示图片异常,这个应该都一样,确定的方式如下:

    确认fancybox

  4. 下面两个执行函数,由于我的页面开了pjax,不需要的删除即可,也可以保留,需要替换其中的posts路径,该路径在随便的文章页链接中就能找到:

    页面白名单

此时,功能基本实现了,你的文章页的外链卡片应该已经被替换为了安全链接。

评论区

每个评论系统基本上都会有一个回调函数,比如butterfly主题我们定位到文件:[blogroot]themes\butterfly\layout\includes\third-party\comments\twikoo.pug,修改其中的代码:

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
 - const { envId, region, option } = theme.twikoo
- const { use, lazyload, count } = theme.comments

script.
(() => {
const getCount = () => {
const countELement = document.getElementById('twikoo-count')
if(!countELement) return
twikoo.getCommentsCount({
envId: '!{envId}',
region: '!{region}',
urls: [window.location.pathname],
includeReply: true
}).then(res => {
countELement.textContent = res[0].count
}).catch(err => {
console.error(err)
})
}

const init = () => {
twikoo.init(Object.assign({
el: '#twikoo-wrap',
envId: '!{envId}',
region: '!{region}',
onCommentLoaded: () => {
btf.loadLightbox(document.querySelectorAll('#twikoo .tk-content img:not(.tk-owo-emotion)'))
+ document.querySelectorAll('#twikoo .tk-comments-container a').forEach(function(aEl){
+ if (!aEl.hasAttribute('data-fancybox')) {
+ if (!aEl.href.startsWith(window.location.origin)) {
+ aEl.href = '/go.html?goUrl=' + encodeURIComponent(aEl.href) + '&type=goDown';
+ }
+ }
+ });
}
}, !{JSON.stringify(option)}))

!{count ? 'GLOBAL_CONFIG_SITE.isPost && getCount()' : ''}
}

const loadTwikoo = () => {
if (typeof twikoo === 'object') setTimeout(init,0)
else getScript('!{url_for(theme.asset.twikoo)}').then(init)
}

if ('!{use[0]}' === 'Twikoo' || !!{lazyload}) {
if (!{lazyload}) btf.loadComment(document.getElementById('twikoo-wrap'), loadTwikoo)
else loadTwikoo()
} else {
window.loadOtherComment = loadTwikoo
}
})()

去掉加号即为正常缩进,注意第一二行,去掉前面的一格空格,可以看到我加了一些限制条件,和上面同理,这里我就不多说了,有什么问题可以在评论区交流。

最后功能实现。

— 柳影曳曳,清酒孤灯,扬笔撒墨,心境如霜