2 |
AutoCheckBJMF 班级魔方自动签到
3 |

4 |

5 |

6 |
7 |

8 |
9 |

10 |
11 | 这是一项基于Python语言的班级魔方GPS自动签到Script
12 |
13 |
14 |
15 |
16 |
17 | 严禁将本程序用于违法用途,请遵守地区法规,另如有违背平台利益请与我联系撤销平台支持
18 | ## 支持平台
19 | **Windows、Mac、Linux**
20 | ## 支持的签到模式
21 | - ✅ 二维码签到 (验证通过)
22 | - ✅ GPS签到 (验证通过)
23 | - ✅ GPS+拍照签到 (验证通过)
24 | - 🚧 密码签到
25 |
26 | ## 功能
27 | - ✅ 支持定时开启签到
28 | - ✅ 支持24小时无人值守
29 | - ✅ 支持msi安装包一键式安装
30 | - ✅ 支持自定义经纬度完成定位签到
31 | - ✅ 支持循环检测GPS及扫码签到任务
32 | - ✅ 支持自动导入data.json配置文件
33 | - ✅ 支持自动保存信息到data.json文件
34 | - ✅ 支持连续签到,无需重复抓取Cookie值
35 |
36 |
37 | 如遇问题,请向我提出issues
38 |
39 | ## 使用教程
40 | 维基Wiki https://github.com/JasonYANG170/AutoCheckBJMF/wiki
41 |
42 | ## 自行打包
43 | `pyinstaller main.spec`
44 |
45 | ## 喜欢这个项目,请为我点个Star ⭐
46 |
47 | [](https://star-history.com/#star-history/star-history&Date)
48 |
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | import random
2 | import requests
3 | import re
4 | import time
5 | import os
6 | from bs4 import BeautifulSoup
7 | import json
8 | import schedule
9 | from datetime import datetime
10 | import logging
11 |
12 | # 获取当前目录
13 | current_directory = os.getcwd()
14 | file_name = "config.json"
15 | file_path = os.path.join(current_directory, file_name)
16 |
17 | print("----------提醒----------")
18 | print("项目地址:https://github.com/JasonYANG170/AutoCheckBJMF")
19 | print("请查看教程以获取Cookie和班级ID")
20 | print("config.json文件位置:", current_directory)
21 |
22 | # 检查文件是否存在
23 | if not os.path.exists(file_path):
24 | # 定义默认的 JSON 数据
25 | default_config = {
26 | "class": "", # 班级ID
27 | "lat": "", # 纬度
28 | "lng": "", # 经度
29 | "acc": "", # 海拔
30 | "time": 0, # 等待时间(已弃用)
31 | "cookie": "", # 用户令牌
32 | "scheduletime": "", # 定时任务
33 | "pushplus": "", # pushpush推送令牌
34 | "debug": False, # 调试模式
35 | "configLock": False #配置编辑状态,
36 | }
37 | # 文件不存在,创建并写入默认数据
38 | with open(file_path, "w") as file:
39 | json.dump(default_config, file, indent=4)
40 | print("----------初始化----------")
41 | print(f"文件 {file_name} 不存在,已创建并填充默认数据。")
42 |
43 | # 读取外部 JSON 文件中的数据
44 | with open(file_path, 'r') as file:
45 | json_data = json.load(file)
46 | debug = json_data["debug"]
47 |
48 | # 判断是否首次使用或解除配置锁定
49 | if not json_data['configLock']:
50 | print("----------基础配置(必填)----------")
51 | print("☆请通过查看教程抓包获取班级ID")
52 | ClassID = input("请输入班级ID:")
53 | print("☆输入的经纬度格式为x.x,请输入至少8位小数用于定位微偏移,不满8位用0替补!")
54 | print("☆腾讯坐标拾取工具:https://lbs.qq.com/getPoint/")
55 | X = input("请输入纬度(X):")
56 | Y = input("请输入经度(Y):")
57 | ACC = input("请输入海拔:")
58 | print("----------配置Cookie(必填)----------")
59 | print("请通过查看教程抓包获取Cookie")
60 | print("教程:https://github.com/JasonYANG170/AutoCheckBJMF/wiki/")
61 | print("登录获取:https://k8n.cn/student/login")
62 | print("Tip:90%的失败由Cookie变更导致")
63 | Cookies = []
64 | print("请输入你的Cookie,输入空行结束,支持用户备注格式如下")
65 | print("username=<备注>;remember....<魔方Cookie>")
66 | while True:
67 | cookie = input("Cookie: ")
68 | if not cookie:
69 | break
70 | Cookies.append(cookie)
71 | print("----------配置定时任务(可选)----------")
72 | print("格式为00:00,例如1:30要填写为01:30!不设置定时请留空")
73 | print("Tip:请注意以上格式并使用英文符号“:”不要使用中文符号“:”")
74 | scheduletime = input("请输入签到时间:")
75 | if scheduletime=="":
76 | print("您未填写签到时间,未启用定时签到,启动即开始签到")
77 | print("----------远程推送----------")
78 | pushtoken = input("(未适配新版多人签到,如果是多人签到建议不使用)\n请输入pushplus推送密钥,不需要请留空:")
79 |
80 | print("配置完成,您的信息将写入json文件,下次使用将直接从json文件导入")
81 | # 2. 修改数据
82 | json_data["class"] = ClassID
83 | json_data["lat"] = X
84 | json_data["lng"] = Y
85 | json_data["acc"] = ACC
86 | json_data["cookie"] = Cookies
87 | json_data["scheduletime"] = scheduletime
88 | json_data["pushplus"] = pushtoken
89 | json_data["configLock"] = True
90 | # 3. 写回JSON文件
91 | with open(file_path, "w") as file:
92 | json.dump(json_data, file, indent=4) # indent 设置缩进为4个空格
93 | print("数据已保存到"+current_directory+"下的data.json中。")
94 | else:
95 | print("----------欢迎回来----------")
96 | ClassID = json_data["class"]
97 | X = json_data["lat"]
98 | Y = json_data["lng"]
99 | ACC = json_data["acc"]
100 | Cookies = json_data["cookie"]
101 | scheduletime = json_data["scheduletime"]
102 | pushtoken = json_data["pushplus"]
103 | print("配置已读取")
104 | if scheduletime=="":
105 | print("当前签到模式为:手动,即将开始签到")
106 | else:
107 | print("当前签到模式为:自动,启动定时任务")
108 | print("----------信息----------")
109 | print("班级ID:" + ClassID)
110 | print("纬度:" + X)
111 | print("经度:" + Y)
112 | print("海拔:" + ACC)
113 | # print("检索间隔:" + str(SearchTime))
114 | print("Cookie数量:" + str(len(Cookies)))
115 | print("定时:" + scheduletime)
116 | print("通知token:" + pushtoken)
117 | if debug:print("Debug:" + str(debug))
118 | print("---------------------")
119 |
120 | def printLog(type, message):
121 | if debug:
122 | if type == "info":
123 | logger.info(message)
124 | elif type == "warning":
125 | logger.warning(message)
126 | elif type == "error":
127 | logger.error(message)
128 | elif type == "critical":
129 | logger.critical(message)
130 | else:
131 | logger.info(message)
132 |
133 | if debug:
134 | # 创建 logger
135 | logger = logging.getLogger()
136 | logger.setLevel(logging.INFO)
137 | # 创建文件处理器并设置编码为 UTF-8
138 | file_handler = logging.FileHandler('AutoCheckBJMF.log', encoding='utf-8')
139 | formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
140 | file_handler.setFormatter(formatter)
141 | # 将处理器添加到 logger
142 | logger.addHandler(file_handler)
143 | printLog("info", "已启动Debug")
144 | print("★一切就绪,程序开始执行\\^o^/")
145 |
146 | ### 请注意,下述两个自动任务功能未适配
147 | # GitHub Actions自动任务
148 | # ClassID = os.environ['ClassID']
149 | # X = os.environ['X']
150 | # Y = os.environ['Y']
151 | # ACC = os.environ['ACC']
152 | # SearchTime = os.environ['SearchTime']
153 | # Cookies = os.environ['Cookies']
154 | # token = os.environ['token']
155 | #scheduletime = os.environ['scheduletime']
156 |
157 | # 本地面板运行
158 | #ClassID = ''
159 | #X = ''
160 | #Y = ''
161 | #ACC = ''
162 | #SearchTime = 60
163 | #Cookies = ''
164 | #token = '' #在pushplus网站中可以找到
165 | #scheduletime =''
166 | ### 请注意,上述两个自动任务功能未适配
167 |
168 | # 随机经纬,用于多人签到定位偏移
169 | def modify_decimal_part(num):
170 | num = float(num)
171 | # print(num)
172 | # 将浮点数转换为字符串
173 | num_str = f"{num:.8f}" # 确保有足够的小数位数
174 | # 找到小数点的位置
175 | decimal_index = num_str.find('.')
176 | # 提取小数点后4到6位
177 | decimal_part = num_str[decimal_index + 4:decimal_index + 9]
178 | # 将提取的小数部分转换为整数
179 | decimal_value = int(decimal_part)
180 | # 生成一个在-150到150范围的随机整数
181 | random_offset = random.randint(-15000, 15000)
182 | # 计算新的小数部分
183 | new_decimal_value = decimal_value + random_offset
184 | # 将新的小数部分转换为字符串,并确保它有3位
185 | new_decimal_str = f"{new_decimal_value:05d}"
186 | # 拼接回原浮点数
187 | new_num_str = num_str[:decimal_index + 4] + new_decimal_str + num_str[decimal_index + 9:]
188 | # 将新的字符串转换回浮点数
189 | new_num = float(new_num_str)
190 |
191 | return new_num
192 |
193 | def thisTime(hour,minute):
194 | # 指定的小时和分钟,这里示例为21:50,你可以按需修改
195 | target_hour,target_minute = hour,minute
196 |
197 | # while True:
198 | # 获取当前时间的时间戳
199 | current_time_stamp = time.time()
200 | # 获取当前时间的结构体
201 | current_time_struct = time.localtime(current_time_stamp)
202 |
203 | # 获取当天的日期部分,构造一个新的时间结构体,用于设置指定时间
204 | today_date = time.strftime("%Y-%m-%d", current_time_struct)
205 | target_time_struct = time.strptime(today_date + " " + str(target_hour) + ":" + str(target_minute) + ":00", "%Y-%m-%d %H:%M:%S")
206 | target_time_stamp = time.mktime(target_time_struct)
207 |
208 | if target_time_stamp < current_time_stamp:
209 | # 如果目标时间已经小于当前时间,说明今天的时间已经过了,那就设置为明天的同样时间
210 | target_time_stamp += 24 * 3600
211 |
212 | # 计算时间差(单位为秒)
213 | remaining_seconds_main = int(target_time_stamp - current_time_stamp)
214 | # 计算剩余小时数
215 | remaining_hours = remaining_seconds_main // 3600
216 | remaining_seconds = remaining_seconds_main % 3600
217 | # 计算剩余分钟数
218 | remaining_minutes = remaining_seconds // 60
219 | remaining_seconds %= 60
220 |
221 | # 格式化当前时间结构体为字符串
222 | current_time = time.strftime("%Y-%m-%d %H:%M", current_time_struct)
223 |
224 | # 区分剩余时间的显示逻辑,以优化终端内容的显示阅读体验
225 | if remaining_seconds_main < 300:
226 | # 如果剩余时间小于5分钟则每秒刷新
227 | print("\r当前时间:{},距离下次任务执行{}:{} 还剩{}分钟{}秒\t\t".format(
228 | current_time, target_hour, target_minute, remaining_minutes, remaining_seconds), end="")
229 | time.sleep(1)
230 | else:
231 | # 如果剩余时间大于5分钟则每分钟刷新
232 | print("\r当前时间:{},距离下次任务执行{}:{} 还剩{}小时{}分钟\t\t".format(
233 | current_time, target_hour, target_minute, remaining_hours, remaining_minutes), end="")
234 | time.sleep(60)
235 |
236 | def qiandao(theCookies):
237 | # title = '班级魔法自动签到任务' # 改成你要的标题内容
238 | url = 'http://k8n.cn/student/course/' + ClassID + '/punchs'
239 | errorCookie = []
240 | nullCookie = 0
241 | # 多用户检测签到
242 | for uid in range(0,len(theCookies)):
243 | onlyCookie = theCookies[uid]
244 |
245 | # 使用正则表达式提取目标字符串 - 用户备注
246 | pattern = r'username=[^;]+'
247 | result = re.search(pattern, onlyCookie)
248 |
249 | if result:
250 | username_string = " <%s>"%result.group(0).split("=")[1]
251 | else:
252 | username_string = ""
253 |
254 | # 用户信息显示与5秒冷却
255 | print("☆☆☆☆☆ 用户UID:%d%s 即将签到 ☆☆☆☆☆"%(uid+1,username_string),end="")
256 | time.sleep(1) #暂停5秒后进行签到
257 | print("\r★☆☆☆☆ 用户UID:%d%s 即将签到 ☆☆☆☆★"%(uid+1,username_string),end="")
258 | time.sleep(1)
259 | print("\r★★☆☆☆ 用户UID:%d%s 即将签到 ☆☆☆★★"%(uid+1,username_string),end="")
260 | time.sleep(1)
261 | print("\r★★★☆☆ 用户UID:%d%s 即将签到 ☆☆★★★"%(uid+1,username_string),end="")
262 | time.sleep(1)
263 | print("\r★★★★☆ 用户UID:%d%s 即将签到 ☆★★★★"%(uid+1,username_string),end="")
264 | time.sleep(1)
265 | print("\r★★★★★ 用户UID:%d%s 开始签到 ★★★★★"%(uid+1,username_string))
266 |
267 | # 使用正则表达式提取目标字符串 - Cookie
268 | pattern = r'remember_student_59ba36addc2b2f9401580f014c7f58ea4e30989d=[^;]+'
269 | result = re.search(pattern, onlyCookie)
270 |
271 | if result:
272 | extracted_string = result.group(0)
273 | if debug:
274 | print(extracted_string)
275 | headers = {
276 | 'User-Agent': 'Mozilla/5.0 (Linux; Android 9; AKT-AK47 Build/USER-AK47; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/116.0.0.0 Mobile Safari/537.36 XWEB/1160065 MMWEBSDK/20231202 MMWEBID/1136 MicroMessenger/8.0.47.2560(0x28002F35) WeChat/arm64 Weixin NetType/4G Language/zh_CN ABI/arm64',
277 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/wxpic,image/tpg,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
278 | 'X-Requested-With': 'com.tencent.mm',
279 | 'Referer': 'http://k8n.cn/student/course/' + ClassID,
280 | 'Accept-Encoding': 'gzip, deflate',
281 | 'Accept-Language': 'zh-CN,zh-SG;q=0.9,zh;q=0.8,en-SG;q=0.7,en-US;q=0.6,en;q=0.5',
282 | 'Cookie': extracted_string
283 | }
284 |
285 | response = requests.get(url, headers=headers)
286 | print("响应:", response)
287 |
288 | # 创建 Beautiful Soup 对象解析 HTML
289 | soup = BeautifulSoup(response.text, 'html.parser')
290 |
291 | title_tag = soup.find('title')
292 |
293 | if debug:
294 | print("★☆★")
295 | print(soup)
296 | print("===")
297 | print(title_tag)
298 | print("★☆★")
299 |
300 | if title_tag and "出错" not in title_tag.text:
301 | # 使用正则表达式从 HTML 文本中提取所有 punch_gps() 中的数字
302 | pattern = re.compile(r'punch_gps\((\d+)\)')
303 | matches = pattern.findall(response.text)
304 | print("找到GPS定位签到:", matches)
305 | pattern2 = re.compile(r'punchcard_(\d+)')
306 | matches2 = pattern2.findall(response.text)
307 | print("找到扫码签到:", matches2)
308 | matches.extend(matches2)
309 | if matches:
310 | for match in matches:
311 | url1 = "http://k8n.cn/student/punchs/course/" + ClassID + "/" + match
312 | newX = modify_decimal_part(X)
313 | newY = modify_decimal_part(Y)
314 | payload = {
315 | 'id': match,
316 | 'lat': newX,
317 | 'lng': newY,
318 | 'acc': ACC, #未知,可能是高度
319 | 'res': '', #拍照签到
320 | 'gps_addr': '' #未知,抓取时该函数为空
321 | }
322 |
323 | response = requests.post(url1, headers=headers, data=payload)
324 | print("签到请求已发送: 签到ID[%s] 签到定位[%s,%s] 签到海拔[%s]"%(match, newX, newY, ACC))
325 | printLog("info", "用户UID[%d%s] | 签到请求已发送: 签到ID[%s] 签到定位[%s,%s] 签到海拔[%s]"%(uid+1, username_string, match, newX, newY, ACC))
326 |
327 | if response.status_code == 200:
328 | print("请求成功,响应:", response)
329 |
330 | # 解析响应的 HTML 内容
331 | soup_response = BeautifulSoup(response.text, 'html.parser')
332 | # h1_tag = soup_response.find('h1')
333 | div_tag = soup_response.find('div', id='title')
334 |
335 | if debug:
336 | print("★☆★")
337 | print(soup_response)
338 | print("===")
339 | print(div_tag)
340 | print("★☆★")
341 |
342 | if div_tag:
343 | h1_text = div_tag.text
344 | print(h1_text)
345 | printLog("info", "用户UID[%d%s] | %s"%(uid+1, username_string, h1_text))
346 | # encoding:utf-8
347 | if pushtoken != "" and h1_text== "签到成功":
348 | url = 'http://www.pushplus.plus/send?token=' + pushtoken + '&title=' + "班级魔法自动签到任务" + '&content=' + h1_text # 不使用请注释
349 | requests.get(url) # 不使用请注释
350 | continue # 返回到查找进行中的签到循环
351 | else:
352 | print("未找到