diff --git a/src/api.ts b/src/api.ts index f676f7b..99e97c2 100644 --- a/src/api.ts +++ b/src/api.ts @@ -4,52 +4,52 @@ */ import { ClasschartsClient } from '.' import { - ActivityResponse, - BehaviourResponse, - GetActivityOptions, - GetBehaviourOptions, - GetHomeworkOptions, - GetLessonsOptions, - HomeworksResponse, - LessonsResponse, - Student, + ActivityResponse, + BehaviourResponse, + GetActivityOptions, + GetBehaviourOptions, + GetHomeworkOptions, + GetLessonsOptions, + HomeworksResponse, + LessonsResponse, + Student, } from './types' export async function getStudentInfo( - studentCode: string, - dateOfBirth: string | null + studentCode: string, + dateOfBirth: string | null ): Promise { - const client = new ClasschartsClient(studentCode, dateOfBirth) - return await client.getStudentInfo() + const client = new ClasschartsClient(studentCode, dateOfBirth) + return await client.getStudentInfo() } export async function getActivity( - studentCode: string, - dateOfBirth: string | null, - options: GetActivityOptions | null + studentCode: string, + dateOfBirth: string | null, + options: GetActivityOptions | null ): Promise { - const client = new ClasschartsClient(studentCode, dateOfBirth) - return await client.getActivity(options) + const client = new ClasschartsClient(studentCode, dateOfBirth) + return await client.getActivity(options) } export async function getBehaviour( - studentCode: string, - dateOfBirth: string | null, - options: GetBehaviourOptions | null + studentCode: string, + dateOfBirth: string | null, + options: GetBehaviourOptions | null ): Promise { - const client = new ClasschartsClient(studentCode, dateOfBirth) - return await client.getBehaviour(options) + const client = new ClasschartsClient(studentCode, dateOfBirth) + return await client.getBehaviour(options) } export async function listHomeworks( - studentCode: string, - dateOfBirth: string | null, - options: GetHomeworkOptions | null + studentCode: string, + dateOfBirth: string | null, + options: GetHomeworkOptions | null ): Promise { - const client = new ClasschartsClient(studentCode, dateOfBirth) - return await client.listHomeworks(options) + const client = new ClasschartsClient(studentCode, dateOfBirth) + return await client.listHomeworks(options) } export async function getLessons( - studentCode: string, - dateOfBirth: string | null, - options: GetLessonsOptions + studentCode: string, + dateOfBirth: string | null, + options: GetLessonsOptions ): Promise { - const client = new ClasschartsClient(studentCode, dateOfBirth) - return await client.getLessons(options) + const client = new ClasschartsClient(studentCode, dateOfBirth) + return await client.getLessons(options) } diff --git a/src/client.ts b/src/client.ts index 467d4f8..dcf0ff7 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,191 +1,191 @@ import Undici from 'undici' import { RequestOptions } from 'undici/types/dispatcher' import { - ActivityResponse, - BehaviourResponse, - GetActivityOptions, - GetBehaviourOptions, - GetHomeworkOptions, - GetLessonsOptions, - Homework, - HomeworksResponse, - LessonsResponse, - Student, + ActivityResponse, + BehaviourResponse, + GetActivityOptions, + GetBehaviourOptions, + GetHomeworkOptions, + GetLessonsOptions, + Homework, + HomeworksResponse, + LessonsResponse, + Student, } from './types' import { API_BASE, BASE_URL } from './consts' /** * The base client */ export class ClasschartsClient { - public studentCode = '' - public dateOfBirth = '' - public studentId = 0 - public studentName = '' - private authCookies: Array | undefined - private sessionId = '' - /** - * - * @param studentCode Classcharts student code - * @param dateOfBirth Student's date of birth - */ - constructor(studentCode: string, dateOfBirth: string | null) { - this.studentCode = String(studentCode) - this.dateOfBirth = String(dateOfBirth) - } - private async makeAuthedRequest( - path: string, - options: Omit - ) { - if (!this.authCookies) throw new Error('Not authenticated') - const requestOptions: Omit = { - ...options, - headers: { - Cookie: this.authCookies.join(';'), - authorization: 'Basic ' + this.sessionId, - }, - } - const request = await Undici.request(path, requestOptions) - let responseJSON - try { - responseJSON = await request.body.json() - } catch (err) { - throw new Error('Invalid JSON response, check your dates') - } - if (responseJSON.success == 0) { - throw new Error(responseJSON.error) - } - return responseJSON.data - } - /** - * Initialises the client and authenticates with classcharts - */ - async init(): Promise { - if (!this.studentCode) throw new Error('Student Code not inputted') - 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-avaliable') - const request = await Undici.request(BASE_URL + '/student/login', { - method: 'POST', - body: formData.toString(), - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - }) - if (request.statusCode != 302 || !request.headers['set-cookie']) - throw new Error('Unauthenticated: Classcharts returned an error') - let cookies = request.headers['set-cookie'] - for (let i = 0; i < cookies.length; i++) { - cookies[i] = cookies[i].substring(0, cookies[i].indexOf(';')) - } - this.authCookies = cookies - let sessionID: any = decodeURI(cookies[2]) - .replace(/%3A/g, ':') - .replace(/%2C/g, ',') - sessionID = JSON.parse( - sessionID.substring(sessionID.indexOf('{'), sessionID.length) - ) - this.sessionId = sessionID.session_id - const user = await this.getStudentInfo() - this.studentId = user.id - this.studentName = user.name - } - /** - * Gets general information about the logged in student - * @returns Student object - */ - async getStudentInfo(): Promise { - if (!this.authCookies) throw new Error('Not authenticated') - const data = await this.makeAuthedRequest(API_BASE + '/ping', { - method: 'POST', - body: 'include_date=true', - }) - return data?.user - } - /** - * Get's the logged in student's general activity - * @param options GetActivityOptions - * @returns Activity data - */ - async getActivity( - options: GetActivityOptions | null - ): Promise { - const params = new URLSearchParams() - options?.from && params.append('form', options?.from) - options?.to && params.append('to', options?.to) - return this.makeAuthedRequest( - API_BASE + '/activity/' + this.sessionId + '?' + params.toString(), - { - method: 'GET', - } - ) - } - /** - * Gets the logged in students behaviour points - * @param options GetBehaviourOptions - * @returns Array of behaviour points - */ - async getBehaviour( - options: GetBehaviourOptions | null - ): Promise { - const params = new URLSearchParams() - options?.from && params.append('form', options?.from) - options?.to && params.append('to', options?.to) - options?.last_id && params.append('last_id', options?.last_id) - return await this.makeAuthedRequest( - API_BASE + '/behaviour/' + this.studentId + '?' + params.toString(), - { - method: 'GET', - } - ) - } - /** - * Gets a list of the logged in student's homeworks - * @param options GetHomeworkOptions - * @returns Array of homeworks - */ - async listHomeworks( - options: GetHomeworkOptions | null - ): Promise { - if (!this.authCookies) throw new Error('Not authenticated') - const params = new URLSearchParams() - params.append('display_date', String(options?.displayDate)) - options?.fromDate && params.append('from', String(options?.fromDate)) - options?.toDate && params.append('to', String(options?.toDate)) - let data: Array = await this.makeAuthedRequest( - API_BASE + '/homeworks/' + this.studentId + '?' + params.toString(), - { - method: 'GET', - } - ) - for (let i = 0; i < data.length; i++) { - // 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() - } - return data - } - /** - * Gets the logged in student's lessons for a day - * @param options GetLessonsOptions - * @returns Array of lessons - */ - async getLessons(options: GetLessonsOptions): Promise { - if (!this.authCookies) throw new Error('Not authenticated') - if (!options?.date) throw new Error('No date specified') - const params = new URLSearchParams() - params.append('date', String(options?.date)) - return await this.makeAuthedRequest( - API_BASE + '/timetable/' + this.studentId + '?' + params.toString(), - { - method: 'GET', - } - ) - } + public studentCode = '' + public dateOfBirth = '' + public studentId = 0 + public studentName = '' + private authCookies: Array | undefined + private sessionId = '' + /** + * + * @param studentCode Classcharts student code + * @param dateOfBirth Student's date of birth + */ + constructor(studentCode: string, dateOfBirth: string) { + this.studentCode = String(studentCode) + this.dateOfBirth = String(dateOfBirth) + } + private async makeAuthedRequest( + path: string, + options: Omit + ) { + if (!this.authCookies) throw new Error('Not authenticated') + const requestOptions: Omit = { + ...options, + headers: { + Cookie: this.authCookies.join(';'), + authorization: 'Basic ' + this.sessionId, + }, + } + const request = await Undici.request(path, requestOptions) + let responseJSON + try { + responseJSON = await request.body.json() + } catch (err) { + throw new Error('Invalid JSON response, check your dates') + } + if (responseJSON.success == 0) { + throw new Error(responseJSON.error) + } + return responseJSON.data + } + /** + * Initialises the client and authenticates with classcharts + */ + async init(): Promise { + if (!this.studentCode) throw new Error('Student Code not inputted') + 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-avaliable') + const request = await Undici.request(BASE_URL + '/student/login', { + method: 'POST', + body: formData.toString(), + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + }) + if (request.statusCode != 302 || !request.headers['set-cookie']) + throw new Error('Unauthenticated: Classcharts returned an error') + let cookies = request.headers['set-cookie'] + for (let i = 0; i < cookies.length; i++) { + cookies[i] = cookies[i].substring(0, cookies[i].indexOf(';')) + } + this.authCookies = cookies + let sessionID: any = decodeURI(cookies[2]) + .replace(/%3A/g, ':') + .replace(/%2C/g, ',') + sessionID = JSON.parse( + sessionID.substring(sessionID.indexOf('{'), sessionID.length) + ) + this.sessionId = sessionID.session_id + const user = await this.getStudentInfo() + this.studentId = user.id + this.studentName = user.name + } + /** + * Gets general information about the logged in student + * @returns Student object + */ + async getStudentInfo(): Promise { + if (!this.authCookies) throw new Error('Not authenticated') + const data = await this.makeAuthedRequest(API_BASE + '/ping', { + method: 'POST', + body: 'include_date=true', + }) + return data?.user + } + /** + * Get's the logged in student's general activity + * @param options GetActivityOptions + * @returns Activity data + */ + async getActivity( + options: GetActivityOptions | null + ): Promise { + const params = new URLSearchParams() + options?.from && params.append('form', options?.from) + options?.to && params.append('to', options?.to) + return this.makeAuthedRequest( + API_BASE + '/activity/' + this.sessionId + '?' + params.toString(), + { + method: 'GET', + } + ) + } + /** + * Gets the logged in students behaviour points + * @param options GetBehaviourOptions + * @returns Array of behaviour points + */ + async getBehaviour( + options: GetBehaviourOptions | null + ): Promise { + const params = new URLSearchParams() + options?.from && params.append('form', options?.from) + options?.to && params.append('to', options?.to) + options?.last_id && params.append('last_id', options?.last_id) + return await this.makeAuthedRequest( + API_BASE + '/behaviour/' + this.studentId + '?' + params.toString(), + { + method: 'GET', + } + ) + } + /** + * Gets a list of the logged in student's homeworks + * @param options GetHomeworkOptions + * @returns Array of homeworks + */ + async listHomeworks( + options: GetHomeworkOptions | null + ): Promise { + if (!this.authCookies) throw new Error('Not authenticated') + const params = new URLSearchParams() + params.append('display_date', String(options?.displayDate)) + options?.fromDate && params.append('from', String(options?.fromDate)) + options?.toDate && params.append('to', String(options?.toDate)) + let data: Array = await this.makeAuthedRequest( + API_BASE + '/homeworks/' + this.studentId + '?' + params.toString(), + { + method: 'GET', + } + ) + for (let i = 0; i < data.length; i++) { + // 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() + } + return data + } + /** + * Gets the logged in student's lessons for a day + * @param options GetLessonsOptions + * @returns Array of lessons + */ + async getLessons(options: GetLessonsOptions): Promise { + if (!this.authCookies) throw new Error('Not authenticated') + if (!options?.date) throw new Error('No date specified') + const params = new URLSearchParams() + params.append('date', String(options?.date)) + return await this.makeAuthedRequest( + API_BASE + '/timetable/' + this.studentId + '?' + params.toString(), + { + method: 'GET', + } + ) + } } diff --git a/src/release.ts b/src/release.ts index 41c5f1f..ea9449b 100644 --- a/src/release.ts +++ b/src/release.ts @@ -5,25 +5,25 @@ import fs from 'fs' // It will not be included in the npm package. function main() { - const source = fs - .readFileSync(__dirname + '/../package.json') - .toString('utf-8') - const sourceObj = JSON.parse(source) - sourceObj.scripts = {} - sourceObj.devDependencies = {} - if (sourceObj.main.startsWith('dist/')) { - sourceObj.main = sourceObj.main.slice(5) - } - fs.writeFileSync( - __dirname + '/package.json', - Buffer.from(JSON.stringify(sourceObj, null, 2), 'utf-8') - ) - fs.writeFileSync( - __dirname + '/version.txt', - Buffer.from(sourceObj.version, 'utf-8') - ) - fs.copyFileSync(__dirname + '/../.npmignore', __dirname + '/.npmignore') - fs.copyFileSync(__dirname + '/../README.MD', __dirname + '/README.MD') + const source = fs + .readFileSync(__dirname + '/../package.json') + .toString('utf-8') + const sourceObj = JSON.parse(source) + sourceObj.scripts = {} + sourceObj.devDependencies = {} + if (sourceObj.main.startsWith('dist/')) { + sourceObj.main = sourceObj.main.slice(5) + } + fs.writeFileSync( + __dirname + '/package.json', + Buffer.from(JSON.stringify(sourceObj, null, 2), 'utf-8') + ) + fs.writeFileSync( + __dirname + '/version.txt', + Buffer.from(sourceObj.version, 'utf-8') + ) + fs.copyFileSync(__dirname + '/../.npmignore', __dirname + '/.npmignore') + fs.copyFileSync(__dirname + '/../README.MD', __dirname + '/README.MD') } main() diff --git a/src/types.d.ts b/src/types.d.ts index 5345f49..30430ad 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -1,145 +1,145 @@ 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: string | 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: string | 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 GetActivityOptions { - from: string | undefined - to: string | undefined + from: string | undefined + to: string | undefined } export interface ActivityTimelinePoint { - positive: number - negative: number - name: string - start: string - end: string + positive: number + negative: number + name: string + start: string + end: string } export interface ActivityResponse { - timeline: Array - positiveReasons: Record - negative_reasons: Record - other_positive: Array - other_negative: Array - other_positive_count: Array - other_negative_count: Array + timeline: Array + positiveReasons: Record + negative_reasons: Record + other_positive: Array + other_negative: Array + other_positive_count: Array + other_negative_count: Array } export interface GetBehaviourOptions { - from: string | undefined - to: string | undefined - last_id: string | undefined + from: string | undefined + to: string | undefined + last_id: string | undefined } export interface BehaviourPoint { - 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 - teacher_name: string - room_name: string | null - note: string - _can_delete: string - 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 + teacher_name: string + room_name: string | null + note: string + _can_delete: string + detention_date: string | null + detention_time: string | null + detention_location: string | null + detention_type: string | null } export type BehaviourResponse = Array export type DisplayDate = 'due_date' | 'issue_date' export interface GetHomeworkOptions { - displayDate: DisplayDate - fromDate: string - toDate: string + displayDate: DisplayDate + fromDate: string + toDate: 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: null - mark: null - mark_relative: number - ticked: boolean - allow_attachments: string - first_seen_date: string - last_seen_date: string - 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: null + mark: null + mark_relative: number + ticked: boolean + allow_attachments: string + first_seen_date: string + last_seen_date: string + attachments: Array + has_feedback: boolean + } + validated_links: Array + validated_attachments: Array } export type HomeworksResponse = Array export interface GetLessonsOptions { - date: string + 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 LessonsResponse = Array