Compare commits
6 Commits
f1c622ef60
...
ab9b3a5b42
Author | SHA1 | Date |
---|---|---|
Christian Landgren | ab9b3a5b42 | |
William Ryder | dd0f8f3cc4 | |
William Ryder | 959b17bbab | |
William Ryder | ef5e5601c2 | |
Christian Landgren | b0764cf65a | |
Christian Landgren | 53ee3c1cad |
|
@ -29,7 +29,13 @@ import { DateTime, FixedOffsetZone } from 'luxon'
|
|||
import * as html from 'node-html-parser'
|
||||
import { fakeFetcher } from './fake/fakeFetcher'
|
||||
import { checkStatus, DummyStatusChecker } from './loginStatus'
|
||||
import { extractMvghostRequestBody, parseCalendarItem } from './parse/parsers'
|
||||
import {
|
||||
extractMvghostRequestBody,
|
||||
parseTimetableData,
|
||||
parseScheduleEventData,
|
||||
parseBreaksData,
|
||||
parseNewsData,
|
||||
} from './parse/parsers'
|
||||
import {
|
||||
bankIdInitUrl,
|
||||
bankIdCheckUrl,
|
||||
|
@ -204,7 +210,7 @@ export class ApiAdmentum extends EventEmitter implements Api {
|
|||
const now = DateTime.local()
|
||||
const [year, week] = now.toISOWeekDate().split('-')
|
||||
const isoWeek = week.replace('W', '')
|
||||
|
||||
|
||||
const fetchUrl = apiUrls.overview(
|
||||
'get-week-data',
|
||||
year.toString(),
|
||||
|
@ -216,94 +222,25 @@ export class ApiAdmentum extends EventEmitter implements Api {
|
|||
'x-requested-with': 'XMLHttpRequest',
|
||||
},
|
||||
})
|
||||
const calendarItems: CalendarItem[] = []
|
||||
|
||||
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([])
|
||||
|
||||
const scheduleEventJson = (await overviewJson)?.data?.schedule_events // .breaks: [] | .assignments: []
|
||||
const schedule_events = parseScheduleEventData(scheduleEventJson)
|
||||
calendarItems.push(...schedule_events)
|
||||
|
||||
const breaks = (await overviewJson)?.data?.breaks
|
||||
const break_events = parseBreaksData(breaks);
|
||||
calendarItems.push(...break_events)
|
||||
|
||||
return calendarItems
|
||||
} 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, {
|
||||
// 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
|
||||
getClassmates(_child: EtjanstChild): Promise<Classmate[]> {
|
||||
// TODO: We could get this from the events a child is associated with...
|
||||
|
@ -366,18 +303,26 @@ export class ApiAdmentum extends EventEmitter implements Api {
|
|||
if (!this.isLoggedIn) {
|
||||
throw new Error('Not logged in...')
|
||||
}
|
||||
const token = await this.fetch('get-token', apiUrls.messages)
|
||||
.then((res) => res.text())
|
||||
.then((html) => /token:\s+'(.*)'/.exec(html)?.pop()) // HACK: this could probably be found at a better place than the html code..
|
||||
|
||||
const fetchUrl = apiUrls.messages(this.userId, '1')
|
||||
console.log('token', token)
|
||||
|
||||
const fetchUrl = apiUrls.conversations(this.userId, '1')
|
||||
console.log('fetching messages', fetchUrl)
|
||||
const messagesResponse = await this.fetch('get-messages', fetchUrl, {
|
||||
headers: {
|
||||
'x-requested-with': 'XMLHttpRequest',
|
||||
'user-info': token,
|
||||
},
|
||||
})
|
||||
const messagesResponseJson = await messagesResponse.json()
|
||||
console.log('messages response', messagesResponseJson)
|
||||
|
||||
return Promise.resolve([])
|
||||
const newsItems = parseNewsData(messagesResponseJson)
|
||||
console.log('newsItems', newsItems)
|
||||
return newsItems
|
||||
}
|
||||
|
||||
async getNewsDetails(_child: EtjanstChild, item: NewsItem): Promise<any> {
|
||||
|
@ -463,7 +408,7 @@ export class ApiAdmentum extends EventEmitter implements Api {
|
|||
console.log('fetching timetable', fetchUrl)
|
||||
const calendarResponse = await this.fetch('get-calendar', fetchUrl)
|
||||
const calendarResponseJson = await calendarResponse.json()
|
||||
const timetableEntries = parseCalendarItem(calendarResponseJson)
|
||||
const timetableEntries = parseTimetableData(calendarResponseJson)
|
||||
return timetableEntries
|
||||
}
|
||||
|
||||
|
|
|
@ -1,51 +1,63 @@
|
|||
import * as html from 'node-html-parser'
|
||||
import { decode } from 'he'
|
||||
import { CalendarItem, TimetableEntry } from 'libs/api/lib/types'
|
||||
import { CalendarItem, NewsItem, TimetableEntry } from 'libs/api/lib/types'
|
||||
import { DateTime, FixedOffsetZone } from 'luxon'
|
||||
import { teacher } from 'libs/api-skolplattformen/lib/parse'
|
||||
import { news, teacher } from 'libs/api-skolplattformen/lib/parse'
|
||||
import { toMarkdown } from '@skolplattformen/api'
|
||||
|
||||
// TODO: Move this into the parse folder and convert it to follow the pattern of other parsers (include tests).
|
||||
|
||||
export const extractInputField = (sought: string, attrs: string[]) => {
|
||||
// there must be a better way to do this...
|
||||
const s = attrs.find(e => e.indexOf(sought) >= 0) || ""
|
||||
const s = attrs.find((e) => e.indexOf(sought) >= 0) || ''
|
||||
const v = s.substring(s.indexOf('value="') + 'value="'.length)
|
||||
return v.substring(0, v.length - 2)
|
||||
}
|
||||
|
||||
export function extractMvghostRequestBody(initBankIdResponseText: string) {
|
||||
const doc = html.parse(decode(initBankIdResponseText))
|
||||
const inputAttrs = doc.querySelectorAll('input').map(i => (i as any).rawAttrs)
|
||||
const inputAttrs = doc
|
||||
.querySelectorAll('input')
|
||||
.map((i) => (i as any).rawAttrs)
|
||||
const relayState = extractInputField('RelayState', inputAttrs)
|
||||
const samlRequest = extractInputField("SAMLRequest", inputAttrs)
|
||||
const mvghostRequestBody = `RelayState=${encodeURIComponent(relayState)}&SAMLRequest=${encodeURIComponent(samlRequest)}`
|
||||
|
||||
const samlRequest = extractInputField('SAMLRequest', inputAttrs)
|
||||
const mvghostRequestBody = `RelayState=${encodeURIComponent(
|
||||
relayState
|
||||
)}&SAMLRequest=${encodeURIComponent(samlRequest)}`
|
||||
|
||||
return mvghostRequestBody
|
||||
}
|
||||
|
||||
export function extractHjarntorgetSAMLLogin(authGbgLoginResponseText: string) {
|
||||
const authGbgLoginDoc = html.parse(decode(authGbgLoginResponseText))
|
||||
const inputAttrs = authGbgLoginDoc.querySelectorAll('input').map(i => (i as any).rawAttrs)
|
||||
const inputAttrs = authGbgLoginDoc
|
||||
.querySelectorAll('input')
|
||||
.map((i) => (i as any).rawAttrs)
|
||||
const RelayStateText = extractInputField('RelayState', inputAttrs)
|
||||
const SAMLResponseText = extractInputField("SAMLResponse", inputAttrs)
|
||||
const SAMLResponseText = extractInputField('SAMLResponse', inputAttrs)
|
||||
|
||||
return `SAMLResponse=${encodeURIComponent(SAMLResponseText || '')}&RelayState=${encodeURIComponent(RelayStateText || '')}`
|
||||
return `SAMLResponse=${encodeURIComponent(
|
||||
SAMLResponseText || ''
|
||||
)}&RelayState=${encodeURIComponent(RelayStateText || '')}`
|
||||
}
|
||||
|
||||
export function extractAuthGbgLoginRequestBody(signatureResponseText: string) {
|
||||
const signatureResponseDoc = html.parse(decode(signatureResponseText))
|
||||
const signatureResponseTextAreas = signatureResponseDoc.querySelectorAll('textarea')
|
||||
const SAMLResponseElem = signatureResponseTextAreas.find(ta => {
|
||||
const nameAttr = ta.getAttribute("name")
|
||||
const signatureResponseTextAreas =
|
||||
signatureResponseDoc.querySelectorAll('textarea')
|
||||
const SAMLResponseElem = signatureResponseTextAreas.find((ta) => {
|
||||
const nameAttr = ta.getAttribute('name')
|
||||
return nameAttr === 'SAMLResponse'
|
||||
})
|
||||
const SAMLResponseText = SAMLResponseElem?.rawText
|
||||
const RelayStateElem = signatureResponseTextAreas.find(ta => {
|
||||
const nameAttr = ta.getAttribute("name")
|
||||
const RelayStateElem = signatureResponseTextAreas.find((ta) => {
|
||||
const nameAttr = ta.getAttribute('name')
|
||||
return nameAttr === 'RelayState'
|
||||
})
|
||||
const RelayStateText = RelayStateElem?.rawText
|
||||
const authGbgLoginBody = `SAMLResponse=${encodeURIComponent(SAMLResponseText || '')}&RelayState=${encodeURIComponent(RelayStateText || '')}`
|
||||
const authGbgLoginBody = `SAMLResponse=${encodeURIComponent(
|
||||
SAMLResponseText || ''
|
||||
)}&RelayState=${encodeURIComponent(RelayStateText || '')}`
|
||||
return authGbgLoginBody
|
||||
}
|
||||
|
||||
|
@ -71,22 +83,169 @@ export const parseScheduleEvent = (({
|
|||
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,
|
||||
/* OVERVIEW:
|
||||
"status": 200,
|
||||
"data": {
|
||||
"year": 2023,
|
||||
"week": 38,
|
||||
"assignments": [],
|
||||
"breaks": [
|
||||
{
|
||||
"id": 11031,
|
||||
"break_type": 1,
|
||||
"break_period": 1,
|
||||
"name": "Studiedag",
|
||||
"date": "2023-09-21",
|
||||
"week": null,
|
||||
"start_date": null,
|
||||
"end_date": null
|
||||
}
|
||||
],
|
||||
"schedule_events": [
|
||||
{
|
||||
"id": 3110610,
|
||||
"name": "Utvecklingssamtal",
|
||||
"formatted_time": "Heldag",
|
||||
"formatted_date": "2023-09-22"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
*/
|
||||
export const parseBreaksData = (jsonData: any): CalendarItem[] => {
|
||||
const breakItems: CalendarItem[] = []
|
||||
if (jsonData) {
|
||||
jsonData.forEach(
|
||||
(event: {
|
||||
id: any
|
||||
name: any
|
||||
date: any
|
||||
start_date: any
|
||||
end_date: any
|
||||
}) => {
|
||||
breakItems.push({
|
||||
id: event.id,
|
||||
title: event.name,
|
||||
startDate: event.start_date || event.date,
|
||||
endDate: event.end_date || event.date,
|
||||
} as CalendarItem)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
console.error('Failed to parse breaks, no breaks found in json data.')
|
||||
}
|
||||
return breakItems
|
||||
}
|
||||
|
||||
export const parseCalendarItem = (jsonData: any): any => {
|
||||
export const parseScheduleEventData = (jsonData: any): CalendarItem[] => {
|
||||
const calendarItems: CalendarItem[] = []
|
||||
if (jsonData) {
|
||||
jsonData.forEach(
|
||||
(event: {
|
||||
id: any
|
||||
name: any
|
||||
formatted_date: any
|
||||
formatted_time: any
|
||||
}) => {
|
||||
calendarItems.push({
|
||||
id: event.id,
|
||||
title: event.name,
|
||||
startDate: event.formatted_date,
|
||||
endDate: event.formatted_date,
|
||||
allDay: event.formatted_time === 'Heldag',
|
||||
} as CalendarItem)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
console.error(
|
||||
'Failed to parse schedule events, no schedule events found in json data.'
|
||||
)
|
||||
}
|
||||
return calendarItems
|
||||
}
|
||||
|
||||
/*
|
||||
"conversations": [
|
||||
{
|
||||
"id": "14b643b9-fd09-4b4a-9313-b1e75a94a0a8",
|
||||
"latest_message": {
|
||||
"id": "72bb3c4b-efb9-4056-822b-9fbd93c7905c",
|
||||
"content": "text",
|
||||
"message_type": 1,
|
||||
"meta_id": "",
|
||||
"created_at": "2023-10-06 10:57:54.795854+00:00",
|
||||
"formatted_created_at": "Idag, 10:57"
|
||||
},
|
||||
"recipients": [],
|
||||
"title": "Veckobrev v 40",
|
||||
"json_recipients": {
|
||||
"names": {
|
||||
"primary_groups": {
|
||||
"36886": "6 A",
|
||||
"36887": "6 B"
|
||||
}
|
||||
},
|
||||
"parents": {
|
||||
"primary_groups": [
|
||||
36886,
|
||||
36887
|
||||
]
|
||||
},
|
||||
"is_information": true
|
||||
},
|
||||
"is_unread": false,
|
||||
"creator": {
|
||||
"id": 437302,
|
||||
"first_name": "Christian",
|
||||
"last_name": "Landgren"
|
||||
},
|
||||
"flag": 0
|
||||
},
|
||||
*/
|
||||
export const parseNewsData = (jsonData: any): NewsItem[] => {
|
||||
const newsItems: NewsItem[] = []
|
||||
if (
|
||||
jsonData &&
|
||||
jsonData.conversations &&
|
||||
Array.isArray(jsonData.conversations) &&
|
||||
jsonData.conversations.length > 0
|
||||
) {
|
||||
jsonData.conversations.forEach((item: any) => {
|
||||
const bodyText = toMarkdown(item.latest_message?.content)
|
||||
newsItems.push({
|
||||
id: item.id,
|
||||
author: item.creator?.first_name + ' ' + item.creator?.last_name,
|
||||
header: item.title,
|
||||
body: bodyText,
|
||||
published: item.latest_message?.created_at.split(' ')[0],
|
||||
} as NewsItem)
|
||||
})
|
||||
} else {
|
||||
console.error('Failed to parse news, no news found in json data.')
|
||||
}
|
||||
return newsItems
|
||||
}
|
||||
|
||||
enum DayOfWeek {
|
||||
'Måndag' = 1,
|
||||
'Tisdag' = 2,
|
||||
'Onsdag' = 3,
|
||||
'Torsdag' = 4,
|
||||
'Fredag' = 5,
|
||||
'Lördag' = 6,
|
||||
'Söndag' = 7,
|
||||
}
|
||||
|
||||
export const parseTimetableData = (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 => {
|
||||
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,
|
||||
|
@ -97,12 +256,12 @@ export const parseCalendarItem = (jsonData: any): any => {
|
|||
dayOfWeek,
|
||||
blockName: lesson.title || lesson.subject_name,
|
||||
} as TimetableEntry)
|
||||
});
|
||||
})
|
||||
})
|
||||
} else {
|
||||
console.error("Failed to parse calendar item, no days found in json data.")
|
||||
console.error('Failed to parse timetable, no days found in json data.')
|
||||
}
|
||||
return timetableEntries;
|
||||
return timetableEntries
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -208,4 +367,4 @@ export const parseCalendarItem = (jsonData: any): any => {
|
|||
"weeks_amount": 52,
|
||||
"break_week": 27
|
||||
}
|
||||
*/
|
||||
*/
|
||||
|
|
|
@ -15,9 +15,10 @@ export const apiUrls = {
|
|||
leisure_groups: api + 'leisure_groups',
|
||||
lesson_infos: api + 'lesson_infos',
|
||||
lessons: api + 'lessons',
|
||||
messages: 'https://skola.admentum.se/messages/',
|
||||
// start at page 1
|
||||
messages: (userId: string, page: string) =>
|
||||
`https://messages.admentum.se/api/users/${userId}/conversations?page=${page}`, // unread_only=1
|
||||
conversations: (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) =>
|
||||
|
|
Loading…
Reference in New Issue