Skip to content

Commit

Permalink
feat: add support for questionnaire list
Browse files Browse the repository at this point in the history
  • Loading branch information
AsakuraMizu committed Nov 8, 2024
1 parent d5af680 commit 326d550
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 6 deletions.
49 changes: 47 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import {
INotificationDetail,
Language,
Notification,
QNR,
QNRType,
Question,
RemoteFile,
SemesterInfo,
Expand All @@ -40,6 +42,7 @@ import {
CONTENT_TYPE_MAP_REVERSE,
GRADE_LEVEL_MAP,
JSONP_EXTRACTOR_NAME,
QNR_TYPE_MAP,
decodeHTML,
extractJSONPResult,
formatFileSize,
Expand Down Expand Up @@ -342,6 +345,8 @@ export class Learn2018Helper {
return this.getDiscussionList(id, courseType) as Promise<ContentTypeMap[T][]>;
case ContentType.QUESTION:
return this.getAnsweredQuestionList(id, courseType) as Promise<ContentTypeMap[T][]>;
case ContentType.QNR:
return this.getQuestionnaireList(id) as Promise<ContentTypeMap[T][]>;
default:
return Promise.reject({
reason: FailReason.NOT_IMPLEMENTED,
Expand Down Expand Up @@ -694,6 +699,46 @@ export class Learn2018Helper {
);
}

/**
* Get all questionnaires (课程问卷/QNR) of the specified course.
*/
public async getQuestionnaireList(courseID: string): Promise<QNR[]> {
return Promise.all([
this.getQuestionnaireListAtUrl(courseID, URLS.LEARN_QNR_LIST_ONGOING),
this.getQuestionnaireListAtUrl(courseID, URLS.LEARN_QNR_LIST_ENDED),
]).then((r) => r.flat());
}

async getQuestionnaireListAtUrl(courseID: string, url: string): Promise<QNR[]> {
const json = await (
await this.#myFetchWithToken(url, { method: 'POST', body: URLS.LEARN_PAGE_LIST_FORM_DATA(courseID) })
).json();
if (json.result !== 'success') {
return Promise.reject({
reason: FailReason.INVALID_RESPONSE,
extra: json,
} as ApiError);
}
const result = (json.object?.aaData ?? []) as any[];
return result.map((e) => {
const type = QNR_TYPE_MAP.get(e.wjlx) ?? QNRType.SURVEY;
return {
id: e.wjid,
type,
title: decodeHTML(e.wjbt),
startTime: new Date(e.kssj),
endTime: new Date(e.jssj),
uploadTime: new Date(e.scsj),
uploaderId: e.scr,
uploaderName: e.scrxm,
submitTime: e.tjsj ? new Date(e.tjsj) : undefined,
isFavorite: e.sfsc === YES,
comment: e.bznr ?? undefined,
url: URLS.LEARN_QNR_DETAIL(e.wlkcid, e.wjid, type),
} satisfies QNR;
});
}

/**
* Add an item to favorites. (收藏)
*/
Expand Down Expand Up @@ -728,7 +773,7 @@ export class Learn2018Helper {
const json = await (
await this.#myFetchWithToken(URLS.LEARN_FAVORITE_LIST(type), {
method: 'POST',
body: URLS.LEARN_FAVORITE_OR_COMMENT_LIST_FORM_DATA(courseID),
body: URLS.LEARN_PAGE_LIST_FORM_DATA(courseID),
})
).json();
if (json.result !== 'success') {
Expand Down Expand Up @@ -824,7 +869,7 @@ export class Learn2018Helper {
const json = await (
await this.#myFetchWithToken(URLS.LEARN_COMMENT_LIST(type), {
method: 'POST',
body: URLS.LEARN_FAVORITE_OR_COMMENT_LIST_FORM_DATA(courseID),
body: URLS.LEARN_PAGE_LIST_FORM_DATA(courseID),
})
).json();
if (json.result !== 'success') {
Expand Down
25 changes: 25 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export enum ContentType {
HOMEWORK = 'homework',
DISCUSSION = 'discussion',
QUESTION = 'question',
QNR = 'questionnaire',
}

interface IUserInfo {
Expand Down Expand Up @@ -288,12 +289,36 @@ interface IQuestion extends IDiscussionBase {

export type Question = IQuestion;

export enum QNRType {
VOTE = 'tp',
FORM = 'tb',
SURVEY = 'wj',
}

interface IQNR {
id: string;
type: QNRType;
title: string;
startTime: Date;
endTime: Date;
uploadTime: Date;
uploaderId: string;
uploaderName: string;
submitTime?: Date;
isFavorite: boolean;
comment?: string;
url: string;
}

export type QNR = IQNR;

export type ContentTypeMap = {
[ContentType.NOTIFICATION]: Notification;
[ContentType.FILE]: File;
[ContentType.HOMEWORK]: Homework;
[ContentType.DISCUSSION]: Discussion;
[ContentType.QUESTION]: Question;
[ContentType.QNR]: QNR;
};

interface ICourseContent<T extends ContentType> {
Expand Down
9 changes: 7 additions & 2 deletions src/urls.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FormData } from 'node-fetch-native';
import { ContentType, CourseType, IHomeworkSubmitAttachment, Language } from './types';
import { ContentType, CourseType, IHomeworkSubmitAttachment, Language, QNRType } from './types';
import { CONTENT_TYPE_MAP, getMkFromType } from './utils';

export const LEARN_PREFIX = 'https://learn.tsinghua.edu.cn';
Expand Down Expand Up @@ -180,6 +180,11 @@ export const LEARN_QUESTION_DETAIL = (courseID: string, questionID: string, cour
? `${LEARN_PREFIX}/f/wlxt/bbs/bbs_kcdy/student/viewDyById?wlkcid=${courseID}&id=${questionID}`
: `${LEARN_PREFIX}/f/wlxt/bbs/bbs_kcdy/teacher/beforeEditDy?wlkcid=${courseID}&id=${questionID}`;

export const LEARN_QNR_LIST_ONGOING = `${LEARN_PREFIX}/b/wlxt/kcwj/wlkc_wjb/student/pageListWks`;
export const LEARN_QNR_LIST_ENDED = `${LEARN_PREFIX}/b/wlxt/kcwj/wlkc_wjb/student/pageListYjs`;
export const LEARN_QNR_DETAIL = (courseID: string, qnrID: string, type: QNRType) =>
`${LEARN_PREFIX}/f/wlxt/kcwj/wlkc_wjb/student/beforeAdd?wlkcid=${courseID}&wjid=${qnrID}&wjlx=${type}&jswj=no`;

export const WebsiteShowLanguage = {
[Language.ZH]: 'zh_CN',
[Language.EN]: 'en_US',
Expand Down Expand Up @@ -219,7 +224,7 @@ export const LEARN_COMMENT_SET_FORM_DATA = (type: ContentType, id: string, conte
export const LEARN_COMMENT_LIST = (type?: ContentType) =>
`${LEARN_PREFIX}/b/wlxt/xt/wlkc_xsbjb/student/pageList?ywlx=${type ? CONTENT_TYPE_MAP.get(type) : 'ALL'}`;

export const LEARN_FAVORITE_OR_COMMENT_LIST_FORM_DATA = (courseID?: string) => {
export const LEARN_PAGE_LIST_FORM_DATA = (courseID?: string) => {
const form = new FormData();
form.append('aoData', JSON.stringify(courseID ? [{ name: 'wlkcid', value: courseID }] : []));
return form;
Expand Down
13 changes: 11 additions & 2 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { decodeHTML as _decodeHTML } from 'entities';

import { ContentType, FailReason, HomeworkGradeLevel, SemesterType } from './types';
import { ContentType, FailReason, HomeworkGradeLevel, QNRType, SemesterType } from './types';

export function parseSemesterType(n: number): SemesterType {
if (n === 1) {
Expand All @@ -20,6 +20,7 @@ const CONTENT_TYPE_MK_MAP = {
[ContentType.HOMEWORK]: 'kczy',
[ContentType.DISCUSSION]: '',
[ContentType.QUESTION]: '',
[ContentType.QNR]: '',
};

export function getMkFromType(type: ContentType): string {
Expand Down Expand Up @@ -99,12 +100,20 @@ export const CONTENT_TYPE_MAP = new Map([
[ContentType.HOMEWORK, 'KCZY'],
[ContentType.DISCUSSION, 'KCTL'],
[ContentType.QUESTION, 'KCDY'],
// omitted: 问卷(KCWJ) & 课表(KCKB) as they are not supported now
[ContentType.QNR, 'KCWJ'],
// omitted: 课表(KCKB)
]);
export const CONTENT_TYPE_MAP_REVERSE = new Map([
[CONTENT_TYPE_MAP.get(ContentType.NOTIFICATION)!, ContentType.NOTIFICATION],
[CONTENT_TYPE_MAP.get(ContentType.FILE)!, ContentType.FILE],
[CONTENT_TYPE_MAP.get(ContentType.HOMEWORK)!, ContentType.HOMEWORK],
[CONTENT_TYPE_MAP.get(ContentType.DISCUSSION)!, ContentType.DISCUSSION],
[CONTENT_TYPE_MAP.get(ContentType.QUESTION)!, ContentType.QUESTION],
[CONTENT_TYPE_MAP.get(ContentType.QNR)!, ContentType.QNR],
]);

export const QNR_TYPE_MAP = new Map([
['投票', QNRType.VOTE],
['填表', QNRType.FORM],
['问卷', QNRType.SURVEY],
]);

0 comments on commit 326d550

Please sign in to comment.