summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Schiewe <git@tforge.de>2018-04-30 19:18:46 +0200
committerTim Schiewe <git@tforge.de>2018-04-30 19:18:46 +0200
commitac85779f3b67ffc2953c17d0824be62b1f0acc86 (patch)
tree5442df8297c3620ed72af7e3ece2a77ad58cce35
parent93b466df50d94d4c15715f5eb4d88a0be7d61c11 (diff)
downloadsplus-ac85779f3b67ffc2953c17d0824be62b1f0acc86.tar.gz
splus-ac85779f3b67ffc2953c17d0824be62b1f0acc86.zip
Implement interface/config based infrastructure
Fixes #2
-rw-r--r--src/ICal.ts30
-rw-r--r--src/config.ts29
-rw-r--r--src/core/IEvent.ts7
-rw-r--r--src/core/ILecture.ts13
-rw-r--r--src/core/SplusParser.ts (renamed from src/SplusParser.ts)13
-rw-r--r--src/index.ts76
-rw-r--r--src/sinks/GoogleCalendarSink.ts (renamed from src/GoogleCalendar.ts)86
-rw-r--r--src/sinks/ISink.ts7
-rw-r--r--src/sinks/IcalSink.ts46
-rw-r--r--src/sources/FileSource.ts27
-rw-r--r--src/sources/HttpSource.ts22
-rw-r--r--src/sources/ISource.ts3
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>;
+}