diff --git a/apps/api-test-app/src/main.js b/apps/api-test-app/src/main.js index ca61f19d2..78988d175 100644 --- a/apps/api-test-app/src/main.js +++ b/apps/api-test-app/src/main.js @@ -27,6 +27,10 @@ const cookieJar = new CookieJar() let bankIdUsed = true const recordFolder = `${__dirname}/record` +const now = DateTime.local() +const [year, week] = now.toISOWeekDate().split('-') +const isoWeek = week.replace('W','') + async function run() { const fetch = fetchCookie(nodeFetch, cookieJar) @@ -45,7 +49,26 @@ async function run() { console.log('children') const children = await api.getChildren() console.log(children) - + + + try { + console.log('timetable') + const timetable = await api.getTimetable( + children[0], + isoWeek, + year, + 'sv' + ) + console.log(inspect(timetable, false, 1000, true)) + } catch (error) { + console.error(error) + } + + console.log('menu') + const menu = await api.getMenu(children[0]) + console.log(menu) + + /* console.log('calendar') const calendar = await api.getCalendar(children[0]) console.log(calendar) @@ -74,18 +97,6 @@ async function run() { console.error(error) } - try { - console.log('timetable') - const timetable = await api.getTimetable( - skola24children[0], - 15, - 2021, - 'sv' - ) - console.log(inspect(timetable, false, 1000, true)) - } catch (error) { - console.error(error) - } /* console.log('news') @@ -100,9 +111,7 @@ async function run() { ) console.log(newsItems) */ - /* console.log('menu') - const menu = await api.getMenu(children[0]) - console.log(menu) */ + // console.log('notifications') // const notifications = await api.getNotifications(children[0]) diff --git a/libs/api-admentum/lib/apiAdmentum.ts b/libs/api-admentum/lib/apiAdmentum.ts index 9fc7a163b..7c4145708 100644 --- a/libs/api-admentum/lib/apiAdmentum.ts +++ b/libs/api-admentum/lib/apiAdmentum.ts @@ -54,6 +54,7 @@ export class ApiAdmentum extends EventEmitter implements Api { private realFetcher: Fetcher private personalNumber?: string + private userId: string private cookieManager: CookieManager @@ -83,6 +84,7 @@ export class ApiAdmentum extends EventEmitter implements Api { this.fetch = wrap(fetch, options) this.realFetcher = this.fetch this.cookieManager = cookieManager + this.userId = '' } public replaceFetcher(fetcher: Fetcher) { @@ -143,12 +145,15 @@ export class ApiAdmentum extends EventEmitter implements Api { } async getUser(): Promise { + const user = await this.fetch('fetch-me', apiUrls.me); + const userJson = await user.json(); + this.userId = userJson.user?.id; + console.log('userId: ', this.userId); console.log('fetching user') - const userId = '437236' const currentUserResponse = await this.fetch( 'current-user', - apiUrls.user(userId) - ) // + /id? + apiUrls.user(this.userId) + ) console.log('current-user', currentUserResponse) if (currentUserResponse.status !== 200) { return { isAuthenticated: false } @@ -163,9 +168,7 @@ export class ApiAdmentum extends EventEmitter implements Api { throw new Error('Not logged in...') } console.log("get children") - const testUserId = '437236' - const fetchUrl = apiUrls.user(testUserId) - console.log('v3.4 fetching children for user id', testUserId, 'from', fetchUrl) + const fetchUrl = apiUrls.user(this.userId) const currentUserResponse = await this.fetch('current-user', fetchUrl, { method: 'GET', headers: { @@ -193,11 +196,19 @@ export class ApiAdmentum extends EventEmitter implements Api { throw new Error('Not logged in...') } - const [year, week] = new DateTime().toISOWeekDate().split('-') - const isoWeek = week.replace('W','') - const fetchUrl = apiUrls.schedule(year, isoWeek) - const calendarResponse = await this.fetch('get-calendar', fetchUrl) - return calendarResponse.map(parseCalendarItem) + return [] + // const fetchUrl = apiUrls.schedule_events + // const events = await this.fetch('scheduled-events', fetchUrl, { + // method: 'GET', + // headers: { + // 'Accept': 'application/json, text/plain, */*', + // }, + // }).then(res => res.json()).then(json => json.results) + + + + + // return events.map(parseScheduleEvent)*/ } // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -236,14 +247,45 @@ export class ApiAdmentum extends EventEmitter implements Api { async getNewsDetails(_child: EtjanstChild, item: NewsItem): Promise { return { ...item } } - +/* + "data": { + "food_week": { + "id": 12846, + "week": 38, + "year": 2023, + "food_days": [ + { + "id": 60620, + "date": "2023-09-18", + "menu": "Förrätt: Morotssoppa med knäckebröd\r\nHuvudrätt: Kycklinggryta med ris och grönsaker\r\nEfterrätt: Fruktkompott", + "weekday": "Måndag", + "weekday_nbr": 0 + }, + { + "id": 60621, + "date": "2023-09-19", + "menu": "Förrätt: Gurksallad\ +*/ // eslint-disable-next-line @typescript-eslint/no-unused-vars - getMenu(_child: EtjanstChild): Promise { + async getMenu(_child: EtjanstChild): Promise { if (!this.isLoggedIn) { throw new Error('Not logged in...') } - // Have not found this available on hjärntorget. Perhaps do a mapping to https://www.skolmaten.se/ ? - return Promise.resolve([]) + const now = DateTime.local() + const [year, week] = now.toISOWeekDate().split('-') + const isoWeek = week.replace('W','') + + const fetchUrl = apiUrls.menu(year.toString(), isoWeek.toString()) + + const menuResponse = (await this.fetch('get-menu', fetchUrl)) + const menuResponseJson = await menuResponse.json() + console.log('menu response', menuResponseJson) + const days = (await menuResponseJson)?.data?.food_week?.food_days + + return Promise.resolve(days.map(({ menu, date } : any) => ({ + title: date, + description: menu + }))) } async getChildEventsWithAssociatedMembers(child: EtjanstChild) { @@ -272,31 +314,13 @@ export class ApiAdmentum extends EventEmitter implements Api { year: number, _lang: string ): Promise { - const startDate = DateTime.fromJSDate(getDateOfISOWeek(week, year)) - const endDate = startDate.plus({ days: 7 }) - const lessonsResponseJson: any[] = [] - - return lessonsResponseJson.map((l) => { - const start = DateTime.fromMillis(l.startDate.ts, { - zone: FixedOffsetZone.instance(l.startDate.timezoneOffsetMinutes), - }) - const end = DateTime.fromMillis(l.endDate.ts, { - zone: FixedOffsetZone.instance(l.endDate.timezoneOffsetMinutes), - }) - return { - ...parse(l.title, _lang), - id: l.id, - teacher: l.bookedTeacherNames && l.bookedTeacherNames[0], - location: l.location, - timeStart: start.toISOTime().substring(0, 5), - timeEnd: end.toISOTime().substring(0, 5), - dayOfWeek: start.toJSDate().getDay(), - blockName: l.title, - dateStart: start.toISODate(), - dateEnd: end.toISODate(), - } as TimetableEntry - }) + const fetchUrl = apiUrls.schedule(year.toString(), week.toString()) + console.log('fetching timetable', fetchUrl) + const calendarResponse = await this.fetch('get-calendar', fetchUrl) + const calendarResponseJson = await calendarResponse.json() + const timetableEntries = parseCalendarItem(calendarResponseJson) + return timetableEntries; } async logout(): Promise { @@ -314,6 +338,16 @@ export class ApiAdmentum extends EventEmitter implements Api { console.log('login adentum', personalNumber) this.isFake = false + + const authenticatedUser = await this.getUser(); + if (authenticatedUser && authenticatedUser.isAuthenticated) { + console.log('already logged in to admentum') + this.isLoggedIn = true + this.personalNumber = personalNumber + this.emit('login') + return new DummyStatusChecker() + } + const url = await this.fetch('get-session', bankIdSessionUrl('')).then( (res) => { console.log('got res', res, (res as any).url, res.headers) @@ -357,15 +391,8 @@ export class ApiAdmentum extends EventEmitter implements Api { const locomotiveUrl = redirectLocomotive(sessionId) console.log('calling locomotive url: ', locomotiveUrl); - /*const response = await this.fetch('follow-locomotive', locomotiveUrl, { - method: 'GET', - redirect: 'follow', - });*/ - //console.log('locomotive response', response) const callbackResponse = await this.followRedirects(locomotiveUrl); console.log('final response:', callbackResponse); - //const testChildren = await this.getChildren() - //console.log('test children', testChildren) this.emit('login') }) statusChecker.on('ERROR', () => { diff --git a/libs/api-admentum/lib/features.ts b/libs/api-admentum/lib/features.ts index b0e530222..a2a7db739 100644 --- a/libs/api-admentum/lib/features.ts +++ b/libs/api-admentum/lib/features.ts @@ -3,6 +3,6 @@ import { Features } from '@skolplattformen/api' export const features: Features = { LOGIN_BANK_ID_SAME_DEVICE_WITHOUT_ID: false, LOGIN_FREJA_EID: false, - FOOD_MENU: false, + FOOD_MENU: true, CLASS_LIST: false, } diff --git a/libs/api-admentum/lib/parse/parsers.ts b/libs/api-admentum/lib/parse/parsers.ts index 83c22e5fb..00fa09b40 100644 --- a/libs/api-admentum/lib/parse/parsers.ts +++ b/libs/api-admentum/lib/parse/parsers.ts @@ -1,5 +1,7 @@ import * as html from 'node-html-parser' import { decode } from 'he' +import { CalendarItem, TimetableEntry } from 'libs/api/lib/types' +import { DateTime, FixedOffsetZone } from 'luxon' // TODO: Move this into the parse folder and convert it to follow the pattern of other parsers (include tests). @@ -46,10 +48,60 @@ export function extractAuthGbgLoginRequestBody(signatureResponseText: string) { return authGbgLoginBody } -export const parseCalendarItem = (jsonRow: any): any => { - - return {} - +/* +return myChildrenResponseJson.students.map((student: { id: any; first_name: any; last_name: any }) => ({ + id: student.id, + sdsId: student.id, + personGuid: student.id, + firstName: student.first_name, + lastName: student.last_name, + name: `${student.first_name} ${student.last_name}`, + }) as Skola24Child & EtjanstChild); +*/ +/* +export const parseScheduleEvent = (({ + url, id, eid, school_id, schedule_id, name, start_time, end_time, rooms: [room], teachers, schedule_groups, primary_groups, weekly_interval +})): CalendarItem => ({ + id + title: name + location?: room?.name + startDate?: start_time + endDate?: end_time + allDay?: start_time === '00:00:00' && end_time === '23:59:00' +}) + */ + +enum DayOfWeek { + 'Måndag'= 1, + 'Tisdag'= 2, + 'Onsdag'= 3, + 'Torsdag'= 4, + 'Fredag'= 5, + 'Lördag'= 6, + 'Söndag'= 7, +} + +export const parseCalendarItem = (jsonData: any): any => { + const timetableEntries: TimetableEntry[] = [] + if (jsonData && jsonData.days && Array.isArray(jsonData.days) && jsonData.days.length > 0) { + jsonData.days.forEach((day: { name: string, lessons: any[] }) => { + day.lessons.forEach(lesson => { + const dayOfWeek = DayOfWeek[day.name as keyof typeof DayOfWeek] + timetableEntries.push({ + id: lesson.id, + teacher: lesson.bookedTeacherNames && lesson.bookedTeacherNames[0], + location: lesson.location, + timeStart: lesson.time.substring(0, 5), + timeEnd: lesson.time.substring(9), + dayOfWeek, + blockName: lesson.title || lesson.subject_name + } as TimetableEntry) + }); + }) + } else { + console.error("Failed to parse calendar item, no days found in json data.") + } + return timetableEntries; } /* diff --git a/libs/api-admentum/lib/routes.ts b/libs/api-admentum/lib/routes.ts index 7b9572095..f78249fd4 100644 --- a/libs/api-admentum/lib/routes.ts +++ b/libs/api-admentum/lib/routes.ts @@ -1,48 +1,51 @@ -const baseUrl = 'https://skola.admentum.se/api/v1/' +const baseUrl = 'https://skola.admentum.se/' +const api = baseUrl + 'api/v1/' export const apiUrls = { - assignments: baseUrl + 'assignments', - attendance_summary_users: baseUrl + 'attendance/summary/users', - course_sections: baseUrl + 'course_sections', - courses: baseUrl + 'courses', - forecast_collections: baseUrl + 'forecast_collections', - forecasts: baseUrl + 'forecasts', - grade_permissions: baseUrl + 'grade_permissions', - grades: baseUrl + 'grades', - gymnasium_courses: baseUrl + 'gymnasium_courses', - leisure_group_enrollments: baseUrl + 'leisure_group_enrollments', - leisure_groups: baseUrl + 'leisure_groups', - lesson_infos: baseUrl + 'lesson_infos', - lessons: baseUrl + 'lessons', - organisations: baseUrl + 'organisations', - orientations: baseUrl + 'orientations', - permission_groups: baseUrl + 'permission_groups', - primary_group_enrollments: baseUrl + 'primary_group_enrollments', + assignments: api + 'assignments', + attendance_summary_users: api + 'attendance/summary/users', + course_sections: api + 'course_sections', + courses: api + 'courses', + forecast_collections: api + 'forecast_collections', + forecasts: api + 'forecasts', + grade_permissions: api + 'grade_permissions', + grades: api + 'grades', + gymnasium_courses: api + 'gymnasium_courses', + leisure_group_enrollments: api + 'leisure_group_enrollments', + leisure_groups: api + 'leisure_groups', + lesson_infos: api + 'lesson_infos', + lessons: api + 'lessons', + organisations: api + 'organisations', + orientations: api + 'orientations', + permission_groups: api + 'permission_groups', + primary_group_enrollments: api + 'primary_group_enrollments', primary_group_municipality_statistics: - baseUrl + 'primary_groups/municipality_statistic', - primary_groups: baseUrl + 'primary_groups', - program_courses: baseUrl + 'program_courses', - programs: baseUrl + 'programs', - reviews: baseUrl + 'reviews', - rooms: baseUrl + 'rooms', - schedule_breaks: baseUrl + 'schedule_breaks', - schedule_event_instances: baseUrl + 'schedule_event_instances', - schedule_events: baseUrl + 'schedule_events', - schedule_group_enrollments: baseUrl + 'schedule_group_enrollments', + api + 'primary_groups/municipality_statistic', + primary_groups: api + 'primary_groups', + program_courses: api + 'program_courses', + programs: api + 'programs', + reviews: api + 'reviews', + rooms: api + 'rooms', + schedule_breaks: api + 'schedule_breaks', + schedule_event_instances: api + 'schedule_event_instances', + schedule_events: api + 'schedule_events', + schedule_group_enrollments: api + 'schedule_group_enrollments', schedule_group_teacher_enrollments: - baseUrl + 'schedule_group_teacher_enrollments', - schedule_groups: baseUrl + 'schedule_groups', - schedules: baseUrl + 'schedules', - schedule: (year: string, week: string) => baseUrl + `schedule?week=${week}&year=${year}`, - school_enrollments: `${baseUrl}school_enrollments`, - school_years: baseUrl + 'school_years', - schools: baseUrl + 'schools', - sickness: baseUrl + 'sickness', - subjects: baseUrl + 'subjects', - teachers: baseUrl + 'teachers', - upper_secondary_subjects: baseUrl + 'upper_secondary_subjects', - users: baseUrl + 'users?format=json', - user: (userId: string) => baseUrl + `users/${userId}/?format=json`, + api + 'schedule_group_teacher_enrollments', + schedule_groups: api + 'schedule_groups', + schedules: api + 'schedules', + schedule: (year: string, week: string) => baseUrl + `schedule/schedule?week=${week}&year=${year}`, + school_enrollments: `${api}school_enrollments`, + school_years: api + 'school_years', + schools: api + 'schools', + sickness: api + 'sickness', + subjects: api + 'subjects', + teachers: api + 'teachers', + menu: (year: string, week: string) => baseUrl + `api/food/week/${week}/${year}`, + upper_secondary_subjects: api + 'upper_secondary_subjects', + users: api + 'users?format=json', + user: (userId: string) => api + `users/${userId}/?format=json`, + me: baseUrl + 'api/me?format=json', } export const bankIdCheckUrl = (sessionId: string) =>