├── requirements.txt
├── config.ini
├── README.MD
└── butian.py
/requirements.txt:
--------------------------------------------------------------------------------
1 | requests==2.22.0
2 |
--------------------------------------------------------------------------------
/config.ini:
--------------------------------------------------------------------------------
1 | {
2 | "发送邮件的配置(暂时只支持qq邮箱和163邮箱)" : "配置错误会导致发送不了文件",
3 | "send_mail" : "xxxxxxxxxx@qq.com",
4 | "send_mail_stmp_key" : "xxxxxxxxxxxxxx",
5 |
6 | "接收邮件的配置" : "",
7 | "add_company_email" : ["xxxxxxxxx@qq.com","xxxxxxxxxx@163.com","xxxxxxxxx#gmail.com"],
8 | "add_message_email" : "xxxxxxxxx@qq.com",
9 |
10 | "配置需要运行的服务,有专属上架监控,和审核消息通知" : "",
11 | "run_add_company" : 1,
12 | "run_add_message" : 1,
13 |
14 | "补天平台的cookie" : "不监控审核通知的话不用填",
15 | "cookie" : "btuc_xxxxxxxxxxxxxxxxx=xxxxxxxxxxxxxxxxxxxxxxxxxxx",
16 |
17 | "监控延时" : "单位是秒",
18 | "delay" : 600
19 | }
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 | ## 程序说明
2 |
3 | 该程序采用Python编写,可监控补天平台的专属列表变动和审核通知。
4 |
5 | ### 文件说明
6 |
7 | - butian.py 监控程序,采用Python3编写
8 | - check_config 检测配置文件格式是否错误
9 | - log.txt 程序运行日志,包括监控信息和邮件发送日志
10 | - requirements.txt 程序依赖包
11 | - config.ini 配置文件
12 |
13 | ### 拉取程序:
14 |
15 | ```
16 | wget https://github.com/0verf1ow/butian/archive/refs/heads/main.zip
17 | ```
18 |
19 | ### 安装依赖:
20 |
21 | ```
22 | pip intsall -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple some-package
23 | ```
24 |
25 | ### 编辑配置文件说明
26 |
27 | qq邮箱获取smtp口令方法:https://www.cnblogs.com/hai-/p/13490814.html?ivk_sa=1024320u
28 |
29 | 浏览器获取cookie,只需要 "btuc" 开头的这一个,建议在浏览器修改下cookie的有效时间,这样不会被浏览器自动清除cookie,就不用频繁更换配置文件中的cookie.
30 |
31 | 
32 |
33 | ```json
34 | # 这里填的是要发送邮件的邮箱号
35 | "send_mail": "xxxxxxxxxx@qq.com",
36 |
37 | # 这里填的是要发送邮件的邮箱号的SMTP口令
38 | "send_mail_stmp_key": "xxxxxxx",
39 |
40 | # 这里填的是接收专属厂商上架的邮箱号
41 | "add_company_email": ["xxxxxx@qq.com","xxxxxxx@163.com","xxxxxxx@gmail.com"],
42 |
43 | # 这里填的是接收审核通知的邮箱号
44 | "add_message_email": "xxxxx@qq.com",
45 |
46 | # 是否运行监控专属厂商上架监控,1 是运行,0 是不运行
47 | "run_add_company" : 1,
48 |
49 | # 是否运行监控账号漏洞审核信息变动,1 是运行,0 是不运行
50 | "run_add_message" : 1,
51 |
52 | # 补天平台的cookie,如果不监控漏洞审核的话不用填
53 | "cookie" : "btuc_xxxxxxxxxxxxxxxxxxxx=xxxxxxxxxxxxxxxxxxxxxxxxxxxx;",
54 |
55 | # 监控时间间隔,单位(秒),时间不宜太小,否则会导致IP被Ban
56 | "delay" : 600
57 | ```
58 |
59 | ### 运行程序:
60 |
61 | linux(放到后台运行):
62 |
63 | ```linux
64 | nohup python3 butian.py>log 2>&1 &
65 | ```
66 |
67 | windows:
68 |
69 | ```
70 | python3 butian.py
71 | ```
72 |
73 | ### 邮件说明
74 |
75 | 该通知为厂商上架通知:
76 |
77 |
78 |
79 | 该通知为漏洞审核通知:
80 |
81 |
82 |
83 | 该通知为cookie过期告警(请及时更换有效cookie,否则无法监控漏洞审核变动):
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/butian.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin/python3
2 | # coding: utf-8
3 | """
4 | date: 2022.03.13
5 | 监控补天专属厂商上架,或者监控用户漏洞审核信息
6 | 如果报错信息显示:"错误信息:'utf-8' codec can't decode byte 0xbb in position 0: invalid start byte",请放linux上跑
7 | """
8 |
9 | import requests
10 | import smtplib
11 | from email.mime.text import MIMEText
12 | from email.utils import formataddr
13 | import time
14 | import re
15 | import json
16 |
17 |
18 | class Butian():
19 |
20 | # 初始化读取配置
21 | def __init__(self):
22 | # 检查配置文件格式
23 | try:
24 | with open('config.ini', encoding="utf-8") as f:
25 | self.config = json.loads(f.read().strip())
26 | except:
27 | exit("[!] 配置文件格式错误")
28 | self.send_mail = self.config["send_mail"] # 发送邮件通知的Email账号
29 | self.send_mail_stmp_key = self.config["send_mail_stmp_key"] # 发送邮件通知的Smtp口令
30 | self.add_company_email = self.config["add_company_email"] # 需要接收厂商上架通知的邮箱账号
31 | self.add_message_email = self.config["add_message_email"] # 需要接受漏洞审核信息的邮箱账号
32 | self.h = {}
33 | self.h["cookie"] = self.config["cookie"] # 补天平台的cookie
34 | self.delay = self.config["delay"] # 监控延时
35 | self.run_add_company = self.config["run_add_company"] # 是否监控厂商上架
36 | self.run_add_message = self.config["run_add_message"] # 时候监控信息通知
37 | self.run() # 开启运行
38 |
39 | # 程序的主方法
40 | def run(self):
41 | self.number, self.old_name_list = self.get_num() # 获取初始的厂商数量
42 | self.old_id_list = self.get_id_list() # 获取初始的消息id列表
43 | while True:
44 | if self.run_add_company == 1: # 运行厂商监控
45 | self.number, self.old_name_list = self.add_company()
46 |
47 | if self.run_add_message == 1: # 运行消息监控
48 | self.old_id_list, self.new_msg_dict = self.get_new_id(self.old_id_list)
49 | if self.new_msg_dict: # 如果新增列表不为空
50 | self.add_message() # 调用发送邮件通知
51 | else:
52 | s = "[-]审核无变动"
53 | self.output_log(s)
54 |
55 | # 没有选择操作的话直接退出程序
56 | if self.run_add_company == 0 and self.run_add_message == 0: break #
57 |
58 | time.sleep(self.delay)
59 |
60 | # 获取专属厂商列表数量
61 | def get_num(self):
62 | if self.run_add_company == 0: return 0, 0
63 | try:
64 | url = "https://www.butian.net/Reward/corps" # 查询厂商列表的接口
65 | data = {'s': 3, 'p': 1, 'sort': 1, 'token': ''}
66 | r = requests.post(url, data=data)
67 | j = json.loads(r.text)
68 | num = len(j['data']['list']) # 厂商数量
69 | name_list = [] # 厂商列表
70 | for row in j['data']['list']:
71 | d = {'company_name': row['company_name'], 'company_id': row['company_id']}
72 | name_list.append(d)
73 | return num, name_list
74 | except:
75 | time.sleep(30)
76 | return self.get_num()
77 |
78 | # 监控厂商上架
79 | def add_company(self):
80 | num, new_name_list = self.get_num() # 获取网页内的礼品数量
81 | if num > self.number:
82 | """
83 | 对比变化前后的厂商,找出新增的厂商
84 | """
85 | try:
86 | for new in new_name_list:
87 | if not new in self.old_name_list:
88 | company_name = new['company_name']
89 | company_id = new['company_id']
90 | break
91 | except:
92 | company_name = ''
93 | company_id = ''
94 |
95 | msg = "检测到厂商[ {} ]上架,请登录到平台查看 https://www.butian.net/Company/{}".format(company_name, company_id)
96 | ret = self.send_Email(self.send_mail, self.send_mail_stmp_key, self.add_company_email, msg) # 调用发送邮件
97 | if ret:
98 | s = "[+]检测到厂商[ {} ]上架,邮件发送成功".format(company_name)
99 | self.output_log(s)
100 | else:
101 | s = "[!]检测到厂商[ {} ]上架,但邮件发送失败".format(company_name)
102 | self.output_log(s)
103 |
104 | else:
105 | s = "[-]厂商没改动"
106 | self.output_log(s)
107 |
108 | # 把获取到的礼品数返回,这样下次就是对比上次的数量,而不是对比初始值,防止变动后一直发邮件
109 | return num, new_name_list
110 |
111 | # 监控审核信息
112 | def add_message(self):
113 | msg = "有新的服务消息通知:可点击 https://www.butian.net/Message 查看\n"
114 | for id, title in self.new_msg_dict.items():
115 | msg += title + "\n"
116 | ret = self.send_Email(self.send_mail, self.send_mail_stmp_key, self.add_message_email, msg) # 调用发送邮件
117 | if ret:
118 | s = "[+]检测到有新的漏洞审核通知,邮件发送成功"
119 | self.output_log(s)
120 | else:
121 | s = "[!]检测到有新的漏洞审核通知,但邮件发送失败"
122 | self.output_log(s)
123 |
124 | # 获取所有的通知信息
125 | def get_id_list(self):
126 | # 未读消息
127 | data = {"ajax": 1, "id": 0, "status": 0, "page": 1}
128 | # 获取通知的接口,返回对象类型是字符串
129 | try:
130 | r = requests.post("https://www.butian.net/Home/Message/lists", headers=self.h, data=data, )
131 | # 转为json对象好操作
132 | r_data = json.loads(r.text)
133 |
134 | # cookie失效检测
135 | if r_data["status"] == 0:
136 | msg = "[!]Cookie已过期,请从新获取cookie填入配置文件,再从新运行程序"
137 | ret = self.send_Email(self.send_mail, self.send_mail_stmp_key, self.add_company_email, msg) # 调用发送邮件
138 | if ret:
139 | s = "[+]检测到 cookie 失效,邮件发送成功"
140 | self.output_log(s)
141 | self.run_add_message = 0 # 发送不再监控漏洞审核通知的信号
142 | return 0
143 | else:
144 | s = "[!]检测到 cookie 失效,但邮件发送失败"
145 | self.output_log(s)
146 | self.run_add_message = 0 # 发送不再监控漏洞审核通知的信号
147 | return 0
148 |
149 | last_lists = r_data['data']['list'] # 通知列表
150 | last_ids = {} # 通知的id和title的字典
151 | try:
152 | for i in last_lists:
153 | last_ids[i["id"]] = i["title"]
154 | return last_ids # 返回的是最新的id和title字典
155 | except:
156 | return {'1': ''} # 防止用户的消息列表为空
157 | except:
158 | # 网络错误的话,延时60秒再重新获取
159 | time.sleep(60)
160 | return self.get_id_list()
161 |
162 | # 获取新信息的id
163 | def get_new_id(self, old_id_list):
164 | new_id_lists = self.get_id_list() # 获取最新的消息列表
165 | if self.run_add_message == 0: return 0, 0 # 截取
166 | new_msg_dict = {}
167 | for new_id in new_id_lists.keys():
168 | if new_id not in old_id_list.keys(): # id不在上一个返回值的列表中,那么他是新的
169 | new_msg_dict[new_id] = new_id_lists[new_id]
170 | new_id_lists['1'] = ''
171 | return new_id_lists, new_msg_dict # 返回的是最新获取的消息列表,和已经新增的消息列表
172 |
173 | # 发送邮件,暂只支持qq邮箱和163邮箱作为发送箱
174 | def send_Email(self, send_mail, key, person, content):
175 | ret = True
176 | if re.findall(r'@\w+.com', send_mail)[0] == '@qq.com': # 判断是否qq邮箱
177 | smtp_server = "smtp.qq.com"
178 | elif re.findall(r'@\w+.com', send_mail)[0] == '@163.com': # 判断是否网易邮箱
179 | smtp_server = "smtp.163.com"
180 | try:
181 | msg = MIMEText(content, 'plain', 'utf-8')
182 | msg['From'] = formataddr(["监控信息", send_mail]) # 括号里的对应发件人邮箱昵称、发件人邮箱账号
183 | msg['Subject'] = "补天监控" # 邮件的主题,也可以说是标题
184 | server = smtplib.SMTP_SSL(smtp_server, 465) # 发件人邮箱中的SMTP服务器,端口是465
185 | server.login(send_mail, key) # 括号中对应的是发件人邮箱账号、邮箱密码
186 | server.sendmail(send_mail, person, msg.as_string()) # 括号中对应的是发件人邮箱账号、收件人邮箱账号、发送邮件
187 | server.quit() # 关闭连接
188 | except Exception as e: # 如果 try 中的语句没有执行,则会执行下面的 ret=False
189 | ret = False
190 | s = "[!]错误信息:" + str(e)
191 | self.output_log(s)
192 | return ret
193 |
194 | # 日志保存
195 | def output_log(self, s): # 写日志,打印信息
196 | print(s)
197 | with open('log.txt', 'a', encoding='utf-8') as log:
198 | t = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
199 | log.write(t + " " + s + "\n")
200 |
201 |
202 | if __name__ == '__main__':
203 | Butian()
204 |
--------------------------------------------------------------------------------