Python3实现Hexo小助手
前言
Hexo是一个基于nodejs的轻量级博客平台,由于它的简单、主题丰富等特点,收获了大量的拥趸。本博客也是基于Hexo,但是在使用Hexo部署和运营博客的过程中,发现了一些小问题。
问题
1、大部分使用Hexo的博主,都是把代码提交到github仓库。而且为了在不同环境都能方便编写博客,通常会把博客的源码(即博客配置文件,主题配置文件,样式布局文件等)和Hexo生成的代码(即public文件下的代码),同时提交到github仓库的不同分支上,如此在新环境下只要把博客源码check out下来即可。在这种情况下,我们博客配置文件的一些敏感信息,例如第三方插件需要的token,都会明码提交到公共仓库上,有一定的安全风险; 2、在做百度SEO的过程中,最有效的链接提交方式是主动提交,虽然Hexo有相关的插件,但是因为插件是注册到Hexo中的,耦合太重,使用不太灵活。 3、一些个性化的需求,例如本博客同时部署在了七牛云,有时候想看一下代码有没有正确同步,需要查看本地的文件数量和大小与七牛云上的是否相同。
解决
作为程序猿,问题有了,剩下的就是写代码解决了。由于Hexo是nodejs实现的,所以使用nodejs来解决这些问题应该是最符合的,可是nodejs并不在本人的技术栈中(- -)。人生苦短,我用Ptyhon,那么就使用简单的脚本语言Python来解决吧。
— talk is cheap show me the code —
配置文件加解密
这里使用rc4加密算法。
1def rc4(p_key, key_len, pin, d_len):
2 n = 65536
3 s = list(range(n))
4 j = 0
5 for i in range(n):
6 j = (j + s[i] + p_key[i % key_len]) % n # type: int
7 temp = s[i]
8 s[i] = s[j]
9 s[j] = temp
10 i = j = 0
11 out = b''
12 for x in range(d_len):
13 i = i + 1
14 j = (j + s[i]) % n # type: int
15 temp = s[i]
16 s[i] = s[j]
17 s[j] = temp
18 out += struct.pack('H', pin[x] ^ s[(s[i] + s[j]) % n])
19 return out
20
21def coding(data):
22 if len(data) % 2:
23 data += b'\0'
24 d_len = len(data) // 2
25 return struct.unpack(str(d_len) + 'H', data)
26
27
28def un_coding(data):
29 d = b''
30 for i in range(len(data)):
31 d += struct.pack('H', data[i])
32 return d
33
34
35def create_key(_key):
36 pl = len(_key)
37 key = b''
38 r = 0
39 for i in range(32):
40 k = (_key[r % pl] + i) % 256
41 key += struct.pack('B', k)
42 r += 1
43 return key
44
45
46def update_key(_key):
47 key = un_coding(_key)
48 # 循环左移
49 key = key[1:] + struct.pack('B', key[0])
50 tem = 0
51 # 求和
52 for i in range(len(key)):
53 tem += key[i]
54 key_o = b''
55 # Xor
56 for i in range(len(key)):
57 key_o += struct.pack('B', (key[i] ^ tem) % 256)
58 tem += key_o[i] >> 3
59 tem = tem % 256
60 return coding(key_o)
有了加密算法,接下来就是对文件的内容进行加解密,首先需要定义一个公共的密码用于生成密钥:
1cipher = "随便写"
下面的方法使用到colorama,为了在控制台输出颜色,可以自己取舍是否需要。
加密文件:
1def encrypt_file(file):
2 a = file
3 fin = open(a, 'rb')
4 fout = open(a + "-encrypt", 'wb')
5 shutil.copy(a, a + ".bak")
6 key = coding(create_key(cipher.encode()))
7 key = update_key(key)
8 fin.seek(0, 2)
9 filelen = fin.tell()
10 print(Fore.GREEN + 'encrypt ' + file + ', length: ' + str(filelen) + '\n......')
11 fin.seek(0, 0)
12 fout.write(struct.pack('I', filelen))
13 while 1:
14 dd = fin.read(65534)
15 if not dd:
16 break
17 srl = len(dd)
18 if srl % 2:
19 srl += 1
20 dd += b'\0'
21 crc = struct.pack('I', binascii.crc32(dd))
22 dd = coding(dd)
23 x = rc4(key, len(key), dd, len(dd))
24 key = update_key(key)
25 fout.write(struct.pack('H', srl))
26 fout.write(x)
27 fout.write(crc)
28 fin.close()
29 fout.close()
30 shutil.copy(a + "-encrypt", a)
31 os.remove(a + "-encrypt")
32 print(Fore.GREEN + 'OK!\n')
解密文件:
1def decrypt_file(file):
2 a = file
3 fin = open(a, 'rb')
4 fout = open(a + "-decrypt", 'wb')
5 key = coding(create_key(cipher.encode()))
6 key = update_key(key)
7 filelen = struct.unpack('I', fin.read(4))[0]
8 print(Fore.GREEN + 'decrypt ' + file + ', length: ' + str(filelen) + '\n......')
9 while 1:
10 ps = fin.read(2)
11 if not ps:
12 break
13 packsize = struct.unpack('H', ps)[0]
14 dd = fin.read(packsize)
15 dd = coding(dd)
16 x = rc4(key, len(key), dd, len(dd))
17 key = update_key(key)
18 crc = struct.unpack('I', fin.read(4))[0]
19 if binascii.crc32(x) != crc:
20 print('CRC32校验错误!', crc, binascii.crc32(x))
21 sys.exit()
22 fout.write(x)
23 fout.truncate(filelen)
24 fin.close()
25 fout.close()
26 shutil.copy(a + "-decrypt", a)
27 os.remove(a + "-decrypt")
28 print(Fore.GREEN + 'OK!\n')
主动推送百度链接
需要用到的变量:
1# 文章路径
2posts = "source" + os.path.sep + "_posts"
3# 百度中注册的域名
4host = "http://www.ciphermagic.cn/"
5# 百度主动推送接口url
6baidu_url = "http://data.zz.baidu.com/urls?site=你的域名&token=你的token"
原理是获取文章的路径,拼接成url,这里要根据自己的博客配置作相应的修改:
1def submit_baidu_all():
2 urls = ""
3 for file in os.listdir(posts):
4 # 本博客的文章地址是:域名 + 文章名 + html后缀
5 url = host + os.path.split(file)[-1][:-3] + ".html"
6 urls += url + "\n"
7 print(urls)
8 r = requests.post(baidu_url, data=urls)
9 print(r.text)
这里是推送所有的文章,在最后的完整代码中会有推送指定文章的方法,因为需要接收命令行的参数,在最后一起展示,便于阅读。
查看文件数量和大小
1# 获取文件夹大小
2def get_file_size(path):
3 if not os.path.exists(path) or not os.path.isdir(path):
4 print(path + " is not existed or is not folder")
5 sys.exit()
6 total_size = 0
7 total = 0
8 try:
9 filename = os.walk(path)
10 for root, dirs, files in filename:
11 for fle in files:
12 current_path = os.path.join(root, fle)
13 size = os.path.getsize(current_path)
14 total_size += size
15 total = total + 1
16 print(Fore.GREEN + "total files: %d" % total)
17 print(Fore.GREEN + "total size: " + format_size(total_size))
18 except Exception as err:
19 print(err)
20
21
22# 字节bytes转化kb\m\g
23def format_size(_bytes):
24 try:
25 _bytes = float(_bytes)
26 kb = _bytes / 1024
27 except Exception as err:
28 return err
29 if kb >= 1024:
30 m = kb / 1024
31 if m >= 1024:
32 g = m / 1024
33 return "%.2fG" % g
34 else:
35 return "%.2fM" % m
36 else:
37 return "%.2fkb" % kb
完整代码
我们在写博客的时候,通常都是通过Hexo的命令来做一系列的操作,为了保持体验的一致性,我们的python助手也使用命令行操作。操作方式是:
1python hexo-helper.py -c <command> -p <path> [-d]
- -c 操作命令,例如加密、解密、推送链接、查看文件夹
- -p 指定文件路径,需要加解密的文件、需要推送链接的文件、需要查看大小的文件夹
- -d 以默认方式执行,加解密默认操作的文件是网站配置和主题配置文件,链接推送默认是推送所有,查看文件默认是
public文件夹
完整代码:
1import sys
2import getopt
3import struct
4import os
5import binascii
6import shutil
7import requests
8from colorama import init, Fore
9
10# 提示信息
11help_msg = "Usage:hexo.py -c <command> -p <path> [-d]"
12# 密码
13cipher = "你的加密密码"
14# 网站配置文件
15config = "_config.yml"
16# 主题配置文件
17theme_config = "themes" + os.path.sep + "next_my" + os.path.sep + "_config.yml"
18# 文章路径
19posts = "source" + os.path.sep + "_posts"
20# 域名
21host = "http://www.ciphermagic.cn/"
22# 百度主动推送接口url
23baidu_url = "http://data.zz.baidu.com/urls?site=你的域名&token=你的token"
24
25
26def get_file_num_size(options):
27 for o, a in options:
28 if o in "-p":
29 get_file_size(a)
30 elif o in "-d":
31 # 默认获取public文件夹
32 get_file_size("public")
33
34
35# 获取文件夹大小
36def get_file_size(path):
37 if not os.path.exists(path) or not os.path.isdir(path):
38 print(path + " is not existed or is not folder")
39 sys.exit()
40 total_size = 0
41 total = 0
42 try:
43 filename = os.walk(path)
44 for root, dirs, files in filename:
45 for fle in files:
46 current_path = os.path.join(root, fle)
47 size = os.path.getsize(current_path)
48 total_size += size
49 total = total + 1
50 print(Fore.GREEN + "total files: %d" % total)
51 print(Fore.GREEN + "total size: " + format_size(total_size))
52 except Exception as err:
53 print(err)
54
55
56# 字节bytes转化kb\m\g
57def format_size(_bytes):
58 try:
59 _bytes = float(_bytes)
60 kb = _bytes / 1024
61 except Exception as err:
62 return err
63 if kb >= 1024:
64 m = kb / 1024
65 if m >= 1024:
66 g = m / 1024
67 return "%.2fG" % g
68 else:
69 return "%.2fM" % m
70 else:
71 return "%.2fkb" % kb
72
73
74def submit_baidu(options):
75 for o, a in options:
76 if o in "-p":
77 url = host + os.path.split(a)[-1][:-3] + ".html"
78 print(url)
79 r = requests.post(baidu_url, data=url)
80 print(r.text)
81 elif o in "-d":
82 # 默认推送所有链接
83 submit_baidu_all()
84
85
86def submit_baidu_all():
87 urls = ""
88 for file in os.listdir(posts):
89 url = host + os.path.split(file)[-1][:-3] + ".html"
90 urls += url + "\n"
91 print(urls)
92 r = requests.post(baidu_url, data=urls)
93 print(r.text)
94
95
96def encrypt(options):
97 for o, a in options:
98 if o in "-p":
99 encrypt_file(a)
100 elif o in "-d":
101 # 默认加密网站配置文件和主题配置文件
102 encrypt_file(config)
103 encrypt_file(theme_config)
104
105
106def decrypt(options):
107 for o, a in options:
108 if o in "-p":
109 decrypt_file(a)
110 elif o in "-d":
111 # 默认解密网站配置文件和主题配置文件
112 decrypt_file(config)
113 decrypt_file(theme_config)
114
115
116def decrypt_file(file):
117 a = file
118 fin = open(a, 'rb')
119 fout = open(a + "-decrypt", 'wb')
120 key = coding(create_key(cipher.encode()))
121 key = update_key(key)
122 filelen = struct.unpack('I', fin.read(4))[0]
123 print(Fore.GREEN + 'decrypt ' + file + ', length: ' + str(filelen) + '\n......')
124 while 1:
125 ps = fin.read(2)
126 if not ps:
127 break
128 packsize = struct.unpack('H', ps)[0]
129 dd = fin.read(packsize)
130 dd = coding(dd)
131 x = rc4(key, len(key), dd, len(dd))
132 key = update_key(key)
133 crc = struct.unpack('I', fin.read(4))[0]
134 if binascii.crc32(x) != crc:
135 print('CRC32校验错误!', crc, binascii.crc32(x))
136 sys.exit()
137 fout.write(x)
138 fout.truncate(filelen)
139 fin.close()
140 fout.close()
141 shutil.copy(a + "-decrypt", a)
142 os.remove(a + "-decrypt")
143 print(Fore.GREEN + 'OK!\n')
144
145
146def encrypt_file(file):
147 a = file
148 fin = open(a, 'rb')
149 fout = open(a + "-encrypt", 'wb')
150 shutil.copy(a, a + ".bak")
151 key = coding(create_key(cipher.encode()))
152 key = update_key(key)
153 fin.seek(0, 2)
154 filelen = fin.tell()
155 print(Fore.GREEN + 'encrypt ' + file + ', length: ' + str(filelen) + '\n......')
156 fin.seek(0, 0)
157 fout.write(struct.pack('I', filelen))
158 while 1:
159 dd = fin.read(65534)
160 if not dd:
161 break
162 srl = len(dd)
163 if srl % 2:
164 srl += 1
165 dd += b'\0'
166 crc = struct.pack('I', binascii.crc32(dd))
167 dd = coding(dd)
168 x = rc4(key, len(key), dd, len(dd))
169 key = update_key(key)
170 fout.write(struct.pack('H', srl))
171 fout.write(x)
172 fout.write(crc)
173 fin.close()
174 fout.close()
175 shutil.copy(a + "-encrypt", a)
176 os.remove(a + "-encrypt")
177 print(Fore.GREEN + 'OK!\n')
178
179
180def rc4(p_key, key_len, pin, d_len):
181 n = 65536
182 s = list(range(n))
183 j = 0
184 for i in range(n):
185 j = (j + s[i] + p_key[i % key_len]) % n # type: int
186 temp = s[i]
187 s[i] = s[j]
188 s[j] = temp
189 i = j = 0
190 out = b''
191 for x in range(d_len):
192 i = i + 1
193 j = (j + s[i]) % n # type: int
194 temp = s[i]
195 s[i] = s[j]
196 s[j] = temp
197 out += struct.pack('H', pin[x] ^ s[(s[i] + s[j]) % n])
198 return out
199
200
201def coding(data):
202 if len(data) % 2:
203 data += b'\0'
204 d_len = len(data) // 2
205 return struct.unpack(str(d_len) + 'H', data)
206
207
208def un_coding(data):
209 d = b''
210 for i in range(len(data)):
211 d += struct.pack('H', data[i])
212 return d
213
214
215def create_key(_key):
216 pl = len(_key)
217 key = b''
218 r = 0
219 for i in range(32):
220 k = (_key[r % pl] + i) % 256
221 key += struct.pack('B', k)
222 r += 1
223 return key
224
225
226def update_key(_key):
227 key = un_coding(_key)
228 # 循环左移
229 key = key[1:] + struct.pack('B', key[0])
230 tem = 0
231 # 求和
232 for i in range(len(key)):
233 tem += key[i]
234 key_o = b''
235 # Xor
236 for i in range(len(key)):
237 key_o += struct.pack('B', (key[i] ^ tem) % 256)
238 tem += key_o[i] >> 3
239 tem = tem % 256
240 return coding(key_o)
241
242
243if __name__ == "__main__":
244 init(autoreset=True)
245 try:
246 opts, args = getopt.getopt(sys.argv[1:], "c:p:d")
247 except getopt.GetoptError:
248 print(help_msg)
249 sys.exit()
250 if len(opts) == 0:
251 print(help_msg)
252 sys.exit()
253 for opt, arg in opts:
254 if opt in "-c":
255 if arg == "en":
256 encrypt(opts)
257 elif arg == "de":
258 decrypt(opts)
259 elif arg == "baidu":
260 submit_baidu(opts)
261 elif arg == "file":
262 get_file_num_size(opts)
263 else:
264 print(help_msg)
265 sys.exit()
文件保存为hexo-helper.py,放到博客的根目录下,使用方法:
- 加密配置文件
python hexo-helper.py -c en -d - 解密配置文件
python hexo-helper.py -c de -d - 推送百度链接
python hexo-helper.py -c baidu -d - 查看文件夹大小
python hexo-helper.py -c file -d
最后
python作为解析型语言,语法简洁,第三方包丰富,可以快速的满足我们的需求。日后再有新的需求,只需要再添加新的操作符和新的方法即可。最后的最后,本人接触python的时间不是很长,代码写的很丑,如有错误,请不吝指正。
