今日校园自动健康上报|自动填表|疫情上报!Python牛逼!

开发环境

  • python3.0

-----自动填表/打卡/上报原理

其实也就是模拟一个数据包,调用今日校园的api;实现登入功能;然后查找未填写的表;倘若表的序列号已经存在你的代码包里面,就会自动将包数据发到职教云服务器上面去,倘若没有,将会把表自动打包程json;然后你只需要编辑下json数据,以后这个序列的包就可以自动提交了


-----使用说明
理论上支持大部分学校(IAP登录方式,自己先试试能用不)和任意表单内容的自定义。目前已知两种学工号登录方式,均已实现。运行以下命令即可

pip install -r requirements.txt
python3 DailyCP.py 学校全名 学号 密码 定位地址
12

注意系统时间是否设置正确
Linux下可以使用corntab -e设置定时任务让程序每天自动打卡(下面的是设置0-9点触发的)

0 9 * * * python3 DailyCP.py 学校全名 学号 密码 定位地址
1

  • 部分代码(主要函数)
  • 获取全部的项目源码请到公众号【sky趣完团】
    或者加入qq群:749717825
import requests
import json
import io
import random
import time
import re
import pyDes
import base64
import uuid
import sys
import os
from Crypto.Cipher import AES

class DailyCP:
    def __init__(self, schoolName="合肥学院"):
        self.key = "ST83=@XV"#dynamic when app update
        self.session = requests.session()
        self.host = ""
        self.loginUrl = ""
        self.isIAPLogin = True
        self.session.headers.update({
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36 Edg/83.0.478.37",
            #"X-Requested-With": "XMLHttpRequest",
            "Pragma": "no-cache",
            "Accept": "application/json, text/plain, */*",
            #"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
            # "User-Agent": "okhttp/3.12.4"
        })
        extension = {"deviceId":str(uuid.uuid4()),"systemName":"保密级操作系统","userId":"5201314","appVersion":"8.1.13","model":"天河一号","lon":0.0,"systemVersion":"初号机","lat":0.0}
        self.session.headers.update({"Cpdaily-Extension": self.encrypt(json.dumps(extension))})
        self.setHostBySchoolName(schoolName)

    def setHostBySchoolName(self,schoolName):
        ret = self.request("https://static.campushoy.com/apicache/tenantListSort")
        school = [j for i in ret["data"] for j in i["datas"] if j["name"] == schoolName]
        if len(school) == 0:
            print("不支持的学校或者学校名称错误,以下是支持的学校列表")
            print(ret)
            exit()
        ret = self.request("httpsz://mobile.campushoy.com/v6/config/guest/tenant/info?ids={ids}".format(ids=school[0]["id"]))
        self.loginUrl = ret["data"][0]["ampUrl"]
        if ret == "":
            print("学校并没有申请入驻今日校园平台")
            exit()
        print("{name}的登录地址{url}".format(name=schoolName,url=self.loginUrl))
        self.host =  re.findall(r"//(.*?)/",self.loginUrl)[0]
        
        #ret = self.request(ret["data"][0]["ampUrl"],parseJson=False).url
        #self.isIAPLogin = "campusphere" in ret
        #if not self.isIAPLogin:
        #    print("注意:包含AuthServer的登陆方式并未测试!且每一个学校的登录方式都不一样。")
        #ret = re.findall(r"//(.*?)/",ret)
        #if len(ret) == 0:
            
        #    exit()
        #self.host = ret[0]

    def encrypt(self,text):
        k = pyDes.des(self.key, pyDes.CBC, b"\x01\x02\x03\x04\x05\x06\x07\x08", pad=None, padmode=pyDes.PAD_PKCS5)
        ret = k.encrypt(text)
        return base64.b64encode(ret).decode()

    def passwordEncrypt(self,text:str,key:str):
        pad = lambda s: s + (len(key) - len(s) % len(key)) * chr(len(key) - len(s) % len(key))
        unpad = lambda s: s[:-ord(s[len(s) - 1:])]
        text = pad("TdEEGazAXQMBzEAisrYaxRRax5kmnMJnpbKxcE6jxQfWRwP2J78adKYm8WzSkfXJ"+text).encode("utf-8")
        aes = AES.new(str.encode(key), AES.MODE_CBC,str.encode("ya8C45aRrBEn8sZH"))
        return base64.b64encode(aes.encrypt(text))

    def request(self,url:str,body=None,parseJson=True,JsonBody=True,Referer=None):
        url = url.format(host=self.host)
        if Referer != None: self.session.headers.update({"Referer":Referer})
        if body == None:ret = self.session.get(url)
        else:
            self.session.headers.update({"Content-Type": ("application/json" if JsonBody else "application/x-www-form-urlencoded")})
            ret = self.session.post(url,data=(json.dumps(body) if JsonBody else body))
        if parseJson:return json.loads(ret.text)
        else:return ret

    def decrypt(self,text):
        k = pyDes.des(self.key, pyDes.CBC, b"\x01\x02\x03\x04\x05\x06\x07\x08", pad=None, padmode=pyDes.PAD_PKCS5)
        ret = k.decrypt(base64.b64decode(text))
        return ret.decode()

    def checkNeedCaptcha(self, username):
        url = "https://{host}/iap/checkNeedCaptcha?username={username}".format(host=self.host,username=username)
        ret = self.session.get(url)
        ret = json.loads(ret.text)
        return ret["needCaptcha"]

    def generateCaptcha(self):
        url = "https://{host}/iap/generateCaptcha?ltId={client}&codeType=2".format(host=self.host,client=self.client)
        ret = self.session.get(url)
        return ret.content

    def getBasicInfo(self):
        return self.request("https://{host}/iap/tenant/basicInfo","{}")

    def login(self, username, password, captcha=""):
        if "campusphere" in self.loginUrl:return self.loginIAP(username,password,captcha)
        else: return self.loginAuthserver(username,password,captcha)

    def loginIAP(self, username, password, captcha=""):
        self.session.headers.update({"X-Requested-With": "XMLHttpRequest"})

        ret = self.session.get("https://{host}/iap/login?service=https://{host}/portal/login".format(host=self.host)).url
        client = ret[ret.find("=")+1:]
        ret = self.request("https://{host}/iap/security/lt","lt={client}".format(client=client),True,False)
        client = ret["result"]["_lt"]
        #self.encryptSalt = ret["result"]["_encryptSalt"]

        body = {
            "username": username,
            "password": password,
            "lt": client,
            "captcha": captcha,
            "rememberMe": "true",
            "dllt": "",
            "mobile": ""
        }
        ret = self.request("https://{host}/iap/doLogin",body,True,False)
        if ret["resultCode"] == "REDIRECT":
            self.session.get(ret["url"])
            return True
        else: return False

    def checkNeedCaptchaAuthServer(self,username):
        ret = self.request("http://{host}/authserver/needCaptcha.html?username={username}&pwdEncrypt2=pwdEncryptSalt".format(username=username),parseJson=False).text
        return ret == "true"

    def loginAuthserver(self,username,password,captcha=""):
        ret = self.request(self.loginUrl,parseJson=False)
        body = dict(re.findall(r'''<input type="hidden" name="(.*?)" value="(.*?)"''',ret.text))
        salt = dict(re.findall(r'''<input type="hidden" id="(.*?)" value="(.*?)"''',ret.text))
    
        body["username"] = username
        if "pwdDefaultEncryptSalt" in salt.keys():
            body["password"] = self.passwordEncrypt(password,salt["pwdDefaultEncryptSalt"])
        else: 
            body["password"] = password
        ret = self.request(ret.url,body,False,False,Referer=self.loginUrl).url
        print(self.session.cookies)
        
        print("出错了")
        return True

    def getCollectorList(self):
        body = {
            "pageSize": 10,
            "pageNumber": 1
        }
        ret = self.request("https://{host}/wec-counselor-collector-apps/stu/collector/queryCollectorProcessingList",body)
        return ret["datas"]["rows"]

    def getNoticeList(self):
        body = {
            "pageSize": 10,
            "pageNumber": 1
        }
        ret = self.request("https://{host}/wec-counselor-stu-apps/stu/notice/queryProcessingNoticeList",body)
        return ret["datas"]["rows"]

    def confirmNotice(self, wid):
        body = {
            "wid": wid
        }
        ret = self.request("https://{host}/wec-counselor-stu-apps/stu/notice/confirmNotice",body)
        print(ret["message"])
        return ret["message"] == "SUCCESS"

    def getCollectorDetail(self, collectorWid):
        body = {
            "collectorWid": collectorWid
        }
        return self.request("https://{host}/wec-counselor-collector-apps/stu/collector/detailCollector",body)["datas"]

    def getCollectorFormFiled(self, formWid, collectorWid):
        body = {
            "pageSize": 50,
            "pageNumber": 1,
            "formWid": formWid,
            "collectorWid": collectorWid
        }
        return self.request("https://{host}/wec-counselor-collector-apps/stu/collector/getFormFields",body)["datas"]["rows"]

    def submitCollectorForm(self, formWid, collectWid, schoolTaskWid, rows, address):
        body = {
            "formWid": formWid,
            "collectWid": collectWid,
            "schoolTaskWid": schoolTaskWid,
            "form": rows,
            "address": address
        }
        ret = self.request("https://{host}/wec-counselor-collector-apps/stu/collector/submitForm",body)
        print(ret["message"])
        return ret["message"] == "SUCCESS"

    def autoFill(self, rows):
        for item in rows:
            index = 0
            while index < len(item["fieldItems"]):
                if item["fieldItems"][index]["isSelected"] == 1:index = index + 1
                else:item["fieldItems"].pop(index)
        #此函数通过表格的默认值自动填写,如果你们学校没有提供默认值的,需要手动编辑此函数。
        #先print(rows),观察表格的形式,想选择哪一个选项,pop掉其他无关选项就行了。当然也可以直接将rows硬编码在代码里面。
        #因为每个人的定位地址都不一样,有些学校的表格也不一定一样。

    def autoComplete(self, address):
        collectList = self.getCollectorList()
        print(collectList)
        for item in collectList:
            detail = self.getCollectorDetail(item["wid"])
            form = self.getCollectorFormFiled(detail["collector"]["formWid"], detail["collector"]["wid"])

            formpath = "./formdb/{formwid}.json".format(formwid=detail["collector"]["formWid"])
            if os.path.exists(formpath):
                with open(formpath,"rb") as file:
                    form = json.loads(file.read().decode("utf-8"))
                    self.autoFill(form)
                self.submitCollectorForm(detail["collector"]["formWid"], detail["collector"]["wid"], detail["collector"]["schoolTaskWid"], form, address)
            else:
                with open(formpath,"wb") as file:
                    file.write(json.dumps(form,ensure_ascii=False).encode("utf-8"))
                    print("请手动填写{formpath},之后重新运行脚本".format(formpath=formpath))
                    exit()

        confirmList = self.getNoticeList()
        print(confirmList)
        for item in confirmList:self.confirmNotice(item["noticeWid"])


if __name__ == "__main__":
    if len(sys.argv) != 5:
        print("python3 DailyCp.py 学校全名 学号 密码 定位地址") 
        exit()
    app = DailyCP(sys.argv[1])
    if not app.login(sys.argv[2], sys.argv[3]):exit()
    app.autoComplete(sys.argv[4])

-----关于自定义任意表单内容

表单的内容多种多样,也不可能共享给其他人使用,因此本脚本采用“一次编辑,永久使用”的思路。如果脚本发现未知的表单,会将表单保存到formdb文件夹下,用户必须手动填写好内容,再次运行脚本即可。

-----关于AuthServer的登录方式

目前已知IAP和AuthServer这两种登录方式,IAP是统一的,AuthServer每个学校都不一样。如果你发现脚本并不能正常运作,请根据输出的信息自行修改代码。(理论上所有学校都可以直接运行登入)

完整项目代码获取点这里即可

猜你喜欢

转载自blog.csdn.net/weixin_43881394/article/details/109071902
今日推荐