mirror of
https://github.com/classchartsapi/classcharts-api-js.git
synced 2026-05-14 11:58:13 +00:00
feat: massive overhaul
This commit is contained in:
parent
22e21f9fcd
commit
24208c310e
10 changed files with 704 additions and 583 deletions
27
package.json
27
package.json
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "classcharts-api",
|
||||
"version": "1.6.0",
|
||||
"description": "A javascript wrapper for getting information from the Classcharts API",
|
||||
"description": "A typescript wrapper for getting information from the Classcharts API",
|
||||
"keywords": [
|
||||
"node",
|
||||
"typescript",
|
||||
|
|
@ -31,23 +31,20 @@
|
|||
"dist/**"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.2.3",
|
||||
"@types/jest": "^29.5.0",
|
||||
"@types/node": "^18",
|
||||
"@typescript-eslint/eslint-plugin": "^5.45.0",
|
||||
"@typescript-eslint/parser": "^5.45.0",
|
||||
"eslint": "^8.28.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"jest": "^29.3.1",
|
||||
"jest-extended": "^3.2.0",
|
||||
"prettier": "^2.8.0",
|
||||
"ts-jest": "^29.0.3",
|
||||
"typedoc": "^0.23.21",
|
||||
"typescript": "^4.9.3"
|
||||
"@typescript-eslint/eslint-plugin": "^5.57.1",
|
||||
"@typescript-eslint/parser": "^5.57.1",
|
||||
"eslint": "^8.37.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"jest": "^29.5.0",
|
||||
"jest-extended": "^3.2.4",
|
||||
"prettier": "^2.8.7",
|
||||
"ts-jest": "^29.1.0",
|
||||
"typedoc": "^0.23.28",
|
||||
"typescript": "^5.0.3"
|
||||
},
|
||||
"types": "./dist/index.d.ts",
|
||||
"volta": {
|
||||
"node": "18.12.1"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
|
|
|
|||
1009
pnpm-lock.yaml
generated
1009
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
|
@ -15,7 +15,6 @@ async function main() {
|
|||
await client.login();
|
||||
console.log(
|
||||
await client.getBehaviour({
|
||||
displayDate: "due_date",
|
||||
fromDate: "20/01/2000",
|
||||
toDate: "01/02/2000",
|
||||
})
|
||||
|
|
|
|||
|
|
@ -13,10 +13,9 @@ import type {
|
|||
GetFullActivityOptions,
|
||||
GetHomeworkOptions,
|
||||
GetLessonsOptions,
|
||||
Homework,
|
||||
GetStudentInfoResponse,
|
||||
HomeworksResponse,
|
||||
LessonsResponse,
|
||||
Student,
|
||||
} from "./types";
|
||||
import { PING_INTERVAL } from "./consts";
|
||||
|
||||
|
|
@ -25,7 +24,7 @@ import { PING_INTERVAL } from "./consts";
|
|||
*/
|
||||
export class ClasschartsClient {
|
||||
public studentId = 0;
|
||||
public authCookies: Array<string> | undefined;
|
||||
public authCookies: Array<string>;
|
||||
public sessionId = "";
|
||||
public lastPing = 0;
|
||||
protected API_BASE = "";
|
||||
|
|
@ -35,6 +34,7 @@ export class ClasschartsClient {
|
|||
* @param API_BASE Base API URL, this is different depending if its called as a parent or student
|
||||
*/
|
||||
constructor(API_BASE: string, axiosConfig?: AxiosRequestConfig) {
|
||||
this.authCookies = [];
|
||||
this.API_BASE = API_BASE;
|
||||
this.axios = axios.create({
|
||||
...axiosConfig,
|
||||
|
|
@ -56,7 +56,7 @@ export class ClasschartsClient {
|
|||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
},
|
||||
{ includeMeta: true, revalidateToken: false }
|
||||
{ revalidateToken: false }
|
||||
);
|
||||
this.sessionId = pingData.meta.session_id;
|
||||
this.lastPing = Date.now();
|
||||
|
|
@ -64,7 +64,7 @@ export class ClasschartsClient {
|
|||
public async makeAuthedRequest(
|
||||
path: string,
|
||||
axiosOptions: Omit<AxiosRequestConfig, "path">,
|
||||
options?: { includeMeta?: boolean; revalidateToken?: boolean }
|
||||
options?: { revalidateToken?: boolean }
|
||||
) {
|
||||
if (!this.sessionId) throw new Error("No session ID");
|
||||
if (!options) {
|
||||
|
|
@ -92,23 +92,19 @@ export class ClasschartsClient {
|
|||
if (responseJSON.success == 0) {
|
||||
throw new Error(responseJSON.error);
|
||||
}
|
||||
if (options?.includeMeta) {
|
||||
return responseJSON;
|
||||
}
|
||||
return responseJSON.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets general information about the logged in student
|
||||
* @returns Student object
|
||||
*/
|
||||
async getStudentInfo(): Promise<Student> {
|
||||
async getStudentInfo(): Promise<GetStudentInfoResponse> {
|
||||
const data = await this.makeAuthedRequest(this.API_BASE + "/ping", {
|
||||
method: "POST",
|
||||
data: "include_data=true",
|
||||
});
|
||||
|
||||
return data?.user;
|
||||
return data;
|
||||
}
|
||||
/**
|
||||
* This function is only used for pagination, you likely want client.getFullActivity
|
||||
|
|
@ -134,8 +130,8 @@ export class ClasschartsClient {
|
|||
*/
|
||||
async getFullActivity(
|
||||
options: GetFullActivityOptions
|
||||
): Promise<ActivityResponse> {
|
||||
let data: ActivityResponse = [];
|
||||
): Promise<ActivityResponse["data"]> {
|
||||
let data: ActivityResponse["data"] = [];
|
||||
let prevLast: number | undefined;
|
||||
let gotData = true;
|
||||
while (gotData) {
|
||||
|
|
@ -146,7 +142,7 @@ export class ClasschartsClient {
|
|||
if (prevLast) {
|
||||
params.last_id = String(prevLast);
|
||||
}
|
||||
const fragment = await this.getActivity(params);
|
||||
const fragment = (await this.getActivity(params)).data;
|
||||
if (!fragment || !fragment.length) {
|
||||
gotData = false;
|
||||
} else {
|
||||
|
|
@ -179,30 +175,32 @@ export class ClasschartsClient {
|
|||
* @param options GetHomeworkOptions
|
||||
* @returns Array of homeworks
|
||||
*/
|
||||
async listHomeworks(
|
||||
options?: GetHomeworkOptions
|
||||
): Promise<HomeworksResponse> {
|
||||
async getHomeworks(options?: GetHomeworkOptions): Promise<HomeworksResponse> {
|
||||
const params = new URLSearchParams();
|
||||
if (options?.displayDate)
|
||||
params.append("display_date", String(options?.displayDate));
|
||||
|
||||
options?.fromDate && params.append("from", String(options?.fromDate));
|
||||
options?.toDate && params.append("to", String(options?.toDate));
|
||||
options?.from && params.append("from", String(options?.from));
|
||||
options?.to && params.append("to", String(options?.to));
|
||||
const data: Array<Homework> = await this.makeAuthedRequest(
|
||||
const data: HomeworksResponse = await this.makeAuthedRequest(
|
||||
this.API_BASE + "/homeworks/" + this.studentId + "?" + params.toString(),
|
||||
{
|
||||
method: "GET",
|
||||
}
|
||||
);
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
data[i].description_raw = data[i].description;
|
||||
for (let i = 0; i < data.data.length; i++) {
|
||||
data.data[i].description_raw = data.data[i].description;
|
||||
// homework.lesson.replace(/\\/g, '')
|
||||
data[i].description = data[i].description.replace(/(<([^>]+)>)/gi, "");
|
||||
data[i].description = data[i].description.replace(/ /g, "");
|
||||
data[i].description = data[i].description.trim();
|
||||
data.data[i].description = data.data[i].description.replace(
|
||||
/(<([^>]+)>)/gi,
|
||||
""
|
||||
);
|
||||
data.data[i].description = data.data[i].description.replace(
|
||||
/ /g,
|
||||
""
|
||||
);
|
||||
data.data[i].description = data.data[i].description.trim();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
|
@ -235,45 +233,55 @@ export class ClasschartsClient {
|
|||
);
|
||||
}
|
||||
/**
|
||||
* Lists the logged in student's announcements
|
||||
* Gets the logged in student's announcements
|
||||
* @returns Array of announcements
|
||||
*/
|
||||
async listAnnouncements(): Promise<AnnouncementsResponse> {
|
||||
return await this.makeAuthedRequest(
|
||||
async getAnnouncements(): Promise<AnnouncementsResponse> {
|
||||
return (
|
||||
await this.makeAuthedRequest(
|
||||
this.API_BASE + "/announcements/" + this.studentId,
|
||||
{
|
||||
method: "GET",
|
||||
}
|
||||
);
|
||||
)
|
||||
).data;
|
||||
}
|
||||
/**
|
||||
* Gets the logged in student's detentions
|
||||
* @returns Array of detentions
|
||||
*/
|
||||
async getDetentions(): Promise<DetentionsResponse> {
|
||||
return await this.makeAuthedRequest(
|
||||
return (
|
||||
await this.makeAuthedRequest(
|
||||
this.API_BASE + "/detentions/" + this.studentId,
|
||||
{
|
||||
method: "GET",
|
||||
}
|
||||
);
|
||||
)
|
||||
).data;
|
||||
}
|
||||
/**
|
||||
* Gets the logged in student's attendance
|
||||
* @param options GetAttendanceOptions
|
||||
* @returns Array of dates of attendance
|
||||
*/
|
||||
async listAttendance(
|
||||
async getAttendance(
|
||||
options?: GetAttendanceOptions
|
||||
): Promise<AttendanceResponse> {
|
||||
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(),
|
||||
return (
|
||||
await this.makeAuthedRequest(
|
||||
this.API_BASE +
|
||||
"/attendance/" +
|
||||
this.studentId +
|
||||
"?" +
|
||||
params.toString(),
|
||||
{
|
||||
method: "GET",
|
||||
}
|
||||
);
|
||||
)
|
||||
).data;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,2 @@
|
|||
export * from "./parentClient";
|
||||
export * from "./studentClient";
|
||||
export * from "./types";
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ import { ClasschartsClient } from "./baseClient";
|
|||
import { API_BASE_PARENT, BASE_URL } from "./consts";
|
||||
import { parseCookies } from "./utils";
|
||||
/**
|
||||
* The base client
|
||||
* Parent Client
|
||||
*/
|
||||
export class ClasschartsParentClient extends ClasschartsClient {
|
||||
export class ParentClient extends ClasschartsClient {
|
||||
private password = "";
|
||||
private email = "";
|
||||
// @ts-expect-error Init in .login
|
||||
|
|
@ -52,7 +52,7 @@ export class ClasschartsParentClient extends ClasschartsClient {
|
|||
throw new Error("Unauthenticated: Classcharts returned an error");
|
||||
|
||||
const cookies = String(request.headers["set-cookie"]);
|
||||
this.authCookies = cookies.split(";");
|
||||
// this.authCookies = cookies.split(";");
|
||||
const sessionCookies = parseCookies(cookies);
|
||||
const sessionID = JSON.parse(
|
||||
String(sessionCookies["parent_session_credentials"])
|
||||
|
|
@ -64,7 +64,7 @@ export class ClasschartsParentClient extends ClasschartsClient {
|
|||
}
|
||||
/**
|
||||
* Get Pupil details
|
||||
* @returns an array fo Pupils connected to this parent's account
|
||||
* @returns an array of Pupils connected to this parent's account
|
||||
*/
|
||||
async getPupils(): Promise<GetPupilsResponse> {
|
||||
return this.makeAuthedRequest(this.API_BASE + "/pupils", {
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ import { API_BASE_STUDENT, BASE_URL } from "./consts";
|
|||
import { ClasschartsClient } from "./baseClient";
|
||||
import { parseCookies } from "./utils";
|
||||
/**
|
||||
* The base client
|
||||
* Student Client
|
||||
*/
|
||||
|
||||
export class ClasschartsStudentClient extends ClasschartsClient {
|
||||
export class StudentClient extends ClasschartsClient {
|
||||
public studentCode = "";
|
||||
public dateOfBirth = "";
|
||||
|
||||
|
|
@ -57,6 +57,6 @@ export class ClasschartsStudentClient extends ClasschartsClient {
|
|||
this.sessionId = sessionID.session_id;
|
||||
await this.getNewSessionId();
|
||||
const user = await this.getStudentInfo();
|
||||
this.studentId = user.id;
|
||||
this.studentId = user.data.user.id;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
106
src/types.ts
106
src/types.ts
|
|
@ -1,4 +1,11 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
type ClassChartsResponse<T, E> = {
|
||||
data: T;
|
||||
meta: E;
|
||||
error?: string;
|
||||
success: number;
|
||||
};
|
||||
|
||||
export interface Student {
|
||||
id: number;
|
||||
name: string;
|
||||
|
|
@ -37,6 +44,18 @@ export interface Student {
|
|||
survey_id: number | null;
|
||||
detention_alias_plural_uc: string;
|
||||
}
|
||||
interface GetStudentInfoData {
|
||||
user: Student;
|
||||
}
|
||||
|
||||
interface GetStudentInfoMeta {
|
||||
version: string;
|
||||
}
|
||||
export type GetStudentInfoResponse = ClassChartsResponse<
|
||||
GetStudentInfoData,
|
||||
GetStudentInfoMeta
|
||||
>;
|
||||
|
||||
export interface GetBehaviourOptions {
|
||||
/**
|
||||
* From date, in format YYYY-MM-DD
|
||||
|
|
@ -47,6 +66,7 @@ export interface GetBehaviourOptions {
|
|||
*/
|
||||
to?: string;
|
||||
}
|
||||
|
||||
export interface BehaviourTimelinePoint {
|
||||
positive: number;
|
||||
negative: number;
|
||||
|
|
@ -54,7 +74,7 @@ export interface BehaviourTimelinePoint {
|
|||
start: string;
|
||||
end: string;
|
||||
}
|
||||
export interface BehaviourResponse {
|
||||
interface BehaviourResponseData {
|
||||
timeline: Array<BehaviourTimelinePoint>;
|
||||
positive_reasons: Record<string, number>;
|
||||
negative_reasons: Record<string, number>;
|
||||
|
|
@ -63,6 +83,16 @@ export interface BehaviourResponse {
|
|||
other_positive_count: Array<Record<string, number>>;
|
||||
other_negative_count: Array<Record<string, number>>;
|
||||
}
|
||||
interface BehaviourResponseMeta {
|
||||
start_date: string;
|
||||
end_date: string;
|
||||
step_size: string;
|
||||
}
|
||||
export type BehaviourResponse = ClassChartsResponse<
|
||||
BehaviourResponseData,
|
||||
BehaviourResponseMeta
|
||||
>;
|
||||
|
||||
export interface GetActivityOptions {
|
||||
/**
|
||||
* From date, in format YYYY-MM-DD
|
||||
|
|
@ -77,6 +107,7 @@ export interface GetActivityOptions {
|
|||
*/
|
||||
last_id?: string;
|
||||
}
|
||||
|
||||
export interface ActivityPoint {
|
||||
id: number;
|
||||
type: string;
|
||||
|
|
@ -101,21 +132,25 @@ export interface ActivityPoint {
|
|||
detention_location: string | null;
|
||||
detention_type: string | null;
|
||||
}
|
||||
export type ActivityResponse = Array<ActivityPoint>;
|
||||
type ActivityResponseData = Array<ActivityPoint>;
|
||||
interface ActivityResponseMeta {
|
||||
start_date: string;
|
||||
end_date: string;
|
||||
last_id: boolean;
|
||||
step_size: string;
|
||||
detention_alias_uc: string;
|
||||
}
|
||||
export type ActivityResponse = ClassChartsResponse<
|
||||
ActivityResponseData,
|
||||
ActivityResponseMeta
|
||||
>;
|
||||
|
||||
export type DisplayDate = "due_date" | "issue_date";
|
||||
export interface GetHomeworkOptions {
|
||||
/**
|
||||
* Way to sort homeworks
|
||||
*/
|
||||
displayDate?: DisplayDate;
|
||||
/**
|
||||
* @deprecated Use "from" instead
|
||||
*/
|
||||
fromDate?: string;
|
||||
/**
|
||||
* @deprecated Use "to" instead
|
||||
*/
|
||||
toDate?: string;
|
||||
/**
|
||||
* From date, in format YYYY-MM-DD
|
||||
*/
|
||||
|
|
@ -161,7 +196,24 @@ export interface Homework {
|
|||
validated_links: Array<any>;
|
||||
validated_attachments: Array<ValidatedHomeworkAttachment>;
|
||||
}
|
||||
export type HomeworksResponse = Array<Homework>;
|
||||
type HomeworksResponseData = Array<Homework>;
|
||||
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;
|
||||
}
|
||||
export type HomeworksResponse = ClassChartsResponse<
|
||||
HomeworksResponseData,
|
||||
HomeworksResponseMeta
|
||||
>;
|
||||
|
||||
export interface GetLessonsOptions {
|
||||
/**
|
||||
* Date to get lessons for, in format YYYY-MM-DD
|
||||
|
|
@ -186,7 +238,24 @@ export interface Lesson {
|
|||
pupil_note: string;
|
||||
pupil_note_raw: string;
|
||||
}
|
||||
export type LessonsResponse = Array<Lesson>;
|
||||
type LessonsResponseData = Lesson[];
|
||||
interface PeriodMeta {
|
||||
number: string;
|
||||
start_time: string;
|
||||
end_time: string;
|
||||
}
|
||||
interface LessonsResponseMeta {
|
||||
dates: string[];
|
||||
timetable_dates: string[];
|
||||
periods: PeriodMeta[];
|
||||
start_time: string;
|
||||
end_time: string;
|
||||
}
|
||||
export type LessonsResponse = ClassChartsResponse<
|
||||
LessonsResponseData,
|
||||
LessonsResponseMeta
|
||||
>;
|
||||
|
||||
// Not sure what to call this
|
||||
export interface LessonPupilBehaviour {
|
||||
reason: string;
|
||||
|
|
@ -216,7 +285,12 @@ export interface Badge {
|
|||
pupil_badges: Array<PupilEvent>;
|
||||
icon_url: string;
|
||||
}
|
||||
export type BadgesResponse = Array<Badge>;
|
||||
type BadgesResponseData = Array<Badge>;
|
||||
type BadgesResponseMeta = [];
|
||||
export type BadgesResponse = ClassChartsResponse<
|
||||
BadgesResponseData,
|
||||
BadgesResponseMeta
|
||||
>;
|
||||
|
||||
export interface Detention {
|
||||
id: number;
|
||||
|
|
@ -257,6 +331,7 @@ export interface Detention {
|
|||
name: string;
|
||||
};
|
||||
}
|
||||
// TODO: Update typings to include meta response. Currently not possible since I don't have access
|
||||
export type DetentionsResponse = Array<Detention>;
|
||||
|
||||
export interface Announcement {
|
||||
|
|
@ -285,6 +360,7 @@ export interface Announcement {
|
|||
pupil_consents: Array<any>;
|
||||
}
|
||||
|
||||
// TODO: Update typings to include meta response. Currently not possible since I don't have access
|
||||
export type AnnouncementsResponse = Array<Announcement>;
|
||||
|
||||
export interface Pupil extends Student {
|
||||
|
|
@ -342,5 +418,5 @@ export interface AttendanceDate {
|
|||
late_minutes: number;
|
||||
};
|
||||
}
|
||||
|
||||
export type AttendanceResponse = Array<Record<string, AttendanceDate>>;
|
||||
// TODO: Update typings to include meta response. Currently not possible since I don't have access
|
||||
export type AttendanceResponse = Record<string, AttendanceDate>[];
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { ClasschartsStudentClient } from "../src";
|
||||
import { StudentClient } from "../src";
|
||||
import { code, dob } from "./config.json";
|
||||
import "jest-extended";
|
||||
const client = new ClasschartsStudentClient(code, dob);
|
||||
const client = new StudentClient(code, dob);
|
||||
jest.setTimeout(10000);
|
||||
|
||||
test("client logs in with correct credentials", () => {
|
||||
|
|
@ -9,7 +9,7 @@ test("client logs in with correct credentials", () => {
|
|||
});
|
||||
|
||||
test("client fails to login with incorrect credentials", () => {
|
||||
const fakeClient = new ClasschartsStudentClient("rewrew", "123");
|
||||
const fakeClient = new StudentClient("rewrew", "123");
|
||||
return expect(fakeClient.login()).rejects.toThrowError();
|
||||
});
|
||||
|
||||
|
|
@ -18,7 +18,7 @@ test("client returns student data", () => {
|
|||
});
|
||||
|
||||
test("client returns activity data", () => {
|
||||
return expect(client.getActivity()).resolves.toBeArray();
|
||||
return expect(client.getActivity()).resolves.toBeObject();
|
||||
});
|
||||
|
||||
test("client returns full activity", () => {
|
||||
|
|
@ -36,9 +36,9 @@ test("client returns behaviour data", () => {
|
|||
});
|
||||
|
||||
test("client returns homework data", () => {
|
||||
return expect(client.listHomeworks()).resolves.toBeArray();
|
||||
return expect(client.getHomeworks()).resolves.toBeObject();
|
||||
});
|
||||
|
||||
test("client returns badges", () => {
|
||||
expect(client.getBadges()).resolves.toBeArray();
|
||||
expect(client.getBadges()).resolves.toBeObject();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -89,5 +89,8 @@
|
|||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
// "skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
},
|
||||
"exclude": ["tests", "dist"]
|
||||
"exclude": [
|
||||
"tests",
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue