feat: 🎸 Hämta lektionsschema (#110)
* feat: 🎸 add timetables from skola24 * refactor: 💡 Clean up sso authorization * feat: 🎸 Reads timetable * feat: 🎸 Veckoschema Veckoschema kan laddas från Skola24 BREAKING CHANGE: 🧨 Child -> EtjanstChild * feat: 🎸 Test data for skola24Children and timetable * docs: ✏️ Updated instructions Co-authored-by: Johan Öbrink <johan.obrink@gmail.com>
This commit is contained in:
parent
3c33c75956
commit
c2884497bf
74
README.md
74
README.md
|
@ -16,69 +16,68 @@ the concrete implementation of fetch and cookie handler must be injected.
|
|||
#### react-native
|
||||
|
||||
```javascript
|
||||
import init from "@skolplattformen/embedded-api";
|
||||
import CookieManager from "@react-native-community/cookies";
|
||||
import init from '@skolplattformen/embedded-api'
|
||||
import CookieManager from '@react-native-community/cookies'
|
||||
|
||||
const api = init(fetch, () => CookieManager.clearAll());
|
||||
const api = init(fetch, () => CookieManager.clearAll())
|
||||
```
|
||||
|
||||
#### node
|
||||
|
||||
```javascript
|
||||
import init from "@skolplattformen/embedded-api";
|
||||
import nodeFetch from "node-fetch";
|
||||
import fetchCookie from "fetch-cookie/node-fetch";
|
||||
import { CookieJar } from "tough-cookie";
|
||||
import init from '@skolplattformen/embedded-api'
|
||||
import nodeFetch from 'node-fetch'
|
||||
import fetchCookie from 'fetch-cookie/node-fetch'
|
||||
import { CookieJar } from 'tough-cookie'
|
||||
|
||||
const cookieJar = new CookieJar();
|
||||
const fetch = fetchCookie(nodeFetch, cookieJar);
|
||||
const cookieJar = new CookieJar()
|
||||
const fetch = fetchCookie(nodeFetch, cookieJar)
|
||||
|
||||
const api = init(fetch, () => cookieJar.removeAllCookies());
|
||||
const api = init(fetch, cookieJar)
|
||||
```
|
||||
|
||||
### Login / logout
|
||||
|
||||
```javascript
|
||||
api.on("login", async () => {
|
||||
api.on('login', async () => {
|
||||
// do stuff
|
||||
console.log(api.isLoggedIn) // true
|
||||
console.log(api.getSessionCookie) // session cookie if you want to save it
|
||||
await api.logout()
|
||||
});
|
||||
})
|
||||
api.on('logout', () => {
|
||||
// handle logout
|
||||
console.log(api.isLoggedIn) // false
|
||||
}
|
||||
|
||||
const loginStatus = await api.login("YYYYMMDDXXXX");
|
||||
const loginStatus = await api.login('YYYYMMDDXXXX')
|
||||
window.open(
|
||||
`https://app.bankid.com/?autostarttoken=${loginStatus.token}&redirect=null`
|
||||
);
|
||||
)
|
||||
|
||||
loginStatus.on("PENDING", () => console.log("BankID app not yet opened"));
|
||||
loginStatus.on("USER_SIGN", () => console.log("BankID app is open"));
|
||||
loginStatus.on("ERROR", () => console.log("Something went wrong"));
|
||||
loginStatus.on("OK", () =>
|
||||
console.log("BankID sign successful. Session will be established.")
|
||||
);
|
||||
loginStatus.on('PENDING', () => console.log('BankID app not yet opened'))
|
||||
loginStatus.on('USER_SIGN', () => console.log('BankID app is open'))
|
||||
loginStatus.on('ERROR', () => console.log('Something went wrong'))
|
||||
loginStatus.on('OK', () =>
|
||||
console.log('BankID sign successful. Session will be established.')
|
||||
)
|
||||
```
|
||||
|
||||
### Loading data
|
||||
|
||||
```javascript
|
||||
// Get current user
|
||||
const user = await api.getUser();
|
||||
const user = await api.getUser()
|
||||
|
||||
// List children
|
||||
const children = await api.getChildren();
|
||||
// List children from Etjanster
|
||||
const children = await api.getChildren()
|
||||
|
||||
// Get calendar
|
||||
const calendar = await api.getCalendar(children[0]);
|
||||
// Get school calendar
|
||||
const calendar = await api.getCalendar(children[0])
|
||||
|
||||
// Get classmates
|
||||
const classmates = await api.getClassmates(children[0]);
|
||||
// Get classmates - disabled for reasons
|
||||
// const classmates = await api.getClassmates(children[0])
|
||||
|
||||
// Get schedule
|
||||
// Get student's personal schedule
|
||||
import { DateTime } from 'luxon'
|
||||
|
||||
const from = DateTime.local()
|
||||
|
@ -88,11 +87,22 @@ const schedule = await api.getSchedule(children[0], from, to)
|
|||
// Get news
|
||||
const news = await api.getNews(children[0])
|
||||
|
||||
// Get news details
|
||||
const newsDetails = await api.getNewsDetails(children[0], news[0])
|
||||
|
||||
// Get menu
|
||||
const menu = await api.getMenu(children[0])
|
||||
|
||||
// Get notifications
|
||||
const notifications = await api.getNotifications(children[0])
|
||||
|
||||
// Get list of children from Skola24 (because of course it's different *DERP*)
|
||||
const skola24Children = await getSkola24Children()
|
||||
|
||||
// Get timetable
|
||||
const weekNumber = 15
|
||||
const year = 2021
|
||||
const timetable = await api.getTimeTable(skola24Children[0], weekNumber, year)
|
||||
```
|
||||
|
||||
### Setting session cookie
|
||||
|
@ -100,9 +110,9 @@ const notifications = await api.getNotifications(children[0])
|
|||
It is possible to resurrect a logged in session by manually setting the session cookie.
|
||||
|
||||
```javascript
|
||||
const sessionCookie = "some value";
|
||||
const sessionCookie = 'some value'
|
||||
|
||||
api.setSessionCookie(sessionCookie); // will trigger `on('login')` event and set `.isLoggedIn = true`
|
||||
api.setSessionCookie(sessionCookie) // will trigger `on('login')` event and set `.isLoggedIn = true`
|
||||
```
|
||||
|
||||
### Fake user
|
||||
|
@ -111,7 +121,7 @@ Login with personal number `12121212121212`, `201212121212` or `1212121212` and
|
|||
api will be put into fake mode.
|
||||
Static data will be returned and no calls to backend will be made.
|
||||
|
||||
The `LoginStatusChecker` returned by the login method will have `.token` set to "fake".
|
||||
The `LoginStatusChecker` returned by the login method will have `.token` set to 'fake'.
|
||||
|
||||
## Try it out
|
||||
|
||||
|
|
|
@ -165,7 +165,7 @@ describe('api', () => {
|
|||
status = await api.login('1212121212')
|
||||
expect(status.token).toEqual('fake')
|
||||
})
|
||||
it.skip('delivers fake data', async (done) => {
|
||||
it('delivers fake data', async (done) => {
|
||||
api.on('login', async () => {
|
||||
const user = await api.getUser()
|
||||
expect(user).toEqual({
|
||||
|
@ -181,6 +181,12 @@ describe('api', () => {
|
|||
const calendar2 = await api.getCalendar(children[1])
|
||||
expect(calendar2).toHaveLength(18)
|
||||
|
||||
const skola24Children = await api.getSkola24Children()
|
||||
expect(skola24Children).toHaveLength(1)
|
||||
|
||||
const timetable = await api.getTimetable(skola24Children[0], 2021, 15)
|
||||
expect(timetable).toHaveLength(32)
|
||||
|
||||
done()
|
||||
})
|
||||
await api.login('121212121212')
|
||||
|
|
172
lib/api.ts
172
lib/api.ts
|
@ -2,11 +2,11 @@ import { DateTime } from 'luxon'
|
|||
import { EventEmitter } from 'events'
|
||||
import { decode } from 'he'
|
||||
import * as html from 'node-html-parser'
|
||||
import { URLSearchParams } from 'url'
|
||||
import { checkStatus, LoginStatusChecker } from './loginStatus'
|
||||
import {
|
||||
AuthTicket,
|
||||
CalendarItem,
|
||||
Child,
|
||||
Classmate,
|
||||
CookieManager,
|
||||
Fetch,
|
||||
|
@ -16,15 +16,35 @@ import {
|
|||
RequestInit,
|
||||
ScheduleItem,
|
||||
User,
|
||||
Skola24Child,
|
||||
EtjanstChild,
|
||||
SSOSystem,
|
||||
} from './types'
|
||||
import * as routes from './routes'
|
||||
import * as parse from './parse'
|
||||
import * as parse from './parse/index'
|
||||
import wrap, { Fetcher, FetcherOptions } from './fetcher'
|
||||
import * as fake from './fakeData'
|
||||
|
||||
const fakeResponse = <T>(data: T): Promise<T> =>
|
||||
new Promise((res) => setTimeout(() => res(data), 200 + Math.random() * 800))
|
||||
|
||||
const s24Init = {
|
||||
headers: {
|
||||
accept: 'application/json, text/javascript, */*; q=0.01',
|
||||
referer: 'https://fns.stockholm.se/ng/timetable/timetable-viewer/fns.stockholm.se/',
|
||||
'accept-language': 'en-US,en;q=0.9,sv;q=0.8',
|
||||
'cache-control': 'no-cache',
|
||||
'content-type': 'application/json',
|
||||
pragma: 'no-cache',
|
||||
host: 'fns.stockholm.se',
|
||||
'x-scope': '8a22163c-8662-4535-9050-bc5e1923df48',
|
||||
},
|
||||
}
|
||||
|
||||
interface SSOSystems {
|
||||
[name: string]: boolean | undefined
|
||||
}
|
||||
|
||||
export class Api extends EventEmitter {
|
||||
private fetch: Fetcher
|
||||
|
||||
|
@ -40,6 +60,8 @@ export class Api extends EventEmitter {
|
|||
|
||||
public childControllerUrl?: string
|
||||
|
||||
private authorizedSystems: SSOSystems = {}
|
||||
|
||||
constructor(
|
||||
fetch: Fetch,
|
||||
cookieManager: CookieManager,
|
||||
|
@ -281,7 +303,7 @@ export class Api extends EventEmitter {
|
|||
return parse.user(data)
|
||||
}
|
||||
|
||||
public async getChildren(): Promise<Child[]> {
|
||||
public async getChildren(): Promise<EtjanstChild[]> {
|
||||
if (this.isFake) return fakeResponse(fake.children())
|
||||
|
||||
const cdnUrl = await this.retrieveCdnUrl()
|
||||
|
@ -309,7 +331,7 @@ export class Api extends EventEmitter {
|
|||
return parse.children(data)
|
||||
}
|
||||
|
||||
public async getCalendar(child: Child): Promise<CalendarItem[]> {
|
||||
public async getCalendar(child: EtjanstChild): Promise<CalendarItem[]> {
|
||||
if (this.isFake) return fakeResponse(fake.calendar(child))
|
||||
|
||||
const url = routes.calendar(child.id)
|
||||
|
@ -319,7 +341,7 @@ export class Api extends EventEmitter {
|
|||
return parse.calendar(data)
|
||||
}
|
||||
|
||||
public async getClassmates(child: Child): Promise<Classmate[]> {
|
||||
public async getClassmates(child: EtjanstChild): Promise<Classmate[]> {
|
||||
if (this.isFake) return fakeResponse(fake.classmates(child))
|
||||
|
||||
const url = routes.classmates(child.sdsId)
|
||||
|
@ -330,7 +352,7 @@ export class Api extends EventEmitter {
|
|||
}
|
||||
|
||||
public async getSchedule(
|
||||
child: Child,
|
||||
child: EtjanstChild,
|
||||
from: DateTime,
|
||||
to: DateTime
|
||||
): Promise<ScheduleItem[]> {
|
||||
|
@ -343,7 +365,7 @@ export class Api extends EventEmitter {
|
|||
return parse.schedule(data)
|
||||
}
|
||||
|
||||
public async getNews(child: Child): Promise<NewsItem[]> {
|
||||
public async getNews(child: EtjanstChild): Promise<NewsItem[]> {
|
||||
if (this.isFake) return fakeResponse(fake.news(child))
|
||||
|
||||
const url = routes.news(child.id)
|
||||
|
@ -353,7 +375,7 @@ export class Api extends EventEmitter {
|
|||
return parse.news(data)
|
||||
}
|
||||
|
||||
public async getNewsDetails(child: Child, item: NewsItem): Promise<any> {
|
||||
public async getNewsDetails(child: EtjanstChild, item: NewsItem): Promise<any> {
|
||||
if (this.isFake) {
|
||||
return fakeResponse(fake.news(child).find((ni) => ni.id === item.id))
|
||||
}
|
||||
|
@ -364,7 +386,7 @@ export class Api extends EventEmitter {
|
|||
return parse.newsItemDetails(data)
|
||||
}
|
||||
|
||||
public async getMenu(child: Child): Promise<MenuItem[]> {
|
||||
public async getMenu(child: EtjanstChild): Promise<MenuItem[]> {
|
||||
if (this.isFake) return fakeResponse(fake.menu(child))
|
||||
|
||||
const menuService = await this.getMenuChoice(child)
|
||||
|
@ -383,7 +405,7 @@ export class Api extends EventEmitter {
|
|||
return parse.menuList(data)
|
||||
}
|
||||
|
||||
private async getMenuChoice(child: Child): Promise<string> {
|
||||
private async getMenuChoice(child: EtjanstChild): Promise<string> {
|
||||
const url = routes.menuChoice(child.id)
|
||||
const session = this.getRequestInit()
|
||||
const response = await this.fetch('menu-choice', url, session)
|
||||
|
@ -392,7 +414,7 @@ export class Api extends EventEmitter {
|
|||
return etjanstResponse
|
||||
}
|
||||
|
||||
public async getNotifications(child: Child): Promise<Notification[]> {
|
||||
public async getNotifications(child: EtjanstChild): Promise<Notification[]> {
|
||||
if (this.isFake) return fakeResponse(fake.notifications(child))
|
||||
|
||||
const url = routes.notifications(child.sdsId)
|
||||
|
@ -402,10 +424,138 @@ export class Api extends EventEmitter {
|
|||
return parse.notifications(data)
|
||||
}
|
||||
|
||||
private async readSAMLRequest(targetSystem: string): Promise<string> {
|
||||
const url = routes.ssoRequestUrl(targetSystem)
|
||||
const session = this.getRequestInit({
|
||||
redirect: 'follow',
|
||||
})
|
||||
const response = await this.fetch('samlRequest', url, session)
|
||||
const text = await response.text()
|
||||
const samlRequest = /name="SAMLRequest" value="(?<saml>\S+)">/gm.exec(text || '')?.groups?.saml
|
||||
if (!samlRequest) {
|
||||
throw new Error('Could not parse SAML Request')
|
||||
} else {
|
||||
return samlRequest
|
||||
}
|
||||
}
|
||||
|
||||
private async submitSAMLRequest(samlRequest: string): Promise<string> {
|
||||
const body = new URLSearchParams({ SAMLRequest: samlRequest }).toString()
|
||||
const url = routes.ssoResponseUrl
|
||||
const session = this.getRequestInit({
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
|
||||
redirect: 'follow',
|
||||
method: 'POST',
|
||||
body,
|
||||
})
|
||||
const response = await this.fetch('samlResponse', url, session)
|
||||
const text = await response.text()
|
||||
const samlResponse = /name="SAMLResponse" value="(?<saml>\S+)">/gm.exec(text)?.groups?.saml
|
||||
if (!samlResponse) {
|
||||
throw new Error('Could not parse SAML Response')
|
||||
} else {
|
||||
return samlResponse
|
||||
}
|
||||
}
|
||||
|
||||
private async ssoAuthorize(targetSystem: SSOSystem): Promise<string> {
|
||||
if (this.authorizedSystems[targetSystem]) {
|
||||
return ''
|
||||
}
|
||||
const samlRequest = await this.readSAMLRequest(targetSystem)
|
||||
const samlResponse = await this.submitSAMLRequest(samlRequest)
|
||||
|
||||
const body = new URLSearchParams({ SAMLResponse: samlResponse }).toString()
|
||||
const url = routes.samlResponseUrl
|
||||
const session = this.getRequestInit({
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
},
|
||||
redirect: 'follow',
|
||||
method: 'POST',
|
||||
body,
|
||||
})
|
||||
const response = await this.fetch('samlAuthorize', url, session)
|
||||
const text = await response.text()
|
||||
this.authorizedSystems[targetSystem] = true
|
||||
return text
|
||||
}
|
||||
|
||||
public async getSkola24Children(): Promise<Skola24Child[]>{
|
||||
if (this.isFake) return fakeResponse(fake.skola24Children())
|
||||
|
||||
await this.ssoAuthorize('TimetableViewer')
|
||||
const body = { getPersonalTimetablesRequest: {
|
||||
hostName: 'fns.stockholm.se'
|
||||
}}
|
||||
const session = this.getRequestInit({
|
||||
...s24Init,
|
||||
body: JSON.stringify(body),
|
||||
method: 'POST',
|
||||
})
|
||||
|
||||
const url = routes.timetables
|
||||
const response = await this.fetch('s24children', url, session)
|
||||
const {
|
||||
data: {
|
||||
getPersonalTimetablesResponse: {
|
||||
childrenTimetables
|
||||
}
|
||||
}
|
||||
} = await response.json()
|
||||
|
||||
return childrenTimetables as Skola24Child[]
|
||||
}
|
||||
|
||||
private async getRenderKey(): Promise<string> {
|
||||
const url = routes.renderKey
|
||||
const session = this.getRequestInit(s24Init)
|
||||
const response = await this.fetch('renderKey', url, session)
|
||||
const { data: { key } } = await response.json()
|
||||
return key as string
|
||||
}
|
||||
|
||||
public async getTimetable(child: Skola24Child, week: number, year: number): Promise<any> {
|
||||
if (this.isFake) return fakeResponse(fake.timetable(child))
|
||||
|
||||
const url = routes.timetable
|
||||
const renderKey = await this.getRenderKey()
|
||||
const params = {
|
||||
blackAndWhite: false,
|
||||
customerKey: '',
|
||||
endDate: null,
|
||||
height: 1063,
|
||||
host: 'fns.stockholm.se',
|
||||
periodText: '',
|
||||
privateFreeTextMode: null,
|
||||
privateSelectionMode: true,
|
||||
renderKey,
|
||||
scheduleDay: 0,
|
||||
selection: child.personGuid,
|
||||
selectionType: 5,
|
||||
showHeader: false,
|
||||
startDate: null,
|
||||
unitGuid: child.unitGuid,
|
||||
week,
|
||||
width: 1227,
|
||||
year,
|
||||
}
|
||||
const session = this.getRequestInit({
|
||||
...s24Init,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(params),
|
||||
})
|
||||
const response = await this.fetch(`timetable_${child.personGuid}_${year}_${week}`, url, session)
|
||||
const json = await response.json()
|
||||
|
||||
return parse.timetable(json, year, week)
|
||||
}
|
||||
|
||||
public async logout() {
|
||||
this.isFake = false
|
||||
this.personalNumber = undefined
|
||||
this.isLoggedIn = false
|
||||
this.authorizedSystems = {}
|
||||
this.emit('logout')
|
||||
await this.clearSession()
|
||||
}
|
||||
|
|
440
lib/fakeData.ts
440
lib/fakeData.ts
|
@ -3,10 +3,13 @@ import {
|
|||
CalendarItem,
|
||||
Child,
|
||||
Classmate,
|
||||
EtjanstChild,
|
||||
MenuItem,
|
||||
NewsItem,
|
||||
Notification,
|
||||
ScheduleItem,
|
||||
Skola24Child,
|
||||
TimetableEntry,
|
||||
User,
|
||||
} from './types'
|
||||
|
||||
|
@ -1090,7 +1093,7 @@ export const user = (): User => ({
|
|||
lastName: 'Namnsson',
|
||||
})
|
||||
|
||||
export const children = (): Child[] => [
|
||||
export const children = (): EtjanstChild[] => [
|
||||
{
|
||||
name: 'Shanel Nilsson (elev)',
|
||||
id: '39b59e-bf4b9f-f68ac25321-977218-bf0',
|
||||
|
@ -1106,8 +1109,19 @@ export const children = (): Child[] => [
|
|||
schoolId: '8e6b13b-3116-e66c39b-a4c3fa5-a1d72d9',
|
||||
},
|
||||
]
|
||||
export const skola24Children = (): Skola24Child[] => [
|
||||
{
|
||||
firstName: 'Shanel',
|
||||
lastName: 'Jonsson Nilsson',
|
||||
personGuid: 'abc123',
|
||||
schoolGuid: 'def456',
|
||||
schoolID: 'ghi789',
|
||||
timetableID: 'jkl012',
|
||||
unitGuid: 'mno345'
|
||||
},
|
||||
]
|
||||
|
||||
export const classmates = (child: Child): Classmate[] =>
|
||||
export const classmates = (child: EtjanstChild): Classmate[] =>
|
||||
data[child.id].classmates
|
||||
|
||||
export const news = (child: Child): NewsItem[] => data[child.id].news
|
||||
|
@ -1122,3 +1136,425 @@ export const menu = (child: Child): MenuItem[] => data[child.id].menu
|
|||
|
||||
export const notifications = (child: Child): Notification[] =>
|
||||
data[child.id].notifications
|
||||
|
||||
export const timetable = (child: Skola24Child): TimetableEntry[] => {
|
||||
if (!child.personGuid || !child.unitGuid) return []
|
||||
return [
|
||||
{
|
||||
id: 'N2FjMDc1NjYtZmM2Yy0wZDQyLTY3M2YtZWI5NGNiZDA3ZGU4',
|
||||
blockName: '',
|
||||
code: 'Lunch',
|
||||
dayOfWeek: 1,
|
||||
location: 'Ö5',
|
||||
teacher: '',
|
||||
timeEnd: '12:05:00',
|
||||
timeStart: '11:40:00',
|
||||
dateStart: '2021-04-12T11:40:00.000+02:00',
|
||||
dateEnd: '2021-04-12T12:05:00.000+02:00',
|
||||
name: '',
|
||||
},
|
||||
{
|
||||
id: 'ZTQ1NWE0N2EtNzAwOS0wZTAzLTQ1ZDYtNTA1NWI4Y2JhNDYw',
|
||||
blockName: '',
|
||||
code: 'BL',
|
||||
dayOfWeek: 1,
|
||||
location: '221',
|
||||
teacher: 'KUr',
|
||||
timeEnd: '11:35:00',
|
||||
timeStart: '09:40:00',
|
||||
dateStart: '2021-04-12T09:40:00.000+02:00',
|
||||
dateEnd: '2021-04-12T11:35:00.000+02:00',
|
||||
name: '',
|
||||
},
|
||||
{
|
||||
id: 'YjAxODRmY2QtNTJjZS0wMDJlLTYxOGItYmFlNTVlNDgzZmVk',
|
||||
blockName: '',
|
||||
code: 'NO',
|
||||
dayOfWeek: 1,
|
||||
location: '307',
|
||||
teacher: 'TBo',
|
||||
timeEnd: '13:30:00',
|
||||
timeStart: '12:30:00',
|
||||
dateStart: '2021-04-12T12:30:00.000+02:00',
|
||||
dateEnd: '2021-04-12T13:30:00.000+02:00',
|
||||
name: '',
|
||||
},
|
||||
{
|
||||
id: 'MWRiZGI1NzgtYWIzNy0wYzMwLTVkMmEtMWFjNWRkMTRmOTdh',
|
||||
blockName: '',
|
||||
code: 'IDH',
|
||||
dayOfWeek: 1,
|
||||
location: '215',
|
||||
teacher: 'HAl',
|
||||
timeEnd: '15:45:00',
|
||||
timeStart: '14:40:00',
|
||||
dateStart: '2021-04-12T14:40:00.000+02:00',
|
||||
dateEnd: '2021-04-12T15:45:00.000+02:00',
|
||||
name: '',
|
||||
},
|
||||
{
|
||||
id: 'MmZkZTZiMzMtMjdjMS0wZGIzLTUzYWYtZTg0Zjc1NDRlNzQw',
|
||||
blockName: '',
|
||||
code: 'M2FR',
|
||||
dayOfWeek: 1,
|
||||
location: '304',
|
||||
teacher: 'DNi',
|
||||
timeEnd: '14:25:00',
|
||||
timeStart: '13:40:00',
|
||||
dateStart: '2021-04-12T13:40:00.000+02:00',
|
||||
dateEnd: '2021-04-12T14:25:00.000+02:00',
|
||||
name: '',
|
||||
},
|
||||
{
|
||||
id: 'MzAxMzU3MWItZGM1Ny0wOGVhLTVkZjUtOGFkMGIyYTY2OTAx',
|
||||
blockName: '',
|
||||
code: 'SO',
|
||||
dayOfWeek: 1,
|
||||
location: '303',
|
||||
teacher: 'HRr',
|
||||
timeEnd: '09:25:00',
|
||||
timeStart: '08:15:00',
|
||||
dateStart: '2021-04-12T08:15:00.000+02:00',
|
||||
dateEnd: '2021-04-12T09:25:00.000+02:00',
|
||||
name: '',
|
||||
},
|
||||
{
|
||||
id: 'NDY3MDY1MmYtOTIzYi0wZmQ0LTVlZGEtNGVhZDRkOTExNTgz',
|
||||
blockName: '',
|
||||
code: 'M2FR',
|
||||
dayOfWeek: 2,
|
||||
location: '302,Fjärr',
|
||||
teacher: 'DNi',
|
||||
timeEnd: '09:50:00',
|
||||
timeStart: '09:05:00',
|
||||
dateStart: '2021-04-13T09:05:00.000+02:00',
|
||||
dateEnd: '2021-04-13T09:50:00.000+02:00',
|
||||
name: '',
|
||||
},
|
||||
{
|
||||
id: 'NmE4OTU1NmItYzM0ZS0wYTI1LTYzM2QtYzBiN2M4OTVmYTQ3',
|
||||
blockName: '',
|
||||
code: 'EN',
|
||||
dayOfWeek: 2,
|
||||
location: 'Fjärr',
|
||||
teacher: 'TPe',
|
||||
timeEnd: '13:15:00',
|
||||
timeStart: '12:30:00',
|
||||
dateStart: '2021-04-13T12:30:00.000+02:00',
|
||||
dateEnd: '2021-04-13T13:15:00.000+02:00',
|
||||
name: '',
|
||||
},
|
||||
{
|
||||
id: 'NDAxODRjOTctMmE5ZC0wMzdjLTY2NDMtODhlODEzOTQ3YTJh',
|
||||
blockName: '',
|
||||
code: 'Lunch',
|
||||
dayOfWeek: 2,
|
||||
location: 'Fjärr',
|
||||
teacher: '',
|
||||
timeEnd: '12:05:00',
|
||||
timeStart: '11:40:00',
|
||||
dateStart: '2021-04-13T11:40:00.000+02:00',
|
||||
dateEnd: '2021-04-13T12:05:00.000+02:00',
|
||||
name: '',
|
||||
},
|
||||
{
|
||||
id: 'ZTc4YTcyZTUtMDc0NS0wNDE0LTVjODctYjY0MzQ2MGM3MDll',
|
||||
blockName: '',
|
||||
code: 'MA',
|
||||
dayOfWeek: 2,
|
||||
location: 'Fjärr',
|
||||
teacher: 'CBr',
|
||||
timeEnd: '11:20:00',
|
||||
timeStart: '10:00:00',
|
||||
dateStart: '2021-04-13T10:00:00.000+02:00',
|
||||
dateEnd: '2021-04-13T11:20:00.000+02:00',
|
||||
name: '',
|
||||
},
|
||||
{
|
||||
id: 'MjRkMWE4YTItYTk5ZC0wYTFmLTVhMDgtMThiMmNhZDc1ZDUz',
|
||||
blockName: '',
|
||||
code: 'MU',
|
||||
dayOfWeek: 2,
|
||||
location: 'Fjärr',
|
||||
teacher: 'KBj',
|
||||
timeEnd: '14:15:00',
|
||||
timeStart: '13:30:00',
|
||||
dateStart: '2021-04-13T13:30:00.000+02:00',
|
||||
dateEnd: '2021-04-13T14:15:00.000+02:00',
|
||||
name: '',
|
||||
},
|
||||
{
|
||||
id: 'NTU4ZTc4ZTctNDQyMy0wMjVkLTRiYzktZGUwYmFmYzk2YTlj',
|
||||
blockName: '',
|
||||
code: 'EN',
|
||||
dayOfWeek: 3,
|
||||
location: '303',
|
||||
teacher: 'TPe',
|
||||
timeEnd: '09:55:00',
|
||||
timeStart: '09:10:00',
|
||||
dateStart: '2021-04-14T09:10:00.000+02:00',
|
||||
dateEnd: '2021-04-14T09:55:00.000+02:00',
|
||||
name: '',
|
||||
},
|
||||
{
|
||||
id: 'NDUyNjIxODItYzFiOC0wOTFjLTYwODYtZDllZjZjN2QyYzA3',
|
||||
blockName: '',
|
||||
code: 'SV a)',
|
||||
dayOfWeek: 3,
|
||||
location: '303',
|
||||
teacher: 'JCa',
|
||||
timeEnd: '14:45:00',
|
||||
timeStart: '14:00:00',
|
||||
dateStart: '2021-04-14T14:00:00.000+02:00',
|
||||
dateEnd: '2021-04-14T14:45:00.000+02:00',
|
||||
name: '',
|
||||
},
|
||||
{
|
||||
id: 'NDdkMGI0ZjItMjkxMC0wYWI1LTQ0YWMtNDY3NTdkZTE2Njg3',
|
||||
blockName: '',
|
||||
code: 'SO',
|
||||
dayOfWeek: 3,
|
||||
location: '303',
|
||||
teacher: 'HRr',
|
||||
timeEnd: '11:00:00',
|
||||
timeStart: '10:05:00',
|
||||
dateStart: '2021-04-14T10:05:00.000+02:00',
|
||||
dateEnd: '2021-04-14T11:00:00.000+02:00',
|
||||
name: '',
|
||||
},
|
||||
{
|
||||
id: 'ZTI2ZDgyNWUtM2ZlOS0wZDVmLTY5NTctNGYzZThjMTMxOTdh',
|
||||
blockName: '',
|
||||
code: 'NO a)',
|
||||
dayOfWeek: 3,
|
||||
location: '307',
|
||||
teacher: 'TBo',
|
||||
timeEnd: '13:50:00',
|
||||
timeStart: '12:50:00',
|
||||
dateStart: '2021-04-14T12:50:00.000+02:00',
|
||||
dateEnd: '2021-04-14T13:50:00.000+02:00',
|
||||
name: '',
|
||||
},
|
||||
{
|
||||
id: 'NzMxNjczNGMtMmZmZi0wM2YzLTU0ZjMtODdjOTAwYzIwNTUw',
|
||||
blockName: '',
|
||||
code: 'Lunch',
|
||||
dayOfWeek: 3,
|
||||
location: 'Ö5',
|
||||
teacher: '',
|
||||
timeEnd: '12:40:00',
|
||||
timeStart: '12:15:00',
|
||||
dateStart: '2021-04-14T12:15:00.000+02:00',
|
||||
dateEnd: '2021-04-14T12:40:00.000+02:00',
|
||||
name: '',
|
||||
},
|
||||
{
|
||||
id: 'MWRkZjhlZTktNTBmMC0wZjNhLTQ1OTgtMWJkOWM3MjI2NWQ4',
|
||||
blockName: '',
|
||||
code: 'SV',
|
||||
dayOfWeek: 3,
|
||||
location: '303',
|
||||
teacher: 'JCa',
|
||||
timeEnd: '12:05:00',
|
||||
timeStart: '11:20:00',
|
||||
dateStart: '2021-04-14T11:20:00.000+02:00',
|
||||
dateEnd: '2021-04-14T12:05:00.000+02:00',
|
||||
name: '',
|
||||
},
|
||||
{
|
||||
id: 'NzM2Mjc2ZTYtY2JlYy0wOTc1LTU1ZGYtNjMwZjhjZWVjNjgy',
|
||||
blockName: '',
|
||||
code: 'MA a)',
|
||||
dayOfWeek: 3,
|
||||
location: '307',
|
||||
teacher: 'CBr',
|
||||
timeEnd: '15:45:00',
|
||||
timeStart: '15:00:00',
|
||||
dateStart: '2021-04-14T15:00:00.000+02:00',
|
||||
dateEnd: '2021-04-14T15:45:00.000+02:00',
|
||||
name: '',
|
||||
},
|
||||
{
|
||||
id: 'YWNlZmEzZjYtM2EwNC0wYWY3LTU1N2MtMDBlMTA4MDQzMzRl',
|
||||
blockName: '',
|
||||
code: 'MU',
|
||||
dayOfWeek: 3,
|
||||
location: '504',
|
||||
teacher: 'KBj',
|
||||
timeEnd: '09:00:00',
|
||||
timeStart: '08:15:00',
|
||||
dateStart: '2021-04-14T08:15:00.000+02:00',
|
||||
dateEnd: '2021-04-14T09:00:00.000+02:00',
|
||||
name: '',
|
||||
},
|
||||
{
|
||||
id: 'NDc4MThmMDYtYmYxYi0wZDBkLTdhNmItZGVjMjY3OWY3MmYz',
|
||||
blockName: '',
|
||||
code: 'IDH',
|
||||
dayOfWeek: 4,
|
||||
location: 'Fjärr',
|
||||
teacher: 'AKö,CSv,HAl',
|
||||
timeEnd: '15:45:00',
|
||||
timeStart: '14:35:00',
|
||||
dateStart: '2021-04-15T14:35:00.000+02:00',
|
||||
dateEnd: '2021-04-15T15:45:00.000+02:00',
|
||||
name: '',
|
||||
},
|
||||
{
|
||||
id: 'ZjQyZjNkOWItYWMzZi0wYWRhLTQ3YzItNTZiNTJkOTRmY2Iy',
|
||||
blockName: '',
|
||||
code: 'M2FR',
|
||||
dayOfWeek: 4,
|
||||
location: 'Fjärr',
|
||||
teacher: 'DNi',
|
||||
timeEnd: '11:55:00',
|
||||
timeStart: '11:10:00',
|
||||
dateStart: '2021-04-15T11:10:00.000+02:00',
|
||||
dateEnd: '2021-04-15T11:55:00.000+02:00',
|
||||
name: '',
|
||||
},
|
||||
{
|
||||
id: 'YzQ2NWZlOWMtYzM3ZC0wYzBlLTQzNTQtODMyYmU3ODcxMDQ3',
|
||||
blockName: '',
|
||||
code: 'MTID Arbetslagsråd 7C',
|
||||
dayOfWeek: 4,
|
||||
location: 'Fjärr',
|
||||
teacher: 'JCa,CBr',
|
||||
timeEnd: '10:00:00',
|
||||
timeStart: '09:15:00',
|
||||
dateStart: '2021-04-15T09:15:00.000+02:00',
|
||||
dateEnd: '2021-04-15T10:00:00.000+02:00',
|
||||
name: '',
|
||||
},
|
||||
{
|
||||
id: 'YzMwMGY0YzAtNjhjNi0wYzY0LTU1MjctODg2MWQ4ZTRmZTI2',
|
||||
blockName: '',
|
||||
code: 'MU',
|
||||
dayOfWeek: 4,
|
||||
location: 'Fjärr',
|
||||
teacher: 'KBj',
|
||||
timeEnd: '10:55:00',
|
||||
timeStart: '10:10:00',
|
||||
dateStart: '2021-04-15T10:10:00.000+02:00',
|
||||
dateEnd: '2021-04-15T10:55:00.000+02:00',
|
||||
name: '',
|
||||
},
|
||||
{
|
||||
id: 'ZDNlNTFhMGUtYWFlYy0wOGI0LTVlMGItOTc0MzFiZmIwODcx',
|
||||
blockName: '',
|
||||
code: 'Lunch',
|
||||
dayOfWeek: 4,
|
||||
location: 'Fjärr',
|
||||
teacher: '',
|
||||
timeEnd: '12:25:00',
|
||||
timeStart: '12:00:00',
|
||||
dateStart: '2021-04-15T12:00:00.000+02:00',
|
||||
dateEnd: '2021-04-15T12:25:00.000+02:00',
|
||||
name: '',
|
||||
},
|
||||
{
|
||||
id: 'MDRiZWMyODMtNjEwZC0wZDYwLTRlOWItYTY1MjAwZTc0YTZm',
|
||||
blockName: '',
|
||||
code: 'SO',
|
||||
dayOfWeek: 4,
|
||||
location: 'Fjärr',
|
||||
teacher: 'HRr',
|
||||
timeEnd: '13:10:00',
|
||||
timeStart: '12:35:00',
|
||||
dateStart: '2021-04-15T12:35:00.000+02:00',
|
||||
dateEnd: '2021-04-15T13:10:00.000+02:00',
|
||||
name: '',
|
||||
},
|
||||
{
|
||||
id: 'YTA0ZTA2NTktYTU5MS0wMTFmLTVlYWYtNWM1MTgxNDJlMDcy',
|
||||
blockName: '',
|
||||
code: 'EN a)',
|
||||
dayOfWeek: 4,
|
||||
location: 'Fjärr',
|
||||
teacher: 'TPe',
|
||||
timeEnd: '14:20:00',
|
||||
timeStart: '13:35:00',
|
||||
dateStart: '2021-04-15T13:35:00.000+02:00',
|
||||
dateEnd: '2021-04-15T14:20:00.000+02:00',
|
||||
name: '',
|
||||
},
|
||||
{
|
||||
id: 'OGJhN2MxYTYtMDQ4NS0wNWNhLTUwZWEtZDQ5YzQyMzFhYzc5',
|
||||
blockName: '',
|
||||
code: 'Lunch',
|
||||
dayOfWeek: 5,
|
||||
location: 'Ö5',
|
||||
teacher: '',
|
||||
timeEnd: '12:05:00',
|
||||
timeStart: '11:40:00',
|
||||
dateStart: '2021-04-16T11:40:00.000+02:00',
|
||||
dateEnd: '2021-04-16T12:05:00.000+02:00',
|
||||
name: '',
|
||||
},
|
||||
{
|
||||
id: 'ZmUwMGEwM2QtNTExMy0wODliLTY1ZGEtODM0YmRjNjc1NDIw',
|
||||
blockName: '',
|
||||
code: 'MA a)',
|
||||
dayOfWeek: 5,
|
||||
location: '303',
|
||||
teacher: 'CBr',
|
||||
timeEnd: '14:00:00',
|
||||
timeStart: '13:15:00',
|
||||
dateStart: '2021-04-16T13:15:00.000+02:00',
|
||||
dateEnd: '2021-04-16T14:00:00.000+02:00',
|
||||
name: '',
|
||||
},
|
||||
{
|
||||
id: 'Y2IwYjYzZDEtODAxYi0wMTNjLTRjNDMtMDFlODgzMmY4MWEy',
|
||||
blockName: '',
|
||||
code: 'MU a)',
|
||||
dayOfWeek: 5,
|
||||
location: '510',
|
||||
teacher: 'KBj',
|
||||
timeEnd: '13:05:00',
|
||||
timeStart: '12:20:00',
|
||||
dateStart: '2021-04-16T12:20:00.000+02:00',
|
||||
dateEnd: '2021-04-16T13:05:00.000+02:00',
|
||||
name: '',
|
||||
},
|
||||
{
|
||||
id: 'N2JkMGFiOTYtMjI5OC0wMjZiLTc3OGEtN2JkN2Q4MDZkNTEy',
|
||||
blockName: '',
|
||||
code: 'SL tmtx)',
|
||||
dayOfWeek: 5,
|
||||
location: '860',
|
||||
teacher: 'EAl',
|
||||
timeEnd: '15:10:00',
|
||||
timeStart: '14:10:00',
|
||||
dateStart: '2021-04-16T14:10:00.000+02:00',
|
||||
dateEnd: '2021-04-16T15:10:00.000+02:00',
|
||||
name: '',
|
||||
},
|
||||
{
|
||||
id: 'NzkxMjE3MDctMWExNS0wN2RmLTQwMzQtNTEyZTczZjQyZTUw',
|
||||
blockName: '',
|
||||
code: 'SV',
|
||||
dayOfWeek: 5,
|
||||
location: '303',
|
||||
teacher: 'JCa',
|
||||
timeEnd: '10:35:00',
|
||||
timeStart: '09:20:00',
|
||||
dateStart: '2021-04-16T09:20:00.000+02:00',
|
||||
dateEnd: '2021-04-16T10:35:00.000+02:00',
|
||||
name: '',
|
||||
},
|
||||
{
|
||||
id: 'ZTU1ZDQxNzQtN2Q3Yy0wMDMxLTY2ZmYtZmIyNGM5MjM3ZTRj',
|
||||
blockName: '',
|
||||
code: 'MA',
|
||||
dayOfWeek: 5,
|
||||
location: '303',
|
||||
teacher: 'CBr',
|
||||
timeEnd: '11:35:00',
|
||||
timeStart: '10:40:00',
|
||||
dateStart: '2021-04-16T10:40:00.000+02:00',
|
||||
dateEnd: '2021-04-16T11:35:00.000+02:00',
|
||||
name: '',
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
import { timetable, timetableEntry, TimetableResponse } from '../'
|
||||
|
||||
let response: TimetableResponse
|
||||
|
||||
describe('Timetable', () => {
|
||||
beforeEach(() => {
|
||||
response = {
|
||||
error: null,
|
||||
data: {
|
||||
textList: [
|
||||
{
|
||||
x: 11,
|
||||
y: 64,
|
||||
fColor: '#000000',
|
||||
fontsize: 14,
|
||||
text: '8:30',
|
||||
bold: false,
|
||||
italic: false,
|
||||
id: 9,
|
||||
parentId: 6,
|
||||
type: 'ClockAxisBox'
|
||||
},
|
||||
{
|
||||
x: 11,
|
||||
y: 125,
|
||||
fColor: '#000000',
|
||||
fontsize: 14,
|
||||
text: '9:00',
|
||||
bold: false,
|
||||
italic: false,
|
||||
id: 12,
|
||||
parentId: 6,
|
||||
type: 'ClockAxisBox'
|
||||
},
|
||||
],
|
||||
boxList: [
|
||||
{
|
||||
x: 0,
|
||||
y: 950,
|
||||
width: 1226,
|
||||
height: 112,
|
||||
bColor: '#FFFFFF',
|
||||
fColor: '#FFFFFF',
|
||||
id: 0,
|
||||
parentId: null,
|
||||
type: 'Footer',
|
||||
lessonGuids: null
|
||||
},
|
||||
{
|
||||
x: 56,
|
||||
y: 0,
|
||||
width: 223,
|
||||
height: 34,
|
||||
bColor: '#FFFFFF',
|
||||
fColor: '#000000',
|
||||
id: 1,
|
||||
parentId: null,
|
||||
type: 'HeadingDay',
|
||||
lessonGuids: null
|
||||
},
|
||||
],
|
||||
lineList: [
|
||||
{
|
||||
p1x: 51,
|
||||
p1y: 34,
|
||||
p2x: 56,
|
||||
p2y: 34,
|
||||
color: '#000000',
|
||||
id: 7,
|
||||
parentId: 6,
|
||||
type: 'ClockAxisGradiation'
|
||||
},
|
||||
{
|
||||
p1x: 0,
|
||||
p1y: 64,
|
||||
p2x: 56,
|
||||
p2y: 64,
|
||||
color: '#000000',
|
||||
id: 8,
|
||||
parentId: 6,
|
||||
type: 'ClockAxisGradiation'
|
||||
},
|
||||
],
|
||||
lessonInfo: [
|
||||
{
|
||||
guidId: 'N2FjMDc1NjYtZmM2Yy0wZDQyLTY3M2YtZWI5NGNiZDA3ZGU4',
|
||||
texts: [
|
||||
'Lunch',
|
||||
'',
|
||||
'Ö5'
|
||||
],
|
||||
timeStart: '11:40:00',
|
||||
timeEnd: '12:05:00',
|
||||
dayOfWeekNumber: 1,
|
||||
blockName: ''
|
||||
},
|
||||
{
|
||||
guidId: 'ZTQ1NWE0N2EtNzAwOS0wZTAzLTQ1ZDYtNTA1NWI4Y2JhNDYw',
|
||||
texts: [
|
||||
'BL',
|
||||
'KUr',
|
||||
'221'
|
||||
],
|
||||
timeStart: '09:40:00',
|
||||
timeEnd: '11:35:00',
|
||||
dayOfWeekNumber: 1,
|
||||
blockName: 'block'
|
||||
},
|
||||
]
|
||||
},
|
||||
exception: null,
|
||||
validation: [],
|
||||
}
|
||||
})
|
||||
describe('timetableEntry', () => {
|
||||
it('parses basic timeTableEntry data correctly', () => {
|
||||
const entry = timetableEntry(response.data.lessonInfo[1], 2021, 15)
|
||||
|
||||
expect(entry.id).toEqual('ZTQ1NWE0N2EtNzAwOS0wZTAzLTQ1ZDYtNTA1NWI4Y2JhNDYw')
|
||||
expect(entry.code).toEqual('BL')
|
||||
expect(entry.teacher).toEqual('KUr')
|
||||
expect(entry.location).toEqual('221')
|
||||
expect(entry.timeStart).toEqual('09:40:00')
|
||||
expect(entry.timeEnd).toEqual('11:35:00')
|
||||
expect(entry.dayOfWeek).toEqual(1)
|
||||
expect(entry.blockName).toEqual('block')
|
||||
})
|
||||
it('parses dates correctly', () => {
|
||||
const entry = timetableEntry(response.data.lessonInfo[1], 2021, 15)
|
||||
|
||||
expect(entry.dateStart).toEqual('2021-04-12T09:40:00.000+02:00')
|
||||
expect(entry.dateEnd).toEqual('2021-04-12T11:35:00.000+02:00')
|
||||
})
|
||||
})
|
||||
describe('timetable', () => {
|
||||
it('throws error', () => {
|
||||
response.error = 'b0rk'
|
||||
expect(() => timetable(response, 2021, 15)).toThrow('b0rk')
|
||||
})
|
||||
it('parses lessonInfo', () => {
|
||||
const table = timetable(response, 2021, 15)
|
||||
|
||||
expect(table).toHaveLength(2)
|
||||
expect(table[0].id).toEqual('N2FjMDc1NjYtZmM2Yy0wZDQyLTY3M2YtZWI5NGNiZDA3ZGU4')
|
||||
expect(table[1].id).toEqual('ZTQ1NWE0N2EtNzAwOS0wZTAzLTQ1ZDYtNTA1NWI4Y2JhNDYw')
|
||||
})
|
||||
})
|
||||
})
|
|
@ -6,4 +6,5 @@ export * from './menu'
|
|||
export * from './news'
|
||||
export * from './notifications'
|
||||
export * from './schedule'
|
||||
export * from './timetable'
|
||||
export * from './user'
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
import { DateTime } from 'luxon'
|
||||
import { TimetableEntry } from '../types'
|
||||
|
||||
const calculateDate = (year: number, weekNumber: number, weekday: number, time: string): string => {
|
||||
const [hours, minutes, seconds] = time.split(':')
|
||||
return DateTime.local()
|
||||
.set({
|
||||
year,
|
||||
weekNumber,
|
||||
weekday,
|
||||
hour: parseInt(hours, 10),
|
||||
minute: parseInt(minutes, 10),
|
||||
second: parseInt(seconds, 10),
|
||||
millisecond: 0,
|
||||
}).toISO()
|
||||
}
|
||||
|
||||
interface TimetableResponseEntry {
|
||||
guidId: string
|
||||
texts: string[]
|
||||
timeStart: string
|
||||
timeEnd: string
|
||||
dayOfWeekNumber: number
|
||||
blockName: string
|
||||
}
|
||||
export interface TimetableResponse {
|
||||
error: string | null
|
||||
data: {
|
||||
textList: any[]
|
||||
boxList: any[]
|
||||
lineList: any[]
|
||||
lessonInfo: TimetableResponseEntry[]
|
||||
}
|
||||
exception: any
|
||||
validation: any[]
|
||||
}
|
||||
|
||||
interface EntryParser {
|
||||
(args: TimetableResponseEntry, year: number, week: number): TimetableEntry
|
||||
}
|
||||
export const timetableEntry: EntryParser = ({
|
||||
guidId, texts: [code, teacher, location], timeStart, timeEnd, dayOfWeekNumber, blockName,
|
||||
}, year, week) => ({
|
||||
id: guidId,
|
||||
blockName,
|
||||
code,
|
||||
dayOfWeek: dayOfWeekNumber,
|
||||
location,
|
||||
teacher,
|
||||
timeEnd,
|
||||
timeStart,
|
||||
dateStart: calculateDate(year, week, dayOfWeekNumber, timeStart),
|
||||
dateEnd: calculateDate(year, week, dayOfWeekNumber, timeEnd),
|
||||
name: ''
|
||||
})
|
||||
|
||||
export const timetable = (response: TimetableResponse, year: number, week: number) => {
|
||||
if (response.error) {
|
||||
throw new Error(response.error)
|
||||
}
|
||||
return response.data.lessonInfo.map((entry) => timetableEntry(entry, year, week))
|
||||
}
|
|
@ -62,3 +62,14 @@ export const childcontrollerScript = `https://etjanst.stockholm.se/vardnadshavar
|
|||
|
||||
export const createItemConfig =
|
||||
'https://raw.githubusercontent.com/kolplattformen/embedded-api/main/config.json'
|
||||
|
||||
// Skola24
|
||||
export const ssoRequestUrl = (targetSystem: string) =>
|
||||
`https://fnsservicesso1.stockholm.se/sso-ng/saml-2.0/authenticate?customer=https://login001.stockholm.se&targetsystem=${targetSystem}`
|
||||
|
||||
export const ssoResponseUrl = 'https://login001.stockholm.se/affwebservices/public/saml2sso'
|
||||
export const samlResponseUrl = 'https://fnsservicesso1.stockholm.se/sso-ng/saml-2.0/response'
|
||||
|
||||
export const timetables = 'https://fns.stockholm.se/ng/api/services/skola24/get/personal/timetables'
|
||||
export const renderKey = 'https://fns.stockholm.se/ng/api/get/timetable/render/key'
|
||||
export const timetable = 'https://fns.stockholm.se/ng/api/render/timetable'
|
||||
|
|
30
lib/types.ts
30
lib/types.ts
|
@ -66,7 +66,7 @@ export interface CalendarItem {
|
|||
* @export
|
||||
* @interface Child
|
||||
*/
|
||||
export interface Child {
|
||||
export interface EtjanstChild {
|
||||
id: string
|
||||
/**
|
||||
* <p>Special ID used to access certain subsystems</p>
|
||||
|
@ -84,6 +84,8 @@ export interface Child {
|
|||
schoolId?: string
|
||||
}
|
||||
|
||||
export interface Child extends EtjanstChild, Skola24Child {}
|
||||
|
||||
/**
|
||||
* @export
|
||||
* @interface Classmate
|
||||
|
@ -181,3 +183,29 @@ export interface User {
|
|||
email?: string | null
|
||||
notificationId?: string
|
||||
}
|
||||
|
||||
export interface Skola24Child {
|
||||
schoolGuid?: string
|
||||
unitGuid?: string
|
||||
schoolID?: string
|
||||
timetableID?: string
|
||||
personGuid?: string
|
||||
firstName?: string
|
||||
lastName?: string
|
||||
}
|
||||
|
||||
export type SSOSystem = 'TimetableViewer'
|
||||
|
||||
export interface TimetableEntry {
|
||||
id: string
|
||||
code: string
|
||||
name: string
|
||||
teacher: string
|
||||
location: string
|
||||
timeStart: string
|
||||
timeEnd: string
|
||||
dayOfWeek: number
|
||||
blockName: string
|
||||
dateStart: string
|
||||
dateEnd: string
|
||||
}
|
||||
|
|
16
run.js
16
run.js
|
@ -22,6 +22,7 @@ const fetchCookie = require('fetch-cookie/node-fetch')
|
|||
const { writeFile } = require('fs/promises')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const { inspect } = require('util')
|
||||
|
||||
const init = require('./dist').default
|
||||
|
||||
|
@ -118,23 +119,30 @@ async function run() {
|
|||
console.log('news')
|
||||
const news = await api.getNews(children[0])
|
||||
*/
|
||||
/*console.log('news details')
|
||||
/* console.log('news details')
|
||||
const newsItems = await Promise.all(
|
||||
news.map((newsItem) =>
|
||||
api.getNewsDetails(children[0], newsItem)
|
||||
.catch((err) => { console.error(newsItem.id, err) })
|
||||
)
|
||||
)
|
||||
console.log(newsItems)*/
|
||||
console.log(newsItems) */
|
||||
|
||||
/*console.log('menu')
|
||||
/* console.log('menu')
|
||||
const menu = await api.getMenu(children[0])
|
||||
console.log(menu)*/
|
||||
console.log(menu) */
|
||||
|
||||
// console.log('notifications')
|
||||
// const notifications = await api.getNotifications(children[0])
|
||||
// console.log(notifications)
|
||||
|
||||
const skola24children = await api.getSkola24Children()
|
||||
console.log(skola24children)
|
||||
|
||||
console.log('timetable')
|
||||
const timetable = await api.getTimetable(skola24children[0], 15, 2021)
|
||||
console.log(inspect(timetable, false, 1000, true))
|
||||
|
||||
await api.logout()
|
||||
})
|
||||
|
||||
|
|
Loading…
Reference in New Issue