@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: SourceIdSchema,
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-12T14:56:10.997Z",
53020
- sourceHash: "52b07b8f76cb92078fdac8448f2c9bd07a3f795e8aa528f082d5b6a3395cc863",
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.label,
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.label,
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
- if (!SOURCE_ID_PATTERN.test(input.id)) throw new Error("id must be a lowercase slug (e.g. ics-home, junker)");
139072
- if (this.config.sources.some((s) => s.id === input.id)) throw new Error(`source '${input.id}' already exists`);
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: input.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: input.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: SourceIdSchema,
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-12T14:56:10.997Z",
53016
- sourceHash: "52b07b8f76cb92078fdac8448f2c9bd07a3f795e8aa528f082d5b6a3395cc863",
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.label,
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.label,
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
- if (!SOURCE_ID_PATTERN.test(input.id)) throw new Error("id must be a lowercase slug (e.g. ics-home, junker)");
139068
- if (this.config.sources.some((s) => s.id === input.id)) throw new Error(`source '${input.id}' already exists`);
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: input.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: input.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.4",
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",
@@ -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": "139F3D2A83E34AdF98c80566f2eb7212",
28
+ "client_secret": _BINS_CLIENT_SECRET,
27
29
  }
28
30
 
29
31
  _LOGGER = logging.getLogger(__name__)