From a439d29d8ff0bbee2d7c92c7594729c13c87baf8 Mon Sep 17 00:00:00 2001 From: Thunderbolt Date: Thu, 26 Apr 2018 10:09:34 +0200 Subject: Initial commit --- GoogleCalendar.ts | 90 ++++ LICENSE.md | 9 + SplusParser.ts | 160 +++++++ index.ts | 57 +++ package-lock.json | 518 ++++++++++++++++++++++ package.json | 20 + sample.htm | 1247 +++++++++++++++++++++++++++++++++++++++++++++++++++++ tsconfig.json | 11 + 8 files changed, 2112 insertions(+) create mode 100644 GoogleCalendar.ts create mode 100644 LICENSE.md create mode 100644 SplusParser.ts create mode 100644 index.ts create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 sample.htm create mode 100644 tsconfig.json diff --git a/GoogleCalendar.ts b/GoogleCalendar.ts new file mode 100644 index 0000000..2376c6b --- /dev/null +++ b/GoogleCalendar.ts @@ -0,0 +1,90 @@ +import * as fs from 'fs'; +import * as readline from 'readline'; +import {google} from "googleapis"; + +const OAuth2Client = google.auth.OAuth2; +const scopes = ['https://www.googleapis.com/auth/calendar']; + +const credentialsPath = 'client_secret.json'; +const tokenPath = 'credentials.json'; + +export const clientPromise = new Promise((resolve, reject) => { + fs.readFile(credentialsPath, (err, data) => { + if (err) return reject(err); + + const credentials = JSON.parse(data.toString()); + const {client_secret, client_id, redirect_uris} = credentials.installed; + + const client = new OAuth2Client(client_id, client_secret, redirect_uris[0]); + + fs.readFile(tokenPath, (err, data) => { + if (err) { + const authUrl = client.generateAuthUrl({ + access_type: 'offline', + scope: scopes, + }); + + console.log('Authorize this app by visiting this url:', authUrl); + + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + + rl.question('Enter the code from that page here: ', code => { + rl.close(); + client.getToken(code, (err, token) => { + if (err) return reject(err); + fs.writeFile(tokenPath, JSON.stringify(token), err => err ? console.error(err) : 0); + client.setCredentials(token); + resolve(client); + }); + }); + } + else { + client.setCredentials(JSON.parse(data.toString())); + resolve(client); + } + }); + }); +}); + +export 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); + }); + }) + }); +} diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..51aaf09 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,9 @@ +The MIT License (MIT) + +Copyright (c) 2018 Thunderbolt + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/SplusParser.ts b/SplusParser.ts new file mode 100644 index 0000000..be693fd --- /dev/null +++ b/SplusParser.ts @@ -0,0 +1,160 @@ +import {load} from 'cheerio'; + +interface IBlock { + title: string; + durationHours: number; + durationSlots: number; + info: string; + room: string; + 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; + + private readonly $: CheerioStatic; + private readonly $table: Cheerio; + private readonly $rows: Cheerio; + + private colWidths: number[] = []; + private colBlocked: number[] = []; + + private blocks: IBlock[][][] = []; + + constructor(data: string) { + this.$ = load(data); + + this.$table = this.$(this.$('table.grid-border-args')[0]); + this.$rows = this.$table.find('> tbody > tr'); + + this.parseColWidths(); + this.parseTable(); + } + + getLectures(filter?: (lecture: ILecture) => boolean): ILecture[] { + let lectures: ILecture[] = []; + + for (let i = 0; i < this.blocks.length; i++) { + const begin = this.startHour + i * 0.25; + + const slot = this.blocks[i]; + for (let j = 0; j < slot.length; j++) { + const day = slot[j]; + for (let k = 0; k < day.length; k++) { + const block = day[k]; + + const lecture = { + title: block.title, + day: j, + begin: begin, + end: begin + block.durationHours, + info: block.info, + room: block.room, + lecturer: block.lecturer + }; + + if(!filter || filter(lecture)) { + lectures.push(lecture); + } + } + } + } + + return lectures; + } + + private getDay(col: number) { + return this.colWidths.findIndex(width => (col -= width) < 0); + } + + private parseColWidths() { + const $cols = this.$(this.$rows[0]).find('td:not(:first-child)'); + + for (let i = 0; i < $cols.length; i++) { + const $col = this.$($cols[i]); + this.colWidths[i] = parseInt($col.attr('colspan')); + } + + let index = 0; + for (let i = 0; i < this.colWidths.length; i++) { + for (let j = 0; j < this.colWidths[i]; i++) { + this.colBlocked[index++] = 0; + } + } + } + + private parseTable() { + for (let i = 0; i < this.$rows.length - 1; i++) { + // i + 1 to skip the columns row + const $row = this.$(this.$rows[i + 1]); + + this.colBlocked = this.colBlocked.map(v => v > 0 ? v - 1 : 0); + + this.blocks.push(this.parseRow($row)); + } + } + + private parseRow($row: Cheerio) { + const $cols = $row.find('> td:not(:first-child)'); + const blocks: IBlock[][] = []; + + let offset = 0; + for (let i = 0; i < $cols.length; i++) { + while (this.colBlocked[i + offset] > 0) { + const day = this.getDay(i + offset); + if (blocks[day] === undefined) { + blocks[day] = []; + } + + offset++; + } + + const day = this.getDay(i + offset); + if (blocks[day] === undefined) { + blocks[day] = []; + } + + const $c = this.$($cols[i]); + if ($c.hasClass('object-cell-border')) { + const block = this.parseBlock($c); + blocks[day].push(block); + + this.colBlocked[i + offset] = block.durationSlots; + } + } + + return blocks; + } + + private parseBlock($block: Cheerio): IBlock { + const $tblTitle = $block.find('table:nth-child(1)'); + const $tblInfo = $block.find('table:nth-child(2)'); + const $tblLocation = $block.find('table:nth-child(3)'); + + const $tdRoom = $tblLocation.find('td:nth-child(1)'); + const $tdLecturer = $tblLocation.find('td:nth-child(2)'); + + // Remove the Modulhandbuch "mhb" link + $tblTitle.find('br + strong').remove(); + + const slots = parseInt($block.attr('rowspan')); + return { + title: $tblTitle.text().trim(), + durationHours: slots / 4, + durationSlots: slots, + info: $tblInfo.text().trim(), + room: $tdRoom.text().trim(), + lecturer: $tdLecturer.text().trim() + }; + } +} diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..04af68a --- /dev/null +++ b/index.ts @@ -0,0 +1,57 @@ +import {get} from 'http'; +import {readFile} from 'fs'; +import {SplusParser} from './SplusParser'; +import {createEvent, listEvents} from "./GoogleCalendar"; + +const splusUrl = 'http://splus.ostfalia.de/semesterplan123.php?id=1362F014835FFFD0F67159E302EC1A3C&identifier=%23SPLUS7A3292'; + +/* +get(splusUrl, (res) => { + let data = ''; + res.on('data', chunk => data += chunk); + res.on('end', () => TODO); +}); +*/ + +const baseDate = new Date(2018, 3, 23); + +readFile('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; + }); + + lectures.forEach(lec => { + let beginDate = new Date(baseDate); + + beginDate.setDate(beginDate.getDate() + lec.day); + beginDate.setMinutes(beginDate.getMinutes() + lec.begin * 60); + + let endDate = new Date(baseDate); + endDate.setDate(endDate.getDate() + lec.day); + endDate.setMinutes(endDate.getMinutes() + lec.end * 60); + + console.log(lec.title, beginDate.toISOString(), endDate.toISOString()); + + createEvent({ + summary: lec.title, + description: lec.lecturer + (lec.info !== '' ? ' - ' : '') + lec.info, + location: lec.room, + start: { + dateTime: beginDate.toISOString() + }, + end: { + dateTime: endDate.toISOString() + }, + reminders: { + useDefault: false + } + }); + }) +}); + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..fff7d9e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,518 @@ +{ + "name": "splus", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/cheerio": { + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.7.tgz", + "integrity": "sha512-+T9qBbqe/jXtTjzVddArZExahoPPmt8eq3O1ZuCKZXjBVxf/ciUYNXrIDZJEVgYvpELnv6VlPRCfLzufRxpAag==", + "dev": true + }, + "@types/node": { + "version": "9.6.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.6.tgz", + "integrity": "sha512-SJe0g5cZeGNDP5sD8mIX3scb+eq8LQQZ60FXiKZHipYSeEFZ5EKml+NNMiO76F74TY4PoMWlNxF/YRY40FOvZQ==" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "axios": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz", + "integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=", + "requires": { + "follow-redirects": "1.4.1", + "is-buffer": "1.1.6" + } + }, + "base64url": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-2.0.0.tgz", + "integrity": "sha1-6sFuA+oUOO/5Qj1puqNiYu0fcLs=" + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, + "chalk": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.4.0" + } + }, + "cheerio": { + "version": "1.0.0-rc.2", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.2.tgz", + "integrity": "sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs=", + "requires": { + "css-select": "1.2.0", + "dom-serializer": "0.1.0", + "entities": "1.1.1", + "htmlparser2": "3.9.2", + "lodash": "4.17.10", + "parse5": "3.0.3" + } + }, + "color-convert": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", + "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "requires": { + "boolbase": "1.0.0", + "css-what": "2.1.0", + "domutils": "1.5.1", + "nth-check": "1.0.1" + } + }, + "css-what": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz", + "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=" + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "dom-serializer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", + "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", + "requires": { + "domelementtype": "1.1.3", + "entities": "1.1.1" + }, + "dependencies": { + "domelementtype": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", + "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=" + } + } + }, + "domelementtype": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", + "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=" + }, + "domhandler": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.1.tgz", + "integrity": "sha1-iS5HAAqZvlW783dP/qBWHYh5wlk=", + "requires": { + "domelementtype": "1.3.0" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "requires": { + "dom-serializer": "0.1.0", + "domelementtype": "1.3.0" + } + }, + "ecdsa-sig-formatter": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz", + "integrity": "sha1-S8kmJ07Dtau1AW5+HWCSGsJisqE=", + "requires": { + "base64url": "2.0.0", + "safe-buffer": "5.1.1" + } + }, + "entities": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", + "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + }, + "follow-redirects": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.4.1.tgz", + "integrity": "sha512-uxYePVPogtya1ktGnAAXOacnbIuRMB4dkvqeNz2qTtTQsuzSfbDolV+wMMKxAmCx0bLgAKLbBOkjItMbbkR1vg==", + "requires": { + "debug": "3.1.0" + } + }, + "gcp-metadata": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-0.6.3.tgz", + "integrity": "sha512-MSmczZctbz91AxCvqp9GHBoZOSbJKAICV7Ow/AIWSJZRrRchUd5NL1b2P4OfP+4m490BEUPhhARfpHdqCxuCvg==", + "requires": { + "axios": "0.18.0", + "extend": "3.0.1", + "retry-axios": "0.3.2" + } + }, + "google-auth-library": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-1.4.0.tgz", + "integrity": "sha512-vWRx6pJulK7Y5V/Xyr7MPMlx2mWfmrUVbcffZ7hpq8ElFg5S8WY6PvjMovdcr6JfuAwwpAX4R0I1XOcyWuBcUw==", + "requires": { + "axios": "0.18.0", + "gcp-metadata": "0.6.3", + "gtoken": "2.3.0", + "jws": "3.1.4", + "lodash.isstring": "4.0.1", + "lru-cache": "4.1.2", + "retry-axios": "0.3.2" + } + }, + "google-p12-pem": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-1.0.2.tgz", + "integrity": "sha512-+EuKr4CLlGsnXx4XIJIVkcKYrsa2xkAmCvxRhX2HsazJzUBAJ35wARGeApHUn4nNfPD03Vl057FskNr20VaCyg==", + "requires": { + "node-forge": "0.7.5", + "pify": "3.0.0" + } + }, + "googleapis": { + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-27.0.0.tgz", + "integrity": "sha512-Cz0BRsZmewc21N50x5nAUW5cqaGhJ9ETQKZMGqGL4BxmCV7ETELazSqmNi4oCDeRwM4Iub/fIJWAWZk2i6XLCg==", + "requires": { + "google-auth-library": "1.4.0", + "pify": "3.0.0", + "qs": "6.5.1", + "string-template": "1.0.0", + "uuid": "3.2.1" + } + }, + "gtoken": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-2.3.0.tgz", + "integrity": "sha512-Jc9/8mV630cZE9FC5tIlJCZNdUjwunvlwOtCz6IDlaiB4Sz68ki29a1+q97sWTnTYroiuF9B135rod9zrQdHLw==", + "requires": { + "axios": "0.18.0", + "google-p12-pem": "1.0.2", + "jws": "3.1.4", + "mime": "2.3.1", + "pify": "3.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "htmlparser2": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", + "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=", + "requires": { + "domelementtype": "1.3.0", + "domhandler": "2.4.1", + "domutils": "1.5.1", + "entities": "1.1.1", + "inherits": "2.0.3", + "readable-stream": "2.3.6" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "jwa": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.5.tgz", + "integrity": "sha1-oFUs4CIHQs1S4VN3SjKQXDDnVuU=", + "requires": { + "base64url": "2.0.0", + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.9", + "safe-buffer": "5.1.1" + } + }, + "jws": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.4.tgz", + "integrity": "sha1-+ei5M46KhHJ31kRLFGT2GIDgUKI=", + "requires": { + "base64url": "2.0.0", + "jwa": "1.1.5", + "safe-buffer": "5.1.1" + } + }, + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "lru-cache": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.2.tgz", + "integrity": "sha512-wgeVXhrDwAWnIF/yZARsFnMBtdFXOg1b8RIrhilp+0iDYN4mdQcNZElDZ0e4B64BhaxeQ5zN7PMyvu7we1kPeQ==", + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.1.2" + } + }, + "make-error": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.4.tgz", + "integrity": "sha512-0Dab5btKVPhibSalc9QGXb559ED7G7iLjFXBaj9Wq8O3vorueR5K5jaE3hkG6ZQINyhA/JgG6Qk4qdFQjsYV6g==", + "dev": true + }, + "mime": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz", + "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==" + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node-forge": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.5.tgz", + "integrity": "sha512-MmbQJ2MTESTjt3Gi/3yG1wGpIMhUfcIypUCGtTizFR9IiccFwxSpfp0vtIZlkFclEqERemxfnSdZEMR9VqqEFQ==" + }, + "nth-check": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", + "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", + "requires": { + "boolbase": "1.0.0" + } + }, + "parse5": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", + "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", + "requires": { + "@types/node": "9.6.6" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + }, + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" + } + }, + "retry-axios": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/retry-axios/-/retry-axios-0.3.2.tgz", + "integrity": "sha512-jp4YlI0qyDFfXiXGhkCOliBN1G7fRH03Nqy8YdShzGqbY5/9S2x/IR6C88ls2DFkbWuL3ASkP7QD3pVrNpPgwQ==" + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.4.tgz", + "integrity": "sha512-PETSPG6BjY1AHs2t64vS2aqAgu6dMIMXJULWFBGbh2Gr8nVLbCFDo6i/RMMvviIQ2h1Z8+5gQhVKSn2je9nmdg==", + "dev": true, + "requires": { + "source-map": "0.6.1" + } + }, + "string-template": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string-template/-/string-template-1.0.0.tgz", + "integrity": "sha1-np8iM9wA8hhxjsN5oopWc+zKi5Y=" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "5.1.1" + } + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + }, + "ts-node": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-6.0.0.tgz", + "integrity": "sha512-+CQev+4J7BAUNUnW9piRzSfSZZWeFCjgUjMSgGs4+dJ2RZa86NVW9MOlP4e6/kEHTyOqdxHxcIMd7KgmY/ynVw==", + "dev": true, + "requires": { + "arrify": "1.0.1", + "chalk": "2.4.0", + "diff": "3.5.0", + "make-error": "1.3.4", + "minimist": "1.2.0", + "mkdirp": "0.5.1", + "source-map-support": "0.5.4", + "yn": "2.0.0" + } + }, + "typescript": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.8.3.tgz", + "integrity": "sha512-K7g15Bb6Ra4lKf7Iq2l/I5/En+hLIHmxWZGq3D4DIRNFxMNV6j2SHSvDOqs2tGd4UvD/fJvrwopzQXjLrT7Itw==", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + }, + "yn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", + "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..ba71cd4 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "splus", + "version": "1.0.0", + "description": "", + "private": true, + "main": "index.js", + "license": "UNLICENSED", + "scripts": { + "start": "ts-node index.ts" + }, + "dependencies": { + "cheerio": "^1.0.0-rc.2", + "googleapis": "^27.0.0" + }, + "devDependencies": { + "@types/cheerio": "^0.22.7", + "ts-node": "^6.0.0", + "typescript": "^2.8.3" + } +} diff --git a/sample.htm b/sample.htm new file mode 100644 index 0000000..6e4f0d9 --- /dev/null +++ b/sample.htm @@ -0,0 +1,1247 @@ + + + + Ostfalia Hochschule für angewandte Wissenschaften + + + + + + + + + + + + + + + + + +
+
+ + +
+ + +
+ + + + + + +
AuswahlKalenderwoche AnfangEndeWochentage
+
+
+
    
+
+ +
+
+
+
+
+
+ +
+
+Mo +Di +Mi +Do +Fr +Sa +So +
+
+ +
+
+
+ + +
+ +
+ + +
+ +
+
+
 drucken ... + + + +Individual_Semester + + + + + + + + + + + + + + + + + +
+ ++ + + +
Semester B.Sc. - 1. Sem.. Informatik
+
+ ++ + + +
+
+ ++ + + +
+
+ ++ + + +
Plan ist gültig von 23.04.2018 bis 29.04.2018
+
+ ++ + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MoDiMiDoFrSa
7:00       
7:15       
7:30       
7:45       
8:00       
8:15    + + + + + + +
Grundlagen des Programmierens - VL
mhb
+ + + + + +
+ + + + + + + +
Hörsaal 252Prof. Dr. J. Weimar
+ +
+ + + + + + +
Mathe-Cafe 1. Sem.
+ + + + + +
+ + + + + + + +
Hörsaal 223Dr. S. Bellmer
+ +
  
8:30     
8:45     
9:00     
9:15     
9:30  + + + + + + +
CS-Kompetenzen für die Informatik
mhb
+ + + + + +
+ + + + + + + +
Seminarraum 136Fr. J. Schwiebert
+ +
   
9:45 + + + + + + +
CS-Kompetenzen für die Informatik
mhb
+ + + + + +
+ + + + + + + +
11/5 ExProf. Dr. D. Justen
+ +
     
10:00 + + + + + + +
Mathe-Repetitorium
+ + + + + +
+ + + + + + + +
Hörsaal 223Dr. S. Bellmer
+ +
+ + + + + + +
Reservierung Prof . Riegler
+ + + + + +
+ + + + + + + +
Scale Up - Raum 201
+ +
+ + + + + + +
Diskrete Strukturen
mhb
+ + + + + +
+ + + + + + + +
Hörsaal 252Prof. Dr. P. Riegler
+ +
  
10:15  
10:30  
10:45  
11:00  
11:15  
11:30      
11:45      
12:00 + + + + + + +
CS-Kompetenzen für die Informatik
mhb
+ + + + + +
+ + + + + + + +
Seminarraum 136Fr. J. Schwiebert
+ +
+ + + + + + +
CS-Kompetenzen für die Informatik
mhb
+ + + + + +
+ + + + + + + +
11/5 ExProf. Dr. D. Justen
+ +
+ + + + + + +
Grundlagen des Programmierens - Labor
mhb
+ + + + + +
Findet ab KW11 (13.3.) statt, also nicht am 6.3.
+ + + + + + + +
P4, P3, P2, P1Prof. Dr. J. Weimar
+ +
  + + + + + + +
Informatik-Lounge 1. Sem
+ + + + + +
+ + + + + + + +
Poolraum 132Dipl.-Inf. D.Dick-Anwander
+ +
  
12:15   
12:30   
12:45   
13:00   
13:15   
13:30     
13:45     
14:00      
14:15 + + + + + + +
Technische Grundlagen der Informatik - VL
mhb
+ + + + + +
+ + + + + + + +
11/1Dipl.-Ing. K. Dammann
+ +
  + + + + + + +
Grundlagen des Programmierens - Labor
mhb
+ + + + + +
Findet ab KW11 (13.3.) statt, also nicht am 6.3.
+ + + + + + + +
P4, P3, P2, P1Prof. Dr. J. Weimar
+ +
+ + + + + + +
Technische Grundlagen der Informatik - VL
mhb
+ + + + + +
Achtung, Veranstaltung findet unregelmässig statt. Wöchentlich Stundenplan beachten!
+ + + + + + + +
11/1Dipl.-Ing. K. Dammann
+ +
+ + + + + + +
Grundlagen des Programmierens - VL
mhb
+ + + + + +
+ + + + + + + +
Hörsaal 026Prof. Dr. J. Weimar
+ +
  
14:30   
14:45   
15:00   
15:15   
15:30   
15:45       
16:00 + + + + + + +
Einführung in die Informatik
mhb
+ + + + + +
+ + + + + + + +
11/1Prof. Dr. W. Pekrun
+ +
  + + + + + + +
Diskrete Strukturen
mhb
+ + + + + +
+ + + + + + + +
Hörsaal 252Prof. Dr. P. Riegler
+ +
  + + + + + + +
+ + + + + +
+ + + + + + + +
11/5 ExProf. Dr. D. Justen
+ +
  
16:15    
16:30    
16:45    
17:00    
17:15    
17:30      
17:45 + + + + + + +
Einführung in die Informatik
mhb
+ + + + + +
+ + + + + + + +
11/1Prof. Dr. W. Pekrun
+ +
     
18:00     
18:15      
18:30      
18:45      
19:00      
19:15       
19:30       
19:45       
20:00       
+ + + + + + + +
+ +
+ + + \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a4a0da2 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "outDir": "dist/", + "moduleResolution": "node", + "target": "es2016", + "sourceMap": true + }, + "exclude": [ + "node_modules" + ] +} \ No newline at end of file -- cgit v1.3.1