1
|
|
|
# -*- coding: utf-8 -*- |
2
|
|
|
""" |
3
|
|
|
cron: 48 */12 * * * |
4
|
|
|
new Env('HOSTLOC'); |
5
|
|
|
""" |
6
|
|
|
|
7
|
|
|
import random |
8
|
|
|
import re |
9
|
|
|
import textwrap |
10
|
|
|
import time |
11
|
|
|
|
12
|
|
|
import requests |
13
|
|
|
from pyaes import AESModeOfOperationCBC |
14
|
|
|
from requests import Session as req_Session |
15
|
|
|
|
16
|
|
|
from notify_mtr import send |
17
|
|
|
from utils import get_data |
18
|
|
|
|
19
|
|
|
desp = "" # 空值 |
20
|
|
|
|
21
|
|
|
|
22
|
|
|
def log(info: str): |
23
|
|
|
global desp |
24
|
|
|
desp = desp + info + "\n" |
25
|
|
|
|
26
|
|
|
|
27
|
|
|
class HOSTLOC: |
28
|
|
|
def __init__(self, check_items): |
29
|
|
|
self.check_items = check_items |
30
|
|
|
self.home_page = "https://hostloc.com/forum.php" |
31
|
|
|
|
32
|
|
|
# 随机生成用户空间链接 |
33
|
|
|
@staticmethod |
34
|
|
|
def randomly_gen_uspace_url() -> list: |
35
|
|
|
url_list = [] |
36
|
|
|
# 访问小黑屋用户空间不会获得积分、生成的随机数可能会重复,这里多生成两个链接用作冗余 |
37
|
|
|
for _ in range(12): |
38
|
|
|
uid = random.randint(10000, 50000) |
39
|
|
|
url = f"https://hostloc.com/space-uid-{uid}.html" |
40
|
|
|
url_list.append(url) |
41
|
|
|
return url_list |
42
|
|
|
|
43
|
|
|
# 使用Python实现防CC验证页面中JS写的的toNumbers函数 |
44
|
|
|
@staticmethod |
45
|
|
|
def toNumbers(secret: str) -> list: |
46
|
|
|
return [int(value, 16) for value in textwrap.wrap(secret, 2)] |
47
|
|
|
|
48
|
|
|
# 不带Cookies访问论坛首页,检查是否开启了防CC机制,将开启状态、AES计算所需的参数全部放在一个字典中返回 |
49
|
|
|
def check_anti_cc(self) -> dict: |
50
|
|
|
result_dict = {} |
51
|
|
|
headers = { |
52
|
|
|
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " |
53
|
|
|
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36" |
54
|
|
|
} |
55
|
|
|
home_page = self.home_page |
56
|
|
|
r = requests.get(home_page, headers=headers) |
57
|
|
|
aes_keys = re.findall(r'toNumbers\("(.*?)"\)', r.text) |
58
|
|
|
cookie_name = re.findall('cookie="(.*?)="', r.text) |
59
|
|
|
|
60
|
|
|
if len(aes_keys) != 0: # 开启了防CC机制 |
61
|
|
|
log("检测到防 CC 机制开启!") |
62
|
|
|
if ( |
63
|
|
|
len(aes_keys) != 3 or len(cookie_name) != 1 |
64
|
|
|
): # 正则表达式匹配到了参数,但是参数个数不对(不正常的情况) |
65
|
|
|
result_dict["ok"] = 0 |
66
|
|
|
else: # 匹配正常时将参数存到result_dict中 |
67
|
|
|
result_dict["ok"] = 1 |
68
|
|
|
result_dict["cookie_name"] = cookie_name[0] |
69
|
|
|
result_dict["a"] = aes_keys[0] |
70
|
|
|
result_dict["b"] = aes_keys[1] |
71
|
|
|
result_dict["c"] = aes_keys[2] |
72
|
|
|
|
73
|
|
|
return result_dict |
74
|
|
|
|
75
|
|
|
# 在开启了防CC机制时使用获取到的数据进行AES解密计算生成一条Cookie(未开启防CC机制时返回空Cookies) |
76
|
|
|
def gen_anti_cc_cookies(self) -> dict: |
77
|
|
|
cookies = {} |
78
|
|
|
anti_cc_status = self.check_anti_cc() |
79
|
|
|
|
80
|
|
|
if anti_cc_status: # 不为空,代表开启了防CC机制 |
81
|
|
|
if anti_cc_status["ok"] == 0: |
82
|
|
|
log("防 CC 验证过程所需参数不符合要求,页面可能存在错误!") |
83
|
|
|
else: # 使用获取到的三个值进行AES Cipher-Block Chaining解密计算以生成特定的Cookie值用于通过防CC验证 |
84
|
|
|
log("自动模拟计尝试通过防 CC 验证") |
85
|
|
|
a = bytes(self.toNumbers(anti_cc_status["a"])) |
86
|
|
|
b = bytes(self.toNumbers(anti_cc_status["b"])) |
87
|
|
|
c = bytes(self.toNumbers(anti_cc_status["c"])) |
88
|
|
|
cbc_mode = AESModeOfOperationCBC(a, b) |
89
|
|
|
res = cbc_mode.decrypt(c) |
90
|
|
|
|
91
|
|
|
name = anti_cc_status["cookie_name"] |
92
|
|
|
cookies[name] = res.hex() |
93
|
|
|
|
94
|
|
|
return cookies |
95
|
|
|
|
96
|
|
|
# 登录账户 |
97
|
|
|
def login(self, username: str, password: str) -> req_Session: |
98
|
|
|
headers = { |
99
|
|
|
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " |
100
|
|
|
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36", |
101
|
|
|
"origin": "https://hostloc.com", |
102
|
|
|
"referer": self.home_page, |
103
|
|
|
} |
104
|
|
|
login_url = ( |
105
|
|
|
"https://hostloc.com/member.php?mod=logging&action=login&" |
106
|
|
|
"loginsubmit=yes&infloat=yes&lssubmit=yes&inajax=1" |
107
|
|
|
) |
108
|
|
|
login_data = { |
109
|
|
|
"fastloginfield": "username", |
110
|
|
|
"username": username, |
111
|
|
|
"password": password, |
112
|
|
|
"quickforward": "yes", |
113
|
|
|
"handlekey": "ls", |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
s = req_Session() |
117
|
|
|
s.headers.update(headers) |
118
|
|
|
s.cookies.update(self.gen_anti_cc_cookies()) |
119
|
|
|
r = s.post(url=login_url, data=login_data) |
120
|
|
|
r.raise_for_status() |
121
|
|
|
return s |
122
|
|
|
|
123
|
|
|
# 通过抓取用户设置页面的标题检查是否登录成功 |
124
|
|
|
@staticmethod |
125
|
|
|
def check_login_status(s: req_Session, number_c: int) -> bool: |
126
|
|
|
test_url = "https://hostloc.com/home.php?mod=spacecp" |
127
|
|
|
r = s.get(test_url) |
128
|
|
|
r.raise_for_status() |
129
|
|
|
r.encoding = "utf-8" |
130
|
|
|
test_title = re.findall(r"<title>(.*?)</title>", r.text) |
131
|
|
|
|
132
|
|
|
if len(test_title) != 0: # 确保正则匹配到了内容,防止出现数组索引越界的情况 |
133
|
|
|
if test_title[0] != "个人资料 - 全球主机交流论坛 - Powered by Discuz!": |
134
|
|
|
log(f"第 {number_c} 个帐户登录失败!") |
135
|
|
|
return False |
136
|
|
|
log(f"第 {number_c} 个帐户登录成功!") |
137
|
|
|
return True |
138
|
|
|
log("无法在用户设置页面找到标题,该页面存在错误或被防 CC 机制拦截!") |
139
|
|
|
return False |
140
|
|
|
|
141
|
|
|
# 抓取并打印输出账户当前积分 |
142
|
|
|
def log_current_points(self, s: req_Session): |
143
|
|
|
test_url = self.home_page |
144
|
|
|
r = s.get(test_url) |
145
|
|
|
r.raise_for_status() |
146
|
|
|
r.encoding = "utf-8" |
147
|
|
|
points = re.findall(r"积分: (\d+)", r.text) |
148
|
|
|
|
149
|
|
|
if len(points) != 0: # 确保正则匹配到了内容,防止出现数组索引越界的情况 |
150
|
|
|
log(f"帐户当前积分:{points[0]}") |
151
|
|
|
else: |
152
|
|
|
log("无法获取帐户积分,可能页面存在错误或者未登录!") |
153
|
|
|
time.sleep(5) |
154
|
|
|
|
155
|
|
|
# 依次访问随机生成的用户空间链接获取积分 |
156
|
|
|
def get_points(self, s: req_Session, number_c: int): |
157
|
|
|
if self.check_login_status(s, number_c): |
158
|
|
|
self.log_current_points(s) # 打印账户当前积分 |
159
|
|
|
url_list = self.randomly_gen_uspace_url() |
160
|
|
|
# 依次访问用户空间链接获取积分,出现错误时不中断程序继续尝试访问下一个链接 |
161
|
|
|
for i, url in enumerate(url_list): |
162
|
|
|
try: |
163
|
|
|
r = s.get(url) |
164
|
|
|
r.raise_for_status() |
165
|
|
|
log(f"第 {str(i + 1)} 个用户空间链接访问成功") |
166
|
|
|
time.sleep(5) # 每访问一个链接后休眠5秒,以避免触发论坛的防CC机制 |
167
|
|
|
except Exception as e: |
168
|
|
|
log(f"链接访问异常:{str(e)}") |
169
|
|
|
self.log_current_points(s) # 再次打印账户当前积分 |
170
|
|
|
else: |
171
|
|
|
log("请检查你的帐户是否正确!") |
172
|
|
|
|
173
|
|
|
# 打印输出当前ip地址 |
174
|
|
|
@staticmethod |
175
|
|
|
def log_my_ip(): |
176
|
|
|
api_url = "https://api.ipify.org/" |
177
|
|
|
try: |
178
|
|
|
r = requests.get(url=api_url) |
179
|
|
|
r.raise_for_status() |
180
|
|
|
r.encoding = "utf-8" |
181
|
|
|
log(f"当前使用 ip 地址:{r.text}") |
182
|
|
|
except Exception as e: |
183
|
|
|
log(f"获取当前 ip 地址失败:{str(e)}") |
184
|
|
|
|
185
|
|
|
def main(self): |
186
|
|
|
for i, check_item in enumerate(self.check_items): |
187
|
|
|
username = check_item.get("username") |
188
|
|
|
password = check_item.get("password") |
189
|
|
|
self.log_my_ip() |
190
|
|
|
log(f"共检测到 {len(self.check_items)} 个帐户,开始获取积分") |
191
|
|
|
log("*" * 12) |
192
|
|
|
|
193
|
|
|
try: |
194
|
|
|
s = self.login(username, password) |
195
|
|
|
self.get_points(s, i + 1) |
196
|
|
|
log("*" * 12) |
197
|
|
|
except Exception as e: |
198
|
|
|
log(f"程序执行异常:{str(e)}") |
199
|
|
|
log("*" * 12) |
200
|
|
|
|
201
|
|
|
log("程序执行完毕,获取积分过程结束") |
202
|
|
|
return desp |
203
|
|
|
|
204
|
|
|
|
205
|
|
|
if __name__ == "__main__": |
206
|
|
|
_data = get_data() |
207
|
|
|
_check_items = _data.get("HOSTLOC", []) |
208
|
|
|
result = HOSTLOC(check_items=_check_items).main() |
209
|
|
|
send("HOSTLOC", result) |
210
|
|
|
|