├── .gitignore ├── src └── main │ └── java │ └── daily │ ├── pojo │ ├── KeyValueClass.java │ ├── MessageBox.java │ └── UserConfig.java │ ├── constant │ ├── Headers.java │ ├── CpDaily.java │ └── CpDailyApi.java │ ├── AutoDailyCp.java │ ├── request │ ├── ServerChanRequest.java │ ├── InitialRequest.java │ ├── LoginRequest.java │ └── SignRequest.java │ ├── util │ └── DesUtil.java │ └── SignApplication.java ├── README.md ├── docs ├── update-log.md └── how-to-usegithub-action.md ├── LICENSE ├── .github └── workflows │ └── sign-action-config.yml └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | target 4 | daily.properties 5 | mail.properties 6 | test -------------------------------------------------------------------------------- /src/main/java/daily/pojo/KeyValueClass.java: -------------------------------------------------------------------------------- 1 | package daily.pojo; 2 | 3 | /** 4 | * @author Neo.Zzj 5 | * @date 2021/2/19 6 | */ 7 | public class KeyValueClass { 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/daily/constant/Headers.java: -------------------------------------------------------------------------------- 1 | package daily.constant; 2 | 3 | /** 4 | * @author Neo.Zzj 5 | * @date 2020/7/16 6 | */ 7 | public interface Headers { 8 | 9 | String APPLICATION_JSON = "application/json"; 10 | 11 | } 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 今日校园自动签到-SWU 2 | 3 | 用于swu的今日校园打卡脚本 4 | 5 | 6 | 7 | ## 说明 8 | 9 | 本人已经毕业,所以以后可能无法及时回复/更新,有需要的可以优先使用:[F-19-F/SWU-CpDaily](https://github.com/F-19-F/SWU-CpDaily) 10 | 11 | 此仓库仅用于学习使用,请勿用于非法用途 12 | 13 | 有问题可联系邮箱:me@neow.cc 14 | 15 | ## 使用 16 | 17 | - **腾讯云函数** 18 | 19 | 使用手册:https://blog.neoniou.com/posts/auto-serverless-readme/ 20 | 21 | ## 其他 22 | 23 | - 更新记录:[链接](./docs/update-log.md) 24 | - releases(用于云函数):[链接](https://github.com/aowubulao/auto-cpdaily/releases) 25 | -------------------------------------------------------------------------------- /src/main/java/daily/pojo/MessageBox.java: -------------------------------------------------------------------------------- 1 | package daily.pojo; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.Date; 6 | 7 | /** 8 | * @author Neo.Zzj 9 | * @date 2020/7/17 10 | */ 11 | @Data 12 | public class MessageBox { 13 | 14 | private Integer type; 15 | 16 | private String taskName; 17 | 18 | private String rateTaskBeginTime; 19 | 20 | private String rateTaskEndTime; 21 | 22 | private Date currentTime; 23 | 24 | private String signInstanceWid; 25 | 26 | private String signWid; 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/daily/AutoDailyCp.java: -------------------------------------------------------------------------------- 1 | package daily; 2 | 3 | import daily.pojo.KeyValueClass; 4 | 5 | /** 6 | * @author Neo.Zzj 7 | * @date 2020/6/22 8 | */ 9 | public class AutoDailyCp { 10 | 11 | /** 12 | * Github Action以及测试用入口 13 | * 14 | * @param args Secret 参数 15 | * @throws Exception 异常 16 | */ 17 | public static void main(String[] args) throws Exception { 18 | SignApplication.start(args); 19 | } 20 | 21 | /** 22 | * 腾讯云函数入口 23 | * 24 | * @param kv 腾讯云函数必须使用的一个类,无实际作用 25 | * @throws Exception 异常 26 | */ 27 | public void mainHandler(KeyValueClass kv) throws Exception { 28 | SignApplication.start(); 29 | } 30 | } -------------------------------------------------------------------------------- /src/main/java/daily/pojo/UserConfig.java: -------------------------------------------------------------------------------- 1 | package daily.pojo; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author Neo.Zzj 7 | * @date 2020/7/20 8 | */ 9 | @Data 10 | public class UserConfig { 11 | 12 | private String username; 13 | 14 | private String password; 15 | 16 | private String position; 17 | 18 | private String longitude; 19 | 20 | private String latitude; 21 | 22 | private Boolean activeAttendance; 23 | 24 | private String scKey; 25 | 26 | public UserConfig() { 27 | this.username = null; 28 | } 29 | 30 | public UserConfig(String username, String password, String position, String longitude, String latitude, Boolean activeAttendance) { 31 | this.username = username; 32 | this.password = password; 33 | this.position = position; 34 | this.longitude = longitude; 35 | this.latitude = latitude; 36 | this.activeAttendance = activeAttendance; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /docs/update-log.md: -------------------------------------------------------------------------------- 1 | ## 更新记录 2 | 3 | **2021.05.20** 4 | 5 | 适配新登录接口 6 | 7 | **2021.02.08** 8 | 9 | 新增Github Action打卡 10 | 11 | **2021.01.24** 12 | 13 | 修复了请假后无法打卡的问题 14 | 15 | **2021.01.08** 16 | 17 | **重要更新:可以提交查寝了**(虽然已经放假了=w=),具体可以在配置文件中设置是否启用。**查寝的话经纬度需要在查寝范围内**。 18 | 19 | 目前在测试中,应该没有问题。 20 | 21 | **2021.01.07** 22 | 23 | 感谢[链接](https://github.com/ZimoLoveShuang/auto-sign/issues/38)提供的DES key =w=,更新了新的des key 24 | 25 | **2020.12.29** 26 | 27 | 增加了server酱的推送,可以将打卡结果推送到微信,下载最新版,然后[使用链接](https://blog.neoniou.com/posts/auto-serverless-readme/#6%E3%80%81%E9%85%8D%E7%BD%AE-Server%E9%85%B1%E6%8E%A8%E9%80%81) 28 | 29 | **2020.12.25** 30 | 31 | 删除了很蠢的更新机制,更新了API,以后如果打卡失败请及时提醒我更新API。 32 | 33 | 关于腾讯云函数登录失败的问题,把定时任务执行的cron表达式改为: 34 | 35 | ``` 36 | 0 0 9,20 * * * * 37 | ``` 38 | 39 | 这表示每天9点、20点执行一次。 40 | 41 | **2020.12.1** 42 | 43 | 新版 44 | 45 | **2020.10.30** 46 | 47 | 更新了今日校园新的API 48 | 49 | 更新了从服务器获取最新API的逻辑,后续如果今日校园API再有变动,不用重新下载安装程序,只需等待服务端热更新API 50 | 51 | **2020.10.23** 52 | 53 | 更新了今日校园新的API 54 | 55 | **2020.7.23** 56 | 57 | 更新同一设备登录无法签到的问题 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Neo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/java/daily/constant/CpDaily.java: -------------------------------------------------------------------------------- 1 | package daily.constant; 2 | 3 | /** 4 | * @author Neo.Zzj 5 | * @date 2020/7/20 6 | */ 7 | public interface CpDaily { 8 | 9 | String TENANT_ID = "1019318364515869"; 10 | 11 | String CP_EXTENSION = "{\"systemName\":\"android\",\"systemVersion\":\"10\",\"model\":\"Mi 10\"" + 12 | ",\"deviceId\":\"r1\",\"appVersion\":" + 13 | "\"8.2.14\",\"lon\":r2,\"lat\":r3,\"userId\":\"r4\"}"; 14 | 15 | String SIGN_INFO = "{\"longitude\":r1,\"latitude\":r2,\"isMalposition\":1,\"abnormalReason\":\"\"," + 16 | "\"signPhotoUrl\":\"\",\"isNeedExtra\":1,\"position\":\"local\"," + 17 | "\"uaIsCpadaily\":true,\"signInstanceWid\":\"siWid\",\"" + 18 | "extraFieldItems\":[{\"extraFieldItemValue\":\"正常,<37.2℃\",\"extraFieldItemWid\":itemId}],\"signVersion\":\"1.0.0\"}"; 19 | 20 | String SUBMIT_INFO = "{\"signInstanceWid\":\"siWid\",\"longitude\":r1,\"latitude\":r2,\"isMalposition\":0" + 21 | ",\"abnormalReason\":\"\",\"signPhotoUrl\":\"" + 22 | "https://wecres.cpdaily.com/counselor/1019318364515869/attachment/03b0532446d14be99f057ba744c7af95.png\",\"" + 23 | "position\":\"local\",\"uaIsCpadaily\":true}"; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/daily/constant/CpDailyApi.java: -------------------------------------------------------------------------------- 1 | package daily.constant; 2 | 3 | /** 4 | * @author Neo.Zzj 5 | * @date 2021/2/19 6 | */ 7 | public interface CpDailyApi { 8 | 9 | String SWU_LT = "https://swu.campusphere.net/iap/security/lt"; 10 | 11 | String SWU_DO_LOGIN = "https://swu.campusphere.net/iap/doLogin"; 12 | 13 | String SWU_AUTH_LOGIN = "https://swu.campusphere.net/iap/login?service=https%3A%2F%2Fswu.campusphere.net%2Fportal%2Flogin"; 14 | 15 | String SIGN_GET_MESSAGE = "https://swu.campusphere.net/wec-counselor-sign-apps/stu/sign/getStuSignInfosInOneDay"; 16 | 17 | String SIGN_GET_FORM = "https://swu.campusphere.net/wec-counselor-sign-apps/stu/sign/detailSignInstance"; 18 | 19 | String SIGN_SUBMIT_FORM = "https://swu.campusphere.net/wec-counselor-sign-apps/stu/sign/submitSign"; 20 | 21 | String ATTENDANCE_GET_MESSAGE = "https://swu.campusphere.net/wec-counselor-attendance-apps/student/attendance/getStuAttendacesInOneDay"; 22 | 23 | String ATTENDANCE_GET_FORM = "https://swu.campusphere.net/wec-counselor-attendance-apps/student/attendance/detailSignInstance"; 24 | 25 | String ATTENDANCE_SUBMIT_FORM = "https://swu.campusphere.net/wec-counselor-attendance-apps/student/attendance/submitSign"; 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/sign-action-config.yml: -------------------------------------------------------------------------------- 1 | #每天9:02 20:02 22:02 进行三次打卡 2 | 3 | name: Auto Sign 4 | 5 | on: 6 | schedule: 7 | - cron: '2 1,12,13 * * *' 8 | push: 9 | branches: 10 | - serverless 11 | - master 12 | - dev 13 | 14 | jobs: 15 | auto-cpdaily: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Set up JDK 1.8 21 | uses: actions/setup-java@v1 22 | with: 23 | java-version: 1.8 24 | - name: Cache local Maven repository 25 | uses: actions/cache@v2 26 | with: 27 | path: ~/.m2/repository 28 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 29 | restore-keys: | 30 | ${{ runner.os }}-maven- 31 | - name: Build with Maven 32 | env: 33 | USERNAME: ${{ secrets.USERNAME }} 34 | PASSWORD: ${{ secrets.PASSWORD }} 35 | POSITION: ${{ secrets.POSITION }} 36 | LONGITUDE: ${{ secrets.LONGITUDE }} 37 | LATITUDE: ${{ secrets.LATITUDE }} 38 | ACTIVEATTENDANCE: ${{ secrets.ACTIVEATTENDANCE }} 39 | SCKEY: ${{ secrets.SCKEY }} 40 | run: | 41 | mvn compile exec:java -Dexec.mainClass="daily.AutoDailyCp" -Dexec.args="${USERNAME} ${PASSWORD} ${POSITION} ${LONGITUDE} ${LATITUDE} ${ACTIVEATTENDANCE} ${SCKEY}" 42 | -------------------------------------------------------------------------------- /docs/how-to-usegithub-action.md: -------------------------------------------------------------------------------- 1 | ## 利用Github Action进行签到 2 | 3 | ### 一、Fork并配置 4 | 5 | 1、进入仓库,点击右上角的**Fork** 6 | 7 | ![](https://img.neoniou.com/blog/20210207133537.png) 8 | 9 | 2、Fork过后自动进入了Fork过后的仓库 10 | 11 | 3、点击**Settings->Secrets** 12 | 13 | ![](https://img.neoniou.com/blog/20210207133709.png) 14 | 15 | ![](https://img.neoniou.com/blog/20210207133733.png) 16 | 17 | 4、点击**New Reposity secret**创建新的secret 18 | 19 | ![](https://img.neoniou.com/blog/20210207133849.png) 20 | 21 | ![](https://img.neoniou.com/blog/20210207133929.png) 22 | 23 | 5、分别创建**6个secret**,如图 24 | 25 | ![](https://img.neoniou.com/blog/20210207134319.png) 26 | 27 | 6、微信推送服务(可选) 28 | 29 | 本脚本使用server酱推送,首先你要申请到一个sc key,教程:[链接](https://blog.neoniou.com/posts/auto-serverless-readme/#6%E3%80%81%E9%85%8D%E7%BD%AE-Server%E9%85%B1%E6%8E%A8%E9%80%81) 30 | 31 | 随后将这个key也添加到secret中即可 32 | 33 | ![](https://img.neoniou.com/blog/20210207134530.png) 34 | 35 | ### 二、启动Github Actions 36 | 37 | ![](https://img.neoniou.com/blog/20210207134620.png) 38 | 39 | 1、点击Actions并启用 40 | 41 | ![](https://img.neoniou.com/blog/20210207134657.png) 42 | 43 | ![](https://img.neoniou.com/blog/20210207134718.png) 44 | 45 | 2、进行一次push 46 | 47 | 编辑README.md 48 | 49 | ![](https://img.neoniou.com/blog/20210207134906.png) 50 | 51 | 52 | 53 | 随便删除或者增加一个空格什么的就可以,滑到页面最下方 54 | 55 | ![](https://img.neoniou.com/blog/20210207135037.png) 56 | 57 | ### 三、完成! 58 | 59 | 进入Actions,可以看到已经成功启动,本脚本每天9:02,20:02,22:02会进行三次检测进行打卡 60 | 61 | ![](https://img.neoniou.com/blog/20210207135104.png) 62 | 63 | ### 四、如何进行更新? 64 | 65 | 删除掉这个仓库,重新进行[一]到[三]步即可 -------------------------------------------------------------------------------- /src/main/java/daily/request/ServerChanRequest.java: -------------------------------------------------------------------------------- 1 | package daily.request; 2 | 3 | import cn.hutool.http.HttpRequest; 4 | import cn.hutool.http.HttpResponse; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | /** 8 | * @author Neo.Zzj 9 | * @date 2020/12/29 10 | */ 11 | @Slf4j 12 | public class ServerChanRequest { 13 | 14 | private static String url = null; 15 | 16 | private static final String KEY = "SCU"; 17 | 18 | private static String scKey; 19 | 20 | public static void initial(String key) { 21 | url = "https://sc.ftqq.com/" + key + ".send"; 22 | scKey = key; 23 | } 24 | 25 | /** 26 | * ServerChan发送消息 27 | * 28 | * @param message 消息标题 29 | * @param description 消息描述 30 | */ 31 | public static void sendMessage(String message, String description) { 32 | send(message, description); 33 | } 34 | 35 | private static void send(String message, String description) { 36 | if (scKey != null && scKey.contains(KEY)) { 37 | log.info("发送server酱通知消息至微信"); 38 | 39 | try { 40 | HttpResponse response = HttpRequest.post(url) 41 | .form("text", message) 42 | .form("desp", description) 43 | .execute(); 44 | 45 | if (response.isOk()) { 46 | log.info("server酱推送成功!"); 47 | } else { 48 | log.info("server酱推送失败,返回状态码: [{}]", response.getStatus()); 49 | } 50 | } catch (Exception e) { 51 | log.error("server酱通知消息发送失败,错误原因:", e); 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/daily/request/InitialRequest.java: -------------------------------------------------------------------------------- 1 | package daily.request; 2 | 3 | import cn.hutool.core.util.RandomUtil; 4 | import daily.pojo.UserConfig; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.util.Properties; 10 | 11 | /** 12 | * @author Neo.Zzj 13 | * @date 2020/10/31 14 | */ 15 | @Slf4j 16 | public class InitialRequest { 17 | 18 | /** 19 | * 随机坐标最小值 20 | */ 21 | private static final int RANDOM_MIN = -50; 22 | 23 | /** 24 | * 随机坐标最大值 25 | */ 26 | private static final int RANDOM_MAX = 50; 27 | 28 | private static final int MULTIPLIER = 1000000; 29 | 30 | /** 31 | * 初始化配置文件 32 | * 33 | * @return UserConfig 34 | */ 35 | public static UserConfig initialConfig() { 36 | return readDailyProps(); 37 | } 38 | 39 | public static void initialServerChan(String scKey) { 40 | ServerChanRequest.initial(scKey); 41 | } 42 | 43 | private static UserConfig readDailyProps() { 44 | Properties props = new Properties(); 45 | InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("daily.properties"); 46 | try { 47 | UserConfig userConfig = new UserConfig(); 48 | props.load(is); 49 | userConfig.setUsername(props.getProperty("username")); 50 | userConfig.setPassword(props.getProperty("password")); 51 | userConfig.setLongitude(randomPosition(props.getProperty("longitude"))); 52 | userConfig.setLatitude(randomPosition(props.getProperty("latitude"))); 53 | userConfig.setPosition(props.getProperty("position")); 54 | userConfig.setScKey(props.getProperty("scKey")); 55 | userConfig.setActiveAttendance(true); 56 | userConfig.setActiveAttendance(Boolean.parseBoolean(props.getProperty("activeAttendance"))); 57 | return userConfig; 58 | } catch (IOException e) { 59 | log.info("读取daily.properties错误: ", e); 60 | return null; 61 | } 62 | } 63 | 64 | /** 65 | * 位置坐标随机正负50 66 | * 67 | * @param positionStr 位置坐标字符串 68 | * @return 随机后的坐标 69 | */ 70 | private static String randomPosition(String positionStr) { 71 | double position = Double.parseDouble(positionStr); 72 | 73 | double randomInt = RandomUtil.randomInt(RANDOM_MIN, RANDOM_MAX); 74 | position -= randomInt / MULTIPLIER; 75 | return String.format("%.6f", position); 76 | } 77 | 78 | } -------------------------------------------------------------------------------- /src/main/java/daily/request/LoginRequest.java: -------------------------------------------------------------------------------- 1 | package daily.request; 2 | 3 | import cn.hutool.http.Header; 4 | import cn.hutool.http.HttpRequest; 5 | import cn.hutool.http.HttpResponse; 6 | import cn.hutool.json.JSONUtil; 7 | import daily.constant.CpDailyApi; 8 | 9 | import java.net.HttpCookie; 10 | import java.util.HashMap; 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | 15 | /** 16 | * @author Neo.Zzj 17 | * @date 2020/7/20 18 | */ 19 | public class LoginRequest { 20 | 21 | private static final int OK = 200; 22 | 23 | private static String lt; 24 | 25 | /** 26 | * 根据用户名密码登录 27 | * 28 | * @param username 用户名(学号) 29 | * @param password 密码 30 | * @return 登录后的Cookie, 返回null为登录失败 31 | */ 32 | public static String login(String username, String password) { 33 | HttpResponse ltRes = HttpRequest.post(CpDailyApi.SWU_LT) 34 | .body("lt", "") 35 | .execute(); 36 | 37 | String lt = JSONUtil.parseObj(ltRes.body()).getJSONObject("result").getStr("_lt"); 38 | 39 | HttpResponse loginRes = HttpRequest.post(CpDailyApi.SWU_DO_LOGIN) 40 | .form(genLoginMap(username, password, lt)) 41 | .cookie(parseCookie(ltRes.getCookies())) 42 | .execute(); 43 | 44 | HttpResponse doLoginRes = HttpRequest.get(CpDailyApi.SWU_AUTH_LOGIN) 45 | .cookie(parseCookie(loginRes.getCookies())) 46 | .execute(); 47 | 48 | HttpResponse resultRes = HttpRequest.get(doLoginRes.header(Header.LOCATION)) 49 | .cookie(parseCookie(doLoginRes.getCookies())) 50 | .execute(); 51 | 52 | return parseCookie(resultRes.getCookies()); 53 | } 54 | 55 | private static String parseCookie(List cookies) { 56 | StringBuilder sb = new StringBuilder(); 57 | for (HttpCookie cookie : cookies) { 58 | sb.append(cookie.toString()).append(";"); 59 | } 60 | sb.deleteCharAt(sb.length() - 1); 61 | return sb.toString(); 62 | } 63 | 64 | private static Map genLoginMap(String username, String password, String lt) { 65 | Map loginMap = new HashMap<>(16); 66 | loginMap.put("username", username); 67 | loginMap.put("password", password); 68 | loginMap.put("mobile", ""); 69 | loginMap.put("dllt", ""); 70 | loginMap.put("captcha", ""); 71 | loginMap.put("rememberMe", "false"); 72 | loginMap.put("lt", lt); 73 | return loginMap; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/daily/util/DesUtil.java: -------------------------------------------------------------------------------- 1 | package daily.util; 2 | 3 | import javax.crypto.BadPaddingException; 4 | import javax.crypto.Cipher; 5 | import javax.crypto.IllegalBlockSizeException; 6 | import javax.crypto.NoSuchPaddingException; 7 | import javax.crypto.spec.IvParameterSpec; 8 | import javax.crypto.spec.SecretKeySpec; 9 | import java.io.UnsupportedEncodingException; 10 | import java.security.InvalidAlgorithmParameterException; 11 | import java.security.InvalidKeyException; 12 | import java.security.NoSuchAlgorithmException; 13 | import java.util.Base64; 14 | 15 | /** 16 | * @author Neo.Zzj 17 | * @date 2020/7/23 18 | */ 19 | public class DesUtil { 20 | 21 | private static final String DEFAULT_KEY = "b3L26XNL"; 22 | private static final byte[] IV = {1, 2, 3, 4, 5, 6, 7, 8}; 23 | 24 | private static final String CHARSET_NAME = "utf-8"; 25 | private static final String DES = "DES"; 26 | private static final String CIPHER_NAME = "DES/CBC/PKCS5Padding"; 27 | 28 | private static byte[] encrypt(String data) throws NoSuchAlgorithmException, UnsupportedEncodingException, NoSuchPaddingException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException { 29 | Cipher cipher = Cipher.getInstance(CIPHER_NAME); 30 | SecretKeySpec secretKeySpec = new SecretKeySpec(DEFAULT_KEY.getBytes(CHARSET_NAME), DES); 31 | IvParameterSpec ivParameterSpec = new IvParameterSpec(IV); 32 | cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec); 33 | return cipher.doFinal(data.getBytes(CHARSET_NAME)); 34 | } 35 | 36 | private static String decrypt(byte[] data) throws UnsupportedEncodingException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException { 37 | Cipher cipher = Cipher.getInstance(CIPHER_NAME); 38 | SecretKeySpec secretKeySpec = new SecretKeySpec(DEFAULT_KEY.getBytes(CHARSET_NAME), DES); 39 | IvParameterSpec ivParameterSpec = new IvParameterSpec(IV); 40 | cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec); 41 | return new String(cipher.doFinal(data), CHARSET_NAME); 42 | } 43 | 44 | private static String base64Encrypt(byte[] data) { 45 | return Base64.getEncoder().encodeToString(data); 46 | } 47 | 48 | private static byte[] base64Decrypt(String data) throws UnsupportedEncodingException { 49 | return Base64.getMimeDecoder().decode(data.getBytes(CHARSET_NAME)); 50 | } 51 | 52 | public static String encode(String data) throws NoSuchPaddingException, BadPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, UnsupportedEncodingException, InvalidKeyException { 53 | return base64Encrypt(encrypt(data)); 54 | } 55 | 56 | public static String decode(String data) throws UnsupportedEncodingException, NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException { 57 | return decrypt(base64Decrypt(data)); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.neoniou 8 | auto-cpdaily-serverless 9 | 1.4.0 10 | 11 | 12 | spring-boot-starter-parent 13 | org.springframework.boot 14 | 2.3.1.RELEASE 15 | 16 | 17 | 18 | UTF-8 19 | UTF-8 20 | 8 21 | 8 22 | 23 | 24 | 25 | 26 | cn.hutool 27 | hutool-core 28 | 5.1.2 29 | 30 | 31 | cn.hutool 32 | hutool-json 33 | 5.1.2 34 | 35 | 36 | cn.hutool 37 | hutool-http 38 | 5.7.19 39 | 40 | 41 | org.projectlombok 42 | lombok 43 | 1.18.12 44 | 45 | 46 | ch.qos.logback 47 | logback-classic 48 | 1.2.3 49 | 50 | 51 | org.junit.jupiter 52 | junit-jupiter-api 53 | 5.7.0 54 | test 55 | 56 | 57 | 58 | 59 | 60 | 61 | org.apache.maven.plugins 62 | maven-shade-plugin 63 | 3.2.4 64 | 65 | false 66 | 67 | 68 | 69 | package 70 | 71 | shade 72 | 73 | 74 | 75 | 76 | 77 | org.codehaus.mojo 78 | exec-maven-plugin 79 | 80 | 81 | 82 | java 83 | 84 | 85 | 86 | 87 | daily.AutoDailyCp 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/main/java/daily/SignApplication.java: -------------------------------------------------------------------------------- 1 | package daily; 2 | 3 | import daily.constant.CpDaily; 4 | import daily.pojo.MessageBox; 5 | import daily.pojo.UserConfig; 6 | import daily.request.InitialRequest; 7 | import daily.request.LoginRequest; 8 | import daily.request.ServerChanRequest; 9 | import daily.request.SignRequest; 10 | import daily.util.DesUtil; 11 | import lombok.extern.slf4j.Slf4j; 12 | 13 | import java.util.List; 14 | import java.util.UUID; 15 | 16 | /** 17 | * @author Neo.Zzj 18 | * @date 2021/2/19 19 | */ 20 | @Slf4j 21 | public class SignApplication { 22 | 23 | /** 24 | * Github Action需求的最少参数量 25 | */ 26 | private static final int MIN_PARAMS = 6; 27 | 28 | private static final int ZERO = 0; 29 | 30 | 31 | public static void start(String... args) { 32 | UserConfig userConfig = readAndConfigArgs(args); 33 | if (userConfig == null) { 34 | return; 35 | } 36 | 37 | try { 38 | startApplication(userConfig); 39 | } catch (Exception e) { 40 | log.error("打卡失败,错误信息:", e); 41 | ServerChanRequest.sendMessage("今日校园自动打卡失败啦QAQ!", 42 | "如果一直打卡失败的话,可以去github上看看有没有新版本,或者发送邮件到:me@neow.cc 提醒作者更新"); 43 | } 44 | } 45 | 46 | private static void startApplication(UserConfig userConfig) throws Exception { 47 | log.info("程序启动... By Neo"); 48 | 49 | // 初始化 50 | log.info("程序初始化中..."); 51 | //判断是否需要读取配置文件初始化 52 | if (userConfig.getUsername() == null) { 53 | userConfig = InitialRequest.initialConfig(); 54 | if (userConfig == null) { 55 | log.error("初始化失败"); 56 | return; 57 | } 58 | } 59 | InitialRequest.initialServerChan(userConfig.getScKey()); 60 | 61 | //登录 62 | String cookie = LoginRequest.login(userConfig.getUsername(), userConfig.getPassword()); 63 | if (cookie == null) { 64 | log.error("用户名或密码错误, 登陆失败"); 65 | return; 66 | } else { 67 | log.info("今日校园登陆成功"); 68 | } 69 | 70 | //签到 71 | sign(userConfig, cookie); 72 | } 73 | 74 | private static void sign(UserConfig userConfig, String cookie) throws Exception { 75 | SignRequest signRequest = new SignRequest(userConfig.getLongitude(), userConfig.getLatitude(), userConfig.getPosition()); 76 | 77 | signRequest.setCookie(cookie); 78 | List messages = signRequest.getMessage(userConfig.getActiveAttendance()); 79 | 80 | if (messages.size() < 1) { 81 | log.info("当前时间段未获取到签到信息"); 82 | } else { 83 | log.info("获取到[{}]条签到信息", messages.size()); 84 | 85 | int successNum = 0; 86 | 87 | String cpExtension = generateCpExtension(userConfig); 88 | for (MessageBox message : messages) { 89 | if (signRequest.submitMessage(message, cpExtension)) { 90 | successNum++; 91 | log.info("[{}]签到成功", message.getSignInstanceWid()); 92 | } else { 93 | log.info("[{}]签到失败", message.getSignInstanceWid()); 94 | } 95 | } 96 | log.info("签到完成, 成功[{}]条, 失败[{}]条", successNum, messages.size() - successNum); 97 | ServerChanRequest.sendMessage("来自今日校园自动打卡的通知=w=", 98 | "成功了" + successNum + "条" + ",失败了" + (messages.size() - successNum) + "条"); 99 | } 100 | } 101 | 102 | private static String generateCpExtension(UserConfig userConfig) throws Exception { 103 | String replace = CpDaily.CP_EXTENSION.replace("r1", UUID.randomUUID().toString()) 104 | .replace("r2", userConfig.getLongitude()) 105 | .replace("r3", userConfig.getLatitude()) 106 | .replace("r4", userConfig.getUsername()); 107 | return DesUtil.encode(replace); 108 | } 109 | 110 | private static UserConfig readAndConfigArgs(String[] args) { 111 | int length = args.length; 112 | 113 | UserConfig userConfig = new UserConfig(); 114 | // 参数大于0小于6则参数不完整,参数大于6则存在scKey,需要推送 115 | if (length < MIN_PARAMS && length > ZERO) { 116 | log.error("github仓库secret key数量设置错误!"); 117 | return null; 118 | } else if (length >= MIN_PARAMS) { 119 | userConfig = new UserConfig(args[0], args[1], args[2], args[3], args[4], Boolean.parseBoolean(args[5])); 120 | if (length > MIN_PARAMS) { 121 | userConfig.setScKey(args[6]); 122 | } 123 | } 124 | return userConfig; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/daily/request/SignRequest.java: -------------------------------------------------------------------------------- 1 | package daily.request; 2 | 3 | import cn.hutool.core.date.DateTime; 4 | import cn.hutool.core.date.DateUtil; 5 | import cn.hutool.http.HttpRequest; 6 | import cn.hutool.http.HttpResponse; 7 | import cn.hutool.json.JSONArray; 8 | import cn.hutool.json.JSONObject; 9 | import cn.hutool.json.JSONUtil; 10 | import daily.constant.CpDaily; 11 | import daily.constant.CpDailyApi; 12 | import daily.constant.Headers; 13 | import daily.pojo.MessageBox; 14 | import lombok.extern.slf4j.Slf4j; 15 | 16 | import java.util.ArrayList; 17 | import java.util.Date; 18 | import java.util.List; 19 | 20 | /** 21 | * @author Neo.Zzj 22 | * @date 2020/7/17 23 | */ 24 | @Slf4j 25 | public class SignRequest { 26 | 27 | private final String longitude; 28 | private final String latitude; 29 | private final String position; 30 | 31 | private static String cookie; 32 | 33 | private static final String UNSIGNED_TASKS = "unSignedTasks"; 34 | private static final String LEAVE_TASKS = "leaveTasks"; 35 | private static final String SIGNED_TASKS = "signedTasks"; 36 | private static final String DATAS = "datas"; 37 | private static final String SUCCESS = "SUCCESS"; 38 | private static final String MESSAGE = "message"; 39 | private static final String ATTENDANCE = "查寝"; 40 | 41 | public SignRequest(String longitude, String latitude, String position) { 42 | this.longitude = longitude; 43 | this.latitude = latitude; 44 | this.position = position; 45 | } 46 | 47 | public void setCookie(String cookies) { 48 | cookie = cookies; 49 | } 50 | 51 | /** 52 | * 获取当前时段的签到查寝信息 53 | * 54 | * @param activeAttendance 是否获取查寝信息 55 | * @return 信息列表 56 | */ 57 | public List getMessage(boolean activeAttendance) { 58 | JSONArray jsonArray = getResponseJson(CpDailyApi.SIGN_GET_MESSAGE); 59 | if (activeAttendance) { 60 | jsonArray.addAll(getResponseJson(CpDailyApi.ATTENDANCE_GET_MESSAGE)); 61 | } 62 | 63 | List messages = new ArrayList<>(); 64 | for (Object msg : jsonArray) { 65 | MessageBox message = JSONUtil.toBean(msg.toString(), MessageBox.class); 66 | if (msg.toString().contains(ATTENDANCE)) { 67 | message.setType(1); 68 | } else { 69 | message.setType(0); 70 | } 71 | DateTime startTime = DateUtil.parse(generateTime(message.getRateTaskBeginTime())); 72 | DateTime endTime = DateUtil.parse(generateTime(message.getRateTaskEndTime())); 73 | if (message.getCurrentTime().after(startTime) && message.getCurrentTime().before(endTime)) { 74 | messages.add(message); 75 | } 76 | } 77 | 78 | return messages; 79 | } 80 | 81 | private static JSONArray getResponseJson(String api) { 82 | String responseBody = HttpRequest.post(api) 83 | .contentType(Headers.APPLICATION_JSON) 84 | .cookie(cookie) 85 | .body("{\"pageSize\": 10,\"pageNumber\": 1}") 86 | .execute().body(); 87 | JSONArray unsigned = JSONUtil.parseObj(responseBody).getJSONObject(DATAS).getJSONArray(UNSIGNED_TASKS); 88 | JSONArray leave = JSONUtil.parseObj(responseBody).getJSONObject(DATAS).getJSONArray(LEAVE_TASKS); 89 | unsigned.addAll(leave); 90 | return unsigned; 91 | } 92 | 93 | private String generateTime(String str) { 94 | return DateUtil.parse(DateUtil.format(new Date(), "yyyy-MM-dd") + " " + str).toString(); 95 | } 96 | 97 | /** 98 | * 签到或查寝 99 | * 100 | * @param message 消息体 101 | * @param cpExtension 加密字符串 102 | * @return 是否成功 103 | */ 104 | public boolean submitMessage(MessageBox message, String cpExtension) { 105 | String body; 106 | // 查寝 107 | if (message.getType() == 1) { 108 | log.info("===查寝==="); 109 | body = CpDaily.SUBMIT_INFO.replace("siWid", message.getSignInstanceWid()) 110 | .replace("r1", longitude) 111 | .replace("r2", latitude) 112 | .replace("local", position); 113 | } else { 114 | log.info("===签到==="); 115 | body = CpDaily.SIGN_INFO.replace("siWid", message.getSignInstanceWid()) 116 | .replace("itemId", getExtraFieldItemWid(message.getSignInstanceWid(), message.getSignWid())) 117 | .replace("r1", longitude) 118 | .replace("r2", latitude) 119 | .replace("local", position); 120 | } 121 | return submitForm( 122 | body, 123 | cpExtension, 124 | message.getType() == 1 ? CpDailyApi.ATTENDANCE_SUBMIT_FORM : CpDailyApi.SIGN_SUBMIT_FORM 125 | ); 126 | } 127 | 128 | /** 129 | * 提交消息 130 | * 131 | * @param body 签到或查寝的请求Body 132 | * @param cpExtension 加密字符串 133 | * @param api 签到或查寝的提交Api 134 | * @return 是否成功 135 | */ 136 | public boolean submitForm(String body, String cpExtension, String api) { 137 | HttpResponse response = HttpRequest.post(api) 138 | .contentType(Headers.APPLICATION_JSON) 139 | .cookie(cookie) 140 | .header("tenantId", CpDaily.TENANT_ID) 141 | .header("Cpdaily-Extension", cpExtension) 142 | .body(body) 143 | .execute(); 144 | 145 | String responseBody = response.body(); 146 | log.info("签到后的返回信息:[{}]", responseBody); 147 | 148 | boolean flag = false; 149 | try { 150 | JSONObject resJson = JSONUtil.parseObj(responseBody); 151 | if (SUCCESS.equals(resJson.get(MESSAGE))) { 152 | flag = true; 153 | } 154 | } catch (Exception e) { 155 | log.error(e.toString()); 156 | } 157 | 158 | return flag; 159 | } 160 | 161 | public String getExtraFieldItemWid(String signInstanceWid, String signWid) { 162 | String responseBody = HttpRequest.post(CpDailyApi.SIGN_GET_FORM) 163 | .contentType(Headers.APPLICATION_JSON) 164 | .cookie(cookie) 165 | .body("{\"signInstanceWid\":\"" + signInstanceWid + "\",\"signWid\":\"" + signWid + "\"}") 166 | .execute().body(); 167 | 168 | String temp = responseBody.substring(responseBody.lastIndexOf("extraFieldItems")); 169 | String temp2 = temp.substring(temp.indexOf("wid")); 170 | return temp2.substring(5, temp2.indexOf(",")); 171 | } 172 | } --------------------------------------------------------------------------------