mirror of
https://github.com/classchartsapi/classcharts-api-js.git
synced 2026-05-14 11:58:13 +00:00
feat: denoify (#33)
This commit is contained in:
parent
f2981dd167
commit
f86ded2ae3
31 changed files with 331 additions and 4940 deletions
|
|
@ -15,8 +15,8 @@ import type {
|
|||
GetStudentInfoResponse,
|
||||
HomeworksResponse,
|
||||
LessonsResponse,
|
||||
} from "../types.js";
|
||||
import { PING_INTERVAL } from "../utils/consts.js";
|
||||
} from "../types.ts";
|
||||
import { PING_INTERVAL } from "../utils/consts.ts";
|
||||
|
||||
/**
|
||||
* Shared client for both parent and student. This is not exported and should not be used directly
|
||||
|
|
@ -46,7 +46,6 @@ export class BaseClient {
|
|||
*/
|
||||
protected API_BASE = "";
|
||||
/**
|
||||
*
|
||||
* @param API_BASE Base API URL, this is different depending on if its called as a parent or student
|
||||
*/
|
||||
constructor(API_BASE: string) {
|
||||
|
|
@ -113,7 +112,7 @@ export class BaseClient {
|
|||
let responseJSON: ClassChartsResponse<unknown, unknown>;
|
||||
try {
|
||||
responseJSON = await request.json();
|
||||
} catch (err) {
|
||||
} catch {
|
||||
throw new Error(
|
||||
"Error parsing JSON. Returned response: " + (await request.text())
|
||||
);
|
||||
|
|
@ -121,8 +120,8 @@ export class BaseClient {
|
|||
if (responseJSON.success == 0) {
|
||||
throw new Error(responseJSON.error);
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return responseJSON as any;
|
||||
// deno-lint-ignore no-explicit-any
|
||||
return responseJSON as unknown as any;
|
||||
}
|
||||
/**
|
||||
* Gets general information about the current student
|
||||
|
|
@ -150,7 +149,7 @@ export class BaseClient {
|
|||
options?.from && params.append("from", options?.from);
|
||||
options?.to && params.append("to", options?.to);
|
||||
options?.last_id && params.append("last_id", options?.last_id);
|
||||
return this.makeAuthedRequest(
|
||||
return await this.makeAuthedRequest(
|
||||
this.API_BASE + "/activity/" + this.studentId + "?" + params.toString(),
|
||||
{
|
||||
method: "GET",
|
||||
|
|
@ -214,8 +213,9 @@ export class BaseClient {
|
|||
*/
|
||||
async getHomeworks(options?: GetHomeworkOptions): Promise<HomeworksResponse> {
|
||||
const params = new URLSearchParams();
|
||||
if (options?.displayDate)
|
||||
if (options?.displayDate) {
|
||||
params.append("display_date", String(options?.displayDate));
|
||||
}
|
||||
|
||||
options?.from && params.append("from", String(options?.from));
|
||||
options?.to && params.append("to", String(options?.to));
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import type { GetPupilsResponse } from "../types.js";
|
||||
import type { GetPupilsResponse } from "../types.ts";
|
||||
|
||||
import { BaseClient } from "./baseClient.js";
|
||||
import { API_BASE_PARENT, BASE_URL } from "../utils/consts.js";
|
||||
import { parseCookies } from "../utils/utils.js";
|
||||
import { BaseClient } from "./baseClient.ts";
|
||||
import { API_BASE_PARENT, BASE_URL } from "../utils/consts.ts";
|
||||
import { parseCookies } from "../utils/utils.ts";
|
||||
/**
|
||||
* Parent Client
|
||||
*/
|
||||
|
|
@ -12,7 +12,6 @@ export class ParentClient extends BaseClient {
|
|||
// @ts-expect-error Init in .login
|
||||
public pupils: GetPupilsResponse;
|
||||
/**
|
||||
*
|
||||
* @param email Parent's email address
|
||||
* @param password Parent's password
|
||||
*/
|
||||
|
|
@ -26,7 +25,7 @@ export class ParentClient extends BaseClient {
|
|||
* Authenticates with ClassCharts
|
||||
*/
|
||||
async login(): Promise<void> {
|
||||
if (!this.email) throw new Error("Email not inputted");
|
||||
if (!this.email) throw new Error("Email not provided");
|
||||
const formData = new URLSearchParams();
|
||||
formData.append("_method", "POST");
|
||||
formData.append("email", this.email);
|
||||
|
|
@ -42,13 +41,14 @@ export class ParentClient extends BaseClient {
|
|||
headers: headers,
|
||||
credentials: undefined,
|
||||
});
|
||||
if (response.status != 302 || !response.headers.get("set-cookie"))
|
||||
if (response.status != 302 || !response.headers.get("set-cookie")) {
|
||||
throw new Error(
|
||||
"Unauthenticated: ClassCharts returned an error: " +
|
||||
response.status +
|
||||
" " +
|
||||
response.statusText
|
||||
);
|
||||
}
|
||||
|
||||
const cookies = String(response.headers.get("set-cookie"));
|
||||
// this.authCookies = cookies.split(";");
|
||||
|
|
@ -66,7 +66,7 @@ export class ParentClient extends BaseClient {
|
|||
* @returns an array of Pupils connected to this parent's account
|
||||
*/
|
||||
async getPupils(): Promise<GetPupilsResponse> {
|
||||
return super.makeAuthedRequest(super.API_BASE + "/pupils", {
|
||||
return await super.makeAuthedRequest(super.API_BASE + "/pupils", {
|
||||
method: "GET",
|
||||
});
|
||||
}
|
||||
|
|
@ -76,7 +76,7 @@ export class ParentClient extends BaseClient {
|
|||
*
|
||||
* @see getPupils
|
||||
*/
|
||||
async selectPupil(pupilId: number): Promise<void> {
|
||||
selectPupil(pupilId: number) {
|
||||
if (!pupilId) throw new Error("No pupil ID specified");
|
||||
const pupils = this.pupils;
|
||||
for (let i = 0; i < pupils.length; i++) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { API_BASE_STUDENT, BASE_URL } from "../utils/consts.js";
|
||||
import { BaseClient } from "./baseClient.js";
|
||||
import { parseCookies } from "../utils/utils.js";
|
||||
import { API_BASE_STUDENT, BASE_URL } from "../utils/consts.ts";
|
||||
import { BaseClient } from "./baseClient.ts";
|
||||
import { parseCookies } from "../utils/utils.ts";
|
||||
|
||||
/**
|
||||
* Student Client
|
||||
|
|
@ -18,7 +18,6 @@ export class StudentClient extends BaseClient {
|
|||
private dateOfBirth = "";
|
||||
|
||||
/**
|
||||
*
|
||||
* @param studentCode ClassCharts student code
|
||||
* @param dateOfBirth Student's date of birth
|
||||
*/
|
||||
|
|
@ -32,7 +31,7 @@ export class StudentClient extends BaseClient {
|
|||
* Authenticates with ClassCharts
|
||||
*/
|
||||
async login(): Promise<void> {
|
||||
if (!this.studentCode) throw new Error("Student Code not inputted");
|
||||
if (!this.studentCode) throw new Error("Student Code not provided");
|
||||
const formData = new URLSearchParams();
|
||||
formData.append("_method", "POST");
|
||||
formData.append("code", this.studentCode.toUpperCase());
|
||||
|
|
@ -43,14 +42,11 @@ export class StudentClient extends BaseClient {
|
|||
method: "POST",
|
||||
body: formData,
|
||||
redirect: "manual",
|
||||
credentials: undefined,
|
||||
});
|
||||
if (request.status != 302 || !request.headers.get("set-cookie")) {
|
||||
await request.body?.cancel(); // Make deno tests happy by closing the body, unsure whether this is needed for the actual library
|
||||
throw new Error(
|
||||
"Unauthenticated: ClassCharts returned an error: " +
|
||||
request.status +
|
||||
" " +
|
||||
request.statusText
|
||||
"Unauthenticated: ClassCharts didn't return authentication cookies"
|
||||
);
|
||||
}
|
||||
const cookies = String(request.headers.get("set-cookie"));
|
||||
|
|
|
|||
25
src/core/studentClient_test.ts
Normal file
25
src/core/studentClient_test.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { assertRejects } from "https://deno.land/std@0.200.0/assert/mod.ts";
|
||||
|
||||
import { StudentClient } from "./studentClient.ts";
|
||||
|
||||
Deno.test("Throws when no student code is provided", async () => {
|
||||
const client = new StudentClient("");
|
||||
await assertRejects(
|
||||
async () => {
|
||||
await client.login();
|
||||
},
|
||||
Error,
|
||||
"Student Code not provided"
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("Throws with invalid student code", async () => {
|
||||
const client = new StudentClient("invalid");
|
||||
await assertRejects(
|
||||
async () => {
|
||||
await client.login();
|
||||
},
|
||||
Error,
|
||||
"Unauthenticated: ClassCharts didn't return authentication cookies"
|
||||
);
|
||||
});
|
||||
13
src/index.ts
13
src/index.ts
|
|
@ -1,13 +0,0 @@
|
|||
export * from "./core/studentClient.js";
|
||||
export * from "./core/parentClient.js";
|
||||
export type {
|
||||
Student,
|
||||
ActivityPoint,
|
||||
BehaviourTimelinePoint,
|
||||
Homework,
|
||||
Lesson,
|
||||
Badge,
|
||||
Announcement,
|
||||
Detention,
|
||||
AttendanceDate,
|
||||
} from "./types.js";
|
||||
13
src/types.ts
13
src/types.ts
|
|
@ -1,5 +1,3 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
/**
|
||||
* Helper type to define response from ClassCharts
|
||||
* @internal
|
||||
|
|
@ -157,7 +155,6 @@ export interface GetHomeworkOptions {
|
|||
*
|
||||
* Used to sort homeworks by when they are due or when they were issued
|
||||
* @default "issue_date"
|
||||
*
|
||||
*/
|
||||
displayDate?: DisplayDate;
|
||||
/**
|
||||
|
|
@ -193,16 +190,16 @@ export interface Homework {
|
|||
status: {
|
||||
id: number;
|
||||
state: "not_completed" | "late" | "completed" | null;
|
||||
mark: any | null;
|
||||
mark: unknown | null;
|
||||
mark_relative: number;
|
||||
ticked: "yes" | "no";
|
||||
allow_attachments: "yes" | "no";
|
||||
first_seen_date: string;
|
||||
last_seen_date: string;
|
||||
attachments: Array<any>;
|
||||
attachments: Array<unknown>;
|
||||
has_feedback: boolean;
|
||||
};
|
||||
validated_links: Array<any>;
|
||||
validated_links: Array<unknown>;
|
||||
validated_attachments: Array<ValidatedHomeworkAttachment>;
|
||||
}
|
||||
export type HomeworksResponseData = Array<Homework>;
|
||||
|
|
@ -357,7 +354,7 @@ export interface Announcement {
|
|||
filename: string;
|
||||
url: string;
|
||||
}>;
|
||||
for_pupils: Array<any>;
|
||||
for_pupils: Array<unknown>;
|
||||
comment_visibility: string;
|
||||
allow_comments: "yes" | "no";
|
||||
allow_reactions: "yes" | "no";
|
||||
|
|
@ -366,7 +363,7 @@ export interface Announcement {
|
|||
requires_consent: "yes" | "no";
|
||||
can_change_consent: boolean;
|
||||
consent: string | null;
|
||||
pupil_consents: Array<any>;
|
||||
pupil_consents: Array<unknown>;
|
||||
}
|
||||
|
||||
// TODO: Update typings to include meta response. Currently not possible since I don't have access
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ export function parseCookies(input: string) {
|
|||
return output;
|
||||
}
|
||||
|
||||
function leftTrim(str: string) {
|
||||
if (!str) return str;
|
||||
export function leftTrim(str: string) {
|
||||
return str.replace(/^\s+/g, "");
|
||||
}
|
||||
|
|
|
|||
41
src/utils/utils_test.ts
Normal file
41
src/utils/utils_test.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import {
|
||||
assertEquals,
|
||||
assertExists,
|
||||
} from "https://deno.land/std@0.200.0/assert/mod.ts";
|
||||
|
||||
import { parseCookies } from "./utils.ts";
|
||||
Deno.test("Parses simple cookie", () => {
|
||||
const cookie =
|
||||
"testCookie=Hello%20world!; expires=Tue, 28-Nov-2023 10:28:45 GMT; Max-Age=7776000; path=/";
|
||||
const parsed = parseCookies(cookie);
|
||||
assertEquals(parsed.testCookie, "Hello world!");
|
||||
});
|
||||
|
||||
Deno.test("Parses multiple cookies", () => {
|
||||
const cookies =
|
||||
"firstCookie=I'm%20the%20first%20cookie; expires=Tue, 28-Nov-2023 10:28:45 GMT; Max-Age=7776000; path=/, secondCookie=I'm%20the%20second%20cookie; expires=Tue, 28-Nov-2023 10:28:45 GMT; Max-Age=7776000; path=/";
|
||||
const parsed = parseCookies(cookies);
|
||||
assertEquals(parsed.firstCookie, "I'm the first cookie");
|
||||
assertEquals(parsed.secondCookie, "I'm the second cookie");
|
||||
});
|
||||
|
||||
Deno.test("Parses cookie with no value", () => {
|
||||
const cookie =
|
||||
"cookieWithNoValue=; expires=Tue, 28-Nov-2023 10:28:45 GMT; Max-Age=7776000; path=/";
|
||||
const parsed = parseCookies(cookie);
|
||||
assertExists(parsed.cookieWithNoValue);
|
||||
assertEquals(parsed.cookieWithNoValue, "");
|
||||
});
|
||||
|
||||
import { leftTrim } from "./utils.ts";
|
||||
Deno.test("Trims left with spaces", () => {
|
||||
const input = " Hello world!";
|
||||
const output = leftTrim(input);
|
||||
assertEquals(output, "Hello world!");
|
||||
});
|
||||
|
||||
Deno.test("Trims left with no spaces", () => {
|
||||
const input = "Hello world!";
|
||||
const output = leftTrim(input);
|
||||
assertEquals(output, "Hello world!");
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue