Habiticalib 0.1.0a2__py3-none-any.whl → 0.2.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.
- habiticalib/__init__.py +34 -2
- habiticalib/const.py +3 -1
- habiticalib/exceptions.py +13 -4
- habiticalib/helpers.py +25 -1
- habiticalib/lib.py +508 -25
- habiticalib/types.py +488 -66
- {habiticalib-0.1.0a2.dist-info → habiticalib-0.2.0.dist-info}/METADATA +3 -3
- habiticalib-0.2.0.dist-info/RECORD +11 -0
- {habiticalib-0.1.0a2.dist-info → habiticalib-0.2.0.dist-info}/WHEEL +1 -1
- habiticalib-0.1.0a2.dist-info/RECORD +0 -11
- {habiticalib-0.1.0a2.dist-info → habiticalib-0.2.0.dist-info}/licenses/LICENSE +0 -0
habiticalib/lib.py
CHANGED
@@ -6,29 +6,40 @@ import asyncio
|
|
6
6
|
from http import HTTPStatus
|
7
7
|
from io import BytesIO
|
8
8
|
import logging
|
9
|
-
from typing import IO, TYPE_CHECKING, Self
|
9
|
+
from typing import IO, TYPE_CHECKING, Any, Self
|
10
10
|
|
11
11
|
from aiohttp import ClientError, ClientResponseError, ClientSession
|
12
|
+
from habitipy.aio import HabitipyAsync # type: ignore[import-untyped]
|
12
13
|
from PIL import Image
|
13
14
|
from yarl import URL
|
14
15
|
|
15
|
-
from .const import ASSETS_URL, BACKER_ONLY_GEAR, DEFAULT_URL
|
16
|
+
from .const import ASSETS_URL, BACKER_ONLY_GEAR, DEFAULT_URL, PAGE_LIMIT
|
16
17
|
from .exceptions import (
|
17
18
|
BadRequestError,
|
18
19
|
NotAuthorizedError,
|
19
20
|
NotFoundError,
|
20
21
|
TooManyRequestsError,
|
21
22
|
)
|
22
|
-
from .helpers import
|
23
|
+
from .helpers import (
|
24
|
+
deserialize_task,
|
25
|
+
extract_user_styles,
|
26
|
+
get_user_agent,
|
27
|
+
get_x_client,
|
28
|
+
join_fields,
|
29
|
+
)
|
23
30
|
from .types import (
|
24
31
|
Attributes,
|
25
|
-
Class,
|
26
32
|
Direction,
|
33
|
+
HabiticaClass,
|
27
34
|
HabiticaClassSystemResponse,
|
35
|
+
HabiticaContentResponse,
|
28
36
|
HabiticaErrorResponse,
|
37
|
+
HabiticaGroupMembersResponse,
|
29
38
|
HabiticaLoginResponse,
|
39
|
+
HabiticaQuestResponse,
|
30
40
|
HabiticaResponse,
|
31
41
|
HabiticaScoreResponse,
|
42
|
+
HabiticaSleepResponse,
|
32
43
|
HabiticaStatsResponse,
|
33
44
|
HabiticaTagResponse,
|
34
45
|
HabiticaTagsResponse,
|
@@ -254,14 +265,15 @@ class Habitica:
|
|
254
265
|
self,
|
255
266
|
task_type: TaskFilter | None = None,
|
256
267
|
due_date: datetime | None = None,
|
257
|
-
) ->
|
268
|
+
) -> HabiticaTasksResponse:
|
258
269
|
"""Get the authenticated user's tasks.
|
259
270
|
|
260
271
|
Parameters
|
261
272
|
----------
|
262
273
|
task_type : TaskFilter | None
|
263
274
|
The type of task to retrieve, defined in TaskFilter enum.
|
264
|
-
If `None`, all task types will be retrieved
|
275
|
+
If `None`, all task types will be retrieved except completed to-dos
|
276
|
+
(default is None).
|
265
277
|
|
266
278
|
due_date : datetime | None
|
267
279
|
Optional date to use for computing the nextDue field for each returned task.
|
@@ -378,14 +390,16 @@ class Habitica:
|
|
378
390
|
|
379
391
|
Examples
|
380
392
|
--------
|
381
|
-
>>> new_task = Task(
|
393
|
+
>>> new_task = Task(text="New Task", type=TaskType.TODO ...)
|
382
394
|
>>> create_response = await habitica.create_task(new_task)
|
383
395
|
>>> print(create_response.data) # Displays the created task information
|
384
396
|
"""
|
385
397
|
url = self.url / "api/v3/tasks/user"
|
386
398
|
|
399
|
+
json = deserialize_task(task)
|
400
|
+
|
387
401
|
return HabiticaTaskResponse.from_json(
|
388
|
-
await self._request("post", url=url, json=
|
402
|
+
await self._request("post", url=url, json=json),
|
389
403
|
)
|
390
404
|
|
391
405
|
async def update_task(self, task_id: UUID, task: Task) -> HabiticaTaskResponse:
|
@@ -421,14 +435,16 @@ class Habitica:
|
|
421
435
|
Examples
|
422
436
|
--------
|
423
437
|
>>> task_id = UUID("12345678-1234-5678-1234-567812345678")
|
424
|
-
>>> updated_task = Task(
|
438
|
+
>>> updated_task = Task(text="Updated Task", ...)
|
425
439
|
>>> update_response = await habitica.update_task(task_id, updated_task)
|
426
440
|
>>> print(update_response.data) # Displays the updated task information
|
427
441
|
"""
|
428
442
|
url = self.url / "api/v3/tasks" / str(task_id)
|
429
443
|
|
444
|
+
json = deserialize_task(task)
|
445
|
+
|
430
446
|
return HabiticaTaskResponse.from_json(
|
431
|
-
await self._request("put", url=url, json=
|
447
|
+
await self._request("put", url=url, json=json),
|
432
448
|
)
|
433
449
|
|
434
450
|
async def delete_task(self, task_id: UUID) -> HabiticaResponse:
|
@@ -445,7 +461,7 @@ class Habitica:
|
|
445
461
|
Returns
|
446
462
|
-------
|
447
463
|
HabiticaTaskResponse
|
448
|
-
A response
|
464
|
+
A response containing an empty data object.
|
449
465
|
|
450
466
|
Raises
|
451
467
|
------
|
@@ -549,7 +565,7 @@ class Habitica:
|
|
549
565
|
async def get_content(
|
550
566
|
self,
|
551
567
|
language: Language | None = None,
|
552
|
-
) ->
|
568
|
+
) -> HabiticaContentResponse:
|
553
569
|
"""
|
554
570
|
Fetch game content from the Habitica API.
|
555
571
|
|
@@ -591,7 +607,7 @@ class Habitica:
|
|
591
607
|
if language:
|
592
608
|
params.update({"language": language.value})
|
593
609
|
|
594
|
-
return
|
610
|
+
return HabiticaContentResponse.from_json(
|
595
611
|
await self._request("get", url=url, params=params),
|
596
612
|
)
|
597
613
|
|
@@ -799,14 +815,14 @@ class Habitica:
|
|
799
815
|
|
800
816
|
async def cast_skill(
|
801
817
|
self,
|
802
|
-
|
818
|
+
skill: Skill,
|
803
819
|
target_id: UUID | None = None,
|
804
820
|
) -> HabiticaUserResponse:
|
805
821
|
"""Cast a skill (spell) in Habitica, optionally targeting a specific user, task or party.
|
806
822
|
|
807
823
|
Parameters
|
808
824
|
----------
|
809
|
-
|
825
|
+
skill : Skill
|
810
826
|
The skill (or spell) to be cast. This should be a valid `Skill` enum value.
|
811
827
|
target_id : UUID, optional
|
812
828
|
The unique identifier of the target for the skill. If the skill does not require a target,
|
@@ -832,23 +848,23 @@ class Habitica:
|
|
832
848
|
TimeoutError
|
833
849
|
If the connection times out.
|
834
850
|
"""
|
835
|
-
url = self.url / "api/v3/class/cast" /
|
851
|
+
url = self.url / "api/v3/user/class/cast" / skill
|
836
852
|
params = {}
|
837
853
|
|
838
854
|
if target_id:
|
839
855
|
params.update({"targetId": str(target_id)})
|
840
856
|
return HabiticaUserResponse.from_json(
|
841
|
-
await self._request("post", url=url,
|
857
|
+
await self._request("post", url=url, params=params),
|
842
858
|
)
|
843
859
|
|
844
860
|
async def toggle_sleep(
|
845
861
|
self,
|
846
|
-
) ->
|
862
|
+
) -> HabiticaSleepResponse:
|
847
863
|
"""Toggles the user's sleep mode in Habitica.
|
848
864
|
|
849
865
|
Returns
|
850
866
|
-------
|
851
|
-
|
867
|
+
HabiticaSleepResponse
|
852
868
|
A response object containing the result of the sleep mode toggle,
|
853
869
|
and the new sleep state (True if sleeping, False if not).
|
854
870
|
|
@@ -865,7 +881,7 @@ class Habitica:
|
|
865
881
|
"""
|
866
882
|
url = self.url / "api/v3/user/sleep"
|
867
883
|
|
868
|
-
return
|
884
|
+
return HabiticaSleepResponse.from_json(await self._request("post", url=url))
|
869
885
|
|
870
886
|
async def revive(
|
871
887
|
self,
|
@@ -889,7 +905,7 @@ class Habitica:
|
|
889
905
|
|
890
906
|
return HabiticaResponse.from_json(await self._request("post", url=url))
|
891
907
|
|
892
|
-
async def change_class(self, Class:
|
908
|
+
async def change_class(self, Class: HabiticaClass) -> HabiticaClassSystemResponse: # noqa: N803
|
893
909
|
"""Change the user's class in Habitica.
|
894
910
|
|
895
911
|
This method sends a request to the Habitica API to change the user's class
|
@@ -920,7 +936,7 @@ class Habitica:
|
|
920
936
|
|
921
937
|
Examples
|
922
938
|
--------
|
923
|
-
>>> new_class =
|
939
|
+
>>> new_class = HabiticaClass.WARRIOR
|
924
940
|
>>> change_response = await habitica.change_class(new_class)
|
925
941
|
>>> print(change_response.data.stats) # Displays the user's stats after class change
|
926
942
|
"""
|
@@ -1068,7 +1084,7 @@ class Habitica:
|
|
1068
1084
|
url = self.url / "api/v3/tags"
|
1069
1085
|
|
1070
1086
|
return HabiticaTagsResponse.from_json(
|
1071
|
-
await self._request("
|
1087
|
+
await self._request("get", url=url),
|
1072
1088
|
)
|
1073
1089
|
|
1074
1090
|
async def get_tag(self, tag_id: UUID) -> HabiticaTagResponse:
|
@@ -1106,7 +1122,7 @@ class Habitica:
|
|
1106
1122
|
url = self.url / "api/v3/tags" / str(tag_id)
|
1107
1123
|
|
1108
1124
|
return HabiticaTagResponse.from_json(
|
1109
|
-
await self._request("
|
1125
|
+
await self._request("get", url=url),
|
1110
1126
|
)
|
1111
1127
|
|
1112
1128
|
async def delete_tag(self, tag_id: UUID) -> HabiticaResponse:
|
@@ -1266,10 +1282,447 @@ class Habitica:
|
|
1266
1282
|
url = self.url / "api/v3/reorder-tags"
|
1267
1283
|
json = {"tagId": str(tag_id), "to": to}
|
1268
1284
|
|
1269
|
-
return
|
1285
|
+
return HabiticaResponse.from_json(
|
1270
1286
|
await self._request("post", url=url, json=json),
|
1271
1287
|
)
|
1272
1288
|
|
1289
|
+
async def get_group_members(
|
1290
|
+
self,
|
1291
|
+
group_id: UUID | None = None,
|
1292
|
+
*,
|
1293
|
+
limit: int | None = None,
|
1294
|
+
tasks: bool = False,
|
1295
|
+
public_fields: bool = False,
|
1296
|
+
last_id: UUID | None = None,
|
1297
|
+
) -> HabiticaGroupMembersResponse:
|
1298
|
+
"""Get members of the party or a specific group.
|
1299
|
+
|
1300
|
+
This method retrieves a list of members for a party or a specified group
|
1301
|
+
from the Habitica API. Additional options allow including tasks or public
|
1302
|
+
through results if necessary to collect all members. If the API rate limit is
|
1303
|
+
exceeded, the method will pause for the duration specified in the `retry-after`
|
1304
|
+
header and retry the request.
|
1305
|
+
|
1306
|
+
|
1307
|
+
Parameters
|
1308
|
+
----------
|
1309
|
+
group_id : UUID, optional
|
1310
|
+
The UUID of the group. Defaults to the user's party if not specified.
|
1311
|
+
limit : int, optional
|
1312
|
+
Maximum number of members per request (default: 30, max: 60).
|
1313
|
+
tasks : bool, optional
|
1314
|
+
Whether to include tasks associated with the group.
|
1315
|
+
public_fields : bool, optional
|
1316
|
+
Whether to include all public fields for group members.
|
1317
|
+
last_id : UUID, optional
|
1318
|
+
For paginated requests, the UUID of the last member retrieved.
|
1319
|
+
|
1320
|
+
Returns
|
1321
|
+
-------
|
1322
|
+
HabiticaGroupMembersResponse
|
1323
|
+
A response object containing the group member data.
|
1324
|
+
|
1325
|
+
Raises
|
1326
|
+
------
|
1327
|
+
aiohttp.ClientResponseError
|
1328
|
+
For HTTP-related errors, such as HTTP 400 or 500 response status.
|
1329
|
+
aiohttp.ClientConnectionError
|
1330
|
+
If the connection to the API fails.
|
1331
|
+
aiohttp.ClientError
|
1332
|
+
For any other exceptions raised by aiohttp during the request.
|
1333
|
+
TimeoutError
|
1334
|
+
If the connection times out.
|
1335
|
+
|
1336
|
+
Examples
|
1337
|
+
--------
|
1338
|
+
>>> members_response = await habitica.get_group_members()
|
1339
|
+
>>> for member in members_response.data:
|
1340
|
+
... print(member.profile.name)
|
1341
|
+
"""
|
1342
|
+
|
1343
|
+
if limit is not None and (limit < 1 or limit > PAGE_LIMIT):
|
1344
|
+
msg = f"The 'limit' parameter must be between 1 and {PAGE_LIMIT}."
|
1345
|
+
raise ValueError(msg)
|
1346
|
+
|
1347
|
+
group = "party" if not group_id else str(group_id)
|
1348
|
+
url = self.url / "api/v3/groups" / group / "members"
|
1349
|
+
|
1350
|
+
params: dict[str, str | int] = {}
|
1351
|
+
|
1352
|
+
if tasks:
|
1353
|
+
params["includeTasks"] = "true"
|
1354
|
+
if public_fields:
|
1355
|
+
params["includeAllPublicFields"] = "true"
|
1356
|
+
if last_id:
|
1357
|
+
params["lastId"] = str(last_id)
|
1358
|
+
if limit:
|
1359
|
+
params["limit"] = limit
|
1360
|
+
|
1361
|
+
while True:
|
1362
|
+
try:
|
1363
|
+
response = HabiticaGroupMembersResponse.from_json(
|
1364
|
+
await self._request("get", url=url, params=params),
|
1365
|
+
)
|
1366
|
+
break
|
1367
|
+
except TooManyRequestsError as e:
|
1368
|
+
await asyncio.sleep(e.retry_after)
|
1369
|
+
|
1370
|
+
if len(response.data) == limit:
|
1371
|
+
next_page = await self.get_group_members(
|
1372
|
+
group_id=group_id,
|
1373
|
+
limit=limit,
|
1374
|
+
tasks=tasks,
|
1375
|
+
public_fields=public_fields,
|
1376
|
+
last_id=response.data[-1].id,
|
1377
|
+
)
|
1378
|
+
response.data.extend(next_page.data)
|
1379
|
+
|
1380
|
+
return response
|
1381
|
+
|
1382
|
+
async def abort_quest(self, group_id: UUID | None = None) -> HabiticaQuestResponse:
|
1383
|
+
"""Abort an active quest for the party or a specific group.
|
1384
|
+
|
1385
|
+
Prematurely terminates an ongoing quest, causing all progress to be lost.
|
1386
|
+
The quest scroll will be returned to the owner's inventory.
|
1387
|
+
Only the quest leader or group leader is allowed to perform this action.
|
1388
|
+
|
1389
|
+
Parameters
|
1390
|
+
----------
|
1391
|
+
group_id : UUID, optional
|
1392
|
+
The UUID of the specific group whose quest should be aborted.
|
1393
|
+
Defaults to the user's party if not specified.
|
1394
|
+
|
1395
|
+
Returns
|
1396
|
+
-------
|
1397
|
+
HabiticaQuestResponse
|
1398
|
+
A response object containing updated quest data of the group or party.
|
1399
|
+
|
1400
|
+
Raises
|
1401
|
+
------
|
1402
|
+
NotFoundError
|
1403
|
+
If the specified group or quest could not be found.
|
1404
|
+
NotAuthorizedError
|
1405
|
+
If the user does not have permission to abort the quest.
|
1406
|
+
aiohttp.ClientResponseError
|
1407
|
+
For HTTP-related errors, such as HTTP 400 or 500 response status.
|
1408
|
+
aiohttp.ClientConnectionError
|
1409
|
+
If the connection to the API fails.
|
1410
|
+
aiohttp.ClientError
|
1411
|
+
For any other exceptions raised by aiohttp during the request.
|
1412
|
+
TimeoutError
|
1413
|
+
If the connection times out.
|
1414
|
+
|
1415
|
+
Examples
|
1416
|
+
--------
|
1417
|
+
Abort the party's current quest:
|
1418
|
+
>>> response = await habitica.abort_quest()
|
1419
|
+
>>> print(response.success) # True if the quest was successfully aborted.
|
1420
|
+
|
1421
|
+
Abort a quest for a specific group:
|
1422
|
+
>>> group_id = UUID("12345678-1234-5678-1234-567812345678")
|
1423
|
+
>>> response = await habitica.abort_quest(group_id)
|
1424
|
+
>>> print(response.success) # True if the quest was successfully aborted.
|
1425
|
+
"""
|
1426
|
+
group = "party" if not group_id else str(group_id)
|
1427
|
+
url = self.url / "api/v3/groups" / group / "quests/abort"
|
1428
|
+
|
1429
|
+
return HabiticaQuestResponse.from_json(
|
1430
|
+
await self._request("post", url=url),
|
1431
|
+
)
|
1432
|
+
|
1433
|
+
async def accept_quest(self, group_id: UUID | None = None) -> HabiticaQuestResponse:
|
1434
|
+
"""Accept a pending invitation to a quest from the party or a specific group.
|
1435
|
+
|
1436
|
+
Allows a user to accept an invitation to participate in a quest within a
|
1437
|
+
specified group.
|
1438
|
+
|
1439
|
+
Parameters
|
1440
|
+
----------
|
1441
|
+
group_id : UUID, optional
|
1442
|
+
The UUID of the group for which the quest invitation is being accepted.
|
1443
|
+
Defaults to the user's party if not specified.
|
1444
|
+
|
1445
|
+
|
1446
|
+
Returns
|
1447
|
+
-------
|
1448
|
+
HabiticaQuestResponse
|
1449
|
+
A response object containing updated quest data of the group or party.
|
1450
|
+
|
1451
|
+
Raises
|
1452
|
+
------
|
1453
|
+
NotFoundError
|
1454
|
+
If the specified group or quest could not be found.
|
1455
|
+
aiohttp.ClientResponseError
|
1456
|
+
Raised for HTTP-related errors, such as HTTP 400 or 500 response status.
|
1457
|
+
aiohttp.ClientConnectionError
|
1458
|
+
If the connection to the API fails.
|
1459
|
+
aiohttp.ClientError
|
1460
|
+
Raised for any other exceptions encountered by `aiohttp` during the request.
|
1461
|
+
TimeoutError
|
1462
|
+
If the connection to the API times out.
|
1463
|
+
|
1464
|
+
Examples
|
1465
|
+
--------
|
1466
|
+
Accept a pending quest invitation from the party:
|
1467
|
+
>>> response = await habitica.accept_quest()
|
1468
|
+
>>> print(response.success) # True if the quest invitation was successfully accepted.
|
1469
|
+
"""
|
1470
|
+
group = "party" if not group_id else str(group_id)
|
1471
|
+
url = self.url / "api/v3/groups" / group / "quests/accept"
|
1472
|
+
|
1473
|
+
return HabiticaQuestResponse.from_json(
|
1474
|
+
await self._request("post", url=url),
|
1475
|
+
)
|
1476
|
+
|
1477
|
+
async def reject_quest(self, group_id: UUID | None = None) -> HabiticaQuestResponse:
|
1478
|
+
"""Reject a pending quest invitation from the party or a specific group.
|
1479
|
+
|
1480
|
+
Allows a user to reject an invitation to participate in a quest within a
|
1481
|
+
specified group. The user will not join the quest and will be excluded from
|
1482
|
+
its progress and rewards.
|
1483
|
+
|
1484
|
+
Parameters
|
1485
|
+
----------
|
1486
|
+
group_id : UUID, optional
|
1487
|
+
The UUID of the group for which the quest invitation is being rejected.
|
1488
|
+
Defaults to the user's party if not specified.
|
1489
|
+
|
1490
|
+
|
1491
|
+
Returns
|
1492
|
+
-------
|
1493
|
+
HabiticaQuestResponse
|
1494
|
+
A response object containing updated quest data of the group or party.
|
1495
|
+
|
1496
|
+
Raises
|
1497
|
+
------
|
1498
|
+
NotFoundError
|
1499
|
+
If the specified group or quest could not be found.
|
1500
|
+
aiohttp.ClientResponseError
|
1501
|
+
Raised for HTTP-related errors, such as HTTP 400 or 500 response status.
|
1502
|
+
aiohttp.ClientConnectionError
|
1503
|
+
If the connection to the API fails.
|
1504
|
+
aiohttp.ClientError
|
1505
|
+
Raised for any other exceptions encountered by `aiohttp` during the request.
|
1506
|
+
TimeoutError
|
1507
|
+
If the connection to the API times out.
|
1508
|
+
|
1509
|
+
Examples
|
1510
|
+
--------
|
1511
|
+
Reject a pending quest invitation from the party:
|
1512
|
+
>>> response = await habitica.reject_quest()
|
1513
|
+
>>> print(response.success) # True if the quest invitation was successfully rejected.
|
1514
|
+
|
1515
|
+
Reject a pending quest invitation from a specific group:
|
1516
|
+
>>> group_id = UUID("12345678-1234-5678-1234-567812345678")
|
1517
|
+
>>> response = await habitica.reject_quest(group_id)
|
1518
|
+
>>> print(response.success) # True if the quest invitation was successfully rejected.
|
1519
|
+
"""
|
1520
|
+
group = "party" if not group_id else str(group_id)
|
1521
|
+
url = self.url / "api/v3/groups" / group / "quests/reject"
|
1522
|
+
|
1523
|
+
return HabiticaQuestResponse.from_json(
|
1524
|
+
await self._request("post", url=url),
|
1525
|
+
)
|
1526
|
+
|
1527
|
+
async def cancel_quest(self, group_id: UUID | None = None) -> HabiticaQuestResponse:
|
1528
|
+
"""Cancel a pending quest for the party or a specific group.
|
1529
|
+
|
1530
|
+
Cancel a quest that has not yet startet. All accepted and pending invitations
|
1531
|
+
will be canceled and the quest roll returned to the owner's inventory.
|
1532
|
+
Only quest leader or group leader can perform this action.
|
1533
|
+
|
1534
|
+
Parameters
|
1535
|
+
----------
|
1536
|
+
group_id : UUID, optional
|
1537
|
+
The UUID of the group for which the quest is being canceled.
|
1538
|
+
Defaults to the user's party if not specified.
|
1539
|
+
|
1540
|
+
Returns
|
1541
|
+
-------
|
1542
|
+
HabiticaQuestResponse
|
1543
|
+
A response object containing details about the canceled quest.
|
1544
|
+
|
1545
|
+
Raises
|
1546
|
+
------
|
1547
|
+
NotFoundError
|
1548
|
+
If the specified group or quest could not be found.
|
1549
|
+
NotAuthorizedError
|
1550
|
+
If the user does not have permission to cancel the quest.
|
1551
|
+
aiohttp.ClientResponseError
|
1552
|
+
Raised for HTTP-related errors, such as HTTP 400 or 500 response status.
|
1553
|
+
aiohttp.ClientConnectionError
|
1554
|
+
If the connection to the API fails.
|
1555
|
+
aiohttp.ClientError
|
1556
|
+
Raised for any other exceptions encountered by `aiohttp` during the request.
|
1557
|
+
TimeoutError
|
1558
|
+
If the connection to the API times out.
|
1559
|
+
|
1560
|
+
Examples
|
1561
|
+
--------
|
1562
|
+
Cancel a pending quest for the party:
|
1563
|
+
>>> response = await habitica.cancel_quest()
|
1564
|
+
>>> print(response.success) # True if the quest was successfully canceled.
|
1565
|
+
"""
|
1566
|
+
group = "party" if not group_id else str(group_id)
|
1567
|
+
url = self.url / "api/v3/groups" / group / "quests/cancel"
|
1568
|
+
|
1569
|
+
return HabiticaQuestResponse.from_json(
|
1570
|
+
await self._request("post", url=url),
|
1571
|
+
)
|
1572
|
+
|
1573
|
+
async def start_quest(self, group_id: UUID | None = None) -> HabiticaQuestResponse:
|
1574
|
+
"""Force-start a quest for the party or a specific group.
|
1575
|
+
|
1576
|
+
Begins a quest immediately, bypassing any pending invitations that haven't been
|
1577
|
+
accepted or rejected.
|
1578
|
+
Only quest leader or group leader can perform this action.
|
1579
|
+
|
1580
|
+
Parameters
|
1581
|
+
----------
|
1582
|
+
group_id : UUID, optional
|
1583
|
+
The UUID of the group for which the quest should be started.
|
1584
|
+
Defaults to the user's party if not specified.
|
1585
|
+
|
1586
|
+
|
1587
|
+
Returns
|
1588
|
+
-------
|
1589
|
+
HabiticaQuestResponse
|
1590
|
+
A response object containing updated quest data of the group or party.
|
1591
|
+
|
1592
|
+
Raises
|
1593
|
+
------
|
1594
|
+
NotFoundError
|
1595
|
+
If the specified group or quest could not be found.
|
1596
|
+
NotAuthorizedError
|
1597
|
+
If the user does not have permission to start the quest.
|
1598
|
+
aiohttp.ClientResponseError
|
1599
|
+
Raised for HTTP-related errors, such as HTTP 400 or 500 response status.
|
1600
|
+
aiohttp.ClientConnectionError
|
1601
|
+
If the connection to the API fails.
|
1602
|
+
aiohttp.ClientError
|
1603
|
+
Raised for any other exceptions encountered by `aiohttp` during the request.
|
1604
|
+
TimeoutError
|
1605
|
+
If the connection to the API times out.
|
1606
|
+
|
1607
|
+
Examples
|
1608
|
+
--------
|
1609
|
+
Cancel a pending quest for the party:
|
1610
|
+
>>> response = await habitica.cancel_quest()
|
1611
|
+
>>> print(response.success) # True if the quest was successfully canceled.
|
1612
|
+
"""
|
1613
|
+
group = "party" if not group_id else str(group_id)
|
1614
|
+
url = self.url / "api/v3/groups" / group / "quests/force-start"
|
1615
|
+
|
1616
|
+
return HabiticaQuestResponse.from_json(
|
1617
|
+
await self._request("post", url=url),
|
1618
|
+
)
|
1619
|
+
|
1620
|
+
async def invite_quest(
|
1621
|
+
self,
|
1622
|
+
group_id: UUID | None = None,
|
1623
|
+
*,
|
1624
|
+
quest_key: str,
|
1625
|
+
) -> HabiticaQuestResponse:
|
1626
|
+
"""Invite members of the party or a specific group to participate in a quest.
|
1627
|
+
|
1628
|
+
Sends invitations for a quest to all eligible members of the specified group.
|
1629
|
+
The quest is started when all members accept or reject the invitation.
|
1630
|
+
|
1631
|
+
Parameters
|
1632
|
+
----------
|
1633
|
+
group_id : UUID, optional
|
1634
|
+
The UUID of the group for which the quest invitations should be sent.
|
1635
|
+
Defaults to the user's party if not specified.
|
1636
|
+
quest_key : str
|
1637
|
+
The unique key identifying the quest to invite members to.
|
1638
|
+
|
1639
|
+
Returns
|
1640
|
+
-------
|
1641
|
+
HabiticaQuestResponse
|
1642
|
+
A response object containing updated quest data of the group or party.
|
1643
|
+
|
1644
|
+
Raises
|
1645
|
+
------
|
1646
|
+
NotFoundError
|
1647
|
+
If the specified group or quest could not be found.
|
1648
|
+
aiohttp.ClientResponseError
|
1649
|
+
Raised for HTTP-related errors, such as HTTP 400 or 500 response status.
|
1650
|
+
aiohttp.ClientConnectionError
|
1651
|
+
If the connection to the API fails.
|
1652
|
+
aiohttp.ClientError
|
1653
|
+
Raised for any other exceptions encountered by `aiohttp` during the request.
|
1654
|
+
TimeoutError
|
1655
|
+
If the connection to the API times out.
|
1656
|
+
|
1657
|
+
Examples
|
1658
|
+
--------
|
1659
|
+
Send a quest invitation to the party:
|
1660
|
+
>>> response = await habitica.invite_quest(quest_key="dilatory_derby")
|
1661
|
+
>>> print(response.success) # True if invitations were successfully sent.
|
1662
|
+
|
1663
|
+
Send a quest invitation to a specific group:
|
1664
|
+
>>> group_id = UUID("12345678-1234-5678-1234-567812345678")
|
1665
|
+
>>> response = await habitica.invite_quest(group_id, quest_key="golden_knight")
|
1666
|
+
>>> print(response.success) # True if invitations were successfully sent.
|
1667
|
+
"""
|
1668
|
+
group = "party" if not group_id else str(group_id)
|
1669
|
+
url = self.url / "api/v3/groups" / group / "quests/invite" / quest_key
|
1670
|
+
|
1671
|
+
return HabiticaQuestResponse.from_json(
|
1672
|
+
await self._request("post", url=url),
|
1673
|
+
)
|
1674
|
+
|
1675
|
+
async def leave_quest(self, group_id: UUID | None = None) -> HabiticaQuestResponse:
|
1676
|
+
"""Leave the current quest from the party or a specific group.
|
1677
|
+
|
1678
|
+
Allows a user to exit an ongoing quest they are part of. This action removes
|
1679
|
+
them from the quest but does not affect its progress for other participants.
|
1680
|
+
Users who leave a quest will not contribute to its completion or receive rewards.
|
1681
|
+
|
1682
|
+
Parameters
|
1683
|
+
----------
|
1684
|
+
group_id : UUID, optional
|
1685
|
+
The UUID of the group associated with the quest the user is leaving.
|
1686
|
+
Defaults to the user's party if not specified.
|
1687
|
+
quest_key : str
|
1688
|
+
The unique key identifying the quest to invite members to.
|
1689
|
+
|
1690
|
+
Returns
|
1691
|
+
-------
|
1692
|
+
HabiticaQuestResponse
|
1693
|
+
A response object containing updated quest data of the group or party.
|
1694
|
+
|
1695
|
+
Raises
|
1696
|
+
------
|
1697
|
+
NotFoundError
|
1698
|
+
If the specified group or quest could not be found.
|
1699
|
+
aiohttp.ClientResponseError
|
1700
|
+
Raised for HTTP-related errors, such as HTTP 400 or 500 response status.
|
1701
|
+
aiohttp.ClientConnectionError
|
1702
|
+
If the connection to the API fails.
|
1703
|
+
aiohttp.ClientError
|
1704
|
+
Raised for any other exceptions encountered by `aiohttp` during the request.
|
1705
|
+
TimeoutError
|
1706
|
+
If the connection to the API times out.
|
1707
|
+
|
1708
|
+
Examples
|
1709
|
+
--------
|
1710
|
+
Leave the current quest in the user's party:
|
1711
|
+
>>> response = await habitica.leave_quest()
|
1712
|
+
>>> print(response.success) # True if the user successfully left the quest.
|
1713
|
+
|
1714
|
+
Leave the current quest in a specific group:
|
1715
|
+
>>> group_id = UUID("12345678-1234-5678-1234-567812345678")
|
1716
|
+
>>> response = await habitica.leave_quest(group_id)
|
1717
|
+
>>> print(response.success) # True if the user successfully left the quest.
|
1718
|
+
"""
|
1719
|
+
group = "party" if not group_id else str(group_id)
|
1720
|
+
url = self.url / "api/v3/groups" / group / "quests/leave"
|
1721
|
+
|
1722
|
+
return HabiticaQuestResponse.from_json(
|
1723
|
+
await self._request("post", url=url),
|
1724
|
+
)
|
1725
|
+
|
1273
1726
|
def _cache_asset(self, asset: str, asset_data: IO[bytes]) -> None:
|
1274
1727
|
"""Cache an asset and maintain the cache size limit by removing older entries.
|
1275
1728
|
|
@@ -1546,3 +1999,33 @@ class Habitica:
|
|
1546
1999
|
image.save(fp, fmt)
|
1547
2000
|
|
1548
2001
|
return user_styles
|
2002
|
+
|
2003
|
+
async def habitipy(self) -> HabitipyAsync:
|
2004
|
+
"""Create a Habitipy instance."""
|
2005
|
+
|
2006
|
+
_session = self._session
|
2007
|
+
_headers = self._headers
|
2008
|
+
loop = asyncio.get_running_loop()
|
2009
|
+
|
2010
|
+
class HAHabitipyAsync(HabitipyAsync):
|
2011
|
+
"""Closure API class to hold session."""
|
2012
|
+
|
2013
|
+
def __call__(self, **kwargs) -> Any:
|
2014
|
+
"""Pass session to habitipy."""
|
2015
|
+
return super().__call__(_session, **kwargs)
|
2016
|
+
|
2017
|
+
def _make_headers(self) -> dict[str, str]:
|
2018
|
+
"""Inject headers."""
|
2019
|
+
headers = super()._make_headers()
|
2020
|
+
headers.update(_headers)
|
2021
|
+
return headers
|
2022
|
+
|
2023
|
+
return await loop.run_in_executor(
|
2024
|
+
None,
|
2025
|
+
HAHabitipyAsync,
|
2026
|
+
{
|
2027
|
+
"url": str(self.url),
|
2028
|
+
"login": self._headers.get("X-API-USER"),
|
2029
|
+
"password": self._headers.get("X-API-KEY"),
|
2030
|
+
}, # type: ignore[var-annotated]
|
2031
|
+
)
|