From bbc27a885b3a7612bc78eef3d804a40d3e8e1be5 Mon Sep 17 00:00:00 2001 From: Jey Cee Date: Tue, 19 Nov 2024 15:06:56 +0100 Subject: [PATCH 1/3] Added http API for abfall.io --- README.md | 4 + admin/jsonConfig.json | 6 +- lib/provider/api-abfallio.js | 131 ++++++- lib/source/api-abfallio.js | 492 ++++++++++++++++++++----- package-lock.json | 678 ++++++++++++++++++++++++++++++++++- package.json | 4 +- 6 files changed, 1199 insertions(+), 116 deletions(-) diff --git a/README.md b/README.md index d2cb9af..b8fe561 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,10 @@ iobroker add trashschedule Placeholder for the next version (at the beginning of the line): ### **WORK IN PROGRESS** --> + +### **WORK IN PROGRESS** +* (@jey-cee) Added http API for abfall.io + ### 3.4.0 (2024-11-07) * (@klein0r) Validate user inputs in instance configuration diff --git a/admin/jsonConfig.json b/admin/jsonConfig.json index 05898fa..c85f62a 100644 --- a/admin/jsonConfig.json +++ b/admin/jsonConfig.json @@ -235,7 +235,7 @@ }, "apiAbfallioDistrictId": { "newLine": true, - "hidden": "!data.apiAbfallioProvider || data.apiAbfallioProvider === 'err' || !data.apiAbfallioCityId || data.apiAbfallioCityId === 'err'", + "hidden": "!data.apiAbfallioProvider || data.apiAbfallioProvider === 'err' || !data.apiAbfallioCityId || data.apiAbfallioCityId === 'err' ", "type": "autocompleteSendTo", "command": "getApiDistricts", "jsonData": "{\"source\": \"api-abfallio\", \"provider\": \"${data.apiAbfallioProvider}\", \"cityId\": \"${data.apiAbfallioCityId}\"}", @@ -268,7 +268,7 @@ }, "apiAbfallioHouseNumber": { "newLine": true, - "hidden": "!data.apiAbfallioProvider || data.apiAbfallioProvider === 'err' || !data.apiAbfallioCityId || data.apiAbfallioCityId === 'err' || !data.apiAbfallioDistrictId || data.apiAbfallioDistrictId === 'err' || !data.apiAbfallioStreetId || data.apiAbfallioStreetId === 'err'", + "hidden": "!data.apiAbfallioProvider || data.apiAbfallioProvider === 'err' || !data.apiAbfallioCityId || data.apiAbfallioCityId === 'err' || !data.apiAbfallioDistrictId || data.apiAbfallioDistrictId === 'err' || !data.apiAbfallioStreetId || data.apiAbfallioStreetId === 'err' || (Array.isArray(data.apiAbfallioStreetId) ? data.apiAbfallioStreetId.some(item => item.label === 'alle Straßen') : data.apiAbfallioStreetId === 'alle Straßen')", "type": "autocompleteSendTo", "command": "getApiHouseNumbers", "jsonData": "{\"source\": \"api-abfallio\", \"provider\": \"${data.apiAbfallioProvider}\", \"cityId\": \"${data.apiAbfallioCityId}\", \"districtId\": \"${data.apiAbfallioDistrictId}\", \"streetId\": \"${data.apiAbfallioStreetId}\"}", @@ -290,7 +290,7 @@ "container": "text", "copyToClipboard": true, "command": "getApiTypesText", - "jsonData": "{\"source\": \"api-abfallio\", \"provider\": \"${data.apiAbfallioProvider}\", \"cityId\": \"${data.apiAbfallioCityId}\", \"houseNumber\": \"${data.apiAbfallioHouseNumber}\"}", + "jsonData": "{\"source\": \"api-abfallio\", \"provider\": \"${data.apiAbfallioProvider}\", \"cityId\": \"${data.apiAbfallioCityId}\", \"streetId\": \"${data.apiAbfallioStreetId}\", \"houseNumber\": \"${data.apiAbfallioHouseNumber}\"}", "alsoDependsOn": [ "apiAbfallioProvider", "apiAbfallioCityId", diff --git a/lib/provider/api-abfallio.js b/lib/provider/api-abfallio.js index dcc50c9..22c2b93 100644 --- a/lib/provider/api-abfallio.js +++ b/lib/provider/api-abfallio.js @@ -1,24 +1,30 @@ 'use strict'; module.exports = { - /* egst: { title: 'EGST Steinfurt', url: 'https://www.egst.de/', + apiKey: 'e21758b9c711463552fb9c70ac7d4273', + api: 'httpApi', }, alba: { title: 'ALBA Berlin', url: 'https://berlin.alba.info/', + apiKey: '9583a2fa1df97ed95363382c73b41b1b', + api: 'httpApi', }, aso: { title: 'ASO Abfall-Service Osterholz', url: 'https://www.aso-ohz.de/', + apiKey: '040b38fe83f026f161f30f282b2748c0', + api: 'httpApi', }, bayreuth: { title: 'Landkreis Bayreuth', url: 'https://www.landkreis-bayreuth.de/', + apiKey: 'd113742756399052aceb26d2c312a891cee5455767a991fc', + api: 'graphQl', }, - */ boeblingen: { title: 'Abfallwirtschaft Landkreis Böblingen', url: 'https://www.awb-bb.de', @@ -51,103 +57,204 @@ module.exports = { 'Weissach', ], apiKey: 'caac1d0b40973ad8ed652ef666ab7336fd50eede75ff868d', + api: 'graphQl', }, - /* calw: { title: 'Landkreis Calw', url: 'https://www.awg-info.de/privatkunden/abfuhrtermine', + apiKey: '690a3ae4906c52b232c1322e2f88550c', + api: 'httpApi', }, essen: { title: 'Entsorgungsbetriebe Essen', url: 'https://www.ebe-essen.de/', + apiKey: 'MTEwYzI4ODMtNmMzOC00MTlkLTkzZTUtZDJhYjUxNTUwYzk1OjU4NDM', + api: 'graphQl', }, - freudenstadt: { + /*freudenstadt: { title: 'Abfallwirtschaft Landkreis Freudenstadt', url: 'https://www.awb-fds.de/', - }, + apiKey: '', + api: '', + },*/ goeppingen: { title: 'AWB Landkreis Göppingen', url: 'https://www.awb-gp.de/', + apiKey: '3182d305647e50dbe6a90f5815aeb5a1', + api: 'httpApi', }, - goettingen: { + /*goettingen: { title: 'Göttinger Entsorgungsbetriebe', url: 'https://www.geb-goettingen.de/', - }, + apiKey: '', + api: '', + },*/ heilbronn: { title: 'Landkreis Heilbronn', - url: 'https://www.landkreis-heilbronn.de/', + url: 'https://www.aw-landkreis-heilbronn.de/', + apiKey: '18adb00cb5135f6aa16b5fdea6dae2c63a507ae0f836540e', + api: 'graphQl', }, kitzingen: { title: 'Abfallwirtschaft Landkreis Kitzingen', url: 'https://www.abfallwelt.de/', + apiKey: '594f805eb33677ad5bc645aeeeaf2623', + api: 'httpApi', }, landsberg: { title: 'Abfallwirtschaft Landkreis Landsberg am Lech', url: 'https://www.abfallberatung-landsberg.de/', + apiKey: '7df877d4f0e63decfb4d11686c54c5d6', + api: 'httpApi', }, landshut: { title: 'Stadt Landshut', url: 'https://www.landshut.de/', + apiKey: 'bd0c2d0177a0849a905cded5cb734a6f', + api: 'httpApi', }, ludwigshafen: { title: 'Ludwigshafen am Rhein', url: 'https://www.ludwigshafen.de/', + apiKey: '6efba91e69a5b454ac0ae3497978fe1d', + api: 'httpApi', }, muellalarm: { title: 'MüllALARM / Schönmackers', url: 'https://www.schoenmackers.de/', + apiKey: 'e5543a3e190cb8d91c645660ad60965f', + api: 'httpApi', }, ortenaukreis: { title: 'Abfallwirtschaft Ortenaukreis', url: 'https://www.abfallwirtschaft-ortenaukreis.de/', + apiKey: 'bb296b78763112266a391990f803f032', + api: 'httpApi', }, ostalbkreis: { title: 'Abfallbewirtschaftung Ostalbkreis', url: 'https://www.goa-online.de/', + apiKey: '3ca331fb42d25e25f95014693ebcf855', + api: 'httpApi', }, - ostallgäu: { + ostallgaeu: { title: 'Landkreis Ostallgäu', url: 'https://www.buerger-ostallgaeu.de/', + apiKey: '342cedd68ca114560ed4ca4b7c4e5ab6', + api: 'httpApi', }, rheinneckar: { title: 'Rhein-Neckar-Kreis', url: 'https://www.rhein-neckar-kreis.de/', + apiKey: '914fb9d000a9a05af4fd54cfba478860', + api: 'httpApi', }, rotenburg: { title: 'Landkreis Rotenburg (Wümme)', url: 'https://lk-awr.de/', + apiKey: '645adb3c27370a61f7eabbb2039de4f1', + api: 'httpApi', }, sigmaringen: { title: 'Landkreis Sigmaringen', url: 'https://www.landkreis-sigmaringen.de/', + apiKey: '8e2d42fc9e1604a53af1499fad35f3a9', + api: 'httpApi', }, traunstein: { title: 'Landratsamt Traunstein', url: 'https://www.traunstein.com/', + apiKey: '64bc012302ebd3e6bbc767ae6bd5746f57649ca676fa78da', + api: 'graphQl', }, unterallgaeu: { title: 'Landratsamt Unterallgäu', url: 'https://www.landratsamt-unterallgaeu.de/', + cities: [ + 'Amberg', + 'Apfeltrach', + 'Babenhausen', + 'Bad Grönenbach', + 'Bad Wörishofen', + 'Benningen', + 'Böhen', + 'Boos', + 'Breitenbrunn', + 'Buxheim', + 'Dirlewang', + 'Egg an der Günz', + 'Eppishausen', + 'Erkheim', + 'Ettringen', + 'Fellheim', + 'Hawangen', + 'Heimertingen', + 'Holzgünz', + 'Kammlach', + 'Kettershausen', + 'Kirchhaslach', + 'Kirchheim', + 'Kronburg', + 'Lachen', + 'Lauben', + 'Lautrach', + 'Legau', + 'Markt Rettenbach', + 'Markt Wald', + 'Memmingerberg', + 'Mindelheim', + 'Niederrieden', + 'Oberrieden', + 'Oberschönegg', + 'Ottobeuren', + 'Pfaffenhausen', + 'Pleß', + 'Rammingen', + 'Salgen', + 'Sontheim', + 'Stetten', + 'Trunkelsberg', + 'Türkheim', + 'Tussenhausen', + 'Ungerhausen', + 'Unteregg', + 'Westerheim', + 'Wiedergeltingen', + 'Winterrieden', + 'Wolfertschwenden', + 'Woringen', + ], + apiKey: 'd7f2f6a9abb738ae33fba06696f73ddd', + api: 'httpApi', }, westerwald: { title: 'AWB Westerwaldkreis', url: 'https://wab.rlp.de/', + apiKey: '248deacbb49b06e868d29cb53c8ef034', + api: 'httpApi', }, limburg: { title: 'Landkreis Limburg-Weilburg', url: 'https://www.awb-lm.de/', + apiKey: '0ff491ffdf614d6f34870659c0c8d917', + api: 'httpApi', }, weissenburg: { title: 'Landkreis Weißenburg-Gunzenhausen', url: 'https://www.landkreis-wug.de', + apiKey: '31fb9c7d783a030bf9e4e1994c7d2a91', + api: 'httpApi', }, - vivq: { + /*vivq: { title: 'VIVO Landkreis Miesbach', url: 'https://www.vivowarngau.de/', - }, + apiKey: '', + api: '', + },*/ mayen: { title: 'Abfallzweckverband Rhein-Mosel-Eifel (Landkreis Mayen-Koblenz)', url: 'https://www.azv-rme.de/', + apiKey: 'e62c1c1e8fea71cfe0304d0cb893b1d03a2af1091fc024b1', + api: 'graphQl', }, - */ }; diff --git a/lib/source/api-abfallio.js b/lib/source/api-abfallio.js index 4740cd5..f80748c 100644 --- a/lib/source/api-abfallio.js +++ b/lib/source/api-abfallio.js @@ -2,6 +2,9 @@ const BaseSource = require('./base'); const providers = require('../provider/api-abfallio'); +const axios = require('axios'); +const { JSDOM } = require('jsdom'); +const ical = require('ical'); class SourceApiAbfallIo extends BaseSource { async validate() { @@ -34,6 +37,9 @@ class SourceApiAbfallIo extends BaseSource { const apiTypes = await this.getApiTypes(provider, cityId, districtId, streetId, houseNumber); const apiPickups = await this.getApiPickups( provider, + cityId, + streetId, + districtId, houseNumber, apiTypes.map((t) => t.id), ); @@ -62,6 +68,83 @@ class SourceApiAbfallIo extends BaseSource { } async getApiCities(provider) { + try { + if (providers[provider].api === 'graphQl') { + return await this.graphQlGetCities(provider); + } else if (providers[provider].api === 'httpApi') { + return await this.httpApiGetCities(provider); + } + } catch (err) { + this.adapter.log.warn(`Error getting cities: ${err.data}`); + } + } + + async getApiDistricts(provider, cityId) { + try { + if (providers[provider].api === 'graphQl') { + return await this.graphQlGetDistricts(provider, cityId); + } else if (providers[provider].api === 'httpApi') { + return await this.httpApiGetDistricts(provider, cityId); + } + } catch (err) { + this.adapter.log.warn(`Error getting districts: ${err.data}`); + } + } + + async getApiStreets(provider, cityId, districtId) { + if (cityId && districtId) { + try { + if (providers[provider].api === 'graphQl') { + return await this.graphQlGetStreets(provider, cityId, districtId); + } else if (providers[provider].api === 'httpApi') { + return await this.httpApiGetStreets(provider, cityId, districtId); + } + } catch (err) { + this.adapter.log.warn(`Error getting streets: ${err.data}`); + } + } else { + throw new Error('[api-abfallio] Unable to get streets - empty cityId or districtId'); + } + } + + async getApiHouseNumbers(provider, cityId, districtId, streetId) { + if (cityId && districtId && streetId) { + if (providers[provider].api === 'graphQl') { + return await this.graphQlGetHouseNumbers(provider, cityId, districtId, streetId); + } else if (providers[provider].api === 'httpApi') { + return await this.httpApiGetHouseNumbers(provider, cityId, districtId, streetId); + } + } else { + throw new Error('[api-abfallio] Unable to get streets - empty cityId or districtId'); + } + } + + async getApiTypes(provider, cityId, districtId, streetId, houseNumber) { + if (houseNumber) { + if (providers[provider].api === 'graphQl') { + return await this.graphQlGetTypes(provider, houseNumber); + } else if (providers[provider].api === 'httpApi') { + return await this.httpApiGetTypes(provider, cityId, streetId, houseNumber); + } + } else { + throw new Error('[api-abfallio] Unable to get types - empty houseNumber'); + } + } + + async getApiPickups(provider, cityId, streetId, districtId, houseNumber, wasteTypes) { + if (houseNumber) { + if (providers[provider].api === 'graphQl') { + return await this.graphQlGetPickups(provider, houseNumber, wasteTypes); + } else if (providers[provider].api === 'httpApi') { + return await this.httpApiGetPickups(provider, cityId, streetId, districtId, houseNumber, wasteTypes); + } + } else { + throw new Error('[api-abfallio] Unable to get types - empty houseNumber'); + } + } + + /* GraphQL API functions */ + async graphQlGetCities(provider) { const citiesData = await this.getApiCached( { method: 'post', @@ -82,7 +165,7 @@ class SourceApiAbfallIo extends BaseSource { return JSON.parse(citiesData)?.data?.cities; } - async getApiDistricts(provider, cityId) { + async graphQlGetDistricts(provider, cityId) { const districtsData = await this.getApiCached( { method: 'post', @@ -103,111 +186,340 @@ class SourceApiAbfallIo extends BaseSource { return JSON.parse(districtsData)?.data?.city?.districts; } - async getApiStreets(provider, cityId, districtId) { - if (cityId && districtId) { - const streetsData = await this.getApiCached( - { - method: 'post', - baseURL: 'https://widgets.abfall.io/graphql', - headers: { - 'Content-Type': 'application/json', - Origin: providers[provider].url, - 'x-abfallplus-api-key': providers[provider].apiKey, - }, - data: JSON.stringify({ - query: 'query GetStreets($id: ID!, $query: String) {\ndistrict(id: $id) {\nstreets(query: $query) {\nid\nname\nidHouseNumber\n}\n}\n}\n', - variables: { id: String(districtId), query: null }, - }), + async graphQlGetStreets(provider, cityId, districtId) { + const streetsData = await this.getApiCached( + { + method: 'post', + baseURL: 'https://widgets.abfall.io/graphql', + headers: { + 'Content-Type': 'application/json', + Origin: providers[provider].url, + 'x-abfallplus-api-key': providers[provider].apiKey, }, - `abfallio-streets-${provider}-${cityId}-${districtId}.json`, - ); + data: JSON.stringify({ + query: 'query GetStreets($id: ID!, $query: String) {\ndistrict(id: $id) {\nstreets(query: $query) {\nid\nname\nidHouseNumber\n}\n}\n}\n', + variables: { id: String(districtId), query: null }, + }), + }, + `abfallio-streets-${provider}-${cityId}-${districtId}.json`, + ); - return JSON.parse(streetsData)?.data?.district?.streets; - } else { - throw new Error('[api-abfallio] Unable to get streets - empty cityId or districtId'); - } + return JSON.parse(streetsData)?.data?.district?.streets; } - async getApiHouseNumbers(provider, cityId, districtId, streetId) { - if (cityId && districtId && streetId) { - const streetsData = await this.getApiCached( - { - method: 'post', - baseURL: 'https://widgets.abfall.io/graphql', - headers: { - 'Content-Type': 'application/json', - Origin: providers[provider].url, - 'x-abfallplus-api-key': providers[provider].apiKey, - }, - data: JSON.stringify({ - query: 'query GetHouseNumbers($streetId: ID!, $idDistrict: ID, $query: String) {\nstreet(id: $streetId) {\nhouseNumbers(query: $query, idDistrict: $idDistrict) {\nid\nname\n}\n}\n}\n', - variables: { streetId: String(streetId), query: null, idDistrict: String(districtId) }, - }), + async graphQlGetHouseNumbers(provider, cityId, districtId, streetId) { + const streetsData = await this.getApiCached( + { + method: 'post', + baseURL: 'https://widgets.abfall.io/graphql', + headers: { + 'Content-Type': 'application/json', + Origin: providers[provider].url, + 'x-abfallplus-api-key': providers[provider].apiKey, }, - `abfallio-housenumbers-${provider}-${cityId}-${districtId}-${streetId}.json`, - ); + data: JSON.stringify({ + query: 'query GetHouseNumbers($streetId: ID!, $idDistrict: ID, $query: String) {\nstreet(id: $streetId) {\nhouseNumbers(query: $query, idDistrict: $idDistrict) {\nid\nname\n}\n}\n}\n', + variables: { streetId: String(streetId), query: null, idDistrict: String(districtId) }, + }), + }, + `abfallio-housenumbers-${provider}-${cityId}-${districtId}-${streetId}.json`, + ); - return JSON.parse(streetsData)?.data?.street?.houseNumbers; - } else { - throw new Error('[api-abfallio] Unable to get streets - empty cityId or districtId'); - } + return JSON.parse(streetsData)?.data?.street?.houseNumbers; } - async getApiTypes(provider, cityId, districtId, streetId, houseNumber) { - if (houseNumber) { - const streetsData = await this.getApiCached( - { - method: 'post', - baseURL: 'https://widgets.abfall.io/graphql', - headers: { - 'Content-Type': 'application/json', - Origin: providers[provider].url, - 'x-abfallplus-api-key': providers[provider].apiKey, + async graphQlGetTypes(provider, houseNumber) { + const streetsData = await this.getApiCached( + { + method: 'post', + baseURL: 'https://widgets.abfall.io/graphql', + headers: { + 'Content-Type': 'application/json', + Origin: providers[provider].url, + 'x-abfallplus-api-key': providers[provider].apiKey, + }, + data: JSON.stringify({ + query: 'query HouseNumber($houseNumberId: ID!) {\nhouseNumber(id: $houseNumberId) {\nwasteTypes {\nid\nname\ninternals {\npdfLegend\n}\n}\n}\n}\n', + variables: { houseNumberId: String(houseNumber) }, + }), + }, + `abfallio-types-${provider}-${houseNumber}.json`, + ); + return JSON.parse(streetsData)?.data?.houseNumber?.wasteTypes; + } + + async graphQlGetPickups(provider, houseNumber, wasteTypes) { + const now = new Date(); + const pickupData = await this.getApiCached( + { + method: 'post', + baseURL: 'https://widgets.abfall.io/graphql', + headers: { + 'Content-Type': 'application/json', + Origin: providers[provider].url, + 'x-abfallplus-api-key': providers[provider].apiKey, + }, + data: JSON.stringify({ + query: 'query Query($idHouseNumber: ID!, $wasteTypes: [ID], $dateMin: Date, $dateMax: Date, $showInactive: Boolean) {\nappointments(idHouseNumber: $idHouseNumber, wasteTypes: $wasteTypes, dateMin: $dateMin, dateMax: $dateMax, showInactive: $showInactive) {\nid\ndate\ntime\nlocation\nnote\nwasteType {\nid\nname\ncolor\ninternals {\npdfLegend\niconLow\n}\n}\n}\n}\n', + variables: { + idHouseNumber: String(houseNumber), + wasteTypes, + dateMin: `${now.getFullYear()}-01-01`, + dateMax: `${now.getFullYear()}-12-31`, + showInactive: false, }, - data: JSON.stringify({ - query: 'query HouseNumber($houseNumberId: ID!) {\nhouseNumber(id: $houseNumberId) {\nwasteTypes {\nid\nname\ninternals {\npdfLegend\n}\n}\n}\n}\n', - variables: { houseNumberId: String(houseNumber) }, - }), + }), + }, + `abfallio-pickups-${provider}-${houseNumber}.json`, + ); + + return JSON.parse(pickupData)?.data?.appointments; + } + + /* Http API functions */ + async httpApiGetCities(provider) { + const citiesData = await this.getApiCached( + { + method: 'get', + baseURL: `https://api.abfall.io/?key=${providers[provider].apiKey}&modus=d6c5855a62cf32a4dadbc2831f0f295f&waction=init`, + headers: { + 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:120.0) Gecko/20100101 Firefox/120.0', }, - `abfallio-types-${provider}-${houseNumber}.json`, - ); + }, + `abfallio-cities-${provider}.json`, + ); - return JSON.parse(streetsData)?.data?.houseNumber?.wasteTypes; - } else { - throw new Error('[api-abfallio] Unable to get types - empty houseNumber'); - } + const dom = new JSDOM(citiesData); + const doc = dom.window.document; + + return Array.from(doc.querySelectorAll('select[name="f_id_kommune"] option')) + .map((option) => ({ + id: option.value, + name: option.textContent.trim(), + })) + .filter((option) => option.id !== '0'); } - async getApiPickups(provider, houseNumber, wasteTypes) { - if (houseNumber) { - const now = new Date(); - const pickupData = await this.getApiCached( - { - method: 'post', - baseURL: 'https://widgets.abfall.io/graphql', - headers: { - 'Content-Type': 'application/json', - Origin: providers[provider].url, - 'x-abfallplus-api-key': providers[provider].apiKey, - }, - data: JSON.stringify({ - query: 'query Query($idHouseNumber: ID!, $wasteTypes: [ID], $dateMin: Date, $dateMax: Date, $showInactive: Boolean) {\nappointments(idHouseNumber: $idHouseNumber, wasteTypes: $wasteTypes, dateMin: $dateMin, dateMax: $dateMax, showInactive: $showInactive) {\nid\ndate\ntime\nlocation\nnote\nwasteType {\nid\nname\ncolor\ninternals {\npdfLegend\niconLow\n}\n}\n}\n}\n', - variables: { - idHouseNumber: String(houseNumber), - wasteTypes, - dateMin: `${now.getFullYear()}-01-01`, - dateMax: `${now.getFullYear()}-12-31`, - showInactive: false, - }, - }), + async httpApiGetDistricts(provider, cityId) { + const districtsData = await this.getApiCached( + { + method: 'post', + baseURL: `https://api.abfall.io/?key=${providers[provider].apiKey}&modus=d6c5855a62cf32a4dadbc2831f0f295f&waction=auswahl_kommune_set`, + headers: { + 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:120.0) Gecko/20100101 Firefox/120.0', + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', }, - `abfallio-pickups-${provider}-${houseNumber}.json`, - ); + data: { + '31038f3146a0da582405fdc291437261': '63665c30d23bb0fed8889027f0fc762a', + f_id_kommune: cityId, + }, + }, + `abfallio-districts-${provider}-${cityId}.json`, + ); - return JSON.parse(pickupData)?.data?.appointments; - } else { - throw new Error('[api-abfallio] Unable to get types - empty houseNumber'); + const dom = new JSDOM(districtsData); + const doc = dom.window.document; + + const districts = Array.from(doc.querySelectorAll('select[name="f_id_bezirk"] option')) + .map((option) => ({ + id: option.value, + name: option.textContent.trim(), + })) + .filter((option) => option.id !== '0'); + + return districts.length > 0 ? districts : [{ id: '0', name: '--' }]; + } + + async httpApiGetStreets(provider, cityId, districtId) { + // If there is no district the id is 0 + const streetsData = await this.getApiCached( + { + method: 'post', + baseURL: `https://api.abfall.io/?key=${providers[provider].apiKey}&modus=d6c5855a62cf32a4dadbc2831f0f295f&waction=${districtId === '0' ? 'auswahl_kommune_set' : 'auswahl_bezirk_set'}`, + headers: { + 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:120.0) Gecko/20100101 Firefox/120.0', + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + }, + data: { + '31038f3146a0da582405fdc291437261': '63665c30d23bb0fed8889027f0fc762a', + f_id_kommune: cityId, + f_id_bezirk: districtId, + }, + }, + `abfallio-streets-${provider}-${cityId}-${districtId}.json`, + ); + + const dom = new JSDOM(streetsData); + const doc = dom.window.document; + + const streets = Array.from(doc.querySelectorAll('select[name="f_id_strasse"] option')) + .map((option) => ({ + id: option.value, + name: option.textContent.trim(), + })) + .filter((option) => option.id !== '0'); + + return streets; + } + + async httpApiGetHouseNumbers(provider, cityId, districtId, streetId) { + const houseNumbersData = await this.getApiCached( + { + method: 'post', + baseURL: `https://api.abfall.io/?key=${providers[provider].apiKey}&modus=d6c5855a62cf32a4dadbc2831f0f295f&waction=auswahl_strasse_set}`, + headers: { + 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:120.0) Gecko/20100101 Firefox/120.0', + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + }, + data: { + '31038f3146a0da582405fdc291437261': '63665c30d23bb0fed8889027f0fc762a', + f_id_kommune: cityId, + f_id_strasse: streetId, + }, + }, + `abfallio-houseNumbers-${provider}-${cityId}-${districtId}-${streetId}.json`, + ); + + const dom = new JSDOM(houseNumbersData); + const doc = dom.window.document; + + const houseNumbers = Array.from(doc.querySelectorAll('select[name="f_id_strasse_hnr"] option')) + .map((option) => ({ + id: option.value, + name: option.textContent.trim(), + })) + .filter((option) => option.id !== '0'); + return houseNumbers.length > 0 ? houseNumbers : [{ id: '0', name: 'Alle Straßen' }]; + } + + async httpApiGetTypes(provider, cityId, streetId, houseNumber) { + const streetsData = await this.getApiCached( + { + method: 'post', + baseURL: `https://api.abfall.io/?key=${providers[provider].apiKey}&modus=d6c5855a62cf32a4dadbc2831f0f295f&waction=${houseNumber === '0' ? 'auswahl_strasse_set' : 'auswahl_hnr_set'}`, + headers: { + 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:120.0) Gecko/20100101 Firefox/120.0', + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + }, + data: { + '31038f3146a0da582405fdc291437261': '63665c30d23bb0fed8889027f0fc762a', + f_id_kommune: cityId, + f_id_strasse: streetId, + f_id_strasse_hnr: houseNumber, + }, + }, + `abfallio-types-${provider}-${cityId}-${streetId}.json`, + ); + + const dom = new JSDOM(streetsData); + const doc = dom.window.document; + + const types = Array.from(doc.querySelectorAll('label[for^="f_id_abfalltyp_"]')) + .map((label) => { + const checkbox = doc.getElementById(label.getAttribute('for')); + return { + name: label.textContent.trim().replace('Abfallart ', '').replace(' verwenden?', ''), + id: checkbox ? checkbox.value : null, + }; + }) + .filter((label) => label.id !== '0'); + + return types; + } + + async httpApiGetPickups(provider, cityId, streetId, districtId, houseNumber, wasteTypes) { + const init = await axios({ + method: 'POST', + url: `https://api.abfall.io/?key=${providers[provider].apiKey}&modus=d6c5855a62cf32a4dadbc2831f0f295f&waction=init`, + headers: { + 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:120.0) Gecko/20100101 Firefox/120.0', + }, + }); + + const dom = new JSDOM(init.data); + const doc = dom.window.document; + + const currentYear = new Date().getFullYear(); + const zeitraum = `${currentYear}0101-${currentYear}1231`; + let config = { + method: 'post', + baseURL: `https://api.abfall.io/?key=${providers[provider].apiKey}&modus=d6c5855a62cf32a4dadbc2831f0f295f&waction=export_ics`, + headers: { + 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:120.0) Gecko/20100101 Firefox/120.0', + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + }, + data: { + f_id_kommune: cityId, + f_id_strasse: streetId, + f_abfallarten_index_max: wasteTypes.length.toString(), + f_abfallarten: wasteTypes.toString(), + f_zeitraum: zeitraum, + f_export_als: JSON.stringify({ action: `https://api.abfall.io/?key=${providers[provider].apiKey}&modus=d6c5855a62cf32a4dadbc2831f0f295f&waction=export_ics`, target: '' }), + }, + }; + + const input = doc.querySelectorAll('input[type="hidden"]')[1]; + + if (input) { + config.data[input.getAttribute('name')] = input.getAttribute('value'); } + + if (districtId && districtId !== '0') { + config.data['f_id_bezirk'] = districtId; + } + if (houseNumber && houseNumber !== '0') { + config.data['f_id_strasse_hnr'] = houseNumber; + } + for (let type in wasteTypes) { + config.data[`f_id_abfalltyp_${type}`] = wasteTypes[type]; + } + const pickupData = await this.getApiCached(config, `abfallio-pickups-${provider}-${cityId}.ics`); + + const wasteTypeMap = { + Restmüll: { id: '333', name: 'Restmüll 120l/240l', color: '#242425' }, + Biomüll: { id: '50', name: 'Biomüll', color: '#824300' }, + 'Gelbe Tonne': { id: '299', name: 'Wertstoffe', color: '#f4b324' }, + Altpapier: { id: '53', name: 'Papier 120l/240l', color: '#2d76ba' }, + }; + + const events = ical.parseICS(pickupData); + + const appointments = []; + + for (const key in events) { + const event = events[key]; + if (event.type === 'VEVENT') { + const summary = event.summary || ''; + + const localDate = new Date(event.start).toLocaleDateString('en-CA'); + const description = event.description || ''; + + const wasteType = wasteTypeMap[summary] || {}; + + const appointment = { + id: `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`, + date: localDate, + time: null, + location: null, + note: description, + wasteType: { + id: wasteType.id || '', + name: wasteType.name || '', + color: wasteType.color || '', + internals: { + pdfLegend: null, + iconLow: null, + }, + }, + }; + + appointments.push(appointment); + } + } + + const outputJSON = { data: { appointments } }; + + return outputJSON?.data?.appointments; } } diff --git a/package-lock.json b/package-lock.json index 2e74ff2..c50fd36 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,9 @@ "license": "MIT", "dependencies": { "@iobroker/adapter-core": "^3.2.2", - "axios": "^1.7.7" + "axios": "^1.7.7", + "ical": "^0.8.0", + "jsdom": "^25.0.1" }, "devDependencies": { "@alcalzone/release-script": "^3.8.0", @@ -1823,11 +1825,69 @@ "node": ">= 8" } }, + "node_modules/cssstyle": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.1.0.tgz", + "integrity": "sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA==", + "license": "MIT", + "dependencies": { + "rrweb-cssom": "^0.7.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-urls/node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-urls/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz", + "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", + "license": "MIT", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/debug": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -1852,6 +1912,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "license": "MIT" + }, "node_modules/deep-eql": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", @@ -2780,6 +2846,18 @@ "he": "bin/he" } }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/html-tags": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", @@ -2828,6 +2906,27 @@ "node": ">=10.17.0" } }, + "node_modules/ical": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/ical/-/ical-0.8.0.tgz", + "integrity": "sha512-/viUSb/RGLLnlgm0lWRlPBtVeQguQRErSPYl3ugnUaKUnzQswKqOG3M8/P1v1AB5NJwlHTuvTq1cs4mpeG2rCg==", + "license": "Apache-2.0", + "dependencies": { + "rrule": "2.4.1" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ignore": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", @@ -2971,6 +3070,12 @@ "node": ">=8" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "license": "MIT" + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -3072,6 +3177,118 @@ "node": ">=8" } }, + "node_modules/jsdom": { + "version": "25.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz", + "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==", + "license": "MIT", + "dependencies": { + "cssstyle": "^4.1.0", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.12", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.7.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^2.11.2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/jsdom/node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/jsdom/node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/jsdom/node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/jsdom/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/jsdom/node_modules/whatwg-url": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz", + "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", + "license": "MIT", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/json-bigint": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", @@ -3260,6 +3477,16 @@ "node": ">=10" } }, + "node_modules/luxon": { + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.1.tgz", + "integrity": "sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==", + "license": "MIT", + "optional": true, + "engines": { + "node": "*" + } + }, "node_modules/markdown-it": { "version": "12.3.2", "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", @@ -3501,8 +3728,7 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/mustache": { "version": "4.2.0", @@ -3582,6 +3808,12 @@ "node": ">=8" } }, + "node_modules/nwsapi": { + "version": "2.2.13", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.13.tgz", + "integrity": "sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ==", + "license": "MIT" + }, "node_modules/object-hash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", @@ -3674,6 +3906,30 @@ "node": ">=6" } }, + "node_modules/parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "license": "MIT", + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3894,7 +4150,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, "engines": { "node": ">=6" } @@ -3991,6 +4246,21 @@ "node": ">=12" } }, + "node_modules/rrule": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/rrule/-/rrule-2.4.1.tgz", + "integrity": "sha512-+NcvhETefswZq13T8nkuEnnQ6YgUeZaqMqVbp+ZiFDPCbp3AVgQIwUvNVDdMNrP05bKZG9ddDULFp0qZZYDrxg==", + "license": "SEE LICENSE IN LICENSE", + "optionalDependencies": { + "luxon": "^1.3.3" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", + "license": "MIT" + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -4011,6 +4281,24 @@ } ] }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -4228,6 +4516,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "license": "MIT" + }, "node_modules/synckit": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz", @@ -4276,6 +4570,24 @@ "globrex": "^0.1.2" } }, + "node_modules/tldts": { + "version": "6.1.61", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.61.tgz", + "integrity": "sha512-rv8LUyez4Ygkopqn+M6OLItAOT9FF3REpPQDkdMx5ix8w4qkuE7Vo2o/vw1nxKQYmJDV8JpAMJQr1b+lTKf0FA==", + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.61" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.61", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.61.tgz", + "integrity": "sha512-In7VffkDWUPgwa+c9picLUxvb0RltVwTkSgMNFgvlGSWveCzGBemBqTsgJCL4EDFWZ6WH0fKTsot6yNhzy3ZzQ==", + "license": "MIT" + }, "node_modules/tmp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", @@ -4297,6 +4609,18 @@ "node": ">=8.0" } }, + "node_modules/tough-cookie": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz", + "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==", + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -4410,12 +4734,45 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "dev": true }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -4479,6 +4836,42 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT" + }, "node_modules/xmlcreate": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", @@ -5820,11 +6213,51 @@ "which": "^2.0.1" } }, + "cssstyle": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.1.0.tgz", + "integrity": "sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA==", + "requires": { + "rrweb-cssom": "^0.7.1" + } + }, + "data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "requires": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "dependencies": { + "tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "requires": { + "punycode": "^2.3.1" + } + }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" + }, + "whatwg-url": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz", + "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", + "requires": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + } + } + } + }, "debug": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", - "dev": true, "requires": { "ms": "2.1.2" } @@ -5835,6 +6268,11 @@ "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", "dev": true }, + "decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" + }, "deep-eql": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", @@ -6496,6 +6934,14 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, + "html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "requires": { + "whatwg-encoding": "^3.1.1" + } + }, "html-tags": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", @@ -6529,6 +6975,22 @@ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true }, + "ical": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/ical/-/ical-0.8.0.tgz", + "integrity": "sha512-/viUSb/RGLLnlgm0lWRlPBtVeQguQRErSPYl3ugnUaKUnzQswKqOG3M8/P1v1AB5NJwlHTuvTq1cs4mpeG2rCg==", + "requires": { + "rrule": "2.4.1" + } + }, + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, "ignore": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", @@ -6633,6 +7095,11 @@ "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "dev": true }, + "is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" + }, "is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -6712,6 +7179,84 @@ } } }, + "jsdom": { + "version": "25.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz", + "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==", + "requires": { + "cssstyle": "^4.1.0", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.12", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.7.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "dependencies": { + "agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "requires": { + "debug": "^4.3.4" + } + }, + "http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "requires": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + } + }, + "https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "requires": { + "agent-base": "^7.0.2", + "debug": "4" + } + }, + "tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "requires": { + "punycode": "^2.3.1" + } + }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" + }, + "whatwg-url": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz", + "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", + "requires": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + } + } + } + }, "json-bigint": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", @@ -6880,6 +7425,12 @@ "yallist": "^4.0.0" } }, + "luxon": { + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.1.tgz", + "integrity": "sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==", + "optional": true + }, "markdown-it": { "version": "12.3.2", "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", @@ -7064,8 +7615,7 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "mustache": { "version": "4.2.0", @@ -7122,6 +7672,11 @@ "path-key": "^3.0.0" } }, + "nwsapi": { + "version": "2.2.13", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.13.tgz", + "integrity": "sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ==" + }, "object-hash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", @@ -7187,6 +7742,21 @@ "callsites": "^3.0.0" } }, + "parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "requires": { + "entities": "^4.5.0" + }, + "dependencies": { + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" + } + } + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -7352,8 +7922,7 @@ "punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" }, "randombytes": { "version": "2.1.0", @@ -7426,12 +7995,38 @@ "extend": "^3.0.2" } }, + "rrule": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/rrule/-/rrule-2.4.1.tgz", + "integrity": "sha512-+NcvhETefswZq13T8nkuEnnQ6YgUeZaqMqVbp+ZiFDPCbp3AVgQIwUvNVDdMNrP05bKZG9ddDULFp0qZZYDrxg==", + "requires": { + "luxon": "^1.3.3" + } + }, + "rrweb-cssom": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==" + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "requires": { + "xmlchars": "^2.2.0" + } + }, "semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -7605,6 +8200,11 @@ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + }, "synckit": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz", @@ -7644,6 +8244,19 @@ "globrex": "^0.1.2" } }, + "tldts": { + "version": "6.1.61", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.61.tgz", + "integrity": "sha512-rv8LUyez4Ygkopqn+M6OLItAOT9FF3REpPQDkdMx5ix8w4qkuE7Vo2o/vw1nxKQYmJDV8JpAMJQr1b+lTKf0FA==", + "requires": { + "tldts-core": "^6.1.61" + } + }, + "tldts-core": { + "version": "6.1.61", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.61.tgz", + "integrity": "sha512-In7VffkDWUPgwa+c9picLUxvb0RltVwTkSgMNFgvlGSWveCzGBemBqTsgJCL4EDFWZ6WH0fKTsot6yNhzy3ZzQ==" + }, "tmp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", @@ -7659,6 +8272,14 @@ "is-number": "^7.0.0" } }, + "tough-cookie": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz", + "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==", + "requires": { + "tldts": "^6.1.32" + } + }, "tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -7743,12 +8364,33 @@ "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", "dev": true }, + "w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "requires": { + "xml-name-validator": "^5.0.0" + } + }, "webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "dev": true }, + "whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "requires": { + "iconv-lite": "0.6.3" + } + }, + "whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==" + }, "whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -7797,6 +8439,22 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, + "ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "requires": {} + }, + "xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==" + }, + "xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" + }, "xmlcreate": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", diff --git a/package.json b/package.json index 428ddd4..f8c92cc 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,9 @@ }, "dependencies": { "@iobroker/adapter-core": "^3.2.2", - "axios": "^1.7.7" + "axios": "^1.7.7", + "jsdom": "^25.0.1", + "ical": "^0.8.0" }, "devDependencies": { "@alcalzone/release-script": "^3.8.0", From af653c5f9a9151a4637fe2e52c285b449f85051f Mon Sep 17 00:00:00 2001 From: Jey Cee Date: Tue, 19 Nov 2024 15:16:33 +0100 Subject: [PATCH 2/3] Fix house numbers text for no house numbers --- lib/source/api-abfallio.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/source/api-abfallio.js b/lib/source/api-abfallio.js index f80748c..e55127c 100644 --- a/lib/source/api-abfallio.js +++ b/lib/source/api-abfallio.js @@ -389,7 +389,7 @@ class SourceApiAbfallIo extends BaseSource { name: option.textContent.trim(), })) .filter((option) => option.id !== '0'); - return houseNumbers.length > 0 ? houseNumbers : [{ id: '0', name: 'Alle Straßen' }]; + return houseNumbers.length > 0 ? houseNumbers : [{ id: '0', name: 'Alle Hausnummern' }]; } async httpApiGetTypes(provider, cityId, streetId, houseNumber) { From 7f7f9e5871aa6d9acbfe89460d0c29ac30d567c4 Mon Sep 17 00:00:00 2001 From: Jey Cee Date: Tue, 19 Nov 2024 17:53:24 +0100 Subject: [PATCH 3/3] Remove waste type mapping for pickup dates --- lib/source/api-abfallio.js | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/lib/source/api-abfallio.js b/lib/source/api-abfallio.js index e55127c..ea1bfa8 100644 --- a/lib/source/api-abfallio.js +++ b/lib/source/api-abfallio.js @@ -475,13 +475,6 @@ class SourceApiAbfallIo extends BaseSource { } const pickupData = await this.getApiCached(config, `abfallio-pickups-${provider}-${cityId}.ics`); - const wasteTypeMap = { - Restmüll: { id: '333', name: 'Restmüll 120l/240l', color: '#242425' }, - Biomüll: { id: '50', name: 'Biomüll', color: '#824300' }, - 'Gelbe Tonne': { id: '299', name: 'Wertstoffe', color: '#f4b324' }, - Altpapier: { id: '53', name: 'Papier 120l/240l', color: '#2d76ba' }, - }; - const events = ical.parseICS(pickupData); const appointments = []; @@ -489,13 +482,9 @@ class SourceApiAbfallIo extends BaseSource { for (const key in events) { const event = events[key]; if (event.type === 'VEVENT') { - const summary = event.summary || ''; - const localDate = new Date(event.start).toLocaleDateString('en-CA'); const description = event.description || ''; - const wasteType = wasteTypeMap[summary] || {}; - const appointment = { id: `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`, date: localDate, @@ -503,9 +492,9 @@ class SourceApiAbfallIo extends BaseSource { location: null, note: description, wasteType: { - id: wasteType.id || '', - name: wasteType.name || '', - color: wasteType.color || '', + id: '', + name: event.summary, + color: '', internals: { pdfLegend: null, iconLow: null,