@camstack/addon-import-alexa 0.2.171 → 0.2.172
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/dist/addon.js +83 -40
- package/dist/addon.mjs +83 -40
- package/i18n/en.json +0 -1
- package/i18n/it.json +0 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.172] - 2026-10-31
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Alexa shows an error instead of 'connected' when polling Amazon keeps failing, with the HTTP status in the log.
|
|
15
|
+
- Alexa: quiet vs announce is now decided per notification (a silent notification is spoken quietly, any other announces); the per-target Mode setting is gone and a stored one is ignored.
|
|
16
|
+
|
|
10
17
|
## [0.2.171] - 2026-10-30
|
|
11
18
|
|
|
12
19
|
### Changed
|
package/dist/addon.js
CHANGED
|
@@ -137148,7 +137148,21 @@ function isRateLimitError(message) {
|
|
|
137148
137148
|
const m = message.toLowerCase();
|
|
137149
137149
|
return m.includes("rate exceeded") || m.includes("throttl") || m.includes("too many requests") || m.includes("429");
|
|
137150
137150
|
}
|
|
137151
|
+
/** HTTP statuses that mean Amazon rejected the session (a re-link is needed). */
|
|
137152
|
+
var SESSION_DEAD_STATUSES = new Set([401, 403]);
|
|
137153
|
+
/** The library logs `No body (code=NNN)` on a body-less non-2xx; it is the only place the status shows. */
|
|
137154
|
+
var NO_BODY_CODE = /No body \(code=(\d{3})\)/;
|
|
137155
|
+
/** A failed Amazon call whose HTTP status is known (null when the library did not say). */
|
|
137156
|
+
var AlexaHttpError = class extends Error {
|
|
137157
|
+
statusCode;
|
|
137158
|
+
constructor(message, statusCode) {
|
|
137159
|
+
super(message);
|
|
137160
|
+
this.name = "AlexaHttpError";
|
|
137161
|
+
this.statusCode = statusCode;
|
|
137162
|
+
}
|
|
137163
|
+
};
|
|
137151
137164
|
var AlexaRemoteClient = class {
|
|
137165
|
+
#lastHttpStatus = null;
|
|
137152
137166
|
#options;
|
|
137153
137167
|
#logger;
|
|
137154
137168
|
#dataDir;
|
|
@@ -137168,7 +137182,10 @@ var AlexaRemoteClient = class {
|
|
|
137168
137182
|
async connect() {
|
|
137169
137183
|
const remote = new import_alexa_remote.default();
|
|
137170
137184
|
this.#remote = remote;
|
|
137171
|
-
const initOptions =
|
|
137185
|
+
const initOptions = {
|
|
137186
|
+
...await this.#buildInitOptions(),
|
|
137187
|
+
logger: (...args) => this.#captureLibraryLine(args)
|
|
137188
|
+
};
|
|
137172
137189
|
remote.on("cookie", () => {
|
|
137173
137190
|
this.#persistSession(remote);
|
|
137174
137191
|
});
|
|
@@ -137262,11 +137279,17 @@ var AlexaRemoteClient = class {
|
|
|
137262
137279
|
async #withBackoff(op, run) {
|
|
137263
137280
|
let lastError = null;
|
|
137264
137281
|
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) try {
|
|
137282
|
+
this.#lastHttpStatus = null;
|
|
137265
137283
|
return await run();
|
|
137266
137284
|
} catch (err) {
|
|
137267
137285
|
lastError = err;
|
|
137268
137286
|
const message = errMsg(err);
|
|
137269
137287
|
if (isAuthError(message)) throw new AlexaAuthError(message);
|
|
137288
|
+
if (message.toLowerCase().includes("no body")) {
|
|
137289
|
+
const status = this.#lastHttpStatus;
|
|
137290
|
+
if (status !== null && SESSION_DEAD_STATUSES.has(status)) throw new AlexaAuthError(`${message} (HTTP ${status})`);
|
|
137291
|
+
throw new AlexaHttpError(message, status);
|
|
137292
|
+
}
|
|
137270
137293
|
if (!isRateLimitError(message) || attempt === MAX_ATTEMPTS - 1) throw err;
|
|
137271
137294
|
const delay = this.#backoffDelay(attempt);
|
|
137272
137295
|
this.#logger?.warn("AlexaRemoteClient: rate-limited — backing off", { meta: {
|
|
@@ -137279,6 +137302,10 @@ var AlexaRemoteClient = class {
|
|
|
137279
137302
|
}
|
|
137280
137303
|
throw lastError instanceof Error ? lastError : /* @__PURE__ */ new Error(`AlexaRemoteClient: ${op} failed`);
|
|
137281
137304
|
}
|
|
137305
|
+
#captureLibraryLine(args) {
|
|
137306
|
+
const match = NO_BODY_CODE.exec(args.map(String).join(" "));
|
|
137307
|
+
if (match?.[1] !== void 0) this.#lastHttpStatus = Number(match[1]);
|
|
137308
|
+
}
|
|
137282
137309
|
#backoffDelay(attempt) {
|
|
137283
137310
|
return Math.min(BACKOFF_BASE_MS * 2 ** attempt, BACKOFF_MAX_MS) + Math.floor(Math.random() * BACKOFF_BASE_MS);
|
|
137284
137311
|
}
|
|
@@ -137517,8 +137544,6 @@ var AlexaCommandSink = class {
|
|
|
137517
137544
|
};
|
|
137518
137545
|
/** The single in-process command sink shared across the addon. */
|
|
137519
137546
|
var alexaCommands = new AlexaCommandSink();
|
|
137520
|
-
//#endregion
|
|
137521
|
-
//#region src/alexa-integration-manager.ts
|
|
137522
137547
|
/**
|
|
137523
137548
|
* Wraps exactly one {@link AlexaClient} (= one Amazon account). Authenticates on
|
|
137524
137549
|
* `start()`, lists the appliance inventory, then runs a POLL loop (Alexa has no
|
|
@@ -137546,6 +137571,7 @@ var AlexaIntegrationManager = class {
|
|
|
137546
137571
|
#client = null;
|
|
137547
137572
|
#pollTimer = null;
|
|
137548
137573
|
#status = "disconnected";
|
|
137574
|
+
#pollFailures = 0;
|
|
137549
137575
|
#applianceCount = 0;
|
|
137550
137576
|
#lastCheckedAt = null;
|
|
137551
137577
|
#error = null;
|
|
@@ -137650,14 +137676,37 @@ var AlexaIntegrationManager = class {
|
|
|
137650
137676
|
alexaAccounts.recordStates(this.#id, values);
|
|
137651
137677
|
this.#lastCheckedAt = Date.now();
|
|
137652
137678
|
this.#onStatesPolled(this.#id, values);
|
|
137679
|
+
this.#pollFailures = 0;
|
|
137680
|
+
this.#error = null;
|
|
137681
|
+
if (this.#status === "error" || this.#status === "auth-failed") this.#transition("connected");
|
|
137653
137682
|
} catch (err) {
|
|
137654
137683
|
this.#error = errMsg(err);
|
|
137684
|
+
this.#pollFailures += 1;
|
|
137685
|
+
const httpStatus = err instanceof AlexaHttpError ? err.statusCode : null;
|
|
137655
137686
|
this.#logger.warn("AlexaIntegrationManager: poll failed", {
|
|
137656
137687
|
tags: { brokerId: this.#id },
|
|
137657
|
-
meta: {
|
|
137688
|
+
meta: {
|
|
137689
|
+
error: errMsg(err),
|
|
137690
|
+
consecutiveFailures: this.#pollFailures,
|
|
137691
|
+
httpStatus
|
|
137692
|
+
}
|
|
137658
137693
|
});
|
|
137694
|
+
if (this.#pollFailures >= 3 && this.#status !== "disconnected") this.#transition(err instanceof AlexaAuthError ? "auth-failed" : "error");
|
|
137659
137695
|
}
|
|
137660
137696
|
}
|
|
137697
|
+
#transition(next) {
|
|
137698
|
+
if (this.#status === next) return;
|
|
137699
|
+
const previous = this.#status;
|
|
137700
|
+
this.#status = next;
|
|
137701
|
+
this.#logger.info("AlexaIntegrationManager: status changed", {
|
|
137702
|
+
tags: { brokerId: this.#id },
|
|
137703
|
+
meta: {
|
|
137704
|
+
from: previous,
|
|
137705
|
+
to: next,
|
|
137706
|
+
error: this.#error
|
|
137707
|
+
}
|
|
137708
|
+
});
|
|
137709
|
+
}
|
|
137661
137710
|
/** Stop the poll loop, disconnect the client (FIRST), null it. */
|
|
137662
137711
|
async stop() {
|
|
137663
137712
|
if (this.#pollTimer !== null) {
|
|
@@ -139313,9 +139362,16 @@ function readString(config, key) {
|
|
|
139313
139362
|
const value = config[key];
|
|
139314
139363
|
return typeof value === "string" ? value : "";
|
|
139315
139364
|
}
|
|
139316
|
-
/** The
|
|
139317
|
-
|
|
139318
|
-
|
|
139365
|
+
/** The level id an Echo notification uses for "quiet: no chime, no announcement". */
|
|
139366
|
+
var SILENT_LEVEL_ID = "silent";
|
|
139367
|
+
var SILENT_PRIORITY = 1;
|
|
139368
|
+
/**
|
|
139369
|
+
* Quiet or announce is decided PER NOTIFICATION, never per target: a silent
|
|
139370
|
+
* notification (the kind's silent level, or the lowest priority when no level
|
|
139371
|
+
* is named) is spoken quietly, hours-aware; every other one announces.
|
|
139372
|
+
*/
|
|
139373
|
+
function modeFor(notification) {
|
|
139374
|
+
return (notification.level !== void 0 ? notification.level === SILENT_LEVEL_ID : notification.priority <= SILENT_PRIORITY) ? "speak" : "announce";
|
|
139319
139375
|
}
|
|
139320
139376
|
function readVolume(config, delivery) {
|
|
139321
139377
|
const ruled = delivery?.["volume"];
|
|
@@ -139326,21 +139382,9 @@ function readVolume(config, delivery) {
|
|
|
139326
139382
|
/**
|
|
139327
139383
|
* The delivery parameters a rule may set for an Echo target (D938). The
|
|
139328
139384
|
* target's own config holds the default; these override it per notification.
|
|
139385
|
+
* Quiet vs announce is not one of them: it follows the notification's level.
|
|
139329
139386
|
*/
|
|
139330
139387
|
var DELIVERY_PARAMS = [{
|
|
139331
|
-
key: "mode",
|
|
139332
|
-
label: "Mode",
|
|
139333
|
-
labelKey: "import-alexa:echoTarget.s_echo.mode.label",
|
|
139334
|
-
type: "enum",
|
|
139335
|
-
options: [{
|
|
139336
|
-
value: "speak",
|
|
139337
|
-
label: "Speak (quiet hours apply)"
|
|
139338
|
-
}, {
|
|
139339
|
-
value: "announce",
|
|
139340
|
-
label: "Announce (chime, then speech)"
|
|
139341
|
-
}],
|
|
139342
|
-
default: "speak"
|
|
139343
|
-
}, {
|
|
139344
139388
|
key: "volume",
|
|
139345
139389
|
label: "Volume",
|
|
139346
139390
|
labelKey: "import-alexa:echoTarget.s_echo.volume.label",
|
|
@@ -139391,20 +139435,6 @@ function configSchema() {
|
|
|
139391
139435
|
dependsOn: ["brokerId"]
|
|
139392
139436
|
}
|
|
139393
139437
|
}],
|
|
139394
|
-
{
|
|
139395
|
-
type: "select",
|
|
139396
|
-
key: "mode",
|
|
139397
|
-
label: "Mode",
|
|
139398
|
-
labelKey: "import-alexa:echoTarget.s_echo.mode.label",
|
|
139399
|
-
default: "speak",
|
|
139400
|
-
options: [{
|
|
139401
|
-
value: "speak",
|
|
139402
|
-
label: "Speak (quiet hours apply)"
|
|
139403
|
-
}, {
|
|
139404
|
-
value: "announce",
|
|
139405
|
-
label: "Announce (chime, then speech)"
|
|
139406
|
-
}]
|
|
139407
|
-
},
|
|
139408
139438
|
{
|
|
139409
139439
|
type: "number",
|
|
139410
139440
|
key: "volume",
|
|
@@ -139446,9 +139476,16 @@ function alexaTargetKind() {
|
|
|
139446
139476
|
actions: 0,
|
|
139447
139477
|
actionIcons: false,
|
|
139448
139478
|
levels: [{
|
|
139479
|
+
id: SILENT_LEVEL_ID,
|
|
139480
|
+
label: "Silent",
|
|
139481
|
+
ordinal: SILENT_PRIORITY,
|
|
139482
|
+
flags: { silent: true },
|
|
139483
|
+
description: "Spoken quietly: no chime, quiet hours apply."
|
|
139484
|
+
}, {
|
|
139449
139485
|
id: "default",
|
|
139450
|
-
label: "
|
|
139451
|
-
ordinal: 3
|
|
139486
|
+
label: "Announce",
|
|
139487
|
+
ordinal: 3,
|
|
139488
|
+
description: "Announced: chime, then speech."
|
|
139452
139489
|
}],
|
|
139453
139490
|
format: ["text"],
|
|
139454
139491
|
clickUrl: false,
|
|
@@ -139465,7 +139502,6 @@ function discoveredFrom(broker, device) {
|
|
|
139465
139502
|
config: {
|
|
139466
139503
|
brokerId: broker.id,
|
|
139467
139504
|
echoId: device.id,
|
|
139468
|
-
mode: "speak",
|
|
139469
139505
|
volume: 40,
|
|
139470
139506
|
respectDnd: true,
|
|
139471
139507
|
family: device.family,
|
|
@@ -139478,6 +139514,7 @@ function createAlexaNotificationProvider(ports) {
|
|
|
139478
139514
|
let targets = ports.loadTargets();
|
|
139479
139515
|
let ledger = emptySpeechLedger();
|
|
139480
139516
|
let deliveries = [];
|
|
139517
|
+
const warnedLegacyMode = /* @__PURE__ */ new Set();
|
|
139481
139518
|
function record(row) {
|
|
139482
139519
|
deliveries = [...deliveries, row];
|
|
139483
139520
|
}
|
|
@@ -139619,7 +139656,14 @@ function createAlexaNotificationProvider(ports) {
|
|
|
139619
139656
|
renderedAs: speech.renderedAs
|
|
139620
139657
|
};
|
|
139621
139658
|
}
|
|
139622
|
-
const mode =
|
|
139659
|
+
const mode = modeFor(notification);
|
|
139660
|
+
if (target.config["mode"] !== void 0 && !warnedLegacyMode.has(target.id)) {
|
|
139661
|
+
warnedLegacyMode.add(target.id);
|
|
139662
|
+
logger.warn("alexa target mode is ignored: quiet vs announce follows each notification", {
|
|
139663
|
+
...tags,
|
|
139664
|
+
meta: { targetId: target.id }
|
|
139665
|
+
});
|
|
139666
|
+
}
|
|
139623
139667
|
try {
|
|
139624
139668
|
const outcomes = await ports.speak({
|
|
139625
139669
|
brokerId: broker.id,
|
|
@@ -140049,7 +140093,6 @@ function targetsFrom(rows) {
|
|
|
140049
140093
|
config: {
|
|
140050
140094
|
...row.brokerId.length > 0 ? { brokerId: row.brokerId } : {},
|
|
140051
140095
|
echoId: row.echoId,
|
|
140052
|
-
mode: row.mode,
|
|
140053
140096
|
volume: row.volume,
|
|
140054
140097
|
respectDnd: row.respectDnd
|
|
140055
140098
|
}
|
|
@@ -140062,7 +140105,7 @@ function rowsFrom(targets) {
|
|
|
140062
140105
|
enabled: target.enabled,
|
|
140063
140106
|
brokerId: typeof target.config["brokerId"] === "string" ? target.config["brokerId"] : "",
|
|
140064
140107
|
echoId: typeof target.config["echoId"] === "string" ? target.config["echoId"] : "",
|
|
140065
|
-
mode:
|
|
140108
|
+
mode: "speak",
|
|
140066
140109
|
volume: typeof target.config["volume"] === "number" ? target.config["volume"] : 40,
|
|
140067
140110
|
respectDnd: target.config["respectDnd"] !== false
|
|
140068
140111
|
}));
|
package/dist/addon.mjs
CHANGED
|
@@ -137146,7 +137146,21 @@ function isRateLimitError(message) {
|
|
|
137146
137146
|
const m = message.toLowerCase();
|
|
137147
137147
|
return m.includes("rate exceeded") || m.includes("throttl") || m.includes("too many requests") || m.includes("429");
|
|
137148
137148
|
}
|
|
137149
|
+
/** HTTP statuses that mean Amazon rejected the session (a re-link is needed). */
|
|
137150
|
+
var SESSION_DEAD_STATUSES = new Set([401, 403]);
|
|
137151
|
+
/** The library logs `No body (code=NNN)` on a body-less non-2xx; it is the only place the status shows. */
|
|
137152
|
+
var NO_BODY_CODE = /No body \(code=(\d{3})\)/;
|
|
137153
|
+
/** A failed Amazon call whose HTTP status is known (null when the library did not say). */
|
|
137154
|
+
var AlexaHttpError = class extends Error {
|
|
137155
|
+
statusCode;
|
|
137156
|
+
constructor(message, statusCode) {
|
|
137157
|
+
super(message);
|
|
137158
|
+
this.name = "AlexaHttpError";
|
|
137159
|
+
this.statusCode = statusCode;
|
|
137160
|
+
}
|
|
137161
|
+
};
|
|
137149
137162
|
var AlexaRemoteClient = class {
|
|
137163
|
+
#lastHttpStatus = null;
|
|
137150
137164
|
#options;
|
|
137151
137165
|
#logger;
|
|
137152
137166
|
#dataDir;
|
|
@@ -137166,7 +137180,10 @@ var AlexaRemoteClient = class {
|
|
|
137166
137180
|
async connect() {
|
|
137167
137181
|
const remote = new import_alexa_remote.default();
|
|
137168
137182
|
this.#remote = remote;
|
|
137169
|
-
const initOptions =
|
|
137183
|
+
const initOptions = {
|
|
137184
|
+
...await this.#buildInitOptions(),
|
|
137185
|
+
logger: (...args) => this.#captureLibraryLine(args)
|
|
137186
|
+
};
|
|
137170
137187
|
remote.on("cookie", () => {
|
|
137171
137188
|
this.#persistSession(remote);
|
|
137172
137189
|
});
|
|
@@ -137260,11 +137277,17 @@ var AlexaRemoteClient = class {
|
|
|
137260
137277
|
async #withBackoff(op, run) {
|
|
137261
137278
|
let lastError = null;
|
|
137262
137279
|
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) try {
|
|
137280
|
+
this.#lastHttpStatus = null;
|
|
137263
137281
|
return await run();
|
|
137264
137282
|
} catch (err) {
|
|
137265
137283
|
lastError = err;
|
|
137266
137284
|
const message = errMsg(err);
|
|
137267
137285
|
if (isAuthError(message)) throw new AlexaAuthError(message);
|
|
137286
|
+
if (message.toLowerCase().includes("no body")) {
|
|
137287
|
+
const status = this.#lastHttpStatus;
|
|
137288
|
+
if (status !== null && SESSION_DEAD_STATUSES.has(status)) throw new AlexaAuthError(`${message} (HTTP ${status})`);
|
|
137289
|
+
throw new AlexaHttpError(message, status);
|
|
137290
|
+
}
|
|
137268
137291
|
if (!isRateLimitError(message) || attempt === MAX_ATTEMPTS - 1) throw err;
|
|
137269
137292
|
const delay = this.#backoffDelay(attempt);
|
|
137270
137293
|
this.#logger?.warn("AlexaRemoteClient: rate-limited — backing off", { meta: {
|
|
@@ -137277,6 +137300,10 @@ var AlexaRemoteClient = class {
|
|
|
137277
137300
|
}
|
|
137278
137301
|
throw lastError instanceof Error ? lastError : /* @__PURE__ */ new Error(`AlexaRemoteClient: ${op} failed`);
|
|
137279
137302
|
}
|
|
137303
|
+
#captureLibraryLine(args) {
|
|
137304
|
+
const match = NO_BODY_CODE.exec(args.map(String).join(" "));
|
|
137305
|
+
if (match?.[1] !== void 0) this.#lastHttpStatus = Number(match[1]);
|
|
137306
|
+
}
|
|
137280
137307
|
#backoffDelay(attempt) {
|
|
137281
137308
|
return Math.min(BACKOFF_BASE_MS * 2 ** attempt, BACKOFF_MAX_MS) + Math.floor(Math.random() * BACKOFF_BASE_MS);
|
|
137282
137309
|
}
|
|
@@ -137515,8 +137542,6 @@ var AlexaCommandSink = class {
|
|
|
137515
137542
|
};
|
|
137516
137543
|
/** The single in-process command sink shared across the addon. */
|
|
137517
137544
|
var alexaCommands = new AlexaCommandSink();
|
|
137518
|
-
//#endregion
|
|
137519
|
-
//#region src/alexa-integration-manager.ts
|
|
137520
137545
|
/**
|
|
137521
137546
|
* Wraps exactly one {@link AlexaClient} (= one Amazon account). Authenticates on
|
|
137522
137547
|
* `start()`, lists the appliance inventory, then runs a POLL loop (Alexa has no
|
|
@@ -137544,6 +137569,7 @@ var AlexaIntegrationManager = class {
|
|
|
137544
137569
|
#client = null;
|
|
137545
137570
|
#pollTimer = null;
|
|
137546
137571
|
#status = "disconnected";
|
|
137572
|
+
#pollFailures = 0;
|
|
137547
137573
|
#applianceCount = 0;
|
|
137548
137574
|
#lastCheckedAt = null;
|
|
137549
137575
|
#error = null;
|
|
@@ -137648,14 +137674,37 @@ var AlexaIntegrationManager = class {
|
|
|
137648
137674
|
alexaAccounts.recordStates(this.#id, values);
|
|
137649
137675
|
this.#lastCheckedAt = Date.now();
|
|
137650
137676
|
this.#onStatesPolled(this.#id, values);
|
|
137677
|
+
this.#pollFailures = 0;
|
|
137678
|
+
this.#error = null;
|
|
137679
|
+
if (this.#status === "error" || this.#status === "auth-failed") this.#transition("connected");
|
|
137651
137680
|
} catch (err) {
|
|
137652
137681
|
this.#error = errMsg(err);
|
|
137682
|
+
this.#pollFailures += 1;
|
|
137683
|
+
const httpStatus = err instanceof AlexaHttpError ? err.statusCode : null;
|
|
137653
137684
|
this.#logger.warn("AlexaIntegrationManager: poll failed", {
|
|
137654
137685
|
tags: { brokerId: this.#id },
|
|
137655
|
-
meta: {
|
|
137686
|
+
meta: {
|
|
137687
|
+
error: errMsg(err),
|
|
137688
|
+
consecutiveFailures: this.#pollFailures,
|
|
137689
|
+
httpStatus
|
|
137690
|
+
}
|
|
137656
137691
|
});
|
|
137692
|
+
if (this.#pollFailures >= 3 && this.#status !== "disconnected") this.#transition(err instanceof AlexaAuthError ? "auth-failed" : "error");
|
|
137657
137693
|
}
|
|
137658
137694
|
}
|
|
137695
|
+
#transition(next) {
|
|
137696
|
+
if (this.#status === next) return;
|
|
137697
|
+
const previous = this.#status;
|
|
137698
|
+
this.#status = next;
|
|
137699
|
+
this.#logger.info("AlexaIntegrationManager: status changed", {
|
|
137700
|
+
tags: { brokerId: this.#id },
|
|
137701
|
+
meta: {
|
|
137702
|
+
from: previous,
|
|
137703
|
+
to: next,
|
|
137704
|
+
error: this.#error
|
|
137705
|
+
}
|
|
137706
|
+
});
|
|
137707
|
+
}
|
|
137659
137708
|
/** Stop the poll loop, disconnect the client (FIRST), null it. */
|
|
137660
137709
|
async stop() {
|
|
137661
137710
|
if (this.#pollTimer !== null) {
|
|
@@ -139311,9 +139360,16 @@ function readString(config, key) {
|
|
|
139311
139360
|
const value = config[key];
|
|
139312
139361
|
return typeof value === "string" ? value : "";
|
|
139313
139362
|
}
|
|
139314
|
-
/** The
|
|
139315
|
-
|
|
139316
|
-
|
|
139363
|
+
/** The level id an Echo notification uses for "quiet: no chime, no announcement". */
|
|
139364
|
+
var SILENT_LEVEL_ID = "silent";
|
|
139365
|
+
var SILENT_PRIORITY = 1;
|
|
139366
|
+
/**
|
|
139367
|
+
* Quiet or announce is decided PER NOTIFICATION, never per target: a silent
|
|
139368
|
+
* notification (the kind's silent level, or the lowest priority when no level
|
|
139369
|
+
* is named) is spoken quietly, hours-aware; every other one announces.
|
|
139370
|
+
*/
|
|
139371
|
+
function modeFor(notification) {
|
|
139372
|
+
return (notification.level !== void 0 ? notification.level === SILENT_LEVEL_ID : notification.priority <= SILENT_PRIORITY) ? "speak" : "announce";
|
|
139317
139373
|
}
|
|
139318
139374
|
function readVolume(config, delivery) {
|
|
139319
139375
|
const ruled = delivery?.["volume"];
|
|
@@ -139324,21 +139380,9 @@ function readVolume(config, delivery) {
|
|
|
139324
139380
|
/**
|
|
139325
139381
|
* The delivery parameters a rule may set for an Echo target (D938). The
|
|
139326
139382
|
* target's own config holds the default; these override it per notification.
|
|
139383
|
+
* Quiet vs announce is not one of them: it follows the notification's level.
|
|
139327
139384
|
*/
|
|
139328
139385
|
var DELIVERY_PARAMS = [{
|
|
139329
|
-
key: "mode",
|
|
139330
|
-
label: "Mode",
|
|
139331
|
-
labelKey: "import-alexa:echoTarget.s_echo.mode.label",
|
|
139332
|
-
type: "enum",
|
|
139333
|
-
options: [{
|
|
139334
|
-
value: "speak",
|
|
139335
|
-
label: "Speak (quiet hours apply)"
|
|
139336
|
-
}, {
|
|
139337
|
-
value: "announce",
|
|
139338
|
-
label: "Announce (chime, then speech)"
|
|
139339
|
-
}],
|
|
139340
|
-
default: "speak"
|
|
139341
|
-
}, {
|
|
139342
139386
|
key: "volume",
|
|
139343
139387
|
label: "Volume",
|
|
139344
139388
|
labelKey: "import-alexa:echoTarget.s_echo.volume.label",
|
|
@@ -139389,20 +139433,6 @@ function configSchema() {
|
|
|
139389
139433
|
dependsOn: ["brokerId"]
|
|
139390
139434
|
}
|
|
139391
139435
|
}],
|
|
139392
|
-
{
|
|
139393
|
-
type: "select",
|
|
139394
|
-
key: "mode",
|
|
139395
|
-
label: "Mode",
|
|
139396
|
-
labelKey: "import-alexa:echoTarget.s_echo.mode.label",
|
|
139397
|
-
default: "speak",
|
|
139398
|
-
options: [{
|
|
139399
|
-
value: "speak",
|
|
139400
|
-
label: "Speak (quiet hours apply)"
|
|
139401
|
-
}, {
|
|
139402
|
-
value: "announce",
|
|
139403
|
-
label: "Announce (chime, then speech)"
|
|
139404
|
-
}]
|
|
139405
|
-
},
|
|
139406
139436
|
{
|
|
139407
139437
|
type: "number",
|
|
139408
139438
|
key: "volume",
|
|
@@ -139444,9 +139474,16 @@ function alexaTargetKind() {
|
|
|
139444
139474
|
actions: 0,
|
|
139445
139475
|
actionIcons: false,
|
|
139446
139476
|
levels: [{
|
|
139477
|
+
id: SILENT_LEVEL_ID,
|
|
139478
|
+
label: "Silent",
|
|
139479
|
+
ordinal: SILENT_PRIORITY,
|
|
139480
|
+
flags: { silent: true },
|
|
139481
|
+
description: "Spoken quietly: no chime, quiet hours apply."
|
|
139482
|
+
}, {
|
|
139447
139483
|
id: "default",
|
|
139448
|
-
label: "
|
|
139449
|
-
ordinal: 3
|
|
139484
|
+
label: "Announce",
|
|
139485
|
+
ordinal: 3,
|
|
139486
|
+
description: "Announced: chime, then speech."
|
|
139450
139487
|
}],
|
|
139451
139488
|
format: ["text"],
|
|
139452
139489
|
clickUrl: false,
|
|
@@ -139463,7 +139500,6 @@ function discoveredFrom(broker, device) {
|
|
|
139463
139500
|
config: {
|
|
139464
139501
|
brokerId: broker.id,
|
|
139465
139502
|
echoId: device.id,
|
|
139466
|
-
mode: "speak",
|
|
139467
139503
|
volume: 40,
|
|
139468
139504
|
respectDnd: true,
|
|
139469
139505
|
family: device.family,
|
|
@@ -139476,6 +139512,7 @@ function createAlexaNotificationProvider(ports) {
|
|
|
139476
139512
|
let targets = ports.loadTargets();
|
|
139477
139513
|
let ledger = emptySpeechLedger();
|
|
139478
139514
|
let deliveries = [];
|
|
139515
|
+
const warnedLegacyMode = /* @__PURE__ */ new Set();
|
|
139479
139516
|
function record(row) {
|
|
139480
139517
|
deliveries = [...deliveries, row];
|
|
139481
139518
|
}
|
|
@@ -139617,7 +139654,14 @@ function createAlexaNotificationProvider(ports) {
|
|
|
139617
139654
|
renderedAs: speech.renderedAs
|
|
139618
139655
|
};
|
|
139619
139656
|
}
|
|
139620
|
-
const mode =
|
|
139657
|
+
const mode = modeFor(notification);
|
|
139658
|
+
if (target.config["mode"] !== void 0 && !warnedLegacyMode.has(target.id)) {
|
|
139659
|
+
warnedLegacyMode.add(target.id);
|
|
139660
|
+
logger.warn("alexa target mode is ignored: quiet vs announce follows each notification", {
|
|
139661
|
+
...tags,
|
|
139662
|
+
meta: { targetId: target.id }
|
|
139663
|
+
});
|
|
139664
|
+
}
|
|
139621
139665
|
try {
|
|
139622
139666
|
const outcomes = await ports.speak({
|
|
139623
139667
|
brokerId: broker.id,
|
|
@@ -140047,7 +140091,6 @@ function targetsFrom(rows) {
|
|
|
140047
140091
|
config: {
|
|
140048
140092
|
...row.brokerId.length > 0 ? { brokerId: row.brokerId } : {},
|
|
140049
140093
|
echoId: row.echoId,
|
|
140050
|
-
mode: row.mode,
|
|
140051
140094
|
volume: row.volume,
|
|
140052
140095
|
respectDnd: row.respectDnd
|
|
140053
140096
|
}
|
|
@@ -140060,7 +140103,7 @@ function rowsFrom(targets) {
|
|
|
140060
140103
|
enabled: target.enabled,
|
|
140061
140104
|
brokerId: typeof target.config["brokerId"] === "string" ? target.config["brokerId"] : "",
|
|
140062
140105
|
echoId: typeof target.config["echoId"] === "string" ? target.config["echoId"] : "",
|
|
140063
|
-
mode:
|
|
140106
|
+
mode: "speak",
|
|
140064
140107
|
volume: typeof target.config["volume"] === "number" ? target.config["volume"] : 40,
|
|
140065
140108
|
respectDnd: target.config["respectDnd"] !== false
|
|
140066
140109
|
}));
|
package/i18n/en.json
CHANGED
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
"echoTarget.s_echo.title": "Echo",
|
|
15
15
|
"echoTarget.s_echo.brokerId.label": "Amazon account",
|
|
16
16
|
"echoTarget.s_echo.echoId.label": "Echo",
|
|
17
|
-
"echoTarget.s_echo.mode.label": "Mode",
|
|
18
17
|
"echoTarget.s_echo.volume.label": "Volume",
|
|
19
18
|
"echoTarget.s_echo.respectDnd.label": "Respect Do Not Disturb",
|
|
20
19
|
"globalSettings": {
|
package/i18n/it.json
CHANGED
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
"echoTarget.s_echo.title": "Echo",
|
|
15
15
|
"echoTarget.s_echo.brokerId.label": "Account Amazon",
|
|
16
16
|
"echoTarget.s_echo.echoId.label": "Echo",
|
|
17
|
-
"echoTarget.s_echo.mode.label": "Modalità",
|
|
18
17
|
"echoTarget.s_echo.volume.label": "Volume",
|
|
19
18
|
"echoTarget.s_echo.respectDnd.label": "Rispetta Non disturbare",
|
|
20
19
|
"globalSettings": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-import-alexa",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.172",
|
|
4
4
|
"description": "Alexa device-import provider for CamStack — imports the smart-home devices in a user's Alexa account via the unofficial alexa-remote2 cookie/token client (the inverse of the Alexa exporter)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|