Skip to content

Commit eff8260

Browse files
authored
Merge pull request #51 from ludoux/master
西北工业大学:新教务适配;Parser.kt 更新了上游的可被重写方法
2 parents 9399a8c + 224c767 commit eff8260

File tree

6 files changed

+342
-3
lines changed

6 files changed

+342
-3
lines changed

src/main/java/LoginException.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package main.java.exception
2+
3+
class NetworkErrorException(message: String) : Exception(message)
4+
5+
class ServerErrorException(message: String) : Exception(message)
6+
7+
class UserNameErrorException(message: String) : Exception(message)
8+
9+
class PasswordErrorException(message: String) : Exception(message)
10+
11+
class CheckCodeErrorException(message: String) : Exception(message)
12+
13+
class QueuingUpException(message: String) : Exception(message)
14+
15+
class GetTermDataErrorException(message: String) : Exception(message)
16+
17+
class EmptyException(message: String) : Exception(message)

src/main/java/parser/NWPUParser.kt

Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
package main.java.parser
2+
3+
import Common
4+
5+
import bean.Course
6+
import com.google.gson.JsonParser
7+
import main.java.exception.EmptyException
8+
import main.java.exception.GetTermDataErrorException
9+
import main.java.exception.NetworkErrorException
10+
import main.java.exception.PasswordErrorException
11+
import org.jsoup.Connection
12+
import org.jsoup.Jsoup
13+
import parser.Parser
14+
15+
/*
16+
* // 接口地址
17+
18+
/* harmony default export */ __webpack_exports__[\"default\"] = ({
19+
// 获取所有学期
20+
getAllSemester: function getAllSemester() {
21+
return axios__WEBPACK_IMPORTED_MODULE_1___default.a.get('/api/semester/list');
22+
},
23+
// 获取所有周课表
24+
getAllWeek: function getAllWeek(id) {
25+
return axios__WEBPACK_IMPORTED_MODULE_1___default.a.get(\"/api/semester/weekList/\".concat(id));
26+
},
27+
// 获取所有周课表
28+
getSemesterAndWeek: function getSemesterAndWeek(id) {
29+
return axios__WEBPACK_IMPORTED_MODULE_1___default.a.get(\"/api/semester/semesterAndWeek/\".concat(id));
30+
},
31+
// 根据学期+周id 获取课程表数据
32+
getCourseByWeekId: function getCourseByWeekId(semesterId, weekId) {
33+
return axios__WEBPACK_IMPORTED_MODULE_1___default.a.get(\"/api/courseTable/\".concat(semesterId, \"/\").concat(weekId));
34+
},
35+
// 获取当前周的数据
36+
getCurrentWeek: function getCurrentWeek() {
37+
return axios__WEBPACK_IMPORTED_MODULE_1___default.a.get('/api/semester/currentWeek');
38+
},
39+
// 获取学生名单列表
40+
getStudentList: function getStudentList(id) {
41+
return axios__WEBPACK_IMPORTED_MODULE_1___default.a.get(\"/api/stdList/list/\".concat(id));
42+
},
43+
// 课程安排表
44+
getSchedule: function getSchedule(semesterId, courseId) {
45+
return axios__WEBPACK_IMPORTED_MODULE_1___default.a.get(\"/api/courseList/schedule/\".concat(semesterId, \"/\").concat(courseId));
46+
},
47+
// 获取学期全部课程
48+
getAllCourseBySemesterId: function getAllCourseBySemesterId(semesterId) {
49+
return axios__WEBPACK_IMPORTED_MODULE_1___default.a.get(\"/api/courseList/list/\".concat(semesterId));
50+
},
51+
// 获取课程选项数据
52+
getCourseOptions: function getCourseOptions(semesterId) {
53+
return axios__WEBPACK_IMPORTED_MODULE_1___default.a.get(\"/api/courseList/names/\".concat(semesterId));
54+
},
55+
// 根据课程名获取课程列表
56+
getCourseByName: function getCourseByName(semesterId, keyword) {
57+
return axios__WEBPACK_IMPORTED_MODULE_1___default.a.get(\"/api/courseList/list/\".concat(semesterId, \"/\").concat(keyword));
58+
},
59+
//获取用户头像
60+
getAvatar: function getAvatar(stdCode) {
61+
return axios__WEBPACK_IMPORTED_MODULE_1___default.a.get(\"/api/user/avatar/\".concat(stdCode));
62+
}
63+
});
64+
* */
65+
/*
66+
维护小提示:
67+
接口是从 https://students-schedule.nwpu.edu.cn/ui/#/courseTable 来的
68+
大致为 get 拿cookie -> post 发登录凭证,回复url有token -> token 写 header 里面取所有学期列表 -> token 写 header 根据匹配的学期拿课表
69+
70+
关于时间表:是在作者私仓 com/suda/yzune/wakeupschedule/schedule_import/ImportViewModel.kt 里addNwpuTimeTables函数写的。
71+
不管导入的是哪个校区,如上函数都会新建所有的时间表(存在同名不会覆盖)
72+
假如时间表变更,请联系作者更改里面的函数。注意假如有同名时间表将不会覆盖,所以建议新建以“西工大长安v2”类似来命名,同时这里的_timeTableName也相应更改。
73+
假如太仓校区确认了,同上逻辑,建议新建以“西工大太仓”类似来命名,这里的_timeTableName也相应更改。
74+
75+
关于学期开始日期等:_startDate等变量以及 override 的相关 get 函数,wakeup会读取自动正确存储。这里是因为courseadapter没做相关功能,所以导出的wakeup数据时间等不对,但实际上app是对的。
76+
77+
假如你要改相关UI,在作者私仓 com/suda/yzune/wakeupschedule/schedule_import/LoginWebFragment.kt 里,请联系开发者。
78+
*/
79+
80+
/*
81+
* Last_Update: 2022-4-9
82+
* Creator: @ludoux ([email protected])
83+
* Maintainer:
84+
*/
85+
class NWPUParser(
86+
private val xh: String,
87+
private val pwd: String,
88+
private val semesterYear: String,
89+
private val semesterTerm: Int
90+
) : Parser("") {
91+
private val headers: Map<String, String>? = mapOf(
92+
"User-Agent" to "Mozilla/5.0 (X11; Linux x86_64; rv:99.0) Gecko/20100101 Firefox/99.0",
93+
"Accept-Language" to "zh-CN"
94+
)
95+
private val timeout = 5000
96+
97+
private var _timeTableName = "西工大长安"
98+
private var _tableName = "西工大 2020-2021 春学期"
99+
private var _nodes = 13
100+
private var _startDate = "1970-01-01"
101+
private var _maxWeek = 22
102+
103+
//相当于 login_school/NWPU/nwpu 里的 getWebApi。所以这两个函数不建议融合在一起
104+
private fun webFunc(): String {
105+
//针对 vue 应用网页 https://students-schedule.nwpu.edu.cn/ui/
106+
var rt = ""
107+
val cookies: Map<String, String>?
108+
var token: String? = ""
109+
if (false) {//强制SSO//原来是[port==1] 教务系统直接登录
110+
/*cookies = withContext(Dispatchers.IO) {
111+
Jsoup.connect("http://us.nwpu.edu.cn/eams/login.action")
112+
.headers(headers)//第一步获取cookies
113+
.timeout(timeout).method(Connection.Method.GET).execute().cookies()
114+
}
115+
116+
responseBody = withContext(Dispatchers.IO) {
117+
Jsoup.connect("http://us.nwpu.edu.cn/eams/login.action").headers(headers)
118+
.cookies(cookies)//第二步骤模拟登录
119+
.data("username", xh).data("password", pwd).data("encodedPassword", "")
120+
.data("session_locale", "zh_CN")
121+
.timeout(timeout).method(Connection.Method.POST).execute().body()
122+
}
123+
if (responseBody.contains("欢迎使用西北工业大学教务系统。")) {
124+
//ok
125+
} else if (responseBody.contains("已经被锁定")) {
126+
throw NetworkErrorException("账号被锁定,请稍等后重试")
127+
} else if (responseBody.contains("密码错误")) {
128+
throw PasswordErrorException("密码错误哦")
129+
} else if (responseBody.contains("账户不存在")) {
130+
throw UserNameErrorException("登录失败,账户不存在。")
131+
} else if (responseBody.contains("验证码不正确")) {
132+
throw NetworkErrorException("登录失败,失败尝试过多,请尝试更换网络环境。")
133+
}*/
134+
} else {//走统一登录
135+
var response =
136+
Jsoup.connect("https://uis.nwpu.edu.cn/cas/login?service=https://students-schedule.nwpu.edu.cn/login/cas?redirect_uri=https://students-schedule.nwpu.edu.cn/ui/")
137+
.headers(headers)//第一步获取cookies
138+
.timeout(timeout).method(Connection.Method.GET).execute()
139+
140+
cookies = response.cookies()
141+
//Copy from SUSTechParser.kt@GGAutomaton,thanks!
142+
val executionValue =
143+
response.parse().getElementsByAttributeValue("name", "execution")[0].attributes().get("value")
144+
response =
145+
Jsoup.connect("https://uis.nwpu.edu.cn/cas/login?service=https://students-schedule.nwpu.edu.cn/login/cas?redirect_uri=https://students-schedule.nwpu.edu.cn/ui/")
146+
.headers(headers).cookies(cookies)//第二步骤模拟登录
147+
.data("username", xh).data("password", pwd).data("currentMenu", "1")
148+
.data("execution", executionValue).data("_eventId", "submit").data("geolocation", "")
149+
.data("submit", "稍等片刻……")
150+
.timeout(timeout).method(Connection.Method.POST).execute()
151+
152+
if (response.statusCode() != 200 && response.statusCode() != 302) {
153+
throw PasswordErrorException("登录失败。因帐号或密码错误,或失败过多暂时被锁定,请核对或稍后更换网络后重试。[${response.statusCode()}]")
154+
}
155+
156+
token = Regex("token=.+$").find(response.url().toString())?.value
157+
if (token != null) {
158+
token = token.replace("token=", "")
159+
} else {
160+
throw NetworkErrorException("获取 token 失败,这不应该发生。若持续出错请联系维护者。[${response.url()}]")
161+
}
162+
}
163+
164+
//获得学期id
165+
var response =
166+
Jsoup.connect("https://students-schedule.nwpu.edu.cn/api/semester/list")
167+
.header("X-Id-Token", token).header("X-Device-Info", "PC").header("X-Requested-With", "XMLHttpRequest")
168+
.header("X-Terminal-Info", "PC").header("X-User-Type", "student")
169+
.ignoreContentType(true)//Jsoup 不支持返回的content-type为 application/json,所以强制忽略
170+
.timeout(5000).method(Connection.Method.GET).execute()
171+
172+
rt = response.body()
173+
174+
//本来不应该解析json的,但是考虑到要获取课表要先知道学期id,所以还是解析了。
175+
var json = JsonParser.parseString(response.body())
176+
var semesterId = ""
177+
if (json.asJsonObject.get("msg").asString == "成功") {
178+
val semestersName = "秋春夏"
179+
json.asJsonObject.get("data").asJsonArray.forEach {
180+
var curSemesterName = it.asJsonObject.get("name").toString()
181+
//以防后面学校会变更,所以这里不精确匹配
182+
if (curSemesterName.contains("$semesterYear-") && curSemesterName.contains(semestersName[semesterTerm])) {
183+
semesterId = it.asJsonObject.get("id").asString
184+
}
185+
}
186+
} else {
187+
throw GetTermDataErrorException("没有在教务网站上查找到相关学期信息,请重新选择。若持续出错请联系维护者。")
188+
}
189+
190+
response =
191+
Jsoup.connect("https://students-schedule.nwpu.edu.cn/api/courseList/list/$semesterId")
192+
.header("X-Id-Token", token).header("X-Device-Info", "PC").header("X-Requested-With", "XMLHttpRequest")
193+
.header("X-Terminal-Info", "PC").header("X-User-Type", "student")
194+
.ignoreContentType(true)//Jsoup 不支持返回的content-type为 application/json,所以强制忽略
195+
.timeout(5000).method(Connection.Method.GET).execute()
196+
197+
198+
json = JsonParser.parseString(response.body())
199+
println(json.asJsonObject.get("msg").asString)
200+
if (json.asJsonObject.get("msg").asString != "成功") {
201+
throw EmptyException("查询课表信息失败,这不应该发生。若持续出错请联系维护者。[${json.asJsonObject.get("msg").asString}]")
202+
}
203+
rt = rt + "<split>" + response.body() + "<split>" + semesterId
204+
//所以rt其实是 回应1<split>回应2<split>学期id
205+
return rt
206+
}
207+
208+
override fun generateCourseList(): List<Course> {
209+
val ori = webFunc().split("<split>")
210+
//回应1<split>回应2<split>学期id
211+
var json = JsonParser.parseString(ori[0])
212+
val semesterId = ori[2]
213+
214+
json.asJsonObject.get("data").asJsonArray.forEach {
215+
if (it.asJsonObject.get("id").asString == semesterId) {
216+
_tableName = "西工大 " + it.asJsonObject.get("code").asString // 西工大 2019-2020-秋
217+
_maxWeek = it.asJsonObject.get("weeks").asJsonArray.count()
218+
_startDate = it.asJsonObject.get("startDay").asString
219+
}
220+
}
221+
222+
json = JsonParser.parseString(ori[1])
223+
224+
val courseList = ArrayList<Course>()
225+
json.asJsonObject.get("data").asJsonArray.forEach { course ->
226+
227+
228+
//如果为 false,则为网课一类,不会在日历上显示,skip
229+
if (!course.asJsonObject.get("hasSchedule").asBoolean)
230+
return@forEach//相当于continue
231+
232+
course.asJsonObject.get("weekdayUnits").asJsonArray.forEach { unit ->
233+
234+
val weekList = arrayListOf<Int>()
235+
//weeks: [1,2,3,5]
236+
unit.asJsonObject.get("weeks").asJsonArray.forEach { x ->
237+
weekList.add(x.asInt)
238+
}
239+
240+
Common.weekIntList2WeekBeanList(weekList).forEach {
241+
courseList.add(Course(
242+
name = course.asJsonObject.get("name").asString,
243+
day = unit.asJsonObject.get("weekday").asInt,
244+
room = unit.asJsonObject.get("roomString").asString.replace(regex = Regex("(长安|友谊|太仓)校区 "), replacement = ""),
245+
teacher = unit.asJsonObject.get("teacherString").asString.toString().trim(),
246+
startNode = unit.asJsonObject.get("startUnit").asInt,
247+
endNode = unit.asJsonObject.get("endUnit").asInt,
248+
startWeek = it.start,
249+
endWeek = it.end,
250+
type = it.type,
251+
credit = course.asJsonObject.get("credit").asFloat,
252+
note = course.asJsonObject.get("code").asString
253+
))
254+
255+
when {
256+
unit.asJsonObject.get("roomString").asString.contains("长安校区") -> {
257+
_nodes = 13
258+
_timeTableName = "西工大长安"
259+
}
260+
unit.asJsonObject.get("roomString").asString.contains("友谊校区") -> {
261+
_nodes = 12
262+
val month = java.util.Calendar.getInstance().get(java.util.Calendar.MONTH)
263+
_timeTableName = if (month >= 10 || month <= 4) {
264+
"西工大友谊冬(10.1-4.30)"
265+
} else {
266+
"西工大友谊夏(5.1-9.30)"
267+
}
268+
}
269+
unit.asJsonObject.get("roomString").asString.contains("太仓校区") -> {
270+
_nodes = 13
271+
_timeTableName = "西工大太仓(未实现)"
272+
}
273+
}
274+
}
275+
}
276+
}
277+
println("课表名称:$_tableName\n启用时间表:$_timeTableName\n每天节数:$_nodes\n学期开始日期(务必周一):$_startDate\n学期周数:$_maxWeek")
278+
return courseList
279+
}
280+
281+
/*override fun generateTimeTable(): TimeTable {
282+
return TimeTable(name = _timeTableName, timeList = listOf())
283+
}*/
284+
override fun getTableName(): String {
285+
return _tableName
286+
}
287+
288+
override fun getNodes(): Int {
289+
return _nodes
290+
}
291+
292+
override fun getStartDate(): String? {
293+
return if (_startDate == "1970-01-01") {
294+
null
295+
} else {
296+
_startDate
297+
}
298+
}
299+
300+
override fun getMaxWeek(): Int? {
301+
return _maxWeek
302+
}
303+
304+
}

src/main/java/parser/Parser.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ abstract class Parser(val source: String) {
1717
// TimeTable中的name属性将起到标识作用,如果在数据库中发现同名时间表,则不再覆盖写入
1818
open fun generateTimeTable(): TimeTable? = null
1919

20+
open fun getTableName(): String? = null
21+
22+
open fun getNodes(): Int? = null
23+
24+
open fun getStartDate(): String? = null
25+
26+
open fun getMaxWeek(): Int? = null
27+
2028
private fun convertCourse() {
2129
generateCourseList().forEach { course ->
2230
var id = Common.findExistedCourseId(_baseList, course.name)
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package parser.qz
22

33
class QzCrazyParser(source: String) : QzParser(source) {
4-
override val tableName: String
4+
override val webTableName: String
55
get() = "kbcontent1"
66
}

src/main/java/parser/qz/QzParser.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ open class QzParser(source: String) : Parser(source) {
88

99
private val sundayFirstDayMap = arrayOf(0, 7, 1, 2, 3, 4, 5, 6)
1010
private var sundayFirst = false
11-
open val tableName = "kbcontent"
11+
open val webTableName = "kbcontent"
1212

1313
open fun parseCourseName(infoStr: String): String {
1414
return Jsoup.parse(infoStr.substringBefore("<font").trim()).text()
@@ -84,7 +84,7 @@ open class QzParser(source: String) : Parser(source) {
8484
day++
8585
val divs = td.getElementsByTag("div")
8686
for (div in divs) {
87-
val courseElements = div.getElementsByClass(tableName)
87+
val courseElements = div.getElementsByClass(webTableName)
8888
if (courseElements.text().isBlank()) {
8989
continue
9090
}

src/main/java/test/NWPUTest.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package main.java.test
2+
3+
import main.java.parser.NWPUParser
4+
5+
fun main() {
6+
//NWPUParser 里面有维护小提示,希望可以帮助到你。
7+
//秋春夏对应0、1、2
8+
val parser = NWPUParser("2019000000", "password", "2021", 1)
9+
parser.saveCourse(true)
10+
}

0 commit comments

Comments
 (0)