这篇文章通过一个简单的 Python 脚本演示 Github API 的使用、Python 编写以及 Webhook 的调用。

以此为基础,你可以展开更多的想法:

  • 实时监测 GitHub 的各项信息并推送;
  • NPM 包下载量监测;
  • LeetCode 每日一题;
  • 排名监测;
  • 获取某官网上的重要公告等等。

本文主要介绍:

  1. 获取用户 Github 上的 Star 数量并推送的脚本编写
  2. 根据用户名监测某作者 NPM 包年下载量

获取 Github 上的 Star 数量并推送

GitHub 应用注册

打开你的 GitHub 主页(profile)进入开发者选项:

image.png

选择「新建一个」GitHub APP。根据需要进行相关设置的填写。

image.png

生成密钥:

image.png

记住密钥,待会脚本需要调用。

脚本的编写

这里参考了 songquanpeng/scripts 的实现,并进行修复与改编。

这个 python 脚本(这里命名为 github_stars.py)所做的工作:

  1. 调用 GitHub API(Repositories - GitHub Docs
  2. 统计所有项目的 star 数量
  3. 与上次统计结果进行比较(结果保存在本地)
  4. 调用推送服务发送 POST JSON 格式的请求

执行脚本需要的三个参数:

  • GitHub 用户名
  • GitHub API 令牌
  • 调用的推送服务(将发送 POST)请求

源代码【非面向对象版本】如下:

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
# -*- coding: utf-8 -*-

#!/usr/bin/python3
import json
import requests
import sys

# 存储上次检测的 star 数的文件
filename = "./.star_count"
# Push链接
push_url = ""
# 用户名
username=""

def star_counter(username: str, token: "") -> int:
"""
调用GitHub API获取个人仓库信息,返回统计的stars数
:param username:
:param token:
:return:
"""
# 链接的构建
all_repos_url = f"https://api.github.com/users/{username}/repos?per_page=100"
header = {} if token == "" else {"Authorization": f"bearer {token}"}
# 向GitHub发送请求
res = requests.get(all_repos_url, header)
repos = res.json()
# 解析响应并统计
count = 0
for repo in repos:
count += repo["stargazers_count"]
return count


def load_count() -> int:
'''
加载上一次的统计结果,若无则默认为0
:return:
'''
try:
with open(filename, 'r') as f:
count = int(f.read())
except IOError:
count = 0
return count


def save_count(count: int):
'''
保存统计结果
:param count:
:return:
'''
with open(filename, 'w') as f:
f.write(str(count))


def send_message(msg: str):
"""
发送消息
:param msg:
:return:
"""
headers = {
"Content-Type": "application/json; charset=UTF-8"
}
pyload = {
"title":"GitHub Stars",
"msg":msg
}
response = requests.post(push_url,data=json.dumps(pyload),headers=headers).text
print(response)

def message_construct(last_count,current_count) -> str:
"""
自定义的信息构造函数。
样例:【每日检测】检测到用户uuanqin总star数增加 :) 。[0 -> 6, total: 6]
:param last_count:
:param current_count:
:return:
"""
s = f"【每日检测】检测到用户{username}总star数"
if current_count > last_count:
s += f"增加 :) 。"
else:
s += f"降低 :( 。"
s += f"[{last_count} -> {current_count}, total: {current_count-last_count}]"
return s

def main():
# 参数检查
if len(sys.argv) != 4:
print("Error! This script requires three arguments: GITHUB_USERNAME GITHUB_TOKEN PUSH_URL")
return
# 参数设置
global push_url,username
username = sys.argv[1]
token = sys.argv[2]
push_url = sys.argv[3]

# 获取上一次统计结果
last_count = load_count()
# 获取本次统计结果
current_count = star_counter(username, token)

# 根据数量变化进行判断
if current_count != last_count:
send_message(message_construct(last_count,current_count))
# 只有数量发生变化才保存结果
save_count(current_count)

if __name__ == '__main__':
main()

注意,代码中不要使用奇奇怪怪的字符,比如 emoji 表情等,导致 utf-8 和 gbk 都不能识别。

这里我使用了自己的部署服务。使用示例:

1
python ./github_stars.py uuanqin 9xxxxxxxx7 https://push.uuanqin.top/webhook/8xxxxxxe

把脚本放在 Linux 服务器上,记得测试。

这里使用的 API 似乎有点老了,但是还能用。我在文档中没有找到确切的对应文档说明。

定时执行

新建 cron 文件:

1
2 8 * * * /usr/bin/python /var/www/push_script/github_stars/github_stars.py uuanqin 9xxxxxxxxxxxxxx7 https://push.uuanqin.top/webhook/8xxxxxxxxxxxxxxe

添加定时任务:

1
crontab github_stars.cron

每天早上 8 点 02 分调用该脚本。

在线网站 验证结果:

image.png

不了解 cron 可以查看这篇文章:Linux 使用 cron 创建定时任务

像我的部署服务使用了飞书群机器人,推送效果如下:

image.png

想了解这个项目可以查看这篇文章:[[TBD]]

NPM 下载量的监测

根据上一章介绍的脚本,我们重构一下,改成面向对象的版本。以下脚本能同时支持 GitHub stars 数量的检测以及 NPM 年下载量的检测。注意,临时的数据文件存储在 root 的目录下。

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
# -*- coding: utf-8 -*-
import datetime
#!/usr/bin/python3
import json
import os

import requests
import sys
from abc import ABC, abstractmethod

from dateutil.relativedelta import relativedelta


class Obj:
# 存储统计数的文件名
filename = ""
title = ""
last_count = 0
current_count = 0
# Push链接
push_url = ""

def __init__(self,push_url):
self.push_url = push_url
self.load_count()
self.count()


@abstractmethod
def count(self):
pass

@abstractmethod
def message_construct(self):
pass


def load_count(self):
'''
加载上一次的统计结果,若无则默认为0
'''
try:
with open(self.filename, 'r') as f:
count = int(f.read())
except IOError:
count = 0
self.last_count = count


def save_count(self):
'''
保存统计结果
'''
with open(self.filename, 'w') as f:
f.write(str(self.current_count))

def send_message(self, msg: str):
"""
发送消息
"""
headers = {
"Content-Type": "application/json; charset=UTF-8"
}
pyload = {
"title": self.title,
"msg": msg
}
response = requests.post(self.push_url, data=json.dumps(pyload), headers=headers).text
print(response)

def push(self):
# 根据数量变化进行判断
current_count = self.current_count
last_count = self.last_count
if current_count != last_count:
self.send_message(self.message_construct())
# 只有数量发生变化才保存结果
self.save_count()


class GitHubStars(Obj):

# 用户名
username = ""
github_token = ""
def __init__(self,push_url,token,username):

self.filename = os.path.join(os.getcwd(),".star_count")
self.title = "GitHub Stars Count"
self.github_token = token
self.username = username
super().__init__(push_url)

def count(self):
"""
调用GitHub API获取个人仓库信息,返回统计的stars数
"""
# 链接的构建
all_repos_url = f"https://api.github.com/users/{self.username}/repos?per_page=100"
header = {} if self.github_token == "" else {"Authorization": f"bearer {self.github_token}"}
# 向GitHub发送请求
res = requests.get(all_repos_url, header)
repos = res.json()
# 解析响应并统计
count = 0
for repo in repos:
count += repo["stargazers_count"]
self.current_count = count

def message_construct(self) -> str:
"""
自定义的信息构造函数。
样例:【每日检测】检测到用户uuanqin总star数增加 :) 。[0 -> 6, total: 6]
"""
s = f"【每日检测】检测到用户{self.username}总star数"
if self.current_count > self.last_count:
s += f"增加 :) 。"
else:
s += f"降低 :( 。"
s += f"[{self.last_count} -> {self.current_count}, total: {self.current_count-self.last_count}]"
return s

class NpmAuthorDownloads(Obj):
username = ""
def __init__(self,push_url,username):
self.filename = os.path.join(os.getcwd(),".npm_count")
self.title = "GitHub Stars Count"
self.username = username
super().__init__(push_url)

def count(self):
nowtime = datetime.datetime.now()
yesterdate = nowtime - datetime.timedelta(days=+1)
last_year = yesterdate - relativedelta(years=1)
yesterdate_strf=yesterdate.strftime('%Y-%m-%d')
last_year_strf=last_year.strftime('%Y-%m-%d')
url = f"https://npm-stat.com/api/download-counts?author={self.username}&from={last_year_strf}&until={yesterdate_strf}"
# 向GitHub发送请求
res = requests.get(url)
repos = res.json()
# 解析响应并统计
count = 0
for p,con in repos.items():
for d,v in con.items():
count += v
self.current_count = count


def message_construct(self):
"""
自定义的信息构造函数。
样例:【每日检测】检测到用户uuanqin总star数增加 :) 。[0 -> 6, total: 6]
"""
s = f"【每日检测】检测到用户{self.username}的NPM账户去年包下载量发生变动。"
s += f"[{self.last_count} -> {self.current_count}, total: {self.current_count - self.last_count}]"
return s


def main():
# 参数检查
if len(sys.argv) <2:
print("Error! Arguments is illegal")
return

type = sys.argv[1]
obj = None

if type=="github_stars" and len(sys.argv) != 5:
print("Error! This script requires Four arguments: TYPE GITHUB_USERNAME GITHUB_TOKEN PUSH_URL")
return
elif type=="npm_downloads" and len(sys.argv) != 4:
print("Error! This script requires Four arguments: TYPE NPM_AUTHOR PUSH_URL")
return
elif type!= "github_stars" and type!= "npm_downloads":
print("Error! Arguments is illegal")
return


if type == "github_stars":
obj = GitHubStars(sys.argv[4],sys.argv[3],sys.argv[2])
elif type == "npm_downloads":
obj = NpmAuthorDownloads(sys.argv[3],sys.argv[2])

obj.push()


if __name__ == '__main__':
main()

cron 的其它应用

本文参考