diff --git a/src/current-timetable.ts b/src/current-timetable.ts new file mode 100644 index 0000000..65a86b8 --- /dev/null +++ b/src/current-timetable.ts @@ -0,0 +1,54 @@ +import { Lesson } from './types'; +import { defined } from './utils'; + +interface CurrentTimetableCard { + type: 'card'; + date: string; + uniperiod: string; + starttime: string; + endtime: string; + subjectid: string; + classids: string[] + groupnames: string[]; + igroupid: string; + teacherids: string[]; + classroomids: string[]; + colors: string[]; + cellSlices: string; + cellOrder: number; + removed?: boolean; +} + +type CurrentTimetableItem = CurrentTimetableCard; + +export function parseCurrentTimetable(json: string): { + lessons: Lesson[]; +} { + const body = JSON.parse(json); + if (body === null || typeof body !== 'object') throw new Error('current timetable response should be an object'); + if (body.r === null || typeof body.r !== 'object') { + throw new Error('current timetable response should have a field "r"'); + } + if (!(body.r.ttitems instanceof Array)) throw new Error('current timetable response should have an array "r.ttitems"'); + const items = body.r.ttitems as CurrentTimetableItem[]; + const lessons = items.map((item): Lesson | undefined => { + if (item.type !== 'card') return undefined; + return { + type: 'lesson', + date: item.date, + periodId: item.uniperiod, + startTime: item.starttime, + endTime: item.endtime, + classIds: item.classids, + classroomIds: item.classroomids, + color: item.colors[0] ?? null, + groupNames: item.groupnames.filter((name) => name !== ''), + subjectId: item.subjectid, + teacherIds: item.teacherids, + removed: item.removed ?? false, + }; + }).filter(defined); + return { + lessons, + }; +} diff --git a/src/index.ts b/src/index.ts index 6bf15f9..edad9cd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,5 @@ export * from './substitutions'; +export * from './maindbi'; +export * from './current-timetable'; export * from './url'; export * from './types'; diff --git a/src/maindbi.ts b/src/maindbi.ts new file mode 100644 index 0000000..c0c19ce --- /dev/null +++ b/src/maindbi.ts @@ -0,0 +1,91 @@ +import { MainDbi } from './types.js'; + +interface MainDbiTableRows { + teachers: { + id: string; + short: string; + }; + + subjects: { + id: string; + name: string; + short: string; + }; + + classrooms: { + id: string; + short: string; + }; + + classes: { + id: string; + name: string; + short: string; + }; + + periods: { + id: string; + name: string; + starttime: string; + endtime: string; + }; +} + +type MainDbiTable<ID extends keyof MainDbiTableRows = keyof MainDbiTableRows> = ID extends any ? { + id: ID; + data_rows: MainDbiTableRows[ID][]; +} : never; + +interface MainDbiResponse { + type: 'maindbi'; + dbid: string; + tables: MainDbiTable[]; +} + +export function parseMainDbi(json: string): MainDbi { + const body = JSON.parse(json); + if (body === null || typeof body !== 'object') throw new Error('maindbi response should be an object'); + if (body.r === null || typeof body.r !== 'object') throw new Error('maindbi response should have a field "r"'); + if (body.r.type !== 'maindbi') throw new Error('"r.type" should be equal to "maindbi" in maindbi response'); + const response = body.r as MainDbiResponse; + const result: MainDbi = { + classes: {}, + classrooms: {}, + subjects: {}, + teachers: {}, + periods: {}, + }; + + const mappers: { + [ID in keyof MainDbiTableRows]: (row: MainDbiTableRows[ID]) => MainDbi[ID][string]; + } = { + classes: (row) => ({ + name: row.name, + short: row.short, + }), + teachers: (row) => ({ + name: row.short, + }), + classrooms: (row) => ({ + name: row.short, + }), + periods: (row) => ({ + name: row.name, + startTime: row.starttime, + endTime: row.endtime, + }), + subjects: (row) => ({ + name: row.name, + short: row.short, + }), + }; + response.tables.forEach((table) => { + const resultTable = result[table.id]; + const mapper = mappers[table.id]; + table.data_rows.forEach((row) => { + resultTable[row.id] = mapper(row as any); + }); + }); + + return result; +} diff --git a/src/types.ts b/src/types.ts index cad36c5..27b5b62 100644 --- a/src/types.ts +++ b/src/types.ts @@ -18,9 +18,57 @@ export interface SubstitutionSection { name: string; changes: Substitution[]; } + export interface Substitutions { date: string; absentTeachers: Absence[]; absentClasses: Absence[]; sections: SubstitutionSection[]; } + +interface Teacher { + name: string; +} + +interface Subject { + name: string; + short: string; +} + +interface Classroom { + name: string; +} + +interface Class { + name: string; + short: string; +} + +interface Period { + name: string; + startTime: string; + endTime: string; +} + +export interface MainDbi { + teachers: Record<string, Teacher>; + subjects: Record<string, Subject>; + classrooms: Record<string, Classroom>; + classes: Record<string, Class>; + periods: Record<string, Period>; +} + +export interface Lesson { + type: 'lesson'; + date: string; + periodId: string; + startTime: string; + endTime: string; + subjectId: string; + classIds: string[]; + groupNames: string[]; + teacherIds: string[]; + classroomIds: string[]; + color: string | null; + removed: boolean; +} diff --git a/src/url.ts b/src/url.ts index 610370f..e736be1 100644 --- a/src/url.ts +++ b/src/url.ts @@ -3,7 +3,51 @@ export const getSubstitutionsUrl = () => '/substitution/server/viewer.js?__func= export const getSubstitutionsBody = (date: string, teachersMode = false) => ({ __args: [ null, - { date: date, mode: teachersMode ? 'teachers' : 'classes' } + { date, mode: teachersMode ? 'teachers' : 'classes' }, ], - __gsh: '00000000' + __gsh: '00000000', +}); + +export const getMainDbiBody = (schoolYear: number) => ({ + __args: [ + null, + schoolYear, + {}, + { + op: 'fetch', + needed_part: { + teachers: ['short'], + subjects: ['short', 'name'], + classrooms: ['short'], + classes: ['short', 'name'], + periods: ['name', 'starttime', 'endtime'], + }, + needed_combos: {}, + }, + ], + __gsh: '00000000', +}); + +export const getCurrentTimetableBody = ( + schoolYear: number, + dateFrom: string, + dateTo: string, + table: 'classes' | 'teachers' | 'classrooms' | 'subjects', + id: string, +) => ({ + __args: [ + null, + { + year: schoolYear, + datefrom: dateFrom, + dateto: dateTo, + table, + id, + showColors: true, + showIgroupsInClasses: false, + showOrig: true, + log_module: 'CurrentTTView', + }, + ], + __gsh: '00000000', }); diff --git a/test/expected/class-current-timetable-0.json b/test/expected/class-current-timetable-0.json new file mode 100644 index 0000000..dc98d30 --- /dev/null +++ b/test/expected/class-current-timetable-0.json @@ -0,0 +1,109 @@ +{ + "lessons": [ + { + "type": "lesson", + "date": "2022-10-31", + "periodId": "1", + "startTime": "08:00", + "endTime": "09:45", + "subjectId": "-22", + "classIds": [ + "-105" + ], + "groupNames": [], + "teacherIds": [ + "-48" + ], + "classroomIds": [ + "-28" + ], + "color": "#60FFC0", + "removed": false + }, + { + "type": "lesson", + "date": "2022-10-31", + "periodId": "3", + "startTime": "10:00", + "endTime": "10:45", + "subjectId": "-2", + "classIds": [ + "-105" + ], + "groupNames": [], + "teacherIds": [ + "-161" + ], + "classroomIds": [ + "-16" + ], + "color": null, + "removed": true + }, + { + "type": "lesson", + "date": "2022-10-31", + "periodId": "4", + "startTime": "11:00", + "endTime": "11:45", + "subjectId": "-14", + "classIds": [ + "-105" + ], + "groupNames": [], + "teacherIds": [ + "-37" + ], + "classroomIds": [ + "-12" + ], + "color": "#66FFFF", + "removed": false + }, + { + "type": "lesson", + "date": "2022-10-31", + "periodId": "5", + "startTime": "12:00", + "endTime": "13:45", + "subjectId": "-43", + "classIds": [ + "-105" + ], + "groupNames": [ + "wychowanie fizyczne_ch" + ], + "teacherIds": [ + "-74" + ], + "classroomIds": [ + "-39" + ], + "color": "#003399", + "removed": false + }, + { + "type": "lesson", + "date": "2022-10-31", + "periodId": "5", + "startTime": "12:00", + "endTime": "13:45", + "subjectId": "-43", + "classIds": [ + "-107", + "-105" + ], + "groupNames": [ + "wychowanie fizyczne_dz" + ], + "teacherIds": [ + "-70" + ], + "classroomIds": [ + "-64" + ], + "color": "#FF8080", + "removed": false + } + ] +} diff --git a/test/expected/class-current-timetable-event.json b/test/expected/class-current-timetable-event.json new file mode 100644 index 0000000..d703267 --- /dev/null +++ b/test/expected/class-current-timetable-event.json @@ -0,0 +1,3 @@ +{ + "lessons": [] +} diff --git a/test/expected/maindbi-0.json b/test/expected/maindbi-0.json new file mode 100644 index 0000000..afaad29 --- /dev/null +++ b/test/expected/maindbi-0.json @@ -0,0 +1,64 @@ +{ + "teachers": { + "-1": { + "name": "Wierzbicka_W" + }, + "-2": { + "name": "Błaszczyk_A" + }, + "-3": { + "name": "Bednarek_M" + } + }, + "subjects": { + "-1": { + "name": "wiedza o społeczeństwie", + "short": "WOS" + }, + "-2": { + "name": "historia i teraźniejszość", + "short": "HIT" + }, + "-3": { + "name": "biologia", + "short": "biologia" + } + }, + "classrooms": { + "-30": { + "name": "Uniwersytet" + }, + "-31": { + "name": "1" + }, + "-32": { + "name": "01" + } + }, + "classes": { + "-40": { + "name": "1A", + "short": "1A" + }, + "-41": { + "name": "2B - mat. fiz. inf.", + "short": "2B" + }, + "-42": { + "name": "3C 3C_integracja", + "short": "3C" + } + }, + "periods": { + "0": { + "name": "0", + "startTime": "07:10", + "endTime": "07:55" + }, + "1": { + "name": "Lekcja 1", + "startTime": "08:00", + "endTime": "08:45" + } + } +} diff --git a/test/fixtures/class-current-timetable-0.json b/test/fixtures/class-current-timetable-0.json new file mode 100644 index 0000000..aa2443e --- /dev/null +++ b/test/fixtures/class-current-timetable-0.json @@ -0,0 +1,151 @@ +{ + "r": { + "ttitems": [ + { + "type": "card", + "date": "2022-10-31", + "uniperiod": "1", + "starttime": "08:00", + "endtime": "09:45", + "subjectid": "-22", + "classids": [ + "-105" + ], + "groupnames": [ + "" + ], + "igroupid": "", + "teacherids": [ + "-48" + ], + "classroomids": [ + "-28" + ], + "colors": [ + "#60FFC0" + ], + "durationperiods": 2 + }, + { + "type": "card", + "date": "2022-10-31", + "uniperiod": "3", + "starttime": "10:00", + "endtime": "10:45", + "subjectid": "-2", + "classids": [ + "-105" + ], + "groupnames": [ + "" + ], + "igroupid": "", + "teacherids": [ + "-161" + ], + "classroomids": [ + "-16" + ], + "colors": [], + "removed": true + }, + { + "type": "card", + "date": "2022-10-31", + "uniperiod": "4", + "starttime": "11:00", + "endtime": "11:45", + "subjectid": "-14", + "classids": [ + "-105" + ], + "groupnames": [ + "" + ], + "igroupid": "", + "teacherids": [ + "-37" + ], + "classroomids": [ + "-12" + ], + "colors": [ + "#66FFFF", + "#220000" + ] + }, + { + "type": "card", + "date": "2022-10-31", + "uniperiod": "5", + "starttime": "12:00", + "endtime": "13:45", + "subjectid": "-43", + "classids": [ + "-105" + ], + "groupnames": [ + "wychowanie fizyczne_ch" + ], + "igroupid": "", + "teacherids": [ + "-74" + ], + "classroomids": [ + "-39" + ], + "colors": [ + "#003399" + ], + "durationperiods": 2, + "cellSlices": "01", + "cellOrder": 1 + }, + { + "type": "card", + "date": "2022-10-31", + "uniperiod": "5", + "starttime": "12:00", + "endtime": "13:45", + "subjectid": "-43", + "classids": [ + "-107", + "-105" + ], + "groupnames": [ + "wychowanie fizyczne_dz" + ], + "igroupid": "", + "teacherids": [ + "-70" + ], + "classroomids": [ + "-64" + ], + "colors": [ + "#FF8080" + ], + "durationperiods": 2, + "cellSlices": "10", + "cellOrder": 0 + } + ], + "swapAxis": false, + "rights": { + "subjects": true, + "classes": true, + "teachers": true, + "classrooms": true, + "students": true, + "igroups": true, + "classroomsupervision": false, + "teachers_summary": true, + "classes_summary": true, + "classrooms_summary": true, + "igroups_summary": true + }, + "_changeEvents": { + "dbi:global_settings": 5037479 + } + } +} diff --git a/test/fixtures/class-current-timetable-event.json b/test/fixtures/class-current-timetable-event.json new file mode 100644 index 0000000..267cf94 --- /dev/null +++ b/test/fixtures/class-current-timetable-event.json @@ -0,0 +1,43 @@ +{ + "r": { + "ttitems": [ + { + "type": "event", + "uniperiod": "ad", + "date": "2022-11-01", + "starttime": "00:00", + "endtime": "24:00", + "durationperiods": null, + "subjectid": "", + "classids": [], + "groupnames": [ + "" + ], + "studentids": null, + "igroupid": "", + "teacherids": [], + "classroomids": [], + "eventid": "2022-11-01:215A97CFC43A0D30", + "name": "Święto: Wszystkich Świętych", + "main": true + } + ], + "swapAxis": false, + "rights": { + "subjects": true, + "classes": true, + "teachers": true, + "classrooms": true, + "students": true, + "igroups": true, + "classroomsupervision": false, + "teachers_summary": true, + "classes_summary": true, + "classrooms_summary": true, + "igroups_summary": true + }, + "_changeEvents": { + "dbi:global_settings": 5037479 + } + } +} diff --git a/test/fixtures/maindbi-0.json b/test/fixtures/maindbi-0.json new file mode 100644 index 0000000..b1b9ec5 --- /dev/null +++ b/test/fixtures/maindbi-0.json @@ -0,0 +1,118 @@ +{ + "r": { + "type": "maindbi", + "dbid": "2022", + "tables": [ + { + "id": "teachers", + "data_rows": [ + { + "id": "-1", + "short": "Wierzbicka_W" + }, + { + "id": "-2", + "short": "Błaszczyk_A" + }, + { + "id": "-3", + "short": "Bednarek_M" + } + ], + "data_columns": [ + "short" + ] + }, + { + "id": "subjects", + "data_rows": [ + { + "id": "-1", + "name": "wiedza o społeczeństwie", + "short": "WOS" + }, + { + "id": "-2", + "name": "historia i teraźniejszość", + "short": "HIT" + }, + { + "id": "-3", + "name": "biologia", + "short": "biologia" + } + ], + "data_columns": [ + "name", + "short" + ] + }, + { + "id": "classrooms", + "data_rows": [ + { + "id": "-30", + "short": "Uniwersytet" + }, + { + "id": "-31", + "short": "1" + }, + { + "id": "-32", + "short": "01" + } + ], + "data_columns": [ + "short" + ] + }, + { + "id": "classes", + "data_rows": [ + { + "id": "-40", + "name": "1A", + "short": "1A" + }, + { + "id": "-41", + "name": "2B - mat. fiz. inf.", + "short": "2B" + }, + { + "id": "-42", + "name": "3C 3C_integracja", + "short": "3C" + } + ], + "data_columns": [ + "name", + "short" + ] + }, + { + "id": "periods", + "data_rows": [ + { + "id": "0", + "name": "0", + "starttime": "07:10", + "endtime": "07:55" + }, + { + "id": "1", + "name": "Lekcja 1", + "starttime": "08:00", + "endtime": "08:45" + } + ], + "data_columns": [ + "name", + "starttime", + "endtime" + ] + } + ] + } +} diff --git a/test/test.ts b/test/test.ts index 0b3df9c..d1f488b 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1,6 +1,6 @@ import * as path from "path"; import * as fs from "fs"; -import { parseSubstitutions } from "../src/index"; +import { parseCurrentTimetable, parseMainDbi, parseSubstitutions } from "../src/index"; import { describe, test } from "mocha"; import { expect } from 'chai'; @@ -19,3 +19,35 @@ describe('Substitutions', () => { }); }); }); + +describe('maindbi', () => { + ['maindbi-0'].forEach((key) => { + test(`Parse maindbi ${key}`, () => { + const inputJson = fs.readFileSync(path.join(__dirname, 'fixtures', `${key}.json`), { + encoding: 'utf8', + }); + const expectedResultJson = fs.readFileSync(path.join(__dirname, 'expected', `${key}.json`), { + encoding: 'utf8', + }); + + const maindbi = parseMainDbi(inputJson); + expect(maindbi).to.deep.equal(JSON.parse(expectedResultJson)); + }); + }); +}); + +describe('Current timetable', () => { + ['class-current-timetable-0', 'class-current-timetable-event'].forEach((key) => { + test(`Parse maindbi ${key}`, () => { + const inputJson = fs.readFileSync(path.join(__dirname, 'fixtures', `${key}.json`), { + encoding: 'utf8', + }); + const expectedResultJson = fs.readFileSync(path.join(__dirname, 'expected', `${key}.json`), { + encoding: 'utf8', + }); + + const result = parseCurrentTimetable(inputJson); + expect(result).to.deep.equal(JSON.parse(expectedResultJson)); + }); + }); +});