pypetkitapi 1.11.0__py3-none-any.whl → 1.11.3__py3-none-any.whl
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.
- pypetkitapi/__init__.py +1 -1
- pypetkitapi/media.py +39 -21
- {pypetkitapi-1.11.0.dist-info → pypetkitapi-1.11.3.dist-info}/METADATA +12 -10
- {pypetkitapi-1.11.0.dist-info → pypetkitapi-1.11.3.dist-info}/RECORD +6 -6
- {pypetkitapi-1.11.0.dist-info → pypetkitapi-1.11.3.dist-info}/LICENSE +0 -0
- {pypetkitapi-1.11.0.dist-info → pypetkitapi-1.11.3.dist-info}/WHEEL +0 -0
pypetkitapi/__init__.py
CHANGED
pypetkitapi/media.py
CHANGED
@@ -18,13 +18,14 @@ import aiohttp
|
|
18
18
|
from Crypto.Cipher import AES
|
19
19
|
from Crypto.Util.Padding import unpad
|
20
20
|
|
21
|
-
from pypetkitapi import Feeder, Litter, PetKitClient, RecordType
|
21
|
+
from pypetkitapi import Feeder, Litter, PetKitClient, RecordsItems, RecordType
|
22
22
|
from pypetkitapi.const import (
|
23
23
|
FEEDER_WITH_CAMERA,
|
24
24
|
LITTER_WITH_CAMERA,
|
25
25
|
MediaType,
|
26
26
|
RecordTypeLST,
|
27
27
|
)
|
28
|
+
from pypetkitapi.litter_container import LitterRecord
|
28
29
|
|
29
30
|
_LOGGER = logging.getLogger(__name__)
|
30
31
|
|
@@ -269,21 +270,22 @@ class MediaManager:
|
|
269
270
|
device_type = (
|
270
271
|
device_obj.device_nfo.device_type if device_obj.device_nfo else None
|
271
272
|
)
|
272
|
-
cp_sub = (
|
273
|
-
device_obj.cloud_product.subscribe if device_obj.cloud_product else None
|
274
|
-
)
|
273
|
+
cp_sub = self.is_subscription_active(device_obj)
|
275
274
|
|
276
275
|
if not feeder_id:
|
277
|
-
_LOGGER.
|
276
|
+
_LOGGER.warning("Missing feeder_id for record")
|
278
277
|
return media_files
|
279
278
|
|
280
279
|
if not record.items:
|
281
280
|
return media_files
|
282
281
|
|
283
282
|
for item in record.items:
|
283
|
+
if not isinstance(item, RecordsItems):
|
284
|
+
_LOGGER.debug("Record is empty")
|
285
|
+
continue
|
284
286
|
timestamp = await self._get_timestamp(item)
|
285
287
|
if timestamp is None:
|
286
|
-
_LOGGER.
|
288
|
+
_LOGGER.warning("Missing timestamp for record item")
|
287
289
|
continue
|
288
290
|
if not item.event_id:
|
289
291
|
# Skip feed event in the future
|
@@ -292,10 +294,10 @@ class MediaManager:
|
|
292
294
|
)
|
293
295
|
continue
|
294
296
|
if not user_id:
|
295
|
-
_LOGGER.
|
297
|
+
_LOGGER.warning("Missing user_id for record item")
|
296
298
|
continue
|
297
299
|
if not item.aes_key:
|
298
|
-
_LOGGER.
|
300
|
+
_LOGGER.debug("Missing aes_key for record item")
|
299
301
|
continue
|
300
302
|
|
301
303
|
date_str = await self.get_date_from_ts(timestamp)
|
@@ -327,24 +329,27 @@ class MediaManager:
|
|
327
329
|
litter_id = litter.device_nfo.device_id if litter.device_nfo else None
|
328
330
|
device_type = litter.device_nfo.device_type if litter.device_nfo else None
|
329
331
|
user_id = litter.user.id if litter.user else None
|
330
|
-
cp_sub =
|
332
|
+
cp_sub = self.is_subscription_active(litter)
|
331
333
|
|
332
334
|
if not litter_id:
|
333
|
-
_LOGGER.
|
335
|
+
_LOGGER.warning("Missing litter_id for record")
|
334
336
|
return media_files
|
335
337
|
|
336
338
|
if not device_type:
|
337
|
-
_LOGGER.
|
339
|
+
_LOGGER.warning("Missing device_type for record")
|
338
340
|
return media_files
|
339
341
|
|
340
342
|
if not user_id:
|
341
|
-
_LOGGER.
|
343
|
+
_LOGGER.warning("Missing user_id for record")
|
342
344
|
return media_files
|
343
345
|
|
344
346
|
if not records:
|
345
347
|
return media_files
|
346
348
|
|
347
349
|
for record in records:
|
350
|
+
if not isinstance(record, LitterRecord):
|
351
|
+
_LOGGER.debug("Record is empty")
|
352
|
+
continue
|
348
353
|
if not record.event_id:
|
349
354
|
_LOGGER.debug("Missing event_id for record item")
|
350
355
|
continue
|
@@ -376,6 +381,19 @@ class MediaManager:
|
|
376
381
|
)
|
377
382
|
return media_files
|
378
383
|
|
384
|
+
@staticmethod
|
385
|
+
def is_subscription_active(device: Feeder | Litter) -> bool:
|
386
|
+
"""Check if the subscription is active based on the work_indate timestamp.
|
387
|
+
:param device: Device object
|
388
|
+
:return: True if the subscription is active, False otherwise
|
389
|
+
"""
|
390
|
+
if device.cloud_product and device.cloud_product.work_indate:
|
391
|
+
return (
|
392
|
+
datetime.fromtimestamp(device.cloud_product.work_indate)
|
393
|
+
> datetime.now()
|
394
|
+
)
|
395
|
+
return False
|
396
|
+
|
379
397
|
@staticmethod
|
380
398
|
async def get_date_from_ts(timestamp: int | None) -> str:
|
381
399
|
"""Get date from timestamp.
|
@@ -388,7 +406,10 @@ class MediaManager:
|
|
388
406
|
|
389
407
|
@staticmethod
|
390
408
|
async def construct_video_url(
|
391
|
-
device_type: str | None,
|
409
|
+
device_type: str | None,
|
410
|
+
media_url: str | None,
|
411
|
+
user_id: int,
|
412
|
+
cp_sub: bool | None,
|
392
413
|
) -> str | None:
|
393
414
|
"""Construct the video URL.
|
394
415
|
:param device_type: Device type
|
@@ -397,19 +418,19 @@ class MediaManager:
|
|
397
418
|
:param cp_sub: Cpsub value
|
398
419
|
:return: Constructed video URL
|
399
420
|
"""
|
400
|
-
if not media_url or not user_id or cp_sub
|
421
|
+
if not media_url or not user_id or not cp_sub:
|
401
422
|
return None
|
402
423
|
params = parse_qs(urlparse(media_url).query)
|
403
424
|
param_dict = {k: v[0] for k, v in params.items()}
|
404
425
|
return f"/{device_type}/cloud/video?startTime={param_dict.get("startTime")}&deviceId={param_dict.get("deviceId")}&userId={user_id}&mark={param_dict.get("mark")}"
|
405
426
|
|
406
427
|
@staticmethod
|
407
|
-
async def _get_timestamp(item) -> int:
|
428
|
+
async def _get_timestamp(item) -> int | None:
|
408
429
|
"""Extract timestamp from a record item and raise an exception if it is None.
|
409
430
|
:param item: Record item
|
410
431
|
:return: Timestamp
|
411
432
|
"""
|
412
|
-
|
433
|
+
return (
|
413
434
|
item.timestamp
|
414
435
|
or item.completed_at
|
415
436
|
or item.eat_start_time
|
@@ -419,9 +440,6 @@ class MediaManager:
|
|
419
440
|
or item.time
|
420
441
|
or None
|
421
442
|
)
|
422
|
-
if timestamp is None:
|
423
|
-
raise ValueError("Can't find timestamp in record item")
|
424
|
-
return timestamp
|
425
443
|
|
426
444
|
|
427
445
|
class DownloadDecryptMedia:
|
@@ -515,7 +533,7 @@ class DownloadDecryptMedia:
|
|
515
533
|
]
|
516
534
|
|
517
535
|
if not segment_files:
|
518
|
-
_LOGGER.
|
536
|
+
_LOGGER.warning("No segment files found")
|
519
537
|
elif len(segment_files) == 1:
|
520
538
|
_LOGGER.debug("Single file segment, no need to concatenate")
|
521
539
|
elif len(segment_files) > 1:
|
@@ -535,7 +553,7 @@ class DownloadDecryptMedia:
|
|
535
553
|
|
536
554
|
media_api = video_data.get("mediaApi", None)
|
537
555
|
if not media_api:
|
538
|
-
_LOGGER.
|
556
|
+
_LOGGER.debug("Missing mediaApi in video data")
|
539
557
|
raise ValueError("Missing mediaApi in video data")
|
540
558
|
return await self.client.extract_segments_m3u8(str(media_api))
|
541
559
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: pypetkitapi
|
3
|
-
Version: 1.11.
|
3
|
+
Version: 1.11.3
|
4
4
|
Summary: Python client for PetKit API
|
5
5
|
License: MIT
|
6
6
|
Author: Jezza34000
|
@@ -77,7 +77,7 @@ import asyncio
|
|
77
77
|
import logging
|
78
78
|
import aiohttp
|
79
79
|
from pypetkitapi.client import PetKitClient
|
80
|
-
from pypetkitapi.command import DeviceCommand, FeederCommand, LBCommand,
|
80
|
+
from pypetkitapi.command import DeviceCommand, FeederCommand, LBCommand, DeviceAction, LitterCommand
|
81
81
|
|
82
82
|
logging.basicConfig(level=logging.DEBUG)
|
83
83
|
|
@@ -86,8 +86,8 @@ async def main():
|
|
86
86
|
client = PetKitClient(
|
87
87
|
username="username", # Your PetKit account username or id
|
88
88
|
password="password", # Your PetKit account password
|
89
|
-
region="FR", # Your region or country code (e.g. FR, US, etc.)
|
90
|
-
timezone="Europe/Paris", # Your timezone
|
89
|
+
region="FR", # Your region or country code (e.g. FR, US,CN etc.)
|
90
|
+
timezone="Europe/Paris", # Your timezone(e.g. "Asia/Shanghai")
|
91
91
|
session=session,
|
92
92
|
)
|
93
93
|
|
@@ -98,26 +98,28 @@ async def main():
|
|
98
98
|
for key, value in client.petkit_entities.items():
|
99
99
|
print(f"{key}: {type(value).__name__} - {value.name}")
|
100
100
|
|
101
|
+
# Select a device
|
102
|
+
device_id = key
|
101
103
|
# Read devices or pet information
|
102
|
-
print(client.petkit_entities[
|
104
|
+
print(client.petkit_entities[device_id])
|
103
105
|
|
104
106
|
# Send command to the devices
|
105
107
|
### Example 1 : Turn on the indicator light
|
106
108
|
### Device_ID, Command, Payload
|
107
|
-
await client.send_api_request(
|
109
|
+
await client.send_api_request(device_id, DeviceCommand.UPDATE_SETTING, {"lightMode": 1})
|
108
110
|
|
109
111
|
### Example 2 : Feed the pet
|
110
112
|
### Device_ID, Command, Payload
|
111
113
|
# simple hopper :
|
112
|
-
await client.send_api_request(
|
114
|
+
await client.send_api_request(device_id, FeederCommand.MANUAL_FEED, {"amount": 1})
|
113
115
|
# dual hopper :
|
114
|
-
await client.send_api_request(
|
116
|
+
await client.send_api_request(device_id, FeederCommand.MANUAL_FEED, {"amount1": 2})
|
115
117
|
# or
|
116
|
-
await client.send_api_request(
|
118
|
+
await client.send_api_request(device_id, FeederCommand.MANUAL_FEED, {"amount2": 2})
|
117
119
|
|
118
120
|
### Example 3 : Start the cleaning process
|
119
121
|
### Device_ID, Command, Payload
|
120
|
-
await client.send_api_request(
|
122
|
+
await client.send_api_request(device_id, LitterCommand.CONTROL_DEVICE, {DeviceAction.START: LBCommand.CLEANING})
|
121
123
|
|
122
124
|
|
123
125
|
if __name__ == "__main__":
|
@@ -1,4 +1,4 @@
|
|
1
|
-
pypetkitapi/__init__.py,sha256=
|
1
|
+
pypetkitapi/__init__.py,sha256=Ta6BJBUyI7-C8kUXHJ1nym7-oXdmKWxXn1OfCssy9U8,2107
|
2
2
|
pypetkitapi/bluetooth.py,sha256=eu6c2h6YHBafAhhSSy4As6tn29i5WbOH9tZzRlMm44U,7843
|
3
3
|
pypetkitapi/client.py,sha256=wzZQUHg_ee6lmdAjli6zS7qw_sgPER9iLfcTZe4VTH4,27190
|
4
4
|
pypetkitapi/command.py,sha256=cMCUutZCQo9Ddvjl_FYR5UjU_CqFz1iyetMznYwjpzM,7500
|
@@ -7,13 +7,13 @@ pypetkitapi/containers.py,sha256=F_uyDBD0a5QD4s_ArjYiKTAAg1XHYBvmV_lEnO9RQ-U,478
|
|
7
7
|
pypetkitapi/exceptions.py,sha256=4BXUyYXLfZjNxdnOGJPjyE9ASIl7JmQphjws87jvHtE,1631
|
8
8
|
pypetkitapi/feeder_container.py,sha256=PhidWd5WpsZqtdKZy60PzE67YXgQfApjm8CqvMCHK3U,14743
|
9
9
|
pypetkitapi/litter_container.py,sha256=KWvHNAOJ6hDSeJ_55tqtzY9GxHtd9gAntPkbnVbdb-I,19275
|
10
|
-
pypetkitapi/media.py,sha256=
|
10
|
+
pypetkitapi/media.py,sha256=aGl4nw8q8UK5J2pDEH-lP-Jhy4RWFAqWfyZaLeX3wKM,26356
|
11
11
|
pypetkitapi/purifier_container.py,sha256=ssyIxhNben5XJ4KlQTXTrtULg2ji6DqHqjzOq08d1-I,2491
|
12
12
|
pypetkitapi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
13
|
pypetkitapi/schedule_container.py,sha256=OjLAY6FY-g14JNJJnYMNFV5ZtdkjUzNBit1VUiiZKnQ,2053
|
14
14
|
pypetkitapi/utils.py,sha256=z7325kcJQUburnF28HSXrJMvY_gY9007K73Zwxp-4DQ,743
|
15
15
|
pypetkitapi/water_fountain_container.py,sha256=5J0b-fDZYcFLNX2El7fifv8H6JMhBCt-ttxSow1ozRQ,6787
|
16
|
-
pypetkitapi-1.11.
|
17
|
-
pypetkitapi-1.11.
|
18
|
-
pypetkitapi-1.11.
|
19
|
-
pypetkitapi-1.11.
|
16
|
+
pypetkitapi-1.11.3.dist-info/LICENSE,sha256=u5jNkZEn6YMrtN4Kr5rU3TcBJ5-eAt0qMx4JDsbsnzM,1074
|
17
|
+
pypetkitapi-1.11.3.dist-info/METADATA,sha256=Mqjd4OtD4X539bRYn15IxcRzz2nOMYNEj0jQRu_WnyU,6338
|
18
|
+
pypetkitapi-1.11.3.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
19
|
+
pypetkitapi-1.11.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|