Compare commits
5 Commits
6130fdb796
...
f1c622ef60
Author | SHA1 | Date |
---|---|---|
William Ryder | f1c622ef60 | |
Christian Landgren | 8385d30aaf | |
Christian Landgren | 618bdbf73c | |
William Ryder | c4c52b9819 | |
William Ryder | 9276db3c76 |
|
@ -68,11 +68,14 @@ async function run() {
|
|||
const menu = await api.getMenu(children[0])
|
||||
console.log(menu)
|
||||
|
||||
/*
|
||||
|
||||
console.log('calendar')
|
||||
const calendar = await api.getCalendar(children[0])
|
||||
console.log(calendar)
|
||||
|
||||
|
||||
console.log('news')
|
||||
const news = await api.getNews(children[0])
|
||||
/*
|
||||
/*console.log('classmates')
|
||||
const classmates = await api.getClassmates(children[0])
|
||||
console.log(classmates)
|
||||
|
@ -97,11 +100,6 @@ async function run() {
|
|||
console.error(error)
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
console.log('news')
|
||||
const news = await api.getNews(children[0])
|
||||
*/
|
||||
/* console.log('news details')
|
||||
const newsItems = await Promise.all(
|
||||
news.map((newsItem) =>
|
||||
|
|
|
@ -141,14 +141,14 @@ export class ApiAdmentum extends EventEmitter implements Api {
|
|||
//const user = await this.getUser()
|
||||
//if (!user.isAuthenticated) {
|
||||
// throw new Error('Session cookie is expired')
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
async getUser(): Promise<User> {
|
||||
const user = await this.fetch('fetch-me', apiUrls.me);
|
||||
const userJson = await user.json();
|
||||
this.userId = userJson.user?.id;
|
||||
console.log('userId: ', this.userId);
|
||||
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 currentUserResponse = await this.fetch(
|
||||
'current-user',
|
||||
|
@ -167,35 +167,131 @@ export class ApiAdmentum extends EventEmitter implements Api {
|
|||
if (!this.isLoggedIn) {
|
||||
throw new Error('Not logged in...')
|
||||
}
|
||||
console.log("get children")
|
||||
console.log('get children')
|
||||
const fetchUrl = apiUrls.user(this.userId)
|
||||
const currentUserResponse = await this.fetch('current-user', fetchUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json, text/plain, */*',
|
||||
Accept: 'application/json, text/plain, */*',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
if (currentUserResponse.status !== 200) {
|
||||
console.error('Error headers', currentUserResponse.headers)
|
||||
throw new Error('Could not fetch children. Response code: ' + currentUserResponse.status)
|
||||
throw new Error(
|
||||
'Could not fetch children. Response code: ' + currentUserResponse.status
|
||||
)
|
||||
}
|
||||
const myChildrenResponseJson = await currentUserResponse.json();
|
||||
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);
|
||||
const myChildrenResponseJson = await currentUserResponse.json()
|
||||
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)
|
||||
)
|
||||
}
|
||||
|
||||
async getCalendar(child: EtjanstChild): Promise<CalendarItem[]> {
|
||||
try {
|
||||
if (!this.isLoggedIn) {
|
||||
throw new Error('Not logged in...')
|
||||
}
|
||||
const now = DateTime.local()
|
||||
const [year, week] = now.toISOWeekDate().split('-')
|
||||
const isoWeek = week.replace('W', '')
|
||||
|
||||
const fetchUrl = apiUrls.overview(
|
||||
'get-week-data',
|
||||
year.toString(),
|
||||
isoWeek.toString()
|
||||
)
|
||||
console.log('fetching calendar', fetchUrl)
|
||||
const overviewResponse = await this.fetch('get-week-data', fetchUrl, {
|
||||
headers: {
|
||||
'x-requested-with': 'XMLHttpRequest',
|
||||
},
|
||||
})
|
||||
const overviewJson = await overviewResponse.json()
|
||||
console.log('get-week-data response', overviewJson)
|
||||
const schedule_events = (await overviewJson)?.data?.schedule_events // .breaks: [] | .assignments: []
|
||||
if (!schedule_events) {
|
||||
return Promise.resolve([])
|
||||
}
|
||||
/*
|
||||
"url": "https://skola.admentum.se/api/v1/schedule_event_instances/2990834/",
|
||||
"id": 2990834,
|
||||
"school_id": 824,
|
||||
"start_date": "2023-08-07",
|
||||
"end_date": "2023-08-07",
|
||||
"schedule_event": {
|
||||
"url": "https://skola.admentum.se/api/v1/schedule_events/148722/",
|
||||
"id": 148722,
|
||||
"eid": null,
|
||||
"schedule_id": 4385,
|
||||
"name": "Engelska",
|
||||
"start_time": "08:00:00",
|
||||
"end_time": "09:30:00",
|
||||
"rooms": [
|
||||
{
|
||||
"url": "https://skola.admentum.se/api/v1/rooms/7200/",
|
||||
"id": 7200
|
||||
}
|
||||
],
|
||||
"teachers": [
|
||||
{
|
||||
"url": "https://skola.admentum.se/api/v1/users/437302/",
|
||||
"id": 437302
|
||||
}
|
||||
],
|
||||
"schedule_groups": [],
|
||||
"primary_groups": [
|
||||
{
|
||||
"url": "https://skola.admentum.se/api/v1/primary_groups/36874/",
|
||||
"id": 36874
|
||||
}
|
||||
],
|
||||
"weekly_interval": ""
|
||||
}
|
||||
*/
|
||||
return Promise.resolve([])
|
||||
} catch (e) {
|
||||
console.error('Error fetching overview', e)
|
||||
return Promise.resolve([])
|
||||
}
|
||||
}
|
||||
|
||||
async getScheduledEvents(child: EtjanstChild): Promise<CalendarItem[]> {
|
||||
if (!this.isLoggedIn) {
|
||||
throw new Error('Not logged in...')
|
||||
}
|
||||
console.log('get calendar')
|
||||
const fetchUrl = apiUrls.schedule_events
|
||||
console.log('fetching calendar', fetchUrl)
|
||||
const eventsResponse = await this.fetch('scheduled-events', fetchUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json, text/plain, */*',
|
||||
},
|
||||
})
|
||||
|
||||
if (eventsResponse.status === 403) {
|
||||
console.error('Not allwed. Error headers', eventsResponse.headers)
|
||||
return []
|
||||
}
|
||||
if (eventsResponse.status !== 200) {
|
||||
console.error('Error headers', eventsResponse.headers)
|
||||
throw new Error(
|
||||
'Could not fetch children. Response code: ' + eventsResponse.status
|
||||
)
|
||||
}
|
||||
|
||||
const eventsResponseJson = await eventsResponse.json()
|
||||
console.log('eventsResponseJson', eventsResponseJson)
|
||||
return []
|
||||
// const fetchUrl = apiUrls.schedule_events
|
||||
// const events = await this.fetch('scheduled-events', fetchUrl, {
|
||||
|
@ -205,15 +301,44 @@ export class ApiAdmentum extends EventEmitter implements Api {
|
|||
// },
|
||||
// }).then(res => res.json()).then(json => json.results)
|
||||
|
||||
|
||||
|
||||
|
||||
// return events.map(parseScheduleEvent)*/
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
getClassmates(_child: EtjanstChild): Promise<Classmate[]> {
|
||||
// TODO: We could get this from the events a child is associated with...
|
||||
/*
|
||||
GET /api/v1/schedule_groups/423145/
|
||||
{
|
||||
"url": "https://skola.admentum.se/api/v1/schedule_groups/423145/",
|
||||
"id": 423145,
|
||||
"eid": null,
|
||||
"schedule": {
|
||||
"url": "https://skola.admentum.se/api/v1/schedules/4385/",
|
||||
"id": 4385,
|
||||
"school_year": "23/24"
|
||||
},
|
||||
"name": "1 A SV",
|
||||
"guid": null,
|
||||
"users": [
|
||||
{
|
||||
"url": "https://skola.admentum.se/api/v1/users/436741/",
|
||||
"id": 436741,
|
||||
"email": null,
|
||||
"first_name": "Arvid",
|
||||
"last_name": "Forslin",
|
||||
"role": 1
|
||||
},
|
||||
{
|
||||
"url": "https://skola.admentum.se/api/v1/users/436747/",
|
||||
"id": 436747,
|
||||
"email": null,
|
||||
"first_name": "Emmy",
|
||||
"last_name": "Granström",
|
||||
"role": 1
|
||||
}
|
||||
...
|
||||
*/
|
||||
if (!this.isLoggedIn) {
|
||||
throw new Error('Not logged in...')
|
||||
}
|
||||
|
@ -241,13 +366,24 @@ export class ApiAdmentum extends EventEmitter implements Api {
|
|||
if (!this.isLoggedIn) {
|
||||
throw new Error('Not logged in...')
|
||||
}
|
||||
|
||||
const fetchUrl = apiUrls.messages(this.userId, '1')
|
||||
console.log('fetching messages', fetchUrl)
|
||||
const messagesResponse = await this.fetch('get-messages', fetchUrl, {
|
||||
headers: {
|
||||
'x-requested-with': 'XMLHttpRequest',
|
||||
},
|
||||
})
|
||||
const messagesResponseJson = await messagesResponse.json()
|
||||
console.log('messages response', messagesResponseJson)
|
||||
|
||||
return Promise.resolve([])
|
||||
}
|
||||
|
||||
async getNewsDetails(_child: EtjanstChild, item: NewsItem): Promise<any> {
|
||||
return { ...item }
|
||||
}
|
||||
/*
|
||||
/*
|
||||
"data": {
|
||||
"food_week": {
|
||||
"id": 12846,
|
||||
|
@ -268,24 +404,33 @@ export class ApiAdmentum extends EventEmitter implements Api {
|
|||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async getMenu(_child: EtjanstChild): Promise<MenuItem[]> {
|
||||
if (!this.isLoggedIn) {
|
||||
throw new Error('Not logged in...')
|
||||
try {
|
||||
if (!this.isLoggedIn) {
|
||||
throw new Error('Not logged in...')
|
||||
}
|
||||
const now = DateTime.local()
|
||||
const [year, week] = now.toISOWeekDate().split('-')
|
||||
const isoWeek = week.replace('W', '')
|
||||
|
||||
const fetchUrl = apiUrls.menu(year.toString(), isoWeek.toString())
|
||||
console.log('fetching menu', fetchUrl)
|
||||
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
|
||||
if (!days) {
|
||||
return Promise.resolve([])
|
||||
}
|
||||
return Promise.resolve(
|
||||
days.map(({ menu, date }: any) => ({
|
||||
title: date,
|
||||
description: menu,
|
||||
}))
|
||||
)
|
||||
} catch (e) {
|
||||
console.error('Error fetching menu', e)
|
||||
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) {
|
||||
|
@ -314,13 +459,12 @@ export class ApiAdmentum extends EventEmitter implements Api {
|
|||
year: number,
|
||||
_lang: string
|
||||
): Promise<TimetableEntry[]> {
|
||||
|
||||
const fetchUrl = apiUrls.schedule(year.toString(), week.toString())
|
||||
console.log('fetching timetable', fetchUrl)
|
||||
const calendarResponse = await this.fetch('get-calendar', fetchUrl)
|
||||
const calendarResponse = await this.fetch('get-calendar', fetchUrl)
|
||||
const calendarResponseJson = await calendarResponse.json()
|
||||
const timetableEntries = parseCalendarItem(calendarResponseJson)
|
||||
return timetableEntries;
|
||||
return timetableEntries
|
||||
}
|
||||
|
||||
async logout(): Promise<void> {
|
||||
|
@ -335,11 +479,10 @@ export class ApiAdmentum extends EventEmitter implements Api {
|
|||
if (personalNumber !== undefined && personalNumber.endsWith('1212121212'))
|
||||
return this.fakeMode()
|
||||
|
||||
|
||||
console.log('login adentum', personalNumber)
|
||||
this.isFake = false
|
||||
|
||||
const authenticatedUser = await this.getUser();
|
||||
const authenticatedUser = await this.getUser()
|
||||
if (authenticatedUser && authenticatedUser.isAuthenticated) {
|
||||
console.log('already logged in to admentum')
|
||||
this.isLoggedIn = true
|
||||
|
@ -388,11 +531,10 @@ export class ApiAdmentum extends EventEmitter implements Api {
|
|||
this.isLoggedIn = true
|
||||
this.personalNumber = personalNumber
|
||||
|
||||
|
||||
const locomotiveUrl = redirectLocomotive(sessionId)
|
||||
console.log('calling locomotive url: ', locomotiveUrl);
|
||||
const callbackResponse = await this.followRedirects(locomotiveUrl);
|
||||
console.log('final response:', callbackResponse);
|
||||
console.log('calling locomotive url: ', locomotiveUrl)
|
||||
const callbackResponse = await this.followRedirects(locomotiveUrl)
|
||||
console.log('final response:', callbackResponse)
|
||||
this.emit('login')
|
||||
})
|
||||
statusChecker.on('ERROR', () => {
|
||||
|
@ -401,35 +543,38 @@ export class ApiAdmentum extends EventEmitter implements Api {
|
|||
|
||||
return statusChecker
|
||||
}
|
||||
|
||||
|
||||
async followRedirects(initialUrl: string): Promise<Response> {
|
||||
let currentUrl = initialUrl;
|
||||
let redirectCount = 0;
|
||||
const maxRedirects = 10;
|
||||
let currentUrl = initialUrl
|
||||
let redirectCount = 0
|
||||
const maxRedirects = 10
|
||||
|
||||
while (redirectCount < maxRedirects) {
|
||||
console.log('fetching (redirect number ' + redirectCount + ')', currentUrl);
|
||||
console.log(
|
||||
'fetching (redirect number ' + redirectCount + ')',
|
||||
currentUrl
|
||||
)
|
||||
const response = await this.fetch('follow-redirect', currentUrl, {
|
||||
method: 'GET',
|
||||
redirect: 'manual', // Disable automatic redirects
|
||||
});
|
||||
console.log('follow-redirect response', response);
|
||||
})
|
||||
console.log('follow-redirect response', response)
|
||||
if (response.status >= 300 && response.status < 400) {
|
||||
console.log('response status:', response.status);
|
||||
const newLocation = response.headers.get('location');
|
||||
console.log('response status:', response.status)
|
||||
const newLocation = response.headers.get('location')
|
||||
if (!newLocation) {
|
||||
throw new Error('Redirect response missing location header');
|
||||
throw new Error('Redirect response missing location header')
|
||||
}
|
||||
currentUrl = newLocation;
|
||||
redirectCount++;
|
||||
currentUrl = newLocation
|
||||
redirectCount++
|
||||
} else {
|
||||
console.log('response status, not reidrect:', response.status);
|
||||
console.log('response status, not reidrect:', response.status)
|
||||
// The response is not a redirect, return it
|
||||
return response;
|
||||
return response
|
||||
}
|
||||
}
|
||||
throw new Error('Max redirects reached');
|
||||
};
|
||||
throw new Error('Max redirects reached')
|
||||
}
|
||||
|
||||
private async fakeMode(): Promise<LoginStatusChecker> {
|
||||
this.isFake = true
|
||||
|
|
|
@ -33,7 +33,7 @@ export class GrandidChecker extends EventEmitter implements LoginStatusChecker {
|
|||
})
|
||||
console.log('bankid result', result)
|
||||
const ok = result.response?.status === 'complete'
|
||||
const isError = result.response?.status === 'error'
|
||||
const isError = result.response?.status === 'failed'
|
||||
// https://mNN-mg-local.idp.funktionstjanster.se/mg-local/auth/ccp11/grp/pollstatus
|
||||
if (ok) {
|
||||
//===
|
||||
|
@ -60,7 +60,7 @@ export class GrandidChecker extends EventEmitter implements LoginStatusChecker {
|
|||
console.log('locomotive response', response)*/
|
||||
this.emit('OK')
|
||||
} else if (isError) {
|
||||
console.log('polling error')
|
||||
console.log('polling error', result.response?.hintCode)
|
||||
this.emit('ERROR')
|
||||
} else if (!this.cancelled) {
|
||||
console.log('keep on polling...')
|
||||
|
|
|
@ -2,6 +2,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'
|
||||
import { teacher } from 'libs/api-skolplattformen/lib/parse'
|
||||
|
||||
// TODO: Move this into the parse folder and convert it to follow the pattern of other parsers (include tests).
|
||||
|
||||
|
@ -89,12 +90,12 @@ export const parseCalendarItem = (jsonData: any): any => {
|
|||
const dayOfWeek = DayOfWeek[day.name as keyof typeof DayOfWeek]
|
||||
timetableEntries.push({
|
||||
id: lesson.id,
|
||||
teacher: lesson.bookedTeacherNames && lesson.bookedTeacherNames[0],
|
||||
location: lesson.location,
|
||||
teacher: lesson.teachers,
|
||||
location: lesson.room || lesson.title || lesson.subject_name,
|
||||
timeStart: lesson.time.substring(0, 5),
|
||||
timeEnd: lesson.time.substring(9),
|
||||
dayOfWeek,
|
||||
blockName: lesson.title || lesson.subject_name
|
||||
blockName: lesson.title || lesson.subject_name,
|
||||
} as TimetableEntry)
|
||||
});
|
||||
})
|
||||
|
|
|
@ -15,8 +15,13 @@ export const apiUrls = {
|
|||
leisure_groups: api + 'leisure_groups',
|
||||
lesson_infos: api + 'lesson_infos',
|
||||
lessons: api + 'lessons',
|
||||
// start at page 1
|
||||
messages: (userId: string, page: string) =>
|
||||
`https://messages.admentum.se/api/users/${userId}/conversations?page=${page}`, // unread_only=1
|
||||
organisations: api + 'organisations',
|
||||
orientations: api + 'orientations',
|
||||
overview: (action: string, year: string, week: string) =>
|
||||
baseUrl + `overview?action=${action}&week=${week}&year=${year}`,
|
||||
permission_groups: api + 'permission_groups',
|
||||
primary_group_enrollments: api + 'primary_group_enrollments',
|
||||
primary_group_municipality_statistics:
|
||||
|
@ -34,14 +39,16 @@ export const apiUrls = {
|
|||
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}`,
|
||||
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}`,
|
||||
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`,
|
||||
|
|
Loading…
Reference in New Issue