预览

应用中心

加密工具

应用中心

新建 themes\butterfly\layout\includes\apps.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
#apps
.apps-head
span 应用中心
.close-btn(onclick="ctrl.hideAPPs()" href="javascript:void(0);")
i.fas.fa-circle-xmark
.apps-content
.app-box
.app-icon(style="background:url(https://xxx.webp) no-repeat 100% / cover;")
a.app-link(href='https://gavin-chen.top', title='主线路', target='_blank', one-link-mark='yes')
span.app-name 主线路
.app-box
.app-icon(style="background:url(https://xxx.webp) no-repeat 100% / cover;")
a.app-link(href='https://blog.cansin.top', title='Vercel线路', target='_blank', one-link-mark='yes')
span.app-name Vercel线路
.app-box
.app-icon(style="background:url(https://xxx.webp) no-repeat 100% / cover;")
a.app-link(href='https://netlify.gavin-chen.top', title='Netlify线路', target='_blank', one-link-mark='yes')
span.app-name Netlify线路
.app-box
.app-icon(style="background:url(https://xxx.png) no-repeat 100% / cover;")
a.app-link(href='https://zeabur.cansin.top', title='Zeabur线路', target='_blank', one-link-mark='yes')
span.app-name Zeabur线路
.app-box
.app-icon(style="background:url(https://xxx.webp) no-repeat 100% / cover;")
a.app-link(href='https://blogdrive.gavin-chen.top', title='藏兵谷', target='_blank', rel='noopener nofollow', one-link-mark='yes')
span.app-name 藏兵谷
.app-box
.app-icon(style="background:url(https://xxx.webp) no-repeat 100% / cover;")
a.app-link(href='https://music.cansin.top', title='司音堂', target='_blank', rel='noopener nofollow', one-link-mark='yes')
span.app-name 司音堂
.app-box
.app-icon(style="background:url(https://xxx.webp) no-repeat 100% / cover;")
a.app-link(href='https://music.gavin-chen.top', title='幻音坊', target='_blank', rel='noopener nofollow', one-link-mark='yes')
span.app-name 幻音坊
.app-box
.app-icon(style="background:url(https://xxx.webp) no-repeat 100% / cover;")
a.app-link(onclick='ctrl.toggleWinbox("encryption")', title='加密工具', target='_blank', rel='noopener nofollow', one-link-mark='yes')
span.app-name 加密工具
.apps-mask(onclick="ctrl.hideAPPs()" href="javascript:void(0);" rel="external nofollow")

在 themes\butterfly\layout\includes\layout.pug 末尾引入

1
2
    include ./console.pug
+ include ./apps.pug

js部分通过合适的按钮点击打开应用中心,这里通过左上角田字形图标打开,手机端入口在侧滑菜单的右上角。CSS 通过 F12 自取。

加密工具

“加密工具”是应用中心第一款应用,窗口部分采用开源框架winbox实现,加密算法采用 Crypto-JS 库实现。

首先在主题配置文件 _config.butterfly.yml 中引入 winbox

1
2
3
inject:
bottom:
+ - <script src="https://unpkg.com/winbox@0.2.82/dist/winbox.bundle.min.js"></script> # 窗口

自定义 xxx.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
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
var ctrl = {
toggleWinbox(app) {
if (document.getElementById('winboxForApps')) winbox.toggleClass('hide');
else ctrl.createWinboxForApps(app);
},
resizeWinbox() {
let box = document.getElementById('winboxForApps');
if (!box || box.classList.contains('min') || box.classList.contains('max')) return // 2023-02-10更新
var offsetWid = document.documentElement.clientWidth;
if (offsetWid <= 768) {
winbox.resize(offsetWid * 0.95 + "px", "80%").move("center", "center");
} else {
winbox.resize(offsetWid * 0.6 + "px", "70%").move("center", "center");
}
},
createWinboxForApps(app) {
var title = '',
className = '',
html = '';
switch (app) {
case "encryption":
title = '参星阁 - 加密工具';
className = 'encryption';
html=`
<div class="select-items">
<select class="select-item type">
<option class="opt" value="MD5">MD5</option>
<option class="opt" value="SHA1">SHA1</option>
<option class="opt" value="SHA3">SHA3</option>
<option class="opt" value="SHA224">SHA224</option>
<option class="opt" value="SHA256">SHA256</option>
<option class="opt" value="SHA384">SHA384</option>
<option class="opt" value="SHA512">SHA512</option>
<option class="opt" value="RIPEMD160">RIPEMD160</option>
<option class="opt" value="AESEncode">AES加密</option>
<option class="opt" value="AESDecode">AES解密</option>
</select>
<select class="select-item code">
<option class="opt" value="Hex">Hex</option>
<option class="opt" value="Base64">Base64</option>
</select>
<select class="select-item case">
<option class="opt" value="Lower">小写</option>
<option class="opt" value="Upper">大写</option>
</select>
<select class="select-item mode hide">
<option class="opt" value="CBC">CBC</option>
<option class="opt" value="CFB">CFB</option>
<option class="opt" value="CTR">CTR</option>
<option class="opt" value="OFB">OFB</option>
<option class="opt" value="ECB">ECB</option>
</select>
<select class="select-item pad hide">
<option class="opt" value="Pkcs7">Pkcs7</option>
<option class="opt" value="Iso97971">Iso97971</option>
<option class="opt" value="Iso10126">Iso10126</option>
<option class="opt" value="AnsiX923">AnsiX923</option>
<option class="opt" value="ZeroPadding">ZeroPadding</option>
<option class="opt" value="NoPadding">NoPadding</option>
</select>
<input type="text" class="select-item key hide" placeholder="密钥">
<input type="text" class="select-item iv hide" placeholder="偏移量">
</div>
<textarea autocomplete="off" rows="6" placeholder="请输入或者粘贴需要处理的文本" class="inner" style="min-height: 32.6px;"></textarea>
<textarea autocomplete="off" rows="6" placeholder="处理结果" class="outer lock" style="min-height: 32.6px;" disabled="disabled"></textarea>
<div class="btns">
<button class="btn blue" type="button" onclick="transcode()">转码</button>
<button class="btn green" type="button" onclick="copyTranscode()">复制</button>
<button class="btn red" type="button" onclick="clearTranscode()">清空</button>
</div>
`;
document.head.appendChild(Object.assign(document.createElement("script"), { src: "https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/crypto-js/4.1.1/crypto-js.min.js", id: "crypto-js" }));
break;
default:
title = '参星阁 - App';
className = '';
html = '暂无应用程序';
}
let div = document.createElement('div');
document.body.appendChild(div);
winbox = WinBox({
id: 'winboxForApps',
class: className,
index: 989,
title: title,
x: "center",
y: "center",
minwidth: '300px',
height: "60%",
background: '#29516C',
onmaximize: () => { div.innerHTML = `<style>body::-webkit-scrollbar {display: none;}div#winboxForApps {width: 100% !important;}</style>` },
onrestore: () => { div.innerHTML = '' }
});
ctrl.resizeWinbox();
window.addEventListener('resize', ctrl.resizeWinbox);
winbox.body.innerHTML = html;
document.head.appendChild(Object.assign(document.createElement("script"), { src: "/js/" + className + ".js", id: "appScript" }));
document.querySelector('.wb-header .wb-close').addEventListener('click', ()=>{
var script = document.getElementById('appScript');
if (script) document.head.removeChild(script);
var script1 = document.getElementById('crypto-js');
if (script1) document.head.removeChild(script1);
})
}
}

自定义 encryption.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
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
var _type = document.querySelector(".encryption .select-item.type"),
_code = document.querySelector(".encryption .select-item.code"),
_case = document.querySelector(".encryption .select-item.case"),
_mode = document.querySelector(".encryption .select-item.mode"),
_pad = document.querySelector(".encryption .select-item.pad"),
_key = document.querySelector(".encryption .select-item.key"),
_iv = document.querySelector(".encryption .select-item.iv"),
textIn = document.querySelector('.encryption .inner'),
textOut = document.querySelector('.encryption .outer');
textOut.addEventListener("input", ()=>{
if (textOut.value == '') {
textOut.disabled = true;
textOut.classList.add('lock');
} else {
textOut.disabled = false;
textOut.classList.remove('lock');
}
})
_type.addEventListener('change', ()=>{
if (_type.value == 'AESEncode') {
_case.classList.contains('hide') ? null : _case.classList.add('hide'); // 隐藏 大小写
_code.classList.contains('hide') ? _code.classList.remove('hide') : null; // 显示
_mode.classList.contains('hide') ? _mode.classList.remove('hide') : null;
_pad.classList.contains('hide') ? _pad.classList.remove('hide') : null;
_key.classList.contains('hide') ? _key.classList.remove('hide') : null;
_iv.classList.contains('hide') ? _iv.classList.remove('hide') : null;
} else if (_type.value == 'AESDecode') {
_code.classList.contains('hide') ? null : _code.classList.add('hide'); // 隐藏 编码、大小写
_case.classList.contains('hide') ? null : _case.classList.add('hide');
_mode.classList.contains('hide') ? _mode.classList.remove('hide') : null; // 显示
_pad.classList.contains('hide') ? _pad.classList.remove('hide') : null;
_key.classList.contains('hide') ? _key.classList.remove('hide') : null;
_iv.classList.contains('hide') ? _iv.classList.remove('hide') : null;
} else {
_code.classList.contains('hide') ? _code.classList.remove('hide') : null; // 显示
_case.classList.contains('hide') ? _case.classList.remove('hide') : null;
_mode.classList.contains('hide') ? null : _mode.classList.add('hide'); // 隐藏
_pad.classList.contains('hide') ? null : _pad.classList.add('hide');
_key.classList.contains('hide') ? null : _key.classList.add('hide');
_iv.classList.contains('hide') ? null : _iv.classList.add('hide');
}
})
_mode.addEventListener('change', ()=>{
if (_mode.value != 'ECB') {
if (_iv.classList.contains('lock')) {
_iv.classList.remove('lock');
_iv.disabled = false;
};
} else {
if (!_iv.classList.contains('lock')) {
_iv.classList.add('lock');
_iv.disabled = true;
};
}
})

function copyTranscode() {
textOut.select();
document.execCommand('copy');
}

function clearTranscode() {
document.querySelector('.encryption .inner').value = '';
textOut.value = '';
textOut.disabled = true;
textOut.classList.add('lock');
}

function transcode() {
var a = '',
b = '',
__mode = '',
__pad = '',
__text = '',
__key = CryptoJS.enc.Utf8.parse(_key.value),
__iv = CryptoJS.enc.Utf8.parse(_iv.value);
switch (_mode.value) {
case "CBC": __mode = CryptoJS.mode.CBC; break;
case "CFB": __mode = CryptoJS.mode.CFB; break;
case "CTR": __mode = CryptoJS.mode.CTR; break;
case "OFB": __mode = CryptoJS.mode.OFB; break;
case "ECB": __mode = CryptoJS.mode.ECB; break;
default: __mode = CryptoJS.mode.CBC;
}
switch (_pad.value) {
case "Pkcs7": __pad = CryptoJS.pad.Pkcs7; break;
case "Iso97971": __pad = CryptoJS.pad.Iso97971; break;
case "Iso10126": __pad = CryptoJS.pad.Iso10126; break;
case "AnsiX923": __pad = CryptoJS.pad.AnsiX923; break;
case "ZeroPadding": __pad = CryptoJS.pad.ZeroPadding; break;
case "NoPadding": __pad = CryptoJS.pad.NoPadding; break;
default: __pad = CryptoJS.pad.Pkcs7;
}
switch (_type.value) {
case "MD5":
a = CryptoJS.MD5(textIn.value);
b = _code.value == 'Hex' ? a.toString() : CryptoJS.enc.Base64.stringify(a);
textOut.value = _case.value == 'Lower' ? b : b.toUpperCase();
break;
case "SHA1":
a = CryptoJS.SHA1(textIn.value);
b = _code.value == 'Hex' ? a.toString() : CryptoJS.enc.Base64.stringify(a);
textOut.value = _case.value == 'Lower' ? b : b.toUpperCase();
break;
case "SHA3":
a = CryptoJS.SHA3(textIn.value);
b = _code.value == 'Hex' ? a.toString() : CryptoJS.enc.Base64.stringify(a);
textOut.value = _case.value == 'Lower' ? b : b.toUpperCase();
break;
case "SHA224":
a = CryptoJS.SHA224(textIn.value);
b = _code.value == 'Hex' ? a.toString() : CryptoJS.enc.Base64.stringify(a);
textOut.value = _case.value == 'Lower' ? b : b.toUpperCase();
break;
case "SHA256":
a = CryptoJS.SHA256(textIn.value);
b = _code.value == 'Hex' ? a.toString() : CryptoJS.enc.Base64.stringify(a);
textOut.value = _case.value == 'Lower' ? b : b.toUpperCase();
break;
case "SHA384":
a = CryptoJS.SHA384(textIn.value);
b = _code.value == 'Hex' ? a.toString() : CryptoJS.enc.Base64.stringify(a);
textOut.value = _case.value == 'Lower' ? b : b.toUpperCase();
break;
case "SHA512":
a = CryptoJS.SHA512(textIn.value);
b = _code.value == 'Hex' ? a.toString() : CryptoJS.enc.Base64.stringify(a);
textOut.value = _case.value == 'Lower' ? b : b.toUpperCase();
break;
case "RIPEMD160":
a = CryptoJS.RIPEMD160(textIn.value);
b = _code.value == 'Hex' ? a.toString() : CryptoJS.enc.Base64.stringify(a);
textOut.value = _case.value == 'Lower' ? b : b.toUpperCase();
break;
case "AESEncode":
__text = CryptoJS.enc.Utf8.parse(textIn.value);
a = CryptoJS.AES.encrypt(__text, __key, { iv: __iv, mode: __mode, padding: __pad });
b = _code.value == 'Hex' ? CryptoJS.enc.Hex.stringify(a.ciphertext) : a.toString();
textOut.value = b;
break;
case "AESDecode":
// /^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/
var base64Regex = /^[A-Za-z0-9+/]*={1,2}$/,
hexRegex = /^(?:[0-9a-fA-F]{4})*$/;
if (base64Regex.test(textIn.value)) {
console.log('base64');
__text = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Base64.parse(textIn.value));
textOut.value = CryptoJS.AES.decrypt(__text, __key, { iv: __iv, mode: __mode, padding: __pad }).toString(CryptoJS.enc.Utf8);
break;
} else if (hexRegex.test(textIn.value)) {
console.log('hex');
__text = CryptoJS.enc.Hex.parse(textIn.value);
textOut.value = CryptoJS.AES.decrypt({ ciphertext: __text }, __key, { iv: __iv, mode: __mode, padding: __pad }).toString(CryptoJS.enc.Utf8);
break;
} else {
console.log('数据错误');
alert('数据错误');break;
}
default: ;
}
textOut.disabled = false;
textOut.classList.remove('lock');
}

参考资料

winbox 部分代码参考 Leonus 的文章:

winbox 开源仓库:

CryptoJS 参考文档: