@robertying/thu-learn-lib 4.1.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/contents.d.ts +4 -4
- package/lib/contents.d.ts.map +1 -1
- package/lib/contents.js +18 -19
- package/lib/contents.js.map +1 -1
- package/lib/courses.d.ts +5 -3
- package/lib/courses.d.ts.map +1 -1
- package/lib/courses.js +19 -7
- package/lib/courses.js.map +1 -1
- package/lib/homework.d.ts +11 -3
- package/lib/homework.d.ts.map +1 -1
- package/lib/homework.js +27 -26
- package/lib/homework.js.map +1 -1
- package/lib/index.d.ts +39 -10
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +51 -13
- package/lib/index.js.map +1 -1
- package/lib/notices.d.ts +11 -3
- package/lib/notices.d.ts.map +1 -1
- package/lib/notices.js +31 -23
- package/lib/notices.js.map +1 -1
- package/lib/request.d.ts +32 -6
- package/lib/request.d.ts.map +1 -1
- package/lib/request.js +153 -46
- package/lib/request.js.map +1 -1
- package/lib/types.d.ts +43 -2
- package/lib/types.d.ts.map +1 -1
- package/lib/types.js +1 -0
- package/lib/types.js.map +1 -1
- package/lib/utils.d.ts +3 -0
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +2 -0
- package/lib/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/contents.ts +20 -24
- package/src/courses.ts +21 -7
- package/src/homework.ts +89 -84
- package/src/index.ts +77 -14
- package/src/notices.ts +48 -43
- package/src/request.ts +159 -46
- package/src/types.ts +50 -1
- package/src/utils.ts +5 -0
package/lib/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAc,kBAAkB,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1G,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,GAAG,YAAY,CAUzD;AAWD,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CAEvD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAU/C;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAMjF;AAED,eAAO,MAAM,eAAe,iCA0B1B,CAAC;AAEH,eAAO,MAAM,oBAAoB,kCAAkC,CAAC;AAEpE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAQrD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMnD;AAED,eAAO,MAAM,gBAAgB,0BAQ3B,CAAC;AACH,eAAO,MAAM,wBAAwB,0BAOnC,CAAC;AAEH,eAAO,MAAM,YAAY,gCAIvB,CAAC"}
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,WAAW,EAAc,kBAAkB,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1G,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,GAAG,YAAY,CAUzD;AAWD,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CAEvD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAU/C;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAMjF;AAED,eAAO,MAAM,eAAe,iCA0B1B,CAAC;AAEH,eAAO,MAAM,oBAAoB,kCAAkC,CAAC;AAEpE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAQrD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMnD;AAED,eAAO,MAAM,gBAAgB,0BAQ3B,CAAC;AACH,eAAO,MAAM,wBAAwB,0BAOnC,CAAC;AAEH,eAAO,MAAM,YAAY,gCAIvB,CAAC;AAEH,oFAAoF;AACpF,eAAO,MAAM,UAAU,GAAI,CAAC,YAAY,cAAc,CAAC,CAAC,CAAC,GAAG,SAAS,QAAQ,CAAC,KAAG,OACP,CAAC"}
|
package/lib/utils.js
CHANGED
|
@@ -112,4 +112,6 @@ export const QNR_TYPE_MAP = new Map([
|
|
|
112
112
|
['填表', QuestionnaireType.FORM],
|
|
113
113
|
['问卷', QuestionnaireType.SURVEY],
|
|
114
114
|
]);
|
|
115
|
+
/** whether `item` is selected by a `DetailSelector` (`undefined` means `'none'`) */
|
|
116
|
+
export const isSelected = (selector, item) => selector === 'all' || (typeof selector === 'function' && selector(item));
|
|
115
117
|
//# sourceMappingURL=utils.js.map
|
package/lib/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,IAAI,WAAW,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/contents.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// One kind of content for many courses at once.
|
|
2
2
|
|
|
3
3
|
import type { Session } from './request.ts';
|
|
4
|
-
import type { ContentTypeMap, CourseContent } from './types.ts';
|
|
4
|
+
import type { AllContentsOptions, AllContentsResult, ContentTypeMap, CourseContent } from './types.ts';
|
|
5
5
|
import { ContentType, CourseType, FailReason } from './types.ts';
|
|
6
6
|
import { getAnsweredQuestionList, getDiscussionList } from './discussions.ts';
|
|
7
7
|
import { getFileList } from './files.ts';
|
|
@@ -11,23 +11,27 @@ import { getQuestionnaireList } from './questionnaires.ts';
|
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Get certain type of content of all specified courses.
|
|
14
|
-
* It
|
|
14
|
+
* It wraps around the `getXXX` list functions; one course failing doesn't fail the others: its error is in `errors`.
|
|
15
15
|
*/
|
|
16
16
|
export async function getAllContents<T extends ContentType>(
|
|
17
17
|
s: Session,
|
|
18
18
|
courseIDs: string[],
|
|
19
19
|
type: T,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const fetchContentForCourse = (id: string) => {
|
|
20
|
+
options: AllContentsOptions<T> = {},
|
|
21
|
+
): Promise<AllContentsResult<T>> {
|
|
22
|
+
const courseType = options.courseType ?? CourseType.STUDENT;
|
|
23
|
+
const fetchContentForCourse = (id: string): Promise<ContentTypeMap[T][]> => {
|
|
24
24
|
switch (type) {
|
|
25
25
|
case ContentType.NOTIFICATION:
|
|
26
|
-
return getNotificationList(s, id, courseType
|
|
26
|
+
return getNotificationList(s, id, courseType, {
|
|
27
|
+
attachments: (options as AllContentsOptions<ContentType.NOTIFICATION>).details,
|
|
28
|
+
}) as Promise<ContentTypeMap[T][]>;
|
|
27
29
|
case ContentType.FILE:
|
|
28
30
|
return getFileList(s, id, courseType) as Promise<ContentTypeMap[T][]>;
|
|
29
31
|
case ContentType.HOMEWORK:
|
|
30
|
-
return getHomeworkList(s, id
|
|
32
|
+
return getHomeworkList(s, id, CourseType.STUDENT, {
|
|
33
|
+
details: (options as AllContentsOptions<ContentType.HOMEWORK>).details,
|
|
34
|
+
}) as Promise<ContentTypeMap[T][]>;
|
|
31
35
|
case ContentType.DISCUSSION:
|
|
32
36
|
return getDiscussionList(s, id, courseType) as Promise<ContentTypeMap[T][]>;
|
|
33
37
|
case ContentType.QUESTION:
|
|
@@ -43,25 +47,17 @@ export async function getAllContents<T extends ContentType>(
|
|
|
43
47
|
};
|
|
44
48
|
|
|
45
49
|
const contents: CourseContent<T> = {};
|
|
50
|
+
const errors: AllContentsResult<T>['errors'] = {};
|
|
46
51
|
|
|
47
|
-
|
|
52
|
+
await Promise.all(
|
|
48
53
|
courseIDs.map(async (id) => {
|
|
49
|
-
|
|
54
|
+
try {
|
|
55
|
+
contents[id] = await fetchContentForCourse(id);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
errors[id] = err;
|
|
58
|
+
}
|
|
50
59
|
}),
|
|
51
60
|
);
|
|
52
61
|
|
|
53
|
-
|
|
54
|
-
for (const r of results) {
|
|
55
|
-
if (r.status === 'rejected') {
|
|
56
|
-
return Promise.reject({
|
|
57
|
-
reason: FailReason.INVALID_RESPONSE,
|
|
58
|
-
extra: {
|
|
59
|
-
reason: r.reason,
|
|
60
|
-
},
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return contents;
|
|
62
|
+
return { contents, errors };
|
|
67
63
|
}
|
package/src/courses.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Semesters and courses.
|
|
2
2
|
|
|
3
3
|
import type { Session } from './request.ts';
|
|
4
|
-
import type { CourseInfo, SemesterInfo } from './types.ts';
|
|
4
|
+
import type { ApiError, CourseInfo, CourseListOptions, SemesterInfo } from './types.ts';
|
|
5
5
|
import { CourseType, FailReason } from './types.ts';
|
|
6
6
|
import * as URLS from './urls.ts';
|
|
7
7
|
import { decodeHTML, parseSemesterType } from './utils.ts';
|
|
@@ -38,12 +38,13 @@ export async function getCurrentSemester(s: Session): Promise<SemesterInfo> {
|
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
/** get all courses in the specified semester */
|
|
41
|
+
/** get all courses in the specified semester; time and location only with `options.timeAndLocation` */
|
|
42
42
|
export async function getCourseList(
|
|
43
43
|
s: Session,
|
|
44
44
|
semesterID: string,
|
|
45
45
|
courseType: CourseType = CourseType.STUDENT,
|
|
46
46
|
lang = s.lang,
|
|
47
|
+
options: CourseListOptions = {},
|
|
47
48
|
): Promise<CourseInfo[]> {
|
|
48
49
|
const json = await (await s.fetchWithToken(URLS.LEARN_COURSE_LIST(semesterID, courseType, lang))).json();
|
|
49
50
|
if (json.message !== 'success' || !Array.isArray(json.resultList)) {
|
|
@@ -56,11 +57,14 @@ export async function getCourseList(
|
|
|
56
57
|
|
|
57
58
|
return Promise.all(
|
|
58
59
|
result.map(async (c) => {
|
|
59
|
-
let timeAndLocation: string[]
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
let timeAndLocation: string[] | undefined;
|
|
61
|
+
if (options.timeAndLocation) {
|
|
62
|
+
try {
|
|
63
|
+
timeAndLocation = await getCourseTimeAndLocation(s, c.wlkcid);
|
|
64
|
+
} catch {
|
|
65
|
+
timeAndLocation = [];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
64
68
|
return {
|
|
65
69
|
id: c.wlkcid,
|
|
66
70
|
name: decodeHTML(c.zywkcm),
|
|
@@ -78,6 +82,16 @@ export async function getCourseList(
|
|
|
78
82
|
);
|
|
79
83
|
}
|
|
80
84
|
|
|
85
|
+
/** get the time and location of one course (1 request) */
|
|
86
|
+
export async function getCourseTimeAndLocation(s: Session, courseID: string): Promise<string[]> {
|
|
87
|
+
// see https://github.com/Harry-Chen/Learn-Helper/issues/145
|
|
88
|
+
const json = await (await s.fetchWithToken(URLS.LEARN_COURSE_TIME_LOCATION(courseID))).json();
|
|
89
|
+
if (!Array.isArray(json)) {
|
|
90
|
+
throw { reason: FailReason.INVALID_RESPONSE, extra: json } as ApiError;
|
|
91
|
+
}
|
|
92
|
+
return json as string[];
|
|
93
|
+
}
|
|
94
|
+
|
|
81
95
|
export async function sortCourses(s: Session, courseIDs: string[]): Promise<void> {
|
|
82
96
|
const json = await (
|
|
83
97
|
await s.fetchWithToken(URLS.LEARN_SORT_COURSES, {
|
package/src/homework.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
ApiError,
|
|
10
10
|
ExcellentHomework,
|
|
11
11
|
Homework,
|
|
12
|
+
HomeworkListOptions,
|
|
12
13
|
HomeworkTA,
|
|
13
14
|
IExcellentHomework,
|
|
14
15
|
IHomework,
|
|
@@ -19,12 +20,20 @@ import type {
|
|
|
19
20
|
} from './types.ts';
|
|
20
21
|
import { ContentType, CourseType, FailReason } from './types.ts';
|
|
21
22
|
import * as URLS from './urls.ts';
|
|
22
|
-
import { GRADE_LEVEL_MAP, decodeHTML, trimAndDefine } from './utils.ts';
|
|
23
|
+
import { GRADE_LEVEL_MAP, decodeHTML, isSelected, trimAndDefine } from './utils.ts';
|
|
23
24
|
|
|
24
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* Get all homeworks (课程作业) of the specified course: 3 status lists and the excellent homework list.
|
|
27
|
+
* Details (page + description) only for the homework `options.details` selects.
|
|
28
|
+
*/
|
|
25
29
|
export function getHomeworkList(s: Session, courseID: string): Promise<Homework[]>;
|
|
26
30
|
|
|
27
|
-
export function getHomeworkList(
|
|
31
|
+
export function getHomeworkList(
|
|
32
|
+
s: Session,
|
|
33
|
+
courseID: string,
|
|
34
|
+
courseType: CourseType.STUDENT,
|
|
35
|
+
options?: HomeworkListOptions,
|
|
36
|
+
): Promise<Homework[]>;
|
|
28
37
|
|
|
29
38
|
export function getHomeworkList(s: Session, courseID: string, courseType: CourseType.TEACHER): Promise<HomeworkTA[]>;
|
|
30
39
|
|
|
@@ -32,6 +41,7 @@ export async function getHomeworkList(
|
|
|
32
41
|
s: Session,
|
|
33
42
|
courseID: string,
|
|
34
43
|
courseType: CourseType = CourseType.STUDENT,
|
|
44
|
+
options: HomeworkListOptions = {},
|
|
35
45
|
): Promise<Homework[] | HomeworkTA[]> {
|
|
36
46
|
if (courseType === CourseType.TEACHER) {
|
|
37
47
|
const json = await (
|
|
@@ -70,9 +80,19 @@ export async function getHomeworkList(
|
|
|
70
80
|
}) satisfies HomeworkTA,
|
|
71
81
|
);
|
|
72
82
|
} else {
|
|
83
|
+
const [lists, excellentHomeworkListByHomework] = await Promise.all([
|
|
84
|
+
Promise.all(
|
|
85
|
+
URLS.LEARN_HOMEWORK_LIST_SOURCE.map((source) => getHomeworkListAtUrl(s, courseID, source.url, source.status)),
|
|
86
|
+
),
|
|
87
|
+
// Don't block the whole process if excellent homework list cannot be fetched
|
|
88
|
+
getExcellentHomeworkListByHomework(s, courseID).catch((): { [id: string]: ExcellentHomework[] } => ({})),
|
|
89
|
+
]);
|
|
73
90
|
return Promise.all(
|
|
74
|
-
|
|
75
|
-
|
|
91
|
+
lists.flat().map(async (h) => {
|
|
92
|
+
const homework: Homework = { ...h, excellentHomeworkList: excellentHomeworkListByHomework[h.baseId] };
|
|
93
|
+
return isSelected(options.details, homework) ? { ...homework, ...(await getHomeworkDetail(s, h)) } : homework;
|
|
94
|
+
}),
|
|
95
|
+
);
|
|
76
96
|
}
|
|
77
97
|
}
|
|
78
98
|
|
|
@@ -81,7 +101,7 @@ async function getHomeworkListAtUrl(
|
|
|
81
101
|
courseID: string,
|
|
82
102
|
url: string,
|
|
83
103
|
status: IHomeworkStatus,
|
|
84
|
-
): Promise<
|
|
104
|
+
): Promise<IHomework[]> {
|
|
85
105
|
const json = await (
|
|
86
106
|
await s.fetchWithToken(url, {
|
|
87
107
|
method: 'POST',
|
|
@@ -97,50 +117,31 @@ async function getHomeworkListAtUrl(
|
|
|
97
117
|
|
|
98
118
|
const result = (json.object?.aaData ?? []) as any[];
|
|
99
119
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
gradeLevel: GRADE_LEVEL_MAP.get(h.cj),
|
|
126
|
-
graderName: trimAndDefine(h.jsm),
|
|
127
|
-
gradeContent: trimAndDefine(h.pynr),
|
|
128
|
-
gradeTime: h.pysj === null ? undefined : h.pysj,
|
|
129
|
-
isFavorite: h.sfsc === YES,
|
|
130
|
-
favoriteTime: h.scsj === null || h.sfsc !== YES ? undefined : h.scsj,
|
|
131
|
-
comment: h.bznr ?? undefined,
|
|
132
|
-
excellentHomeworkList: excellentHomeworkListByHomework[h.zyid],
|
|
133
|
-
...status,
|
|
134
|
-
}) satisfies IHomework,
|
|
135
|
-
)
|
|
136
|
-
.map(
|
|
137
|
-
async (h) =>
|
|
138
|
-
({
|
|
139
|
-
...h,
|
|
140
|
-
...(await parseHomeworkAtUrl(s, h.url)),
|
|
141
|
-
...(await getHomeworkDetail(s, h.baseId)),
|
|
142
|
-
}) satisfies Homework,
|
|
143
|
-
),
|
|
120
|
+
return result.map(
|
|
121
|
+
(h) =>
|
|
122
|
+
({
|
|
123
|
+
id: h.xszyid,
|
|
124
|
+
studentHomeworkId: h.xszyid,
|
|
125
|
+
baseId: h.zyid,
|
|
126
|
+
title: decodeHTML(h.bt),
|
|
127
|
+
url: URLS.LEARN_HOMEWORK_PAGE(h.wlkcid, h.xszyid),
|
|
128
|
+
deadline: h.jzsj,
|
|
129
|
+
lateSubmissionDeadline: h.bjjzsj ? h.bjjzsj : undefined,
|
|
130
|
+
isLateSubmission: h.sfbj === YES,
|
|
131
|
+
completionType: h.zywcfs,
|
|
132
|
+
submissionType: h.zytjfs,
|
|
133
|
+
submitUrl: URLS.LEARN_HOMEWORK_SUBMIT_PAGE(h.wlkcid, h.xszyid),
|
|
134
|
+
submitTime: h.scsj === null ? undefined : h.scsj,
|
|
135
|
+
grade: h.cj === null ? undefined : h.cj,
|
|
136
|
+
gradeLevel: GRADE_LEVEL_MAP.get(h.cj),
|
|
137
|
+
graderName: trimAndDefine(h.jsm),
|
|
138
|
+
gradeContent: trimAndDefine(h.pynr),
|
|
139
|
+
gradeTime: h.pysj === null ? undefined : h.pysj,
|
|
140
|
+
isFavorite: h.sfsc === YES,
|
|
141
|
+
favoriteTime: h.scsj === null || h.sfsc !== YES ? undefined : h.scsj,
|
|
142
|
+
comment: h.bznr ?? undefined,
|
|
143
|
+
...status,
|
|
144
|
+
}) satisfies IHomework,
|
|
144
145
|
);
|
|
145
146
|
}
|
|
146
147
|
|
|
@@ -163,40 +164,29 @@ async function getExcellentHomeworkListByHomework(
|
|
|
163
164
|
|
|
164
165
|
const result = (json.object?.aaData ?? []) as any[];
|
|
165
166
|
|
|
166
|
-
return
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
},
|
|
182
|
-
}) satisfies IExcellentHomework,
|
|
183
|
-
)
|
|
184
|
-
.map(
|
|
185
|
-
async (h) =>
|
|
186
|
-
({
|
|
187
|
-
...h,
|
|
188
|
-
...(await parseHomeworkAtUrl(s, h.url)),
|
|
189
|
-
...(await getHomeworkDetail(s, h.baseId)),
|
|
190
|
-
}) satisfies ExcellentHomework,
|
|
191
|
-
),
|
|
167
|
+
return result
|
|
168
|
+
.map(
|
|
169
|
+
(h) =>
|
|
170
|
+
({
|
|
171
|
+
id: h.xszyid,
|
|
172
|
+
baseId: h.zyid,
|
|
173
|
+
title: decodeHTML(h.bt),
|
|
174
|
+
url: URLS.LEARN_HOMEWORK_EXCELLENT_PAGE(h.wlkcid, h.xszyid),
|
|
175
|
+
completionType: h.zywcfs,
|
|
176
|
+
author: {
|
|
177
|
+
id: h.cy?.split(' ')?.[0],
|
|
178
|
+
name: h.cy?.split(' ')?.[1],
|
|
179
|
+
anonymous: h.sfzm === YES,
|
|
180
|
+
},
|
|
181
|
+
}) satisfies IExcellentHomework,
|
|
192
182
|
)
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
183
|
+
.reduce<{ [id: string]: ExcellentHomework[] }>((acc, cur) => {
|
|
184
|
+
if (!acc[cur.baseId]) {
|
|
185
|
+
acc[cur.baseId] = [];
|
|
186
|
+
}
|
|
187
|
+
acc[cur.baseId].push(cur);
|
|
188
|
+
return acc;
|
|
189
|
+
}, {});
|
|
200
190
|
}
|
|
201
191
|
|
|
202
192
|
async function parseHomeworkAtUrl(s: Session, url: string): Promise<IHomeworkDetail> {
|
|
@@ -216,7 +206,22 @@ async function parseHomeworkAtUrl(s: Session, url: string): Promise<IHomeworkDet
|
|
|
216
206
|
};
|
|
217
207
|
}
|
|
218
208
|
|
|
219
|
-
|
|
209
|
+
/**
|
|
210
|
+
* Get the detail of one homework or excellent homework: its page (answer, submitted content, attachments) and the
|
|
211
|
+
* description JSON (which wins for `description`). 2 requests.
|
|
212
|
+
*/
|
|
213
|
+
export async function getHomeworkDetail(
|
|
214
|
+
s: Session,
|
|
215
|
+
homework: Pick<IHomework | IExcellentHomework, 'url' | 'baseId'>,
|
|
216
|
+
): Promise<IHomeworkDetail> {
|
|
217
|
+
const [page, description] = await Promise.all([
|
|
218
|
+
parseHomeworkAtUrl(s, homework.url),
|
|
219
|
+
getHomeworkDescription(s, homework.baseId),
|
|
220
|
+
]);
|
|
221
|
+
return { ...page, ...description };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function getHomeworkDescription(s: Session, baseId: string): Promise<IHomeworkDetail> {
|
|
220
225
|
const json = await (
|
|
221
226
|
await s.fetchWithToken(URLS.LEARN_HOMEWORK_DETAIL, {
|
|
222
227
|
method: 'POST',
|
package/src/index.ts
CHANGED
|
@@ -10,8 +10,16 @@ import * as notices from './notices.ts';
|
|
|
10
10
|
import * as questionnaires from './questionnaires.ts';
|
|
11
11
|
import { Session } from './request.ts';
|
|
12
12
|
import type {
|
|
13
|
+
AllContentsOptions,
|
|
14
|
+
AllContentsResult,
|
|
13
15
|
CalendarOptions,
|
|
14
16
|
CalendarResult,
|
|
17
|
+
CourseListOptions,
|
|
18
|
+
ExcellentHomework,
|
|
19
|
+
HomeworkListOptions,
|
|
20
|
+
IHomeworkDetail,
|
|
21
|
+
NotificationListOptions,
|
|
22
|
+
RemoteFile,
|
|
15
23
|
CommentItem,
|
|
16
24
|
CourseInfo,
|
|
17
25
|
Discussion,
|
|
@@ -31,6 +39,7 @@ import type {
|
|
|
31
39
|
ContentType,
|
|
32
40
|
} from './types.ts';
|
|
33
41
|
import { CourseType } from './types.ts';
|
|
42
|
+
import * as URLS from './urls.ts';
|
|
34
43
|
import * as user from './user.ts';
|
|
35
44
|
|
|
36
45
|
export { addCSRFTokenToUrl } from './request.ts';
|
|
@@ -42,7 +51,7 @@ export class Learn2018Helper {
|
|
|
42
51
|
|
|
43
52
|
/** you can provide a CookieJar and / or CredentialProvider in the configuration */
|
|
44
53
|
constructor(config?: HelperConfig) {
|
|
45
|
-
this.#session = new Session(config, () =>
|
|
54
|
+
this.#session = new Session(config, () => auth.login(this.#session));
|
|
46
55
|
}
|
|
47
56
|
|
|
48
57
|
/** whether preview URLs open the first page only (`HelperConfig.generatePreviewUrlForFirstPage`) */
|
|
@@ -78,7 +87,10 @@ export class Learn2018Helper {
|
|
|
78
87
|
return auth.getRoamingTicket(this.#session, username, password, fingerPrint, fingerGenPrint, fingerGenPrint3);
|
|
79
88
|
}
|
|
80
89
|
|
|
81
|
-
/**
|
|
90
|
+
/**
|
|
91
|
+
* Login is necessary if you do not provide a `CredentialProvider`.
|
|
92
|
+
* Without credentials it uses the provider, and concurrent calls share one SSO login.
|
|
93
|
+
*/
|
|
82
94
|
public async login(
|
|
83
95
|
username?: string,
|
|
84
96
|
password?: string,
|
|
@@ -86,9 +98,19 @@ export class Learn2018Helper {
|
|
|
86
98
|
fingerGenPrint?: string,
|
|
87
99
|
fingerGenPrint3?: string,
|
|
88
100
|
): Promise<void> {
|
|
101
|
+
if (!username || !password || !fingerPrint) return this.#session.relogin();
|
|
89
102
|
return auth.login(this.#session, username, password, fingerPrint, fingerGenPrint, fingerGenPrint3);
|
|
90
103
|
}
|
|
91
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Make sure the session is usable without a full SSO login when possible: with a CSRF token (from an earlier login
|
|
107
|
+
* or `setCSRFToken`), make one cheap request and log in again only if the session has expired; without one, log in.
|
|
108
|
+
* Use it when resuming an app instead of `login()`.
|
|
109
|
+
*/
|
|
110
|
+
public async ensureSession(): Promise<void> {
|
|
111
|
+
return this.#session.ensureSession(URLS.LEARN_CURRENT_SEMESTER());
|
|
112
|
+
}
|
|
113
|
+
|
|
92
114
|
/** logout (to make everyone happy) */
|
|
93
115
|
public async logout(): Promise<void> {
|
|
94
116
|
return auth.logout(this.#session);
|
|
@@ -116,34 +138,56 @@ export class Learn2018Helper {
|
|
|
116
138
|
return courses.getCurrentSemester(this.#session);
|
|
117
139
|
}
|
|
118
140
|
|
|
119
|
-
/**
|
|
141
|
+
/**
|
|
142
|
+
* Get all courses in the specified semester.
|
|
143
|
+
* `timeAndLocation` is only fetched (one request per course) with `options.timeAndLocation`.
|
|
144
|
+
*/
|
|
120
145
|
public async getCourseList(
|
|
121
146
|
semesterID: string,
|
|
122
147
|
courseType: CourseType = CourseType.STUDENT,
|
|
123
148
|
lang: Language = this.#session.lang,
|
|
149
|
+
options?: CourseListOptions,
|
|
124
150
|
): Promise<CourseInfo[]> {
|
|
125
|
-
return courses.getCourseList(this.#session, semesterID, courseType, lang);
|
|
151
|
+
return courses.getCourseList(this.#session, semesterID, courseType, lang, options);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Get the time and location of one course. */
|
|
155
|
+
public async getCourseTimeAndLocation(courseID: string): Promise<string[]> {
|
|
156
|
+
return courses.getCourseTimeAndLocation(this.#session, courseID);
|
|
126
157
|
}
|
|
127
158
|
|
|
128
159
|
/**
|
|
129
160
|
* Get certain type of content of all specified courses.
|
|
130
|
-
* It
|
|
161
|
+
* It wraps around the `getXXX` list functions; a course that fails is reported in `errors` instead of failing
|
|
162
|
+
* the rest. `options.details` is passed on as homework `details` or notification `attachments`.
|
|
131
163
|
*/
|
|
132
164
|
public async getAllContents<T extends ContentType>(
|
|
133
165
|
courseIDs: string[],
|
|
134
166
|
type: T,
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
return contents.getAllContents(this.#session, courseIDs, type, courseType, allowFailure);
|
|
167
|
+
options?: AllContentsOptions<T>,
|
|
168
|
+
): Promise<AllContentsResult<T>> {
|
|
169
|
+
return contents.getAllContents(this.#session, courseIDs, type, options);
|
|
139
170
|
}
|
|
140
171
|
|
|
141
|
-
/**
|
|
172
|
+
/**
|
|
173
|
+
* Get all notifications (课程公告) of the specified course.
|
|
174
|
+
* Attachments are only fetched (one request per notice) for the notices `options.attachments` selects;
|
|
175
|
+
* use `getNotificationAttachment` for the others.
|
|
176
|
+
*/
|
|
142
177
|
public async getNotificationList(
|
|
143
178
|
courseID: string,
|
|
144
179
|
courseType: CourseType = CourseType.STUDENT,
|
|
180
|
+
options?: NotificationListOptions,
|
|
145
181
|
): Promise<Notification[]> {
|
|
146
|
-
return notices.getNotificationList(this.#session, courseID, courseType);
|
|
182
|
+
return notices.getNotificationList(this.#session, courseID, courseType, options);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Get the attachment of a notice (one request), `undefined` if it has none. */
|
|
186
|
+
public async getNotificationAttachment(
|
|
187
|
+
notification: Pick<Notification, 'url' | 'attachmentName'>,
|
|
188
|
+
courseType: CourseType = CourseType.STUDENT,
|
|
189
|
+
): Promise<RemoteFile | undefined> {
|
|
190
|
+
return notices.getNotificationAttachment(this.#session, notification, courseType);
|
|
147
191
|
}
|
|
148
192
|
|
|
149
193
|
/** Get all files (课程文件) of the specified course. */
|
|
@@ -171,17 +215,36 @@ export class Learn2018Helper {
|
|
|
171
215
|
return files.getFileListByCategory(this.#session, courseID, categoryId, courseType);
|
|
172
216
|
}
|
|
173
217
|
|
|
174
|
-
/**
|
|
218
|
+
/**
|
|
219
|
+
* Get all homeworks (课程作业) of the specified course, with the excellent homework list (without details).
|
|
220
|
+
* Details (page + description, two requests per homework) are only fetched for the homework `options.details`
|
|
221
|
+
* selects; use `getHomeworkDetail` for the others.
|
|
222
|
+
*/
|
|
175
223
|
public async getHomeworkList(courseID: string): Promise<Homework[]>;
|
|
176
|
-
public async getHomeworkList(
|
|
224
|
+
public async getHomeworkList(
|
|
225
|
+
courseID: string,
|
|
226
|
+
courseType: CourseType.STUDENT,
|
|
227
|
+
options?: HomeworkListOptions,
|
|
228
|
+
): Promise<Homework[]>;
|
|
177
229
|
public async getHomeworkList(courseID: string, courseType: CourseType.TEACHER): Promise<HomeworkTA[]>;
|
|
178
230
|
public async getHomeworkList(
|
|
179
231
|
courseID: string,
|
|
180
232
|
courseType: CourseType = CourseType.STUDENT,
|
|
233
|
+
options?: HomeworkListOptions,
|
|
181
234
|
): Promise<Homework[] | HomeworkTA[]> {
|
|
182
235
|
return courseType === CourseType.TEACHER
|
|
183
236
|
? homework.getHomeworkList(this.#session, courseID, CourseType.TEACHER)
|
|
184
|
-
: homework.getHomeworkList(this.#session, courseID, CourseType.STUDENT);
|
|
237
|
+
: homework.getHomeworkList(this.#session, courseID, CourseType.STUDENT, options);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Get the detail (page content, attachments and description) of one homework. */
|
|
241
|
+
public async getHomeworkDetail(item: Pick<Homework, 'url' | 'baseId'>): Promise<IHomeworkDetail> {
|
|
242
|
+
return homework.getHomeworkDetail(this.#session, item);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** Get the detail (page content, attachments and description) of one excellent homework. */
|
|
246
|
+
public async getExcellentHomeworkDetail(item: Pick<ExcellentHomework, 'url' | 'baseId'>): Promise<IHomeworkDetail> {
|
|
247
|
+
return homework.getHomeworkDetail(this.#session, item);
|
|
185
248
|
}
|
|
186
249
|
|
|
187
250
|
/** Get all discussions (课程讨论) of the specified course. */
|