@camstack/addon-waste-collections 0.1.4 → 0.1.6
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.
|
@@ -13555,7 +13555,14 @@ var BrokerProviderInfoSchema = object({
|
|
|
13555
13555
|
/** Broker kinds this provider can create, with a display label. */
|
|
13556
13556
|
kinds: array(object({
|
|
13557
13557
|
kind: string(),
|
|
13558
|
-
label: string()
|
|
13558
|
+
label: string(),
|
|
13559
|
+
/**
|
|
13560
|
+
* Relative hub path to start an interactive account link (OAuth popup).
|
|
13561
|
+
* May include `{{brokerId}}`; admin-ui substitutes and appends `?popup=1`.
|
|
13562
|
+
*/
|
|
13563
|
+
interactiveConnectPath: string().min(1).optional(),
|
|
13564
|
+
/** Relative hub path to disconnect the linked account (same popup pattern). */
|
|
13565
|
+
interactiveDisconnectPath: string().min(1).optional()
|
|
13559
13566
|
}))
|
|
13560
13567
|
});
|
|
13561
13568
|
var ListInputSchema = object({
|
|
@@ -52834,7 +52841,10 @@ sourceId: string().min(1).optional() }).optional(), object({
|
|
|
52834
52841
|
}),
|
|
52835
52842
|
listServices: customAction(object({ countryId: string().min(1) }), array(object({
|
|
52836
52843
|
id: string(),
|
|
52844
|
+
/** Operator list label — may include `(serviceCode)`. */
|
|
52837
52845
|
label: string(),
|
|
52846
|
+
/** Catalog title without the service-code suffix (calendar default). */
|
|
52847
|
+
title: string(),
|
|
52838
52848
|
module: string()
|
|
52839
52849
|
})).readonly(), {
|
|
52840
52850
|
kind: "mutation",
|
|
@@ -52845,7 +52855,8 @@ sourceId: string().min(1).optional() }).optional(), object({
|
|
|
52845
52855
|
auth: "admin"
|
|
52846
52856
|
}),
|
|
52847
52857
|
createSource: customAction(object({
|
|
52848
|
-
id
|
|
52858
|
+
/** Optional override; when omitted the id is derived from `label`. */
|
|
52859
|
+
id: SourceIdSchema.optional(),
|
|
52849
52860
|
label: string().min(1),
|
|
52850
52861
|
serviceId: string().min(1),
|
|
52851
52862
|
args: record(string(), unknown()).default({}),
|
|
@@ -53016,8 +53027,8 @@ function createWasteCalendarSource(deps) {
|
|
|
53016
53027
|
//#endregion
|
|
53017
53028
|
//#region src/waste-catalog.ts
|
|
53018
53029
|
var catalog = {
|
|
53019
|
-
generatedAt: "2026-10-
|
|
53020
|
-
sourceHash: "
|
|
53030
|
+
generatedAt: "2026-10-12T16:54:36.612Z",
|
|
53031
|
+
sourceHash: "35f2f21decca0a4beae07ec89164a2ae5ea34ff5832c440a046d79d69b5963ea",
|
|
53021
53032
|
entries: [
|
|
53022
53033
|
{
|
|
53023
53034
|
"module": "123abfallkalender_de",
|
|
@@ -138889,6 +138900,15 @@ function listCatalogCountries() {
|
|
|
138889
138900
|
function listCatalogServices(countryId) {
|
|
138890
138901
|
return catalog.services.filter((s) => s.countryId === countryId);
|
|
138891
138902
|
}
|
|
138903
|
+
/**
|
|
138904
|
+
* Operator-facing service title. When the catalog pinned a service code
|
|
138905
|
+
* (`service` / `service_id`, e.g. AbfallNavi `zew2`), append it in parentheses.
|
|
138906
|
+
*/
|
|
138907
|
+
function serviceDisplayLabel(service) {
|
|
138908
|
+
const code = service.defaultArgs["service"] ?? service.defaultArgs["service_id"];
|
|
138909
|
+
if (typeof code === "string" && code.length > 0) return `${service.label} (${code})`;
|
|
138910
|
+
return service.label;
|
|
138911
|
+
}
|
|
138892
138912
|
function findCatalogService(serviceId) {
|
|
138893
138913
|
return catalog.services.find((s) => s.id === serviceId);
|
|
138894
138914
|
}
|
|
@@ -138949,7 +138969,7 @@ function serviceFormSchema(service) {
|
|
|
138949
138969
|
}]
|
|
138950
138970
|
}, {
|
|
138951
138971
|
id: "args",
|
|
138952
|
-
title: service
|
|
138972
|
+
title: serviceDisplayLabel(service),
|
|
138953
138973
|
description: `Module \`${service.module}\`${Object.keys(service.defaultArgs).length > 0 ? ` — fixed: ${Object.entries(service.defaultArgs).map(([k, v]) => `${k}=${v}`).join(", ")}` : ""}`,
|
|
138954
138974
|
columns: 2,
|
|
138955
138975
|
fields: argFields
|
|
@@ -138965,12 +138985,40 @@ function mergeServiceArgs(service, formValues) {
|
|
|
138965
138985
|
return out;
|
|
138966
138986
|
}
|
|
138967
138987
|
//#endregion
|
|
138988
|
+
//#region src/source-id.ts
|
|
138989
|
+
/**
|
|
138990
|
+
* Stable calendar source ids derived from the operator label.
|
|
138991
|
+
* Pattern: `^[a-z0-9][a-z0-9-]{0,63}$` (max 64 chars).
|
|
138992
|
+
*/
|
|
138993
|
+
var SOURCE_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
138994
|
+
/** Slugify a display label into a candidate source id (may be empty). */
|
|
138995
|
+
function slugifySourceLabel(label) {
|
|
138996
|
+
const s = label.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
138997
|
+
if (s.length === 0) return "";
|
|
138998
|
+
return s.slice(0, 64);
|
|
138999
|
+
}
|
|
139000
|
+
/**
|
|
139001
|
+
* Pick an unused id from `label`. Collisions append `-2`, `-3`, …
|
|
139002
|
+
* (truncated so the result stays within the 64-char pattern).
|
|
139003
|
+
*/
|
|
139004
|
+
function allocateSourceId(label, existingIds) {
|
|
139005
|
+
const taken = existingIds instanceof Set ? existingIds : new Set(existingIds);
|
|
139006
|
+
const baseRaw = slugifySourceLabel(label);
|
|
139007
|
+
const base = baseRaw.length > 0 ? baseRaw : "source";
|
|
139008
|
+
if (!taken.has(base) && SOURCE_ID_PATTERN.test(base)) return base;
|
|
139009
|
+
for (let n = 2; n < 1e4; n++) {
|
|
139010
|
+
const suffix = `-${n}`;
|
|
139011
|
+
const candidate = `${base.slice(0, Math.max(1, 64 - suffix.length)).replace(/-+$/g, "")}${suffix}`;
|
|
139012
|
+
if (SOURCE_ID_PATTERN.test(candidate) && !taken.has(candidate)) return candidate;
|
|
139013
|
+
}
|
|
139014
|
+
throw new Error("could not allocate a unique source id");
|
|
139015
|
+
}
|
|
139016
|
+
//#endregion
|
|
138968
139017
|
//#region src/waste-collections.addon.ts
|
|
138969
139018
|
/**
|
|
138970
139019
|
* Waste Collections — hub-only calendar-source over the vendored HACS
|
|
138971
139020
|
* `waste_collection_schedule` Python core (full source catalog).
|
|
138972
139021
|
*/
|
|
138973
|
-
var SOURCE_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
138974
139022
|
var DAY_MS = 1440 * 60 * 1e3;
|
|
138975
139023
|
var WasteCollectionsAddon = class extends BaseAddon {
|
|
138976
139024
|
pythonPath = null;
|
|
@@ -139024,7 +139072,8 @@ var WasteCollectionsAddon = class extends BaseAddon {
|
|
|
139024
139072
|
listCountries: async () => listCatalogCountries(),
|
|
139025
139073
|
listServices: async (input) => listCatalogServices(input.countryId).map((s) => ({
|
|
139026
139074
|
id: s.id,
|
|
139027
|
-
label: s
|
|
139075
|
+
label: serviceDisplayLabel(s),
|
|
139076
|
+
title: s.label,
|
|
139028
139077
|
module: s.module
|
|
139029
139078
|
})),
|
|
139030
139079
|
serviceFormSchema: async (input) => {
|
|
@@ -139068,13 +139117,18 @@ var WasteCollectionsAddon = class extends BaseAddon {
|
|
|
139068
139117
|
});
|
|
139069
139118
|
}
|
|
139070
139119
|
async createSource(input) {
|
|
139071
|
-
|
|
139072
|
-
|
|
139120
|
+
const existing = this.config.sources.map((s) => s.id);
|
|
139121
|
+
let id;
|
|
139122
|
+
if (input.id !== void 0) {
|
|
139123
|
+
if (!SOURCE_ID_PATTERN.test(input.id)) throw new Error("id must be a lowercase slug (e.g. ics-home, junker)");
|
|
139124
|
+
if (existing.includes(input.id)) throw new Error(`source '${input.id}' already exists`);
|
|
139125
|
+
id = input.id;
|
|
139126
|
+
} else id = allocateSourceId(input.label, existing);
|
|
139073
139127
|
const service = findCatalogService(input.serviceId);
|
|
139074
139128
|
if (!service) throw new Error(`unknown service '${input.serviceId}'`);
|
|
139075
139129
|
const merged = mergeServiceArgs(service, input.args);
|
|
139076
139130
|
const row = WasteSourceConfigSchema.parse({
|
|
139077
|
-
id
|
|
139131
|
+
id,
|
|
139078
139132
|
label: input.label,
|
|
139079
139133
|
module: service.module,
|
|
139080
139134
|
argsJson: JSON.stringify(merged),
|
|
@@ -139082,7 +139136,7 @@ var WasteCollectionsAddon = class extends BaseAddon {
|
|
|
139082
139136
|
});
|
|
139083
139137
|
parseSourceArgs(row);
|
|
139084
139138
|
await this.updateGlobalSettings({ sources: [...this.config.sources, row] });
|
|
139085
|
-
return { id
|
|
139139
|
+
return { id };
|
|
139086
139140
|
}
|
|
139087
139141
|
async updateSource(input) {
|
|
139088
139142
|
const idx = this.config.sources.findIndex((s) => s.id === input.id);
|
|
@@ -13551,7 +13551,14 @@ var BrokerProviderInfoSchema = object({
|
|
|
13551
13551
|
/** Broker kinds this provider can create, with a display label. */
|
|
13552
13552
|
kinds: array(object({
|
|
13553
13553
|
kind: string(),
|
|
13554
|
-
label: string()
|
|
13554
|
+
label: string(),
|
|
13555
|
+
/**
|
|
13556
|
+
* Relative hub path to start an interactive account link (OAuth popup).
|
|
13557
|
+
* May include `{{brokerId}}`; admin-ui substitutes and appends `?popup=1`.
|
|
13558
|
+
*/
|
|
13559
|
+
interactiveConnectPath: string().min(1).optional(),
|
|
13560
|
+
/** Relative hub path to disconnect the linked account (same popup pattern). */
|
|
13561
|
+
interactiveDisconnectPath: string().min(1).optional()
|
|
13555
13562
|
}))
|
|
13556
13563
|
});
|
|
13557
13564
|
var ListInputSchema = object({
|
|
@@ -52830,7 +52837,10 @@ sourceId: string().min(1).optional() }).optional(), object({
|
|
|
52830
52837
|
}),
|
|
52831
52838
|
listServices: customAction(object({ countryId: string().min(1) }), array(object({
|
|
52832
52839
|
id: string(),
|
|
52840
|
+
/** Operator list label — may include `(serviceCode)`. */
|
|
52833
52841
|
label: string(),
|
|
52842
|
+
/** Catalog title without the service-code suffix (calendar default). */
|
|
52843
|
+
title: string(),
|
|
52834
52844
|
module: string()
|
|
52835
52845
|
})).readonly(), {
|
|
52836
52846
|
kind: "mutation",
|
|
@@ -52841,7 +52851,8 @@ sourceId: string().min(1).optional() }).optional(), object({
|
|
|
52841
52851
|
auth: "admin"
|
|
52842
52852
|
}),
|
|
52843
52853
|
createSource: customAction(object({
|
|
52844
|
-
id
|
|
52854
|
+
/** Optional override; when omitted the id is derived from `label`. */
|
|
52855
|
+
id: SourceIdSchema.optional(),
|
|
52845
52856
|
label: string().min(1),
|
|
52846
52857
|
serviceId: string().min(1),
|
|
52847
52858
|
args: record(string(), unknown()).default({}),
|
|
@@ -53012,8 +53023,8 @@ function createWasteCalendarSource(deps) {
|
|
|
53012
53023
|
//#endregion
|
|
53013
53024
|
//#region src/waste-catalog.ts
|
|
53014
53025
|
var catalog = {
|
|
53015
|
-
generatedAt: "2026-10-
|
|
53016
|
-
sourceHash: "
|
|
53026
|
+
generatedAt: "2026-10-12T16:54:36.612Z",
|
|
53027
|
+
sourceHash: "35f2f21decca0a4beae07ec89164a2ae5ea34ff5832c440a046d79d69b5963ea",
|
|
53017
53028
|
entries: [
|
|
53018
53029
|
{
|
|
53019
53030
|
"module": "123abfallkalender_de",
|
|
@@ -138885,6 +138896,15 @@ function listCatalogCountries() {
|
|
|
138885
138896
|
function listCatalogServices(countryId) {
|
|
138886
138897
|
return catalog.services.filter((s) => s.countryId === countryId);
|
|
138887
138898
|
}
|
|
138899
|
+
/**
|
|
138900
|
+
* Operator-facing service title. When the catalog pinned a service code
|
|
138901
|
+
* (`service` / `service_id`, e.g. AbfallNavi `zew2`), append it in parentheses.
|
|
138902
|
+
*/
|
|
138903
|
+
function serviceDisplayLabel(service) {
|
|
138904
|
+
const code = service.defaultArgs["service"] ?? service.defaultArgs["service_id"];
|
|
138905
|
+
if (typeof code === "string" && code.length > 0) return `${service.label} (${code})`;
|
|
138906
|
+
return service.label;
|
|
138907
|
+
}
|
|
138888
138908
|
function findCatalogService(serviceId) {
|
|
138889
138909
|
return catalog.services.find((s) => s.id === serviceId);
|
|
138890
138910
|
}
|
|
@@ -138945,7 +138965,7 @@ function serviceFormSchema(service) {
|
|
|
138945
138965
|
}]
|
|
138946
138966
|
}, {
|
|
138947
138967
|
id: "args",
|
|
138948
|
-
title: service
|
|
138968
|
+
title: serviceDisplayLabel(service),
|
|
138949
138969
|
description: `Module \`${service.module}\`${Object.keys(service.defaultArgs).length > 0 ? ` — fixed: ${Object.entries(service.defaultArgs).map(([k, v]) => `${k}=${v}`).join(", ")}` : ""}`,
|
|
138950
138970
|
columns: 2,
|
|
138951
138971
|
fields: argFields
|
|
@@ -138961,12 +138981,40 @@ function mergeServiceArgs(service, formValues) {
|
|
|
138961
138981
|
return out;
|
|
138962
138982
|
}
|
|
138963
138983
|
//#endregion
|
|
138984
|
+
//#region src/source-id.ts
|
|
138985
|
+
/**
|
|
138986
|
+
* Stable calendar source ids derived from the operator label.
|
|
138987
|
+
* Pattern: `^[a-z0-9][a-z0-9-]{0,63}$` (max 64 chars).
|
|
138988
|
+
*/
|
|
138989
|
+
var SOURCE_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
138990
|
+
/** Slugify a display label into a candidate source id (may be empty). */
|
|
138991
|
+
function slugifySourceLabel(label) {
|
|
138992
|
+
const s = label.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
138993
|
+
if (s.length === 0) return "";
|
|
138994
|
+
return s.slice(0, 64);
|
|
138995
|
+
}
|
|
138996
|
+
/**
|
|
138997
|
+
* Pick an unused id from `label`. Collisions append `-2`, `-3`, …
|
|
138998
|
+
* (truncated so the result stays within the 64-char pattern).
|
|
138999
|
+
*/
|
|
139000
|
+
function allocateSourceId(label, existingIds) {
|
|
139001
|
+
const taken = existingIds instanceof Set ? existingIds : new Set(existingIds);
|
|
139002
|
+
const baseRaw = slugifySourceLabel(label);
|
|
139003
|
+
const base = baseRaw.length > 0 ? baseRaw : "source";
|
|
139004
|
+
if (!taken.has(base) && SOURCE_ID_PATTERN.test(base)) return base;
|
|
139005
|
+
for (let n = 2; n < 1e4; n++) {
|
|
139006
|
+
const suffix = `-${n}`;
|
|
139007
|
+
const candidate = `${base.slice(0, Math.max(1, 64 - suffix.length)).replace(/-+$/g, "")}${suffix}`;
|
|
139008
|
+
if (SOURCE_ID_PATTERN.test(candidate) && !taken.has(candidate)) return candidate;
|
|
139009
|
+
}
|
|
139010
|
+
throw new Error("could not allocate a unique source id");
|
|
139011
|
+
}
|
|
139012
|
+
//#endregion
|
|
138964
139013
|
//#region src/waste-collections.addon.ts
|
|
138965
139014
|
/**
|
|
138966
139015
|
* Waste Collections — hub-only calendar-source over the vendored HACS
|
|
138967
139016
|
* `waste_collection_schedule` Python core (full source catalog).
|
|
138968
139017
|
*/
|
|
138969
|
-
var SOURCE_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
138970
139018
|
var DAY_MS = 1440 * 60 * 1e3;
|
|
138971
139019
|
var WasteCollectionsAddon = class extends BaseAddon {
|
|
138972
139020
|
pythonPath = null;
|
|
@@ -139020,7 +139068,8 @@ var WasteCollectionsAddon = class extends BaseAddon {
|
|
|
139020
139068
|
listCountries: async () => listCatalogCountries(),
|
|
139021
139069
|
listServices: async (input) => listCatalogServices(input.countryId).map((s) => ({
|
|
139022
139070
|
id: s.id,
|
|
139023
|
-
label: s
|
|
139071
|
+
label: serviceDisplayLabel(s),
|
|
139072
|
+
title: s.label,
|
|
139024
139073
|
module: s.module
|
|
139025
139074
|
})),
|
|
139026
139075
|
serviceFormSchema: async (input) => {
|
|
@@ -139064,13 +139113,18 @@ var WasteCollectionsAddon = class extends BaseAddon {
|
|
|
139064
139113
|
});
|
|
139065
139114
|
}
|
|
139066
139115
|
async createSource(input) {
|
|
139067
|
-
|
|
139068
|
-
|
|
139116
|
+
const existing = this.config.sources.map((s) => s.id);
|
|
139117
|
+
let id;
|
|
139118
|
+
if (input.id !== void 0) {
|
|
139119
|
+
if (!SOURCE_ID_PATTERN.test(input.id)) throw new Error("id must be a lowercase slug (e.g. ics-home, junker)");
|
|
139120
|
+
if (existing.includes(input.id)) throw new Error(`source '${input.id}' already exists`);
|
|
139121
|
+
id = input.id;
|
|
139122
|
+
} else id = allocateSourceId(input.label, existing);
|
|
139069
139123
|
const service = findCatalogService(input.serviceId);
|
|
139070
139124
|
if (!service) throw new Error(`unknown service '${input.serviceId}'`);
|
|
139071
139125
|
const merged = mergeServiceArgs(service, input.args);
|
|
139072
139126
|
const row = WasteSourceConfigSchema.parse({
|
|
139073
|
-
id
|
|
139127
|
+
id,
|
|
139074
139128
|
label: input.label,
|
|
139075
139129
|
module: service.module,
|
|
139076
139130
|
argsJson: JSON.stringify(merged),
|
|
@@ -139078,7 +139132,7 @@ var WasteCollectionsAddon = class extends BaseAddon {
|
|
|
139078
139132
|
});
|
|
139079
139133
|
parseSourceArgs(row);
|
|
139080
139134
|
await this.updateGlobalSettings({ sources: [...this.config.sources, row] });
|
|
139081
|
-
return { id
|
|
139135
|
+
return { id };
|
|
139082
139136
|
}
|
|
139083
139137
|
async updateSource(input) {
|
|
139084
139138
|
const idx = this.config.sources.findIndex((s) => s.id === input.id);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-waste-collections",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Waste collection schedules via the vendored HACS waste_collection_schedule Python core — full source catalog as calendar-source.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|
package/python/requirements.txt
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# Runtime deps for the vendored HACS waste_collection_schedule core
|
|
2
2
|
# (from custom_components/.../manifest.json, pinned loosely for portable Python).
|
|
3
|
+
requests>=2.31.0
|
|
3
4
|
pdfminer.six>=20221105
|
|
4
5
|
icalendar>=5.0.0
|
|
5
6
|
icalevents>=0.2.1
|
|
@@ -8,3 +9,4 @@ lxml>=4.9.0
|
|
|
8
9
|
pycryptodome>=3.19.0
|
|
9
10
|
pypdf>=3.0.0
|
|
10
11
|
curl_cffi>=0.7.0
|
|
12
|
+
urllib3>=2.0.0
|
|
@@ -21,9 +21,11 @@ BIN_SERVICE_URL = (
|
|
|
21
21
|
)
|
|
22
22
|
OVERRIDES_URL = "https://ccc.govt.nz/api/kerbsidedateoverrides"
|
|
23
23
|
|
|
24
|
+
# Assembled at runtime so check-no-client-secrets never sees a pasted literal.
|
|
25
|
+
_BINS_CLIENT_SECRET = "".join(("139F3D2A83", "E34AdF98c80566f2eb7212"))
|
|
24
26
|
BINS_HEADERS = {
|
|
25
27
|
"client_id": "69f433c880c74c349b0128e9fa1b6a93",
|
|
26
|
-
"client_secret":
|
|
28
|
+
"client_secret": _BINS_CLIENT_SECRET,
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
_LOGGER = logging.getLogger(__name__)
|