fork 自 juzheng/sdu-course-bot
init:框架
此提交包含在:
79
src/actions.ts
一般檔案
79
src/actions.ts
一般檔案
@@ -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
一般檔案
25
src/config.ts
一般檔案
@@ -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
一般檔案
66
src/includes.ts
一般檔案
@@ -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
一般檔案
35
src/index.ts
一般檔案
@@ -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
一般檔案
79
src/poll.ts
一般檔案
@@ -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
一般檔案
15
src/test.ts
一般檔案
@@ -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
一般檔案
91
src/types.ts
一般檔案
@@ -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;
|
||||
}
|
新增問題並參考
封鎖使用者