@udondan/dsbmobile 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Daniel Schroeder
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,251 @@
1
+ # @udondan/dsbmobile
2
+
3
+ Node.js-Paket für den Zugriff auf [DSBmobile](https://www.dsbmobile.de) – die digitale Schulkommunikationsplattform für Vertretungspläne, Neuigkeiten und Dokumente. Es kapselt die DSBmobile-API und bietet drei Schnittstellen über einen gemeinsamen Kern:
4
+
5
+ - **SDK** – importierbare `DsbmobileClient`-Klasse für Node.js-Projekte
6
+ - **CLI** – `dsbmobile`-Befehl für das Terminal
7
+ - **MCP-Server** – `dsbmobile mcp` stellt alle Funktionen als Tools für KI-Assistenten bereit
8
+
9
+ ## Inhalt
10
+
11
+ - [Installation](#installation)
12
+ - [Konfiguration](#konfiguration)
13
+ - [SDK](#sdk)
14
+ - [CLI](#cli)
15
+ - [MCP-Server](#mcp-server)
16
+ - [Verfügbare MCP-Tools](#verfügbare-mcp-tools)
17
+ - [Entwicklung](#entwicklung)
18
+ - [Lizenz](#lizenz)
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ # Globale Installation (empfohlen für CLI-Nutzung)
24
+ npm install -g @udondan/dsbmobile
25
+
26
+ # Ohne Installation direkt nutzen
27
+ DSB_USERNAME=benutzername DSB_PASSWORD=passwort npx @udondan/dsbmobile mcp
28
+ ```
29
+
30
+ ## Konfiguration
31
+
32
+ Die Zugangsdaten werden über Umgebungsvariablen übergeben:
33
+
34
+ | Variable | Pflicht | Beschreibung |
35
+ | -------------- | ------- | --------------------------------------------------------------------------------------------------- |
36
+ | `DSB_USERNAME` | ✅ | DSBmobile-Benutzername bzw. -ID |
37
+ | `DSB_PASSWORD` | ✅ | DSBmobile-Passwort |
38
+ | `DSB_CLASS` | ❌ | Standard-Klassenfilter für `get_substitutions` (z. B. `07b`). Kann pro Aufruf überschrieben werden. |
39
+
40
+ ## SDK
41
+
42
+ ```bash
43
+ npm install @udondan/dsbmobile
44
+ ```
45
+
46
+ ```ts
47
+ import { DsbmobileClient } from '@udondan/dsbmobile';
48
+
49
+ const client = new DsbmobileClient({
50
+ username: process.env.DSB_USERNAME!,
51
+ password: process.env.DSB_PASSWORD!,
52
+ });
53
+
54
+ // Vertretungsplan abrufen
55
+ const plans = await client.getSubstitutions();
56
+
57
+ // Timetable-Einträge (Plan-URLs) abrufen
58
+ const timetables = await client.getTimetables();
59
+
60
+ // Neuigkeiten abrufen
61
+ const news = await client.getNews();
62
+
63
+ // Dokumente abrufen
64
+ const documents = await client.getDocuments();
65
+ ```
66
+
67
+ ### Exportierte Typen
68
+
69
+ ```ts
70
+ import type {
71
+ DsbmobileConfig,
72
+ SubstitutionPlan,
73
+ SubstitutionEntry,
74
+ TimetableEntry,
75
+ NewsEntry,
76
+ DocumentEntry,
77
+ DsbItem,
78
+ } from '@udondan/dsbmobile';
79
+ ```
80
+
81
+ ## CLI
82
+
83
+ ```bash
84
+ # Vertretungspläne als JSON ausgeben
85
+ dsbmobile substitutions
86
+
87
+ # Nur eine bestimmte Klasse (überschreibt DSB_CLASS)
88
+ dsbmobile substitutions --class 07b
89
+
90
+ # Timetable-Einträge (Plan-URLs) als JSON ausgeben
91
+ dsbmobile timetables
92
+
93
+ # Neuigkeiten als JSON ausgeben
94
+ dsbmobile news
95
+
96
+ # Dokumente als JSON ausgeben
97
+ dsbmobile documents
98
+
99
+ # MCP-Server über stdio starten
100
+ dsbmobile mcp
101
+ ```
102
+
103
+ ## MCP-Server
104
+
105
+ ### Einrichtung in Claude Code
106
+
107
+ ```bash
108
+ claude mcp add dsbmobile -- npx @udondan/dsbmobile mcp
109
+ ```
110
+
111
+ Anschließend die Zugangsdaten in der MCP-Konfiguration hinterlegen (`~/.claude.json` oder `.claude/settings.json`):
112
+
113
+ ```json
114
+ {
115
+ "mcpServers": {
116
+ "dsbmobile": {
117
+ "command": "npx",
118
+ "args": ["@udondan/dsbmobile", "mcp"],
119
+ "env": {
120
+ "DSB_USERNAME": "benutzername",
121
+ "DSB_PASSWORD": "passwort",
122
+ "DSB_CLASS": "07b"
123
+ }
124
+ }
125
+ }
126
+ }
127
+ ```
128
+
129
+ ### Einrichtung in Claude Desktop
130
+
131
+ **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
132
+ **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
133
+
134
+ ```json
135
+ {
136
+ "mcpServers": {
137
+ "dsbmobile": {
138
+ "command": "npx",
139
+ "args": ["@udondan/dsbmobile", "mcp"],
140
+ "env": {
141
+ "DSB_USERNAME": "benutzername",
142
+ "DSB_PASSWORD": "passwort",
143
+ "DSB_CLASS": "07b"
144
+ }
145
+ }
146
+ }
147
+ }
148
+ ```
149
+
150
+ ### Einrichtung in anderen MCP-Clients
151
+
152
+ ```json
153
+ {
154
+ "command": "npx",
155
+ "args": ["@udondan/dsbmobile", "mcp"],
156
+ "env": {
157
+ "DSB_USERNAME": "benutzername",
158
+ "DSB_PASSWORD": "passwort",
159
+ "DSB_CLASS": "07b"
160
+ }
161
+ }
162
+ ```
163
+
164
+ ## Verfügbare MCP-Tools
165
+
166
+ ### `get_timetables`
167
+
168
+ Gibt alle verfügbaren Vertretungsplan-Einträge zurück.
169
+
170
+ **Rückgabe**: Liste von Plan-Einträgen, jeweils mit:
171
+
172
+ - `id`: Eindeutige ID
173
+ - `title`: Planname (z. B. „V-Homepage heute - subst_001 (Seite 1)")
174
+ - `date`: Zeitstempel der letzten Aktualisierung im Format `TT.MM.JJJJ HH:MM`
175
+ - `url`: Link zur HTML-Planseite mit der Vertretungstabelle
176
+ - `previewUrl`: Link zu einem Vorschaubild (optional)
177
+
178
+ ### `get_substitutions`
179
+
180
+ Lädt und parst alle Vertretungsplan-Seiten und gibt strukturierte Einträge zurück.
181
+
182
+ **Parameter**:
183
+
184
+ - `className` (optional): Klassenfilter, z. B. `07b` oder `Q2_Kra`. Groß-/Kleinschreibung wird ignoriert. Standardmäßig wird `DSB_CLASS` verwendet, falls gesetzt.
185
+
186
+ **Rückgabe**: Liste von Plänen, jeweils mit:
187
+
188
+ - `title`: Planname
189
+ - `planDate`: Datum laut Plan (z. B. „20.3.2026 Freitag (Seite 1 / 8)")
190
+ - `lastUpdated`: Zeitstempel der letzten Aktualisierung
191
+ - `affectedClasses`: Kommagetrennte Liste betroffener Klassen
192
+ - `entries`: Liste der Vertretungseinträge, jeweils mit:
193
+ - `className`: Klasse (z. B. `07b`, `Q2_Kra`)
194
+ - `type`: Art der Vertretung (z. B. `Vertretung`, `Statt-Vertretung`, `Entfall`)
195
+ - `period`: Stunde(n) (z. B. `3` oder `5 - 6`)
196
+ - `originalTeacher`: Kürzel der vertretenen Lehrkraft
197
+ - `substituteTeacher`: Kürzel der vertretenden Lehrkraft
198
+ - `subject`: Fachkürzel (z. B. `SPO`, `ETHI`, `E`)
199
+ - `originalRoom`: Ursprünglicher Raum
200
+ - `substituteRoom`: Ausweichraum
201
+ - `text`: Zusätzliche Hinweise
202
+
203
+ ### `get_news`
204
+
205
+ Ruft alle Neuigkeiten und Ankündigungen von DSBmobile ab.
206
+
207
+ **Rückgabe**: Liste von Nachrichten, jeweils mit `id`, `title`, `detail`, `date`, `tags`.
208
+
209
+ ### `get_documents`
210
+
211
+ Listet alle verfügbaren Dokumente und Dateien auf.
212
+
213
+ **Rückgabe**: Liste von Dokumenten, jeweils mit `id`, `title`, `url`, `date`.
214
+
215
+ ## Sicherheit
216
+
217
+ - **CLI und MCP-Server**: Zugangsdaten werden ausschließlich über Umgebungsvariablen übergeben und nie im Code hinterlegt
218
+ - **SDK**: Zugangsdaten werden explizit als `DsbmobileConfig`-Objekt im Konstruktor übergeben – nie hartcodiert oder aus Umgebungsvariablen gelesen
219
+ - Zugangsdaten erscheinen weder in Logs noch in Fehlermeldungen
220
+ - Es werden keine sensiblen Daten auf der Festplatte gespeichert
221
+ - Der Server ist schreibgeschützt – er kann keine Daten auf DSBmobile verändern
222
+
223
+ ## Entwicklung
224
+
225
+ ```bash
226
+ # Repository klonen
227
+ git clone https://github.com/udondan/dsbmobile-mcp.git
228
+ cd dsbmobile-mcp
229
+
230
+ # Abhängigkeiten installieren
231
+ bun install
232
+
233
+ # TypeScript kompilieren
234
+ bun run build
235
+
236
+ # Im Watch-Modus kompilieren
237
+ bun run dev
238
+
239
+ # MCP-Server starten
240
+ DSB_USERNAME=benutzername DSB_PASSWORD=passwort node dist/cli.js mcp
241
+
242
+ # Tests ausführen
243
+ bun run test
244
+
245
+ # Lint
246
+ bun run lint
247
+ ```
248
+
249
+ ## Lizenz
250
+
251
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * DSBmobile CLI
4
+ *
5
+ * Usage:
6
+ * dsbmobile mcp Start the MCP server (stdio)
7
+ * dsbmobile substitutions [--class name] Fetch substitution plans as JSON
8
+ * dsbmobile timetables Fetch timetable entries as JSON
9
+ * dsbmobile news Fetch news entries as JSON
10
+ * dsbmobile documents Fetch documents as JSON
11
+ *
12
+ * Environment variables:
13
+ * DSB_USERNAME DSBmobile username/ID (required)
14
+ * DSB_PASSWORD DSBmobile password (required)
15
+ * DSB_CLASS Default class filter for substitutions (optional)
16
+ */
17
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * DSBmobile CLI
4
+ *
5
+ * Usage:
6
+ * dsbmobile mcp Start the MCP server (stdio)
7
+ * dsbmobile substitutions [--class name] Fetch substitution plans as JSON
8
+ * dsbmobile timetables Fetch timetable entries as JSON
9
+ * dsbmobile news Fetch news entries as JSON
10
+ * dsbmobile documents Fetch documents as JSON
11
+ *
12
+ * Environment variables:
13
+ * DSB_USERNAME DSBmobile username/ID (required)
14
+ * DSB_PASSWORD DSBmobile password (required)
15
+ * DSB_CLASS Default class filter for substitutions (optional)
16
+ */
17
+ import { createRequire } from 'node:module';
18
+ import { Command } from 'commander';
19
+ import { DsbmobileClient } from './services/dsbmobile.js';
20
+ import { startMcpServer } from './mcp.js';
21
+ import { ENV_CLASS, ENV_PASSWORD, ENV_USERNAME } from './constants.js';
22
+ const { version } = createRequire(import.meta.url)('../package.json');
23
+ function loadClient() {
24
+ const username = process.env[ENV_USERNAME];
25
+ const password = process.env[ENV_PASSWORD];
26
+ const missing = [!username && ENV_USERNAME, !password && ENV_PASSWORD].filter((v) => typeof v === 'string');
27
+ if (missing.length > 0) {
28
+ console.error(`Error: Required environment variables not set: ${missing.join(', ')}\n` +
29
+ ` export ${ENV_USERNAME}=your_username\n` +
30
+ ` export ${ENV_PASSWORD}=your_password`);
31
+ process.exit(1);
32
+ }
33
+ return new DsbmobileClient({ username: username, password: password });
34
+ }
35
+ function print(data) {
36
+ console.log(JSON.stringify(data, undefined, 2));
37
+ }
38
+ const program = new Command();
39
+ program
40
+ .name('dsbmobile')
41
+ .description('CLI and MCP server for DSBmobile — access German school substitution plans')
42
+ .version(version);
43
+ program
44
+ .command('mcp')
45
+ .description('Start the DSBmobile MCP server over stdio')
46
+ .action(async () => {
47
+ const client = loadClient();
48
+ await startMcpServer(client);
49
+ });
50
+ program
51
+ .command('substitutions')
52
+ .description('Fetch substitution plans (Vertretungspläne) as JSON')
53
+ .option('--class <name>', `Filter by class name (e.g. 05b). Defaults to ${ENV_CLASS} env var.`)
54
+ .action(async (options) => {
55
+ const client = loadClient();
56
+ const plans = await client.getSubstitutions();
57
+ const filter = (options.class ?? process.env[ENV_CLASS])?.toLowerCase();
58
+ const result = filter
59
+ ? plans.map((plan) => ({
60
+ ...plan,
61
+ entries: plan.entries.filter((entry) => entry.className.toLowerCase().includes(filter)),
62
+ }))
63
+ : plans;
64
+ print(result);
65
+ });
66
+ program
67
+ .command('timetables')
68
+ .description('Fetch timetable entries (plan URLs) as JSON')
69
+ .action(async () => {
70
+ const client = loadClient();
71
+ print(await client.getTimetables());
72
+ });
73
+ program
74
+ .command('news')
75
+ .description('Fetch news and announcements as JSON')
76
+ .action(async () => {
77
+ const client = loadClient();
78
+ print(await client.getNews());
79
+ });
80
+ program
81
+ .command('documents')
82
+ .description('Fetch available documents as JSON')
83
+ .action(async () => {
84
+ const client = loadClient();
85
+ print(await client.getDocuments());
86
+ });
87
+ try {
88
+ await program.parseAsync(process.argv);
89
+ }
90
+ catch (error) {
91
+ const message = error instanceof Error ? error.message : String(error);
92
+ console.error(message.startsWith('Error:') ? message : `Error: ${message}`);
93
+ process.exit(1);
94
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * DSBmobile API constants
3
+ */
4
+ /** Base URL for the DSBmobile Mobile API */
5
+ export declare const DSB_API_BASE_URL = "https://mobileapi.dsbcontrol.de";
6
+ /** Bundle ID used to identify the DSBmobile app */
7
+ export declare const DSB_BUNDLE_ID = "de.heinekingmedia.dsbmobile";
8
+ /** DSBmobile app version to report */
9
+ export declare const DSB_APP_VERSION = "35";
10
+ /** OS version to report (Android API level) */
11
+ export declare const DSB_OS_VERSION = "22";
12
+ /** Request timeout in milliseconds */
13
+ export declare const REQUEST_TIMEOUT_MS = 30000;
14
+ /** Maximum response size in characters before truncation */
15
+ export declare const CHARACTER_LIMIT = 25000;
16
+ /** Environment variable name for DSBmobile username */
17
+ export declare const ENV_USERNAME = "DSB_USERNAME";
18
+ /** Environment variable name for DSBmobile password */
19
+ export declare const ENV_PASSWORD = "DSB_PASSWORD";
20
+ /** Environment variable name for the default class filter */
21
+ export declare const ENV_CLASS = "DSB_CLASS";
@@ -0,0 +1,21 @@
1
+ /**
2
+ * DSBmobile API constants
3
+ */
4
+ /** Base URL for the DSBmobile Mobile API */
5
+ export const DSB_API_BASE_URL = 'https://mobileapi.dsbcontrol.de';
6
+ /** Bundle ID used to identify the DSBmobile app */
7
+ export const DSB_BUNDLE_ID = 'de.heinekingmedia.dsbmobile';
8
+ /** DSBmobile app version to report */
9
+ export const DSB_APP_VERSION = '35';
10
+ /** OS version to report (Android API level) */
11
+ export const DSB_OS_VERSION = '22';
12
+ /** Request timeout in milliseconds */
13
+ export const REQUEST_TIMEOUT_MS = 30_000;
14
+ /** Maximum response size in characters before truncation */
15
+ export const CHARACTER_LIMIT = 25_000;
16
+ /** Environment variable name for DSBmobile username */
17
+ export const ENV_USERNAME = 'DSB_USERNAME';
18
+ /** Environment variable name for DSBmobile password */
19
+ export const ENV_PASSWORD = 'DSB_PASSWORD';
20
+ /** Environment variable name for the default class filter */
21
+ export const ENV_CLASS = 'DSB_CLASS';
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @udondan/dsbmobile — SDK entry point.
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * import { DsbmobileClient } from '@udondan/dsbmobile';
7
+ *
8
+ * const client = new DsbmobileClient({ username: 'user', password: 'pass' });
9
+ * const substitutions = await client.getSubstitutions();
10
+ * ```
11
+ */
12
+ export { DsbmobileClient, parseSubstitutionHtml } from './services/dsbmobile.js';
13
+ export type { DsbmobileConfig } from './services/dsbmobile.js';
14
+ export type { DsbItem, TimetableEntry, NewsEntry, SubstitutionEntry, SubstitutionPlan, DocumentEntry, } from './types.js';
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @udondan/dsbmobile — SDK entry point.
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * import { DsbmobileClient } from '@udondan/dsbmobile';
7
+ *
8
+ * const client = new DsbmobileClient({ username: 'user', password: 'pass' });
9
+ * const substitutions = await client.getSubstitutions();
10
+ * ```
11
+ */
12
+ export { DsbmobileClient, parseSubstitutionHtml } from './services/dsbmobile.js';
package/dist/mcp.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ import type { DsbmobileClient } from './services/dsbmobile.js';
2
+ export declare function startMcpServer(client: DsbmobileClient): Promise<void>;
package/dist/mcp.js ADDED
@@ -0,0 +1,18 @@
1
+ import { createRequire } from 'node:module';
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { registerDocumentsTool } from './tools/documents.js';
5
+ import { registerNewsTool } from './tools/news.js';
6
+ import { registerSubstitutionsTool } from './tools/substitutions.js';
7
+ import { registerTimetablesTool } from './tools/timetables.js';
8
+ const { version } = createRequire(import.meta.url)('../package.json');
9
+ export async function startMcpServer(client) {
10
+ const server = new McpServer({ name: 'dsbmobile', version });
11
+ registerTimetablesTool(server, client);
12
+ registerSubstitutionsTool(server, client);
13
+ registerNewsTool(server, client);
14
+ registerDocumentsTool(server, client);
15
+ const transport = new StdioServerTransport();
16
+ await server.connect(transport);
17
+ console.error('DSBmobile MCP server running via stdio');
18
+ }
@@ -0,0 +1,81 @@
1
+ import type { DocumentEntry, NewsEntry, SubstitutionPlan, TimetableEntry } from '../types.js';
2
+ export interface DsbmobileConfig {
3
+ username: string;
4
+ password: string;
5
+ }
6
+ /**
7
+ * Client for the DSBmobile Mobile API.
8
+ *
9
+ * Accepts explicit credentials via {@link DsbmobileConfig} and provides
10
+ * methods to fetch timetables, news, documents, and substitution plans.
11
+ *
12
+ * The authentication token is cached in memory for the session lifetime.
13
+ */
14
+ export declare class DsbmobileClient {
15
+ private readonly username;
16
+ private readonly password;
17
+ private readonly http;
18
+ private token;
19
+ constructor(config: DsbmobileConfig);
20
+ /**
21
+ * Authenticates with DSBmobile and caches the token.
22
+ * The token is stable per username, so it can be cached indefinitely.
23
+ *
24
+ * @throws Error if credentials are invalid or the request fails
25
+ */
26
+ private authenticate;
27
+ /**
28
+ * Ensures a valid token is available, authenticating if necessary.
29
+ */
30
+ private ensureAuthenticated;
31
+ /**
32
+ * Fetches all available substitution plan (Vertretungsplan) entries.
33
+ *
34
+ * @returns Array of timetable entries with URLs to HTML plan pages
35
+ * @throws Error if authentication fails or the request fails
36
+ */
37
+ getTimetables(): Promise<TimetableEntry[]>;
38
+ /**
39
+ * Fetches all news and announcements.
40
+ *
41
+ * @returns Array of news entries
42
+ * @throws Error if authentication fails or the request fails
43
+ */
44
+ getNews(): Promise<NewsEntry[]>;
45
+ /**
46
+ * Fetches all available documents.
47
+ *
48
+ * @returns Array of document entries with download URLs
49
+ * @throws Error if authentication fails or the request fails
50
+ */
51
+ getDocuments(): Promise<DocumentEntry[]>;
52
+ /**
53
+ * Parses raw DSBmobile timetable items into structured TimetableEntry objects.
54
+ * Timetables have ConType=2 with children that contain the actual plan URLs.
55
+ * The parent item holds the descriptive title; children are individual pages.
56
+ */
57
+ private parseTimetables;
58
+ /**
59
+ * Parses raw DSBmobile news items into structured NewsEntry objects.
60
+ */
61
+ private parseNews;
62
+ /**
63
+ * Fetches and parses all substitution plan pages, returning structured entries.
64
+ * This fetches the actual HTML content of each timetable page and parses the
65
+ * substitution table into structured data.
66
+ *
67
+ * @returns Array of parsed substitution plans (one per HTML page)
68
+ * @throws Error if authentication fails or the request fails
69
+ */
70
+ getSubstitutions(): Promise<SubstitutionPlan[]>;
71
+ /**
72
+ * Parses raw DSBmobile document items into structured DocumentEntry objects.
73
+ */
74
+ private parseDocuments;
75
+ }
76
+ /**
77
+ * Parses the HTML content of a DSBmobile substitution plan page.
78
+ * The HTML is generated by Untis and has a consistent structure.
79
+ * Exported for unit testing.
80
+ */
81
+ export declare function parseSubstitutionHtml(html: string, title: string, lastUpdated: string, url: string): SubstitutionPlan;