pycatlink 1.0.0__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.
- pycatlink/__init__.py +1 -0
- pycatlink/__main__.py +252 -0
- pycatlink/account.py +75 -0
- pycatlink/c08.py +444 -0
- pycatlink/client.py +159 -0
- pycatlink/const.py +243 -0
- pycatlink/device.py +43 -0
- pycatlink/exceptions.py +28 -0
- pycatlink/models.py +364 -0
- pycatlink/py.typed +0 -0
- pycatlink/utils.py +40 -0
- pycatlink-1.0.0.dist-info/METADATA +35 -0
- pycatlink-1.0.0.dist-info/RECORD +16 -0
- pycatlink-1.0.0.dist-info/WHEEL +4 -0
- pycatlink-1.0.0.dist-info/licenses/LICENSE.md +9 -0
- pycatlink-1.0.0.dist-info/licenses/NOTICE.md +205 -0
pycatlink/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""CatLink API Library."""
|
pycatlink/__main__.py
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from collections.abc import Callable, Coroutine
|
|
3
|
+
from functools import wraps
|
|
4
|
+
from typing import Any, ParamSpec, TypeVar
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from dotenv import load_dotenv
|
|
8
|
+
|
|
9
|
+
from pycatlink.c08 import CatlinkC08Device
|
|
10
|
+
from pycatlink.client import CatlinkApiClient
|
|
11
|
+
|
|
12
|
+
from .account import CatlinkAccount
|
|
13
|
+
from .const import (
|
|
14
|
+
ENV_CATLINK_PASSWORD,
|
|
15
|
+
ENV_CATLINK_PHONE,
|
|
16
|
+
ENV_CATLINK_PHONE_INTERNATIONAL_CODE,
|
|
17
|
+
HttpMethod,
|
|
18
|
+
)
|
|
19
|
+
from .models import CatlinkAccountConfig
|
|
20
|
+
|
|
21
|
+
app = typer.Typer()
|
|
22
|
+
|
|
23
|
+
P = ParamSpec("P")
|
|
24
|
+
R = TypeVar("R")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def syncify(f: Callable[P, Coroutine[Any, Any, R]]) -> Callable[P, R]:
|
|
28
|
+
"""Decorator to convert async function to sync by running with asyncio.run."""
|
|
29
|
+
|
|
30
|
+
@wraps(f)
|
|
31
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
32
|
+
return asyncio.run(f(*args, **kwargs))
|
|
33
|
+
|
|
34
|
+
return wrapper
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@app.command("fetch")
|
|
38
|
+
@syncify
|
|
39
|
+
async def api_fetch(
|
|
40
|
+
phone: str = typer.Option(
|
|
41
|
+
...,
|
|
42
|
+
help="Catlink account phone number (not starting with 0 or country code).",
|
|
43
|
+
envvar=ENV_CATLINK_PHONE,
|
|
44
|
+
),
|
|
45
|
+
password: str = typer.Option(
|
|
46
|
+
...,
|
|
47
|
+
help="Catlink account password.",
|
|
48
|
+
envvar=ENV_CATLINK_PASSWORD,
|
|
49
|
+
),
|
|
50
|
+
phone_international_code: str = typer.Option(
|
|
51
|
+
...,
|
|
52
|
+
help="Catlink account phone international code.",
|
|
53
|
+
envvar=ENV_CATLINK_PHONE_INTERNATIONAL_CODE,
|
|
54
|
+
),
|
|
55
|
+
path: str = typer.Option(
|
|
56
|
+
...,
|
|
57
|
+
help="API path to fetch data from.",
|
|
58
|
+
),
|
|
59
|
+
parameters: str = typer.Option(
|
|
60
|
+
"",
|
|
61
|
+
help="Query parameters in key1=value1&key2=value2 format.",
|
|
62
|
+
),
|
|
63
|
+
) -> None:
|
|
64
|
+
"""GET Request to the Catlink API."""
|
|
65
|
+
|
|
66
|
+
config = CatlinkAccountConfig(
|
|
67
|
+
phone=phone,
|
|
68
|
+
password=password,
|
|
69
|
+
phone_international_code=phone_international_code,
|
|
70
|
+
)
|
|
71
|
+
client = CatlinkApiClient(config)
|
|
72
|
+
|
|
73
|
+
parameters_dict: dict[str, str] = {}
|
|
74
|
+
if parameters:
|
|
75
|
+
for pair in parameters.split("&"):
|
|
76
|
+
if "=" in pair:
|
|
77
|
+
key, value = pair.split("=", 1)
|
|
78
|
+
parameters_dict[key] = value
|
|
79
|
+
|
|
80
|
+
response = await client.request_with_auto_login(
|
|
81
|
+
path=path,
|
|
82
|
+
parameters=parameters_dict,
|
|
83
|
+
method=HttpMethod.GET,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
typer.echo("Response:")
|
|
87
|
+
typer.echo(response)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@app.command()
|
|
91
|
+
@syncify
|
|
92
|
+
async def pets(
|
|
93
|
+
phone: str = typer.Option(
|
|
94
|
+
...,
|
|
95
|
+
help="Catlink account phone number (not starting with 0 or country code).",
|
|
96
|
+
envvar=ENV_CATLINK_PHONE,
|
|
97
|
+
),
|
|
98
|
+
password: str = typer.Option(
|
|
99
|
+
...,
|
|
100
|
+
help="Catlink account password.",
|
|
101
|
+
envvar=ENV_CATLINK_PASSWORD,
|
|
102
|
+
),
|
|
103
|
+
phone_international_code: str = typer.Option(
|
|
104
|
+
...,
|
|
105
|
+
help="Catlink account phone international code.",
|
|
106
|
+
envvar=ENV_CATLINK_PHONE_INTERNATIONAL_CODE,
|
|
107
|
+
),
|
|
108
|
+
) -> None:
|
|
109
|
+
"""Print the list of pets associated with the account."""
|
|
110
|
+
|
|
111
|
+
config = CatlinkAccountConfig(
|
|
112
|
+
phone=phone,
|
|
113
|
+
password=password,
|
|
114
|
+
phone_international_code=phone_international_code,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
account = CatlinkAccount(config)
|
|
118
|
+
|
|
119
|
+
typer.echo("Fetching pets...")
|
|
120
|
+
|
|
121
|
+
pets = await account.get_pets()
|
|
122
|
+
|
|
123
|
+
typer.echo(f"Found {len(pets)} pet(s)")
|
|
124
|
+
|
|
125
|
+
for i, pet in enumerate(pets):
|
|
126
|
+
typer.echo(f"Pet {i + 1}")
|
|
127
|
+
typer.echo(pet)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@app.command()
|
|
131
|
+
@syncify
|
|
132
|
+
async def devices(
|
|
133
|
+
phone: str = typer.Option(
|
|
134
|
+
...,
|
|
135
|
+
help="Catlink account phone number (not starting with 0 or country code).",
|
|
136
|
+
envvar=ENV_CATLINK_PHONE,
|
|
137
|
+
),
|
|
138
|
+
password: str = typer.Option(
|
|
139
|
+
...,
|
|
140
|
+
help="Catlink account password.",
|
|
141
|
+
envvar=ENV_CATLINK_PASSWORD,
|
|
142
|
+
),
|
|
143
|
+
phone_international_code: str = typer.Option(
|
|
144
|
+
...,
|
|
145
|
+
help="Catlink account phone international code.",
|
|
146
|
+
envvar=ENV_CATLINK_PHONE_INTERNATIONAL_CODE,
|
|
147
|
+
),
|
|
148
|
+
) -> None:
|
|
149
|
+
"""Print the list of devices associated with the account."""
|
|
150
|
+
|
|
151
|
+
config = CatlinkAccountConfig(
|
|
152
|
+
phone=phone,
|
|
153
|
+
password=password,
|
|
154
|
+
phone_international_code=phone_international_code,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
account = CatlinkAccount(config)
|
|
158
|
+
|
|
159
|
+
typer.echo("Fetching devices...")
|
|
160
|
+
|
|
161
|
+
devices = await account.get_devices()
|
|
162
|
+
|
|
163
|
+
typer.echo(f"Found {len(devices)} device(s)")
|
|
164
|
+
|
|
165
|
+
for i, device in enumerate(devices):
|
|
166
|
+
typer.echo(f"Device {i + 1}")
|
|
167
|
+
typer.echo(device.device_info)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@app.command()
|
|
171
|
+
@syncify
|
|
172
|
+
async def device(
|
|
173
|
+
phone: str = typer.Option(
|
|
174
|
+
...,
|
|
175
|
+
help="Catlink account phone number (not starting with 0 or country code).",
|
|
176
|
+
envvar=ENV_CATLINK_PHONE,
|
|
177
|
+
),
|
|
178
|
+
password: str = typer.Option(
|
|
179
|
+
...,
|
|
180
|
+
help="Catlink account password.",
|
|
181
|
+
envvar=ENV_CATLINK_PASSWORD,
|
|
182
|
+
),
|
|
183
|
+
phone_international_code: str = typer.Option(
|
|
184
|
+
...,
|
|
185
|
+
help="Catlink account phone international code.",
|
|
186
|
+
envvar=ENV_CATLINK_PHONE_INTERNATIONAL_CODE,
|
|
187
|
+
),
|
|
188
|
+
device_id: str = typer.Argument(
|
|
189
|
+
...,
|
|
190
|
+
help="ID of the device to get details for.",
|
|
191
|
+
),
|
|
192
|
+
) -> None:
|
|
193
|
+
"""Print detailed information for a specific device."""
|
|
194
|
+
|
|
195
|
+
config = CatlinkAccountConfig(
|
|
196
|
+
phone=phone,
|
|
197
|
+
password=password,
|
|
198
|
+
phone_international_code=phone_international_code,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
account = CatlinkAccount(config)
|
|
202
|
+
|
|
203
|
+
typer.echo("Fetching devices...")
|
|
204
|
+
|
|
205
|
+
devices = await account.get_devices()
|
|
206
|
+
|
|
207
|
+
device = next(
|
|
208
|
+
(device for device in devices if str(device.device_info.id) == device_id), None
|
|
209
|
+
)
|
|
210
|
+
if not device:
|
|
211
|
+
typer.echo(f"Device with ID {device_id} not found.")
|
|
212
|
+
raise typer.Exit(code=1)
|
|
213
|
+
|
|
214
|
+
typer.echo(f"Refreshing data for device ID {device_id}...")
|
|
215
|
+
|
|
216
|
+
await device.refresh()
|
|
217
|
+
|
|
218
|
+
typer.echo(f"Info for device ID {device_id}:")
|
|
219
|
+
typer.echo(device.device_info)
|
|
220
|
+
|
|
221
|
+
if not isinstance(device, (CatlinkC08Device,)):
|
|
222
|
+
typer.echo("Detailed information fetching is only supported for C08 devices.")
|
|
223
|
+
raise typer.Exit(code=0)
|
|
224
|
+
|
|
225
|
+
typer.echo(f"Details for device ID {device_id}:")
|
|
226
|
+
typer.echo(device.device_details)
|
|
227
|
+
|
|
228
|
+
typer.echo(f"Stats for device ID {device_id}:")
|
|
229
|
+
typer.echo(device.device_stats)
|
|
230
|
+
|
|
231
|
+
typer.echo(f"Logs for device ID {device_id}:")
|
|
232
|
+
typer.echo(device.device_logs)
|
|
233
|
+
|
|
234
|
+
typer.echo(f"Pet stats for device ID {device_id}:")
|
|
235
|
+
typer.echo(device.pet_stats)
|
|
236
|
+
|
|
237
|
+
typer.echo(f"Linked pets for device ID {device_id}:")
|
|
238
|
+
typer.echo(device.linked_pets)
|
|
239
|
+
|
|
240
|
+
typer.echo(f"Selectable pets for device ID {device_id}:")
|
|
241
|
+
typer.echo(device.selectable_pets)
|
|
242
|
+
|
|
243
|
+
typer.echo(f"Notice configurations for device ID {device_id}:")
|
|
244
|
+
typer.echo(device.notice_configs)
|
|
245
|
+
|
|
246
|
+
typer.echo(f"About device information for device ID {device_id}:")
|
|
247
|
+
typer.echo(device.about_device)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
if __name__ == "__main__":
|
|
251
|
+
load_dotenv()
|
|
252
|
+
app()
|
pycatlink/account.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Account module for CatLink API."""
|
|
2
|
+
|
|
3
|
+
from .c08 import CatlinkC08Device
|
|
4
|
+
from .client import CatlinkApiClient
|
|
5
|
+
from .const import (
|
|
6
|
+
API_DEVICE_LIST,
|
|
7
|
+
API_PET_LIST,
|
|
8
|
+
PARAMETER_TYPE,
|
|
9
|
+
RESPONSE_KEY_DATA,
|
|
10
|
+
RESPONSE_KEY_DEVICES,
|
|
11
|
+
RESPONSE_KEY_PETS,
|
|
12
|
+
RESPONSE_KEY_RECORDS,
|
|
13
|
+
TYPE_NONE,
|
|
14
|
+
HttpMethod,
|
|
15
|
+
)
|
|
16
|
+
from .device import CatlinkDevice
|
|
17
|
+
from .models import CatlinkAccountConfig, CatlinkDeviceInfo, CatlinkPet
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CatlinkAccount:
|
|
21
|
+
"""Account class for CatLink integration."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, config: CatlinkAccountConfig) -> None:
|
|
24
|
+
"""Initialize the account."""
|
|
25
|
+
self.config = config
|
|
26
|
+
|
|
27
|
+
self._client = CatlinkApiClient(config)
|
|
28
|
+
|
|
29
|
+
async def get_devices(self) -> list[CatlinkDevice | CatlinkC08Device]:
|
|
30
|
+
"""Get the list of devices associated with the account."""
|
|
31
|
+
response = await self._client.request_with_auto_login(
|
|
32
|
+
path=API_DEVICE_LIST,
|
|
33
|
+
method=HttpMethod.GET,
|
|
34
|
+
parameters={
|
|
35
|
+
PARAMETER_TYPE: TYPE_NONE,
|
|
36
|
+
},
|
|
37
|
+
)
|
|
38
|
+
device_infos = [
|
|
39
|
+
CatlinkDeviceInfo.from_dict(response_element)
|
|
40
|
+
for response_element in response.get(RESPONSE_KEY_DATA, {}).get(
|
|
41
|
+
RESPONSE_KEY_DEVICES, []
|
|
42
|
+
)
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
return [self._create_device(device_info) for device_info in device_infos]
|
|
46
|
+
|
|
47
|
+
async def get_pets(self) -> list[CatlinkPet]:
|
|
48
|
+
"""Get the list of pets associated with the account."""
|
|
49
|
+
response = await self._client.request_with_auto_login(
|
|
50
|
+
path=API_PET_LIST,
|
|
51
|
+
method=HttpMethod.GET,
|
|
52
|
+
parameters={},
|
|
53
|
+
)
|
|
54
|
+
pet_infos = [
|
|
55
|
+
CatlinkPet.from_dict(response_element)
|
|
56
|
+
for response_element in response.get(RESPONSE_KEY_DATA, {})
|
|
57
|
+
.get(RESPONSE_KEY_PETS, {})
|
|
58
|
+
.get(RESPONSE_KEY_RECORDS, [])
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
return pet_infos
|
|
62
|
+
|
|
63
|
+
def _create_device(
|
|
64
|
+
self, device_info: CatlinkDeviceInfo
|
|
65
|
+
) -> CatlinkDevice | CatlinkC08Device:
|
|
66
|
+
"""Create the appropriate device instance based on device type."""
|
|
67
|
+
if not device_info.device_type:
|
|
68
|
+
return CatlinkDevice(device_info, self._client)
|
|
69
|
+
|
|
70
|
+
device_type_upper = device_info.device_type.upper()
|
|
71
|
+
|
|
72
|
+
if device_type_upper == "C08":
|
|
73
|
+
return CatlinkC08Device(device_info, self._client)
|
|
74
|
+
|
|
75
|
+
return CatlinkDevice(device_info, self._client)
|