Py自动更新脚本的简单思路

终于又有新文章了呢!好耶

这半年来出现了一个alist的开源程序,用于分享阿里网盘的资源及获取直链,深受百度祸害的我终于找到了另一款好用白嫖的云盘,我直接化为alist的内测用户,并提供了巨大贡献—-

(指从别处复制过来两行代码然后pr,雾

并稍微赞助了一点点 在群内表现积极(,不小心当上了alist的管理员

但由于阿里云盘还在内测阶段,频繁 (偶尔会更换api接口什么的,导致现在alist不会处于一个稳定版本的状态,经常更新,而我又是一个急性子玩家,一有新版本恨不得马上跟上更新,但是又很懒(废人一个,而且刚好趁着暑假稍微了解了一下python,感觉可以写一个脚本上上手。

稍微有一点简单的思路:

写个脚本用服务器定时运行检查更新,检测到更新就更新,没有更新就当作无事发生。

好像还挺简单的,请求github的api获取到最新提交的commit,然后和本地对比

当alist后端是一个二进制文件,怎么才能判断本地版本和新版本是否一样呢?

对于这个我也有一点简单的想法,把本地的版本信息放在一个文件里,然后和新版做对比即可。然后写一个脚本,如果本地没有这个文件,就直接更新,然后把新版本的commit信息放进这个文件里:

1
2
3
4
5
6
if not os.path.exists("/opt/alist/version"):
print("无法获取后端版本信息")
print("创建文件并写入...")
version = open("/opt/alist/version", "w")
version.write("无信息")
version.close()

我在alist同文件夹内新建一个version用来储存本地的commit。然后用requests获取新的版本commit:

1
2
3
import requests

latest_commit = requests.get("https://api.github.com/repos/Xhofe/alist/commits/main").json()['sha']

返回的json文件内第一行sha就是最新的commit,分别设置一个变量然后通过if对比:

1
2
3
4
5
6
7
8
9
10
11
current_version = open("/opt/alist/version").read()

latest_date = requests.get("https://api.github.com/repos/Xhofe/alist/commits/main").json()['commit']['author']['date']
if current_version == latest_commit:
print("后端当前版本已为最新版:%s"%current_version)
print("后端更新日期:%s"%latest_date)
print("后端检测完成")
else:
print("后端获取到%s版本更新"%(latest_commit))
print("后端最新版本更新日期为%s"%(latest_date))
print("后端准备更新...")

我在上面又加一个最新commit的时间,这样子写会请求两次api,对于GitHub在国内的表现….最好还是改一下

1
2
3
4
alist = requests.get("https://api.github.com/repos/Xhofe/alist/commits/main").json()

latest_commit = alist['sha']
latest_date = alist['commit']['author']['date']

open的使用方法也不是很规范,文件打开之后不close()会有一些小问题,修改成用with或者添加一个close:

1
2
3
4
5
6
7
file = open("/opt/alist/version")
current_version = file.read()
file.close()


with open("/opt/alist/version") as f:
current_version = f.read()

解释来自某位知乎网友:点我直达

“文件I / O的首选方法是使用with关键字;这样可以确保一旦阅读或写作完成后,python能够帮你处理剩余的文件关闭、资源清理等工作”

很有精神!接下来只要写怎么更新就行了

因为我获取的是commit而不是release里的tag就说明了一件事。。。。我需要把源码下载下来编译而不是直接下载二进制。

而python里运行shell脚本的逻辑很奇怪,,,可能是我没找到真正的食用方法,我决定再搞一个shell脚本,然后直接把大白写的一键安装脚本里的东西扒进来,借鉴一下大白写的安装脚本中编译的部分((((链接直达

1
2
3
4
os.system("systemctl stop alist")
feedback = os.popen("bash -c /opt/update_alist.sh").read()
print(feedback)
os.system("systemctl start alist")

我又想起了上面的请求GitHub api,前段时间电信的出国线路有点问题,我决定加一个try,防止出错都出的不明不白的:

1
2
3
4
5
6
7
8
try:
r = requests.get(choice)
except Exception as ex:
print("请求GitHub api异常,错误信息:%s"%ex)
exit(1)
else:
r.raise_for_status()
rs = r.json()

r.raise_for_status()作用是,如果返回状态码在400-500会报错,其他则正常通过,不会有任何信息,所以json()就只能放在后面。加一个try这样如果访问github有什么问题也会打印出来。

既然都打算自动化了,就干脆把自动rebuild的功能也添加进来:

1
2
c = requests.post("http://127.0.0.1:5244/api/rebuild", json={"path" : "drive", "password":"password", "depth":-1}).json()['message']
print("重建drive盘:%s"%c)

如果有多个盘建议加一个sleep(10),避免短时间请求太多次。

以此类推,我把前端自动更新也加了上去,因为我前端在GitHub action有自动编译并自动上传到仓库,

然后通过cloudflare pages和本地服务器组成了一个全球加速,所以只需要进入本地的仓库git pull一下就好,很简单:

1
os.popen("cd /www/wwwroot/alist/ && git pull https://ghproxy.com/https://github.com/zsbai/alist.git master").read()

因为是在服务器上跑,我想让他有检测到更新之后通知我,我又加了一个邮件通知的函数:如果web或者后端其中一个有更新,就通知我,如果两个都没有就不通知,两个都有就都通知。为了实现这个我加了一个词典,如果执行了任意一个更新函数就把0变成1:

1
2
3
4
DATA = {
'web':'0',
'back':'0',
}

然后用两个if套一下:

1
2
3
4
5
6
7
if DATA['web'] == 0:
if DATA['back'] == 0:
exit(0)
else:
email(update-backward)
else:
email(update-web)

这样好像不太行啊,如果前后端都有更新呢?这时候我想起了学校学的逻辑门,既然都学了不在生活中用一下都说不过去-然后在网上找了个OR门写了上去:

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
#OR门
def OR(*args):
expression = ' or '.join(['{}'.format(k) for k in args])
expression = 'truth({})'.format(expression)
return eval(expression)
def email():
mail_host="smtp.exmail.qq.com" #设置服务器
mail_user="xxxxxx" #用户名
mail_pass="xxxx" #口令

sender = 'xxxxx'
receivers = ['xxx@qq.com'] # 接收邮件,可设置为你的QQ邮箱或者其他邮箱

if DATA["web"] == "1":


msg_web = """
前端获取到%s更新,更新已完成,更新时间为%s

"""%(latest_web_commit,latest_web_date)
else:
msg_web = ""

if DATA["back"] == "1":
msg_back = '''
后端获取到%s更新,更新已完成,更新时间为%s
'''%(latest_back_commit
,latest_back_date)
else:
msg_back = ""
msg = msg_web + "\n" + msg_back


message = MIMEText(msg, 'plain', 'utf-8')
message['From'] = Header("来自萌萌哒的可爱服务器", 'utf-8')
message['To'] = Header("白鹭", 'utf-8')

subject = '更新日志'
message['Subject'] = Header(subject, 'utf-8')
try:
smtpObj = smtplib.SMTP()
smtpObj.connect(mail_host, 465) # 25 为 SMTP 端口号
smtpObj.login(mail_user,mail_pass)
smtpObj.sendmail(sender, receivers, message.as_string())
print ("邮件发送成功")
except smtplib.SMTPException:
print ("Error: 无法发送邮件")

if OR(DATA["back"],DATA['web']):
print("更新完成,邮件通知")
email()

OR逻辑门的作用是如果前后端都没有更新(输入都是0),就不执行email这个函数了(输出也是0);如果前后端有一个有更新(输入中有一个是1),就执行email(输出就是1)。

这里我总感觉有更简单的方法,当无奈能力有限,我现在顶多算是个小小白,如果各位有更简单的办法请告知。

但email不知为何总是抽风,测试的时候好好的,实际运行起来就总是超时,或者发送很长时间最后出个无法发送邮件,但这个也不是必需品,就先这么用着吧

再搞一个check的函数,万一以后alist不是装在/opt/下就出问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def check():

#检查/opt/
if not os.path.exists("/opt/alist/"):
print("alist未安装在/opt/路径下")
exit(1)

#检查版本文件是否存在
if not os.path.exists("/opt/alist/version-web"):
print("无法获取web版本信息")
print("准备更新...")
print("创建版本信息文件并写入...")
version = open("/opt/alist/version-web", "w")
version.write("无信息")
version.close()

if not os.path.exists("/opt/alist/version"):
print("无法获取后端版本信息")
print("准备更新...")
print("创建版本信息文件并写入...")
version1 = open("/opt/alist/version", "w")
version1.write("无信息")
version1.close()

最后就是排序的问题,获取本地的commit要放在check之后;如果要本地测试的话,记得加一个test函数并放在check之前,不然会出问题,最后挂在服务器上每天跑一次,圆满结束~


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!