@robertying/thu-learn-lib 4.1.0 → 5.1.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/README.md +41 -7
- package/lib/auth.d.ts +1 -1
- package/lib/auth.d.ts.map +1 -1
- package/lib/auth.js +17 -5
- package/lib/auth.js.map +1 -1
- package/lib/calendar.d.ts +6 -4
- package/lib/calendar.d.ts.map +1 -1
- package/lib/calendar.js +21 -8
- package/lib/calendar.js.map +1 -1
- package/lib/contents.d.ts +4 -4
- package/lib/contents.d.ts.map +1 -1
- package/lib/contents.js +28 -20
- 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 +24 -12
- package/lib/courses.js.map +1 -1
- package/lib/discussions.js +2 -2
- package/lib/discussions.js.map +1 -1
- package/lib/favorites.d.ts.map +1 -1
- package/lib/favorites.js +12 -12
- package/lib/favorites.js.map +1 -1
- package/lib/files.d.ts +1 -1
- package/lib/files.d.ts.map +1 -1
- package/lib/files.js +10 -8
- package/lib/files.js.map +1 -1
- package/lib/homework.d.ts +11 -4
- package/lib/homework.d.ts.map +1 -1
- package/lib/homework.js +46 -43
- package/lib/homework.js.map +1 -1
- package/lib/index.d.ts +47 -13
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +63 -17
- 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 +40 -36
- package/lib/notices.js.map +1 -1
- package/lib/questionnaires.d.ts +6 -3
- package/lib/questionnaires.d.ts.map +1 -1
- package/lib/questionnaires.js +20 -12
- package/lib/questionnaires.js.map +1 -1
- package/lib/request.d.ts +66 -10
- package/lib/request.d.ts.map +1 -1
- package/lib/request.js +234 -50
- package/lib/request.js.map +1 -1
- package/lib/types.d.ts +52 -3
- package/lib/types.d.ts.map +1 -1
- package/lib/types.js +1 -0
- package/lib/types.js.map +1 -1
- package/lib/user.js +1 -1
- package/lib/user.js.map +1 -1
- package/lib/utils.d.ts +3 -0
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +11 -3
- package/lib/utils.js.map +1 -1
- package/package.json +2 -2
- package/src/auth.ts +18 -5
- package/src/calendar.ts +28 -10
- package/src/contents.ts +30 -25
- package/src/courses.ts +31 -19
- package/src/discussions.ts +2 -2
- package/src/favorites.ts +22 -32
- package/src/files.ts +12 -12
- package/src/homework.ts +118 -123
- package/src/index.ts +94 -19
- package/src/notices.ts +58 -59
- package/src/questionnaires.ts +51 -37
- package/src/request.ts +260 -48
- package/src/types.ts +62 -2
- package/src/user.ts +1 -1
- package/src/utils.ts +14 -3
package/src/types.ts
CHANGED
|
@@ -14,6 +14,10 @@ export type HelperConfig = {
|
|
|
14
14
|
fetch?: Fetch;
|
|
15
15
|
cookieJar?: CookieJar;
|
|
16
16
|
generatePreviewUrlForFirstPage?: boolean;
|
|
17
|
+
/** at most this many requests in flight at once, including login (default 6; `Infinity` for no limit) */
|
|
18
|
+
maxConcurrentRequests?: number;
|
|
19
|
+
/** milliseconds to wait for a response before rejecting with `FailReason.REQUEST_TIMEOUT` (default 60000; 0 for none) */
|
|
20
|
+
requestTimeout?: number;
|
|
17
21
|
};
|
|
18
22
|
|
|
19
23
|
export enum FailReason {
|
|
@@ -27,6 +31,7 @@ export enum FailReason {
|
|
|
27
31
|
UNEXPECTED_STATUS = 'unexpected status',
|
|
28
32
|
OPERATION_FAILED = 'operation failed',
|
|
29
33
|
ERROR_SETTING_COOKIES = 'could not set cookies',
|
|
34
|
+
REQUEST_TIMEOUT = 'request timed out',
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
export interface ApiError {
|
|
@@ -78,7 +83,8 @@ interface ICourseInfo {
|
|
|
78
83
|
name: string;
|
|
79
84
|
chineseName: string;
|
|
80
85
|
englishName: string;
|
|
81
|
-
timeAndLocation:
|
|
86
|
+
/** only with `getCourseList(..., { timeAndLocation: true })`; see `getCourseTimeAndLocation` */
|
|
87
|
+
timeAndLocation?: string[];
|
|
82
88
|
url: string;
|
|
83
89
|
teacherName: string;
|
|
84
90
|
teacherNumber: string;
|
|
@@ -111,6 +117,8 @@ export interface INotification {
|
|
|
111
117
|
expireTime?: string;
|
|
112
118
|
isFavorite: boolean;
|
|
113
119
|
comment?: string;
|
|
120
|
+
/** name of the attachment, if the notice has one; fetch it with `getNotificationAttachment` */
|
|
121
|
+
attachmentName?: string;
|
|
114
122
|
}
|
|
115
123
|
|
|
116
124
|
export interface INotificationDetail {
|
|
@@ -357,11 +365,63 @@ export interface IQuestionnaire {
|
|
|
357
365
|
isFavorite: boolean;
|
|
358
366
|
comment?: string;
|
|
359
367
|
url: string;
|
|
360
|
-
|
|
368
|
+
/** the questions; only for the questionnaires `getQuestionnaireList`'s `details` selects (by default all) */
|
|
369
|
+
detail?: QuestionnaireDetail[];
|
|
361
370
|
}
|
|
362
371
|
|
|
363
372
|
export type Questionnaire = IQuestionnaire;
|
|
364
373
|
|
|
374
|
+
/** which items get their extra per-item requests: none (default), all, or those the predicate accepts */
|
|
375
|
+
export type DetailSelector<T> = 'none' | 'all' | ((item: T) => boolean);
|
|
376
|
+
|
|
377
|
+
export interface CourseListOptions {
|
|
378
|
+
/** fetch each course's time and location (one request per course; default false) */
|
|
379
|
+
timeAndLocation?: boolean;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export interface HomeworkListOptions {
|
|
383
|
+
/**
|
|
384
|
+
* Which homework gets its detail (page + description JSON, 2 requests each); default `'none'`.
|
|
385
|
+
* The same as calling `getHomeworkDetail` for those items.
|
|
386
|
+
*/
|
|
387
|
+
details?: DetailSelector<IHomework>;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export interface NotificationListOptions {
|
|
391
|
+
/**
|
|
392
|
+
* Which notices with an attachment get it (1 request each); default `'none'`.
|
|
393
|
+
* The same as calling `getNotificationAttachment` for those items.
|
|
394
|
+
*/
|
|
395
|
+
attachments?: DetailSelector<INotification>;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export interface QuestionnaireListOptions {
|
|
399
|
+
/**
|
|
400
|
+
* Which questionnaires get their questions (`detail`, 1 request each); default `'all'` (unlike the other lists, for
|
|
401
|
+
* compatibility). The same as calling `getQuestionnaireDetail` for those items.
|
|
402
|
+
*/
|
|
403
|
+
details?: DetailSelector<IQuestionnaire>;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export interface AllContentsOptions<T extends ContentType> {
|
|
407
|
+
courseType?: CourseType;
|
|
408
|
+
/** homework or questionnaire `details`, or notification `attachments`, passed to each course's list */
|
|
409
|
+
details?: T extends ContentType.HOMEWORK
|
|
410
|
+
? DetailSelector<IHomework>
|
|
411
|
+
: T extends ContentType.NOTIFICATION
|
|
412
|
+
? DetailSelector<INotification>
|
|
413
|
+
: T extends ContentType.QUESTIONNAIRE
|
|
414
|
+
? DetailSelector<IQuestionnaire>
|
|
415
|
+
: never;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
export interface AllContentsResult<T extends ContentType> {
|
|
419
|
+
/** content of each course that succeeded */
|
|
420
|
+
contents: CourseContent<T>;
|
|
421
|
+
/** the error of each course that failed */
|
|
422
|
+
errors: { [courseID: string]: unknown };
|
|
423
|
+
}
|
|
424
|
+
|
|
365
425
|
export type ContentTypeMap = {
|
|
366
426
|
[ContentType.NOTIFICATION]: Notification;
|
|
367
427
|
[ContentType.FILE]: File;
|
package/src/user.ts
CHANGED
|
@@ -8,7 +8,7 @@ import * as URLS from './urls.ts';
|
|
|
8
8
|
|
|
9
9
|
/** get user's name and department */
|
|
10
10
|
export async function getUserInfo(s: Session, courseType = CourseType.STUDENT): Promise<UserInfo> {
|
|
11
|
-
const content = await
|
|
11
|
+
const content = await s.fetchTextWithToken(URLS.LEARN_HOMEPAGE(courseType));
|
|
12
12
|
|
|
13
13
|
const dom = $(content);
|
|
14
14
|
const name = dom('a.user-log').text().trim();
|
package/src/utils.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { decodeHTML as _decodeHTML } from 'entities';
|
|
2
2
|
|
|
3
|
+
import type { ApiError, DetailSelector } from './types.ts';
|
|
3
4
|
import { ContentType, FailReason, HomeworkGradeLevel, QuestionnaireType, SemesterType } from './types.ts';
|
|
4
5
|
|
|
5
6
|
export function parseSemesterType(n: number): SemesterType {
|
|
@@ -77,14 +78,20 @@ export const GRADE_LEVEL_MAP = new Map([
|
|
|
77
78
|
|
|
78
79
|
export const JSONP_EXTRACTOR_NAME = 'thu_learn_lib_jsonp_extractor';
|
|
79
80
|
|
|
81
|
+
const invalidJSONP = (extra: unknown): ApiError => ({ reason: FailReason.INVALID_RESPONSE, extra });
|
|
82
|
+
|
|
80
83
|
export function extractJSONPResult(jsonp: string): any {
|
|
81
84
|
// check jsonp format
|
|
82
85
|
if (!jsonp.startsWith(JSONP_EXTRACTOR_NAME)) {
|
|
83
|
-
throw
|
|
86
|
+
throw invalidJSONP(jsonp.slice(0, 200));
|
|
84
87
|
}
|
|
85
88
|
// evaluate the result
|
|
86
|
-
|
|
87
|
-
|
|
89
|
+
try {
|
|
90
|
+
// oxlint-disable-next-line typescript/no-implied-eval -- JSONP from the registrar is a JS expression, not JSON
|
|
91
|
+
return Function(`"use strict";const ${JSONP_EXTRACTOR_NAME}=(s)=>s;return ${jsonp};`)();
|
|
92
|
+
} catch (err) {
|
|
93
|
+
throw invalidJSONP(err);
|
|
94
|
+
}
|
|
88
95
|
}
|
|
89
96
|
|
|
90
97
|
export function formatFileSize(size: number): string {
|
|
@@ -118,3 +125,7 @@ export const QNR_TYPE_MAP = new Map([
|
|
|
118
125
|
['填表', QuestionnaireType.FORM],
|
|
119
126
|
['问卷', QuestionnaireType.SURVEY],
|
|
120
127
|
]);
|
|
128
|
+
|
|
129
|
+
/** whether `item` is selected by a `DetailSelector` (`undefined` means `'none'`) */
|
|
130
|
+
export const isSelected = <T>(selector: DetailSelector<T> | undefined, item: T): boolean =>
|
|
131
|
+
selector === 'all' || (typeof selector === 'function' && selector(item));
|