Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
lsp12321 committed Nov 14, 2021
0 parents commit d18da6a
Show file tree
Hide file tree
Showing 17 changed files with 440 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto
31 changes: 31 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: BUPT ncov auto-report Python+Actions V3.0

on:
schedule:
- cron: "*/15 0,23 * * *"
workflow_dispatch:

jobs:
build:

runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.7]

steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Run BUPT ncov Auto-report Script
run: |
python3 main.py
env:
USERS: ${{ secrets.USERS }}
SERVER_KEY: ${{ secrets.SERVER_KEY }}
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2021 zzp-seeker

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
111 changes: 111 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<h2 align="center">:helicopter:北邮疫情自动填报(Python+Github Actions)长期维护</h2>
<p align="center">
<a href="https://github.com/zzp-seeker/bupt-ncov-auto-report/actions/workflows/main.yml">
<img src="https://github.com/zzp-seeker/bupt-ncov-auto-report/actions/workflows/main.yml/badge.svg?branch=master" alt="Action Badge">
</a>
<img src="https://img.shields.io/badge/python-%3d%203.7-blue" alt="Python Version">
</p>



## 特点:

- 每天7-8点自动多次填报(可通过main.yml修改)
- 运行失败会自动往邮箱(注册github所用邮箱)发送邮件
- 可以多人同时填报(可以帮小伙伴一起打卡哦)
- 填报情况可以使用上一次打卡数据(如需更换请于当日脚本运行前手动打卡),也可以使用固定数据(地点始终位于北邮)
- **(可选)** 填报结果通过Server酱推送至微信

<div align="center">
<img src='img/0.png' width=55%/>
</div>




## 使用详解

1. 点击右上角的 **Use this template** <img src="img/2.png" width="50%">然后给仓库随便起一个名字,点击 **Create repository from template**
2. 点击 **Settings** ,进入 **Secrets** 页面,点击右上角的 **New repository secret**,流程如下图所示
<div align="center">
<img src="img/3.png" width="90%">
</div>

3. 一共有****个secret,第一个Name填**USERS**,Value按照如下格式填写:

```python
[
(学号:str,密码:str,用户名:str,0 or 1),
(学号:str,密码:str,用户名:str,0 or 1),
(学号:str,密码:str,用户名:str,0 or 1),
... 如果还有则继续往后面加
]
```

相当于**列表**里面有很多**元组**,每个元组代表一个用户,可以有任意多个。每个元组有**四个元素**,前两个分别为**学号****密码**,字符串格式(可自行通过 [https://app.bupt.edu.cn/ncov/wap/default/index](https://app.bupt.edu.cn/ncov/wap/default/index) 登陆验证账号密码正确性,密码一般为身份证后8位),第三个为**用户名**(随便填,用于控制台与Server显示),第四个为是否用上一次打卡数据,**0或者1**,0代表使用上一次打卡数据(**某一次自己在脚本运行前打卡之后都采用这次打卡数据**),1代表使用固定数据(固定数据的地点始终位于北邮),以下是一个样例:

<div align="center">
<img src="img/4.png" width="55%">
</div>


4. 第二个secret的Name填写**SERVER_KEY**,如果不配置Server酱微信推送,那么Value里填写**0**即可,如果想配置的话看下一点
5. **(可选)** Value填写Server酱的SendKey(在这里查看 [https://sct.ftqq.com/sendkey](https://sct.ftqq.com/sendkey)),在此之前需要微信注册企业号,并加入Server酱内部应用,具体流程见 [https://sct.ftqq.com/forward](https://sct.ftqq.com/forward),看起来比较多,但也不是很麻烦,一步步照做即可

最后Actions secrets效果:

<div align="center">
<img src="img/5.png" width="100%">
</div>

6. 点击上方**Actions**按钮:

<img src="img/6.png" width="100%">

点击左侧的**BUPT ncov auto-report Python**,再点击右侧的**Run workflow**,如下图所示:

<div align="center">
<img src="img/7.png" width="100%">
</div>

点击这个workflow(没看到的话请刷新一下),然后再次点进去jobs查看执行情况

<div align="center">
<img src="img/8.png" >
</div>

7. 如果准确按照上述步骤执行,你应该会看到类似的如下输出:

<div align="center">
<img src="img/9.png" width="95%">
</div>

若想查看上图一样具体填报信息需要取消注释main.py的75行

**恭喜你,你还有你的小伙伴不用为被催打卡而烦恼了~**


## 参数更改:
### 更改每日打卡时间
在 .github/workflows/main.yml 中来设置每天运行的时间:
```python
on:
schedule:
- cron: "*/20 16,23 * * *"
```
cron里的"\*/20 16,23 * * \*"代表 at every 20th minute past hour 16 and 23,然而这是UTC,北京时间为UTC+8,代表0点与7点之后每隔20分钟
[https://crontab.guru/#*/10_16,23_*_*_*](https://crontab.guru/#*/10_16,23_*_*_*) 用这个网站来选取你想要的时间
### 更改打卡的固定数据
[https://app.bupt.edu.cn/ncov/wap/default/index](https://app.bupt.edu.cn/ncov/wap/default/index) 进行填报,全部填完后最后不要提交,f12打开控制台,在Console页面下输入代码vm.info回车得到填报数据,替换掉 constant.py 里的INFO变量


## Credit
参考了[ipid/bupt-ncov-report](https://github.com/ipid/bupt-ncov-report)[imtsuki/bupt-ncov-report-action](https://github.com/imtsuki/bupt-ncov-report-action), 十分感谢

## License
MIT © [zzp-seeker](https://github.com/zzp-seeker)

### 好用的话别忘了:star::wink:


114 changes: 114 additions & 0 deletions constant.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import os

USERS = eval(os.environ['USERS'])
SERVER_KEY = os.environ['SERVER_KEY']


LOGIN_API = 'https://app.bupt.edu.cn/uc/wap/login/check'
GET_API = 'https://app.bupt.edu.cn/ncov/wap/default/index'
REPORT_API = 'https://app.bupt.edu.cn/ncov/wap/default/save'

# 当今日没有填报时,在https://app.bupt.edu.cn/ncov/wap/default/index下进行填报,
# 全部填完,不要提交,f12打开控制台,在Console页面下输入代码 console.log(vm.info) 就会得到以下信息,之后每天就默认填以下信息
INFO = r"""{
"address":"北京市海淀区北太平庄街道北京邮电大学计算机学院北京邮电大学海淀校区",
"area":"北京市 海淀区",
"bztcyy":"",
"city":"北京市",
"csmjry":"0",
"fjqszgjdq":"",
"geo_api_info":"{\"type\":\"complete\",\"position\":{\"Q\":39.960390625,\"R\":116.356397569445,\"lng\":116.356398,\"lat\":39.960391},\"location_type\":\"html5\",\"message\":\"Get ipLocation failed.Get geolocation success.Convert Success.Get address success.\",\"accuracy\":23,\"isConverted\":true,\"status\":1,\"addressComponent\":{\"citycode\":\"010\",\"adcode\":\"110108\",\"businessAreas\":[{\"name\":\"北下关\",\"id\":\"110108\",\"location\":{\"Q\":39.955976,\"R\":116.33873,\"lng\":116.33873,\"lat\":39.955976}},{\"name\":\"西直门\",\"id\":\"110102\",\"location\":{\"Q\":39.942856,\"R\":116.34666099999998,\"lng\":116.346661,\"lat\":39.942856}},{\"name\":\"小西天\",\"id\":\"110108\",\"location\":{\"Q\":39.957147,\"R\":116.364058,\"lng\":116.364058,\"lat\":39.957147}}],\"neighborhoodType\":\"科教文化服务;学校;高等院校\",\"neighborhood\":\"北京邮电大学\",\"building\":\"北京邮电大学计算机学院\",\"buildingType\":\"科教文化服务;学校;高等院校\",\"street\":\"西土城路\",\"streetNumber\":\"10号\",\"country\":\"中国\",\"province\":\"北京市\",\"city\":\"\",\"district\":\"海淀区\",\"township\":\"北太平庄街道\"},\"formattedAddress\":\"北京市海淀区北太平庄街道北京邮电大学计算机学院北京邮电大学海淀校区\",\"roads\":[],\"crosses\":[],\"pois\":[],\"info\":\"SUCCESS\"}",
"glksrq":"",
"gllx":"",
"gtjzzchdfh":"",
"gtjzzfjsj":"",
"ismoved":"0",
"jcbhlx":"",
"jcbhrq":"",
"jchbryfs":"",
"jcjgqr":"0",
"jcwhryfs":"",
"jhfjhbcc":"",
"jhfjjtgj":"",
"jhfjrq":"",
"mjry":"0",
"province":"北京市",
"qksm":"",
"remark":"",
"sfcxtz":"0",
"sfcxzysx":"0",
"sfcyglq":"0",
"sfjcbh":"0",
"sfjchbry":"0",
"sfjcwhry":"0",
"sfjzdezxgym":"1",
"sfjzxgym":"1",
"sfsfbh":"0",
"sftjhb":"0",
"sftjwh":"0",
"sfxk":"0",
"sfygtjzzfj":"",
"sfyyjc":"0",
"sfzx":1,
"szcs":"",
"szgj":"",
"szsqsfybl":"0",
"tw":"2",
"xjzd":"",
"xkqq":"",
"xwxgymjzqk":"3",
"ymjzxgqk":"已接种",
"zgfxdq":"0"
}"""

REASONABLE_LENGTH = 24
TIMEOUT_SECOND = 25

class HEADERS:
REFERER_LOGIN_API = 'https://app.bupt.edu.cn/uc/wap/login'
REFERER_POST_API = 'https://app.bupt.edu.cn/ncov/wap/default/index'
ORIGIN_BUPTAPP = 'https://app.bupt.edu.cn'

UA = ('Mozilla/5.0 (iPhone; CPU iPhone OS 13_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) '
'Mobile/15E148 MicroMessenger/7.0.11(0x17000b21) NetType/4G Language/zh_CN')
ACCEPT_JSON = 'application/json'
ACCEPT_HTML = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'
REQUEST_WITH_XHR = 'XMLHttpRequest'
ACCEPT_LANG = 'zh-cn'
CONTENT_TYPE_UTF8 = 'application/x-www-form-urlencoded; charset=UTF-8'

def __init__(self) -> None:
raise NotImplementedError

COMMON_HEADERS = {
'User-Agent': HEADERS.UA,
'Accept-Language': HEADERS.ACCEPT_LANG,
}
COMMON_POST_HEADERS = {
'Accept': HEADERS.ACCEPT_JSON,
'Origin': HEADERS.ORIGIN_BUPTAPP,
'X-Requested-With': HEADERS.REQUEST_WITH_XHR,
'Content-Type': HEADERS.CONTENT_TYPE_UTF8,
}

from typing import Optional
from abc import ABCMeta, abstractmethod

class INotifier(metaclass=ABCMeta):
@property
@abstractmethod
def PLATFORM_NAME(self) -> str:
"""
将 PLATFORM_NAME 设为类的 Class Variable,内容是通知平台的名字(用于打日志)。
如:PLATFORM_NAME = 'Telegram 机器人'
:return: 通知平台名
"""
@abstractmethod
def notify(self, *, success, msg, data,username, name) -> None:
"""
通过该平台通知用户操作成功的消息。失败时将抛出各种异常。
:param success: 表示是否成功
:param msg: 成功时表示服务器的返回值,失败时表示失败原因;None 表示没有上述内容
:return: None
"""

Binary file added img/0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/7.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/8.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/9.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
93 changes: 93 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
from serverJiang import *
from constant import *

import requests, re, json, copy, traceback


session = requests.Session()


def ncov_report(username, password, name, is_useold):
print('登录北邮 nCoV 上报网站')
login_res = session.post(
LOGIN_API,
data={'username': username, 'password': password, },
headers={**COMMON_HEADERS, **COMMON_POST_HEADERS, 'Referer': HEADERS.REFERER_LOGIN_API,
})
if login_res.status_code != 200:
raise RuntimeError('login_res 状态码不是 200')
get_res = session.get(
GET_API,
headers={**COMMON_HEADERS, 'Accept': HEADERS.ACCEPT_HTML},
)
if get_res.status_code != 200:
raise RuntimeError('get_res 状态码不是 200')
try:
old_data = json.loads('{' + re.search(r'(?<=oldInfo: {).+(?=})', get_res.text)[0] + '}')
except:
raise RuntimeError('未获取到昨日打卡数据,请今日手动打卡明日再执行脚本或使用固定打卡数据')
post_data = json.loads(copy.deepcopy(INFO).replace("\n", "").replace(" ", ""))
if is_useold:
try:
for k, v in old_data.items():
if k in post_data:
post_data[k] = v
geo = json.loads(old_data['geo_api_info'])

province = geo['addressComponent']['province']
city = geo['addressComponent']['city']
if geo['addressComponent']['city'].strip() == "" and len(re.findall(r'北京市|上海市|重庆市|天津市', province)) != 0:
city = geo['addressComponent']['province']
area = province + " " + city + " " + geo['addressComponent']['district']
address = geo['formattedAddress']
post_data['province'] = province
post_data['city'] = city
post_data['area'] = area
post_data['address'] = address

# 强行覆盖一些字段
post_data['ismoved'] = 0 # 是否移动了位置?否
post_data['bztcyy'] = '' # 不在同城原因?空
post_data['sfsfbh'] = 0 # 是否省份不合?否
except:
print("加载昨日数据错误,采用固定数据")
post_data = json.loads(copy.deepcopy(INFO).replace("\n", "").replace(" ", ""))
report_res = session.post(
REPORT_API,
data=post_data,
headers={**COMMON_HEADERS,**COMMON_POST_HEADERS,'Referer': HEADERS.REFERER_POST_API,},
)
if report_res.status_code != 200:
raise RuntimeError('report_res 状态码不是 200')
return post_data,report_res.text

successs,ress,usernames,names,datas = [],[],[],[],[]
for user in USERS:
success=True
username,password,name,useold=user
try:
data,res = ncov_report(username=username,password=password,name=name,is_useold=(useold==0))
except:
success = False
data,res = '',traceback.format_exc()

print(f'{name}填报成功!服务器返回数据:\n{res}' if success else f'{name}填报失败!发生如下异常:\n{res}')
# print(f'填报数据:\n{data}\n')

successs+=[success]
ress+=[res]
datas+=[data]
usernames+=[username]
names+=[name]

try:
notifier = ServerJiangNotifier(
sckey=SERVER_KEY,
sess=requests.Session()
)
print(f'通过「{notifier.PLATFORM_NAME}」给用户发送通知')
notifier.notify(success=successs, msg=ress,data=datas,username=usernames,name=names)
except:
print(r"可能由于 「SERVER_KEY未设置」 或 「SERVER_KEY不正确」 或 「网络波动」 ,SERVER酱发送失败")


1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
requests
Loading

0 comments on commit d18da6a

Please sign in to comment.