iobroker.webuntis2 0.0.1

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/build/main.js ADDED
@@ -0,0 +1,514 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (let key of __getOwnPropNames(from))
11
+ if (!__hasOwnProp.call(to, key) && key !== except)
12
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ }
14
+ return to;
15
+ };
16
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
+ // If the importer is in node compatibility mode or this is not an ESM
18
+ // file that has been converted to a CommonJS file using a Babel-
19
+ // compatible transform (i.e. "__esModule" has not been set), then set
20
+ // "default" to the CommonJS "module.exports" for node compatibility.
21
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
+ mod
23
+ ));
24
+ var utils = __toESM(require("@iobroker/adapter-core"));
25
+ var import_WebUntisService = require("./lib/webuntis/WebUntisService");
26
+ var import_LegacyNamespaceCleanup = require("./lib/LegacyNamespaceCleanup");
27
+ var import_WebUntisErrors = require("./lib/webuntis/WebUntisErrors");
28
+ var import_Timetable = require("./lib/webuntis/Timetable");
29
+ var import_CurrentNextLesson = require("./lib/webuntis/CurrentNextLesson");
30
+ const TIMETABLE_INTERVAL_MS = 5 * 60 * 1e3;
31
+ const CURRENT_NEXT_INTERVAL_MS = 60 * 1e3;
32
+ const TIMETABLE_GROUPS = ["today", "tomorrow", "week"];
33
+ const SUMMARY_STATE_DEFINITIONS = {
34
+ hasSchool: { type: "boolean", role: "indicator", name: "Has school", def: false },
35
+ lessonCount: { type: "number", role: "value", name: "Lesson count", def: 0 },
36
+ lessonDurationMinutes: { type: "number", role: "value", name: "Lesson duration in minutes", def: 0 },
37
+ schoolStart: { type: "string", role: "value.time", name: "School start", def: "" },
38
+ schoolEnd: { type: "string", role: "value.time", name: "School end", def: "" },
39
+ subjects: { type: "string", role: "text", name: "Subjects", def: "" },
40
+ subjectCount: { type: "number", role: "value", name: "Subject count", def: 0 },
41
+ firstSubject: { type: "string", role: "text", name: "First subject", def: "" },
42
+ lastSubject: { type: "string", role: "text", name: "Last subject", def: "" },
43
+ hasChanges: { type: "boolean", role: "indicator", name: "Has changes", def: false },
44
+ changeCount: { type: "number", role: "value", name: "Change count", def: 0 },
45
+ cancellationCount: { type: "number", role: "value", name: "Cancellation count", def: 0 }
46
+ };
47
+ const LESSON_STATE_NAMES = {
48
+ date: "Date",
49
+ startTime: "Lesson start",
50
+ endTime: "Lesson end",
51
+ subject: "Subject",
52
+ subjectLong: "Subject name",
53
+ teacher: "Teacher",
54
+ room: "Room",
55
+ class: "Class",
56
+ lessonId: "WebUntis lesson ID",
57
+ periodId: "WebUntis period ID",
58
+ status: "Lesson status",
59
+ changed: "Lesson changed",
60
+ cancelled: "Lesson cancelled",
61
+ substitution: "Substitution",
62
+ originalTeacher: "Original teacher",
63
+ originalRoom: "Original room",
64
+ originalSubject: "Original subject"
65
+ };
66
+ const CURRENT_NEXT_STATE_DEFINITIONS = {
67
+ currentLesson: { type: "string", role: "text", name: "Current lesson", def: "" },
68
+ currentSubject: { type: "string", role: "text", name: "Current subject", def: "" },
69
+ currentRoom: { type: "string", role: "text", name: "Current room", def: "" },
70
+ currentTeacher: { type: "string", role: "text", name: "Current teacher", def: "" },
71
+ nextLesson: { type: "string", role: "text", name: "Next lesson", def: "" },
72
+ nextSubject: { type: "string", role: "text", name: "Next subject", def: "" },
73
+ nextRoom: { type: "string", role: "text", name: "Next room", def: "" },
74
+ nextTeacher: { type: "string", role: "text", name: "Next teacher", def: "" },
75
+ nextLessonStart: { type: "string", role: "value.time", name: "Next lesson start", def: "" },
76
+ minutesUntilNextLesson: {
77
+ type: "number",
78
+ role: "value.interval",
79
+ name: "Minutes until next lesson",
80
+ def: 0,
81
+ unit: "min"
82
+ },
83
+ schoolRunning: { type: "boolean", role: "indicator", name: "School running", def: false },
84
+ minutesUntilSchoolEnd: {
85
+ type: "number",
86
+ role: "value.interval",
87
+ name: "Minutes until school end",
88
+ def: 0,
89
+ unit: "min"
90
+ }
91
+ };
92
+ class WebuntisNext extends utils.Adapter {
93
+ webUntis;
94
+ timetableTimer;
95
+ currentNextTimer;
96
+ todayLessons = [];
97
+ constructor(options = {}) {
98
+ super({ ...options, name: "webuntis2" });
99
+ this.webUntis = new import_WebUntisService.WebUntisService(
100
+ this.logHttpDiagnostic.bind(this),
101
+ (message) => this.log.info(message),
102
+ (message) => this.log.warn(message),
103
+ fetch,
104
+ (message) => this.log.debug(message)
105
+ );
106
+ this.on("ready", this.onReady.bind(this));
107
+ this.on("message", this.onMessage.bind(this));
108
+ this.on("unload", this.onUnload.bind(this));
109
+ }
110
+ logHttpDiagnostic(diagnostic) {
111
+ this.log.debug(`WebUntis ${diagnostic.method || "request"} HTTP ${JSON.stringify(diagnostic)}`);
112
+ }
113
+ async onReady() {
114
+ await this.setState("info.connection", false, true);
115
+ if (!this.hasConnectionConfig()) {
116
+ return;
117
+ }
118
+ await this.updateTimetable();
119
+ this.timetableTimer = setInterval(() => void this.updateTimetable(), TIMETABLE_INTERVAL_MS);
120
+ this.currentNextTimer = setInterval(() => void this.updateCurrentNextStates(), CURRENT_NEXT_INTERVAL_MS);
121
+ }
122
+ hasConnectionConfig() {
123
+ return Boolean(
124
+ this.config.server && this.config.schoolName && typeof this.config.schoolId === "number" && this.config.username && this.config.password
125
+ );
126
+ }
127
+ async updateTimetable() {
128
+ await this.setState("info.lastUpdate", (/* @__PURE__ */ new Date()).toISOString(), true);
129
+ try {
130
+ await (0, import_LegacyNamespaceCleanup.cleanupLegacyNamespace)(this, "messages");
131
+ await this.ensureTimetableRoot();
132
+ await this.ensureHolidaysRoot();
133
+ await this.cleanupObsoleteStructure();
134
+ const now = /* @__PURE__ */ new Date();
135
+ const data = await this.webUntis.loadTimetable(this.config, now);
136
+ this.todayLessons = data.today;
137
+ let updated = 0;
138
+ let removed = 0;
139
+ for (const group of TIMETABLE_GROUPS) {
140
+ const result = await this.writeTimetableGroup(group, data[group], now);
141
+ updated += result.updated;
142
+ removed += result.removed;
143
+ }
144
+ if (data.holidays) {
145
+ await this.writeHolidaySummary(data.holidays);
146
+ } else {
147
+ this.log.warn("WebUntis holidays are not available; previous holiday values were preserved");
148
+ }
149
+ await this.setState("info.lastSuccessfulUpdate", now.toISOString(), true);
150
+ await this.setState("info.nextUpdate", new Date(now.getTime() + TIMETABLE_INTERVAL_MS).toISOString(), true);
151
+ await this.setState("info.connection", true, true);
152
+ this.log.warn(`Timetable updated: ${updated} lessons updated, ${removed} obsolete lessons removed`);
153
+ } catch (error) {
154
+ await this.setState("info.connection", false, true);
155
+ await this.setState("info.nextUpdate", new Date(Date.now() + TIMETABLE_INTERVAL_MS).toISOString(), true);
156
+ const code = error instanceof import_WebUntisErrors.WebUntisError ? error.code : "UNEXPECTED_RESPONSE";
157
+ const detail = error instanceof Error ? ` (${error.message})` : "";
158
+ this.log.warn(`Timetable update failed: ${code}${detail}; existing timetable data was kept`);
159
+ }
160
+ }
161
+ async ensureTimetableRoot() {
162
+ await this.setObjectNotExistsAsync("timetable", {
163
+ type: "channel",
164
+ common: { name: "Timetable" },
165
+ native: {}
166
+ });
167
+ }
168
+ async ensureHolidaysRoot() {
169
+ await this.setObjectNotExistsAsync("holidays", {
170
+ type: "channel",
171
+ common: { name: "Holidays" },
172
+ native: {}
173
+ });
174
+ await this.setObjectNotExistsAsync("holidays.current", {
175
+ type: "channel",
176
+ common: { name: "Current holiday" },
177
+ native: {}
178
+ });
179
+ await this.setObjectNotExistsAsync("holidays.next", {
180
+ type: "channel",
181
+ common: { name: "Next holiday" },
182
+ native: {}
183
+ });
184
+ }
185
+ async writeHolidaySummary(summary) {
186
+ await this.writeSimpleState(
187
+ "holidays.current.active",
188
+ summary.current.active,
189
+ "Active",
190
+ "boolean",
191
+ "indicator",
192
+ false
193
+ );
194
+ await this.writeSimpleState("holidays.current.name", summary.current.name, "Name", "string", "text", "");
195
+ await this.writeSimpleState(
196
+ "holidays.current.longName",
197
+ summary.current.longName,
198
+ "Long name",
199
+ "string",
200
+ "text",
201
+ ""
202
+ );
203
+ await this.writeSimpleState(
204
+ "holidays.current.startDate",
205
+ summary.current.startDate,
206
+ "Start date",
207
+ "string",
208
+ "date",
209
+ ""
210
+ );
211
+ await this.writeSimpleState(
212
+ "holidays.current.endDate",
213
+ summary.current.endDate,
214
+ "End date",
215
+ "string",
216
+ "date",
217
+ ""
218
+ );
219
+ await this.writeSimpleState("holidays.next.name", summary.next.name, "Name", "string", "text", "");
220
+ await this.writeSimpleState("holidays.next.longName", summary.next.longName, "Long name", "string", "text", "");
221
+ await this.writeSimpleState(
222
+ "holidays.next.startDate",
223
+ summary.next.startDate,
224
+ "Start date",
225
+ "string",
226
+ "date",
227
+ ""
228
+ );
229
+ await this.writeSimpleState("holidays.next.endDate", summary.next.endDate, "End date", "string", "date", "");
230
+ await this.writeSimpleState(
231
+ "holidays.next.daysUntil",
232
+ summary.next.daysUntil,
233
+ "Days until",
234
+ "number",
235
+ "value",
236
+ 0
237
+ );
238
+ }
239
+ async writeDaySummary(path, summary) {
240
+ for (const [name, definition] of Object.entries(SUMMARY_STATE_DEFINITIONS)) {
241
+ if (name === "lessonCount") {
242
+ continue;
243
+ }
244
+ await this.setObjectNotExistsAsync(`timetable.${path}.${name}`, {
245
+ type: "state",
246
+ common: {
247
+ name: definition.name,
248
+ type: definition.type,
249
+ role: definition.role,
250
+ read: true,
251
+ write: false,
252
+ def: definition.def
253
+ },
254
+ native: {}
255
+ });
256
+ await this.setStateAsync(`timetable.${path}.${name}`, summary[name], true);
257
+ }
258
+ }
259
+ async writeCurrentNextSummary(lessons, now) {
260
+ const summary = (0, import_CurrentNextLesson.deriveCurrentNextLesson)(lessons, now);
261
+ for (const [name, definition] of Object.entries(CURRENT_NEXT_STATE_DEFINITIONS)) {
262
+ await this.setObjectNotExistsAsync(`timetable.today.${name}`, {
263
+ type: "state",
264
+ common: {
265
+ name: definition.name,
266
+ type: definition.type,
267
+ role: definition.role,
268
+ read: true,
269
+ write: false,
270
+ def: definition.def,
271
+ ...definition.unit ? { unit: definition.unit } : {}
272
+ },
273
+ native: {}
274
+ });
275
+ await this.setStateAsync(`timetable.today.${name}`, summary[name], true);
276
+ }
277
+ }
278
+ async updateCurrentNextStates() {
279
+ await this.writeCurrentNextSummary(this.todayLessons, /* @__PURE__ */ new Date());
280
+ }
281
+ async cleanupObsoleteStructure() {
282
+ for (const id of [
283
+ "info.timetable",
284
+ "timetable.timetable",
285
+ "timetable.today.count",
286
+ "timetable.tomorrow.count",
287
+ "timetable.today.subjectsJson",
288
+ "timetable.tomorrow.subjectsJson"
289
+ ]) {
290
+ if (await this.getObjectAsync(id)) {
291
+ await this.delObjectAsync(id, { recursive: true });
292
+ }
293
+ }
294
+ }
295
+ async writeTimetableGroup(group, lessons, now) {
296
+ await this.setObjectNotExistsAsync(`timetable.${group}`, {
297
+ type: "channel",
298
+ common: { name: `Timetable ${group}` },
299
+ native: {}
300
+ });
301
+ let removed = await this.removeLegacyLessons(group);
302
+ if (group === "week") {
303
+ const weekdayNames = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"];
304
+ const byDay = weekdayNames.map(
305
+ (_, index) => lessons.filter((lesson) => {
306
+ const date = /* @__PURE__ */ new Date(`${lesson.date}T12:00:00`);
307
+ return (date.getDay() + 6) % 7 === index;
308
+ })
309
+ );
310
+ for (let index = 0; index < weekdayNames.length; index++) {
311
+ const result = await this.writeTimetableDay(
312
+ `timetable.week.${weekdayNames[index]}`,
313
+ byDay[index],
314
+ this.timetableDate(this.getMonday(now, index))
315
+ );
316
+ removed += result.removed;
317
+ }
318
+ } else {
319
+ const day = new Date(now);
320
+ if (group === "tomorrow") {
321
+ day.setDate(day.getDate() + 1);
322
+ }
323
+ const result = await this.writeTimetableDay(`timetable.${group}`, lessons, this.timetableDate(day));
324
+ await this.writeDaySummary(group, (0, import_Timetable.summarizeTimetable)(lessons));
325
+ if (group === "today") {
326
+ await this.writeCurrentNextSummary(lessons, now);
327
+ }
328
+ removed += result.removed;
329
+ }
330
+ return { updated: lessons.length, removed };
331
+ }
332
+ async removeLegacyLessons(group) {
333
+ const existing = await this.getObjectListAsync({
334
+ startkey: `${this.namespace}.timetable.${group}.`,
335
+ endkey: `${this.namespace}.timetable.${group}.\uFFFF`
336
+ });
337
+ const legacy = /* @__PURE__ */ new Set();
338
+ for (const object of existing.rows || []) {
339
+ const first = object.id.replace(`${this.namespace}.timetable.${group}.`, "").split(".")[0];
340
+ if (first.startsWith("period-")) {
341
+ legacy.add(first);
342
+ }
343
+ }
344
+ for (const id of legacy) {
345
+ await this.delObjectAsync(`timetable.${group}.${id}`, { recursive: true });
346
+ }
347
+ return legacy.size;
348
+ }
349
+ async writeTimetableDay(path, lessons, date) {
350
+ await this.setObjectNotExistsAsync(path, {
351
+ type: "channel",
352
+ common: { name: path.split(".").at(-1) || "Day" },
353
+ native: {}
354
+ });
355
+ await this.writeSimpleState(`${path}.date`, date, "Date", "string", "date", "");
356
+ await this.writeSimpleState(`${path}.lessonCount`, lessons.length, "Lesson count", "number", "value", 0);
357
+ await this.setObjectNotExistsAsync(`${path}.lessons`, {
358
+ type: "channel",
359
+ common: { name: "Lessons" },
360
+ native: {}
361
+ });
362
+ const expected = new Set(lessons.map((_lesson, index) => String(index + 1).padStart(2, "0")));
363
+ const existing = await this.getObjectListAsync({
364
+ startkey: `${this.namespace}.${path}.lessons.`,
365
+ endkey: `${this.namespace}.${path}.lessons.\uFFFF`
366
+ });
367
+ const stale = /* @__PURE__ */ new Set();
368
+ for (const object of existing.rows || []) {
369
+ const slot = object.id.replace(`${this.namespace}.${path}.lessons.`, "").split(".")[0];
370
+ if (slot && !expected.has(slot)) {
371
+ stale.add(slot);
372
+ }
373
+ }
374
+ for (const slot of stale) {
375
+ await this.delObjectAsync(`${path}.lessons.${slot}`, { recursive: true });
376
+ }
377
+ for (const [index, lesson] of lessons.entries()) {
378
+ await this.writeLesson(path, String(index + 1).padStart(2, "0"), lesson);
379
+ }
380
+ return { removed: stale.size };
381
+ }
382
+ getMonday(date, dayOffset) {
383
+ const monday = new Date(date);
384
+ monday.setHours(12, 0, 0, 0);
385
+ monday.setDate(monday.getDate() - (monday.getDay() + 6) % 7 + dayOffset);
386
+ return monday;
387
+ }
388
+ timetableDate(date) {
389
+ return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
390
+ }
391
+ async writeSimpleState(id, value, name, type, role, def) {
392
+ await this.setObjectNotExistsAsync(id, {
393
+ type: "state",
394
+ common: { name, type, role, read: true, write: false, def },
395
+ native: {}
396
+ });
397
+ await this.setStateAsync(id, value, true);
398
+ }
399
+ async writeLesson(path, slot, lesson) {
400
+ const base = `${path}.lessons.${slot}`;
401
+ const label = (0, import_Timetable.lessonChannelName)(lesson);
402
+ await this.setObjectNotExistsAsync(base, {
403
+ type: "channel",
404
+ common: { name: label || `Lesson ${slot}` },
405
+ native: {}
406
+ });
407
+ await this.extendObjectAsync(base, { common: { name: label || `Lesson ${slot}` } });
408
+ const values = {
409
+ date: { value: lesson.date, type: "string", role: "date" },
410
+ startTime: { value: lesson.startTime, type: "string", role: "value.time" },
411
+ endTime: { value: lesson.endTime, type: "string", role: "value.time" },
412
+ subject: { value: lesson.subject, type: "string", role: "text" },
413
+ subjectLong: { value: lesson.subjectLong, type: "string", role: "text" },
414
+ teacher: { value: lesson.teacher, type: "string", role: "text" },
415
+ room: { value: lesson.room, type: "string", role: "text" },
416
+ class: { value: lesson.class, type: "string", role: "text" },
417
+ ...lesson.lessonId === null ? {} : { lessonId: { value: String(lesson.lessonId), type: "string", role: "value" } },
418
+ ...lesson.periodId === null ? {} : { periodId: { value: String(lesson.periodId), type: "string", role: "value" } },
419
+ status: { value: lesson.status, type: "string", role: "text" },
420
+ changed: { value: lesson.changed, type: "boolean", role: "indicator" },
421
+ cancelled: { value: lesson.cancelled, type: "boolean", role: "indicator" },
422
+ ...lesson.substitution ? { substitution: { value: lesson.substitution, type: "string", role: "text" } } : {},
423
+ ...lesson.originalTeacher && lesson.originalTeacher !== lesson.teacher ? { originalTeacher: { value: lesson.originalTeacher, type: "string", role: "text" } } : {},
424
+ ...lesson.originalRoom && lesson.originalRoom !== lesson.room ? { originalRoom: { value: lesson.originalRoom, type: "string", role: "text" } } : {},
425
+ ...lesson.originalSubject && lesson.originalSubject !== lesson.subject ? { originalSubject: { value: lesson.originalSubject, type: "string", role: "text" } } : {}
426
+ };
427
+ const optional = ["substitution", "originalTeacher", "originalRoom", "originalSubject"];
428
+ for (const name of optional) {
429
+ if (!(name in values) && await this.getObjectAsync(`${base}.${name}`)) {
430
+ await this.delObjectAsync(`${base}.${name}`);
431
+ }
432
+ }
433
+ for (const [name, item] of Object.entries(values)) {
434
+ await this.setObjectNotExistsAsync(`${base}.${name}`, {
435
+ type: "state",
436
+ common: {
437
+ name: LESSON_STATE_NAMES[name] || name,
438
+ type: item.type,
439
+ role: item.role,
440
+ read: true,
441
+ write: false,
442
+ def: item.value
443
+ },
444
+ native: {}
445
+ });
446
+ await this.extendObjectAsync(`${base}.${name}`, { common: { name: LESSON_STATE_NAMES[name] || name } });
447
+ await this.setStateAsync(`${base}.${name}`, item.value, true);
448
+ }
449
+ }
450
+ async onMessage(obj) {
451
+ if (!obj || !obj.command || !obj.callback) {
452
+ return;
453
+ }
454
+ try {
455
+ if (obj.command === "searchSchools") {
456
+ const results = await this.webUntis.searchSchools(this.getStringPayload(obj.message, "query"));
457
+ this.log.info(`School search returned ${results.length} results`);
458
+ this.sendTo(obj.from, obj.command, { ok: true, results }, obj.callback);
459
+ return;
460
+ }
461
+ if (obj.command === "testConnection") {
462
+ const result = await this.webUntis.testConnection(this.getConnectionPayload(obj.message));
463
+ await this.setState("info.connection", result.ok, true);
464
+ if (result.ok) {
465
+ this.log.info("WebUntis authentication successful");
466
+ } else {
467
+ this.log.warn(`WebUntis authentication failed: ${result.code}`);
468
+ }
469
+ this.sendTo(obj.from, obj.command, result, obj.callback);
470
+ }
471
+ } catch (error) {
472
+ const code = error instanceof import_WebUntisErrors.WebUntisError ? error.code : "UNEXPECTED_RESPONSE";
473
+ this.log.warn(`WebUntis request failed: ${code}`);
474
+ this.sendTo(obj.from, obj.command, { ok: false, code }, obj.callback);
475
+ }
476
+ }
477
+ getStringPayload(message, key) {
478
+ if (typeof message !== "object" || message === null || typeof message[key] !== "string") {
479
+ throw new import_WebUntisErrors.WebUntisError("INVALID_CONFIG", "Invalid message payload");
480
+ }
481
+ return message[key];
482
+ }
483
+ getConnectionPayload(message) {
484
+ if (typeof message !== "object" || message === null) {
485
+ throw new import_WebUntisErrors.WebUntisError("INVALID_CONFIG", "Invalid connection payload");
486
+ }
487
+ const value = message;
488
+ const stringValue = (key) => typeof value[key] === "string" ? value[key] : "";
489
+ return {
490
+ server: stringValue("server"),
491
+ schoolName: stringValue("schoolName"),
492
+ schoolDisplayName: stringValue("schoolDisplayName"),
493
+ schoolAddress: stringValue("schoolAddress"),
494
+ schoolId: typeof value.schoolId === "number" ? value.schoolId : null,
495
+ username: stringValue("username"),
496
+ password: stringValue("password")
497
+ };
498
+ }
499
+ onUnload(callback) {
500
+ if (this.timetableTimer) {
501
+ clearInterval(this.timetableTimer);
502
+ }
503
+ if (this.currentNextTimer) {
504
+ clearInterval(this.currentNextTimer);
505
+ }
506
+ callback();
507
+ }
508
+ }
509
+ if (require.main !== module) {
510
+ module.exports = (options) => new WebuntisNext(options);
511
+ } else {
512
+ (() => new WebuntisNext())();
513
+ }
514
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/main.ts"],
4
+ "sourcesContent": ["import * as utils from \"@iobroker/adapter-core\";\nimport { WebUntisService, type WebUntisConnectionConfig } from \"./lib/webuntis/WebUntisService\";\nimport { cleanupLegacyNamespace } from \"./lib/LegacyNamespaceCleanup\";\nimport type { HolidaySummary } from \"./lib/webuntis/Holidays\";\nimport { WebUntisError } from \"./lib/webuntis/WebUntisErrors\";\nimport {\n\tlessonChannelName,\n\tsummarizeTimetable,\n\ttype TimetableLesson,\n\ttype TimetableSummary,\n} from \"./lib/webuntis/Timetable\";\nimport type { WebUntisHttpDiagnostic } from \"./lib/webuntis/WebUntisTypes\";\nimport { deriveCurrentNextLesson, type CurrentNextLessonSummary } from \"./lib/webuntis/CurrentNextLesson\";\n\nconst TIMETABLE_INTERVAL_MS = 5 * 60 * 1000;\nconst CURRENT_NEXT_INTERVAL_MS = 60 * 1000;\nconst TIMETABLE_GROUPS = [\"today\", \"tomorrow\", \"week\"] as const;\ntype TimetableGroup = (typeof TIMETABLE_GROUPS)[number];\n\nconst SUMMARY_STATE_DEFINITIONS: Record<\n\tkeyof TimetableSummary,\n\t{ type: \"string\" | \"number\" | \"boolean\"; role: string; name: string; def: string | number | boolean }\n> = {\n\thasSchool: { type: \"boolean\", role: \"indicator\", name: \"Has school\", def: false },\n\tlessonCount: { type: \"number\", role: \"value\", name: \"Lesson count\", def: 0 },\n\tlessonDurationMinutes: { type: \"number\", role: \"value\", name: \"Lesson duration in minutes\", def: 0 },\n\tschoolStart: { type: \"string\", role: \"value.time\", name: \"School start\", def: \"\" },\n\tschoolEnd: { type: \"string\", role: \"value.time\", name: \"School end\", def: \"\" },\n\tsubjects: { type: \"string\", role: \"text\", name: \"Subjects\", def: \"\" },\n\tsubjectCount: { type: \"number\", role: \"value\", name: \"Subject count\", def: 0 },\n\tfirstSubject: { type: \"string\", role: \"text\", name: \"First subject\", def: \"\" },\n\tlastSubject: { type: \"string\", role: \"text\", name: \"Last subject\", def: \"\" },\n\thasChanges: { type: \"boolean\", role: \"indicator\", name: \"Has changes\", def: false },\n\tchangeCount: { type: \"number\", role: \"value\", name: \"Change count\", def: 0 },\n\tcancellationCount: { type: \"number\", role: \"value\", name: \"Cancellation count\", def: 0 },\n};\n\nconst LESSON_STATE_NAMES: Record<string, string> = {\n\tdate: \"Date\",\n\tstartTime: \"Lesson start\",\n\tendTime: \"Lesson end\",\n\tsubject: \"Subject\",\n\tsubjectLong: \"Subject name\",\n\tteacher: \"Teacher\",\n\troom: \"Room\",\n\tclass: \"Class\",\n\tlessonId: \"WebUntis lesson ID\",\n\tperiodId: \"WebUntis period ID\",\n\tstatus: \"Lesson status\",\n\tchanged: \"Lesson changed\",\n\tcancelled: \"Lesson cancelled\",\n\tsubstitution: \"Substitution\",\n\toriginalTeacher: \"Original teacher\",\n\toriginalRoom: \"Original room\",\n\toriginalSubject: \"Original subject\",\n};\n\nconst CURRENT_NEXT_STATE_DEFINITIONS: Record<\n\tkeyof CurrentNextLessonSummary,\n\t{ type: \"string\" | \"number\" | \"boolean\"; role: string; name: string; def: string | number | boolean; unit?: string }\n> = {\n\tcurrentLesson: { type: \"string\", role: \"text\", name: \"Current lesson\", def: \"\" },\n\tcurrentSubject: { type: \"string\", role: \"text\", name: \"Current subject\", def: \"\" },\n\tcurrentRoom: { type: \"string\", role: \"text\", name: \"Current room\", def: \"\" },\n\tcurrentTeacher: { type: \"string\", role: \"text\", name: \"Current teacher\", def: \"\" },\n\tnextLesson: { type: \"string\", role: \"text\", name: \"Next lesson\", def: \"\" },\n\tnextSubject: { type: \"string\", role: \"text\", name: \"Next subject\", def: \"\" },\n\tnextRoom: { type: \"string\", role: \"text\", name: \"Next room\", def: \"\" },\n\tnextTeacher: { type: \"string\", role: \"text\", name: \"Next teacher\", def: \"\" },\n\tnextLessonStart: { type: \"string\", role: \"value.time\", name: \"Next lesson start\", def: \"\" },\n\tminutesUntilNextLesson: {\n\t\ttype: \"number\",\n\t\trole: \"value.interval\",\n\t\tname: \"Minutes until next lesson\",\n\t\tdef: 0,\n\t\tunit: \"min\",\n\t},\n\tschoolRunning: { type: \"boolean\", role: \"indicator\", name: \"School running\", def: false },\n\tminutesUntilSchoolEnd: {\n\t\ttype: \"number\",\n\t\trole: \"value.interval\",\n\t\tname: \"Minutes until school end\",\n\t\tdef: 0,\n\t\tunit: \"min\",\n\t},\n};\n\nclass WebuntisNext extends utils.Adapter {\n\tprivate readonly webUntis: WebUntisService;\n\tprivate timetableTimer?: NodeJS.Timeout;\n\tprivate currentNextTimer?: NodeJS.Timeout;\n\tprivate todayLessons: TimetableLesson[] = [];\n\n\tpublic constructor(options: Partial<utils.AdapterOptions> = {}) {\n\t\tsuper({ ...options, name: \"webuntis2\" });\n\t\tthis.webUntis = new WebUntisService(\n\t\t\tthis.logHttpDiagnostic.bind(this),\n\t\t\tmessage => this.log.info(message),\n\t\t\tmessage => this.log.warn(message),\n\t\t\tfetch,\n\t\t\tmessage => this.log.debug(message),\n\t\t);\n\t\tthis.on(\"ready\", this.onReady.bind(this));\n\t\tthis.on(\"message\", this.onMessage.bind(this));\n\t\tthis.on(\"unload\", this.onUnload.bind(this));\n\t}\n\n\tprivate logHttpDiagnostic(diagnostic: WebUntisHttpDiagnostic): void {\n\t\tthis.log.debug(`WebUntis ${diagnostic.method || \"request\"} HTTP ${JSON.stringify(diagnostic)}`);\n\t}\n\n\tprivate async onReady(): Promise<void> {\n\t\tawait this.setState(\"info.connection\", false, true);\n\t\tif (!this.hasConnectionConfig()) {\n\t\t\treturn;\n\t\t}\n\t\tawait this.updateTimetable();\n\t\tthis.timetableTimer = setInterval(() => void this.updateTimetable(), TIMETABLE_INTERVAL_MS);\n\t\tthis.currentNextTimer = setInterval(() => void this.updateCurrentNextStates(), CURRENT_NEXT_INTERVAL_MS);\n\t}\n\n\tprivate hasConnectionConfig(): this is { config: WebUntisConnectionConfig } {\n\t\treturn Boolean(\n\t\t\tthis.config.server &&\n\t\t\tthis.config.schoolName &&\n\t\t\ttypeof this.config.schoolId === \"number\" &&\n\t\t\tthis.config.username &&\n\t\t\tthis.config.password,\n\t\t);\n\t}\n\n\tprivate async updateTimetable(): Promise<void> {\n\t\tawait this.setState(\"info.lastUpdate\", new Date().toISOString(), true);\n\t\ttry {\n\t\t\tawait cleanupLegacyNamespace(this, \"messages\");\n\t\t\tawait this.ensureTimetableRoot();\n\t\t\tawait this.ensureHolidaysRoot();\n\t\t\tawait this.cleanupObsoleteStructure();\n\t\t\tconst now = new Date();\n\t\t\tconst data = await this.webUntis.loadTimetable(this.config, now);\n\t\t\tthis.todayLessons = data.today;\n\t\t\tlet updated = 0;\n\t\t\tlet removed = 0;\n\t\t\tfor (const group of TIMETABLE_GROUPS) {\n\t\t\t\tconst result = await this.writeTimetableGroup(group, data[group], now);\n\t\t\t\tupdated += result.updated;\n\t\t\t\tremoved += result.removed;\n\t\t\t}\n\t\t\tif (data.holidays) {\n\t\t\t\tawait this.writeHolidaySummary(data.holidays);\n\t\t\t} else {\n\t\t\t\tthis.log.warn(\"WebUntis holidays are not available; previous holiday values were preserved\");\n\t\t\t}\n\t\t\tawait this.setState(\"info.lastSuccessfulUpdate\", now.toISOString(), true);\n\t\t\tawait this.setState(\"info.nextUpdate\", new Date(now.getTime() + TIMETABLE_INTERVAL_MS).toISOString(), true);\n\t\t\tawait this.setState(\"info.connection\", true, true);\n\t\t\tthis.log.warn(`Timetable updated: ${updated} lessons updated, ${removed} obsolete lessons removed`);\n\t\t} catch (error) {\n\t\t\tawait this.setState(\"info.connection\", false, true);\n\t\t\tawait this.setState(\"info.nextUpdate\", new Date(Date.now() + TIMETABLE_INTERVAL_MS).toISOString(), true);\n\t\t\tconst code = error instanceof WebUntisError ? error.code : \"UNEXPECTED_RESPONSE\";\n\t\t\tconst detail = error instanceof Error ? ` (${error.message})` : \"\";\n\t\t\tthis.log.warn(`Timetable update failed: ${code}${detail}; existing timetable data was kept`);\n\t\t}\n\t}\n\n\tprivate async ensureTimetableRoot(): Promise<void> {\n\t\tawait this.setObjectNotExistsAsync(\"timetable\", {\n\t\t\ttype: \"channel\",\n\t\t\tcommon: { name: \"Timetable\" },\n\t\t\tnative: {},\n\t\t});\n\t}\n\n\tprivate async ensureHolidaysRoot(): Promise<void> {\n\t\tawait this.setObjectNotExistsAsync(\"holidays\", {\n\t\t\ttype: \"channel\",\n\t\t\tcommon: { name: \"Holidays\" },\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.setObjectNotExistsAsync(\"holidays.current\", {\n\t\t\ttype: \"channel\",\n\t\t\tcommon: { name: \"Current holiday\" },\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.setObjectNotExistsAsync(\"holidays.next\", {\n\t\t\ttype: \"channel\",\n\t\t\tcommon: { name: \"Next holiday\" },\n\t\t\tnative: {},\n\t\t});\n\t}\n\n\tprivate async writeHolidaySummary(summary: HolidaySummary): Promise<void> {\n\t\tawait this.writeSimpleState(\n\t\t\t\"holidays.current.active\",\n\t\t\tsummary.current.active,\n\t\t\t\"Active\",\n\t\t\t\"boolean\",\n\t\t\t\"indicator\",\n\t\t\tfalse,\n\t\t);\n\t\tawait this.writeSimpleState(\"holidays.current.name\", summary.current.name, \"Name\", \"string\", \"text\", \"\");\n\t\tawait this.writeSimpleState(\n\t\t\t\"holidays.current.longName\",\n\t\t\tsummary.current.longName,\n\t\t\t\"Long name\",\n\t\t\t\"string\",\n\t\t\t\"text\",\n\t\t\t\"\",\n\t\t);\n\t\tawait this.writeSimpleState(\n\t\t\t\"holidays.current.startDate\",\n\t\t\tsummary.current.startDate,\n\t\t\t\"Start date\",\n\t\t\t\"string\",\n\t\t\t\"date\",\n\t\t\t\"\",\n\t\t);\n\t\tawait this.writeSimpleState(\n\t\t\t\"holidays.current.endDate\",\n\t\t\tsummary.current.endDate,\n\t\t\t\"End date\",\n\t\t\t\"string\",\n\t\t\t\"date\",\n\t\t\t\"\",\n\t\t);\n\t\tawait this.writeSimpleState(\"holidays.next.name\", summary.next.name, \"Name\", \"string\", \"text\", \"\");\n\t\tawait this.writeSimpleState(\"holidays.next.longName\", summary.next.longName, \"Long name\", \"string\", \"text\", \"\");\n\t\tawait this.writeSimpleState(\n\t\t\t\"holidays.next.startDate\",\n\t\t\tsummary.next.startDate,\n\t\t\t\"Start date\",\n\t\t\t\"string\",\n\t\t\t\"date\",\n\t\t\t\"\",\n\t\t);\n\t\tawait this.writeSimpleState(\"holidays.next.endDate\", summary.next.endDate, \"End date\", \"string\", \"date\", \"\");\n\t\tawait this.writeSimpleState(\n\t\t\t\"holidays.next.daysUntil\",\n\t\t\tsummary.next.daysUntil,\n\t\t\t\"Days until\",\n\t\t\t\"number\",\n\t\t\t\"value\",\n\t\t\t0,\n\t\t);\n\t}\n\n\tprivate async writeDaySummary(path: string, summary: TimetableSummary): Promise<void> {\n\t\tfor (const [name, definition] of Object.entries(SUMMARY_STATE_DEFINITIONS) as [\n\t\t\tkeyof TimetableSummary,\n\t\t\t(typeof SUMMARY_STATE_DEFINITIONS)[keyof TimetableSummary],\n\t\t][]) {\n\t\t\tif (name === \"lessonCount\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tawait this.setObjectNotExistsAsync(`timetable.${path}.${name}`, {\n\t\t\t\ttype: \"state\",\n\t\t\t\tcommon: {\n\t\t\t\t\tname: definition.name,\n\t\t\t\t\ttype: definition.type,\n\t\t\t\t\trole: definition.role,\n\t\t\t\t\tread: true,\n\t\t\t\t\twrite: false,\n\t\t\t\t\tdef: definition.def,\n\t\t\t\t},\n\t\t\t\tnative: {},\n\t\t\t});\n\t\t\tawait this.setStateAsync(`timetable.${path}.${name}`, summary[name], true);\n\t\t}\n\t}\n\n\tprivate async writeCurrentNextSummary(lessons: TimetableLesson[], now: Date): Promise<void> {\n\t\tconst summary = deriveCurrentNextLesson(lessons, now);\n\t\tfor (const [name, definition] of Object.entries(CURRENT_NEXT_STATE_DEFINITIONS) as [\n\t\t\tkeyof CurrentNextLessonSummary,\n\t\t\t(typeof CURRENT_NEXT_STATE_DEFINITIONS)[keyof CurrentNextLessonSummary],\n\t\t][]) {\n\t\t\tawait this.setObjectNotExistsAsync(`timetable.today.${name}`, {\n\t\t\t\ttype: \"state\",\n\t\t\t\tcommon: {\n\t\t\t\t\tname: definition.name,\n\t\t\t\t\ttype: definition.type,\n\t\t\t\t\trole: definition.role,\n\t\t\t\t\tread: true,\n\t\t\t\t\twrite: false,\n\t\t\t\t\tdef: definition.def,\n\t\t\t\t\t...(definition.unit ? { unit: definition.unit } : {}),\n\t\t\t\t},\n\t\t\t\tnative: {},\n\t\t\t});\n\t\t\tawait this.setStateAsync(`timetable.today.${name}`, summary[name], true);\n\t\t}\n\t}\n\n\tprivate async updateCurrentNextStates(): Promise<void> {\n\t\tawait this.writeCurrentNextSummary(this.todayLessons, new Date());\n\t}\n\n\tprivate async cleanupObsoleteStructure(): Promise<void> {\n\t\tfor (const id of [\n\t\t\t\"info.timetable\",\n\t\t\t\"timetable.timetable\",\n\t\t\t\"timetable.today.count\",\n\t\t\t\"timetable.tomorrow.count\",\n\t\t\t\"timetable.today.subjectsJson\",\n\t\t\t\"timetable.tomorrow.subjectsJson\",\n\t\t]) {\n\t\t\tif (await this.getObjectAsync(id)) {\n\t\t\t\tawait this.delObjectAsync(id, { recursive: true });\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async writeTimetableGroup(\n\t\tgroup: TimetableGroup,\n\t\tlessons: TimetableLesson[],\n\t\tnow: Date,\n\t): Promise<{ updated: number; removed: number }> {\n\t\tawait this.setObjectNotExistsAsync(`timetable.${group}`, {\n\t\t\ttype: \"channel\",\n\t\t\tcommon: { name: `Timetable ${group}` },\n\t\t\tnative: {},\n\t\t});\n\t\tlet removed = await this.removeLegacyLessons(group);\n\t\tif (group === \"week\") {\n\t\t\tconst weekdayNames = [\"monday\", \"tuesday\", \"wednesday\", \"thursday\", \"friday\", \"saturday\", \"sunday\"];\n\t\t\tconst byDay = weekdayNames.map((_, index) =>\n\t\t\t\tlessons.filter(lesson => {\n\t\t\t\t\tconst date = new Date(`${lesson.date}T12:00:00`);\n\t\t\t\t\treturn (date.getDay() + 6) % 7 === index;\n\t\t\t\t}),\n\t\t\t);\n\t\t\tfor (let index = 0; index < weekdayNames.length; index++) {\n\t\t\t\tconst result = await this.writeTimetableDay(\n\t\t\t\t\t`timetable.week.${weekdayNames[index]}`,\n\t\t\t\t\tbyDay[index],\n\t\t\t\t\tthis.timetableDate(this.getMonday(now, index)),\n\t\t\t\t);\n\t\t\t\tremoved += result.removed;\n\t\t\t}\n\t\t} else {\n\t\t\tconst day = new Date(now);\n\t\t\tif (group === \"tomorrow\") {\n\t\t\t\tday.setDate(day.getDate() + 1);\n\t\t\t}\n\t\t\tconst result = await this.writeTimetableDay(`timetable.${group}`, lessons, this.timetableDate(day));\n\t\t\tawait this.writeDaySummary(group, summarizeTimetable(lessons));\n\t\t\tif (group === \"today\") {\n\t\t\t\tawait this.writeCurrentNextSummary(lessons, now);\n\t\t\t}\n\t\t\tremoved += result.removed;\n\t\t}\n\t\treturn { updated: lessons.length, removed };\n\t}\n\n\tprivate async removeLegacyLessons(group: TimetableGroup): Promise<number> {\n\t\tconst existing = await this.getObjectListAsync({\n\t\t\tstartkey: `${this.namespace}.timetable.${group}.`,\n\t\t\tendkey: `${this.namespace}.timetable.${group}.\\uffff`,\n\t\t});\n\t\tconst legacy = new Set<string>();\n\t\tfor (const object of existing.rows || []) {\n\t\t\tconst first = object.id.replace(`${this.namespace}.timetable.${group}.`, \"\").split(\".\")[0];\n\t\t\tif (first.startsWith(\"period-\")) {\n\t\t\t\tlegacy.add(first);\n\t\t\t}\n\t\t}\n\t\tfor (const id of legacy) {\n\t\t\tawait this.delObjectAsync(`timetable.${group}.${id}`, { recursive: true });\n\t\t}\n\t\treturn legacy.size;\n\t}\n\n\tprivate async writeTimetableDay(\n\t\tpath: string,\n\t\tlessons: TimetableLesson[],\n\t\tdate: string,\n\t): Promise<{ removed: number }> {\n\t\tawait this.setObjectNotExistsAsync(path, {\n\t\t\ttype: \"channel\",\n\t\t\tcommon: { name: path.split(\".\").at(-1) || \"Day\" },\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.writeSimpleState(`${path}.date`, date, \"Date\", \"string\", \"date\", \"\");\n\t\tawait this.writeSimpleState(`${path}.lessonCount`, lessons.length, \"Lesson count\", \"number\", \"value\", 0);\n\t\tawait this.setObjectNotExistsAsync(`${path}.lessons`, {\n\t\t\ttype: \"channel\",\n\t\t\tcommon: { name: \"Lessons\" },\n\t\t\tnative: {},\n\t\t});\n\t\tconst expected = new Set(lessons.map((_lesson, index) => String(index + 1).padStart(2, \"0\")));\n\t\tconst existing = await this.getObjectListAsync({\n\t\t\tstartkey: `${this.namespace}.${path}.lessons.`,\n\t\t\tendkey: `${this.namespace}.${path}.lessons.\\uffff`,\n\t\t});\n\t\tconst stale = new Set<string>();\n\t\tfor (const object of existing.rows || []) {\n\t\t\tconst slot = object.id.replace(`${this.namespace}.${path}.lessons.`, \"\").split(\".\")[0];\n\t\t\tif (slot && !expected.has(slot)) {\n\t\t\t\tstale.add(slot);\n\t\t\t}\n\t\t}\n\t\tfor (const slot of stale) {\n\t\t\tawait this.delObjectAsync(`${path}.lessons.${slot}`, { recursive: true });\n\t\t}\n\t\tfor (const [index, lesson] of lessons.entries()) {\n\t\t\tawait this.writeLesson(path, String(index + 1).padStart(2, \"0\"), lesson);\n\t\t}\n\t\treturn { removed: stale.size };\n\t}\n\n\tprivate getMonday(date: Date, dayOffset: number): Date {\n\t\tconst monday = new Date(date);\n\t\tmonday.setHours(12, 0, 0, 0);\n\t\tmonday.setDate(monday.getDate() - ((monday.getDay() + 6) % 7) + dayOffset);\n\t\treturn monday;\n\t}\n\n\tprivate timetableDate(date: Date): string {\n\t\treturn `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, \"0\")}-${String(date.getDate()).padStart(2, \"0\")}`;\n\t}\n\n\tprivate async writeSimpleState(\n\t\tid: string,\n\t\tvalue: string | number | boolean,\n\t\tname: string,\n\t\ttype: \"string\" | \"number\" | \"boolean\",\n\t\trole: string,\n\t\tdef: string | number | boolean,\n\t): Promise<void> {\n\t\tawait this.setObjectNotExistsAsync(id, {\n\t\t\ttype: \"state\",\n\t\t\tcommon: { name, type, role, read: true, write: false, def },\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.setStateAsync(id, value, true);\n\t}\n\n\tprivate async writeLesson(path: string, slot: string, lesson: TimetableLesson): Promise<void> {\n\t\tconst base = `${path}.lessons.${slot}`;\n\t\tconst label = lessonChannelName(lesson);\n\t\tawait this.setObjectNotExistsAsync(base, {\n\t\t\ttype: \"channel\",\n\t\t\tcommon: { name: label || `Lesson ${slot}` },\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.extendObjectAsync(base, { common: { name: label || `Lesson ${slot}` } });\n\t\tconst values: Record<\n\t\t\tstring,\n\t\t\t{ value: string | number | boolean; type: \"string\" | \"number\" | \"boolean\"; role: string }\n\t\t> = {\n\t\t\tdate: { value: lesson.date, type: \"string\", role: \"date\" },\n\t\t\tstartTime: { value: lesson.startTime, type: \"string\", role: \"value.time\" },\n\t\t\tendTime: { value: lesson.endTime, type: \"string\", role: \"value.time\" },\n\t\t\tsubject: { value: lesson.subject, type: \"string\", role: \"text\" },\n\t\t\tsubjectLong: { value: lesson.subjectLong, type: \"string\", role: \"text\" },\n\t\t\tteacher: { value: lesson.teacher, type: \"string\", role: \"text\" },\n\t\t\troom: { value: lesson.room, type: \"string\", role: \"text\" },\n\t\t\tclass: { value: lesson.class, type: \"string\", role: \"text\" },\n\t\t\t...(lesson.lessonId === null\n\t\t\t\t? {}\n\t\t\t\t: { lessonId: { value: String(lesson.lessonId), type: \"string\" as const, role: \"value\" } }),\n\t\t\t...(lesson.periodId === null\n\t\t\t\t? {}\n\t\t\t\t: { periodId: { value: String(lesson.periodId), type: \"string\" as const, role: \"value\" } }),\n\t\t\tstatus: { value: lesson.status, type: \"string\", role: \"text\" },\n\t\t\tchanged: { value: lesson.changed, type: \"boolean\", role: \"indicator\" },\n\t\t\tcancelled: { value: lesson.cancelled, type: \"boolean\", role: \"indicator\" },\n\t\t\t...(lesson.substitution\n\t\t\t\t? { substitution: { value: lesson.substitution, type: \"string\" as const, role: \"text\" } }\n\t\t\t\t: {}),\n\t\t\t...(lesson.originalTeacher && lesson.originalTeacher !== lesson.teacher\n\t\t\t\t? { originalTeacher: { value: lesson.originalTeacher, type: \"string\" as const, role: \"text\" } }\n\t\t\t\t: {}),\n\t\t\t...(lesson.originalRoom && lesson.originalRoom !== lesson.room\n\t\t\t\t? { originalRoom: { value: lesson.originalRoom, type: \"string\" as const, role: \"text\" } }\n\t\t\t\t: {}),\n\t\t\t...(lesson.originalSubject && lesson.originalSubject !== lesson.subject\n\t\t\t\t? { originalSubject: { value: lesson.originalSubject, type: \"string\" as const, role: \"text\" } }\n\t\t\t\t: {}),\n\t\t};\n\t\tconst optional = [\"substitution\", \"originalTeacher\", \"originalRoom\", \"originalSubject\"];\n\t\tfor (const name of optional) {\n\t\t\tif (!(name in values) && (await this.getObjectAsync(`${base}.${name}`))) {\n\t\t\t\tawait this.delObjectAsync(`${base}.${name}`);\n\t\t\t}\n\t\t}\n\t\tfor (const [name, item] of Object.entries(values)) {\n\t\t\tawait this.setObjectNotExistsAsync(`${base}.${name}`, {\n\t\t\t\ttype: \"state\",\n\t\t\t\tcommon: {\n\t\t\t\t\tname: LESSON_STATE_NAMES[name] || name,\n\t\t\t\t\ttype: item.type,\n\t\t\t\t\trole: item.role,\n\t\t\t\t\tread: true,\n\t\t\t\t\twrite: false,\n\t\t\t\t\tdef: item.value,\n\t\t\t\t},\n\t\t\t\tnative: {},\n\t\t\t});\n\t\t\tawait this.extendObjectAsync(`${base}.${name}`, { common: { name: LESSON_STATE_NAMES[name] || name } });\n\t\t\tawait this.setStateAsync(`${base}.${name}`, item.value, true);\n\t\t}\n\t}\n\n\tprivate async onMessage(obj: ioBroker.Message): Promise<void> {\n\t\tif (!obj || !obj.command || !obj.callback) {\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tif (obj.command === \"searchSchools\") {\n\t\t\t\tconst results = await this.webUntis.searchSchools(this.getStringPayload(obj.message, \"query\"));\n\t\t\t\tthis.log.info(`School search returned ${results.length} results`);\n\t\t\t\tthis.sendTo(obj.from, obj.command, { ok: true, results }, obj.callback);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (obj.command === \"testConnection\") {\n\t\t\t\tconst result = await this.webUntis.testConnection(this.getConnectionPayload(obj.message));\n\t\t\t\tawait this.setState(\"info.connection\", result.ok, true);\n\t\t\t\tif (result.ok) {\n\t\t\t\t\tthis.log.info(\"WebUntis authentication successful\");\n\t\t\t\t} else {\n\t\t\t\t\tthis.log.warn(`WebUntis authentication failed: ${result.code}`);\n\t\t\t\t}\n\t\t\t\tthis.sendTo(obj.from, obj.command, result, obj.callback);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst code = error instanceof WebUntisError ? error.code : \"UNEXPECTED_RESPONSE\";\n\t\t\tthis.log.warn(`WebUntis request failed: ${code}`);\n\t\t\tthis.sendTo(obj.from, obj.command, { ok: false, code }, obj.callback);\n\t\t}\n\t}\n\n\tprivate getStringPayload(message: ioBroker.MessagePayload, key: string): string {\n\t\tif (\n\t\t\ttypeof message !== \"object\" ||\n\t\t\tmessage === null ||\n\t\t\ttypeof (message as Record<string, unknown>)[key] !== \"string\"\n\t\t) {\n\t\t\tthrow new WebUntisError(\"INVALID_CONFIG\", \"Invalid message payload\");\n\t\t}\n\t\treturn (message as Record<string, unknown>)[key] as string;\n\t}\n\n\tprivate getConnectionPayload(message: ioBroker.MessagePayload): WebUntisConnectionConfig {\n\t\tif (typeof message !== \"object\" || message === null) {\n\t\t\tthrow new WebUntisError(\"INVALID_CONFIG\", \"Invalid connection payload\");\n\t\t}\n\t\tconst value = message as Record<string, unknown>;\n\t\tconst stringValue = (key: string): string => (typeof value[key] === \"string\" ? value[key] : \"\");\n\t\treturn {\n\t\t\tserver: stringValue(\"server\"),\n\t\t\tschoolName: stringValue(\"schoolName\"),\n\t\t\tschoolDisplayName: stringValue(\"schoolDisplayName\"),\n\t\t\tschoolAddress: stringValue(\"schoolAddress\"),\n\t\t\tschoolId: typeof value.schoolId === \"number\" ? value.schoolId : null,\n\t\t\tusername: stringValue(\"username\"),\n\t\t\tpassword: stringValue(\"password\"),\n\t\t};\n\t}\n\n\tprivate onUnload(callback: () => void): void {\n\t\tif (this.timetableTimer) {\n\t\t\tclearInterval(this.timetableTimer);\n\t\t}\n\t\tif (this.currentNextTimer) {\n\t\t\tclearInterval(this.currentNextTimer);\n\t\t}\n\t\tcallback();\n\t}\n}\n\nif (require.main !== module) {\n\tmodule.exports = (options: Partial<utils.AdapterOptions> | undefined) => new WebuntisNext(options);\n} else {\n\t(() => new WebuntisNext())();\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }