|
| 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 | +} |
0 commit comments