Skip to content

Commit 5d965eb

Browse files
authored
arlo: upstreaming changes to 0.8.15 (#982)
* wip hidden devices * hidden devices impl * bump 0.8.12 for beta * update backup auth hosts * bump 0.8.13 for release * use curl-cffi everywhere * bump 0.8.14 for beta * Revert "use curl-cffi everywhere" This reverts commit 80422a8. * update auth hosts * bump 0.8.15 for release
1 parent b462249 commit 5d965eb

File tree

7 files changed

+73
-11
lines changed

7 files changed

+73
-11
lines changed

plugins/arlo/README.md

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ If you experience any trouble logging in, clear the username and password boxes,
1010

1111
If you are unable to see shared cameras in your separate Arlo account, ensure that both your primary and secondary accounts are upgraded according to this [forum post](https://web.archive.org/web/20230710141914/https://community.arlo.com/t5/Arlo-Secure/Invited-friend-cannot-see-devices-on-their-dashboard-Arlo-Pro-2/m-p/1889396#M1813). Verify the sharing worked by logging in via the Arlo web dashboard.
1212

13+
**If you add or remove cameras from your main Arlo account, or share/un-share/re-share cameras with the Arlo account used with this plugin, ensure that you reload this plugin to get the updated camera state from Arlo Cloud.**
14+
1315
## General Setup Notes
1416

1517
* Ensure that your Arlo account's default 2FA option is set to either SMS or email.

plugins/arlo/package-lock.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

plugins/arlo/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@scrypted/arlo",
3-
"version": "0.8.11",
3+
"version": "0.8.15",
44
"description": "Arlo Plugin for Scrypted",
55
"license": "Apache",
66
"keywords": [

plugins/arlo/src/arlo_plugin/arlo/arlo_async.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def change_stream_class(s_class):
9292
class Arlo(object):
9393
BASE_URL = 'my.arlo.com'
9494
AUTH_URL = 'ocapi-app.arlo.com'
95-
BACKUP_AUTH_HOSTS = ['NTIuMjEwLjMuMTIx', 'MzQuMjU1LjkyLjIxMg==', 'MzQuMjUxLjE3Ny45MA==', 'NTQuMjQ2LjE3MS4x']
95+
BACKUP_AUTH_HOSTS = ["MzQuMjQzLjE3Ljkw","MzQuMjQ4Ljg2LjQ2","MzQuMjQwLjI0Ny4xODY=","NTIuMzEuMTg1LjE4NQ=="]
9696
TRANSID_PREFIX = 'web'
9797

9898
random.shuffle(BACKUP_AUTH_HOSTS)
@@ -102,6 +102,7 @@ def __init__(self, username, password):
102102
self.password = password
103103
self.event_stream = None
104104
self.request = None
105+
self.logged_in = False
105106

106107
def to_timestamp(self, dt):
107108
if sys.version[0] == '2':
@@ -153,6 +154,7 @@ def UseExistingAuth(self, user_id, headers):
153154
self.request = Request(mode="cloudscraper")
154155
self.request.session.headers.update(headers)
155156
self.BASE_URL = 'myapi.arlo.com'
157+
self.logged_in = True
156158

157159
def LoginMFA(self):
158160
device_id = str(uuid.uuid4())
@@ -258,6 +260,7 @@ def complete_auth(code):
258260
}
259261
self.request.session.headers.update(headers)
260262
self.BASE_URL = 'myapi.arlo.com'
263+
self.logged_in = True
261264

262265
return complete_auth
263266

plugins/arlo/src/arlo_plugin/base.py

+3
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ def get_device_manifest(self) -> Device:
5757
if self.arlo_device.get("parentId") and self.arlo_device["parentId"] != self.arlo_device["deviceId"]:
5858
parent = self.arlo_device["parentId"]
5959

60+
if parent in self.provider.hidden_device_ids:
61+
parent = None
62+
6063
return {
6164
"info": {
6265
"model": f"{self.arlo_device['modelId']} {self.arlo_device['properties'].get('hwVersion', '')}".strip(),

plugins/arlo/src/arlo_plugin/camera.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -516,10 +516,10 @@ async def _getVideoStreamURL(self, container: str) -> str:
516516
return url
517517

518518
@async_print_exception_guard
519-
async def getVideoStream(self, options: RequestMediaStreamOptions = None) -> MediaObject:
519+
async def getVideoStream(self, options: RequestMediaStreamOptions = {}) -> MediaObject:
520520
self.logger.debug("Entered getVideoStream")
521521

522-
mso = await self.getVideoStreamOptions(id=options["id"])
522+
mso = await self.getVideoStreamOptions(id=options.get("id", "default"))
523523
mso['refreshAt'] = round(time.time() * 1000) + 30 * 60 * 1000
524524
container = mso["container"]
525525

plugins/arlo/src/arlo_plugin/provider.py

+59-5
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,10 @@
2727
class ArloProvider(ScryptedDeviceBase, Settings, DeviceProvider, ScryptedDeviceLoggerMixin, BackgroundTaskMixin):
2828
arlo_cameras = None
2929
arlo_basestations = None
30+
all_device_ids: set = set()
3031
_arlo_mfa_code = None
3132
scrypted_devices = None
32-
_arlo = None
33+
_arlo: Arlo = None
3334
_arlo_mfa_complete_auth = None
3435
device_discovery_lock: asyncio.Lock = None
3536

@@ -157,6 +158,23 @@ def imap_mfa_interval(self) -> int:
157158
self.storage.setItem("imap_mfa_interval", interval)
158159
return int(interval)
159160

161+
@property
162+
def hidden_devices(self) -> List[str]:
163+
hidden = self.storage.getItem("hidden_devices")
164+
if hidden is None:
165+
hidden = []
166+
self.storage.setItem("hidden_devices", hidden)
167+
return hidden
168+
169+
@property
170+
def hidden_device_ids(self) -> List[str]:
171+
ids = []
172+
for id in self.hidden_devices:
173+
m = re.match(r".*\((.*)\)$", id)
174+
if m is not None:
175+
ids.append(m.group(1))
176+
return ids
177+
160178
@property
161179
def arlo(self) -> Arlo:
162180
if self._arlo is not None:
@@ -530,6 +548,16 @@ async def getSettings(self) -> List[Setting]:
530548
"value": self.plugin_verbosity == "Verbose",
531549
"type": "boolean",
532550
},
551+
{
552+
"group": "General",
553+
"key": "hidden_devices",
554+
"title": "Hidden Devices",
555+
"description": "Select the Arlo devices to hide in this plugin. Hidden devices will be removed from Scrypted and will "
556+
"not be re-added when the plugin reloads.",
557+
"value": self.hidden_devices,
558+
"multiple": True,
559+
"choices": [id for id in self.all_device_ids],
560+
},
533561
])
534562

535563
return results
@@ -573,6 +601,11 @@ async def putSetting(self, key: str, value: SettingValue) -> None:
573601
elif key.startswith("imap_mfa"):
574602
self.initialize_imap()
575603
skip_arlo_client = True
604+
elif key == "hidden_devices":
605+
if self._arlo is not None and self._arlo.logged_in:
606+
self._arlo.Unsubscribe()
607+
await self.do_arlo_setup()
608+
skip_arlo_client = True
576609
else:
577610
# force arlo client to be invalidated and reloaded
578611
self.invalidate_arlo_client()
@@ -618,12 +651,13 @@ async def discover_devices(self) -> None:
618651
return await self.discover_devices_impl()
619652

620653
async def discover_devices_impl(self) -> None:
621-
if not self.arlo:
654+
if not self._arlo or not self._arlo.logged_in:
622655
raise Exception("Arlo client not connected, cannot discover devices")
623656

624657
self.logger.info("Discovering devices...")
625658
self.arlo_cameras = {}
626659
self.arlo_basestations = {}
660+
self.all_device_ids = set()
627661
self.scrypted_devices = {}
628662

629663
camera_devices = []
@@ -632,13 +666,20 @@ async def discover_devices_impl(self) -> None:
632666
basestations = self.arlo.GetDevices(['basestation', 'siren'])
633667
for basestation in basestations:
634668
nativeId = basestation["deviceId"]
669+
self.all_device_ids.add(f"{basestation['deviceName']} ({nativeId})")
670+
635671
self.logger.debug(f"Adding {nativeId}")
636672

637673
if nativeId in self.arlo_basestations:
638674
self.logger.info(f"Skipping basestation {nativeId} ({basestation['modelId']}) as it has already been added")
639675
continue
676+
640677
self.arlo_basestations[nativeId] = basestation
641678

679+
if nativeId in self.hidden_device_ids:
680+
self.logger.info(f"Skipping manifest for basestation {nativeId} ({basestation['modelId']}) as it is hidden")
681+
continue
682+
642683
device = await self.getDevice_impl(nativeId)
643684
scrypted_interfaces = device.get_applicable_interfaces()
644685
manifest = device.get_device_manifest()
@@ -657,11 +698,13 @@ async def discover_devices_impl(self) -> None:
657698
await scrypted_sdk.deviceManager.onDeviceDiscovered(child_manifest)
658699
provider_to_device_map.setdefault(child_manifest["providerNativeId"], []).append(child_manifest)
659700

660-
self.logger.info(f"Discovered {len(basestations)} basestations")
701+
self.logger.info(f"Discovered {len(self.arlo_basestations)} basestations")
661702

662703
cameras = self.arlo.GetDevices(['camera', "arloq", "arloqs", "doorbell"])
663704
for camera in cameras:
664705
nativeId = camera["deviceId"]
706+
self.all_device_ids.add(f"{camera['deviceName']} ({nativeId})")
707+
665708
self.logger.debug(f"Adding {nativeId}")
666709

667710
if camera["deviceId"] != camera["parentId"] and camera["parentId"] not in self.arlo_basestations:
@@ -671,6 +714,11 @@ async def discover_devices_impl(self) -> None:
671714
if nativeId in self.arlo_cameras:
672715
self.logger.info(f"Skipping camera {nativeId} ({camera['modelId']}) as it has already been added")
673716
continue
717+
718+
if nativeId in self.hidden_device_ids:
719+
self.logger.info(f"Skipping camera {camera['deviceId']} ({camera['modelId']}) because it is hidden")
720+
continue
721+
674722
self.arlo_cameras[nativeId] = camera
675723

676724
if camera["deviceId"] == camera["parentId"]:
@@ -683,7 +731,7 @@ async def discover_devices_impl(self) -> None:
683731
manifest = device.get_device_manifest()
684732
self.logger.debug(f"Interfaces for {nativeId} ({camera['modelId']} parent {camera['parentId']}): {scrypted_interfaces}")
685733

686-
if camera["deviceId"] == camera["parentId"]:
734+
if camera["deviceId"] == camera["parentId"] or camera["parentId"] in self.hidden_device_ids:
687735
provider_to_device_map.setdefault(None, []).append(manifest)
688736
else:
689737
provider_to_device_map.setdefault(camera["parentId"], []).append(manifest)
@@ -701,7 +749,9 @@ async def discover_devices_impl(self) -> None:
701749

702750
if len(cameras) != len(camera_devices):
703751
self.logger.info(f"Discovered {len(cameras)} cameras, but only {len(camera_devices)} are usable")
704-
self.logger.info(f"Are all cameras shared with admin permissions?")
752+
self.logger.info("This could be because some cameras are hidden.")
753+
self.logger.info("If a camera is not hidden but is still missing, ensure all cameras shared with "
754+
"admin permissions in the Arlo app.")
705755
else:
706756
self.logger.info(f"Discovered {len(cameras)} cameras")
707757

@@ -726,7 +776,11 @@ async def discover_devices_impl(self) -> None:
726776
})
727777
self.logger.debug("Done discovering devices")
728778

779+
# force a settings refresh so the hidden devices list can be updated
780+
await self.onDeviceEvent(ScryptedInterface.Settings.value, None)
781+
729782
async def getDevice(self, nativeId: str) -> ArloDeviceBase:
783+
self.logger.debug(f"Scrypted requested to load device {nativeId}")
730784
async with self.device_discovery_lock:
731785
return await self.getDevice_impl(nativeId)
732786

0 commit comments

Comments
 (0)