diff options
| author | Tim Schiewe <git@tforge.de> | 2018-04-30 19:18:46 +0200 |
|---|---|---|
| committer | Tim Schiewe <git@tforge.de> | 2018-04-30 19:18:46 +0200 |
| commit | ac85779f3b67ffc2953c17d0824be62b1f0acc86 (patch) | |
| tree | 5442df8297c3620ed72af7e3ece2a77ad58cce35 | |
| parent | 93b466df50d94d4c15715f5eb4d88a0be7d61c11 (diff) | |
| download | splus-ac85779f3b67ffc2953c17d0824be62b1f0acc86.tar.gz splus-ac85779f3b67ffc2953c17d0824be62b1f0acc86.zip | |
Implement interface/config based infrastructure
Fixes #2
| -rw-r--r-- | src/ICal.ts | 30 | ||||
| -rw-r--r-- | src/config.ts | 29 | ||||
| -rw-r--r-- | src/core/IEvent.ts | 7 | ||||
| -rw-r--r-- | src/core/ILecture.ts | 13 | ||||
| -rw-r--r-- | src/core/SplusParser.ts (renamed from src/SplusParser.ts) | 13 | ||||
| -rw-r--r-- | src/index.ts | 76 | ||||
| -rw-r--r-- | src/sinks/GoogleCalendarSink.ts (renamed from src/GoogleCalendar.ts) | 86 | ||||
| -rw-r--r-- | src/sinks/ISink.ts | 7 | ||||
| -rw-r--r-- | src/sinks/IcalSink.ts | 46 | ||||
| -rw-r--r-- | src/sources/FileSource.ts | 27 | ||||
| -rw-r--r-- | src/sources/HttpSource.ts | 22 | ||||
| -rw-r--r-- | src/sources/ISource.ts | 3 |
12 files changed, 230 insertions, 129 deletions
diff --git a/src/ICal.ts b/src/ICal.ts deleted file mode 100644 index 09bc9bf..0000000 --- a/src/ICal.ts +++ /dev/null @@ -1,30 +0,0 @@ -// ref. https://www.npmjs.com/package/ical-generator -import * as ical from 'ical-generator'; -import * as crypto from 'crypto'; -import * as fs from 'fs'; - -const TARGET_FILE = 'etc/informatik1.ics'; -const DOMAIN = 'schneefux.github.io'; // TODO config management - -const sha256 = (x) => crypto.createHash('sha256').update(x, 'utf8').digest('hex'); - -const cal = ical(); - -// TODO declare an interface -export function createEvent(event) { - // TODO better duplicate detection and resolve conflicts lol - const eventId = sha256(JSON.stringify(event)).substr(0, 16); - - const icalEvent = cal.createEvent({ - uid: eventId, - start: event.start.dateTime, - end: event.end.dateTime, - timestamp: new Date(), - summary: event.summary, - description: event.description, - }); -} - -export function commit() { - fs.writeFile(TARGET_FILE, cal.toString(), (err) => {}); -} diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..47bcd02 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,29 @@ +import {ISource} from './sources/ISource'; +import {ISink} from './sinks/ISink'; +import {ILectureFilter} from './core/ILecture'; + +import {HttpSource} from './sources/HttpSource'; +import {IcalSink} from './sinks/IcalSink'; + +export interface SplusConfig { + source: ISource; + sink: ISink; + + lectureFilter?: ILectureFilter; +} + +export const config: SplusConfig = { + source: new HttpSource('http://splus.ostfalia.de/semesterplan123.php?id=1362F014835FFFD0F67159E302EC1A3C&identifier=%23SPLUS7A3292'), + sink: new IcalSink('out.ics'), + + lectureFilter: lecture => { + // Filter some lectures out + if (lecture.title === '') return false; + if (lecture.title.indexOf('Mathe-Cafe') !== -1) return false; + if (lecture.title.indexOf('Mathe-Repetitorium') !== -1) return false; + if (lecture.title.indexOf('Informatik-Lounge') !== -1) return false; + if (lecture.title.indexOf('Grundlagen des Programmierens') !== -1) return false; + if (lecture.title.indexOf('Reservierung') !== -1) return false; + return lecture.title.indexOf('Kompetenzen') === -1; + } +}; diff --git a/src/core/IEvent.ts b/src/core/IEvent.ts new file mode 100644 index 0000000..6ce817d --- /dev/null +++ b/src/core/IEvent.ts @@ -0,0 +1,7 @@ +export interface IEvent { + summary: string; + description: string; + location: string; + start: Date; + end: Date; +} diff --git a/src/core/ILecture.ts b/src/core/ILecture.ts new file mode 100644 index 0000000..39be85e --- /dev/null +++ b/src/core/ILecture.ts @@ -0,0 +1,13 @@ +export interface ILecture { + title: string; + day: number; + begin: number; + end: number; + info: string; + room: string, + lecturer: string; +} + +export interface ILectureFilter { + (lecture: ILecture): boolean; +} diff --git a/src/SplusParser.ts b/src/core/SplusParser.ts index be693fd..ae634b1 100644 --- a/src/SplusParser.ts +++ b/src/core/SplusParser.ts @@ -1,4 +1,5 @@ import {load} from 'cheerio'; +import {ILecture, ILectureFilter} from './ILecture'; interface IBlock { title: string; @@ -9,16 +10,6 @@ interface IBlock { lecturer: string } -export interface ILecture { - title: string; - day: number; - begin: number; - end: number; - info: string; - room: string, - lecturer: string; -} - export class SplusParser { private readonly startHour = 7; @@ -41,7 +32,7 @@ export class SplusParser { this.parseTable(); } - getLectures(filter?: (lecture: ILecture) => boolean): ILecture[] { + getLectures(filter?: ILectureFilter): ILecture[] { let lectures: ILecture[] = []; for (let i = 0; i < this.blocks.length; i++) { diff --git a/src/index.ts b/src/index.ts index 4791fb2..95d3797 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,41 +1,26 @@ -import {readFile} from 'fs'; -import {SplusParser} from './SplusParser'; -let createEvent = (arg) => {}; -let commit = () => {}; -if (process.argv.includes('ics')) { - createEvent = require('./ICal').createEvent; - commit = require('./ICal').commit; -} else { - createEvent = require('./GoogleCalendar').createEvent; -} +import {SplusParser} from './core/SplusParser'; +import {config} from './config'; +import {IEvent} from './core/IEvent'; -const splusUrl = 'http://splus.ostfalia.de/semesterplan123.php?id=1362F014835FFFD0F67159E302EC1A3C&identifier=%23SPLUS7A3292'; +function getMonday(date: Date) { + const day = date.getDay() || 7; + if (day !== 1) { + date.setHours(-24 * (day - 1)); + } -/* -get(splusUrl, (res) => { - let data = ''; - res.on('data', chunk => data += chunk); - res.on('end', () => TODO); -}); -*/ + return new Date(date.getFullYear(), date.getMonth(), date.getDate()); +} -const baseDate = new Date(2018, 3, 23); +const baseDate = getMonday(new Date()); -readFile('etc/sample.htm', (err, data) => { - const lectures = new SplusParser(data.toString()).getLectures(lecture => { - // Filter some lectures out - if (lecture.title === '') return false; - if (lecture.title.indexOf('Mathe-Cafe') !== -1) return false; - if (lecture.title.indexOf('Mathe-Repetitorium') !== -1) return false; - if (lecture.title.indexOf('Informatik-Lounge') !== -1) return false; - if (lecture.title.indexOf('Reservierung') !== -1) return false; - return lecture.title.indexOf('Kompetenzen') === -1; - }); +config.source.getData().then(async data => { + const lectures = new SplusParser(data.toString()).getLectures(config.lectureFilter); - lectures.forEach((lec, index) => { - let beginDate = new Date(baseDate); + for (let i = 0; i < lectures.length; i++) { + const lec = lectures[i]; + let beginDate = new Date(baseDate); beginDate.setDate(beginDate.getDate() + lec.day); beginDate.setMinutes(beginDate.getMinutes() + lec.begin * 60); @@ -43,27 +28,18 @@ readFile('etc/sample.htm', (err, data) => { endDate.setDate(endDate.getDate() + lec.day); endDate.setMinutes(endDate.getMinutes() + lec.end * 60); - console.log(lec.title, beginDate.toISOString(), endDate.toISOString()); - - createEvent({ + const event: IEvent = { summary: lec.title, description: lec.lecturer + (lec.info !== '' ? ' - ' : '') + lec.info, location: lec.room, - start: { - dateTime: beginDate.toISOString() - }, - end: { - dateTime: endDate.toISOString() - }, - reminders: { - useDefault: false - } - }); + start: beginDate, + end: endDate + }; - // TODO - if (index == lectures.length - 1) { - commit(); - } - }) -}); + await config.sink.createEvent(event); + console.log(lec.title, beginDate.toISOString(), endDate.toISOString()); + } + + await config.sink.commit(); +}); diff --git a/src/GoogleCalendar.ts b/src/sinks/GoogleCalendarSink.ts index 2376c6b..374cc48 100644 --- a/src/GoogleCalendar.ts +++ b/src/sinks/GoogleCalendarSink.ts @@ -1,14 +1,19 @@ import * as fs from 'fs'; import * as readline from 'readline'; -import {google} from "googleapis"; + +import {google} from 'googleapis'; + +import {ISink} from './ISink'; +import {IEvent} from '../core/IEvent'; + const OAuth2Client = google.auth.OAuth2; const scopes = ['https://www.googleapis.com/auth/calendar']; -const credentialsPath = 'client_secret.json'; -const tokenPath = 'credentials.json'; +const credentialsPath = 'etc/google/client_secret.json'; +const tokenPath = 'etc/google/credentials.json'; -export const clientPromise = new Promise((resolve, reject) => { +const clientPromise = new Promise((resolve, reject) => { fs.readFile(credentialsPath, (err, data) => { if (err) return reject(err); @@ -49,42 +54,47 @@ export const clientPromise = new Promise((resolve, reject) => { }); }); -export const calendarPromise = clientPromise.then(auth => google.calendar({version: 'v3', auth})); +const calendarPromise = clientPromise.then(auth => google.calendar({version: 'v3', auth})); -export async function listEvents() { - const calendar = await calendarPromise; - calendar.events.list({ - calendarId: 'primary', - timeMin: (new Date()).toISOString(), - maxResults: 10, - singleEvents: true, - orderBy: 'startTime', - }, (err, data) => { - if (err) return console.log('The API returned an error: ' + err); - const events = data.data.items; - if (events.length) { - console.log('Upcoming 10 events:'); - events.map((event, i) => { - const start = event.start.dateTime || event.start.date; - console.log(`${start} - ${event.summary}`); - }); - } else { - console.log('No upcoming events found.'); - } - }); -} -export function createEvent(event, calendarId = 'primary') { - return calendarPromise.then(calendar => { - return new Promise((resolve, reject) => { - calendar.events.insert({ - calendarId: calendarId, - resource: event, - }, function (err, event) { - if (err) return reject(err); - resolve(event); +export class GoogleCalendarSink implements ISink { + private _calendarId: string; + + constructor(calendarId = 'primary') { + this._calendarId = calendarId; + } + + createEvent(event: IEvent): Promise<void> { + const googleEvent = { + summary: event.summary, + description: event.description, + location: event.location, + start: { + dateTime: event.start.toISOString() + }, + end: { + dateTime: event.end.toISOString() + }, + reminders: { + useDefault: false + } + }; + + return calendarPromise.then(calendar => { + return new Promise<void>((resolve, reject) => { + calendar.events.insert({ + calendarId: this._calendarId, + resource: googleEvent, + }, function (err, event) { + if (err) return reject(err); + resolve(); + }); }); - }) - }); + }); + } + + commit(): Promise<void> { + return Promise.resolve(); + } } diff --git a/src/sinks/ISink.ts b/src/sinks/ISink.ts new file mode 100644 index 0000000..aaacf35 --- /dev/null +++ b/src/sinks/ISink.ts @@ -0,0 +1,7 @@ +import {IEvent} from '../core/IEvent'; + +export interface ISink { + createEvent(event: IEvent): Promise<void>; + + commit(): Promise<void> +} diff --git a/src/sinks/IcalSink.ts b/src/sinks/IcalSink.ts new file mode 100644 index 0000000..efb94ac --- /dev/null +++ b/src/sinks/IcalSink.ts @@ -0,0 +1,46 @@ +import {createHash} from 'crypto'; +import {writeFile} from 'fs'; + +import * as ical from 'ical-generator'; + +import {ISink} from './ISink'; +import {IEvent} from '../core/IEvent'; + + +const sha256 = (x) => createHash('sha256').update(x, 'utf8').digest('hex'); + + +// ref. https://www.npmjs.com/package/ical-generator +export class IcalSink implements ISink { + private _cal = ical(); + private _targetFile: string; + + constructor(targetFile: string) { + this._targetFile = targetFile; + } + + createEvent(event: IEvent): Promise<void> { + // TODO better duplicate detection and resolve conflicts lol + const eventId = sha256(JSON.stringify(event)).substr(0, 16); + + const icalEvent = this._cal.createEvent({ + uid: eventId, + start: event.start.toISOString(), + end: event.end.toISOString(), + timestamp: new Date(), + summary: event.summary, + description: event.description, + }); + + return Promise.resolve(); + } + + commit(): Promise<void> { + return new Promise<void>((resolve, reject) => { + writeFile(this._targetFile, this._cal.toString(), err => { + if (err) return reject(err); + resolve(); + }); + }); + } +} diff --git a/src/sources/FileSource.ts b/src/sources/FileSource.ts new file mode 100644 index 0000000..fd4f6f7 --- /dev/null +++ b/src/sources/FileSource.ts @@ -0,0 +1,27 @@ +import {readFile} from 'fs'; + +import {ISource} from './ISource'; + +export class FileSource implements ISource { + private _path: string; + private _data: string = null; + + constructor(path: string) { + this._path = path; + } + + getData(): Promise<string> { + if (this._data) { + return Promise.resolve(this._data); + } + + return new Promise<string>((resolve, reject) => { + readFile(this._path, (err, data) => { + if (err) return reject(err); + + this._data = data.toString(); + resolve(this._data); + }); + }); + } +} diff --git a/src/sources/HttpSource.ts b/src/sources/HttpSource.ts new file mode 100644 index 0000000..45ddd7a --- /dev/null +++ b/src/sources/HttpSource.ts @@ -0,0 +1,22 @@ +import {get} from 'http'; + +import {ISource} from './ISource'; + +export class HttpSource implements ISource { + private _uri: string; + + constructor(path: string) { + this._uri = path; + } + + getData(): Promise<string> { + return new Promise<string>((resolve, reject) => { + get(this._uri, (res) => { + let data = ''; + res.on('error', reject); + res.on('data', chunk => data += chunk); + res.on('end', () => resolve(data)); + }); + }); + } +} diff --git a/src/sources/ISource.ts b/src/sources/ISource.ts new file mode 100644 index 0000000..042aaae --- /dev/null +++ b/src/sources/ISource.ts @@ -0,0 +1,3 @@ +export interface ISource { + getData(): Promise<string>; +} |
