forked from juzheng/sdu-course-bot
		
	init:框架
This commit is contained in:
		
							
								
								
									
										79
									
								
								src/actions.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/actions.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | ||||
| import {ICourse} from "./types" | ||||
| import {acquireCourse, exitCourse, findJsonCourse, getTimeNow, logAndNotifyUser, sleep} from "./includes" | ||||
| import {JsonCourseList} from "./poll" | ||||
|  | ||||
| export async function monitProcess(course: ICourse) { | ||||
|     let lastSyrs = -1 | ||||
|     while (true) { | ||||
|         let single = findJsonCourse(course) | ||||
|         if (single == undefined) { | ||||
|             console.log('[monit]无法找到课程 ' + course.kch + ' on channel ' + course.channel) | ||||
|         } else { | ||||
|             if (lastSyrs == -1) { | ||||
|                 console.log('[monit]开始监控 ' + single.kcmc + ' ,当前剩余人数:' + single.syrs) | ||||
|                 lastSyrs = parseInt(single.syrs) | ||||
|             } else { | ||||
|                 if (lastSyrs != parseInt(single.syrs)) { | ||||
|                     logAndNotifyUser('[monit]' + single.kcmc + ' 新剩余人数:' + single.syrs + ' at ' + getTimeNow()) | ||||
|                     lastSyrs = parseInt(single.syrs) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         await sleep(50) | ||||
|     } | ||||
| } | ||||
|  | ||||
| export async function acquireProcess(course: ICourse) { | ||||
|     while (true) { | ||||
|         let single = findJsonCourse(course) | ||||
|         if (single == undefined) { | ||||
|             console.log('[acquire]无法找到课程 ' + course.kch + ' on channel ' + course.channel) | ||||
|         } else { | ||||
|             if (parseInt(single.syrs) > 0) { | ||||
|                 logAndNotifyUser('[acquire]发现 ' + single.kcmc + ' 剩余人数为' + single.syrs + ',进行抢课 at ' + getTimeNow()) | ||||
|                 if (await acquireCourse(single, course.channel)) { | ||||
|                     logAndNotifyUser('[acquire]抢课成功 ' + single.kcmc + ' at ' + getTimeNow()) | ||||
|                     return | ||||
|                 } else { | ||||
|                     logAndNotifyUser('[acquire]抢课失败 ' + single.kcmc + ' at ' + getTimeNow()) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         await sleep(50) | ||||
|     } | ||||
| } | ||||
|  | ||||
| export async function replaceProcess(courseList: ICourse[], exit: ICourse) { | ||||
|     while (true) { | ||||
|         let exitJsonCourse = findJsonCourse(exit) | ||||
|         if (exitJsonCourse == undefined) { | ||||
|             console.log('[replace]找不到需要需要退课的课程 ' + exit.kch + ' on channel ' + exit.channel) | ||||
|         } else { | ||||
|             for (let course of courseList) { | ||||
|                 let single = findJsonCourse(course) | ||||
|                 if (single == undefined) { | ||||
|                     console.log('[replace]无法找到课程 ' + course.kch + ' on channel ' + course.channel) | ||||
|                     continue | ||||
|                 } | ||||
|                 if (parseInt(single.syrs) > 0) { | ||||
|                     logAndNotifyUser('[replace]发现 ' + single.kcmc + ' 剩余人数为' + single.syrs + ',进行换课 at ' + getTimeNow()) | ||||
|                     if (!(await exitCourse(exitJsonCourse, exit.channel))) { | ||||
|                         logAndNotifyUser('[replace]退课 ' + exitJsonCourse.kcmc + ' 失败 at ' + getTimeNow()) | ||||
|                     } | ||||
|                     if (await acquireCourse(single, course.channel)) { | ||||
|                         logAndNotifyUser('[replace]换课 ' + single.kcmc + ' 成功 at ' + getTimeNow()) | ||||
|                         return | ||||
|                     } else { | ||||
|                         logAndNotifyUser('[replace]选课 ' + single.kcmc + ' 失败,进行回滚 at ' + getTimeNow()) | ||||
|                         if (await acquireCourse(exitJsonCourse, exit.channel)) { | ||||
|                             logAndNotifyUser('[replace]回滚 ' + single.kcmc + ' 成功 at ' + getTimeNow()) | ||||
|                         } else { | ||||
|                             logAndNotifyUser('[replace]回滚 ' + single.kcmc + ' 失败 at ' + getTimeNow()) | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         await sleep(50) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										25
									
								
								src/config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/config.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| import yaml from "yaml" | ||||
| import * as fs from "fs" | ||||
| import {IAppConfig, TChannel} from "./types" | ||||
|  | ||||
| let appConfig: IAppConfig = yaml.parse(fs.readFileSync('config.yaml', 'utf-8')) | ||||
| let channels: TChannel[] = [] | ||||
| for (let course of appConfig.monit?.list || []) { | ||||
|     if (!channels.includes(course.channel)) { | ||||
|         channels.push(course.channel) | ||||
|     } | ||||
| } | ||||
| for (let course of appConfig.acquire?.list || []) { | ||||
|     if (!channels.includes(course.channel)) { | ||||
|         channels.push(course.channel) | ||||
|     } | ||||
| } | ||||
| if (appConfig.replace) { | ||||
|     for (let course of appConfig.replace.list) { | ||||
|         if (!channels.includes(course.channel)) { | ||||
|             channels.push(course.channel) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| appConfig.channels = channels | ||||
| export {appConfig} | ||||
							
								
								
									
										66
									
								
								src/includes.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/includes.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| import axios from 'axios' | ||||
| import {appConfig} from "./config" | ||||
| import * as qs from "qs" | ||||
| import dayjs from "dayjs" | ||||
| import {ICourse, IJsonCourse, TChannel} from "./types" | ||||
| import {JsonCourseList} from "./poll" | ||||
| import axiosRetry from "axios-retry" | ||||
|  | ||||
| let sduAxios = axios.create({ | ||||
|     headers: { | ||||
|         'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', | ||||
|         'Cookie': appConfig.cookie | ||||
|     }, | ||||
|     baseURL: 'https://bkzhjx.wh.sdu.edu.cn/', | ||||
|     timeout: 5, | ||||
|     // maxRedirects: 0 | ||||
| }) | ||||
|  | ||||
| axiosRetry(sduAxios, { | ||||
|     retries: 2, | ||||
|     shouldResetTimeout: true, | ||||
|     retryCondition: (_error) => true, | ||||
| }) | ||||
|  | ||||
| export async function logAndNotifyUser(text: string) { | ||||
|     console.log(text) | ||||
|     await axios.post('https://qmsg.zendee.cn/send/' + appConfig.qmsgKey, qs.stringify({ | ||||
|         msg: text | ||||
|     }), { | ||||
|         headers: { | ||||
|             'Content-Type': 'application/x-www-form-urlencoded' | ||||
|         } | ||||
|     }) | ||||
| } | ||||
|  | ||||
| export function sleep(ms: number) { | ||||
|     return new Promise(resolve => setTimeout(resolve, ms)) | ||||
| } | ||||
|  | ||||
| export function getTimeNow() { | ||||
|     return dayjs().format('hh:mm:ss A') | ||||
| } | ||||
|  | ||||
| export function findJsonCourse(course: ICourse): IJsonCourse | undefined { | ||||
|     return JsonCourseList[course.channel].find(single => single.kch == course.kch && parseInt(single.kxh) == course.kxh) | ||||
| } | ||||
|  | ||||
| export async function acquireCourse(course: IJsonCourse, channel: TChannel): Promise<boolean> { | ||||
|     if (channel == 'bx' || channel == 'xx') { | ||||
|  | ||||
|     } else { | ||||
|  | ||||
|     } | ||||
|     return true | ||||
| } | ||||
|  | ||||
| export async function exitCourse(course: IJsonCourse, channel: TChannel): Promise<boolean> { | ||||
|     if (channel == 'bx' || channel == 'xx') { | ||||
|  | ||||
|     } else { | ||||
|  | ||||
|     } | ||||
|     return true | ||||
| } | ||||
|  | ||||
| export {sduAxios} | ||||
							
								
								
									
										35
									
								
								src/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| import {JsonCourseList, poll} from "./poll" | ||||
| import {appConfig} from "./config" | ||||
| import {acquireProcess, monitProcess, replaceProcess} from "./actions" | ||||
| import {sleep} from "./includes" | ||||
|  | ||||
| async function start() { | ||||
|     console.log('开始启动轮询进程') | ||||
|     poll() | ||||
|     while (true) { | ||||
|         let pollInitComplete = true | ||||
|         for (let channel of appConfig.channels) { | ||||
|             if (JsonCourseList[channel].length == 0) { | ||||
|                 pollInitComplete = false | ||||
|             } | ||||
|         } | ||||
|         if (pollInitComplete) { | ||||
|             break | ||||
|         } | ||||
|         await sleep(50) | ||||
|     } | ||||
|     console.log('轮询进程启动完毕') | ||||
|     console.log('开始启动用户进程') | ||||
|     for (let course of appConfig.monit?.list || []) { | ||||
|         monitProcess(course) | ||||
|     } | ||||
|     for (let course of appConfig.acquire?.list || []) { | ||||
|         acquireProcess(course) | ||||
|     } | ||||
|     if (appConfig.replace) { | ||||
|         replaceProcess(appConfig.replace.list, appConfig.replace.exit) | ||||
|     } | ||||
|     console.log('用户进程启动完毕') | ||||
| } | ||||
|  | ||||
| start() | ||||
							
								
								
									
										79
									
								
								src/poll.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/poll.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | ||||
| import {IJsonCourse} from "./types" | ||||
| import {getTimeNow, sduAxios, sleep} from "./includes" | ||||
| import {appConfig} from "./config" | ||||
| import * as qs from "qs" | ||||
|  | ||||
| let reqData = [ | ||||
|     {"name": "sEcho", "value": "1"}, | ||||
|     {"name": "iColumns", "value": "2000"},// 1 | ||||
|     {"name": "sColumns", "value": ""}, | ||||
|     {"name": "iDisplayStart", "value": "0"}, | ||||
|     {"name": "iDisplayLength", "value": "2000"},// 1 | ||||
|     {"name": "mDataProp_0", "value": "kch"}, | ||||
|     {"name": "mDataProp_1", "value": "kcmc"}, | ||||
|     {"name": "mDataProp_2", "value": "kxhnew"}, | ||||
|     {"name": "mDataProp_3", "value": "jkfs"}, | ||||
|     {"name": "mDataProp_4", "value": "xmmc"}, | ||||
|     {"name": "mDataProp_5", "value": "xf"}, | ||||
|     {"name": "mDataProp_6", "value": "skls"}, | ||||
|     {"name": "mDataProp_7", "value": "sksj"}, | ||||
|     {"name": "mDataProp_8", "value": "skdd"}, | ||||
|     {"name": "mDataProp_9", "value": "xqmc"}, | ||||
|     {"name": "mDataProp_10", "value": "xkrs"}, | ||||
|     {"name": "mDataProp_11", "value": "syrs"}, | ||||
|     {"name": "mDataProp_12", "value": "ctsm"}, | ||||
|     {"name": "mDataProp_13", "value": "szkcflmc"}, | ||||
|     {"name": "mDataProp_14", "value": "czOper"} | ||||
| ] | ||||
| export const JsonCourseList = <{ bx: IJsonCourse[], xx: IJsonCourse[], rx: IJsonCourse[] }>{ | ||||
|     bx: [], | ||||
|     xx: [], | ||||
|     rx: [] | ||||
| } | ||||
|  | ||||
| async function updateBx() { | ||||
|     try { | ||||
|         let resp = await sduAxios.post('/jsxsd/xsxkkc/xsxkBxxk?1=1&kcxx=&skls=&skfs=', qs.stringify(reqData)) | ||||
|         JsonCourseList.bx.length = 0 | ||||
|         JsonCourseList.bx.push(...resp.data.aaData) | ||||
|         console.log('更新必修JsonList成功') | ||||
|     } catch (e) { | ||||
|         console.log('获取必修JsonList失败 at ' + getTimeNow()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| async function updateXx() { | ||||
|     try { | ||||
|         let resp = await sduAxios.post('/jsxsd/xsxkkc/xsxkXxxk?1=1&kcxx=&skls=&skfs=', qs.stringify(reqData)) | ||||
|         JsonCourseList.xx.length = 0 | ||||
|         JsonCourseList.xx.push(...resp.data.aaData) | ||||
|     } catch (e) { | ||||
|         console.log('获取限选JsonList失败 at ' + getTimeNow()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| async function updateRx() { | ||||
|     try { | ||||
|         let resp = await sduAxios.post('/jsxsd/xsxkkc/xsxkGgxxkxk?kcxx=&skls=&skxq=&skjc=&sfym=false&sfct=false&szjylb=&sfxx=false&skfs=', qs.stringify(reqData)) | ||||
|         JsonCourseList.rx.length = 0 | ||||
|         JsonCourseList.rx.push(...resp.data.aaData) | ||||
|     } catch (e) { | ||||
|         console.log('获取任选JsonList失败 at ' + getTimeNow()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| export async function poll() { | ||||
|     if (appConfig.channels.includes('bx')) { | ||||
|         await updateBx() | ||||
|         await sleep(appConfig.interval) | ||||
|     } | ||||
|     if (appConfig.channels.includes('xx')) { | ||||
|         await updateXx() | ||||
|         await sleep(appConfig.interval) | ||||
|     } | ||||
|     if (appConfig.channels.includes('rx')) { | ||||
|         await updateRx() | ||||
|         await sleep(appConfig.interval) | ||||
|     } | ||||
|     setImmediate(poll) | ||||
| } | ||||
							
								
								
									
										15
									
								
								src/test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/test.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| import {appConfig} from "./config" | ||||
|  | ||||
| console.log('channels: ' + appConfig.channels) | ||||
| for (let course of appConfig.monit?.list || []) { | ||||
|     console.log('添加监控课程 ' + course.kch + ' on channel ' + course.channel) | ||||
| } | ||||
| for (let course of appConfig.acquire?.list || []) { | ||||
|     console.log('添加抢课课程 ' + course.kch + ' on channel ' + course.channel) | ||||
| } | ||||
| if (appConfig.replace) { | ||||
|     console.log('添加退课课程 ' + appConfig.replace.exit.kch + ' on channel ' + appConfig.replace.exit.channel) | ||||
|     for (let course of appConfig.replace.list) { | ||||
|         console.log('添加换课课程 ' + course.kch + ' on channel ' + course.channel) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										91
									
								
								src/types.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								src/types.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,91 @@ | ||||
| export type TChannel = 'bx' | 'xx' | 'rx' | ||||
|  | ||||
| export interface ICourse { | ||||
|     kch: string, | ||||
|     kxh: number, | ||||
|     channel: TChannel | ||||
| } | ||||
|  | ||||
| export interface IAppConfig { | ||||
|     cookie: string, | ||||
|     qmsgKey: string, | ||||
|     interval: number, | ||||
|     channels: TChannel[], | ||||
|     monit?: { | ||||
|         list: ICourse[] | ||||
|     }, | ||||
|     acquire?: { | ||||
|         list: ICourse[] | ||||
|     }, | ||||
|     replace?: { | ||||
|         list: ICourse[], | ||||
|         exit: ICourse | ||||
|     } | ||||
| } | ||||
|  | ||||
| export interface IJsonCourse { | ||||
|     parentjx0404id: null; | ||||
|     mfkc: string; | ||||
|     ksfs: string; | ||||
|     jx02kcmkid: null; | ||||
|     ktmc: string; | ||||
|     tzdlb: string; | ||||
|     kcsx: string; | ||||
|     skfs: string; | ||||
|     kch: string; | ||||
|     syrs: string; | ||||
|     kxh: string; | ||||
|     sksj: string; | ||||
|     szkcfl: string; | ||||
|     kcjj: null; | ||||
|     skfsmc: string; | ||||
|     wlpt: null; | ||||
|     kcxzmc: string; | ||||
|     kexuhao: null; | ||||
|     xbyq: null; | ||||
|     sftk: null; | ||||
|     kkdw: string; | ||||
|     kcxzm: string; | ||||
|     szkcflmc: string; | ||||
|     xnxq01id: string; | ||||
|     dwmc: string; | ||||
|     kxhnew: string; | ||||
|     pkrs: number; | ||||
|     xkrs: number; | ||||
|     fzmc: null; | ||||
|     cfbs: null; | ||||
|     kcmc: string; | ||||
|     xyxsnj: null; | ||||
|     kkapList: KkapList[]; | ||||
|     dyrsbl: null; | ||||
|     isnetworkcourse: string; | ||||
|     sklsid: string; | ||||
|     skls: string; | ||||
|     xqid: string; | ||||
|     sfkfxk: string; | ||||
|     skdd: string; | ||||
|     xf: number; | ||||
|     skdws: null; | ||||
|     txsfkxq: string; | ||||
|     xmmc: null; | ||||
|     zxs: number; | ||||
|     jx0404id: string; | ||||
|     xqmc: string; | ||||
|     jx02id: string; | ||||
|     xbyqmc: null; | ||||
|     jkfs: null; | ||||
| } | ||||
|  | ||||
| export interface KkapList { | ||||
|     jssj: string; | ||||
|     jzwmc: string; | ||||
|     jgxm: string; | ||||
|     skjcmc: string; | ||||
|     skzcList: string[]; | ||||
|     xq: string; | ||||
|     kbjcmsid: string; | ||||
|     kkzc: string; | ||||
|     kssj: string; | ||||
|     jsmc: string; | ||||
|     kkdlb: string; | ||||
| } | ||||
		Reference in New Issue
	
	Block a user