diff --git a/server/parser/bolle.js b/server/parser/bolle.js new file mode 100644 index 0000000..a5e32e6 --- /dev/null +++ b/server/parser/bolle.js @@ -0,0 +1,85 @@ +import axios from "axios"; +import crypto from "node:crypto"; +import { log } from "../logs.js"; + +/* +This BOLLE (https://bolle-software.de/) parser tries to download +the latest substitution plan HTML files from the "Pinnwand". + +It uses the same API that the mobile app (v0.3.6) uses. +To get your login credentials you need to log into your BOLLE account +and register a new device in the settings (/einstellungen/geraete_personal). +Click on "Manuelle Logindaten Einblenden" and use "ID" as apiUser +and "Token" as apiKey. You need to make at least one request with +this API token before closing the registration window or else the +token will be invalidated immediately. +*/ + +// Files to download from the "Pinnwand" +const filenames = ["vp_heute", "vp_morgen"]; + +export class BolleClient { + constructor(bolleInstance, apiUser, apiKey) { + this.bolleInstance = bolleInstance; + this.apiUser = apiUser; + this.apiKey = apiKey; + } + + async getFiles() { + try { + let contents = []; + for (let file of filenames) { + contents.push(await this.getFile(file)); + } + return contents; + } catch (error) { + log("Parser / Bolle", "Error getting data: " + error); + return []; + } + } + + async getFile(filename) { + // Generate the BOLLE api payload + let payload = { + api_payload: JSON.stringify({ + method: "vertretungsplan_html", + payload: { content: filename }, + }), + }; + // Generate request headers + let headers = this.buildRequestHeaders(payload.api_payload); + // Send the POST request + let response = await axios.post(this.getRequestUrl(), payload, { + headers, + }); + // The server responds with a json object + // containing the base64 encoded html data + let base64 = response.data["html_base64"]; + // Decode the base64 data using the latin1 (ISO 8859-1) character set + return Buffer.from(base64, "base64").toString("latin1"); + } + getRequestUrl() { + // The API that the bolle mobile app uses is available at /app/basic + return `https://${this.bolleInstance}/app/basic`; + } + buildRequestHeaders(payload) { + // Bolle needs the sha1 hash of the payload + // to be set as the "b-hash" header + let hash = crypto.createHash("sha1"); + hash.update(payload); + + return { + Accept: "application/json", + // Set the auth headers + "X-Auth-User": this.apiUser, + "X-Auth-Token": this.apiKey, + "App-Version": "4", + // Set the hash + "B-Hash": hash.digest("hex"), + "Content-Type": "application/json", + Connection: "Keep-Alive", + "Accept-Encoding": "gzip", + "User-Agent": "okhttp/4.9.2", + }; + } +}