Habiticalib 0.1.0a3__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 CHANGED
@@ -8,17 +8,24 @@ from .exceptions import (
8
8
  NotFoundError,
9
9
  TooManyRequestsError,
10
10
  )
11
+ from .helpers import deserialize_task
11
12
  from .lib import Habitica
12
13
  from .types import (
13
14
  Attributes,
15
+ ChangeClassData,
16
+ ContentData,
14
17
  Direction,
15
18
  Frequency,
16
19
  HabiticaClass,
17
20
  HabiticaClassSystemResponse,
21
+ HabiticaContentResponse,
18
22
  HabiticaErrorResponse,
23
+ HabiticaGroupMembersResponse,
19
24
  HabiticaLoginResponse,
25
+ HabiticaQuestResponse,
20
26
  HabiticaResponse,
21
27
  HabiticaScoreResponse,
28
+ HabiticaSleepResponse,
22
29
  HabiticaStatsResponse,
23
30
  HabiticaTagResponse,
24
31
  HabiticaTagsResponse,
@@ -28,10 +35,18 @@ from .types import (
28
35
  HabiticaUserExport,
29
36
  HabiticaUserResponse,
30
37
  Language,
38
+ LoginData,
39
+ QuestData,
40
+ ScoreData,
31
41
  Skill,
42
+ StatsUser,
43
+ TagsUser,
32
44
  Task,
45
+ TaskData,
33
46
  TaskFilter,
47
+ TaskPriority,
34
48
  TaskType,
49
+ UserData,
35
50
  UserStyles,
36
51
  )
37
52
 
@@ -40,17 +55,24 @@ __all__ = [
40
55
  "ASSETS_URL",
41
56
  "Attributes",
42
57
  "BadRequestError",
58
+ "ChangeClassData",
59
+ "ContentData",
43
60
  "DEFAULT_URL",
61
+ "deserialize_task",
44
62
  "Direction",
45
63
  "Frequency",
46
64
  "Habitica",
47
65
  "HabiticaClass",
48
66
  "HabiticaClassSystemResponse",
67
+ "HabiticaContentResponse",
49
68
  "HabiticaErrorResponse",
50
69
  "HabiticaException",
70
+ "HabiticaGroupMembersResponse",
51
71
  "HabiticaLoginResponse",
72
+ "HabiticaQuestResponse",
52
73
  "HabiticaResponse",
53
74
  "HabiticaScoreResponse",
75
+ "HabiticaSleepResponse",
54
76
  "HabiticaStatsResponse",
55
77
  "HabiticaTagResponse",
56
78
  "HabiticaTagsResponse",
@@ -60,12 +82,20 @@ __all__ = [
60
82
  "HabiticaUserExport",
61
83
  "HabiticaUserResponse",
62
84
  "Language",
85
+ "LoginData",
63
86
  "NotAuthorizedError",
64
87
  "NotFoundError",
88
+ "QuestData",
89
+ "ScoreData",
65
90
  "Skill",
91
+ "StatsUser",
92
+ "TagsUser",
66
93
  "Task",
94
+ "TaskData",
67
95
  "TaskFilter",
96
+ "TaskPriority",
68
97
  "TaskType",
69
98
  "TooManyRequestsError",
99
+ "UserData",
70
100
  "UserStyles",
71
101
  ]
habiticalib/const.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """Constants for Habiticalib."""
2
2
 
3
- __version__ = "0.1.0a3"
3
+ __version__ = "0.2.0"
4
4
 
5
5
  DEFAULT_URL = "https://habitica.com/"
6
6
  ASSETS_URL = "https://habitica-assets.s3.amazonaws.com/mobileApp/images/"
@@ -14,3 +14,5 @@ BACKER_ONLY_GEAR = {
14
14
  "shield_special_ks2019": "BackerOnly-Equip-MythicGryphonShield.gif",
15
15
  "weapon_special_ks2019": "BackerOnly-Equip-MythicGryphonGlaive.gif",
16
16
  }
17
+
18
+ PAGE_LIMIT = 60
habiticalib/lib.py CHANGED
@@ -6,13 +6,14 @@ 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,
@@ -31,10 +32,14 @@ from .types import (
31
32
  Direction,
32
33
  HabiticaClass,
33
34
  HabiticaClassSystemResponse,
35
+ HabiticaContentResponse,
34
36
  HabiticaErrorResponse,
37
+ HabiticaGroupMembersResponse,
35
38
  HabiticaLoginResponse,
39
+ HabiticaQuestResponse,
36
40
  HabiticaResponse,
37
41
  HabiticaScoreResponse,
42
+ HabiticaSleepResponse,
38
43
  HabiticaStatsResponse,
39
44
  HabiticaTagResponse,
40
45
  HabiticaTagsResponse,
@@ -260,14 +265,15 @@ class Habitica:
260
265
  self,
261
266
  task_type: TaskFilter | None = None,
262
267
  due_date: datetime | None = None,
263
- ) -> HabiticaResponse:
268
+ ) -> HabiticaTasksResponse:
264
269
  """Get the authenticated user's tasks.
265
270
 
266
271
  Parameters
267
272
  ----------
268
273
  task_type : TaskFilter | None
269
274
  The type of task to retrieve, defined in TaskFilter enum.
270
- If `None`, all task types will be retrieved (default is None).
275
+ If `None`, all task types will be retrieved except completed to-dos
276
+ (default is None).
271
277
 
272
278
  due_date : datetime | None
273
279
  Optional date to use for computing the nextDue field for each returned task.
@@ -455,7 +461,7 @@ class Habitica:
455
461
  Returns
456
462
  -------
457
463
  HabiticaTaskResponse
458
- A response object containing the data for the deleted task.
464
+ A response containing an empty data object.
459
465
 
460
466
  Raises
461
467
  ------
@@ -559,7 +565,7 @@ class Habitica:
559
565
  async def get_content(
560
566
  self,
561
567
  language: Language | None = None,
562
- ) -> HabiticaResponse:
568
+ ) -> HabiticaContentResponse:
563
569
  """
564
570
  Fetch game content from the Habitica API.
565
571
 
@@ -601,7 +607,7 @@ class Habitica:
601
607
  if language:
602
608
  params.update({"language": language.value})
603
609
 
604
- return HabiticaResponse.from_json(
610
+ return HabiticaContentResponse.from_json(
605
611
  await self._request("get", url=url, params=params),
606
612
  )
607
613
 
@@ -848,17 +854,17 @@ class Habitica:
848
854
  if target_id:
849
855
  params.update({"targetId": str(target_id)})
850
856
  return HabiticaUserResponse.from_json(
851
- await self._request("post", url=url, json=params),
857
+ await self._request("post", url=url, params=params),
852
858
  )
853
859
 
854
860
  async def toggle_sleep(
855
861
  self,
856
- ) -> HabiticaResponse:
862
+ ) -> HabiticaSleepResponse:
857
863
  """Toggles the user's sleep mode in Habitica.
858
864
 
859
865
  Returns
860
866
  -------
861
- HabiticaResponse
867
+ HabiticaSleepResponse
862
868
  A response object containing the result of the sleep mode toggle,
863
869
  and the new sleep state (True if sleeping, False if not).
864
870
 
@@ -875,7 +881,7 @@ class Habitica:
875
881
  """
876
882
  url = self.url / "api/v3/user/sleep"
877
883
 
878
- return HabiticaResponse.from_json(await self._request("post", url=url))
884
+ return HabiticaSleepResponse.from_json(await self._request("post", url=url))
879
885
 
880
886
  async def revive(
881
887
  self,
@@ -1280,6 +1286,443 @@ class Habitica:
1280
1286
  await self._request("post", url=url, json=json),
1281
1287
  )
1282
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
+
1283
1726
  def _cache_asset(self, asset: str, asset_data: IO[bytes]) -> None:
1284
1727
  """Cache an asset and maintain the cache size limit by removing older entries.
1285
1728
 
@@ -1556,3 +1999,33 @@ class Habitica:
1556
1999
  image.save(fp, fmt)
1557
2000
 
1558
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
+ )
habiticalib/types.py CHANGED
@@ -591,7 +591,7 @@ class PreferencesUser:
591
591
  webhooks: dict = field(default_factory=dict)
592
592
  improvementCategories: list[str] = field(default_factory=list)
593
593
  timezoneOffsetAtLastCron: int | None = None
594
- language: str | None = None
594
+ language: Language | None = None
595
595
 
596
596
 
597
597
  @dataclass(kw_only=True)
@@ -662,13 +662,6 @@ class StatsUser:
662
662
  Int: int | None = field(default=None, metadata=field_options(alias="int"))
663
663
 
664
664
 
665
- field(
666
- metadata=field_options(
667
- deserialize=serialize_datetime,
668
- )
669
- )
670
-
671
-
672
665
  @dataclass(kw_only=True)
673
666
  class TagsUser:
674
667
  """Tags user data."""
@@ -777,6 +770,13 @@ class HabiticaUserResponse(HabiticaResponse):
777
770
  data: UserData
778
771
 
779
772
 
773
+ @dataclass(kw_only=True)
774
+ class HabiticaGroupMembersResponse(HabiticaResponse):
775
+ """Representation of a group members data response."""
776
+
777
+ data: list[UserData]
778
+
779
+
780
780
  @dataclass(kw_only=True)
781
781
  class CompletedBy:
782
782
  """Task group completedby data."""
@@ -1107,6 +1107,25 @@ class HabiticaTagResponse(HabiticaResponse, DataClassORJSONMixin):
1107
1107
  data: TagsUser
1108
1108
 
1109
1109
 
1110
+ @dataclass(kw_only=True)
1111
+ class QuestData:
1112
+ """Quest data."""
1113
+
1114
+ progress: ProgressQuest = field(default_factory=ProgressQuest)
1115
+ active: bool = False
1116
+ members: dict[str, bool | None]
1117
+ extra: dict | None = None
1118
+ key: str
1119
+ leader: UUID | None = None
1120
+
1121
+
1122
+ @dataclass(kw_only=True)
1123
+ class HabiticaQuestResponse(HabiticaResponse, DataClassORJSONMixin):
1124
+ """Representation of a quest response."""
1125
+
1126
+ data: QuestData
1127
+
1128
+
1110
1129
  @dataclass
1111
1130
  class ChangeClassData:
1112
1131
  """Change class data."""
@@ -1131,6 +1150,13 @@ class HabiticaTaskOrderResponse(HabiticaResponse):
1131
1150
  data: list[UUID] = field(default_factory=list)
1132
1151
 
1133
1152
 
1153
+ @dataclass
1154
+ class HabiticaSleepResponse(HabiticaResponse):
1155
+ """Representation of a sleep response."""
1156
+
1157
+ data: bool
1158
+
1159
+
1134
1160
  class TaskFilter(StrEnum):
1135
1161
  """Enum representing the valid types of tasks for requests."""
1136
1162
 
@@ -1187,7 +1213,7 @@ class Skill(StrEnum):
1187
1213
  VALOROUS_PRESENCE = "valorousPresence"
1188
1214
  INTIMIDATING_GAZE = "intimidate"
1189
1215
  # Rogue skills
1190
- PICKPOCKET = "Pickpocket"
1216
+ PICKPOCKET = "pickPocket"
1191
1217
  BACKSTAB = "backStab"
1192
1218
  TOOLS_OF_THE_TRADE = "toolsOfTrade"
1193
1219
  STEALTH = "stealth"
@@ -1222,3 +1248,387 @@ class TaskPriority(Enum):
1222
1248
  EASY = 1
1223
1249
  MEDIUM = 1.5
1224
1250
  HARD = 2
1251
+
1252
+
1253
+ @dataclass
1254
+ class AchievmentContent:
1255
+ """Achievment content data."""
1256
+
1257
+ icon: str
1258
+ key: str
1259
+ titleKey: str | None = None
1260
+ textKey: str | None = None
1261
+ singularTitleKey: str | None = None
1262
+ singularTextKey: str | None = None
1263
+ pluralTitleKey: str | None = None
1264
+ pluralTextKey: str | None = None
1265
+
1266
+
1267
+ @dataclass
1268
+ class AnimalColorAchievementContent:
1269
+ """animalColorAchievement content data."""
1270
+
1271
+ color: str
1272
+ petAchievement: str
1273
+ petNotificationType: str
1274
+ mountAchievement: str
1275
+ mountNotificationType: str
1276
+
1277
+
1278
+ @dataclass
1279
+ class AnimalSetAchievementContent:
1280
+ """animalSetAchievements content data."""
1281
+
1282
+ Type: str = field(metadata=field_options(alias="type"))
1283
+ species: list[str]
1284
+ achievementKey: str
1285
+ notificationType: str
1286
+
1287
+
1288
+ @dataclass
1289
+ class StableAchievementContent:
1290
+ """stableAchievements content data."""
1291
+
1292
+ masterAchievement: str
1293
+ masterNotificationType: str
1294
+
1295
+
1296
+ @dataclass
1297
+ class PetSetCompleteAchievsContent:
1298
+ """petSetCompleteAchievs content data."""
1299
+
1300
+ color: str
1301
+ petAchievement: str
1302
+ petNotificationType: str
1303
+
1304
+
1305
+ @dataclass
1306
+ class QuestBossRage:
1307
+ """QuestBossRage content data."""
1308
+
1309
+ title: str
1310
+ description: str
1311
+ value: float
1312
+ effect: str | None = None
1313
+ healing: float | None = None
1314
+
1315
+
1316
+ @dataclass
1317
+ class QuestBoss:
1318
+ """QuestBoss content data."""
1319
+
1320
+ name: str
1321
+ hp: float
1322
+ Str: float = field(metadata=field_options(alias="str"))
1323
+ Def: float = field(metadata=field_options(alias="def"))
1324
+ rage: QuestBossRage | None = None
1325
+
1326
+
1327
+ @dataclass
1328
+ class QuestItem:
1329
+ """QuestItem content data."""
1330
+
1331
+ Type: str = field(metadata=field_options(alias="type"))
1332
+ key: str
1333
+ text: str
1334
+
1335
+
1336
+ @dataclass
1337
+ class QuestDrop:
1338
+ """QuestDrop content data."""
1339
+
1340
+ gp: float
1341
+ exp: float
1342
+ items: list[QuestItem] | None = None
1343
+
1344
+
1345
+ @dataclass
1346
+ class QuestCollect:
1347
+ """QuestCollect content data."""
1348
+
1349
+ text: str
1350
+ count: int
1351
+
1352
+
1353
+ @dataclass
1354
+ class QuestUnlockCondition:
1355
+ """QuestUnlockCondition content data."""
1356
+
1357
+ condition: str
1358
+ text: str
1359
+
1360
+
1361
+ @dataclass
1362
+ class QuestsContent:
1363
+ """petSetCompleteAchievs content data."""
1364
+
1365
+ text: str
1366
+ notes: str
1367
+
1368
+ completion: str
1369
+ category: str
1370
+
1371
+ drop: QuestDrop
1372
+ key: str
1373
+ goldValue: float | None = None
1374
+ value: float | None = None
1375
+ previous: str | None = None
1376
+ prereqQuests: list[str] | None = None
1377
+ collect: dict[str, QuestCollect] | None = None
1378
+ unlockCondition: QuestUnlockCondition | None = None
1379
+ boss: QuestBoss | None = None
1380
+ group: str | None = None
1381
+
1382
+
1383
+ @dataclass
1384
+ class ItemListEntry:
1385
+ """ItemListEntry content data."""
1386
+
1387
+ localeKey: str
1388
+ isEquipment: bool
1389
+
1390
+
1391
+ @dataclass
1392
+ class ItemListContent:
1393
+ """ItemListContent content data."""
1394
+
1395
+ weapon: ItemListEntry
1396
+ armor: ItemListEntry
1397
+ head: ItemListEntry
1398
+ shield: ItemListEntry
1399
+ back: ItemListEntry
1400
+ body: ItemListEntry
1401
+ headAccessory: ItemListEntry
1402
+ eyewear: ItemListEntry
1403
+ hatchingPotions: ItemListEntry
1404
+ premiumHatchingPotions: ItemListEntry
1405
+ eggs: ItemListEntry
1406
+ quests: ItemListEntry
1407
+ food: ItemListEntry
1408
+ Saddle: ItemListEntry
1409
+ bundles: ItemListEntry
1410
+
1411
+
1412
+ @dataclass
1413
+ class GearEntry:
1414
+ """GearEntry content data."""
1415
+
1416
+ text: str
1417
+ notes: str
1418
+ Int: int = field(metadata=field_options(alias="int"))
1419
+ value: int
1420
+ Type: str = field(metadata=field_options(alias="type"))
1421
+ key: str
1422
+ Set: str = field(metadata=field_options(alias="set"))
1423
+ klass: str
1424
+ index: str
1425
+ Str: int = field(metadata=field_options(alias="str"))
1426
+ per: int
1427
+ con: int
1428
+
1429
+
1430
+ @dataclass
1431
+ class GearClass:
1432
+ """GearClass content data."""
1433
+
1434
+ base: dict[str, GearEntry] | None = None
1435
+ warrior: dict[str, GearEntry] | None = None
1436
+ wizard: dict[str, GearEntry] | None = None
1437
+ rogue: dict[str, GearEntry] | None = None
1438
+ special: dict[str, GearEntry] | None = None
1439
+ armoire: dict[str, GearEntry] | None = None
1440
+ mystery: dict[str, GearEntry] | None = None
1441
+ healer: dict[str, GearEntry] | None = None
1442
+
1443
+
1444
+ @dataclass
1445
+ class GearType:
1446
+ """GearType content data."""
1447
+
1448
+ weapon: GearClass
1449
+ armor: GearClass
1450
+ head: GearClass
1451
+ shield: GearClass
1452
+ back: GearClass
1453
+ body: GearClass
1454
+ headAccessory: GearClass
1455
+ eyewear: GearClass
1456
+
1457
+
1458
+ @dataclass
1459
+ class GearContent:
1460
+ """GearContent content data."""
1461
+
1462
+ tree: GearType
1463
+ flat: dict[str, GearEntry]
1464
+
1465
+
1466
+ @dataclass
1467
+ class SpellEntry:
1468
+ """SpellEntry content data."""
1469
+
1470
+ text: str
1471
+ mana: int
1472
+ target: str
1473
+ notes: str
1474
+ key: str
1475
+ previousPurchase: bool | None = None
1476
+ limited: bool | None = None
1477
+ lvl: int | None = None
1478
+ value: int | None = None
1479
+ immediateUse: bool | None = None
1480
+ purchaseType: str | None = None
1481
+ silent: bool | None = None
1482
+
1483
+
1484
+ @dataclass
1485
+ class SpellsClass:
1486
+ """SpellsClass content data."""
1487
+
1488
+ wizard: dict[str, SpellEntry]
1489
+ warrior: dict[str, SpellEntry]
1490
+ rogue: dict[str, SpellEntry]
1491
+ healer: dict[str, SpellEntry]
1492
+ special: dict[str, SpellEntry]
1493
+
1494
+
1495
+ @dataclass
1496
+ class CarTypes:
1497
+ """CarTypes content data."""
1498
+
1499
+ key: str
1500
+ messageOptions: int
1501
+ yearRound: bool = False
1502
+
1503
+
1504
+ @dataclass
1505
+ class SpecialItemEntry:
1506
+ """Item content data."""
1507
+
1508
+ key: str | None = None
1509
+ text: str | None = None
1510
+ notes: str | None = None
1511
+ immediateUse: bool | None = None
1512
+ limited: bool | None = None
1513
+ mana: int | None = None
1514
+ previousPurchase: bool | None = None
1515
+ purchaseType: str | None = None
1516
+ silent: bool | None = None
1517
+ target: str | None = None
1518
+ value: int | None = None
1519
+
1520
+
1521
+ @dataclass
1522
+ class EggEntry:
1523
+ """Egg content data."""
1524
+
1525
+ text: str | None = None
1526
+ mountText: str | None = None
1527
+ adjective: str | None = None
1528
+ value: int | None = None
1529
+ key: str | None = None
1530
+ notes: str | None = None
1531
+
1532
+
1533
+ @dataclass
1534
+ class HatchingPotionEntry:
1535
+ """Hatching potion content data."""
1536
+
1537
+ value: int | None = None
1538
+ key: str | None = None
1539
+ text: str | None = None
1540
+ notes: str | None = None
1541
+ premium: bool | None = None
1542
+ limited: bool | None = None
1543
+ _addlNotes: str | None = None
1544
+ wacky: bool | None = None
1545
+
1546
+
1547
+ @dataclass
1548
+ class PetEntry:
1549
+ """Pet content data."""
1550
+
1551
+ key: str | None = None
1552
+ Type: str | None = field(default=None, metadata=field_options(alias="type"))
1553
+ potion: str | None = None
1554
+ egg: str | None = None
1555
+ text: str | None = None
1556
+
1557
+
1558
+ @dataclass
1559
+ class InventoryItemEntry:
1560
+ """Inventory item content data."""
1561
+
1562
+ text: str | None = None
1563
+ textA: str | None = None
1564
+ textThe: str | None = None
1565
+ target: str | None = None
1566
+ value: int | None = None
1567
+ key: str | None = None
1568
+ notes: str | None = None
1569
+ canDrop: bool | None = None
1570
+
1571
+
1572
+ @dataclass
1573
+ class ContentData:
1574
+ """Content data."""
1575
+
1576
+ achievements: dict[str, AchievmentContent]
1577
+ questSeriesAchievements: dict[str, list[str]]
1578
+ animalColorAchievements: list[AnimalColorAchievementContent]
1579
+ animalSetAchievements: dict[str, AnimalSetAchievementContent]
1580
+ stableAchievements: dict[str, StableAchievementContent]
1581
+ petSetCompleteAchievs: list[PetSetCompleteAchievsContent]
1582
+ quests: dict[str, QuestsContent]
1583
+ questsByLevel: list[QuestsContent]
1584
+ userCanOwnQuestCategories: list[str]
1585
+ itemList: ItemListContent
1586
+ gear: GearContent
1587
+ spells: SpellsClass
1588
+ audioThemes: list[str]
1589
+ # mystery
1590
+ officialPinnedItems: list
1591
+ # bundles
1592
+ # potion
1593
+ # armoire
1594
+ # events
1595
+ # repeatingEvents
1596
+ classes: list[str]
1597
+ gearTypes: list[str]
1598
+ cardTypes: dict[str, CarTypes]
1599
+ special: dict[str, SpecialItemEntry]
1600
+ dropEggs: dict[str, EggEntry]
1601
+ questEggs: dict[str, EggEntry]
1602
+ eggs: dict[str, EggEntry]
1603
+ # timeTravelStable
1604
+ dropHatchingPotions: dict[str, HatchingPotionEntry]
1605
+ premiumHatchingPotions: dict[str, HatchingPotionEntry]
1606
+ wackyHatchingPotions: dict[str, HatchingPotionEntry]
1607
+ hatchingPotions: dict[str, HatchingPotionEntry]
1608
+ pets: dict[str, bool]
1609
+ premiumPets: dict[str, bool]
1610
+ questPets: dict[str, bool]
1611
+ specialPets: dict[str, str]
1612
+ wackyPets: dict[str, bool]
1613
+ petInfo: dict[str, PetEntry]
1614
+ mounts: dict[str, bool]
1615
+ premiumMounts: dict[str, bool]
1616
+ questMounts: dict[str, bool]
1617
+ specialMounts: dict[str, str]
1618
+ mountInfo: dict[str, PetEntry]
1619
+ food: dict[str, InventoryItemEntry]
1620
+ # appearances
1621
+ # backgrounds
1622
+ # backgroundsFlat
1623
+ # userDefaults
1624
+ # tasksByCategory
1625
+ # userDefaultsMobile
1626
+ # faq
1627
+ # loginIncentives
1628
+
1629
+
1630
+ @dataclass
1631
+ class HabiticaContentResponse(HabiticaResponse):
1632
+ """Representation of a content response."""
1633
+
1634
+ data: ContentData
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: Habiticalib
3
- Version: 0.1.0a3
3
+ Version: 0.2.0
4
4
  Summary: Asynchronous Python client library for the Habitica API
5
5
  Project-URL: Documentation, https://tr4nt0r.github.io/habiticalib/
6
6
  Project-URL: Source, https://github.com/tr4nt0r/habiticalib
@@ -11,6 +11,7 @@ Classifier: Operating System :: OS Independent
11
11
  Classifier: Programming Language :: Python :: 3 :: Only
12
12
  Requires-Python: >=3.12
13
13
  Requires-Dist: aiohttp~=3.9
14
+ Requires-Dist: habitipy~=0.3.3
14
15
  Requires-Dist: mashumaro~=3.13
15
16
  Requires-Dist: orjson~=3.10
16
17
  Requires-Dist: pillow~=11.0
@@ -0,0 +1,11 @@
1
+ habiticalib/__init__.py,sha256=Mo0G1V_RKbIiAGYMWqznhOLtMxCM2UAmCqHW5JBEryw,2201
2
+ habiticalib/const.py,sha256=pqAPlZylW8FyBWg31nixggB3u9mLksdo_D0nyLJulfM,624
3
+ habiticalib/exceptions.py,sha256=oVFCGbHkVn0UpIKIPZPzXfvzs9US4R05ebdEn6cOpqM,1350
4
+ habiticalib/helpers.py,sha256=IRZLYWkDVLI0iVBgBMmvZ6L83KCUl-CnzGhUR_tP6Fg,4576
5
+ habiticalib/lib.py,sha256=woSpO1YeFv2d1BtHM1pKR1S4FmzbMnqSM0SAgQpupFU,72405
6
+ habiticalib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ habiticalib/types.py,sha256=zJq3jnZNUSU0p5XX_14PpGHmxmCeuf1F_YQjFpcoMLo,42341
8
+ habiticalib-0.2.0.dist-info/METADATA,sha256=G5slvwHpL9ELZkJRjE_e8YUw4UFdNPuTEvcfC59M1Po,4185
9
+ habiticalib-0.2.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
10
+ habiticalib-0.2.0.dist-info/licenses/LICENSE,sha256=oIinIOSJ49l1iVIRI3XGXFWt6SF7a83kEFBAY8ORwNI,1084
11
+ habiticalib-0.2.0.dist-info/RECORD,,
@@ -1,11 +0,0 @@
1
- habiticalib/__init__.py,sha256=rwRSTk3JFaSnVKRniTC82tA5JKPSuQ-AD_mAok2QU_g,1561
2
- habiticalib/const.py,sha256=aeFLubB_xXy_3ChIDmT9Atp_aOe58oOqcAs6vepIsoI,609
3
- habiticalib/exceptions.py,sha256=oVFCGbHkVn0UpIKIPZPzXfvzs9US4R05ebdEn6cOpqM,1350
4
- habiticalib/helpers.py,sha256=IRZLYWkDVLI0iVBgBMmvZ6L83KCUl-CnzGhUR_tP6Fg,4576
5
- habiticalib/lib.py,sha256=MMs3BINdbZUszUy_DCWvFWDZKy2or1UA5R47X3GeUwI,54274
6
- habiticalib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- habiticalib/types.py,sha256=7xpxpvpDQBxsyuD241vQxeXjk6zuEC4yXzw-Of7G7Bw,33187
8
- habiticalib-0.1.0a3.dist-info/METADATA,sha256=fmMClL_kOz3sWYotJu9QZT49CfV6sEcq9sq0bfV7YZ8,4156
9
- habiticalib-0.1.0a3.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
10
- habiticalib-0.1.0a3.dist-info/licenses/LICENSE,sha256=oIinIOSJ49l1iVIRI3XGXFWt6SF7a83kEFBAY8ORwNI,1084
11
- habiticalib-0.1.0a3.dist-info/RECORD,,