politeshop 0.0.1
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 +33 -0
- package/dist/clients/brightspace.d.ts +75 -0
- package/dist/clients/brightspace.d.ts.map +1 -0
- package/dist/clients/brightspace.js +104 -0
- package/dist/clients/brightspace.js.map +1 -0
- package/dist/clients/polite.d.ts +117 -0
- package/dist/clients/polite.d.ts.map +1 -0
- package/dist/clients/polite.js +191 -0
- package/dist/clients/polite.js.map +1 -0
- package/dist/clients/politeshop.d.ts +119 -0
- package/dist/clients/politeshop.d.ts.map +1 -0
- package/dist/clients/politeshop.js +401 -0
- package/dist/clients/politeshop.js.map +1 -0
- package/dist/errors.d.ts +7 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +9 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/schema/polite.d.ts +809 -0
- package/dist/schema/polite.d.ts.map +1 -0
- package/dist/schema/polite.js +197 -0
- package/dist/schema/polite.js.map +1 -0
- package/dist/schema/siren.d.ts +66 -0
- package/dist/schema/siren.d.ts.map +1 -0
- package/dist/schema/siren.js +112 -0
- package/dist/schema/siren.js.map +1 -0
- package/dist/types.d.ts +105 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/array.d.ts +5 -0
- package/dist/utils/array.d.ts.map +1 -0
- package/dist/utils/array.js +13 -0
- package/dist/utils/array.js.map +1 -0
- package/dist/utils/url.d.ts +23 -0
- package/dist/utils/url.d.ts.map +1 -0
- package/dist/utils/url.js +53 -0
- package/dist/utils/url.js.map +1 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# POLITEShop library
|
|
2
|
+
|
|
3
|
+
JS/TS library for interacting with reverse-engineered [POLITEMall](https://politemall.polite.edu.sg/) APIs (`*.polite.edu.sg` and `*.api.brightspace.com`). Made for the [POLITEShop browser extension](https://github.com/haziq21/politeshop).
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { POLITEShop } from "politeshop";
|
|
7
|
+
|
|
8
|
+
const ps = new POLITEShop({
|
|
9
|
+
d2lSessionVal: "sNtnzd...",
|
|
10
|
+
d2lSecureSessionVal: "eqoLoG...",
|
|
11
|
+
domain: "nplms",
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// { id: '490586', name: 'HAZIQ DANISH BIN HAIRIL RIZAL' }
|
|
15
|
+
const user = await ps.getUser();
|
|
16
|
+
|
|
17
|
+
// [{ id: '803172', name: 'DATA STRUCTURES & ALGORITHMS (2_DSA_011791)', code: '25S2-2_DSA_011791' }, ...]
|
|
18
|
+
const modules = await ps.getModules();
|
|
19
|
+
|
|
20
|
+
const content = await ps.getModuleContent(modules[0].id);
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Terminology
|
|
24
|
+
|
|
25
|
+
There are some differences in terminology between Singapore's Polytechnics / ITE and the underlying APIs. This library follows Polytechnic / ITE terminology.
|
|
26
|
+
|
|
27
|
+
| POLITEShop | Underlying APIs | Definition |
|
|
28
|
+
| ------------------------- | ---------------------------- | ------------------------------------------------------------ |
|
|
29
|
+
| Course | Course | A course of study (e.g. Information Technology). A student can only be in one course. |
|
|
30
|
+
| Module | Enrollment / Course offering | A timetabled subject studied, e.g. Data Structures & Algorithms. |
|
|
31
|
+
| Activity | Activity / Topic | An individual POLITEMall page containing text, media embeds, submission dropboxes, interactive activities, etc. |
|
|
32
|
+
| Top-level activity folder | Module | A "root" group of POLITEMall content. By definition, these are not nested. |
|
|
33
|
+
| Activity folder | Unit | A group of POLITEMall content. These can be nested. |
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { type SirenEntity } from "../schema/siren";
|
|
2
|
+
/**
|
|
3
|
+
* Client for `*.api.brightspace.com`.
|
|
4
|
+
*/
|
|
5
|
+
export declare class Brightspace {
|
|
6
|
+
#private;
|
|
7
|
+
userId: string;
|
|
8
|
+
/** The Brightspace tenant ID (a UUID). */
|
|
9
|
+
tenantId: string;
|
|
10
|
+
/** The expiration date of the current `d2lFetchToken`. */
|
|
11
|
+
tokenExpiry: Date;
|
|
12
|
+
constructor(config: {
|
|
13
|
+
/** Short-lived JWT for Brightspace API authentication. */
|
|
14
|
+
d2lFetchToken: string;
|
|
15
|
+
});
|
|
16
|
+
/**
|
|
17
|
+
* GET /{moduleId}/activity/{topicId}?filterOnDatesAndDepth=0
|
|
18
|
+
*
|
|
19
|
+
* Fetches a Siren entity representing a single activity (topic) in a module.
|
|
20
|
+
* Used for document-embed activities to discover the preview PDF URL.
|
|
21
|
+
*/
|
|
22
|
+
getActivity({ moduleId, topicId, }: {
|
|
23
|
+
moduleId: string;
|
|
24
|
+
topicId: string | number;
|
|
25
|
+
}): Promise<SirenEntity>;
|
|
26
|
+
/**
|
|
27
|
+
* GET /old/activities/{orgId}_2000_{dropboxId}/usages/{moduleId}/users/{userId}
|
|
28
|
+
*
|
|
29
|
+
* Fetches submission information for a dropbox that is **closed** (past its
|
|
30
|
+
* availability end date). The POLITE API returns HTTP 403 for closed
|
|
31
|
+
* dropboxes, so this endpoint is used as a fallback.
|
|
32
|
+
*
|
|
33
|
+
* The constant `2000` in the path is the tool ID for dropbox activities.
|
|
34
|
+
*/
|
|
35
|
+
getClosedDropboxSubmissions({ orgId, dropboxId, moduleId, }: {
|
|
36
|
+
orgId: string;
|
|
37
|
+
dropboxId: string;
|
|
38
|
+
moduleId: string;
|
|
39
|
+
}): Promise<SirenEntity>;
|
|
40
|
+
/**
|
|
41
|
+
* Fetch a submission entity from an **absolute** Brightspace URL.
|
|
42
|
+
*
|
|
43
|
+
* The href values surfaced by {@link getClosedDropboxSubmissions} point to
|
|
44
|
+
* the `assignments.api.brightspace.com` subdomain (or similar). Pass them
|
|
45
|
+
* here directly without modification.
|
|
46
|
+
*/
|
|
47
|
+
getSubmissionDetails({ href }: {
|
|
48
|
+
href: string;
|
|
49
|
+
}): Promise<SirenEntity>;
|
|
50
|
+
/**
|
|
51
|
+
* GET /topics/{moduleId}/{activityId}
|
|
52
|
+
*
|
|
53
|
+
* Fetches thumbnail metadata for a ContentService topic (typically a video).
|
|
54
|
+
* The returned entity contains a `thumbnail` sub-entity whose `properties.src`
|
|
55
|
+
* is a time-limited thumbnail image URL.
|
|
56
|
+
*/
|
|
57
|
+
getTopicThumbnail({ moduleId, activityId, }: {
|
|
58
|
+
moduleId: string;
|
|
59
|
+
activityId: string;
|
|
60
|
+
}): Promise<SirenEntity>;
|
|
61
|
+
/**
|
|
62
|
+
* GET /topics/{moduleId}/{activityId}/media
|
|
63
|
+
*
|
|
64
|
+
* Fetches media metadata for a ContentService topic (typically a video).
|
|
65
|
+
* The returned entity's `properties.src` is a time-limited URL for the
|
|
66
|
+
* video file itself.
|
|
67
|
+
*/
|
|
68
|
+
getTopicMedia({ moduleId, activityId, }: {
|
|
69
|
+
moduleId: string;
|
|
70
|
+
activityId: string;
|
|
71
|
+
}): Promise<SirenEntity>;
|
|
72
|
+
/** Abort all in-flight requests made by this client. */
|
|
73
|
+
abort(): void;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=brightspace.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brightspace.d.ts","sourceRoot":"","sources":["../../clients/brightspace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAIhE;;GAEG;AACH,qBAAa,WAAW;;IACtB,MAAM,EAAE,MAAM,CAAC;IAEf,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;IAEjB,0DAA0D;IAC1D,WAAW,EAAE,IAAI,CAAC;gBAKN,MAAM,EAAE;QAClB,0DAA0D;QAC1D,aAAa,EAAE,MAAM,CAAC;KACvB;IAiBD;;;;;OAKG;IACG,WAAW,CAAC,EAChB,QAAQ,EACR,OAAO,GACR,EAAE;QACD,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;KAC1B,GAAG,OAAO,CAAC,WAAW,CAAC;IASxB;;;;;;;;OAQG;IACG,2BAA2B,CAAC,EAChC,KAAK,EACL,SAAS,EACT,QAAQ,GACT,EAAE;QACD,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC,WAAW,CAAC;IAOxB;;;;;;OAMG;IACG,oBAAoB,CAAC,EAAE,IAAI,EAAE,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,WAAW,CAAC;IAM5E;;;;;;OAMG;IACG,iBAAiB,CAAC,EACtB,QAAQ,EACR,UAAU,GACX,EAAE;QACD,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC,WAAW,CAAC;IAOxB;;;;;;OAMG;IACG,aAAa,CAAC,EAClB,QAAQ,EACR,UAAU,GACX,EAAE;QACD,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC,WAAW,CAAC;IASxB,wDAAwD;IACxD,KAAK,IAAI,IAAI;CAsCd"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { sirenEntity } from "../schema/siren";
|
|
2
|
+
import { decodeJwt } from "jose";
|
|
3
|
+
import z from "zod";
|
|
4
|
+
/**
|
|
5
|
+
* Client for `*.api.brightspace.com`.
|
|
6
|
+
*/
|
|
7
|
+
export class Brightspace {
|
|
8
|
+
userId;
|
|
9
|
+
/** The Brightspace tenant ID (a UUID). */
|
|
10
|
+
tenantId;
|
|
11
|
+
/** The expiration date of the current `d2lFetchToken`. */
|
|
12
|
+
tokenExpiry;
|
|
13
|
+
#d2lFetchToken;
|
|
14
|
+
#abortController = new AbortController();
|
|
15
|
+
constructor(config) {
|
|
16
|
+
this.#d2lFetchToken = config.d2lFetchToken;
|
|
17
|
+
const { sub, exp, tenantid } = z
|
|
18
|
+
.object({
|
|
19
|
+
tenantid: z.string(),
|
|
20
|
+
sub: z.string(),
|
|
21
|
+
exp: z.date({ coerce: true }),
|
|
22
|
+
})
|
|
23
|
+
.parse(decodeJwt(this.#d2lFetchToken));
|
|
24
|
+
this.userId = sub;
|
|
25
|
+
this.tokenExpiry = exp;
|
|
26
|
+
this.tenantId = tenantid;
|
|
27
|
+
}
|
|
28
|
+
// ── Sequences API (sequences.api.brightspace.com) ────────────────────────────
|
|
29
|
+
/**
|
|
30
|
+
* GET /{moduleId}/activity/{topicId}?filterOnDatesAndDepth=0
|
|
31
|
+
*
|
|
32
|
+
* Fetches a Siren entity representing a single activity (topic) in a module.
|
|
33
|
+
* Used for document-embed activities to discover the preview PDF URL.
|
|
34
|
+
*/
|
|
35
|
+
async getActivity({ moduleId, topicId, }) {
|
|
36
|
+
return this.#fetchSiren(`/${moduleId}/activity/${topicId}?filterOnDatesAndDepth=0`, "sequences");
|
|
37
|
+
}
|
|
38
|
+
// ── Activities API (activities.api.brightspace.com) ──────────────────────────
|
|
39
|
+
/**
|
|
40
|
+
* GET /old/activities/{orgId}_2000_{dropboxId}/usages/{moduleId}/users/{userId}
|
|
41
|
+
*
|
|
42
|
+
* Fetches submission information for a dropbox that is **closed** (past its
|
|
43
|
+
* availability end date). The POLITE API returns HTTP 403 for closed
|
|
44
|
+
* dropboxes, so this endpoint is used as a fallback.
|
|
45
|
+
*
|
|
46
|
+
* The constant `2000` in the path is the tool ID for dropbox activities.
|
|
47
|
+
*/
|
|
48
|
+
async getClosedDropboxSubmissions({ orgId, dropboxId, moduleId, }) {
|
|
49
|
+
return this.#fetchSiren(`/old/activities/${orgId}_2000_${dropboxId}/usages/${moduleId}/users/${this.userId}`, "activities");
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Fetch a submission entity from an **absolute** Brightspace URL.
|
|
53
|
+
*
|
|
54
|
+
* The href values surfaced by {@link getClosedDropboxSubmissions} point to
|
|
55
|
+
* the `assignments.api.brightspace.com` subdomain (or similar). Pass them
|
|
56
|
+
* here directly without modification.
|
|
57
|
+
*/
|
|
58
|
+
async getSubmissionDetails({ href }) {
|
|
59
|
+
return this.#fetchSiren(href);
|
|
60
|
+
}
|
|
61
|
+
// ── Content Service API (content-service.api.brightspace.com) ────────────────
|
|
62
|
+
/**
|
|
63
|
+
* GET /topics/{moduleId}/{activityId}
|
|
64
|
+
*
|
|
65
|
+
* Fetches thumbnail metadata for a ContentService topic (typically a video).
|
|
66
|
+
* The returned entity contains a `thumbnail` sub-entity whose `properties.src`
|
|
67
|
+
* is a time-limited thumbnail image URL.
|
|
68
|
+
*/
|
|
69
|
+
async getTopicThumbnail({ moduleId, activityId, }) {
|
|
70
|
+
return this.#fetchSiren(`/topics/${moduleId}/${activityId}`, "content-service");
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* GET /topics/{moduleId}/{activityId}/media
|
|
74
|
+
*
|
|
75
|
+
* Fetches media metadata for a ContentService topic (typically a video).
|
|
76
|
+
* The returned entity's `properties.src` is a time-limited URL for the
|
|
77
|
+
* video file itself.
|
|
78
|
+
*/
|
|
79
|
+
async getTopicMedia({ moduleId, activityId, }) {
|
|
80
|
+
return this.#fetchSiren(`/topics/${moduleId}/${activityId}/media`, "content-service");
|
|
81
|
+
}
|
|
82
|
+
// ── Lifecycle ─────────────────────────────────────────────────────────────────
|
|
83
|
+
/** Abort all in-flight requests made by this client. */
|
|
84
|
+
abort() {
|
|
85
|
+
this.#abortController.abort();
|
|
86
|
+
}
|
|
87
|
+
async #fetchSiren(pathOrURL, api) {
|
|
88
|
+
const url = api
|
|
89
|
+
? new URL(pathOrURL, `https://${this.tenantId}.${api}.api.brightspace.com`)
|
|
90
|
+
: new URL(pathOrURL);
|
|
91
|
+
const res = await fetch(url, {
|
|
92
|
+
headers: { Authorization: `Bearer ${this.#d2lFetchToken}` },
|
|
93
|
+
signal: this.#abortController.signal,
|
|
94
|
+
});
|
|
95
|
+
if (!res.ok) {
|
|
96
|
+
const body = res.headers.get("content-type")?.includes("json")
|
|
97
|
+
? JSON.stringify(await res.json())
|
|
98
|
+
: await res.text();
|
|
99
|
+
throw new Error(`Brightspace API error ${res.status} at ${url}: ${body}`);
|
|
100
|
+
}
|
|
101
|
+
return sirenEntity.parse(await res.json());
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=brightspace.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brightspace.js","sourceRoot":"","sources":["../../clients/brightspace.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import * as schema from "../schema/polite";
|
|
2
|
+
/**
|
|
3
|
+
* Low-level client for the POLITE API (`*.polite.edu.sg`).
|
|
4
|
+
*
|
|
5
|
+
* Handles authentication via D2L session cookies and exposes one method per
|
|
6
|
+
* API endpoint. All response bodies are validated with Zod schemas before
|
|
7
|
+
* being returned, so callers receive fully-typed data.
|
|
8
|
+
*/
|
|
9
|
+
export declare class POLITE {
|
|
10
|
+
#private;
|
|
11
|
+
constructor(config: {
|
|
12
|
+
/** `d2lSessionVal` cookie value. */
|
|
13
|
+
d2lSessionVal: string;
|
|
14
|
+
/** `d2lSecureSessionVal` cookie value. */
|
|
15
|
+
d2lSecureSessionVal: string;
|
|
16
|
+
/** Subdomain of the POLITEMall site (e.g. `"nplms"`). */
|
|
17
|
+
domain: string;
|
|
18
|
+
});
|
|
19
|
+
get baseURL(): string;
|
|
20
|
+
/**
|
|
21
|
+
* GET /d2l/lp/auth/oauth2/token
|
|
22
|
+
*
|
|
23
|
+
* Exchanges the current D2L session cookies for a short-lived Brightspace
|
|
24
|
+
* JWT that can be used with the `*.api.brightspace.com` APIs.
|
|
25
|
+
*/
|
|
26
|
+
getNewFetchToken(): Promise<schema.BrightspaceToken>;
|
|
27
|
+
/**
|
|
28
|
+
* GET /d2l/api/lp/1.0/users/whoami
|
|
29
|
+
*
|
|
30
|
+
* Returns basic information about the currently authenticated user.
|
|
31
|
+
*/
|
|
32
|
+
whoAmI(): Promise<schema.WhoAmIUser>;
|
|
33
|
+
/**
|
|
34
|
+
* GET /d2l/api/lp/1.46/organization/info
|
|
35
|
+
*
|
|
36
|
+
* Returns information about the organisation (institution).
|
|
37
|
+
*/
|
|
38
|
+
getOrganizationInfo(): Promise<schema.Organization>;
|
|
39
|
+
/**
|
|
40
|
+
* GET /d2l/api/lp/1.46/enrollments/myenrollments/
|
|
41
|
+
*
|
|
42
|
+
* Returns one page of the authenticated user's org-unit enrollments.
|
|
43
|
+
* Pass `bookmark` to fetch subsequent pages.
|
|
44
|
+
*/
|
|
45
|
+
getMyEnrollments({ bookmark }?: {
|
|
46
|
+
bookmark?: string;
|
|
47
|
+
}): Promise<schema.PagedResultSet<schema.MyOrgUnitInfo>>;
|
|
48
|
+
/**
|
|
49
|
+
* GET /d2l/api/lp/1.46/courses/parentorgunits?orgUnitIdsCSV=…
|
|
50
|
+
*
|
|
51
|
+
* Returns semester/department parent information for up to 25 course-offering
|
|
52
|
+
* org units at a time.
|
|
53
|
+
*/
|
|
54
|
+
getParentOrgUnits({ orgUnitIdsCSV, }: {
|
|
55
|
+
orgUnitIdsCSV: string;
|
|
56
|
+
}): Promise<schema.CourseParent[]>;
|
|
57
|
+
/**
|
|
58
|
+
* GET /d2l/api/le/1.75/{moduleId}/content/toc
|
|
59
|
+
*
|
|
60
|
+
* Returns the table of contents for a module, including all nested folders
|
|
61
|
+
* (Modules) and topics (Activities).
|
|
62
|
+
*/
|
|
63
|
+
getModuleTOC({ moduleId, }: {
|
|
64
|
+
moduleId: string;
|
|
65
|
+
}): Promise<schema.TableOfContents>;
|
|
66
|
+
/**
|
|
67
|
+
* Fetch a content URL as raw text (used for HTML-type activities).
|
|
68
|
+
*
|
|
69
|
+
* `urlOrPath` may be either an absolute URL or a path relative to
|
|
70
|
+
* `this.baseURL`.
|
|
71
|
+
*/
|
|
72
|
+
getContentHTML({ urlOrPath }: {
|
|
73
|
+
urlOrPath: string;
|
|
74
|
+
}): Promise<string>;
|
|
75
|
+
/**
|
|
76
|
+
* Return the image URL for a module or organisation.
|
|
77
|
+
*/
|
|
78
|
+
getImageURL(id: string, dim?: {
|
|
79
|
+
width: number;
|
|
80
|
+
height: number;
|
|
81
|
+
}): string;
|
|
82
|
+
/**
|
|
83
|
+
* GET /d2l/api/le/1.75/{moduleId}/dropbox/folders/
|
|
84
|
+
*
|
|
85
|
+
* Returns all submission dropbox folders in a module.
|
|
86
|
+
*/
|
|
87
|
+
getDropboxFolders({ moduleId, }: {
|
|
88
|
+
moduleId: string;
|
|
89
|
+
}): Promise<schema.DropboxFolder[]>;
|
|
90
|
+
/**
|
|
91
|
+
* GET /d2l/api/le/1.75/{moduleId}/dropbox/folders/{dropboxId}/submissions/
|
|
92
|
+
*
|
|
93
|
+
* Returns the current user's submissions for a dropbox. Returns at most one
|
|
94
|
+
* `EntityDropbox` object (the user's own entry).
|
|
95
|
+
*
|
|
96
|
+
* > **Note:** This endpoint returns HTTP 403 for dropboxes whose availability
|
|
97
|
+
* > window has closed. In that case, use the Brightspace Activities API
|
|
98
|
+
* > instead (`Brightspace.getClosedDropboxSubmissions`).
|
|
99
|
+
*/
|
|
100
|
+
getDropboxSubmissions({ moduleId, dropboxId, }: {
|
|
101
|
+
moduleId: string;
|
|
102
|
+
dropboxId: string;
|
|
103
|
+
}): Promise<schema.EntityDropbox[]>;
|
|
104
|
+
/**
|
|
105
|
+
* Fetch one page of quizzes from the given URL or path.
|
|
106
|
+
*
|
|
107
|
+
* For the first page, pass `/d2l/api/le/1.75/{moduleId}/quizzes/`.
|
|
108
|
+
* The `Next` field of the returned object is the URL for the next page
|
|
109
|
+
* (or `null` if there are no more pages).
|
|
110
|
+
*/
|
|
111
|
+
getQuizzesPage({ urlOrPath, }: {
|
|
112
|
+
urlOrPath: string;
|
|
113
|
+
}): Promise<schema.ObjectListPage<schema.QuizReadData>>;
|
|
114
|
+
/** Abort all in-flight requests made by this client. */
|
|
115
|
+
abort(): void;
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=polite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"polite.d.ts","sourceRoot":"","sources":["../../clients/polite.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,MAAM,kBAAkB,CAAC;AAM3C;;;;;;GAMG;AACH,qBAAa,MAAM;;gBAML,MAAM,EAAE;QAClB,oCAAoC;QACpC,aAAa,EAAE,MAAM,CAAC;QACtB,0CAA0C;QAC1C,mBAAmB,EAAE,MAAM,CAAC;QAC5B,yDAAyD;QACzD,MAAM,EAAE,MAAM,CAAC;KAChB;IAMD,IAAI,OAAO,IAAI,MAAM,CAEpB;IAID;;;;;OAKG;IACG,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC;IAqB1D;;;;OAIG;IACG,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC;IAQ1C;;;;OAIG;IACG,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC;IAQzD;;;;;OAKG;IACG,gBAAgB,CAAC,EAAE,QAAQ,EAAE,GAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CACvE,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,aAAa,CAAC,CAC5C;IAQD;;;;;OAKG;IACG,iBAAiB,CAAC,EACtB,aAAa,GACd,EAAE;QACD,aAAa,EAAE,MAAM,CAAC;KACvB,GAAG,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;IASlC;;;;;OAKG;IACG,YAAY,CAAC,EACjB,QAAQ,GACT,EAAE;QACD,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC,MAAM,CAAC,eAAe,CAAC;IAMnC;;;;;OAKG;IACG,cAAc,CAAC,EAAE,SAAS,EAAE,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAI3E;;OAEG;IACH,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM;IAOxE;;;;OAIG;IACG,iBAAiB,CAAC,EACtB,QAAQ,GACT,EAAE;QACD,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC;IAMnC;;;;;;;;;OASG;IACG,qBAAqB,CAAC,EAC1B,QAAQ,EACR,SAAS,GACV,EAAE;QACD,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC;IASnC;;;;;;OAMG;IACG,cAAc,CAAC,EACnB,SAAS,GACV,EAAE;QACD,SAAS,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IAQvD,wDAAwD;IACxD,KAAK,IAAI,IAAI;CA4Dd"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import * as schema from "../schema/polite";
|
|
2
|
+
import { defaultToBaseURL } from "../utils/url";
|
|
3
|
+
import { UnexpectedResponseError } from "../errors";
|
|
4
|
+
// TODO: /d2l/api/le/manageCourses/courses-searches/490586/BySemester
|
|
5
|
+
/**
|
|
6
|
+
* Low-level client for the POLITE API (`*.polite.edu.sg`).
|
|
7
|
+
*
|
|
8
|
+
* Handles authentication via D2L session cookies and exposes one method per
|
|
9
|
+
* API endpoint. All response bodies are validated with Zod schemas before
|
|
10
|
+
* being returned, so callers receive fully-typed data.
|
|
11
|
+
*/
|
|
12
|
+
export class POLITE {
|
|
13
|
+
#d2lSessionVal;
|
|
14
|
+
#d2lSecureSessionVal;
|
|
15
|
+
#domain;
|
|
16
|
+
#abortController = new AbortController();
|
|
17
|
+
constructor(config) {
|
|
18
|
+
this.#domain = config.domain;
|
|
19
|
+
this.#d2lSessionVal = config.d2lSessionVal;
|
|
20
|
+
this.#d2lSecureSessionVal = config.d2lSecureSessionVal;
|
|
21
|
+
}
|
|
22
|
+
get baseURL() {
|
|
23
|
+
return `https://${this.#domain}.polite.edu.sg`;
|
|
24
|
+
}
|
|
25
|
+
// ── Auth ────────────────────────────────────────────────────────────────────
|
|
26
|
+
/**
|
|
27
|
+
* GET /d2l/lp/auth/oauth2/token
|
|
28
|
+
*
|
|
29
|
+
* Exchanges the current D2L session cookies for a short-lived Brightspace
|
|
30
|
+
* JWT that can be used with the `*.api.brightspace.com` APIs.
|
|
31
|
+
*/
|
|
32
|
+
async getNewFetchToken() {
|
|
33
|
+
// Fetch the homepage HTML to extract the XSRF token from it
|
|
34
|
+
const homepage = await this.#fetchText("/d2l/home");
|
|
35
|
+
const xsrfToken = homepage.match(/\.setItem\(['"]XSRF.Token['"],\s*['"](.+?)['"]\)/)?.[1];
|
|
36
|
+
if (!xsrfToken) {
|
|
37
|
+
throw new UnexpectedResponseError("No XSRF token found in homepage");
|
|
38
|
+
}
|
|
39
|
+
return this.#fetchJSON("/d2l/lp/auth/oauth2/token", {
|
|
40
|
+
method: "POST",
|
|
41
|
+
headers: { "X-Csrf-Token": xsrfToken },
|
|
42
|
+
body: new URLSearchParams({ scope: "*:*:*" }),
|
|
43
|
+
schema: schema.brightspaceToken,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
// ── Users ───────────────────────────────────────────────────────────────────
|
|
47
|
+
/**
|
|
48
|
+
* GET /d2l/api/lp/1.0/users/whoami
|
|
49
|
+
*
|
|
50
|
+
* Returns basic information about the currently authenticated user.
|
|
51
|
+
*/
|
|
52
|
+
async whoAmI() {
|
|
53
|
+
return this.#fetchJSON("/d2l/api/lp/1.0/users/whoami", {
|
|
54
|
+
schema: schema.whoAmIUser,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
// ── Organization ────────────────────────────────────────────────────────────
|
|
58
|
+
/**
|
|
59
|
+
* GET /d2l/api/lp/1.46/organization/info
|
|
60
|
+
*
|
|
61
|
+
* Returns information about the organisation (institution).
|
|
62
|
+
*/
|
|
63
|
+
async getOrganizationInfo() {
|
|
64
|
+
return this.#fetchJSON("/d2l/api/lp/1.46/organization/info", {
|
|
65
|
+
schema: schema.organization,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
// ── Enrollments ─────────────────────────────────────────────────────────────
|
|
69
|
+
/**
|
|
70
|
+
* GET /d2l/api/lp/1.46/enrollments/myenrollments/
|
|
71
|
+
*
|
|
72
|
+
* Returns one page of the authenticated user's org-unit enrollments.
|
|
73
|
+
* Pass `bookmark` to fetch subsequent pages.
|
|
74
|
+
*/
|
|
75
|
+
async getMyEnrollments({ bookmark } = {}) {
|
|
76
|
+
const query = bookmark ? `?bookmark=${encodeURIComponent(bookmark)}` : "";
|
|
77
|
+
return this.#fetchJSON(`/d2l/api/lp/1.46/enrollments/myenrollments/${query}`, { schema: schema.pagedResultSet(schema.myOrgUnitInfo) });
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* GET /d2l/api/lp/1.46/courses/parentorgunits?orgUnitIdsCSV=…
|
|
81
|
+
*
|
|
82
|
+
* Returns semester/department parent information for up to 25 course-offering
|
|
83
|
+
* org units at a time.
|
|
84
|
+
*/
|
|
85
|
+
async getParentOrgUnits({ orgUnitIdsCSV, }) {
|
|
86
|
+
return this.#fetchJSON(`/d2l/api/lp/1.46/courses/parentorgunits?orgUnitIdsCSV=${encodeURIComponent(orgUnitIdsCSV)}`, { schema: schema.courseParent.array() });
|
|
87
|
+
}
|
|
88
|
+
// ── Content ─────────────────────────────────────────────────────────────────
|
|
89
|
+
/**
|
|
90
|
+
* GET /d2l/api/le/1.75/{moduleId}/content/toc
|
|
91
|
+
*
|
|
92
|
+
* Returns the table of contents for a module, including all nested folders
|
|
93
|
+
* (Modules) and topics (Activities).
|
|
94
|
+
*/
|
|
95
|
+
async getModuleTOC({ moduleId, }) {
|
|
96
|
+
return this.#fetchJSON(`/d2l/api/le/1.75/${moduleId}/content/toc`, {
|
|
97
|
+
schema: schema.tableOfContents,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Fetch a content URL as raw text (used for HTML-type activities).
|
|
102
|
+
*
|
|
103
|
+
* `urlOrPath` may be either an absolute URL or a path relative to
|
|
104
|
+
* `this.baseURL`.
|
|
105
|
+
*/
|
|
106
|
+
async getContentHTML({ urlOrPath }) {
|
|
107
|
+
return this.#fetchText(urlOrPath);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Return the image URL for a module or organisation.
|
|
111
|
+
*/
|
|
112
|
+
getImageURL(id, dim) {
|
|
113
|
+
const query = dim ? `?width=${dim.width}&height=${dim.height}` : "";
|
|
114
|
+
return `${this.baseURL}/d2l/api/lp/1.46/courses/${id}/image${query}`;
|
|
115
|
+
}
|
|
116
|
+
// ── Dropbox / Submissions ────────────────────────────────────────────────────
|
|
117
|
+
/**
|
|
118
|
+
* GET /d2l/api/le/1.75/{moduleId}/dropbox/folders/
|
|
119
|
+
*
|
|
120
|
+
* Returns all submission dropbox folders in a module.
|
|
121
|
+
*/
|
|
122
|
+
async getDropboxFolders({ moduleId, }) {
|
|
123
|
+
return this.#fetchJSON(`/d2l/api/le/1.75/${moduleId}/dropbox/folders/`, {
|
|
124
|
+
schema: schema.dropboxFolder.array(),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* GET /d2l/api/le/1.75/{moduleId}/dropbox/folders/{dropboxId}/submissions/
|
|
129
|
+
*
|
|
130
|
+
* Returns the current user's submissions for a dropbox. Returns at most one
|
|
131
|
+
* `EntityDropbox` object (the user's own entry).
|
|
132
|
+
*
|
|
133
|
+
* > **Note:** This endpoint returns HTTP 403 for dropboxes whose availability
|
|
134
|
+
* > window has closed. In that case, use the Brightspace Activities API
|
|
135
|
+
* > instead (`Brightspace.getClosedDropboxSubmissions`).
|
|
136
|
+
*/
|
|
137
|
+
async getDropboxSubmissions({ moduleId, dropboxId, }) {
|
|
138
|
+
return this.#fetchJSON(`/d2l/api/le/1.75/${moduleId}/dropbox/folders/${dropboxId}/submissions/`, { schema: schema.entityDropbox.array().max(1) });
|
|
139
|
+
}
|
|
140
|
+
// ── Quizzes ──────────────────────────────────────────────────────────────────
|
|
141
|
+
/**
|
|
142
|
+
* Fetch one page of quizzes from the given URL or path.
|
|
143
|
+
*
|
|
144
|
+
* For the first page, pass `/d2l/api/le/1.75/{moduleId}/quizzes/`.
|
|
145
|
+
* The `Next` field of the returned object is the URL for the next page
|
|
146
|
+
* (or `null` if there are no more pages).
|
|
147
|
+
*/
|
|
148
|
+
async getQuizzesPage({ urlOrPath, }) {
|
|
149
|
+
return this.#fetchJSON(urlOrPath, {
|
|
150
|
+
schema: schema.objectListPage(schema.quizReadData),
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
// ── Lifecycle ────────────────────────────────────────────────────────────────
|
|
154
|
+
/** Abort all in-flight requests made by this client. */
|
|
155
|
+
abort() {
|
|
156
|
+
this.#abortController.abort();
|
|
157
|
+
}
|
|
158
|
+
async #fetchJSON(input, init) {
|
|
159
|
+
const res = await this.#fetch(input, init);
|
|
160
|
+
if (!res.ok) {
|
|
161
|
+
throw new UnexpectedResponseError(`Received ${res.status} for ${defaultToBaseURL(input, this.baseURL)}`, { response: res });
|
|
162
|
+
}
|
|
163
|
+
const data = await res.json();
|
|
164
|
+
if (init?.schema) {
|
|
165
|
+
return init.schema.parse(data);
|
|
166
|
+
}
|
|
167
|
+
return data;
|
|
168
|
+
}
|
|
169
|
+
async #fetchText(urlOrPath) {
|
|
170
|
+
const res = await this.#fetch(urlOrPath);
|
|
171
|
+
if (!res.ok) {
|
|
172
|
+
throw new Error(`POLITE API error ${res.status} at ${urlOrPath}`);
|
|
173
|
+
}
|
|
174
|
+
return res.text();
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* {@link fetch} wrapper that:
|
|
178
|
+
* - Defaults to {@link baseURL} as the base URL.
|
|
179
|
+
* - Adds authentication cookies.
|
|
180
|
+
*/
|
|
181
|
+
async #fetch(input, init) {
|
|
182
|
+
const headers = new Headers(init?.headers);
|
|
183
|
+
headers.set("Cookie", `d2lSessionVal=${this.#d2lSessionVal}; d2lSecureSessionVal=${this.#d2lSecureSessionVal}`);
|
|
184
|
+
return fetch(defaultToBaseURL(input, this.baseURL), {
|
|
185
|
+
...init,
|
|
186
|
+
headers,
|
|
187
|
+
signal: this.#abortController.signal,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=polite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"polite.js","sourceRoot":"","sources":["../../clients/polite.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { ActivityFolder, Module, Institution, Quiz, Semester, SubmissionDropbox, User, Submission } from "../types";
|
|
2
|
+
import { Brightspace } from "./brightspace";
|
|
3
|
+
import { POLITE } from "./polite";
|
|
4
|
+
/**
|
|
5
|
+
* High-level POLITEMall client.
|
|
6
|
+
*
|
|
7
|
+
* Composes {@link POLITE} (for `*.polite.edu.sg` APIs) and {@link Brightspace}
|
|
8
|
+
* (for `*.api.brightspace.com` APIs) into a single, convenient interface.
|
|
9
|
+
*
|
|
10
|
+
* Both lower-level clients are accessible so callers can reach endpoints not
|
|
11
|
+
* covered by the high-level methods.
|
|
12
|
+
*/
|
|
13
|
+
export declare class POLITEShop {
|
|
14
|
+
#private;
|
|
15
|
+
/** Client for `*.polite.edu.sg` APIs. */
|
|
16
|
+
readonly polite: POLITE;
|
|
17
|
+
/**
|
|
18
|
+
* Initialize the underlying {@link POLITE} client. If `d2lFetchToken` is
|
|
19
|
+
* provided, the {@link Brightspace} client is also initialized immediately.
|
|
20
|
+
* Otherwise, it is initialized lazily via the {@link brightspace} getter.
|
|
21
|
+
*/
|
|
22
|
+
constructor(config: {
|
|
23
|
+
/** `d2lSessionVal` cookie value. */
|
|
24
|
+
d2lSessionVal: string;
|
|
25
|
+
/** `d2lSecureSessionVal` cookie value. */
|
|
26
|
+
d2lSecureSessionVal: string;
|
|
27
|
+
/** Subdomain of the POLITEMall site (e.g. `"nplms"`). */
|
|
28
|
+
domain: string;
|
|
29
|
+
/**
|
|
30
|
+
* Brightspace JWT found in localStorage. If not provided,
|
|
31
|
+
* it will be fetched automatically the first time it is needed.
|
|
32
|
+
*/
|
|
33
|
+
d2lFetchToken?: string;
|
|
34
|
+
});
|
|
35
|
+
/**
|
|
36
|
+
* Returns the initialized {@link Brightspace} client, fetching or refreshing
|
|
37
|
+
* the `d2lFetchToken` automatically when needed.
|
|
38
|
+
*
|
|
39
|
+
* Implemented as a Promise-returning getter rather than a plain `Brightspace`
|
|
40
|
+
* property because the token may be unavailable or expired at access time,
|
|
41
|
+
* requiring an asynchronous network request to obtain a fresh one.
|
|
42
|
+
*/
|
|
43
|
+
get brightspace(): Promise<Brightspace>;
|
|
44
|
+
/** Fetch the user's ID and display name. */
|
|
45
|
+
getUser(): Promise<User>;
|
|
46
|
+
/** Fetch information about the educational institution the user is in. */
|
|
47
|
+
getInstitution(): Promise<Institution>;
|
|
48
|
+
/**
|
|
49
|
+
* Return the URL of the institution's image. The institution's ID is needed to
|
|
50
|
+
* construct the URL, so if not provided, this calls {@link getInstitution}.
|
|
51
|
+
* The URL construction itself is synchronous. */
|
|
52
|
+
getInstitutionImageURL(config: {
|
|
53
|
+
width: number;
|
|
54
|
+
height: number;
|
|
55
|
+
}): Promise<string>;
|
|
56
|
+
getInstitutionImageURL(config: {
|
|
57
|
+
width: number;
|
|
58
|
+
height: number;
|
|
59
|
+
institutionId: string;
|
|
60
|
+
}): string;
|
|
61
|
+
/**
|
|
62
|
+
* Fetch the modules the user is enrolled in.
|
|
63
|
+
*/
|
|
64
|
+
getModules(): Promise<Module[]>;
|
|
65
|
+
/**
|
|
66
|
+
* Fetch all modules the user is enrolled in, together with the associated semesters.
|
|
67
|
+
*/
|
|
68
|
+
getModulesAndSemesters(): Promise<{
|
|
69
|
+
modules: (Module & {
|
|
70
|
+
semesterId: string;
|
|
71
|
+
})[];
|
|
72
|
+
semesters: Semester[];
|
|
73
|
+
}>;
|
|
74
|
+
/**
|
|
75
|
+
* Fetch all activity folders and activities for a module.
|
|
76
|
+
*
|
|
77
|
+
* Retrieves the table of contents then recursively parses every folder and
|
|
78
|
+
* topic, making additional requests as needed to resolve activity details.
|
|
79
|
+
*/
|
|
80
|
+
getModuleContent({ moduleId, }: {
|
|
81
|
+
moduleId: string;
|
|
82
|
+
}): Promise<ActivityFolder[]>;
|
|
83
|
+
/** Fetch all submission dropbox folders in a module. */
|
|
84
|
+
getSubmissionDropboxes({ moduleId, }: {
|
|
85
|
+
moduleId: string;
|
|
86
|
+
}): Promise<SubmissionDropbox[]>;
|
|
87
|
+
/**
|
|
88
|
+
* Fetch the authenticated user's submissions for a given dropbox.
|
|
89
|
+
*
|
|
90
|
+
* Two strategies are attempted in order:
|
|
91
|
+
*
|
|
92
|
+
* 1. **POLITE API** — A single request returns all submissions. This is the
|
|
93
|
+
* preferred path but returns HTTP 403 for dropboxes whose availability
|
|
94
|
+
* window has closed.
|
|
95
|
+
* 2. **Brightspace Activities API** — Used as a fallback when the POLITE API
|
|
96
|
+
* fails. Requires one additional request per submission. If `organizationId`
|
|
97
|
+
* is not provided, it is fetched automatically via {@link getInstitution}.
|
|
98
|
+
*
|
|
99
|
+
* @param params.moduleId - The module (course offering) ID.
|
|
100
|
+
* @param params.dropboxId - The dropbox folder ID.
|
|
101
|
+
* @param params.organizationId - The organisation ID, used by the Brightspace
|
|
102
|
+
* Activities API path. If omitted and the fallback is needed, it is fetched
|
|
103
|
+
* automatically via {@link getInstitution}.
|
|
104
|
+
*/
|
|
105
|
+
getSubmissions({ moduleId, dropboxId, organizationId, }: {
|
|
106
|
+
moduleId: string;
|
|
107
|
+
dropboxId: string;
|
|
108
|
+
organizationId?: string;
|
|
109
|
+
}): Promise<Submission[]>;
|
|
110
|
+
/**
|
|
111
|
+
* Fetch all quizzes in a module.
|
|
112
|
+
*/
|
|
113
|
+
getQuizzes({ moduleId }: {
|
|
114
|
+
moduleId: string;
|
|
115
|
+
}): Promise<Quiz[]>;
|
|
116
|
+
/** Abort all in-flight requests on both the POLITE and Brightspace clients. */
|
|
117
|
+
abort(): void;
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=politeshop.d.ts.map
|