11import { File , Paths } from 'expo-file-system' ;
2+ import ICAL from 'ical.js' ;
3+ import type { CalendarEvent , NextEvent , WidgetClass } from '../types' ;
24import { stringToColour } from '../utils/color' ;
3- import type { CalendarEvent , NextEvent } from '../types' ;
4-
5- const ICAL = require ( 'ical.js' ) as {
6- parse : ( input : string ) => unknown [ ] ;
7- Component : new ( jcalData : unknown ) => ICALComponent ;
8- Event : new ( component : unknown ) => ICALEvent ;
9- } ;
105
116interface ICALComponent {
127 getAllSubcomponents ( name : string ) : unknown [ ] ;
@@ -23,96 +18,86 @@ interface ICALEvent {
2318
2419const ADE_BASE = 'https://edtweb.univ-cotedazur.fr' ;
2520const CALENDAR_FILE = new File ( Paths . document , 'calendar.json' ) ;
21+ const ONGOING_THRESHOLD_MS = 15 * 60 * 1000 ;
22+
23+ function getAcademicYearDateRange ( ) : string {
24+ const now = new Date ( ) ;
25+ const month = now . getMonth ( ) + 1 ;
26+ const year = now . getFullYear ( ) ;
27+ const startYear = month >= 9 ? year : year - 1 ;
28+ const endYear = startYear + 1 ;
29+ return `&firstDate=${ startYear } -09-01&lastDate=${ endYear } -08-31` ;
30+ }
2631
27- class EDTDate {
28- ADE_DATE : string ;
29-
30- constructor ( ) {
31- const now = new Date ( ) ;
32- const month = now . getMonth ( ) + 1 ;
33- const year = now . getFullYear ( ) ;
34- const startYear = month >= 9 ? year : year - 1 ;
35- const endYear = startYear + 1 ;
36- this . ADE_DATE = `&firstDate=${ startYear } -09-01&lastDate=${ endYear } -08-31` ;
37- }
32+ function getCurrentAcademicYearString ( ) : string {
33+ const now = new Date ( ) ;
34+ const startYear = now . getMonth ( ) >= 8 ? now . getFullYear ( ) : now . getFullYear ( ) - 1 ;
35+ return `${ startYear } -${ startYear + 1 } ` ;
3836}
3937
40- class EDTConfig {
41- async getSessionId ( ) : Promise < string | null > {
42- try {
43- const res = await fetch (
44- `${ ADE_BASE } /jsp/webapi?function=connect&login=Individuel&password=` ,
45- ) ;
46- if ( ! res . ok ) return null ;
47- const text = await res . text ( ) ;
48- const match = text . match ( / < s e s s i o n i d = " ( .* ?) " \s * \/ > / ) ;
49- return match ? match [ 1 ] : null ;
50- } catch {
51- return null ;
52- }
38+ async function fetchSessionId ( ) : Promise < string | null > {
39+ try {
40+ const res = await fetch ( `${ ADE_BASE } /jsp/webapi?function=connect&login=Individuel&password=` ) ;
41+ if ( ! res . ok ) return null ;
42+ const text = await res . text ( ) ;
43+ const match = text . match ( / < s e s s i o n i d = " ( .* ?) " \s * \/ > / ) ;
44+ return match ? match [ 1 ] : null ;
45+ } catch {
46+ return null ;
5347 }
48+ }
5449
55- async getProjects ( sessionId : string ) : Promise < string | null > {
56- try {
57- const res = await fetch (
58- `${ ADE_BASE } /jsp/webapi?function=getProjects&sessionId=${ sessionId } &detail=2` ,
59- ) ;
60- if ( ! res . ok ) return null ;
61- const text = await res . text ( ) ;
62- const now = new Date ( ) ;
63- const startYear = now . getMonth ( ) >= 8 ? now . getFullYear ( ) : now . getFullYear ( ) - 1 ;
64- const yearStr = `${ startYear } -${ startYear + 1 } ` ;
65- const projectRegex = / < p r o j e c t \s + i d = " ( \d + ) " \s + n a m e = " ( [ ^ " ] * ) " / g;
66- const matches = text . matchAll ( projectRegex ) ;
67-
68- for ( const match of matches ) {
69- const [ _ , id , name ] = match ;
70-
71- if ( name . includes ( yearStr ) && name . toLowerCase ( ) . includes ( 'prod' ) ) {
72- return id ;
73- }
50+ async function fetchProjectId ( sessionId : string ) : Promise < string | null > {
51+ try {
52+ const res = await fetch (
53+ `${ ADE_BASE } /jsp/webapi?function=getProjects&sessionId=${ sessionId } &detail=2` ,
54+ ) ;
55+ if ( ! res . ok ) return null ;
56+ const text = await res . text ( ) ;
57+ const yearStr = getCurrentAcademicYearString ( ) ;
58+ const projectRegex = / < p r o j e c t \s + i d = " ( \d + ) " \s + n a m e = " ( [ ^ " ] * ) " / g;
59+
60+ for ( const [ _ , id , name ] of text . matchAll ( projectRegex ) ) {
61+ if ( name . includes ( yearStr ) && name . toLowerCase ( ) . includes ( 'prod' ) ) {
62+ return id ;
7463 }
75-
76- return null ;
77- } catch {
78- return null ;
7964 }
65+
66+ return null ;
67+ } catch {
68+ return null ;
8069 }
70+ }
71+
72+ async function resolveProjectId ( ) : Promise < string | null > {
73+ const sessionId = await fetchSessionId ( ) ;
74+ if ( ! sessionId ) return null ;
75+ return fetchProjectId ( sessionId ) ;
76+ }
8177
82- async getConfig ( ) : Promise < string | null > {
83- const sessionId = await this . getSessionId ( ) ;
84- if ( ! sessionId ) return null ;
85- return this . getProjects ( sessionId ) ;
78+ async function getCalendarFromCache ( ) : Promise < CalendarEvent [ ] > {
79+ try {
80+ const json = await CALENDAR_FILE . text ( ) ;
81+ return JSON . parse ( json ) as CalendarEvent [ ] ;
82+ } catch {
83+ return [ ] ;
8684 }
8785}
8886
8987export class EDT {
9088 private ADE_PROJECT : string | null = null ;
91- private ADE_DATE : string ;
92- READY = false ;
89+ private readonly ADE_DATE : string ;
90+ private readonly _ready : Promise < void > ;
9391
9492 constructor ( ) {
95- this . ADE_DATE = new EDTDate ( ) . ADE_DATE ;
96- new EDTConfig ( ) . getConfig ( ) . then ( ( projectId ) => {
97- this . ADE_PROJECT = projectId ;
98- this . READY = true ;
93+ this . ADE_DATE = getAcademicYearDateRange ( ) ;
94+ this . _ready = resolveProjectId ( ) . then ( ( id ) => {
95+ this . ADE_PROJECT = id ;
9996 } ) ;
10097 }
10198
10299 private waitUntilReady ( ) : Promise < void > {
103- if ( this . READY ) return Promise . resolve ( ) ;
104- return new Promise < void > ( ( resolve ) => {
105- const interval = setInterval ( ( ) => {
106- if ( this . READY ) {
107- clearInterval ( interval ) ;
108- resolve ( ) ;
109- }
110- } , 100 ) ;
111- setTimeout ( ( ) => {
112- clearInterval ( interval ) ;
113- resolve ( ) ;
114- } , 10000 ) ;
115- } ) ;
100+ return this . _ready ;
116101 }
117102
118103 async fetchEDT ( adeid : string ) : Promise < string | null > {
@@ -133,16 +118,16 @@ export class EDT {
133118 parseICal ( icalData : string ) : ICALEvent [ ] {
134119 try {
135120 const parsed = ICAL . parse ( icalData ) ;
136- const comp = new ICAL . Component ( parsed ) ;
137- return comp . getAllSubcomponents ( 'vevent' ) . map ( ( v ) => new ICAL . Event ( v ) ) ;
121+ const comp = new ICAL . Component ( parsed ) as ICALComponent ;
122+ return comp . getAllSubcomponents ( 'vevent' ) . map ( ( v ) => new ICAL . Event ( v as ICAL . Component ) as ICALEvent ) ;
138123 } catch {
139124 return [ ] ;
140125 }
141126 }
142127
143128 findNextEvent ( events : ICALEvent [ ] ) : NextEvent {
144129 const now = new Date ( ) ;
145- const windowStart = new Date ( now . getTime ( ) - 15 * 60 * 1000 ) ;
130+ const windowStart = new Date ( now . getTime ( ) - ONGOING_THRESHOLD_MS ) ;
146131
147132 const sorted = events
148133 . map ( ( e ) => ( {
@@ -157,6 +142,38 @@ export class EDT {
157142 return { summary : next . summary , location : next . location } ;
158143 }
159144
145+ findNextTwoCourses ( events : ICALEvent [ ] ) : WidgetClass [ ] {
146+ const now = new Date ( ) ;
147+ const windowStart = new Date ( now . getTime ( ) - 15 * 60 * 1000 ) ;
148+
149+ const pad = ( n : number ) => String ( n ) . padStart ( 2 , '0' ) ;
150+ const fmt = ( d : Date ) => `${ pad ( d . getHours ( ) ) } :${ pad ( d . getMinutes ( ) ) } ` ;
151+
152+ return events
153+ . map ( ( e ) => ( {
154+ start : e . startDate . toJSDate ( ) ,
155+ end : e . endDate . toJSDate ( ) ,
156+ title : e . summary ?? 'Cours inconnu' ,
157+ room : e . location ?? '' ,
158+ } ) )
159+ . filter ( ( e ) => e . start >= windowStart )
160+ . sort ( ( a , b ) => a . start . getTime ( ) - b . start . getTime ( ) )
161+ . slice ( 0 , 2 )
162+ . map ( ( e ) => ( {
163+ title : e . title ,
164+ room : e . room ,
165+ startTime : fmt ( e . start ) ,
166+ endTime : fmt ( e . end ) ,
167+ } ) ) ;
168+ }
169+
170+ async getNextTwoCourses ( adeid : string ) : Promise < WidgetClass [ ] > {
171+ const icalData = await this . fetchEDT ( adeid ) ;
172+ if ( ! icalData ) return [ ] ;
173+ const events = this . parseICal ( icalData ) ;
174+ return this . findNextTwoCourses ( events ) ;
175+ }
176+
160177 async getNextEvent ( adeid : string ) : Promise < NextEvent > {
161178 const icalData = await this . fetchEDT ( adeid ) ;
162179 if ( ! icalData ) {
@@ -190,13 +207,4 @@ export class EDT {
190207 }
191208}
192209
193- async function getCalendarFromCache ( ) : Promise < CalendarEvent [ ] > {
194- try {
195- const json = await CALENDAR_FILE . text ( ) ;
196- return JSON . parse ( json ) as CalendarEvent [ ] ;
197- } catch {
198- return [ ] ;
199- }
200- }
201-
202- export const edtService = new EDT ( ) ;
210+ export const edtService = new EDT ( ) ;
0 commit comments