diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..938e7cb --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,33 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node +{ + "name": "Node.js & TypeScript", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/typescript-node:1-20-bullseye", + "features": { + "ghcr.io/devcontainers-contrib/features/deno:1": {} + }, + "customizations": { + "vscode": { + "extensions": [ + "biomejs.biome", + "denoland.vscode-deno" + ] + } + } + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "yarn install", + + // Configure tool-specific properties. + // "customizations": {}, + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 09cf720..97352cc 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,5 +1,3 @@ { - "recommendations": [ - "denoland.vscode-deno" - ] + "recommendations": ["denoland.vscode-deno", "biomejs.biome"] } diff --git a/.vscode/settings.json b/.vscode/settings.json index e1533c2..28867f2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,4 @@ { - "deno.enable": true, - "deno.lint": true + "deno.enable": true, + "deno.lint": false } diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..bfd6919 --- /dev/null +++ b/biome.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.4.0/schema.json", + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "files": { + "ignore": [".devcontainer/*", "npm/*", "deno.lock"] + } +} diff --git a/deno.jsonc b/deno.jsonc index 175e392..736d625 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -1,6 +1,8 @@ { "tasks": { - "npm": "deno run -A scripts/build_npm.ts" + "npm": "deno run -A scripts/build_npm.ts", + "format": "deno run -A npm:@biomejs/biome format . --write", + "lint": "deno run -A npm:@biomejs/biome lint ." }, "exclude": ["npm"] } diff --git a/deno.lock b/deno.lock index 43b65b6..c0d9cfd 100644 --- a/deno.lock +++ b/deno.lock @@ -1,5 +1,47 @@ { - "version": "2", + "version": "3", + "packages": { + "specifiers": { + "npm:@biomejs/biome": "npm:@biomejs/biome@1.4.0" + }, + "npm": { + "@biomejs/biome@1.4.0": { + "integrity": "sha512-/rDlao6ra38nhxo4IYCqWCzfTJcpMk4YHjSVBI9yN/ifdhnzSwirL25xDVH7G9hZdNhpF9g78FaPJhFa9DX0Cw==", + "dependencies": { + "@biomejs/cli-darwin-arm64": "@biomejs/cli-darwin-arm64@1.4.0", + "@biomejs/cli-darwin-x64": "@biomejs/cli-darwin-x64@1.4.0", + "@biomejs/cli-linux-arm64": "@biomejs/cli-linux-arm64@1.4.0", + "@biomejs/cli-linux-x64": "@biomejs/cli-linux-x64@1.4.0", + "@biomejs/cli-win32-arm64": "@biomejs/cli-win32-arm64@1.4.0", + "@biomejs/cli-win32-x64": "@biomejs/cli-win32-x64@1.4.0" + } + }, + "@biomejs/cli-darwin-arm64@1.4.0": { + "integrity": "sha512-nBrtVRwr4IlTtxLOHwBwLv1sWvggf9/DnT5/ALIANJZOpoING6u8jHWipods69wK8kGa8Ld7iwHm3W5BrJJFFQ==", + "dependencies": {} + }, + "@biomejs/cli-darwin-x64@1.4.0": { + "integrity": "sha512-nny0VgOj3ksUGzU5GblgtQEvrAZFgFe1IJBoYOP978OQdDrg7BpS+GX5udfof87Dl4ZlHPRBU951ceHOxF7BTg==", + "dependencies": {} + }, + "@biomejs/cli-linux-arm64@1.4.0": { + "integrity": "sha512-gyLkT/Yh9xfW1T9yjQs/2txkCeG0e+LRs0adLugMwN0ptcNTRyusBvUoiHnpB+9rS6hWu9ZCedGMNmKQ8v2GSw==", + "dependencies": {} + }, + "@biomejs/cli-linux-x64@1.4.0": { + "integrity": "sha512-LIxTuU2zSbIHM9XDYjQphJ5UU8h2eS7yR8uIvGYSba7Qt9AKqfbenyVJTsVnoj1CXxxgKNVSc/wVmlOlGz5DBQ==", + "dependencies": {} + }, + "@biomejs/cli-win32-arm64@1.4.0": { + "integrity": "sha512-U2jT1/0wZLJIRqnU8qHAfi/A/+yUwlL3sYJgqs+wO0BbR22WGQZlj03u5FdpEoyLXdsLv1pbeIcjNp+V0NYXWA==", + "dependencies": {} + }, + "@biomejs/cli-win32-x64@1.4.0": { + "integrity": "sha512-gN6DgyyBxIwoCovAUFJHFWVallb0cLosayDRtNyxU3MDv/atZxSXOWQezfVKBIbgmFPxYWJObd+awvbPYXwwww==", + "dependencies": {} + } + } + }, "remote": { "https://deno.land/std@0.140.0/_util/assert.ts": "e94f2eb37cebd7f199952e242c77654e43333c1ac4c5c700e929ea3aa5489f74", "https://deno.land/std@0.140.0/_util/os.ts": "3b4c6e27febd119d36a416d7a97bd3b0251b77c88942c8f16ee5953ea13e2e49", diff --git a/deps_dev.ts b/deps_dev.ts index e094a6d..4bc1c08 100644 --- a/deps_dev.ts +++ b/deps_dev.ts @@ -1,5 +1,5 @@ export { - assertEquals, - assertExists, - assertRejects, + assertEquals, + assertExists, + assertRejects, } from "https://deno.land/std@0.201.0/assert/mod.ts"; diff --git a/scripts/build_npm.ts b/scripts/build_npm.ts index 8cce7c8..a79bb54 100644 --- a/scripts/build_npm.ts +++ b/scripts/build_npm.ts @@ -6,49 +6,52 @@ if (!Deno.args[0]) throw new Error("No version specified"); await emptyDir("./npm"); await build({ - entryPoints: [{ - name: ".", - path: "./mod.ts", - }, { - name: "./types", - path: "./src/types.ts", - }], - outDir: "./npm", - importMap: "./deno.jsonc", - shims: { - deno: true, - }, - packageManager: "pnpm", - compilerOptions: { - lib: ["DOM", "ESNext"], - }, - typeCheck: "both", - package: { - name: "classcharts-api", - version: String(Deno.args[0]).replace("v", ""), - author: { - name: "James Cook", - email: "james@jaminit.co.uk", - }, - description: - "A Typescript wrapper for getting information from the ClassCharts API", - license: "ISC", - keywords: ["node", "typescript", "classcharts", "class charts"], - bugs: { - url: "https://github.com/classchartsapi/classcharts-api-js/issues", - }, - repository: { - type: "git", - url: "https://github.com/classchartsapi/classcharts-api-js.git", - }, - homepage: "https://classchartsapi.github.io/classcharts-api-js/", - engines: { - node: ">=18", - }, - sideEffects: false, - }, - postBuild() { - Deno.copyFileSync("LICENSE", "npm/LICENSE"); - Deno.copyFileSync("README.md", "npm/README.md"); - }, + entryPoints: [ + { + name: ".", + path: "./mod.ts", + }, + { + name: "./types", + path: "./src/types.ts", + }, + ], + outDir: "./npm", + importMap: "./deno.jsonc", + shims: { + deno: true, + }, + packageManager: "pnpm", + compilerOptions: { + lib: ["DOM", "ESNext"], + }, + typeCheck: "both", + package: { + name: "classcharts-api", + version: String(Deno.args[0]).replace("v", ""), + author: { + name: "James Cook", + email: "james@jaminit.co.uk", + }, + description: + "A Typescript wrapper for getting information from the ClassCharts API", + license: "ISC", + keywords: ["node", "typescript", "classcharts", "class charts"], + bugs: { + url: "https://github.com/classchartsapi/classcharts-api-js/issues", + }, + repository: { + type: "git", + url: "https://github.com/classchartsapi/classcharts-api-js.git", + }, + homepage: "https://classchartsapi.github.io/classcharts-api-js/", + engines: { + node: ">=18", + }, + sideEffects: false, + }, + postBuild() { + Deno.copyFileSync("LICENSE", "npm/LICENSE"); + Deno.copyFileSync("README.md", "npm/README.md"); + }, }); diff --git a/src/core/baseClient.ts b/src/core/baseClient.ts index 96f5076..6aec0e9 100644 --- a/src/core/baseClient.ts +++ b/src/core/baseClient.ts @@ -1,21 +1,21 @@ import type { - ActivityResponse, - AnnouncementsResponse, - AttendanceResponse, - BadgesResponse, - BehaviourResponse, - ClassChartsResponse, - DetentionsResponse, - GetActivityOptions, - GetAttendanceOptions, - GetBehaviourOptions, - GetFullActivityOptions, - GetHomeworkOptions, - GetLessonsOptions, - GetStudentInfoResponse, - HomeworksResponse, - LessonsResponse, - PupilFieldsResponse, + ActivityResponse, + AnnouncementsResponse, + AttendanceResponse, + BadgesResponse, + BehaviourResponse, + ClassChartsResponse, + DetentionsResponse, + GetActivityOptions, + GetAttendanceOptions, + GetBehaviourOptions, + GetFullActivityOptions, + GetHomeworkOptions, + GetLessonsOptions, + GetStudentInfoResponse, + HomeworksResponse, + LessonsResponse, + PupilFieldsResponse, } from "../types.ts"; import { PING_INTERVAL } from "../utils/consts.ts"; @@ -23,290 +23,286 @@ import { PING_INTERVAL } from "../utils/consts.ts"; * Shared client for both parent and student. This is not exported and should not be used directly */ export class BaseClient { - /** - * @property studentId Currently selected student ID - */ - public studentId = 0; - /** - * @property authCookies Cookies used for authentication (set during login and can be empty) - */ - public authCookies: Array; - /** - * @property sessionId Session ID used for authentication - */ - public sessionId = ""; - /** - * @property lastPing Last time the sessionId was updated - */ - public lastPing = 0; - /** - * @property API_BASE Base API URL, this is different depending on if its called as a parent or student - */ - 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) { - this.authCookies = []; - this.API_BASE = API_BASE; - } - /** - * Revalidates the session ID. - * - * This is called automatically when the session ID is older than 3 minutes or when initially using the .login() method - */ - public async getNewSessionId() { - const pingFormData = new URLSearchParams(); - pingFormData.append("include_data", "true"); - const pingData = await this.makeAuthedRequest( - this.API_BASE + "/ping", - { - method: "POST", - body: pingFormData, - }, - { revalidateToken: false }, - ); - this.sessionId = pingData.meta.session_id; - this.lastPing = Date.now(); - } - /** - * Makes a request to the ClassCharts API with the required authentication headers - * - * @param path Path to the API endpoint - * @param fetchOptions Request Options - * @param options - * @param options.revalidateToken Whether to revalidate the session ID if it is older than 3 minutes - * - * @returns Response - */ - public async makeAuthedRequest( - path: string, - fetchOptions: RequestInit, - options?: { revalidateToken?: boolean }, - ) { - if (!this.sessionId) throw new Error("No session ID"); - if (!options) { - options = {}; - } - if (typeof options?.revalidateToken == "undefined") { - options.revalidateToken = true; - } - const requestOptions = { - ...fetchOptions, - headers: { - Cookie: this?.authCookies?.join(";") ?? [], - Authorization: "Basic " + this.sessionId, - "User-Agent": - "classcharts-api https://github.com/classchartsapi/classcharts-api-js", - ...fetchOptions.headers, - }, - } satisfies RequestInit; - if (options?.revalidateToken === true && this.lastPing) { - if (Date.now() - this.lastPing + 5000 > PING_INTERVAL) { - await this.getNewSessionId(); - } - } - const request = await fetch(path, requestOptions); - // deno-lint-ignore no-explicit-any - let responseJSON: ClassChartsResponse; - try { - responseJSON = await request.json(); - } catch { - throw new Error( - "Error parsing JSON. Returned response: " + (await request.text()), - ); - } - if (responseJSON.success == 0) { - throw new Error(responseJSON.error); - } - return responseJSON; - } - /** - * Gets general information about the current student - * @returns Student object - */ - async getStudentInfo(): Promise { - const body = new URLSearchParams(); - body.append("include_data", "true"); - return await this.makeAuthedRequest(this.API_BASE + "/ping", { - method: "POST", - body: body, - }); - } - /** - * Gets the current student's activity - * - * This function is only used for pagination, you likely want client.getFullActivity - * @param options GetActivityOptions - * @returns Activity data - * @see getFullActivity - */ - async getActivity(options?: GetActivityOptions): Promise { - const params = new URLSearchParams(); - 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 await this.makeAuthedRequest( - this.API_BASE + "/activity/" + this.studentId + "?" + params.toString(), - { - method: "GET", - }, - ); - } - /** - * Gets the current student's activity between two dates - * - * This function will automatically paginate through all the data returned by getActivity - * @param options GetFullActivityOptions - * @returns Activity Data - * @see getActivity - */ - async getFullActivity( - options: GetFullActivityOptions, - ): Promise { - let data: ActivityResponse["data"] = []; - let prevLast: number | undefined; - let gotData = true; - while (gotData) { - const params: GetActivityOptions = { - from: options.from, - to: options.to, - }; - if (prevLast) { - params.last_id = String(prevLast); - } - const fragment = (await this.getActivity(params)).data; - if (!fragment || !fragment.length) { - gotData = false; - } else { - data = data.concat(fragment); - prevLast = fragment[fragment.length - 1].id; - } - } - return data; - } - /** - * Gets the current student's behaviour - * @param options GetBehaviourOptions - * @returns Array of behaviour points - */ - async getBehaviour( - options?: GetBehaviourOptions, - ): Promise { - const params = new URLSearchParams(); - options?.from && params.append("from", options?.from); - options?.to && params.append("to", options?.to); - return await this.makeAuthedRequest( - this.API_BASE + "/behaviour/" + this.studentId + "?" + params.toString(), - { - method: "GET", - }, - ); - } - /** - * Gets the current student's homework - * @param options GetHomeworkOptions - * @returns Array of homeworks - */ - async getHomeworks(options?: GetHomeworkOptions): Promise { - const params = new URLSearchParams(); - if (options?.displayDate) { - params.append("display_date", String(options?.displayDate)); - } + /** + * @property studentId Currently selected student ID + */ + public studentId = 0; + /** + * @property authCookies Cookies used for authentication (set during login and can be empty) + */ + public authCookies: Array; + /** + * @property sessionId Session ID used for authentication + */ + public sessionId = ""; + /** + * @property lastPing Last time the sessionId was updated + */ + public lastPing = 0; + /** + * @property API_BASE Base API URL, this is different depending on if its called as a parent or student + */ + 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) { + this.authCookies = []; + this.API_BASE = API_BASE; + } + /** + * Revalidates the session ID. + * + * This is called automatically when the session ID is older than 3 minutes or when initially using the .login() method + */ + public async getNewSessionId() { + const pingFormData = new URLSearchParams(); + pingFormData.append("include_data", "true"); + const pingData = await this.makeAuthedRequest( + this.API_BASE + "/ping", + { + method: "POST", + body: pingFormData, + }, + { revalidateToken: false }, + ); + this.sessionId = pingData.meta.session_id; + this.lastPing = Date.now(); + } + /** + * Makes a request to the ClassCharts API with the required authentication headers + * + * @param path Path to the API endpoint + * @param fetchOptions Request Options + * @param options + * @param options.revalidateToken Whether to revalidate the session ID if it is older than 3 minutes + * + * @returns Response + */ + public async makeAuthedRequest( + path: string, + fetchOptions: RequestInit, + options?: { revalidateToken?: boolean }, + ) { + if (!this.sessionId) throw new Error("No session ID"); + if (!options) { + options = {}; + } + if (typeof options?.revalidateToken == "undefined") { + options.revalidateToken = true; + } + const requestOptions = { + ...fetchOptions, + headers: { + Cookie: this?.authCookies?.join(";") ?? [], + Authorization: "Basic " + this.sessionId, + "User-Agent": + "classcharts-api https://github.com/classchartsapi/classcharts-api-js", + ...fetchOptions.headers, + }, + } satisfies RequestInit; + if (options?.revalidateToken === true && this.lastPing) { + if (Date.now() - this.lastPing + 5000 > PING_INTERVAL) { + await this.getNewSessionId(); + } + } + const request = await fetch(path, requestOptions); + // deno-lint-ignore no-explicit-any + let responseJSON: ClassChartsResponse; + try { + responseJSON = await request.json(); + } catch { + throw new Error( + "Error parsing JSON. Returned response: " + (await request.text()), + ); + } + if (responseJSON.success == 0) { + throw new Error(responseJSON.error); + } + return responseJSON; + } + /** + * Gets general information about the current student + * @returns Student object + */ + async getStudentInfo(): Promise { + const body = new URLSearchParams(); + body.append("include_data", "true"); + return await this.makeAuthedRequest(this.API_BASE + "/ping", { + method: "POST", + body: body, + }); + } + /** + * Gets the current student's activity + * + * This function is only used for pagination, you likely want client.getFullActivity + * @param options GetActivityOptions + * @returns Activity data + * @see getFullActivity + */ + async getActivity(options?: GetActivityOptions): Promise { + const params = new URLSearchParams(); + 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 await this.makeAuthedRequest( + this.API_BASE + "/activity/" + this.studentId + "?" + params.toString(), + { + method: "GET", + }, + ); + } + /** + * Gets the current student's activity between two dates + * + * This function will automatically paginate through all the data returned by getActivity + * @param options GetFullActivityOptions + * @returns Activity Data + * @see getActivity + */ + async getFullActivity( + options: GetFullActivityOptions, + ): Promise { + let data: ActivityResponse["data"] = []; + let prevLast: number | undefined; + let gotData = true; + while (gotData) { + const params: GetActivityOptions = { + from: options.from, + to: options.to, + }; + if (prevLast) { + params.last_id = String(prevLast); + } + const fragment = (await this.getActivity(params)).data; + if (!fragment || !fragment.length) { + gotData = false; + } else { + data = data.concat(fragment); + prevLast = fragment[fragment.length - 1].id; + } + } + return data; + } + /** + * Gets the current student's behaviour + * @param options GetBehaviourOptions + * @returns Array of behaviour points + */ + async getBehaviour( + options?: GetBehaviourOptions, + ): Promise { + const params = new URLSearchParams(); + options?.from && params.append("from", options?.from); + options?.to && params.append("to", options?.to); + return await this.makeAuthedRequest( + this.API_BASE + "/behaviour/" + this.studentId + "?" + params.toString(), + { + method: "GET", + }, + ); + } + /** + * Gets the current student's homework + * @param options GetHomeworkOptions + * @returns Array of homeworks + */ + async getHomeworks(options?: GetHomeworkOptions): Promise { + const params = new URLSearchParams(); + 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)); - return await this.makeAuthedRequest( - this.API_BASE + "/homeworks/" + this.studentId + "?" + params.toString(), - { - method: "GET", - }, - ); - } - /** - * Gets the current student's lessons for a given date - * @param options GetLessonsOptions - * @returns Array of lessons - */ - async getLessons(options: GetLessonsOptions): Promise { - if (!options?.date) throw new Error("No date specified"); - const params = new URLSearchParams(); - params.append("date", String(options?.date)); - return await this.makeAuthedRequest( - this.API_BASE + "/timetable/" + this.studentId + "?" + params.toString(), - { - method: "GET", - }, - ); - } - /** - * Gets the current student's earned badges - * @returns Array of badges - */ - async getBadges(): Promise { - return await this.makeAuthedRequest( - this.API_BASE + "/eventbadges/" + this.studentId, - { - method: "GET", - }, - ); - } - /** - * Gets the current student's announcements - * @returns Array of announcements - */ - async getAnnouncements(): Promise { - return await this.makeAuthedRequest( - this.API_BASE + "/announcements/" + this.studentId, - { - method: "GET", - }, - ); - } - /** - * Gets the current student's detentions - * @returns Array of detentions - */ - async getDetentions(): Promise { - return await this.makeAuthedRequest( - this.API_BASE + "/detentions/" + this.studentId, - { - method: "GET", - }, - ); - } - /** - * Gets the current student's attendance - * @param options GetAttendanceOptions - * @returns Array of dates of attendance - */ - async getAttendance( - options?: GetAttendanceOptions, - ): Promise { - const params = new URLSearchParams(); - options?.from && params.append("from", options?.from); - options?.to && params.append("to", options?.to); - return await this.makeAuthedRequest( - this.API_BASE + - "/attendance/" + - this.studentId + - "?" + - params.toString(), - { - method: "GET", - }, - ); - } - /** - * Gets the current student's pupil fields - * @returns Array of stats - */ - async getPupilFields(): Promise { - return await this.makeAuthedRequest( - this.API_BASE + "/customfields/" + this.studentId, - { - method: "GET", - }, - ); - } + options?.from && params.append("from", String(options?.from)); + options?.to && params.append("to", String(options?.to)); + return await this.makeAuthedRequest( + this.API_BASE + "/homeworks/" + this.studentId + "?" + params.toString(), + { + method: "GET", + }, + ); + } + /** + * Gets the current student's lessons for a given date + * @param options GetLessonsOptions + * @returns Array of lessons + */ + async getLessons(options: GetLessonsOptions): Promise { + if (!options?.date) throw new Error("No date specified"); + const params = new URLSearchParams(); + params.append("date", String(options?.date)); + return await this.makeAuthedRequest( + this.API_BASE + "/timetable/" + this.studentId + "?" + params.toString(), + { + method: "GET", + }, + ); + } + /** + * Gets the current student's earned badges + * @returns Array of badges + */ + async getBadges(): Promise { + return await this.makeAuthedRequest( + this.API_BASE + "/eventbadges/" + this.studentId, + { + method: "GET", + }, + ); + } + /** + * Gets the current student's announcements + * @returns Array of announcements + */ + async getAnnouncements(): Promise { + return await this.makeAuthedRequest( + this.API_BASE + "/announcements/" + this.studentId, + { + method: "GET", + }, + ); + } + /** + * Gets the current student's detentions + * @returns Array of detentions + */ + async getDetentions(): Promise { + return await this.makeAuthedRequest( + this.API_BASE + "/detentions/" + this.studentId, + { + method: "GET", + }, + ); + } + /** + * Gets the current student's attendance + * @param options GetAttendanceOptions + * @returns Array of dates of attendance + */ + async getAttendance( + options?: GetAttendanceOptions, + ): Promise { + const params = new URLSearchParams(); + options?.from && params.append("from", options?.from); + options?.to && params.append("to", options?.to); + return await this.makeAuthedRequest( + this.API_BASE + "/attendance/" + this.studentId + "?" + params.toString(), + { + method: "GET", + }, + ); + } + /** + * Gets the current student's pupil fields + * @returns Array of stats + */ + async getPupilFields(): Promise { + return await this.makeAuthedRequest( + this.API_BASE + "/customfields/" + this.studentId, + { + method: "GET", + }, + ); + } } diff --git a/src/core/parentClient.ts b/src/core/parentClient.ts index 5f1f0ea..7d04159 100644 --- a/src/core/parentClient.ts +++ b/src/core/parentClient.ts @@ -7,112 +7,107 @@ import { parseCookies } from "../utils/utils.ts"; * Parent Client */ export class ParentClient extends BaseClient { - private password = ""; - private email = ""; - // @ts-expect-error Init in .login - public pupils: GetPupilsResponse; - /** - * @param email Parent's email address - * @param password Parent's password - */ - constructor(email: string, password: string) { - super(API_BASE_PARENT); - this.email = String(email); - this.password = String(password); - } + private password = ""; + private email = ""; + // @ts-expect-error Init in .login + public pupils: GetPupilsResponse; + /** + * @param email Parent's email address + * @param password Parent's password + */ + constructor(email: string, password: string) { + super(API_BASE_PARENT); + this.email = String(email); + this.password = String(password); + } - /** - * Authenticates with ClassCharts - */ - async login(): Promise { - if (!this.email) throw new Error("Email not provided"); - if (!this.password) throw new Error("Password not provided"); - const formData = new URLSearchParams(); - formData.append("_method", "POST"); - formData.append("email", this.email); - formData.append("logintype", "existing"); - formData.append("password", this.password); - formData.append("recaptcha-token", "no-token-available"); - const headers = new Headers({ - "Content-Type": "application/x-www-form-urlencoded", - }); - const response = await fetch(BASE_URL + "/parent/login", { - method: "POST", - body: formData, - headers: headers, - redirect: "manual", - }); - if (response.status != 302 || !response.headers.has("set-cookie")) { - await response.body?.cancel(); // Make deno tests happy by closing the body, unsure whether this is needed for the actual library - throw new Error( - "Unauthenticated: ClassCharts didn't return authentication cookies", - ); - } + /** + * Authenticates with ClassCharts + */ + async login(): Promise { + if (!this.email) throw new Error("Email not provided"); + if (!this.password) throw new Error("Password not provided"); + const formData = new URLSearchParams(); + formData.append("_method", "POST"); + formData.append("email", this.email); + formData.append("logintype", "existing"); + formData.append("password", this.password); + formData.append("recaptcha-token", "no-token-available"); + const headers = new Headers({ + "Content-Type": "application/x-www-form-urlencoded", + }); + const response = await fetch(BASE_URL + "/parent/login", { + method: "POST", + body: formData, + headers: headers, + redirect: "manual", + }); + if (response.status != 302 || !response.headers.has("set-cookie")) { + await response.body?.cancel(); // Make deno tests happy by closing the body, unsure whether this is needed for the actual library + throw new Error( + "Unauthenticated: ClassCharts didn't return authentication cookies", + ); + } - const cookies = String(response.headers.get("set-cookie")); - // this.authCookies = cookies.split(";"); - const sessionCookies = parseCookies(cookies); - const sessionID = JSON.parse( - String(sessionCookies["parent_session_credentials"]), - ); - this.sessionId = sessionID.session_id; - this.pupils = await this.getPupils(); - if (!this.pupils) throw new Error("Account has no pupils attached"); - this.studentId = this.pupils[0].id; - } - /** - * Get a list of pupils connected to this parent's account - * @returns an array of Pupils connected to this parent's account - */ - async getPupils(): Promise { - const response = await this.makeAuthedRequest(this.API_BASE + "/pupils", { - method: "GET", - }); - return response.data; - } - /** - * Selects a pupil to be used with API requests - * @param pupilId Pupil ID obtained from this.pupils or getPupils() - * - * @see getPupils - */ - selectPupil(pupilId: number) { - if (!pupilId) throw new Error("No pupil ID specified"); - const pupils = this.pupils; - for (let i = 0; i < pupils.length; i++) { - const pupil = pupils[i]; - if (pupil.id == pupilId) { - this.studentId = pupil.id; - return; - } - } - throw new Error("No pupil with specified ID returned"); - } - /** - * Changes the login password for the current parent account - * @param currentPassword Current password - * @param newPassword New password - * @returns Whether the request was successful - */ - async changePassword( - currentPassword: string, - newPassword: string, - ): Promise { - const formData = new URLSearchParams(); - formData.append("current", currentPassword); - formData.append("new", newPassword); - formData.append("repeat", newPassword); - return ( - await this.makeAuthedRequest( - this.API_BASE + "/password", - { - method: "POST", - body: formData, - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - }, - ) - ); - } + const cookies = String(response.headers.get("set-cookie")); + // this.authCookies = cookies.split(";"); + const sessionCookies = parseCookies(cookies); + const sessionID = JSON.parse( + String(sessionCookies["parent_session_credentials"]), + ); + this.sessionId = sessionID.session_id; + this.pupils = await this.getPupils(); + if (!this.pupils) throw new Error("Account has no pupils attached"); + this.studentId = this.pupils[0].id; + } + /** + * Get a list of pupils connected to this parent's account + * @returns an array of Pupils connected to this parent's account + */ + async getPupils(): Promise { + const response = await this.makeAuthedRequest(this.API_BASE + "/pupils", { + method: "GET", + }); + return response.data; + } + /** + * Selects a pupil to be used with API requests + * @param pupilId Pupil ID obtained from this.pupils or getPupils() + * + * @see getPupils + */ + selectPupil(pupilId: number) { + if (!pupilId) throw new Error("No pupil ID specified"); + const pupils = this.pupils; + for (let i = 0; i < pupils.length; i++) { + const pupil = pupils[i]; + if (pupil.id == pupilId) { + this.studentId = pupil.id; + return; + } + } + throw new Error("No pupil with specified ID returned"); + } + /** + * Changes the login password for the current parent account + * @param currentPassword Current password + * @param newPassword New password + * @returns Whether the request was successful + */ + async changePassword( + currentPassword: string, + newPassword: string, + ): Promise { + const formData = new URLSearchParams(); + formData.append("current", currentPassword); + formData.append("new", newPassword); + formData.append("repeat", newPassword); + return await this.makeAuthedRequest(this.API_BASE + "/password", { + method: "POST", + body: formData, + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + }); + } } diff --git a/src/core/parentClient_test.ts b/src/core/parentClient_test.ts index 7e5ac8c..f4c4234 100644 --- a/src/core/parentClient_test.ts +++ b/src/core/parentClient_test.ts @@ -2,34 +2,34 @@ import { assertRejects } from "../../deps_dev.ts"; import { ParentClient } from "../core/parentClient.ts"; Deno.test("Throws when no email is provided", async () => { - const client = new ParentClient("", "password"); - await assertRejects( - async () => { - await client.login(); - }, - Error, - "Email not provided", - ); + const client = new ParentClient("", "password"); + await assertRejects( + async () => { + await client.login(); + }, + Error, + "Email not provided", + ); }); Deno.test("Throws when no password is provided", async () => { - const client = new ParentClient("email", ""); - await assertRejects( - async () => { - await client.login(); - }, - Error, - "Password not provided", - ); + const client = new ParentClient("email", ""); + await assertRejects( + async () => { + await client.login(); + }, + Error, + "Password not provided", + ); }); Deno.test("Throws with invalid username and password", async () => { - const client = new ParentClient("invalid", "invalid"); - await assertRejects( - async () => { - await client.login(); - }, - Error, - "Unauthenticated: ClassCharts didn't return authentication cookies", - ); + const client = new ParentClient("invalid", "invalid"); + await assertRejects( + async () => { + await client.login(); + }, + Error, + "Unauthenticated: ClassCharts didn't return authentication cookies", + ); }); diff --git a/src/core/studentClient.ts b/src/core/studentClient.ts index 5f3cef1..0514977 100644 --- a/src/core/studentClient.ts +++ b/src/core/studentClient.ts @@ -2,116 +2,109 @@ import { API_BASE_STUDENT, BASE_URL } from "../utils/consts.ts"; import { BaseClient } from "../core/baseClient.ts"; import { parseCookies } from "../utils/utils.ts"; import { - GetStudentCodeOptions, - GetStudentCodeResponse, - RewardPurchaseResponse, - RewardsResponse, + GetStudentCodeOptions, + GetStudentCodeResponse, + RewardPurchaseResponse, + RewardsResponse, } from "../types.ts"; /** * Student Client */ export class StudentClient extends BaseClient { - /** - * @property studentCode ClassCharts student code - */ - private studentCode = ""; - /** - * @property dateOfBirth Student's date of birth - */ - private dateOfBirth = ""; + /** + * @property studentCode ClassCharts student code + */ + private studentCode = ""; + /** + * @property dateOfBirth Student's date of birth + */ + private dateOfBirth = ""; - /** - * @param studentCode ClassCharts student code - * @param dateOfBirth Student's date of birth - */ - constructor(studentCode: string, dateOfBirth?: string) { - super(API_BASE_STUDENT); - this.studentCode = String(studentCode); - this.dateOfBirth = String(dateOfBirth); - } + /** + * @param studentCode ClassCharts student code + * @param dateOfBirth Student's date of birth + */ + constructor(studentCode: string, dateOfBirth?: string) { + super(API_BASE_STUDENT); + this.studentCode = String(studentCode); + this.dateOfBirth = String(dateOfBirth); + } - /** - * Authenticates with ClassCharts - */ - async login(): Promise { - if (!this.studentCode) throw new Error("Student Code not provided"); - const formData = new URLSearchParams(); - formData.append("_method", "POST"); - formData.append("code", this.studentCode.toUpperCase()); - formData.append("dob", this.dateOfBirth); - formData.append("remember_me", "1"); - formData.append("recaptcha-token", "no-token-available"); - const request = await fetch(BASE_URL + "/student/login", { - method: "POST", - body: formData, - redirect: "manual", - }); - if (request.status != 302 || !request.headers.has("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 didn't return authentication cookies", - ); - } - const cookies = String(request.headers.get("set-cookie")); - this.authCookies = cookies.split(","); - const sessionCookies = parseCookies(cookies); - const sessionID = JSON.parse( - String(sessionCookies["student_session_credentials"]), - ); - this.sessionId = sessionID.session_id; - await this.getNewSessionId(); - const user = await this.getStudentInfo(); - this.studentId = user.data.user.id; - } + /** + * Authenticates with ClassCharts + */ + async login(): Promise { + if (!this.studentCode) throw new Error("Student Code not provided"); + const formData = new URLSearchParams(); + formData.append("_method", "POST"); + formData.append("code", this.studentCode.toUpperCase()); + formData.append("dob", this.dateOfBirth); + formData.append("remember_me", "1"); + formData.append("recaptcha-token", "no-token-available"); + const request = await fetch(BASE_URL + "/student/login", { + method: "POST", + body: formData, + redirect: "manual", + }); + if (request.status != 302 || !request.headers.has("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 didn't return authentication cookies", + ); + } + const cookies = String(request.headers.get("set-cookie")); + this.authCookies = cookies.split(","); + const sessionCookies = parseCookies(cookies); + const sessionID = JSON.parse( + String(sessionCookies["student_session_credentials"]), + ); + this.sessionId = sessionID.session_id; + await this.getNewSessionId(); + const user = await this.getStudentInfo(); + this.studentId = user.data.user.id; + } - /** - * Gets the available items in the current student's rewards shop - * @returns Array of purchasable items - */ - async getRewards(): Promise { - return ( - await this.makeAuthedRequest( - this.API_BASE + "/rewards/" + this.studentId, - { - method: "GET", - }, - ) - ); - } + /** + * Gets the available items in the current student's rewards shop + * @returns Array of purchasable items + */ + async getRewards(): Promise { + return await this.makeAuthedRequest( + this.API_BASE + "/rewards/" + this.studentId, + { + method: "GET", + }, + ); + } - /** - * Purchase a reward item from the current student's rewards shop - * @param itemId number - * @returns An object containing the current student's balance and item ID purchased - */ - async purchaseReward(itemId: number): Promise { - return ( - await this.makeAuthedRequest( - this.API_BASE + "/purchase/" + itemId, - { - method: "POST", - body: `pupil_id=${this.studentId}`, - }, - ) - ); - } + /** + * Purchase a reward item from the current student's rewards shop + * @param itemId number + * @returns An object containing the current student's balance and item ID purchased + */ + async purchaseReward(itemId: number): Promise { + return await this.makeAuthedRequest(this.API_BASE + "/purchase/" + itemId, { + method: "POST", + body: `pupil_id=${this.studentId}`, + }); + } - /** - * Gets the current student's student code - * @param options GetStudentCodeOptions - * @param options.dateOfBirth Date of birth in the format YYYY-MM-DD - * @returns - */ - async getStudentCode( - options: GetStudentCodeOptions, - ): Promise { - const data = await this.makeAuthedRequest(this.API_BASE + "/getcode", { - method: "POST", - body: JSON.stringify({ - date: options.dateOfBirth, - }), - }); - return data; - } + /** + * Gets the current student's student code + * @param options GetStudentCodeOptions + * @param options.dateOfBirth Date of birth in the format YYYY-MM-DD + * @returns + */ + async getStudentCode( + options: GetStudentCodeOptions, + ): Promise { + const data = await this.makeAuthedRequest(this.API_BASE + "/getcode", { + method: "POST", + body: JSON.stringify({ + date: options.dateOfBirth, + }), + }); + return data; + } } diff --git a/src/core/studentClient_test.ts b/src/core/studentClient_test.ts index 0f53f3a..d0639c6 100644 --- a/src/core/studentClient_test.ts +++ b/src/core/studentClient_test.ts @@ -2,23 +2,23 @@ import { assertRejects } from "../../deps_dev.ts"; import { StudentClient } from "../core/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", - ); + 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", - ); + const client = new StudentClient("invalid"); + await assertRejects( + async () => { + await client.login(); + }, + Error, + "Unauthenticated: ClassCharts didn't return authentication cookies", + ); }); diff --git a/src/types.ts b/src/types.ts index 2a27865..7e42d3a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -2,490 +2,490 @@ * Helper type to define response from ClassCharts */ export type ClassChartsResponse = { - data: Data; - meta: Meta; - error?: string; - success: number; + data: Data; + meta: Meta; + error?: string; + success: number; }; export interface Student { - id: number; - name: string; - first_name: string; - last_name: string; - avatar_url: string; - display_behaviour: boolean; - display_parent_behaviour: boolean; - display_homework: boolean; - display_rewards: boolean; - display_detentions: boolean; - display_report_cards: boolean; - display_classes: boolean; - display_announcements: boolean; - display_attendance: boolean; - display_attendance_type: string; - display_attendance_percentage: boolean; - display_activity: boolean; - display_mental_health: boolean; - display_timetable: boolean; - is_disabled: boolean; - display_two_way_communications: boolean; - display_absences: boolean; - can_upload_attachments: boolean | null; - display_event_badges: boolean; - display_avatars: boolean; - display_concern_submission: boolean; - display_custom_fields: boolean; - pupil_concerns_help_text: string; - allow_pupils_add_timetable_notes: boolean; - announcements_count: number; - messages_count: number; - pusher_channel_name: string; - has_birthday: boolean; - has_new_survey: boolean; - survey_id: number | null; - detention_alias_plural_uc: string; + id: number; + name: string; + first_name: string; + last_name: string; + avatar_url: string; + display_behaviour: boolean; + display_parent_behaviour: boolean; + display_homework: boolean; + display_rewards: boolean; + display_detentions: boolean; + display_report_cards: boolean; + display_classes: boolean; + display_announcements: boolean; + display_attendance: boolean; + display_attendance_type: string; + display_attendance_percentage: boolean; + display_activity: boolean; + display_mental_health: boolean; + display_timetable: boolean; + is_disabled: boolean; + display_two_way_communications: boolean; + display_absences: boolean; + can_upload_attachments: boolean | null; + display_event_badges: boolean; + display_avatars: boolean; + display_concern_submission: boolean; + display_custom_fields: boolean; + pupil_concerns_help_text: string; + allow_pupils_add_timetable_notes: boolean; + announcements_count: number; + messages_count: number; + pusher_channel_name: string; + has_birthday: boolean; + has_new_survey: boolean; + survey_id: number | null; + detention_alias_plural_uc: string; } export interface GetStudentInfoData { - user: Student; + user: Student; } export interface GetStudentInfoMeta { - version: string; + version: string; } export type GetStudentInfoResponse = ClassChartsResponse< - GetStudentInfoData, - GetStudentInfoMeta + GetStudentInfoData, + GetStudentInfoMeta >; export interface GetBehaviourOptions { - /** - * From date, in format YYYY-MM-DD - */ - from?: string; - /** - * To date, in format YYYY-MM-DD - */ - to?: string; + /** + * From date, in format YYYY-MM-DD + */ + from?: string; + /** + * To date, in format YYYY-MM-DD + */ + to?: string; } export interface BehaviourTimelinePoint { - positive: number; - negative: number; - name: string; - start: string; - end: string; + positive: number; + negative: number; + name: string; + start: string; + end: string; } export interface BehaviourResponseData { - timeline: Array; - positive_reasons: Record; - negative_reasons: Record; - other_positive: Array; - other_negative: Array; - other_positive_count: Array>; - other_negative_count: Array>; + timeline: Array; + positive_reasons: Record; + negative_reasons: Record; + other_positive: Array; + other_negative: Array; + other_positive_count: Array>; + other_negative_count: Array>; } export interface BehaviourResponseMeta { - start_date: string; - end_date: string; - step_size: string; + start_date: string; + end_date: string; + step_size: string; } export type BehaviourResponse = ClassChartsResponse< - BehaviourResponseData, - BehaviourResponseMeta + BehaviourResponseData, + BehaviourResponseMeta >; export interface GetActivityOptions { - /** - * From date, in format YYYY-MM-DD - */ - from?: string; - /** - * To date, in format YYYY-MM-DD - */ - to?: string; - /** - * ID of the last activityPoint (used in pagination) - */ - last_id?: string; + /** + * From date, in format YYYY-MM-DD + */ + from?: string; + /** + * To date, in format YYYY-MM-DD + */ + to?: string; + /** + * ID of the last activityPoint (used in pagination) + */ + last_id?: string; } export interface ActivityPoint { - id: number; - type: string; - polarity: string; - reason: string; - score: number; - timestamp: string; - timestamp_custom_time: string | null; - style: { - border_color: string | null; - custom_class: string | null; - }; - pupil_name: string; - lesson_name: string | null; - teacher_name: string; - room_name: string | null; - note: string | null; - _can_delete: boolean; - badges: string | undefined; - detention_date: string | null; - detention_time: string | null; - detention_location: string | null; - detention_type: string | null; + id: number; + type: string; + polarity: string; + reason: string; + score: number; + timestamp: string; + timestamp_custom_time: string | null; + style: { + border_color: string | null; + custom_class: string | null; + }; + pupil_name: string; + lesson_name: string | null; + teacher_name: string; + room_name: string | null; + note: string | null; + _can_delete: boolean; + badges: string | undefined; + detention_date: string | null; + detention_time: string | null; + detention_location: string | null; + detention_type: string | null; } export type ActivityResponseData = Array; interface ActivityResponseMeta { - start_date: string; - end_date: string; - last_id: number | boolean; - step_size: string; - detention_alias_uc: string; + start_date: string; + end_date: string; + last_id: number | boolean; + step_size: string; + detention_alias_uc: string; } export type ActivityResponse = ClassChartsResponse< - ActivityResponseData, - ActivityResponseMeta + ActivityResponseData, + ActivityResponseMeta >; export type DisplayDate = "due_date" | "issue_date"; export interface GetHomeworkOptions { - /** - * Way to sort homeworks - * - * Used to sort homeworks by when they are due or when they were issued - * @default "issue_date" - */ - displayDate?: DisplayDate; - /** - * From date, in format YYYY-MM-DD - */ - from?: string; - /** - * To date, in format YYYY-MM-DD - */ - to?: string; + /** + * Way to sort homeworks + * + * Used to sort homeworks by when they are due or when they were issued + * @default "issue_date" + */ + displayDate?: DisplayDate; + /** + * From date, in format YYYY-MM-DD + */ + from?: string; + /** + * To date, in format YYYY-MM-DD + */ + to?: string; } export interface ValidatedHomeworkAttachment { - id: number; - file_name: string; - file: string; - validated_file: string; + id: number; + file_name: string; + file: string; + validated_file: string; } export interface Homework { - lesson: string; - subject: string; - teacher: string; - homework_type: string; - id: number; - title: string; - meta_title: string; - description: string; - issue_date: string; - due_date: string; - completion_time_unit: string; - completion_time_value: string; - publish_time: string; - status: { - id: number; - state: "not_completed" | "late" | "completed" | null; - mark: unknown | null; - mark_relative: number; - ticked: "yes" | "no"; - allow_attachments: boolean; - first_seen_date: string | null; - last_seen_date: string | null; - attachments: Array; - has_feedback: boolean; - }; - validated_links: Array; - validated_attachments: Array; + lesson: string; + subject: string; + teacher: string; + homework_type: string; + id: number; + title: string; + meta_title: string; + description: string; + issue_date: string; + due_date: string; + completion_time_unit: string; + completion_time_value: string; + publish_time: string; + status: { + id: number; + state: "not_completed" | "late" | "completed" | null; + mark: unknown | null; + mark_relative: number; + ticked: "yes" | "no"; + allow_attachments: boolean; + first_seen_date: string | null; + last_seen_date: string | null; + attachments: Array; + has_feedback: boolean; + }; + validated_links: Array; + validated_attachments: Array; } export type HomeworksResponseData = Array; export interface HomeworksResponseMeta { - start_date: string; - end_date: string; - display_type: DisplayDate; - max_files_allowed: number; - allowed_file_types: string[]; - this_week_due_count: number; - this_week_outstanding_count: number; - this_week_completed_count: number; - allow_attachments: boolean; - display_marks: boolean; + start_date: string; + end_date: string; + display_type: DisplayDate; + max_files_allowed: number; + allowed_file_types: string[]; + this_week_due_count: number; + this_week_outstanding_count: number; + this_week_completed_count: number; + allow_attachments: boolean; + display_marks: boolean; } export type HomeworksResponse = ClassChartsResponse< - HomeworksResponseData, - HomeworksResponseMeta + HomeworksResponseData, + HomeworksResponseMeta >; export interface GetLessonsOptions { - /** - * Date to get lessons for, in format YYYY-MM-DD - */ - date: string; + /** + * Date to get lessons for, in format YYYY-MM-DD + */ + date: string; } export interface Lesson { - teacher_name: string; - lesson_name: string; - subject_name: string; - is_alternative_lesson: boolean; - period_name: string; - period_number: string; - room_name: string; - date: string; - start_time: string; - end_time: string; - key: number; - note_abstract: string; - note: string; - pupil_note_abstract: string; - pupil_note: string; - pupil_note_raw: string; + teacher_name: string; + lesson_name: string; + subject_name: string; + is_alternative_lesson: boolean; + period_name: string; + period_number: string; + room_name: string; + date: string; + start_time: string; + end_time: string; + key: number; + note_abstract: string; + note: string; + pupil_note_abstract: string; + pupil_note: string; + pupil_note_raw: string; } export type LessonsResponseData = Lesson[]; interface PeriodMeta { - number: string; - start_time: string; - end_time: string; + number: string; + start_time: string; + end_time: string; } export interface LessonsResponseMeta { - dates: string[]; - timetable_dates: string[]; - periods: PeriodMeta[]; - start_time: string; - end_time: string; + dates: string[]; + timetable_dates: string[]; + periods: PeriodMeta[]; + start_time: string; + end_time: string; } export type LessonsResponse = ClassChartsResponse< - LessonsResponseData, - LessonsResponseMeta + LessonsResponseData, + LessonsResponseMeta >; // Not sure what to call this export interface LessonPupilBehaviour { - reason: string; - score: number; - icon: string; - polarity: string; - timestamp: string; - teacher: { - title: string; - first_name: string; - last_name: string; - }; + reason: string; + score: number; + icon: string; + polarity: string; + timestamp: string; + teacher: { + title: string; + first_name: string; + last_name: string; + }; } export interface PupilEvent { - timestamp: string; - lesson_pupil_behaviour: LessonPupilBehaviour; - event: { - label: string; - }; + timestamp: string; + lesson_pupil_behaviour: LessonPupilBehaviour; + event: { + label: string; + }; } export interface Badge { - id: number; - name: string; - icon: string; - colour: string; - created_date: string; - pupil_badges: Array; - icon_url: string; + id: number; + name: string; + icon: string; + colour: string; + created_date: string; + pupil_badges: Array; + icon_url: string; } export type BadgesResponseData = Array; export type BadgesResponseMeta = []; export type BadgesResponse = ClassChartsResponse< - BadgesResponseData, - BadgesResponseMeta + BadgesResponseData, + BadgesResponseMeta >; export interface Detention { - id: number; - attended: "yes" | "no" | "upscaled" | "pending"; - date: string | null; - length: number | null; - location: string | null; - notes: string | null; - time: string | null; - pupil: { - id: number; - first_name: string; - last_name: string; - school: { - opt_notes_names: "yes" | "no"; - opt_notes_comments: "yes" | "no"; - opt_notes_comments_pupils: "yes" | "no"; - }; - }; - lesson: { - id: number; - name: string; - subject: { - id: number; - name: string; - }; - } | null; - lesson_pupil_behaviour: { - reason: string; - }; - teacher: { - id: number; - first_name: string; - last_name: string; - title: string; - } | null; - detention_type: { - name: string; - }; + id: number; + attended: "yes" | "no" | "upscaled" | "pending"; + date: string | null; + length: number | null; + location: string | null; + notes: string | null; + time: string | null; + pupil: { + id: number; + first_name: string; + last_name: string; + school: { + opt_notes_names: "yes" | "no"; + opt_notes_comments: "yes" | "no"; + opt_notes_comments_pupils: "yes" | "no"; + }; + }; + lesson: { + id: number; + name: string; + subject: { + id: number; + name: string; + }; + } | null; + lesson_pupil_behaviour: { + reason: string; + }; + teacher: { + id: number; + first_name: string; + last_name: string; + title: string; + } | null; + detention_type: { + name: string; + }; } export type DetentionsData = Array; export interface DetentionsMeta { - detention_alias_plural: string; + detention_alias_plural: string; } export type DetentionsResponse = ClassChartsResponse< - DetentionsData, - DetentionsMeta + DetentionsData, + DetentionsMeta >; export interface Announcement { - id: number; - title: string; - description: string | null; - school_name: string; - teacher_name: string; - school_logo: string | null; - sticky: "yes" | "no"; - state: string | null; - timestamp: string; - attachments: Array<{ - filename: string; - url: string; - }>; - for_pupils: Array; - comment_visibility: string; - allow_comments: "yes" | "no"; - allow_reactions: "yes" | "no"; - allow_consent: "yes" | "no"; - priority_pinned: "yes" | "no"; - requires_consent: "yes" | "no"; - can_change_consent: boolean; - consent: unknown | null; - pupil_consents: Array; + id: number; + title: string; + description: string | null; + school_name: string; + teacher_name: string; + school_logo: string | null; + sticky: "yes" | "no"; + state: string | null; + timestamp: string; + attachments: Array<{ + filename: string; + url: string; + }>; + for_pupils: Array; + comment_visibility: string; + allow_comments: "yes" | "no"; + allow_reactions: "yes" | "no"; + allow_consent: "yes" | "no"; + priority_pinned: "yes" | "no"; + requires_consent: "yes" | "no"; + can_change_consent: boolean; + consent: unknown | null; + pupil_consents: Array; } export type AnnouncementsResponse = ClassChartsResponse< - Array, - [] + Array, + [] >; export interface Pupil extends Student { - school_name: string; - school_logo: string; - timezone: string; - display_covid_tests: boolean; - can_record_covid_tests: boolean; - detention_yes_count: number; - detention_no_count: number; - detention_pending_count: number; - detention_upscaled_count: number; - homework_todo_count: number; - homework_late_count: number; - homework_not_completed_count: number; - homework_excused_count: number; - homework_completed_count: number; - homework_submitted_count: number; - announcements_count: number; - messages_count: number; + school_name: string; + school_logo: string; + timezone: string; + display_covid_tests: boolean; + can_record_covid_tests: boolean; + detention_yes_count: number; + detention_no_count: number; + detention_pending_count: number; + detention_upscaled_count: number; + homework_todo_count: number; + homework_late_count: number; + homework_not_completed_count: number; + homework_excused_count: number; + homework_completed_count: number; + homework_submitted_count: number; + announcements_count: number; + messages_count: number; } export type GetPupilsResponse = Array; export interface GetFullActivityOptions { - /** - * From date, in format YYYY-MM-DD - */ - from: string; - /** - * To date, in format YYYY-MM-DD - */ - to: string; + /** + * From date, in format YYYY-MM-DD + */ + from: string; + /** + * To date, in format YYYY-MM-DD + */ + to: string; } export interface GetAttendanceOptions { - /** - * From date, in format YYYY-MM-DD - */ - from: string; - /** - * To date, in format YYYY-MM-DD - */ - to: string; + /** + * From date, in format YYYY-MM-DD + */ + from: string; + /** + * To date, in format YYYY-MM-DD + */ + to: string; } export interface AttendancePeriod { - code: string; - status: "present" | "ignore"; - late_minutes: number | string; - lesson_name?: string; - room_name?: string; + code: string; + status: "present" | "ignore"; + late_minutes: number | string; + lesson_name?: string; + room_name?: string; } export interface AttendanceMeta { - dates: Array; - sessions: Array; - start_date: string; - end_date: string; - percentage: string; - percentage_singe_august: string; + dates: Array; + sessions: Array; + start_date: string; + end_date: string; + percentage: string; + percentage_singe_august: string; } export type AttendanceData = Record>; export type AttendanceResponse = ClassChartsResponse< - AttendanceData, - AttendanceMeta + AttendanceData, + AttendanceMeta >; export type RewardsData = { - id: number; - name: string; - description: string; - photo: string; - price: number; - stock_control: boolean; - stock: number; - can_purchase: boolean; - unable_to_purchase_reason: string; - once_per_pupil: boolean; - purchased: boolean; - purchased_count: string | number; - price_balance_difference: number; + id: number; + name: string; + description: string; + photo: string; + price: number; + stock_control: boolean; + stock: number; + can_purchase: boolean; + unable_to_purchase_reason: string; + once_per_pupil: boolean; + purchased: boolean; + purchased_count: string | number; + price_balance_difference: number; }[]; export interface RewardsMeta { - pupil_score_balance: number; + pupil_score_balance: number; } export type RewardsResponse = ClassChartsResponse; export interface RewardPurchaseData { - single_purchase: "yes" | "no"; - order_id: number; - balance: number; + single_purchase: "yes" | "no"; + order_id: number; + balance: number; } export type RewardPurchaseResponse = ClassChartsResponse< - RewardPurchaseData, - [] + RewardPurchaseData, + [] >; export interface PupilFieldsData { - note: string; - fields: Array<{ - id: number; - name: string; - graphic: string; - value: string; - }>; + note: string; + fields: Array<{ + id: number; + name: string; + graphic: string; + value: string; + }>; } export type PupilFieldsResponse = ClassChartsResponse; @@ -493,18 +493,18 @@ export type PupilFieldsResponse = ClassChartsResponse; export type ChangePasswordResponse = ClassChartsResponse<[], []>; export interface GetStudentCodeOptions { - /** - * Date of birth, in format YYYY-MM-DD - */ - dateOfBirth: string; + /** + * Date of birth, in format YYYY-MM-DD + */ + dateOfBirth: string; } export interface GetStudentCodeResponseData { - code: string; + code: string; } export type GetStudentCodeResponseMeta = []; export type GetStudentCodeResponse = ClassChartsResponse< - GetStudentCodeResponseData, - GetStudentCodeResponseMeta + GetStudentCodeResponseData, + GetStudentCodeResponseMeta >; diff --git a/src/utils/utils.ts b/src/utils/utils.ts index aa24abd..dacf45a 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -4,16 +4,16 @@ * @returns Object of cookies */ export function parseCookies(input: string) { - const output: Record = {}; - const cookies = input.split(","); - for (const cookie of cookies) { - const cookieSplit = cookie.split(";")[0].split("="); - output[leftTrim(decodeURIComponent(cookieSplit[0]))] = decodeURIComponent( - cookieSplit[1], - ); - } - return output; + const output: Record = {}; + const cookies = input.split(","); + for (const cookie of cookies) { + const cookieSplit = cookie.split(";")[0].split("="); + output[leftTrim(decodeURIComponent(cookieSplit[0]))] = decodeURIComponent( + cookieSplit[1], + ); + } + return output; } export function leftTrim(str: string) { - return str.replace(/^\s+/g, ""); + return str.replace(/^\s+/g, ""); } diff --git a/src/utils/utils_test.ts b/src/utils/utils_test.ts index dcf0b5e..49ed07b 100644 --- a/src/utils/utils_test.ts +++ b/src/utils/utils_test.ts @@ -1,37 +1,37 @@ import { assertEquals, assertExists } from "../../deps_dev.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!"); + 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"); + 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, ""); + 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!"); + 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!"); + const input = "Hello world!"; + const output = leftTrim(input); + assertEquals(output, "Hello world!"); });