@mega-yfue/eufy-sdk 0.6.0-beta.40 → 0.6.0-beta.42

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/dist/index.js CHANGED
@@ -47,10 +47,10 @@ function decryptBody(b64, shareKeyHex) {
47
47
  return Buffer.concat([decipher.update(raw.subarray(16)), decipher.final()]);
48
48
  }
49
49
  function signRequest(shareKeyHex, ts, once, encBodyB64) {
50
- const parts = [ts, once];
50
+ const parts2 = [ts, once];
51
51
  if (encBodyB64)
52
- parts.push(encBodyB64);
53
- return createHmac("sha256", signKey(shareKeyHex)).update(parts.join("+"), "utf-8").digest("hex");
52
+ parts2.push(encBodyB64);
53
+ return createHmac("sha256", signKey(shareKeyHex)).update(parts2.join("+"), "utf-8").digest("hex");
54
54
  }
55
55
  function prepareKeyExchange(localKeyHex = EUFY_MEGA_LOCAL_KEY_HEX) {
56
56
  const localKey = Buffer.from(localKeyHex, "hex");
@@ -411,8 +411,8 @@ function randomPhoneModel(seed) {
411
411
  const rnd = rng(seed);
412
412
  const pick = (a) => a[Math.floor(rnd() * a.length)];
413
413
  const brand = pick(Object.keys(PHONE_MODELS));
414
- const parts = PHONE_MODELS[brand];
415
- return `${brand}${pick(parts.first)}${pick(parts.second)}`.trim();
414
+ const parts2 = PHONE_MODELS[brand];
415
+ return `${brand}${pick(parts2.first)}${pick(parts2.second)}`.trim();
416
416
  }
417
417
  function randomUserAgent(seed, model) {
418
418
  const rnd = rng(seed);
@@ -1052,10 +1052,10 @@ function getIdSuffix(p2pDid) {
1052
1052
  const match = p2pDid.match(/^[A-Z]+-(\d+)-[A-Z]+$/);
1053
1053
  if (match?.length === 2) {
1054
1054
  const num1 = Number.parseInt(match[1][0]);
1055
- const num2 = Number.parseInt(match[1][1]);
1055
+ const num22 = Number.parseInt(match[1][1]);
1056
1056
  const num3 = Number.parseInt(match[1][3]);
1057
1057
  const num4 = Number.parseInt(match[1][5]);
1058
- result = num1 + num2 + num3;
1058
+ result = num1 + num22 + num3;
1059
1059
  if (num3 < 5)
1060
1060
  result = result + num3;
1061
1061
  result = result + num4;
@@ -1735,6 +1735,10 @@ var MegaHttpClient = class {
1735
1735
  get regionShard() {
1736
1736
  return this.region;
1737
1737
  }
1738
+ /** The account's country code as configured (e.g. `"FR"`), the one logins are sent with. */
1739
+ get countryCode() {
1740
+ return this.cfg.countryCode;
1741
+ }
1738
1742
  /**
1739
1743
  * The shard the estimated domain names (`…-ie-…`/`…-ie.` → `ie-pr`), else {@link regionShard}. The
1740
1744
  * eu/us classification cannot name shards like `ie-pr`.
@@ -2069,6 +2073,21 @@ ${this.auth_?.authToken ?? ""}`;
2069
2073
  getProductDataPoint(code) {
2070
2074
  return this.post("things", "/app/things/get_product_data_point", { code });
2071
2075
  }
2076
+ /**
2077
+ * Fetch the **thing descriptions** of the given product codes from the mega `things` service: per
2078
+ * product, every named property with its type and offered values, its actions and events. The v6 app
2079
+ * fetches the same list for the products an account holds and reads each setting's options from it.
2080
+ * Product-level data, not account data. `codeTimeMap` names, per product code, the `update_time` of the
2081
+ * description already held: a product whose description has not changed since comes back with that
2082
+ * `update_time` and no properties, one that has (or is not named) comes back in full. Returns the raw
2083
+ * `data`.
2084
+ */
2085
+ getThingsList(productCodes, codeTimeMap = {}) {
2086
+ return this.post("things", "/app/things/get_things_list", {
2087
+ product_codes: productCodes,
2088
+ code_time_map: codeTimeMap
2089
+ });
2090
+ }
2072
2091
  /**
2073
2092
  * Fetch the **live param list** from the mega `devicemanage` service. Body keys off a
2074
2093
  * `device_sns` ARRAY (a bare `device_sn` 400s with "type mismatch"). Each returned entry is
@@ -2105,10 +2124,10 @@ ${this.auth_?.authToken ?? ""}`;
2105
2124
  * host it points at is unconfirmed, and this client's binary path (`downloadMediaResource`) is
2106
2125
  * host-allowlisted by design.
2107
2126
  */
2108
- getCleanRecords(deviceSn, num2 = 20, page = 1) {
2127
+ getCleanRecords(deviceSn, num3 = 20, page = 1) {
2109
2128
  return this.post("clean", "/app/clean/get_device_clean_record_list", {
2110
2129
  device_sn: deviceSn,
2111
- num: num2,
2130
+ num: num3,
2112
2131
  page
2113
2132
  });
2114
2133
  }
@@ -2123,11 +2142,11 @@ ${this.auth_?.authToken ?? ""}`;
2123
2142
  * APK, and the extracted `.so` set contains no clean-native module. Handing over bytes a caller can
2124
2143
  * take elsewhere is the honest surface; a decode here would be invention.
2125
2144
  */
2126
- getDeviceMapList(deviceSn, channelId = 0, num2 = 1, page = 1, lastOffset = 0) {
2145
+ getDeviceMapList(deviceSn, channelId = 0, num3 = 1, page = 1, lastOffset = 0) {
2127
2146
  return this.post("clean", "/app/clean/get_device_map_list", {
2128
2147
  device_sn: deviceSn,
2129
2148
  channel_id: channelId,
2130
- num: num2,
2149
+ num: num3,
2131
2150
  page,
2132
2151
  last_offset: lastOffset
2133
2152
  });
@@ -2650,15 +2669,15 @@ function subscribeTopics(device) {
2650
2669
  return [secureTopic(device, "res")];
2651
2670
  }
2652
2671
  function parseSecureTopic(topic) {
2653
- const parts = topic.split("/");
2654
- if (parts.length < 5)
2672
+ const parts2 = topic.split("/");
2673
+ if (parts2.length < 5)
2655
2674
  return void 0;
2656
- const [root, category, model, sn] = parts;
2675
+ const [root, category, model, sn] = parts2;
2657
2676
  if (root !== "cmd" && root !== "synq" && root !== "biz" && root !== "dt")
2658
2677
  return void 0;
2659
2678
  if (!category || !model || !sn)
2660
2679
  return void 0;
2661
- return { root, category, model, sn, tail: parts.slice(4).join("/") };
2680
+ return { root, category, model, sn, tail: parts2.slice(4).join("/") };
2662
2681
  }
2663
2682
  function solixDeviceTopics(appName, productCode, deviceSn) {
2664
2683
  const dt = `dt/${appName}/${productCode}/${deviceSn}`;
@@ -3403,8 +3422,17 @@ function provided(needs, build, description, requiredCapabilities) {
3403
3422
  function hasRequiredCapabilities(member, capabilities) {
3404
3423
  return !member.requiredCapabilities?.some((capability) => !capabilities?.has(capability));
3405
3424
  }
3425
+ function thingEnum(m, ctx) {
3426
+ const named = m.enumValues;
3427
+ const property2 = named && m.thing?.map((id) => ctx.things?.properties[id]).find(Boolean);
3428
+ if (!named || !property2)
3429
+ return void 0;
3430
+ const wire = property2.type === "bool" ? [0, 1] : property2.members.map((x) => Number(x.wireValue ?? NaN));
3431
+ const options = Object.fromEntries(wire.filter((v) => v in named).map((v) => [v, named[v]]));
3432
+ return Object.keys(options).length ? options : void 0;
3433
+ }
3406
3434
  function resolvedEnum(m, ctx) {
3407
- const dynamic = ctx ? m.enumValuesFor?.(ctx) : void 0;
3435
+ const dynamic = ctx ? m.enumValuesFor?.(ctx) ?? thingEnum(m, ctx) : void 0;
3408
3436
  return dynamic ? { values: dynamic, dynamic: true } : { values: m.enumValues, dynamic: false };
3409
3437
  }
3410
3438
  function propertiesOf(members, ctx) {
@@ -3525,6 +3553,46 @@ function wasReported(m, from, ctx) {
3525
3553
  function installs(m, ctx) {
3526
3554
  return reports(m.requires, ctx) && (!m.requiresRead || reads(m, ctx)) && (!m.available || m.available(ctx));
3527
3555
  }
3556
+ function kindRoutable(kind, ctx) {
3557
+ return !ctx.refusedCommandKinds?.has(kind);
3558
+ }
3559
+ function probeValues(m, ctx) {
3560
+ if (m.type === "bool")
3561
+ return [false, true];
3562
+ const { values } = resolvedEnum(m, ctx);
3563
+ const keys = values ? Object.keys(values).map(Number) : void 0;
3564
+ if (m.type === "string")
3565
+ return keys && values ? keys.map((k) => values[k]) : [""];
3566
+ if (m.type === "enum")
3567
+ return keys ?? [0];
3568
+ return keys ?? [m.min ?? 0];
3569
+ }
3570
+ function writeRoutable(m, ctx) {
3571
+ if (!ctx.refusedCommandKinds)
3572
+ return true;
3573
+ for (const value of probeValues(m, ctx)) {
3574
+ let kind;
3575
+ try {
3576
+ kind = m.write?.(value, ctx)?.kind;
3577
+ } catch {
3578
+ return true;
3579
+ }
3580
+ if (kind === void 0 || kindRoutable(kind, ctx))
3581
+ return true;
3582
+ }
3583
+ return false;
3584
+ }
3585
+ function actionRoutable(m, ctx) {
3586
+ if (!ctx.refusedCommandKinds)
3587
+ return true;
3588
+ let kind;
3589
+ try {
3590
+ kind = m.action(ctx).kind;
3591
+ } catch {
3592
+ return true;
3593
+ }
3594
+ return kindRoutable(kind, ctx);
3595
+ }
3528
3596
  function bindMembers(members, deps) {
3529
3597
  const { ctx, sink, read, rawDp: rawDp2 } = deps;
3530
3598
  const out = {};
@@ -3550,7 +3618,7 @@ function bindMembers(members, deps) {
3550
3618
  continue;
3551
3619
  }
3552
3620
  if ("action" in m) {
3553
- if (installs(m, ctx))
3621
+ if (installs(m, ctx) && actionRoutable(m, ctx))
3554
3622
  out[name] = describe(m.description, () => sink.dispatch(m.action(ctx)), {});
3555
3623
  continue;
3556
3624
  }
@@ -3567,7 +3635,7 @@ function bindMembers(members, deps) {
3567
3635
  const get = decode ? () => decode(raw(), rawDp2, ctx) : () => narrow(m.type, read, prop, domain);
3568
3636
  Object.defineProperty(out, name, { get, enumerable: true, configurable: true });
3569
3637
  }
3570
- if (!m.write || m.unverified || !installs(m, ctx))
3638
+ if (!m.write || m.unverified || !installs(m, ctx) || !writeRoutable(m, ctx))
3571
3639
  continue;
3572
3640
  if (m.writeOnly)
3573
3641
  unobservable.push(name);
@@ -3683,73 +3751,6 @@ var VIDEO = {
3683
3751
  detection: { evidenceParams: [1056], codecs: ["camera"] }
3684
3752
  };
3685
3753
 
3686
- // dist/model/device-family.js
3687
- var INDOOR_CAMERA_TYPES = /* @__PURE__ */ new Set([
3688
- DeviceType.INDOOR_CAMERA,
3689
- DeviceType.INDOOR_CAMERA_1080,
3690
- DeviceType.INDOOR_PT_CAMERA,
3691
- DeviceType.INDOOR_PT_CAMERA_1080,
3692
- DeviceType.INDOOR_OUTDOOR_CAMERA_1080P,
3693
- DeviceType.INDOOR_OUTDOOR_CAMERA_1080P_NO_LIGHT,
3694
- DeviceType.INDOOR_OUTDOOR_CAMERA_2K,
3695
- DeviceType.INDOOR_COST_DOWN_CAMERA,
3696
- DeviceType.INDOOR_PT_CAMERA_S350,
3697
- DeviceType.INDOOR_PT_CAMERA_E30,
3698
- DeviceType.INDOOR_PT_CAMERA_C210,
3699
- DeviceType.INDOOR_PT_CAMERA_C220,
3700
- DeviceType.INDOOR_PT_CAMERA_C220_V2
3701
- ]);
3702
- var INDOOR_PT_S350_TYPES = /* @__PURE__ */ new Set([
3703
- DeviceType.INDOOR_PT_CAMERA_S350,
3704
- DeviceType.INDOOR_PT_CAMERA_E30,
3705
- DeviceType.INDOOR_PT_CAMERA_C210,
3706
- DeviceType.INDOOR_PT_CAMERA_C220,
3707
- DeviceType.INDOOR_PT_CAMERA_C220_V2
3708
- ]);
3709
- var OUTDOOR_PT_TYPES = /* @__PURE__ */ new Set([
3710
- DeviceType.OUTDOOR_PT_CAMERA,
3711
- DeviceType.SOLO_CAMERA_E30,
3712
- DeviceType.CAMERA_S4,
3713
- DeviceType.SOLOCAM_E42,
3714
- DeviceType.CAMERA_4G_S330
3715
- ]);
3716
- var FLOODLIGHT_TYPES = /* @__PURE__ */ new Set([
3717
- DeviceType.FLOODLIGHT,
3718
- DeviceType.FLOODLIGHT_CAMERA_8422,
3719
- DeviceType.FLOODLIGHT_CAMERA_8423,
3720
- DeviceType.FLOODLIGHT_CAMERA_8424,
3721
- DeviceType.FLOODLIGHT_CAMERA_8425,
3722
- DeviceType.FLOODLIGHT_CAMERA_8426
3723
- ]);
3724
- var WALL_LIGHT_TYPES = /* @__PURE__ */ new Set([
3725
- DeviceType.WALL_LIGHT_CAM,
3726
- DeviceType.WALL_LIGHT_CAM_81A0,
3727
- DeviceType.WALL_LIGHT_CAM_T86C1
3728
- ]);
3729
- var HOMEBASE_TYPES = /* @__PURE__ */ new Set([
3730
- DeviceType.STATION,
3731
- DeviceType.HB3,
3732
- DeviceType.STATION_9000,
3733
- DeviceType.MINIBASE_CHIME,
3734
- DeviceType.HOMEBASE_MINI
3735
- ]);
3736
- var has = (set, t) => t !== void 0 && set.has(t);
3737
- var isIndoorCamera = (ctx) => has(INDOOR_CAMERA_TYPES, ctx.deviceType);
3738
- var isIndoorCamMini = (ctx) => ctx.deviceType === DeviceType.INDOOR_COST_DOWN_CAMERA;
3739
- var isIndoorPanTiltS350 = (ctx) => has(INDOOR_PT_S350_TYPES, ctx.deviceType);
3740
- var isHomeBase = (ctx) => has(HOMEBASE_TYPES, ctx.deviceType);
3741
- var isStation9000 = (ctx) => ctx.deviceType === DeviceType.STATION_9000;
3742
- var isNvrT8N00 = (ctx) => ctx.model === "T8N00";
3743
- var NVR_MEDIA_CAMERA_MODELS = /* @__PURE__ */ new Set(["T8E00", "T8P00", "T8P10", "T8162", "T8410", "T8214"]);
3744
- var isNvrMediaCamera = (ctx) => ctx.model !== void 0 && NVR_MEDIA_CAMERA_MODELS.has(ctx.model);
3745
- var isNvrDualLensCamera = (ctx) => ctx.model === "T8E00";
3746
- var isNvrFullHdCamera = (ctx) => ctx.model === "T8E00" || ctx.model === "T8P00";
3747
- var isCallDoorbell = (ctx) => ctx.model === "T8223" || ctx.model === "T8224";
3748
- var isAiotVacuum = (ctx) => ctx.category !== "eufy_home_tuya";
3749
- var isTuyaVacuum = (ctx) => ctx.category === "eufy_home_tuya";
3750
- var isVideoSmartLock = (ctx) => ctx.deviceType === DeviceType.LOCK_8530;
3751
- var isClassicWifiLock = (ctx) => ctx.deviceType === DeviceType.LOCK_WIFI && typeof ctx.serial === "string" && ctx.serial.startsWith("T8520") && ctx.serial.charAt(6) === "2";
3752
-
3753
3754
  // dist/model/capabilities/rtsp.js
3754
3755
  var RTSP_PARAM = {
3755
3756
  /**
@@ -3805,7 +3806,7 @@ var RtspRecordingMode = {
3805
3806
  Continuous: 1
3806
3807
  };
3807
3808
  function publishable(ctx) {
3808
- return !isStation9000({ deviceType: ctx.stationDeviceType });
3809
+ return kindRoutable("set-param", ctx);
3809
3810
  }
3810
3811
  function publishCommand(on, ctx) {
3811
3812
  return setScalar(RTSP_PARAM.STREAM_SWITCH, on ? 1 : 0, ctx);
@@ -3831,8 +3832,7 @@ var RTSP_MEMBERS = {
3831
3832
  provenance: "apk",
3832
3833
  description: "Whether this camera's stream is published over RTSP (1145 NAS_STREAM_SWITHC). Name from the apk; the WRITE is verified live on a HomeBase-attached doorbell and two indoor cameras, both directions (DESCRIBE 404\u2192200 with a real SDP, and back).",
3833
3834
  write: (v, ctx) => publishCommand(asBool(v), ctx),
3834
- aliases: { publish: true, withdraw: false },
3835
- available: publishable
3835
+ aliases: { publish: true, withdraw: false }
3836
3836
  },
3837
3837
  /**
3838
3838
  * The device-reported RTSP URL — the full `rtsp://user:pass@host/path`, carrying the credentials the
@@ -4231,6 +4231,13 @@ var MOTION_CMD = {
4231
4231
  * as a `1700` control payload carrying an index rather than the `1350` envelope its siblings take.
4232
4232
  */
4233
4233
  INDOOR_SENSITIVITY_INDEX: 6041,
4234
+ /**
4235
+ * Motion detection on-off on a T8410 (app `CMD_INDOOR_DET_SET_MOTION_DETECT_ENABLE`). `1700` control
4236
+ * payload `{"commandType":6040,"data":{"enable":0,"quality":0,"status":0|1,"value":0}}`.
4237
+ *
4238
+ * ⚠️ Replay + readback confirmed on a standalone and a HomeBase 3-attached T8410, NOT byte-captured.
4239
+ */
4240
+ INDOOR_MOTION_ENABLE: 6040,
4234
4241
  /**
4235
4242
  * Detection sensitivity a solo camera REPORTS (app `SET_MOTION_DETECTION_SENSITIVITY_SOLO`). It does
4236
4243
  * not accept writes on this id — the write goes to {@link MOTION_SENSITIVITY}, which is why a scale
@@ -4362,6 +4369,9 @@ function sensitivityCommand(step, scale, ctx) {
4362
4369
  return setPayload(scale.writeId, { sensitivity: value, channel: ctx.channel }, ctx, 0, void 0, "auto");
4363
4370
  }
4364
4371
  var EXIT_TEST_MODE_VALUE = 808464384;
4372
+ function isT8410(ctx) {
4373
+ return ctx.model === "T8410";
4374
+ }
4365
4375
  function requireFamily(action, ctx, verifiedOn) {
4366
4376
  const isSensor = ctx.codec === "sensor";
4367
4377
  if (isSensor === (verifiedOn === "sensor"))
@@ -4399,10 +4409,15 @@ var MOTION_MEMBERS = {
4399
4409
  type: "bool",
4400
4410
  kind: "boolean",
4401
4411
  provenance: "verified",
4402
- description: "Motion/PIR detection enabled (verified: param 1011 = CAMERA_PIR).",
4412
+ readAvailable: (ctx) => !isT8410(ctx),
4413
+ readAliases: [{ paramType: MOTION_CMD.INDOOR_MOTION_ENABLE, available: isT8410 }],
4414
+ description: "Motion/PIR detection enabled (verified: param 1011 = CAMERA_PIR; a T8410 reads and writes INDOOR_MOTION_ENABLE 6040 instead).",
4403
4415
  write: (v, ctx) => {
4404
4416
  requireFamily("motionDetection", ctx, "camera");
4405
- return setScalar(MOTION_CMD.CAMERA_PIR, asBool(v) ? 1 : 0, ctx, "direct-binary");
4417
+ const on = asBool(v) ? 1 : 0;
4418
+ if (!isT8410(ctx))
4419
+ return setScalar(MOTION_CMD.CAMERA_PIR, on, ctx, "direct-binary");
4420
+ return setJson(MOTION_CMD.INDOOR_MOTION_ENABLE, { enable: 0, quality: 0, status: on, value: 0 }, ctx);
4406
4421
  },
4407
4422
  writeAs: "setDetection"
4408
4423
  },
@@ -4766,8 +4781,8 @@ function decodePowerSource(raw) {
4766
4781
  return String(raw);
4767
4782
  }
4768
4783
  }
4769
- const num2 = Number(raw);
4770
- return Number.isFinite(num2) ? num2 : String(raw);
4784
+ const num3 = Number(raw);
4785
+ return Number.isFinite(num3) ? num3 : String(raw);
4771
4786
  }
4772
4787
  function recordSetting(param, value, ctx) {
4773
4788
  return setStationScalar(param, value, ctx.channel);
@@ -5268,6 +5283,73 @@ var CONTACT = {
5268
5283
  }
5269
5284
  };
5270
5285
 
5286
+ // dist/model/device-family.js
5287
+ var INDOOR_CAMERA_TYPES = /* @__PURE__ */ new Set([
5288
+ DeviceType.INDOOR_CAMERA,
5289
+ DeviceType.INDOOR_CAMERA_1080,
5290
+ DeviceType.INDOOR_PT_CAMERA,
5291
+ DeviceType.INDOOR_PT_CAMERA_1080,
5292
+ DeviceType.INDOOR_OUTDOOR_CAMERA_1080P,
5293
+ DeviceType.INDOOR_OUTDOOR_CAMERA_1080P_NO_LIGHT,
5294
+ DeviceType.INDOOR_OUTDOOR_CAMERA_2K,
5295
+ DeviceType.INDOOR_COST_DOWN_CAMERA,
5296
+ DeviceType.INDOOR_PT_CAMERA_S350,
5297
+ DeviceType.INDOOR_PT_CAMERA_E30,
5298
+ DeviceType.INDOOR_PT_CAMERA_C210,
5299
+ DeviceType.INDOOR_PT_CAMERA_C220,
5300
+ DeviceType.INDOOR_PT_CAMERA_C220_V2
5301
+ ]);
5302
+ var INDOOR_PT_S350_TYPES = /* @__PURE__ */ new Set([
5303
+ DeviceType.INDOOR_PT_CAMERA_S350,
5304
+ DeviceType.INDOOR_PT_CAMERA_E30,
5305
+ DeviceType.INDOOR_PT_CAMERA_C210,
5306
+ DeviceType.INDOOR_PT_CAMERA_C220,
5307
+ DeviceType.INDOOR_PT_CAMERA_C220_V2
5308
+ ]);
5309
+ var OUTDOOR_PT_TYPES = /* @__PURE__ */ new Set([
5310
+ DeviceType.OUTDOOR_PT_CAMERA,
5311
+ DeviceType.SOLO_CAMERA_E30,
5312
+ DeviceType.CAMERA_S4,
5313
+ DeviceType.SOLOCAM_E42,
5314
+ DeviceType.CAMERA_4G_S330
5315
+ ]);
5316
+ var FLOODLIGHT_TYPES = /* @__PURE__ */ new Set([
5317
+ DeviceType.FLOODLIGHT,
5318
+ DeviceType.FLOODLIGHT_CAMERA_8422,
5319
+ DeviceType.FLOODLIGHT_CAMERA_8423,
5320
+ DeviceType.FLOODLIGHT_CAMERA_8424,
5321
+ DeviceType.FLOODLIGHT_CAMERA_8425,
5322
+ DeviceType.FLOODLIGHT_CAMERA_8426
5323
+ ]);
5324
+ var WALL_LIGHT_TYPES = /* @__PURE__ */ new Set([
5325
+ DeviceType.WALL_LIGHT_CAM,
5326
+ DeviceType.WALL_LIGHT_CAM_81A0,
5327
+ DeviceType.WALL_LIGHT_CAM_T86C1
5328
+ ]);
5329
+ var HOMEBASE_TYPES = /* @__PURE__ */ new Set([
5330
+ DeviceType.STATION,
5331
+ DeviceType.HB3,
5332
+ DeviceType.STATION_9000,
5333
+ DeviceType.MINIBASE_CHIME,
5334
+ DeviceType.HOMEBASE_MINI
5335
+ ]);
5336
+ var has = (set, t) => t !== void 0 && set.has(t);
5337
+ var isIndoorCamera = (ctx) => has(INDOOR_CAMERA_TYPES, ctx.deviceType);
5338
+ var isIndoorCamMini = (ctx) => ctx.deviceType === DeviceType.INDOOR_COST_DOWN_CAMERA;
5339
+ var isIndoorPanTiltS350 = (ctx) => has(INDOOR_PT_S350_TYPES, ctx.deviceType);
5340
+ var isHomeBase = (ctx) => has(HOMEBASE_TYPES, ctx.deviceType);
5341
+ var isStation9000 = (ctx) => ctx.deviceType === DeviceType.STATION_9000;
5342
+ var isNvrT8N00 = (ctx) => ctx.model === "T8N00";
5343
+ var NVR_MEDIA_CAMERA_MODELS = /* @__PURE__ */ new Set(["T8E00", "T8P00", "T8P10", "T8162", "T8410", "T8214"]);
5344
+ var isNvrMediaCamera = (ctx) => ctx.model !== void 0 && NVR_MEDIA_CAMERA_MODELS.has(ctx.model);
5345
+ var isNvrDualLensCamera = (ctx) => ctx.model === "T8E00";
5346
+ var isNvrFullHdCamera = (ctx) => ctx.model === "T8E00" || ctx.model === "T8P00";
5347
+ var isCallDoorbell = (ctx) => ctx.model === "T8223" || ctx.model === "T8224";
5348
+ var isAiotVacuum = (ctx) => ctx.category !== "eufy_home_tuya";
5349
+ var isTuyaVacuum = (ctx) => ctx.category === "eufy_home_tuya";
5350
+ var isVideoSmartLock = (ctx) => ctx.deviceType === DeviceType.LOCK_8530;
5351
+ var isClassicWifiLock = (ctx) => ctx.deviceType === DeviceType.LOCK_WIFI && typeof ctx.serial === "string" && ctx.serial.startsWith("T8520") && ctx.serial.charAt(6) === "2";
5352
+
5271
5353
  // dist/model/capabilities/audio.js
5272
5354
  var AUDIO_CMD = {
5273
5355
  /**
@@ -6042,10 +6124,10 @@ function isT8442(ctx) {
6042
6124
  function usesHomeBasePowerPayload(ctx) {
6043
6125
  if (!isT8410HomeBase3(ctx) || !ctx.firmwareVersion || !/^\d+(?:\.\d+){2,3}$/.test(ctx.firmwareVersion))
6044
6126
  return false;
6045
- const version = ctx.firmwareVersion.split(".").map(Number);
6127
+ const version2 = ctx.firmwareVersion.split(".").map(Number);
6046
6128
  const minimum = [2, 3, 1, 0];
6047
6129
  for (let i = 0; i < minimum.length; i++) {
6048
- const difference = (version[i] ?? 0) - minimum[i];
6130
+ const difference = (version2[i] ?? 0) - minimum[i];
6049
6131
  if (difference !== 0)
6050
6132
  return difference > 0;
6051
6133
  }
@@ -7198,17 +7280,17 @@ var SMART_LIGHT = {
7198
7280
  const params = reportFrom(signal);
7199
7281
  if (!params)
7200
7282
  return null;
7201
- const num2 = (id) => params[id] === void 0 ? void 0 : Number(params[id]);
7283
+ const num3 = (id) => params[id] === void 0 ? void 0 : Number(params[id]);
7202
7284
  const bool = (id) => params[id] === void 0 ? void 0 : params[id] === "1";
7203
7285
  return {
7204
7286
  event: "smartLightState",
7205
7287
  payload: {
7206
7288
  power: bool(LIGHT_PARAM.POWER),
7207
- brightness: num2(LIGHT_PARAM.BRIGHTNESS),
7208
- lightLength: num2(LIGHT_PARAM.LENGTH),
7209
- effectId: num2(LIGHT_PARAM.EFFECT_ID),
7289
+ brightness: num3(LIGHT_PARAM.BRIGHTNESS),
7290
+ lightLength: num3(LIGHT_PARAM.LENGTH),
7291
+ effectId: num3(LIGHT_PARAM.EFFECT_ID),
7210
7292
  colorGradient: bool(LIGHT_PARAM.COLOR_GRADIENT),
7211
- cloudEffectId: num2(LIGHT_PARAM.CLOUD_EFFECT_ID)
7293
+ cloudEffectId: num3(LIGHT_PARAM.CLOUD_EFFECT_ID)
7212
7294
  }
7213
7295
  };
7214
7296
  },
@@ -9229,11 +9311,11 @@ function decodeDoNotDisturbTime(raw, codec, field) {
9229
9311
  const point = codec.nested(window.value)?.find((f) => f.field === field);
9230
9312
  if (point?.kind !== "bytes")
9231
9313
  return void 0;
9232
- const parts = codec.nested(point.value);
9233
- if (!parts)
9314
+ const parts2 = codec.nested(point.value);
9315
+ if (!parts2)
9234
9316
  return void 0;
9235
9317
  const at = (n) => {
9236
- const hit = parts.find((f) => f.field === n);
9318
+ const hit = parts2.find((f) => f.field === n);
9237
9319
  return hit?.kind === "int" ? Number(hit.value) : 0;
9238
9320
  };
9239
9321
  return `${String(at(UNDISTURBED_FIELD.HOUR)).padStart(2, "0")}:${String(at(UNDISTURBED_FIELD.MINUTE)).padStart(2, "0")}`;
@@ -11227,12 +11309,12 @@ function parseDpCatalog(raw) {
11227
11309
  if (!raw || typeof raw !== "object" || Array.isArray(raw))
11228
11310
  return EMPTY_DP_CATALOG;
11229
11311
  const r = raw;
11230
- const list = r.data_point_list;
11231
- if (!Array.isArray(list) || list.length === 0)
11312
+ const list2 = r.data_point_list;
11313
+ if (!Array.isArray(list2) || list2.length === 0)
11232
11314
  return EMPTY_DP_CATALOG;
11233
11315
  let found = 0;
11234
11316
  const enumRanges = /* @__PURE__ */ new Map();
11235
- for (const entry of list) {
11317
+ for (const entry of list2) {
11236
11318
  if (!entry || typeof entry !== "object" || Array.isArray(entry))
11237
11319
  continue;
11238
11320
  const e = entry;
@@ -11537,14 +11619,14 @@ function mergeProperties(caps, ctx) {
11537
11619
  return merged;
11538
11620
  }
11539
11621
  function hintHaystack(rec) {
11540
- const parts = [rec.model, rec.category];
11622
+ const parts2 = [rec.model, rec.category];
11541
11623
  const loose = rec;
11542
11624
  for (const key of ["name", "device_name", "alias", "product_name"]) {
11543
11625
  const v = loose[key];
11544
11626
  if (typeof v === "string")
11545
- parts.push(v);
11627
+ parts2.push(v);
11546
11628
  }
11547
- return parts.filter((p) => typeof p === "string").join(" ");
11629
+ return parts2.filter((p) => typeof p === "string").join(" ");
11548
11630
  }
11549
11631
  var CODEC_LINE = {
11550
11632
  station: "security",
@@ -11588,7 +11670,7 @@ function detectCapabilities(rec, codec) {
11588
11670
  matched = d.deviceTypes.includes(rec.deviceType);
11589
11671
  }
11590
11672
  if (!matched && d.modelHints && haystack.length > 0) {
11591
- matched = d.modelHints.some((re) => re.test(haystack));
11673
+ matched = d.modelHints.some((re2) => re2.test(haystack));
11592
11674
  }
11593
11675
  if (!matched && d.codecs && codec !== void 0) {
11594
11676
  matched = d.codecs.includes(codec);
@@ -11775,9 +11857,9 @@ function memberCommand(m, action, value, ctx) {
11775
11857
  for (const [name, member] of Object.entries(m.members ?? {})) {
11776
11858
  if (!("type" in member))
11777
11859
  continue;
11778
- const property = member.property ?? name;
11860
+ const property2 = member.property ?? name;
11779
11861
  const aliased = member.aliases?.[action];
11780
- const claims = action === property || member.intentNames?.includes(action) === true;
11862
+ const claims = action === property2 || member.intentNames?.includes(action) === true;
11781
11863
  if (!claims && aliased === void 0)
11782
11864
  continue;
11783
11865
  if (member.unverified) {
@@ -11785,7 +11867,10 @@ function memberCommand(m, action, value, ctx) {
11785
11867
  }
11786
11868
  if (!member.write || !installs(member, ctx))
11787
11869
  continue;
11788
- return memberWrite(name, member, claims ? value : aliased, ctx);
11870
+ const cmd = memberWrite(name, member, claims ? value : aliased, ctx);
11871
+ if (!kindRoutable(cmd.kind, ctx))
11872
+ continue;
11873
+ return cmd;
11789
11874
  }
11790
11875
  return void 0;
11791
11876
  }
@@ -11985,6 +12070,7 @@ function resolveProperties(rec, codec, capabilities) {
11985
12070
  category: rec.category,
11986
12071
  capabilities: new Set(capabilities),
11987
12072
  paramIds,
12073
+ things: rec.things,
11988
12074
  stationSerial: rec.parentSn
11989
12075
  });
11990
12076
  }
@@ -14320,11 +14406,11 @@ function parseCleanRecords(raw) {
14320
14406
  if (!raw || typeof raw !== "object" || Array.isArray(raw))
14321
14407
  return EMPTY_CLEAN_RECORD_PAGE;
14322
14408
  const r = raw;
14323
- const list = r.clean_record_list;
14324
- if (!Array.isArray(list))
14409
+ const list2 = r.clean_record_list;
14410
+ if (!Array.isArray(list2))
14325
14411
  return EMPTY_CLEAN_RECORD_PAGE;
14326
14412
  const records = [];
14327
- for (const entry of list) {
14413
+ for (const entry of list2) {
14328
14414
  if (!entry || typeof entry !== "object" || Array.isArray(entry))
14329
14415
  continue;
14330
14416
  const e = entry;
@@ -14386,17 +14472,17 @@ function parseCleanRecordDetail(blob, codec) {
14386
14472
  const fields = codec.nested(Buffer.from(body));
14387
14473
  if (!fields)
14388
14474
  return void 0;
14389
- const num2 = (field) => {
14475
+ const num3 = (field) => {
14390
14476
  const found = fields.find((f) => f.field === field);
14391
14477
  return found?.kind === "int" ? Number(found.value) : 0;
14392
14478
  };
14393
14479
  const reason = fields.find((f) => f.field === DETAIL_FIELD.FINISH_REASON);
14394
14480
  return {
14395
- startTime: num2(DETAIL_FIELD.START_TIME),
14396
- endTime: num2(DETAIL_FIELD.END_TIME),
14397
- duration: num2(DETAIL_FIELD.DURATION),
14398
- area: num2(DETAIL_FIELD.AREA),
14399
- cleanType: num2(DETAIL_FIELD.CLEAN_TYPE),
14481
+ startTime: num3(DETAIL_FIELD.START_TIME),
14482
+ endTime: num3(DETAIL_FIELD.END_TIME),
14483
+ duration: num3(DETAIL_FIELD.DURATION),
14484
+ area: num3(DETAIL_FIELD.AREA),
14485
+ cleanType: num3(DETAIL_FIELD.CLEAN_TYPE),
14400
14486
  // An absent reason is the enum's zero member, which is `completed` — the ordinary way a run ends.
14401
14487
  finishReason: reason === void 0 ? CLEAN_FINISH_REASONS[0] : reason.kind === "int" ? CLEAN_FINISH_REASONS[Number(reason.value)] : void 0
14402
14488
  };
@@ -14889,6 +14975,7 @@ function inspectParams(rec, sn) {
14889
14975
  },
14890
14976
  suggestedRegistry: { model, entry, exists: !!MODEL_REGISTRY[model.toUpperCase()] },
14891
14977
  registrySnippet: buildRegistrySnippet(model, entry),
14978
+ things: rec.things,
14892
14979
  dictionarySnippet: buildDictSnippet(model, unknown)
14893
14980
  };
14894
14981
  }
@@ -15183,9 +15270,9 @@ function sceneNum(v) {
15183
15270
  return void 0;
15184
15271
  }
15185
15272
  function solarbankSceneReadings(scene) {
15186
- const list = scene.solarbank_info?.solarbank_list ?? [];
15273
+ const list2 = scene.solarbank_info?.solarbank_list ?? [];
15187
15274
  const out = [];
15188
- for (const sb of list) {
15275
+ for (const sb of list2) {
15189
15276
  const deviceSn = typeof sb.device_sn === "string" ? sb.device_sn : void 0;
15190
15277
  if (!deviceSn)
15191
15278
  continue;
@@ -16413,11 +16500,11 @@ var CMD_CAMERA_INFO = 1103;
16413
16500
  var GCM_AAD = Buffer.from("eufy security");
16414
16501
  var LEVEL2_SEQ_BASE = 16909056;
16415
16502
  function paramReport(payload) {
16416
- const list = payload?.params;
16417
- if (!Array.isArray(list))
16503
+ const list2 = payload?.params;
16504
+ if (!Array.isArray(list2))
16418
16505
  return void 0;
16419
16506
  const out = {};
16420
- for (const entry of list) {
16507
+ for (const entry of list2) {
16421
16508
  const p = entry;
16422
16509
  if (typeof p?.param_type !== "number" || p.param_value == null)
16423
16510
  continue;
@@ -16426,11 +16513,11 @@ function paramReport(payload) {
16426
16513
  return Object.keys(out).length ? out : void 0;
16427
16514
  }
16428
16515
  function channelReport(payload) {
16429
- const list = payload?.params;
16430
- if (!Array.isArray(list))
16516
+ const list2 = payload?.params;
16517
+ if (!Array.isArray(list2))
16431
16518
  return void 0;
16432
16519
  const out = {};
16433
- for (const entry of list) {
16520
+ for (const entry of list2) {
16434
16521
  const p = entry;
16435
16522
  if (typeof p?.dev_type !== "number" || typeof p.param_type !== "number" || p.param_value == null)
16436
16523
  continue;
@@ -18251,19 +18338,19 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
18251
18338
  if (!this.connectAddress)
18252
18339
  throw new Error("not connected");
18253
18340
  const channel = opts.channel ?? 0;
18254
- const keyed = opts.key !== void 0;
18341
+ const keyed2 = opts.key !== void 0;
18255
18342
  const inner = opts.rawValue ?? {
18256
- ...keyed ? { key: opts.key } : {},
18343
+ ...keyed2 ? { key: opts.key } : {},
18257
18344
  account_id: opts.accountId ?? "",
18258
18345
  cmd: subCmd,
18259
18346
  mChannel: channel,
18260
18347
  mValue3: 0,
18261
18348
  ...opts.omitPayload ? {} : { payload }
18262
18349
  };
18263
- const value = keyed ? JSON.stringify(inner).replace(/=/g, "\\u003d") : JSON.stringify(inner);
18350
+ const value = keyed2 ? JSON.stringify(inner).replace(/=/g, "\\u003d") : JSON.stringify(inner);
18264
18351
  const body = Buffer.concat([
18265
18352
  buildCommandHeader(this.seqNumber, opts.wrapCmd ?? CMD_SET_PAYLOAD),
18266
- buildStringCommandPayload(value, channel, keyed ? void 0 : this.level1Key, 1)
18353
+ buildStringCommandPayload(value, channel, keyed2 ? void 0 : this.level1Key, 1)
18267
18354
  ]);
18268
18355
  this.seqNumber = this.seqNumber + 1 & 65535;
18269
18356
  this.logger.debug(`[p2p] ${this.cfg.stationSn} sendSetPayload subCmd=${subCmd}`);
@@ -22076,15 +22163,15 @@ var Fmp4Muxer = class {
22076
22163
  avcC() {
22077
22164
  const ps = this.params;
22078
22165
  const sps = ps.sps[0] ?? Buffer.alloc(4);
22079
- const parts = [
22166
+ const parts2 = [
22080
22167
  Buffer.from([1, sps[1] ?? 66, sps[2] ?? 0, sps[3] ?? 30, 255, 224 | ps.sps.length])
22081
22168
  ];
22082
22169
  for (const s of ps.sps)
22083
- parts.push(u16(s.length), s);
22084
- parts.push(Buffer.from([ps.pps.length]));
22170
+ parts2.push(u16(s.length), s);
22171
+ parts2.push(Buffer.from([ps.pps.length]));
22085
22172
  for (const p of ps.pps)
22086
- parts.push(u16(p.length), p);
22087
- return Buffer.concat(parts);
22173
+ parts2.push(u16(p.length), p);
22174
+ return Buffer.concat(parts2);
22088
22175
  }
22089
22176
  hvcC() {
22090
22177
  const ps = this.params;
@@ -22128,10 +22215,10 @@ var Fmp4Muxer = class {
22128
22215
  const push = (nalType, nals) => {
22129
22216
  if (!nals.length)
22130
22217
  return;
22131
- const parts = [Buffer.from([128 | nalType]), u16(nals.length)];
22218
+ const parts2 = [Buffer.from([128 | nalType]), u16(nals.length)];
22132
22219
  for (const n of nals)
22133
- parts.push(u16(n.length), n);
22134
- arrays.push(Buffer.concat(parts));
22220
+ parts2.push(u16(n.length), n);
22221
+ arrays.push(Buffer.concat(parts2));
22135
22222
  };
22136
22223
  push(32, ps.vps);
22137
22224
  push(33, ps.sps);
@@ -22173,11 +22260,11 @@ var Fmp4Muxer = class {
22173
22260
  */
22174
22261
  trun(samples) {
22175
22262
  const flags = 1793;
22176
- const parts = [u32(flags), u32(samples.length), u32(0)];
22263
+ const parts2 = [u32(flags), u32(samples.length), u32(0)];
22177
22264
  for (const s of samples) {
22178
- parts.push(u32(s.duration), u32(s.data.length), u32(s.keyframe ? 33554432 : 16842752));
22265
+ parts2.push(u32(s.duration), u32(s.data.length), u32(s.keyframe ? 33554432 : 16842752));
22179
22266
  }
22180
- return box("trun", Buffer.concat(parts));
22267
+ return box("trun", Buffer.concat(parts2));
22181
22268
  }
22182
22269
  hasAudioTrack() {
22183
22270
  return !!this.audioCodec && !this.audioDisabled;
@@ -24730,10 +24817,10 @@ var H264Depacketizer = class {
24730
24817
  }
24731
24818
  if (marker) {
24732
24819
  if (!this.broken && !this.fu && this.nals.length) {
24733
- const parts = [];
24820
+ const parts2 = [];
24734
24821
  for (const n of this.nals)
24735
- parts.push(START_CODE, n);
24736
- this.onUnit(Buffer.concat(parts), timestamp);
24822
+ parts2.push(START_CODE, n);
24823
+ this.onUnit(Buffer.concat(parts2), timestamp);
24737
24824
  }
24738
24825
  this.nals = [];
24739
24826
  this.fu = void 0;
@@ -24887,18 +24974,18 @@ var PtcsReassembler = class {
24887
24974
  if (p.lastIndex === void 0)
24888
24975
  return true;
24889
24976
  let total = 0;
24890
- const parts = [];
24977
+ const parts2 = [];
24891
24978
  for (let i = 0; i <= p.lastIndex; i++) {
24892
24979
  const c = p.chunks.get(i);
24893
24980
  if (!c)
24894
24981
  return true;
24895
- parts.push(c);
24982
+ parts2.push(c);
24896
24983
  total += c.length;
24897
24984
  }
24898
24985
  this.partials.delete(key);
24899
24986
  if (total !== p.frameLength)
24900
24987
  return false;
24901
- this.onFrame(Buffer.concat(parts, total), p.channel);
24988
+ this.onFrame(Buffer.concat(parts2, total), p.channel);
24902
24989
  return true;
24903
24990
  }
24904
24991
  /** Drop frames that stopped arriving; call periodically. */
@@ -25871,6 +25958,11 @@ var StationStreamBusyError = class extends Error {
25871
25958
  this.name = "StationStreamBusyError";
25872
25959
  }
25873
25960
  };
25961
+ var RTC_CONTROL_COMMAND_KINDS = /* @__PURE__ */ new Set([
25962
+ "set-payload",
25963
+ "set-json"
25964
+ ]);
25965
+ var isRtcControlCommand = (cmd) => RTC_CONTROL_COMMAND_KINDS.has(cmd.kind);
25874
25966
  var RtcCommandRouter = class {
25875
25967
  deps;
25876
25968
  sessions = /* @__PURE__ */ new Map();
@@ -25884,7 +25976,7 @@ var RtcCommandRouter = class {
25884
25976
  this.deps = deps;
25885
25977
  }
25886
25978
  async dispatchCommand(route, cmd) {
25887
- if (cmd.kind !== "set-payload" && cmd.kind !== "set-json") {
25979
+ if (!isRtcControlCommand(cmd)) {
25888
25980
  throw new Error(`rtc: ${cmd.kind} is not routable over the RTC control channel (only set-payload or set-json)`);
25889
25981
  }
25890
25982
  if (route.mediaTrack)
@@ -28447,10 +28539,10 @@ var McsParser = class extends EventEmitter12 {
28447
28539
  }
28448
28540
  }
28449
28541
  onVersion() {
28450
- const version = this.data.readInt8(0);
28542
+ const version2 = this.data.readInt8(0);
28451
28543
  this.data = this.data.subarray(1);
28452
- if (version < 38)
28453
- throw new Error(`MCS: wrong protocol version ${version}`);
28544
+ if (version2 < 38)
28545
+ throw new Error(`MCS: wrong protocol version ${version2}`);
28454
28546
  this.onTag();
28455
28547
  }
28456
28548
  onTag() {
@@ -29140,7 +29232,272 @@ function mediaFailureTag(error) {
29140
29232
  return Number.isInteger(status) && status >= 100 && status <= 599 ? { cause, status } : { cause };
29141
29233
  }
29142
29234
 
29235
+ // dist/model/capabilities/thing-description.js
29236
+ var APP_SDK_VERSION = "2.3.3.1";
29237
+ var SUFFIXED_STATION_PREFIXES = ["T8010", "T8020", "T8021", "T8023"];
29238
+ function re(pattern, value) {
29239
+ if (!pattern || !value)
29240
+ return true;
29241
+ if (pattern.length > 512 || value.length > 2048)
29242
+ return false;
29243
+ try {
29244
+ const insensitive = pattern.includes("(?i)");
29245
+ return new RegExp(pattern.replaceAll("(?i)", ""), insensitive ? "i" : "").test(value);
29246
+ } catch {
29247
+ return false;
29248
+ }
29249
+ }
29250
+ var parts = (v) => v.split(".");
29251
+ var intOf = (s) => {
29252
+ const t = (s ?? "0").replace(/[a-zA-Z]+$/, "");
29253
+ return /^[+-]?\d+$/.test(t) ? Number(t) : void 0;
29254
+ };
29255
+ var numericPrefix = (p) => {
29256
+ const i = p.findIndex((s) => intOf(s) === void 0);
29257
+ return i < 0 ? p.length : i;
29258
+ };
29259
+ function versionAbove(a, b) {
29260
+ const pa = parts(a);
29261
+ const pb = parts(b);
29262
+ const n = Math.max(numericPrefix(pa), numericPrefix(pb));
29263
+ for (let i = 0; i < n; i++) {
29264
+ const d = (intOf(pa[i]) ?? 0) - (intOf(pb[i]) ?? 0);
29265
+ if (d !== 0)
29266
+ return d > 0;
29267
+ }
29268
+ const ta = pa.slice(n);
29269
+ const tb = pb.slice(n);
29270
+ if (!ta.length || !tb.length)
29271
+ return ta.length > 0 && !tb.length;
29272
+ const lastInt = (t) => t.findLastIndex((s) => /^[+-]?\d+$/.test(s));
29273
+ const ia = lastInt(ta);
29274
+ const ib = lastInt(tb);
29275
+ const head = (t, i) => (i >= 0 ? t.slice(0, i) : t).join(".");
29276
+ if (head(ta, ia) !== head(tb, ib))
29277
+ return false;
29278
+ return (ia >= 0 ? Number(ta[ia]) : 0) > (ib >= 0 ? Number(tb[ib]) : 0);
29279
+ }
29280
+ function versionEqual(a, b) {
29281
+ const pa = parts(a);
29282
+ const pb = parts(b);
29283
+ const n = Math.max(numericPrefix(pa), numericPrefix(pb));
29284
+ for (let i = 0; i < n; i++)
29285
+ if ((intOf(pa[i]) ?? 0) !== (intOf(pb[i]) ?? 0))
29286
+ return false;
29287
+ return pa.slice(n).join(".") === pb.slice(n).join(".");
29288
+ }
29289
+ function version(current, qualifier, target) {
29290
+ switch (qualifier) {
29291
+ case "<":
29292
+ return versionAbove(target, current);
29293
+ case "<=":
29294
+ return versionAbove(target, current) || versionEqual(current, target);
29295
+ case "=":
29296
+ return versionEqual(current, target);
29297
+ case "!=":
29298
+ return !versionEqual(current, target);
29299
+ case ">=":
29300
+ return versionAbove(current, target) || versionEqual(current, target);
29301
+ case ">":
29302
+ return versionAbove(current, target);
29303
+ case "RE":
29304
+ return re(target, current);
29305
+ default:
29306
+ return false;
29307
+ }
29308
+ }
29309
+ function compare(current, qualifier, target) {
29310
+ if (qualifier === "RE")
29311
+ return re(target, current);
29312
+ switch (qualifier) {
29313
+ case "=":
29314
+ return current === target;
29315
+ case "!=":
29316
+ return current !== target;
29317
+ case "<":
29318
+ return current < target;
29319
+ case "<=":
29320
+ return current <= target;
29321
+ case ">=":
29322
+ return current >= target;
29323
+ case ">":
29324
+ return current > target;
29325
+ default:
29326
+ return false;
29327
+ }
29328
+ }
29329
+ function cameraInfo(facts, qualifier, value) {
29330
+ let spec;
29331
+ try {
29332
+ spec = JSON.parse(value);
29333
+ } catch {
29334
+ return false;
29335
+ }
29336
+ const reported = typeof spec?.param === "number" ? facts.params?.[spec.param] : void 0;
29337
+ if (reported === void 0)
29338
+ return false;
29339
+ if (qualifier === "equals")
29340
+ return typeof spec.value === "string" && spec.value === reported;
29341
+ if (qualifier === "RE")
29342
+ return typeof spec.regex === "string" && re(spec.regex, reported);
29343
+ return false;
29344
+ }
29345
+ function condition(c, facts) {
29346
+ const station = facts.stationSerial ?? facts.serial ?? "";
29347
+ switch (c.type) {
29348
+ case "device_version":
29349
+ return version(facts.firmwareVersion ?? "", c.qualifier, c.value);
29350
+ case "parent_version": {
29351
+ const v = facts.stationFirmwareVersion ?? "";
29352
+ const clean = SUFFIXED_STATION_PREFIXES.some((p) => station.startsWith(p)) ? v.replace(/[^0-9.][^.]*/g, "") : v;
29353
+ return version(clean, c.qualifier, c.value);
29354
+ }
29355
+ case "parent_sn":
29356
+ return re(c.value, station);
29357
+ case "device_sn":
29358
+ return re(c.value, facts.serial ?? "");
29359
+ case "user_country":
29360
+ return re(c.value, facts.country ?? "");
29361
+ case "user_domain":
29362
+ return re(c.value, "");
29363
+ case "sdk_version":
29364
+ return version(APP_SDK_VERSION, c.qualifier, c.value);
29365
+ case "associated_field":
29366
+ case "parent_associated_field":
29367
+ return compare("", c.qualifier, c.value);
29368
+ case "user_permission":
29369
+ return c.qualifier === "in";
29370
+ case "camera_info":
29371
+ return cameraInfo(facts, c.qualifier, c.value);
29372
+ default:
29373
+ return false;
29374
+ }
29375
+ }
29376
+ function conditionsPass(list2, facts) {
29377
+ return list2.length === 0 || list2.some((entry) => entry.every((c) => condition(c, facts)));
29378
+ }
29379
+ var bare = (identifier) => {
29380
+ const i = identifier.toLowerCase().indexOf("__v");
29381
+ return i > 0 ? identifier.slice(0, i) : identifier;
29382
+ };
29383
+ function resolveThing(td, facts) {
29384
+ const properties = /* @__PURE__ */ Object.create(null);
29385
+ const keys = Object.keys(td.properties);
29386
+ for (const base of new Set(keys.map(bare))) {
29387
+ let chosen = base;
29388
+ let most = -1;
29389
+ for (const key of keys) {
29390
+ if (key === base || bare(key) !== base)
29391
+ continue;
29392
+ const p2 = td.properties[key];
29393
+ if (p2.readConditions.length === 0 || !conditionsPass(p2.readConditions, facts))
29394
+ continue;
29395
+ const count2 = p2.readConditions.reduce((n, entry) => n + entry.length, 0);
29396
+ if (count2 > most)
29397
+ [chosen, most] = [key, count2];
29398
+ }
29399
+ const p = td.properties[chosen];
29400
+ if (!p || !conditionsPass(p.availableConditions, facts))
29401
+ continue;
29402
+ properties[base] = conditionsPass(p.writeConditions, facts) ? p : { ...p, accessMode: "R" };
29403
+ }
29404
+ const available = (entries) => {
29405
+ const out = /* @__PURE__ */ Object.create(null);
29406
+ for (const [k, v] of Object.entries(entries))
29407
+ if (conditionsPass(v.availableConditions, facts))
29408
+ out[k] = v;
29409
+ return out;
29410
+ };
29411
+ return { ...td, properties, actions: available(td.actions), events: available(td.events) };
29412
+ }
29413
+ var str3 = (v) => typeof v === "string" ? v : v == null ? "" : String(v);
29414
+ var optStr = (v) => v == null || v === "" ? void 0 : str3(v);
29415
+ var num2 = (v) => {
29416
+ const n = typeof v === "number" ? v : typeof v === "string" && v.trim() !== "" ? Number(v) : NaN;
29417
+ return Number.isFinite(n) ? n : void 0;
29418
+ };
29419
+ var list = (v) => Array.isArray(v) ? v.filter((x) => x && typeof x === "object") : [];
29420
+ function conditions(v) {
29421
+ return list(v?.condition_list).map((entry) => list(entry.conditions).map((c) => ({
29422
+ type: str3(c.type),
29423
+ qualifier: str3(c.qualifier),
29424
+ value: str3(c.value),
29425
+ ...c.prop_id ? { propId: str3(c.prop_id) } : {}
29426
+ })));
29427
+ }
29428
+ function keyed(entries, parse) {
29429
+ const out = /* @__PURE__ */ Object.create(null);
29430
+ for (const e of entries)
29431
+ if (typeof e.identifier === "string" && e.identifier)
29432
+ out[e.identifier] = parse(e);
29433
+ return out;
29434
+ }
29435
+ function property(p) {
29436
+ const specs = p.data_type?.specs ?? {};
29437
+ return {
29438
+ identifier: p.identifier,
29439
+ accessMode: str3(p.access_mode),
29440
+ type: str3(p.data_type?.type),
29441
+ members: list(specs.eunmList).map((m) => ({
29442
+ value: str3(m.value),
29443
+ ...m.associatedValue != null ? { wireValue: str3(m.associatedValue) } : {},
29444
+ desc: str3(m.desc),
29445
+ isDefault: m.isDefault === 1 || m.isDefault === true
29446
+ })),
29447
+ ...optStr(specs.defaultValue) !== void 0 ? { defaultValue: str3(specs.defaultValue) } : {},
29448
+ ...num2(specs.min) !== void 0 ? { min: num2(specs.min) } : {},
29449
+ ...num2(specs.max) !== void 0 ? { max: num2(specs.max) } : {},
29450
+ ...num2(specs.step) !== void 0 ? { step: num2(specs.step) } : {},
29451
+ ...optStr(specs.unit) !== void 0 ? { unit: str3(specs.unit) } : {},
29452
+ readConditions: conditions(p.read_conditions),
29453
+ writeConditions: conditions(p.write_conditions),
29454
+ availableConditions: conditions(p.available_conditions)
29455
+ };
29456
+ }
29457
+ function parseThingsList(raw) {
29458
+ const out = /* @__PURE__ */ Object.create(null);
29459
+ for (const t of list(raw?.things_list)) {
29460
+ const profile = t.profile ?? {};
29461
+ const code = profile.product_code;
29462
+ if (typeof code !== "string" || !code)
29463
+ continue;
29464
+ out[code] = {
29465
+ productCode: code,
29466
+ version: str3(t.version),
29467
+ updateTime: num2(t.update_time) ?? 0,
29468
+ largeVersion: num2(t.large_version) ?? 0,
29469
+ largeVersionUpdateTime: num2(t.large_version_update_time) ?? 0,
29470
+ bundle: {
29471
+ id: str3(profile.plugin_id),
29472
+ path: str3(profile.plugin_path),
29473
+ zipId: str3(profile.plugin_id_zip),
29474
+ zipPath: str3(profile.plugin_path_zip)
29475
+ },
29476
+ properties: keyed(list(t.properties), property),
29477
+ actions: keyed(list(t.actions), (a) => ({
29478
+ identifier: a.identifier,
29479
+ ...optStr(a.input_data?.type) ? { inputType: str3(a.input_data.type) } : {},
29480
+ ...optStr(a.output_data?.type) ? { outputType: str3(a.output_data.type) } : {},
29481
+ availableConditions: conditions(a.available_conditions)
29482
+ })),
29483
+ events: keyed(list(t.events), (e) => ({
29484
+ identifier: e.identifier,
29485
+ eventType: str3(e.event_type),
29486
+ ...optStr(e.output_data?.type) ? { outputType: str3(e.output_data.type) } : {},
29487
+ ...optStr(e.output_data?.specs?.defaultValue) ? { defaultValue: str3(e.output_data.specs.defaultValue) } : {},
29488
+ availableConditions: conditions(e.available_conditions)
29489
+ }))
29490
+ };
29491
+ }
29492
+ return out;
29493
+ }
29494
+
29143
29495
  // dist/client/device-registry.js
29496
+ function recordString(raw, key) {
29497
+ const nested = raw.deviceParams?.[key];
29498
+ const v = typeof nested === "string" && nested ? nested : raw[key];
29499
+ return typeof v === "string" && v ? v : void 0;
29500
+ }
29144
29501
  function mergeParams(raw, params, paramUpdatedAt) {
29145
29502
  for (const p of raw ?? []) {
29146
29503
  if (!p || p.param_type == null || p.param_value == null)
@@ -29211,6 +29568,8 @@ var DeviceRegistry = class {
29211
29568
  deviceCapsCache = /* @__PURE__ */ new Map();
29212
29569
  /** Per-serial cloud data points of an AIoT robot, joined beneath the params in {@link record} — see {@link loadCloudDps}. */
29213
29570
  cloudDps = /* @__PURE__ */ new Map();
29571
+ /** Per-product-code thing description — see {@link loadThings}; `undefined` for a code the cloud returned none for. */
29572
+ things = /* @__PURE__ */ new Map();
29214
29573
  /** Per-serial realtime state, keyed by param id in the device's own namespace (see {@link DeviceRecord.dpParams}). */
29215
29574
  dpParams = /* @__PURE__ */ new Map();
29216
29575
  /** Callers blocked in {@link awaitRealtimeState}, released by the device's first report. */
@@ -29324,6 +29683,7 @@ var DeviceRegistry = class {
29324
29683
  }
29325
29684
  }
29326
29685
  await this.loadCloudDps(seen);
29686
+ await this.loadThings(seen);
29327
29687
  this.lastRefreshPartial = partial;
29328
29688
  if (partial) {
29329
29689
  for (const prev of this.devices)
@@ -29450,6 +29810,45 @@ var DeviceRegistry = class {
29450
29810
  this.cloudDps.set(sn, dps);
29451
29811
  }
29452
29812
  }
29813
+ /**
29814
+ * The device's product description resolved for this device ({@link resolveThing}): against its serial
29815
+ * and firmware (`main_sw_version`), the station it is reached through and that station's firmware, the
29816
+ * account's country and its reported params.
29817
+ */
29818
+ thingsFor(dev, station, params) {
29819
+ const td = this.things.get(dev.model ?? "");
29820
+ if (!td)
29821
+ return void 0;
29822
+ const firmware = (d) => recordString(d?.raw ?? {}, "main_sw_version");
29823
+ return resolveThing(td, {
29824
+ serial: dev.sn,
29825
+ firmwareVersion: firmware(dev),
29826
+ stationSerial: station,
29827
+ stationFirmwareVersion: firmware(this.devices.find((d) => d.sn === station)),
29828
+ country: this.mega.countryCode,
29829
+ params
29830
+ });
29831
+ }
29832
+ /**
29833
+ * Fetch, once per start, the thing description of each product code the account has, in one
29834
+ * `get_things_list` call. A failed call is logged and asked again on the next refresh.
29835
+ */
29836
+ async loadThings(seen) {
29837
+ const codes = [...new Set([...seen.values()].map((d) => d.model ?? ""))].filter((c) => c && !this.things.has(c));
29838
+ if (!codes.length)
29839
+ return;
29840
+ let parsed;
29841
+ try {
29842
+ parsed = parseThingsList(await this.mega.getThingsList(codes));
29843
+ } catch (e) {
29844
+ if (e instanceof SessionExpiredError)
29845
+ throw e;
29846
+ this.logger.debug(`[registry] thing descriptions unavailable: ${e?.message ?? e}`);
29847
+ return;
29848
+ }
29849
+ for (const code of codes)
29850
+ this.things.set(code, parsed[code]);
29851
+ }
29453
29852
  /**
29454
29853
  * Resolve a serial to a {@link DeviceRecord} with current params: starts from the device-list params, then
29455
29854
  * overlays a fresh `get_device_param_list` when that call is available to this account. An AIoT robot's
@@ -29514,7 +29913,8 @@ var DeviceRegistry = class {
29514
29913
  parentSn: station === sn ? void 0 : station,
29515
29914
  params,
29516
29915
  paramUpdatedAt,
29517
- dpParams: this.dpParams.get(sn)
29916
+ dpParams: this.dpParams.get(sn),
29917
+ things: this.thingsFor(dev, station, params)
29518
29918
  };
29519
29919
  }
29520
29920
  /**
@@ -29783,11 +30183,6 @@ var PushRepeats = class {
29783
30183
  };
29784
30184
 
29785
30185
  // dist/client/eufy-mega.js
29786
- function recordString(raw, key) {
29787
- const nested = raw.deviceParams?.[key];
29788
- const v = typeof nested === "string" && nested ? nested : raw[key];
29789
- return typeof v === "string" && v ? v : void 0;
29790
- }
29791
30186
  function adminUserIdFrom(raw) {
29792
30187
  const member = (raw ?? {}).member;
29793
30188
  const id = member?.admin_user_id;
@@ -29808,6 +30203,16 @@ var PUSH_RETRY_MIN_MS = 3e4;
29808
30203
  var PUSH_RETRY_MAX_MS = 6e5;
29809
30204
  var STATION_REOPEN_MIN_MS = 5e3;
29810
30205
  var STATION_REOPEN_MAX_MS = 3e5;
30206
+ var PORTAL_COMMAND_KINDS = /* @__PURE__ */ new Set([
30207
+ "set-payload",
30208
+ "set-json",
30209
+ "set-param",
30210
+ "set-json-raw",
30211
+ "p2p-station-scalar",
30212
+ "p2p-int-string",
30213
+ "p2p-privacy-burst"
30214
+ ]);
30215
+ var RTC_CONTROL_REFUSED_KINDS = new Set([...PORTAL_COMMAND_KINDS].filter((kind) => !RTC_CONTROL_COMMAND_KINDS.has(kind)));
29811
30216
  var RealtimeStartupSupersededError = class extends Error {
29812
30217
  };
29813
30218
  var RefreshWindowClosedError = class extends Error {
@@ -31919,6 +32324,8 @@ var EufyMega = class _EufyMega extends EventEmitter14 {
31919
32324
  const parentRaw = parent?.raw;
31920
32325
  const resolved = resolveDevice(rec);
31921
32326
  const dpCatalog = (resolved.codec === "vacuum" || resolved.codec === "mower") && rec.model ? await this.fetchDpCatalog(rec.model) : void 0;
32327
+ const rtcRoute = this.rtcRouteFor(sn);
32328
+ const refusedCommandKinds = !rtcRoute ? void 0 : rtcRoute.mediaTrack ? PORTAL_COMMAND_KINDS : RTC_CONTROL_REFUSED_KINDS;
31922
32329
  return {
31923
32330
  channel,
31924
32331
  timezone: recordString(parentRaw ?? {}, "time_zone"),
@@ -31939,6 +32346,7 @@ var EufyMega = class _EufyMega extends EventEmitter14 {
31939
32346
  macAddress: recordString(raw, "wifi_mac"),
31940
32347
  updateAvailable: raw.needUpdate === true || raw.needUpdate === 1 || raw.needUpdate === "1",
31941
32348
  paramIds: new Set([...Object.keys(rec.params), ...Object.keys(rec.dpParams ?? {})].map(Number)),
32349
+ things: rec.things,
31942
32350
  capabilities: new Set(resolved.capabilities),
31943
32351
  // Member identity for ff09-* command devices (see core/contracts Ff09Identity).
31944
32352
  adminUserId: typeof member.admin_user_id === "string" ? member.admin_user_id : void 0,
@@ -31950,6 +32358,7 @@ var EufyMega = class _EufyMega extends EventEmitter14 {
31950
32358
  // Topology as the record states it: a parent that isn't the device itself means HomeBase-attached.
31951
32359
  homeBaseAttached: !!raw.parent_sn && raw.parent_sn !== dev.sn,
31952
32360
  stationDeviceType: this.stationDeviceTypeOf(sn),
32361
+ refusedCommandKinds,
31953
32362
  dpCatalog
31954
32363
  };
31955
32364
  }
@@ -32458,8 +32867,8 @@ var SolixClient = class _SolixClient {
32458
32867
  */
32459
32868
  async getSites() {
32460
32869
  const data = await this.authed("POST", SOLIX_ENDPOINTS.getSiteList, {});
32461
- const list = data?.site_list ?? [];
32462
- return list.filter((s) => typeof s?.site_id === "string" && s.site_id.length > 0);
32870
+ const list2 = data?.site_list ?? [];
32871
+ return list2.filter((s) => typeof s?.site_id === "string" && s.site_id.length > 0);
32463
32872
  }
32464
32873
  /** Per-user AWS-IoT MQTT credentials (cert/key/endpoint/thing) for the real-time device plane. */
32465
32874
  async getUserMqttInfo() {
@@ -32727,21 +33136,21 @@ function solixStateReadings(frame) {
32727
33136
  continue;
32728
33137
  const type = value[0];
32729
33138
  const pl = value.subarray(1);
32730
- let num2;
33139
+ let num3;
32731
33140
  if (type === 5 && pl.length >= 4)
32732
- num2 = pl.readFloatLE(0);
33141
+ num3 = pl.readFloatLE(0);
32733
33142
  else if (type === 2 && pl.length >= 2)
32734
- num2 = pl.readUInt16LE(0);
33143
+ num3 = pl.readUInt16LE(0);
32735
33144
  else if (type === 1 && pl.length >= 1)
32736
- num2 = pl[0];
33145
+ num3 = pl[0];
32737
33146
  else if (type === 3 && pl.length >= 2)
32738
- num2 = pl[1];
32739
- if (num2 === void 0)
33147
+ num3 = pl[1];
33148
+ if (num3 === void 0)
32740
33149
  continue;
32741
- out[`state_${tag2.toString(16)}`] = num2;
33150
+ out[`state_${tag2.toString(16)}`] = num3;
32742
33151
  const name = SOLIX_STATE_FIELD_NAMES[tag2];
32743
33152
  if (name)
32744
- out[name] = num2;
33153
+ out[name] = num3;
32745
33154
  }
32746
33155
  return out;
32747
33156
  }
@@ -33027,9 +33436,9 @@ var SolixMqtt = class extends EventEmitter15 {
33027
33436
  const frame = decodeSolixParamFrame(buf);
33028
33437
  if (!frame)
33029
33438
  return;
33030
- const parts = topic.split("/");
33031
- const productCode = parts[2] ?? "";
33032
- let deviceSn = frame.deviceSn ?? parts[3] ?? "";
33439
+ const parts2 = topic.split("/");
33440
+ const productCode = parts2[2] ?? "";
33441
+ let deviceSn = frame.deviceSn ?? parts2[3] ?? "";
33033
33442
  if (!this.watched.has(deviceSn)) {
33034
33443
  const ofProduct = [...this.watched.values()].filter((d) => d.product_code === productCode);
33035
33444
  if (ofProduct.length === 1)
@@ -33067,9 +33476,9 @@ var SolixMqtt = class extends EventEmitter15 {
33067
33476
  values.displayTimeoutIndex = a5[1];
33068
33477
  if (Object.keys(values).length === 0)
33069
33478
  return;
33070
- const parts = topic.split("/");
33071
- const productCode = parts[2] ?? "";
33072
- let deviceSn = frame.deviceSn ?? parts[3] ?? "";
33479
+ const parts2 = topic.split("/");
33480
+ const productCode = parts2[2] ?? "";
33481
+ let deviceSn = frame.deviceSn ?? parts2[3] ?? "";
33073
33482
  if (!this.watched.has(deviceSn)) {
33074
33483
  const ofProduct = [...this.watched.values()].filter((d) => d.product_code === productCode);
33075
33484
  if (ofProduct.length === 1)