1
0
Fork 0
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:
James Cook 2023-04-07 13:47:08 +01:00
parent 22e21f9fcd
commit 24208c310e
10 changed files with 704 additions and 583 deletions

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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",
})

View file

@ -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(/&nbsp;/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(
/&nbsp;/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;
}
}

View file

@ -1,3 +1,2 @@
export * from "./parentClient";
export * from "./studentClient";
export * from "./types";

View file

@ -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", {

View file

@ -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;
}
}

View file

@ -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>[];

View file

@ -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();
});

View file

@ -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"
]
}