也玩

我朝着光明走,却走向深渊。

希望这首歌是你喜欢的歌,希望这句话是你喜欢的话,希望一些都如你所愿


今日校园自动签到项目

看的人还是挺多的,我的服务器ip被今日校园封了,若离也已经不再维护了,我会抽空更新的可用的项目

来学校之后,自动填表变成了自动签到,于是更换为了自动签到项目,使用方法同自动填表。

申明

使用本项目即代表同意本申明,如有异议请立即退出

项目原地址https://github.com/ZimoLoveShuang/auto-sign

使用了此脚本或者参考了这个项目,请自觉给项目点个star

  • 原作者已不再维护,后续维护均由志愿者进行
  • 本项目仅供学习交流使用,如作他用所承受的任何直接、间接法律责任一概与作者无关
  • 如果此项目侵犯了您或者您公司的权益,请立即联系我删除
  • 请对自己安全负责严格遵守包括但不限于学校等的相关规定。防控疫情,人人有责。尽量不前往中,高风险地区,出现相关症状请立即报告。

项目说明

  • config.yml 默认配置文件(需要改动)
  • index.py 完成自动提交的py脚本(一般不需要改动)

更新日志

  • 2020-12-11 更新学校登录地址获取域名
  • 2020-12-12 更换Server酱通知为对小白更加友好的邮件通知
  • 2020-12-24 解决获取不到请假后签到任务的问题
  • 2020-12.24 更换url
  • 2021-01.06 更换密钥,添加表单参数
  • 2021-03-15 重新启用签到,更改请假状态签到为正常状态,更改定位,地址信息为学校
  • 2021-03-15 更改获取api的方式
  • 2021-04-17 添加参数

说明

邮件api调用若离提供的api,如果收不到邮件请检查垃圾箱,并将该地址加入白名单以便后续接收。

云函数运行时间配置

点击触发管理关闭或删除原有触发器,创建触发器参见腾讯云函数 定时触发器 开发指南
0 0 0 * * * * 表示每晚12点整运行,建议精确到分,防止服务器过载造成签到失败。

地址更改

进入今日校园签到界面,找到自己当前位置(图片中框选的内容),填入config.yml文件地址栏。
如果不知道怎么配置经纬度信息,可以访问这里,将经纬度四舍五入保留六位小数(必须是6位)之后的放入配置文件对应位置即可(建议使用手机浏览器打开,并允许访问位置)
jinri1.jpg

index.py

# -*- coding: utf-8 -*-
import sys
import json
import uuid
import oss2
import yaml
import base64
import requests
from pyDes import des, CBC, PAD_PKCS5
from datetime import datetime, timedelta, timezone
from urllib.parse import urlparse
from urllib3.exceptions import InsecureRequestWarning

# debug模式
debug = True
if debug:
    requests.packages.urllib3.disable_warnings(InsecureRequestWarning)


# 读取yml配置
def getYmlConfig(yaml_file='config.yml'):
    file = open(yaml_file, 'r', encoding="utf-8")
    file_data = file.read()
    file.close()
    config = yaml.load(file_data, Loader=yaml.FullLoader)
    return dict(config)


# 全局配置
config = getYmlConfig(yaml_file='config.yml')


# 获取当前utc时间,并格式化为北京时间
def getTimeStr():
    utc_dt = datetime.utcnow().replace(tzinfo=timezone.utc)
    bj_dt = utc_dt.astimezone(timezone(timedelta(hours=8)))
    return bj_dt.strftime("%Y-%m-%d %H:%M:%S")


# 输出调试信息,并及时刷新缓冲区
def log(content):
    print(getTimeStr() + ' ' + str(content))
    sys.stdout.flush()


# 获取今日校园api
def getCpdailyApis(user):
    apis = {}
    user = user['user']
    idsUrl ="https://tianshi.campusphere.net/iap"
    ampUrl ="https://tianshi.campusphere.net/wec-portal-mobile/client"
    ampUrl2 = ""
    if 'campusphere' in ampUrl or 'cpdaily' in ampUrl:
        parse = urlparse(ampUrl)
        host = parse.netloc
        apis[
            'login-url'] = idsUrl + '/login?service=' + parse.scheme + r"%3A%2F%2F" + host + r'%2Fportal%2Flogin'
        apis['host'] = host
    if 'campusphere' in ampUrl2 or 'cpdaily' in ampUrl2:
        parse = urlparse(ampUrl2)
        host = parse.netloc
        apis[
            'login-url'] = idsUrl + '/login?service=' + parse.scheme + r"%3A%2F%2F" + host + r'%2Fportal%2Flogin'
        apis['host'] = host
    log(apis)
    return apis


# 登陆并获取session
def getSession(user, apis):
    user = user['user']
    params = {
        # 'login_url': 'http://authserverxg.swu.edu.cn/authserver/login?service=https://swu.cpdaily.com/wec-counselor-sign-apps/stu/sign/getStuSignInfosInOneDay',
        'login_url': apis['login-url'],
        'needcaptcha_url': '',
        'captcha_url': '',
        'username': user['username'],
        'password': user['password']
    }

    cookies = {}
    # 借助上一个项目开放出来的登陆API,模拟登陆
    res = ''
    try:
        res = requests.post(url=config['login']['api'], data=params, verify=not debug)
    except Exception as e:
        res = requests.post(url='http://127.0.0.1:8080/wisedu-unified-login-api-v1.0/api/login', data=params, verify=not debug)
    
    # cookieStr可以使用手动抓包获取到的cookie,有效期暂时未知,请自己测试
    # cookieStr = str(res.json()['cookies'])
    cookieStr = str(res.json()['cookies'])
    # log(cookieStr) 调试时再输出
    if cookieStr == 'None':
        log(res.json())
        exit(-1)
    # log(cookieStr)

    # 解析cookie
    for line in cookieStr.split(';'):
        name, value = line.strip().split('=', 1)
        cookies[name] = value
    session = requests.session()
    session.cookies = requests.utils.cookiejar_from_dict(cookies, cookiejar=None, overwrite=True)
    return session


# 获取最新未签到任务并全部签到
def getUnSignedTasksAndSign(session, apis, user):
    headers = {
        'Accept': 'application/json, text/plain, */*',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36',
        'content-type': 'application/json',
        'Accept-Encoding': 'gzip,deflate',
        'Accept-Language': 'zh-CN,en-US;q=0.8',
        'Content-Type': 'application/json;charset=UTF-8'
    }
    # 第一次请求每日签到任务接口,主要是为了获取MOD_AUTH_CAS
    res = session.post(
        url='https://{host}/wec-counselor-sign-apps/stu/sign/getStuSignInfosInOneDay'.format(host=apis['host']),
        headers=headers, data=json.dumps({}), verify=not debug)
    # 第二次请求每日签到任务接口,拿到具体的签到任务
    res = session.post(
        url='https://{host}/wec-counselor-sign-apps/stu/sign/getStuSignInfosInOneDay'.format(host=apis['host']),
        headers=headers, data=json.dumps({}), verify=not debug)
    if len(res.json()['datas']['unSignedTasks']) < 1:
        log('当前没有未签到任务')
        exit(-1)
    # log(res.json())
    for i in range(0, len(res.json()['datas']['unSignedTasks'])):
       #if '出校' in res.json()['datas']['unSignedTasks'][i]['taskName'] == False:
        # if '入校' in res.json()['datas']['unSignedTasks'][i]['taskName'] == False:
            latestTask = res.json()['datas']['unSignedTasks'][i]
            params = {
              'signInstanceWid': latestTask['signInstanceWid'],
              'signWid': latestTask['signWid']
            }
            task = getDetailTask(session, params, apis)
            form = fillForm(task, session, user, apis)
            
            submitForm(session, user, form, apis)


# 获取签到任务详情
def getDetailTask(session, params, apis):
    headers = {
        'Accept': 'application/json, text/plain, */*',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36 cpdaily/8.2.9 wisedu/8.2.9',
        'content-type': 'application/json',
        'Accept-Encoding': 'gzip,deflate',
        'Accept-Language': 'zh-CN,en-US;q=0.8',
        'Content-Type': 'application/json;charset=UTF-8'
    }
    res = session.post(
        url='https://{host}/wec-counselor-sign-apps/stu/sign/detailSignInstance'.format(host=apis['host']),
        headers=headers, data=json.dumps(params), verify=not debug)
    data = res.json()['datas']
    return data


# 填充表单
def fillForm(task, session, user, apis):
    user = user['user']
    form = {}
    if task['isPhoto'] == 1:
        fileName = uploadPicture(session, user['photo'], apis)
        form['signPhotoUrl'] = getPictureUrl(session, fileName, apis)
    else:
        form['signPhotoUrl'] = ''
    if task['isNeedExtra'] == 1:
        extraFields = task['extraField']
        defaults = config['cpdaily']['defaults']
        extraFieldItemValues = []
        for i in range(0, len(extraFields)):
            default = defaults[i]['default']
            extraField = extraFields[i]
            if config['cpdaily']['check'] and default['title'] != extraField['title']:
                log('第%d个默认配置项错误,请检查' % (i + 1))
                exit(-1)
            extraFieldItems = extraField['extraFieldItems']
            for extraFieldItem in extraFieldItems:
                if extraFieldItem['content'] == default['value']:
                    extraFieldItemValue = {'extraFieldItemValue': default['value'],
                                           'extraFieldItemWid': extraFieldItem['wid']}
                    # 其他,额外文本
                    if extraFieldItem['isOtherItems'] == 1:
                        extraFieldItemValue = {'extraFieldItemValue': default['other'],
                                               'extraFieldItemWid': extraFieldItem['wid']}
                    extraFieldItemValues.append(extraFieldItemValue)
        # log(extraFieldItemValues)
        # 处理带附加选项的签到
        form['extraFieldItems'] = extraFieldItemValues
    # form['signInstanceWid'] = params['signInstanceWid']
    form['signInstanceWid'] = task['signInstanceWid']
    form['longitude'] = user['lon']
    form['latitude'] = user['lat']
    form['isMalposition'] = task['isMalposition']
    form['abnormalReason'] = user['abnormalReason']
    form['position'] = user['address']
    form['uaIsCpadaily'] = True
    form['signVersion'] ='1.0.0'
    return form


# 上传图片到阿里云oss
def uploadPicture(session, image, apis):
    url = 'https://{host}/wec-counselor-sign-apps/stu/oss/getUploadPolicy'.format(host=apis['host'])
    res = session.post(url=url, headers={'content-type': 'application/json'}, data=json.dumps({'fileType':1}), verify=not debug)
    datas = res.json().get('datas')
    #log(datas)
    #new_api_upload
    fileName = datas.get('fileName') + '.png'
    accessKeyId = datas.get('accessid')
    xhost = datas.get('host')
    xdir = datas.get('dir')
    xpolicy = datas.get('policy')
    signature = datas.get('signature')
    #new_api_upload
    #new_api_upload2
    url = xhost + '/'
    data={
        'key':fileName,
        'policy':xpolicy,
        'OSSAccessKeyId':accessKeyId,
        'success_action_status':'200',
        'signature':signature
    }
    data_file = {
        'file':('blob',open(image,'rb'),'image/jpg')
    }
    res = session.post(url=url,data=data,files=data_file)
    if(res.status_code == requests.codes.ok):
        return fileName
    #new_api_upload2
    #log(res)
    return fileName
    return ''


# 获取图片上传位置
def getPictureUrl(session, fileName, apis):
    url = 'https://{host}/wec-counselor-sign-apps/stu/sign/previewAttachment'.format(host=apis['host'])
    data = {
        'ossKey': fileName
    }
    res = session.post(url=url, headers={'content-type': 'application/json'}, data=json.dumps(data), verify=not debug)
    photoUrl = res.json().get('datas')
    return photoUrl


# DES加密
def DESEncrypt(s, key='b3L26XNL'):
    key = key
    iv = b"\x01\x02\x03\x04\x05\x06\x07\x08"
    k = des(key, CBC, iv, pad=None, padmode=PAD_PKCS5)
    encrypt_str = k.encrypt(s)
    return base64.b64encode(encrypt_str).decode()


# 提交签到任务
def submitForm(session, user, form, apis):
    user = user['user']
    # Cpdaily-Extension
    extension = {
        "lon": user['lon'],
        "model": "OPPO R11 Plus",
        "appVersion": "8.1.14",
        "systemVersion": "8.0",
        "userId": user['username'],
        "systemName": "android",
        "lat": user['lat'],
        "deviceId": str(uuid.uuid1())
    }

    headers = {
        # 'tenantId': '1019318364515869',
        'User-Agent': 'Mozilla/5.0 (Linux; Android 4.4.4; OPPO R11 Plus Build/KTU84P) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/33.0.0.0 Safari/537.36 okhttp/3.12.4',
        'CpdailyStandAlone': '0',
        'extension': '1',
        'Cpdaily-Extension': DESEncrypt(json.dumps(extension)),
        'Content-Type': 'application/json; charset=utf-8',
        'Accept-Encoding': 'gzip',
        # 'Host': 'swu.cpdaily.com',
        'Connection': 'Keep-Alive'
    }
    res = session.post(url='https://{host}/wec-counselor-sign-apps/stu/sign/submitSign'.format(host=apis['host']),
                       headers=headers, data=json.dumps(form), verify=not debug)
    message = res.json()['message']
    if message == 'SUCCESS':
        log('自动签到成功')
        sendMessage('自动签到成功', user['email'])
    else:
        log('自动签到失败,原因是:' + message)
        # sendMessage('自动签到失败,原因是:' + message, user['email'])
        exit(-1)


# 发送邮件通知
def sendMessage(msg, email):
    send = email
    if msg.count("未开始")>0:
        return ''
    try:
        if send != '':
                log('正在发送邮件通知。。。')
                res = requests.post(url='https://api.ruoli.cc/sendMail',data={'title': '若离今日校园通知' , 'content': msg, 'to': send}, verify=not debug)
                code = res
                if code == 'ok':
                    log('发送邮件通知成功。。。')
                else:
                    log('发送邮件通知失败。。。')
                log(res)
    except Exception as e:
        log("send failed")



# 主函数
def main():
    for user in config['users']:
        apis = getCpdailyApis(user)
        session = getSession(user, apis)
        getUnSignedTasksAndSign(session, apis, user)


# 提供给腾讯云函数调用的启动函数
def main_handler(event, context):
    try:
        main()
    except Exception as e:
        raise e
    else:
        return 'success'


if __name__ == '__main__':
    # print(extension)
    print(main_handler({}, {}))

config.yml

#登陆相关配置
login:
  #开放的模拟登陆api服务器地址
  api: "http://www.yewan.life:8080/wisedu-unified-login-api-v1.0/api/login"
#用户组配置
users:
  #单个用户配置
  - user:
      #username 学号或者工号
      username: '*******'
      #password 密码
      password: '**********'
      #address 地址,定位信息
      address: 中国天津市武清区东蒲洼街道
      #email 接受通知消息的邮箱
      email: *********@qq.com
      #school 学校全称
      school: 天津天狮学院
      #lon 经度
      lon: '117.046180'
      #lat 纬度
      lat: '39.434766'
      #abnormalReason 反馈信息
      abnormalReason: 
      #photo 签到照片,不需要可不填,或者直接删除
      photo: 
  #多用户配置,将下面的注释去掉即可,如果有表单内容有图片,不建议使用多用户配置
#今日校园相关配置
cpdaily:
  #是否检查问题,true检查,false不检查
  check: false
  #附加信息组默认选项配置,没有可留空
  defaults:
    - default:
        other: '36.1'
        title: 今日体温
        value: 其它
        
最近的文章

闲来无事试试还有没有更方便的疫情打卡项目 申明…

继续阅读
更早的文章

始 疫情期间突然我们又多了一项任务,健康打卡,那只该死的蝙蝠,真是害死我了,天天重复无聊的填表操作,还可能会忘记,足以ť…

继续阅读