hlju.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. function findHljuTimetableRequest(){
  2. const resources=performance.getEntriesByType("resource");
  3. const matches=resources.map(item=>item.name).filter(url=>url.includes("TimeTableNewService/GetTimeTableByStudent"));
  4. return matches.length?matches[matches.length-1]:null;
  5. }
  6. async function fetchHljuTimetable(){
  7. const url=findHljuTimetableRequest();
  8. if(!url){
  9. throw new Error("没有找到课表请求,请先打开黑龙江大学课表并查询。");
  10. }
  11. const response=await fetch(url,{
  12. method:"GET",
  13. credentials:"include",
  14. headers:{
  15. "Accept":"application/json, text/javascript, */*; q=0.01",
  16. "X-Requested-With":"XMLHttpRequest"
  17. }
  18. });
  19. if(!response.ok){
  20. throw new Error(`课表请求失败 HTTP ${response.status}`);
  21. }
  22. const data=await response.json();
  23. if(!Array.isArray(data)){
  24. throw new Error("课表接口返回格式错误");
  25. }
  26. return data;
  27. }
  28. function parseSections(text){
  29. if(!text)return null;
  30. const match=text.match(/(\d+)\s*(?:,|,|-|~|~|至)\s*(\d+)/);
  31. if(!match)return null;
  32. return {
  33. start:Number(match[1]),
  34. end:Number(match[2])
  35. };
  36. }
  37. function parseWeeks(text){
  38. if(!text)return [];
  39. const weeks=new Set();
  40. const regex=/(\d+)\s*[-~~至]\s*(\d+)/g;
  41. let match;
  42. while((match=regex.exec(text))!==null){
  43. const start=Number(match[1]);
  44. const end=Number(match[2]);
  45. for(let i=start;i<=end;i++){
  46. weeks.add(i);
  47. }
  48. }
  49. return Array.from(weeks).sort((a,b)=>a-b);
  50. }
  51. function parseMultiTeacherWeeks(text){
  52. const result=[];
  53. if(!text)return result;
  54. const regex=/(\d+(?:[-~~]\d+)?)\(([^)]+)\)/g;
  55. let match;
  56. while((match=regex.exec(text))!==null){
  57. result.push({
  58. teacher:match[2].trim(),
  59. weeks:parseWeeks(match[1])
  60. });
  61. }
  62. return result;
  63. }
  64. function getField(lines,name){
  65. for(const line of lines){
  66. const match=line.match(new RegExp("^"+name+"\\s*[::](.*)$"));
  67. if(match)return match[1].trim();
  68. }
  69. return "";
  70. }
  71. function parseCourseBlock(block,weekday,sections){
  72. const lines=block.split(/\r?\n/).map(x=>x.trim()).filter(x=>x);
  73. if(lines.length<1)return [];
  74. const name=lines[0];
  75. const weekText=getField(lines,"周次");
  76. const position=getField(lines,"地点");
  77. const multi=parseMultiTeacherWeeks(weekText);
  78. const courses=[];
  79. if(multi.length){
  80. for(const item of multi){
  81. courses.push({
  82. name:name,
  83. teacher:item.teacher,
  84. position:position,
  85. day:weekday,
  86. startSection:sections.start,
  87. endSection:sections.end,
  88. weeks:item.weeks,
  89. isCustomTime:false
  90. });
  91. }
  92. return courses;
  93. }
  94. const teacherLine=lines[1]||"";
  95. const teacher=teacherLine.split(/\s+/)[0].trim();
  96. const weeks=parseWeeks(weekText);
  97. if(!weeks.length)return [];
  98. courses.push({
  99. name:name,
  100. teacher:teacher,
  101. position:position,
  102. day:weekday,
  103. startSection:sections.start,
  104. endSection:sections.end,
  105. weeks:weeks,
  106. isCustomTime:false
  107. });
  108. return courses;
  109. }
  110. function parseCourseCell(content,weekday,sections){
  111. if(!content||typeof content!=="string")return [];
  112. const text=content.replace(/\r\n/g,"\n").replace(/\r/g,"\n").trim();
  113. if(!text)return [];
  114. const blocks=text.split(/\n\s*\n+/).map(x=>x.trim()).filter(x=>x);
  115. let result=[];
  116. for(const block of blocks){
  117. result.push(...parseCourseBlock(block,weekday,sections));
  118. }
  119. return result;
  120. }
  121. function parseHljuTimetable(data){
  122. const weekdayFields=[
  123. {field:"Monday",day:1},
  124. {field:"Tuesday",day:2},
  125. {field:"Wednesday",day:3},
  126. {field:"Thursday",day:4},
  127. {field:"Friday",day:5},
  128. {field:"Saturday",day:6},
  129. {field:"Sunday",day:7}
  130. ];
  131. const courses=[];
  132. for(const row of data){
  133. if(!row||typeof row!=="object")continue;
  134. const sections=parseSections(row.JieCi);
  135. if(!sections){
  136. console.warn("无法解析节次:",row.JieCi);
  137. continue;
  138. }
  139. for(const weekday of weekdayFields){
  140. const content=row[weekday.field];
  141. if(!content)continue;
  142. const result=parseCourseCell(
  143. content,
  144. weekday.day,
  145. sections
  146. );
  147. courses.push(...result);
  148. }
  149. }
  150. return courses;
  151. }
  152. function validateCourses(courses){
  153. if(!Array.isArray(courses)){
  154. throw new Error("课程解析结果错误");
  155. }
  156. if(courses.length===0){
  157. throw new Error("没有解析到任何课程");
  158. }
  159. for(const course of courses){
  160. if(!course.name){
  161. throw new Error("存在课程名称为空");
  162. }
  163. if(!Number.isInteger(course.day)){
  164. throw new Error(
  165. `课程 ${course.name} 星期解析错误`
  166. );
  167. }
  168. if(!Number.isInteger(course.startSection)||
  169. !Number.isInteger(course.endSection)){
  170. throw new Error(
  171. `课程 ${course.name} 节次解析错误`
  172. );
  173. }
  174. if(!Array.isArray(course.weeks)||
  175. course.weeks.length===0){
  176. throw new Error(
  177. `课程 ${course.name} 周次解析错误`
  178. );
  179. }
  180. }
  181. }
  182. function printCourses(courses){
  183. console.log(
  184. "========== 黑龙江大学课程解析结果 =========="
  185. );
  186. console.table(
  187. courses.map(course=>({
  188. 课程:course.name,
  189. 教师:course.teacher,
  190. 地点:course.position,
  191. 星期:course.day,
  192. 节次:
  193. `${course.startSection}-${course.endSection}`,
  194. 周次:
  195. course.weeks.join(",")
  196. }))
  197. );
  198. console.log(
  199. "完整课程数据:",
  200. courses
  201. );
  202. console.log(
  203. "=========================================="
  204. );
  205. }
  206. async function saveHljuCourses(courses){
  207. try{
  208. await window.AndroidBridgePromise.saveImportedCourses(
  209. JSON.stringify(courses)
  210. );
  211. AndroidBridge.showToast(
  212. `成功导入 ${courses.length} 个课程时段`
  213. );
  214. return true;
  215. }catch(error){
  216. console.error(
  217. "保存课程失败:",
  218. error
  219. );
  220. AndroidBridge.showToast(
  221. "课程保存失败:"+error.message
  222. );
  223. return false;
  224. }
  225. }
  226. async function runImportFlow(){
  227. try{
  228. AndroidBridge.showToast(
  229. "正在获取黑龙江大学课表..."
  230. );
  231. const timetable=
  232. await fetchHljuTimetable();
  233. console.log(
  234. "黑龙江大学原始数据:",
  235. timetable
  236. );
  237. const courses=
  238. parseHljuTimetable(
  239. timetable
  240. );
  241. validateCourses(
  242. courses
  243. );
  244. printCourses(
  245. courses
  246. );
  247. const success=
  248. await saveHljuCourses(
  249. courses
  250. );
  251. if(!success){
  252. return;
  253. }
  254. AndroidBridge.showToast(
  255. "黑龙江大学课表导入成功"
  256. );
  257. AndroidBridge.notifyTaskCompletion();
  258. }catch(error){
  259. console.error(
  260. "========== 导入失败 ==========",
  261. error
  262. );
  263. AndroidBridge.showToast(
  264. "导入失败:"+error.message
  265. );
  266. }
  267. }
  268. runImportFlow();