Habiticalib 0.2.0a0__tar.gz → 0.2.0a2__tar.gz

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.
Files changed (45) hide show
  1. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/PKG-INFO +1 -1
  2. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/src/habiticalib/__init__.py +6 -0
  3. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/src/habiticalib/const.py +3 -1
  4. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/src/habiticalib/lib.py +448 -7
  5. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/src/habiticalib/types.py +412 -9
  6. habiticalib-0.2.0a2/tests/__snapshots__/test_lib.ambr +7 -0
  7. habiticalib-0.2.0a0/tests/__snapshots__/test_lib.ambr +0 -7
  8. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/.cruft.json +0 -0
  9. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/.editorconfig +0 -0
  10. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/.github/FUNDING.yml +0 -0
  11. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/.github/dependabot.yml +0 -0
  12. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/.github/labels.yml +0 -0
  13. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/.github/release-drafter.yml +0 -0
  14. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/.github/workflows/build.yml +0 -0
  15. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/.github/workflows/documentation.yml +0 -0
  16. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/.github/workflows/draft.yml +0 -0
  17. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/.github/workflows/labeler.yml +0 -0
  18. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/.gitignore +0 -0
  19. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/.pre-commit-config.yaml +0 -0
  20. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/.vscode/settings.json +0 -0
  21. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/LICENSE +0 -0
  22. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/README.md +0 -0
  23. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/docs/index.md +0 -0
  24. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/docs/reference/habiticalib.md +0 -0
  25. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/mkdocs.yml +0 -0
  26. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/pyproject.toml +0 -0
  27. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/src/habiticalib/exceptions.py +0 -0
  28. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/src/habiticalib/helpers.py +0 -0
  29. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/src/habiticalib/py.typed +0 -0
  30. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/tests/__init__.py +0 -0
  31. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/tests/__snapshots__/test_avatar.ambr +0 -0
  32. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/tests/conftest.py +0 -0
  33. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/tests/fixtures/login.json +0 -0
  34. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/tests/fixtures/user.json +0 -0
  35. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/tests/fixtures/user_styles.json +0 -0
  36. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/tests/fixtures/user_styles_kickstarter.json +0 -0
  37. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/tests/fixtures/user_styles_seafoam.json +0 -0
  38. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/tests/fixtures/user_styles_shinySeed.json +0 -0
  39. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/tests/fixtures/user_styles_sleeping.json +0 -0
  40. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/tests/fixtures/user_styles_snowball.json +0 -0
  41. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/tests/fixtures/user_styles_spookySparkles.json +0 -0
  42. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/tests/fixtures/user_styles_with_chair.json +0 -0
  43. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/tests/test_avatar.py +0 -0
  44. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/tests/test_init.py +0 -0
  45. {habiticalib-0.2.0a0 → habiticalib-0.2.0a2}/tests/test_lib.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: Habiticalib
3
- Version: 0.2.0a0
3
+ Version: 0.2.0a2
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
@@ -15,8 +15,11 @@ from .types import (
15
15
  Frequency,
16
16
  HabiticaClass,
17
17
  HabiticaClassSystemResponse,
18
+ HabiticaContentResponse,
18
19
  HabiticaErrorResponse,
20
+ HabiticaGroupMembersResponse,
19
21
  HabiticaLoginResponse,
22
+ HabiticaQuestResponse,
20
23
  HabiticaResponse,
21
24
  HabiticaScoreResponse,
22
25
  HabiticaSleepResponse,
@@ -47,9 +50,12 @@ __all__ = [
47
50
  "Habitica",
48
51
  "HabiticaClass",
49
52
  "HabiticaClassSystemResponse",
53
+ "HabiticaContentResponse",
50
54
  "HabiticaErrorResponse",
51
55
  "HabiticaException",
56
+ "HabiticaGroupMembersResponse",
52
57
  "HabiticaLoginResponse",
58
+ "HabiticaQuestResponse",
53
59
  "HabiticaResponse",
54
60
  "HabiticaScoreResponse",
55
61
  "HabiticaSleepResponse",
@@ -1,6 +1,6 @@
1
1
  """Constants for Habiticalib."""
2
2
 
3
- __version__ = "0.2.0a0"
3
+ __version__ = "0.2.0a2"
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
@@ -12,7 +12,7 @@ from aiohttp import ClientError, ClientResponseError, ClientSession
12
12
  from PIL import Image
13
13
  from yarl import URL
14
14
 
15
- from .const import ASSETS_URL, BACKER_ONLY_GEAR, DEFAULT_URL
15
+ from .const import ASSETS_URL, BACKER_ONLY_GEAR, DEFAULT_URL, PAGE_LIMIT
16
16
  from .exceptions import (
17
17
  BadRequestError,
18
18
  NotAuthorizedError,
@@ -31,8 +31,11 @@ from .types import (
31
31
  Direction,
32
32
  HabiticaClass,
33
33
  HabiticaClassSystemResponse,
34
+ HabiticaContentResponse,
34
35
  HabiticaErrorResponse,
36
+ HabiticaGroupMembersResponse,
35
37
  HabiticaLoginResponse,
38
+ HabiticaQuestResponse,
36
39
  HabiticaResponse,
37
40
  HabiticaScoreResponse,
38
41
  HabiticaSleepResponse,
@@ -261,14 +264,15 @@ class Habitica:
261
264
  self,
262
265
  task_type: TaskFilter | None = None,
263
266
  due_date: datetime | None = None,
264
- ) -> HabiticaResponse:
267
+ ) -> HabiticaTasksResponse:
265
268
  """Get the authenticated user's tasks.
266
269
 
267
270
  Parameters
268
271
  ----------
269
272
  task_type : TaskFilter | None
270
273
  The type of task to retrieve, defined in TaskFilter enum.
271
- If `None`, all task types will be retrieved (default is None).
274
+ If `None`, all task types will be retrieved except completed to-dos
275
+ (default is None).
272
276
 
273
277
  due_date : datetime | None
274
278
  Optional date to use for computing the nextDue field for each returned task.
@@ -456,7 +460,7 @@ class Habitica:
456
460
  Returns
457
461
  -------
458
462
  HabiticaTaskResponse
459
- A response object containing the data for the deleted task.
463
+ A response containing an empty data object.
460
464
 
461
465
  Raises
462
466
  ------
@@ -560,7 +564,7 @@ class Habitica:
560
564
  async def get_content(
561
565
  self,
562
566
  language: Language | None = None,
563
- ) -> HabiticaResponse:
567
+ ) -> HabiticaContentResponse:
564
568
  """
565
569
  Fetch game content from the Habitica API.
566
570
 
@@ -602,7 +606,7 @@ class Habitica:
602
606
  if language:
603
607
  params.update({"language": language.value})
604
608
 
605
- return HabiticaResponse.from_json(
609
+ return HabiticaContentResponse.from_json(
606
610
  await self._request("get", url=url, params=params),
607
611
  )
608
612
 
@@ -849,7 +853,7 @@ class Habitica:
849
853
  if target_id:
850
854
  params.update({"targetId": str(target_id)})
851
855
  return HabiticaUserResponse.from_json(
852
- await self._request("post", url=url, json=params),
856
+ await self._request("post", url=url, params=params),
853
857
  )
854
858
 
855
859
  async def toggle_sleep(
@@ -1281,6 +1285,443 @@ class Habitica:
1281
1285
  await self._request("post", url=url, json=json),
1282
1286
  )
1283
1287
 
1288
+ async def get_group_members(
1289
+ self,
1290
+ group_id: UUID | None = None,
1291
+ *,
1292
+ limit: int | None = None,
1293
+ tasks: bool = False,
1294
+ public_fields: bool = False,
1295
+ last_id: UUID | None = None,
1296
+ ) -> HabiticaGroupMembersResponse:
1297
+ """Get members of the party or a specific group.
1298
+
1299
+ This method retrieves a list of members for a party or a specified group
1300
+ from the Habitica API. Additional options allow including tasks or public
1301
+ through results if necessary to collect all members. If the API rate limit is
1302
+ exceeded, the method will pause for the duration specified in the `retry-after`
1303
+ header and retry the request.
1304
+
1305
+
1306
+ Parameters
1307
+ ----------
1308
+ group_id : UUID, optional
1309
+ The UUID of the group. Defaults to the user's party if not specified.
1310
+ limit : int, optional
1311
+ Maximum number of members per request (default: 30, max: 60).
1312
+ tasks : bool, optional
1313
+ Whether to include tasks associated with the group.
1314
+ public_fields : bool, optional
1315
+ Whether to include all public fields for group members.
1316
+ last_id : UUID, optional
1317
+ For paginated requests, the UUID of the last member retrieved.
1318
+
1319
+ Returns
1320
+ -------
1321
+ HabiticaGroupMembersResponse
1322
+ A response object containing the group member data.
1323
+
1324
+ Raises
1325
+ ------
1326
+ aiohttp.ClientResponseError
1327
+ For HTTP-related errors, such as HTTP 400 or 500 response status.
1328
+ aiohttp.ClientConnectionError
1329
+ If the connection to the API fails.
1330
+ aiohttp.ClientError
1331
+ For any other exceptions raised by aiohttp during the request.
1332
+ TimeoutError
1333
+ If the connection times out.
1334
+
1335
+ Examples
1336
+ --------
1337
+ >>> members_response = await habitica.get_group_members()
1338
+ >>> for member in members_response.data:
1339
+ ... print(member.profile.name)
1340
+ """
1341
+
1342
+ if limit is not None and (limit < 1 or limit > PAGE_LIMIT):
1343
+ msg = f"The 'limit' parameter must be between 1 and {PAGE_LIMIT}."
1344
+ raise ValueError(msg)
1345
+
1346
+ group = "party" if not group_id else str(group_id)
1347
+ url = self.url / "api/v3/groups" / group / "members"
1348
+
1349
+ params: dict[str, str | int] = {}
1350
+
1351
+ if tasks:
1352
+ params["includeTasks"] = "true"
1353
+ if public_fields:
1354
+ params["includeAllPublicFields"] = "true"
1355
+ if last_id:
1356
+ params["lastId"] = str(last_id)
1357
+ if limit:
1358
+ params["limit"] = limit
1359
+
1360
+ while True:
1361
+ try:
1362
+ response = HabiticaGroupMembersResponse.from_json(
1363
+ await self._request("get", url=url, params=params),
1364
+ )
1365
+ break
1366
+ except TooManyRequestsError as e:
1367
+ await asyncio.sleep(e.retry_after)
1368
+
1369
+ if len(response.data) == limit:
1370
+ next_page = await self.get_group_members(
1371
+ group_id=group_id,
1372
+ limit=limit,
1373
+ tasks=tasks,
1374
+ public_fields=public_fields,
1375
+ last_id=response.data[-1].id,
1376
+ )
1377
+ response.data.extend(next_page.data)
1378
+
1379
+ return response
1380
+
1381
+ async def abort_quest(self, group_id: UUID | None = None) -> HabiticaQuestResponse:
1382
+ """Abort an active quest for the party or a specific group.
1383
+
1384
+ Prematurely terminates an ongoing quest, causing all progress to be lost.
1385
+ The quest scroll will be returned to the owner's inventory.
1386
+ Only the quest leader or group leader is allowed to perform this action.
1387
+
1388
+ Parameters
1389
+ ----------
1390
+ group_id : UUID, optional
1391
+ The UUID of the specific group whose quest should be aborted.
1392
+ Defaults to the user's party if not specified.
1393
+
1394
+ Returns
1395
+ -------
1396
+ HabiticaQuestResponse
1397
+ A response object containing updated quest data of the group or party.
1398
+
1399
+ Raises
1400
+ ------
1401
+ NotFoundError
1402
+ If the specified group or quest could not be found.
1403
+ NotAuthorizedError
1404
+ If the user does not have permission to abort the quest.
1405
+ aiohttp.ClientResponseError
1406
+ For HTTP-related errors, such as HTTP 400 or 500 response status.
1407
+ aiohttp.ClientConnectionError
1408
+ If the connection to the API fails.
1409
+ aiohttp.ClientError
1410
+ For any other exceptions raised by aiohttp during the request.
1411
+ TimeoutError
1412
+ If the connection times out.
1413
+
1414
+ Examples
1415
+ --------
1416
+ Abort the party's current quest:
1417
+ >>> response = await habitica.abort_quest()
1418
+ >>> print(response.success) # True if the quest was successfully aborted.
1419
+
1420
+ Abort a quest for a specific group:
1421
+ >>> group_id = UUID("12345678-1234-5678-1234-567812345678")
1422
+ >>> response = await habitica.abort_quest(group_id)
1423
+ >>> print(response.success) # True if the quest was successfully aborted.
1424
+ """
1425
+ group = "party" if not group_id else str(group_id)
1426
+ url = self.url / "api/v3/groups" / group / "quests/abort"
1427
+
1428
+ return HabiticaQuestResponse.from_json(
1429
+ await self._request("post", url=url),
1430
+ )
1431
+
1432
+ async def accept_quest(self, group_id: UUID | None = None) -> HabiticaQuestResponse:
1433
+ """Accept a pending invitation to a quest from the party or a specific group.
1434
+
1435
+ Allows a user to accept an invitation to participate in a quest within a
1436
+ specified group.
1437
+
1438
+ Parameters
1439
+ ----------
1440
+ group_id : UUID, optional
1441
+ The UUID of the group for which the quest invitation is being accepted.
1442
+ Defaults to the user's party if not specified.
1443
+
1444
+
1445
+ Returns
1446
+ -------
1447
+ HabiticaQuestResponse
1448
+ A response object containing updated quest data of the group or party.
1449
+
1450
+ Raises
1451
+ ------
1452
+ NotFoundError
1453
+ If the specified group or quest could not be found.
1454
+ aiohttp.ClientResponseError
1455
+ Raised for HTTP-related errors, such as HTTP 400 or 500 response status.
1456
+ aiohttp.ClientConnectionError
1457
+ If the connection to the API fails.
1458
+ aiohttp.ClientError
1459
+ Raised for any other exceptions encountered by `aiohttp` during the request.
1460
+ TimeoutError
1461
+ If the connection to the API times out.
1462
+
1463
+ Examples
1464
+ --------
1465
+ Accept a pending quest invitation from the party:
1466
+ >>> response = await habitica.accept_quest()
1467
+ >>> print(response.success) # True if the quest invitation was successfully accepted.
1468
+ """
1469
+ group = "party" if not group_id else str(group_id)
1470
+ url = self.url / "api/v3/groups" / group / "quests/accept"
1471
+
1472
+ return HabiticaQuestResponse.from_json(
1473
+ await self._request("post", url=url),
1474
+ )
1475
+
1476
+ async def reject_quest(self, group_id: UUID | None = None) -> HabiticaQuestResponse:
1477
+ """Reject a pending quest invitation from the party or a specific group.
1478
+
1479
+ Allows a user to reject an invitation to participate in a quest within a
1480
+ specified group. The user will not join the quest and will be excluded from
1481
+ its progress and rewards.
1482
+
1483
+ Parameters
1484
+ ----------
1485
+ group_id : UUID, optional
1486
+ The UUID of the group for which the quest invitation is being rejected.
1487
+ Defaults to the user's party if not specified.
1488
+
1489
+
1490
+ Returns
1491
+ -------
1492
+ HabiticaQuestResponse
1493
+ A response object containing updated quest data of the group or party.
1494
+
1495
+ Raises
1496
+ ------
1497
+ NotFoundError
1498
+ If the specified group or quest could not be found.
1499
+ aiohttp.ClientResponseError
1500
+ Raised for HTTP-related errors, such as HTTP 400 or 500 response status.
1501
+ aiohttp.ClientConnectionError
1502
+ If the connection to the API fails.
1503
+ aiohttp.ClientError
1504
+ Raised for any other exceptions encountered by `aiohttp` during the request.
1505
+ TimeoutError
1506
+ If the connection to the API times out.
1507
+
1508
+ Examples
1509
+ --------
1510
+ Reject a pending quest invitation from the party:
1511
+ >>> response = await habitica.reject_quest()
1512
+ >>> print(response.success) # True if the quest invitation was successfully rejected.
1513
+
1514
+ Reject a pending quest invitation from a specific group:
1515
+ >>> group_id = UUID("12345678-1234-5678-1234-567812345678")
1516
+ >>> response = await habitica.reject_quest(group_id)
1517
+ >>> print(response.success) # True if the quest invitation was successfully rejected.
1518
+ """
1519
+ group = "party" if not group_id else str(group_id)
1520
+ url = self.url / "api/v3/groups" / group / "quests/reject"
1521
+
1522
+ return HabiticaQuestResponse.from_json(
1523
+ await self._request("post", url=url),
1524
+ )
1525
+
1526
+ async def cancel_quest(self, group_id: UUID | None = None) -> HabiticaQuestResponse:
1527
+ """Cancel a pending quest for the party or a specific group.
1528
+
1529
+ Cancel a quest that has not yet startet. All accepted and pending invitations
1530
+ will be canceled and the quest roll returned to the owner's inventory.
1531
+ Only quest leader or group leader can perform this action.
1532
+
1533
+ Parameters
1534
+ ----------
1535
+ group_id : UUID, optional
1536
+ The UUID of the group for which the quest is being canceled.
1537
+ Defaults to the user's party if not specified.
1538
+
1539
+ Returns
1540
+ -------
1541
+ HabiticaQuestResponse
1542
+ A response object containing details about the canceled quest.
1543
+
1544
+ Raises
1545
+ ------
1546
+ NotFoundError
1547
+ If the specified group or quest could not be found.
1548
+ NotAuthorizedError
1549
+ If the user does not have permission to cancel the quest.
1550
+ aiohttp.ClientResponseError
1551
+ Raised for HTTP-related errors, such as HTTP 400 or 500 response status.
1552
+ aiohttp.ClientConnectionError
1553
+ If the connection to the API fails.
1554
+ aiohttp.ClientError
1555
+ Raised for any other exceptions encountered by `aiohttp` during the request.
1556
+ TimeoutError
1557
+ If the connection to the API times out.
1558
+
1559
+ Examples
1560
+ --------
1561
+ Cancel a pending quest for the party:
1562
+ >>> response = await habitica.cancel_quest()
1563
+ >>> print(response.success) # True if the quest was successfully canceled.
1564
+ """
1565
+ group = "party" if not group_id else str(group_id)
1566
+ url = self.url / "api/v3/groups" / group / "quests/cancel"
1567
+
1568
+ return HabiticaQuestResponse.from_json(
1569
+ await self._request("post", url=url),
1570
+ )
1571
+
1572
+ async def start_quest(self, group_id: UUID | None = None) -> HabiticaQuestResponse:
1573
+ """Force-start a quest for the party or a specific group.
1574
+
1575
+ Begins a quest immediately, bypassing any pending invitations that haven't been
1576
+ accepted or rejected.
1577
+ Only quest leader or group leader can perform this action.
1578
+
1579
+ Parameters
1580
+ ----------
1581
+ group_id : UUID, optional
1582
+ The UUID of the group for which the quest should be started.
1583
+ Defaults to the user's party if not specified.
1584
+
1585
+
1586
+ Returns
1587
+ -------
1588
+ HabiticaQuestResponse
1589
+ A response object containing updated quest data of the group or party.
1590
+
1591
+ Raises
1592
+ ------
1593
+ NotFoundError
1594
+ If the specified group or quest could not be found.
1595
+ NotAuthorizedError
1596
+ If the user does not have permission to start the quest.
1597
+ aiohttp.ClientResponseError
1598
+ Raised for HTTP-related errors, such as HTTP 400 or 500 response status.
1599
+ aiohttp.ClientConnectionError
1600
+ If the connection to the API fails.
1601
+ aiohttp.ClientError
1602
+ Raised for any other exceptions encountered by `aiohttp` during the request.
1603
+ TimeoutError
1604
+ If the connection to the API times out.
1605
+
1606
+ Examples
1607
+ --------
1608
+ Cancel a pending quest for the party:
1609
+ >>> response = await habitica.cancel_quest()
1610
+ >>> print(response.success) # True if the quest was successfully canceled.
1611
+ """
1612
+ group = "party" if not group_id else str(group_id)
1613
+ url = self.url / "api/v3/groups" / group / "quests/force-start"
1614
+
1615
+ return HabiticaQuestResponse.from_json(
1616
+ await self._request("post", url=url),
1617
+ )
1618
+
1619
+ async def invite_quest(
1620
+ self,
1621
+ group_id: UUID | None = None,
1622
+ *,
1623
+ quest_key: str,
1624
+ ) -> HabiticaQuestResponse:
1625
+ """Invite members of the party or a specific group to participate in a quest.
1626
+
1627
+ Sends invitations for a quest to all eligible members of the specified group.
1628
+ The quest is started when all members accept or reject the invitation.
1629
+
1630
+ Parameters
1631
+ ----------
1632
+ group_id : UUID, optional
1633
+ The UUID of the group for which the quest invitations should be sent.
1634
+ Defaults to the user's party if not specified.
1635
+ quest_key : str
1636
+ The unique key identifying the quest to invite members to.
1637
+
1638
+ Returns
1639
+ -------
1640
+ HabiticaQuestResponse
1641
+ A response object containing updated quest data of the group or party.
1642
+
1643
+ Raises
1644
+ ------
1645
+ NotFoundError
1646
+ If the specified group or quest could not be found.
1647
+ aiohttp.ClientResponseError
1648
+ Raised for HTTP-related errors, such as HTTP 400 or 500 response status.
1649
+ aiohttp.ClientConnectionError
1650
+ If the connection to the API fails.
1651
+ aiohttp.ClientError
1652
+ Raised for any other exceptions encountered by `aiohttp` during the request.
1653
+ TimeoutError
1654
+ If the connection to the API times out.
1655
+
1656
+ Examples
1657
+ --------
1658
+ Send a quest invitation to the party:
1659
+ >>> response = await habitica.invite_quest(quest_key="dilatory_derby")
1660
+ >>> print(response.success) # True if invitations were successfully sent.
1661
+
1662
+ Send a quest invitation to a specific group:
1663
+ >>> group_id = UUID("12345678-1234-5678-1234-567812345678")
1664
+ >>> response = await habitica.invite_quest(group_id, quest_key="golden_knight")
1665
+ >>> print(response.success) # True if invitations were successfully sent.
1666
+ """
1667
+ group = "party" if not group_id else str(group_id)
1668
+ url = self.url / "api/v3/groups" / group / "quests/invite" / quest_key
1669
+
1670
+ return HabiticaQuestResponse.from_json(
1671
+ await self._request("post", url=url),
1672
+ )
1673
+
1674
+ async def leave_quest(self, group_id: UUID | None = None) -> HabiticaQuestResponse:
1675
+ """Leave the current quest from the party or a specific group.
1676
+
1677
+ Allows a user to exit an ongoing quest they are part of. This action removes
1678
+ them from the quest but does not affect its progress for other participants.
1679
+ Users who leave a quest will not contribute to its completion or receive rewards.
1680
+
1681
+ Parameters
1682
+ ----------
1683
+ group_id : UUID, optional
1684
+ The UUID of the group associated with the quest the user is leaving.
1685
+ Defaults to the user's party if not specified.
1686
+ quest_key : str
1687
+ The unique key identifying the quest to invite members to.
1688
+
1689
+ Returns
1690
+ -------
1691
+ HabiticaQuestResponse
1692
+ A response object containing updated quest data of the group or party.
1693
+
1694
+ Raises
1695
+ ------
1696
+ NotFoundError
1697
+ If the specified group or quest could not be found.
1698
+ aiohttp.ClientResponseError
1699
+ Raised for HTTP-related errors, such as HTTP 400 or 500 response status.
1700
+ aiohttp.ClientConnectionError
1701
+ If the connection to the API fails.
1702
+ aiohttp.ClientError
1703
+ Raised for any other exceptions encountered by `aiohttp` during the request.
1704
+ TimeoutError
1705
+ If the connection to the API times out.
1706
+
1707
+ Examples
1708
+ --------
1709
+ Leave the current quest in the user's party:
1710
+ >>> response = await habitica.leave_quest()
1711
+ >>> print(response.success) # True if the user successfully left the quest.
1712
+
1713
+ Leave the current quest in a specific group:
1714
+ >>> group_id = UUID("12345678-1234-5678-1234-567812345678")
1715
+ >>> response = await habitica.leave_quest(group_id)
1716
+ >>> print(response.success) # True if the user successfully left the quest.
1717
+ """
1718
+ group = "party" if not group_id else str(group_id)
1719
+ url = self.url / "api/v3/groups" / group / "quests/leave"
1720
+
1721
+ return HabiticaQuestResponse.from_json(
1722
+ await self._request("post", url=url),
1723
+ )
1724
+
1284
1725
  def _cache_asset(self, asset: str, asset_data: IO[bytes]) -> None:
1285
1726
  """Cache an asset and maintain the cache size limit by removing older entries.
1286
1727
 
@@ -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."""
@@ -1194,7 +1213,7 @@ class Skill(StrEnum):
1194
1213
  VALOROUS_PRESENCE = "valorousPresence"
1195
1214
  INTIMIDATING_GAZE = "intimidate"
1196
1215
  # Rogue skills
1197
- PICKPOCKET = "Pickpocket"
1216
+ PICKPOCKET = "pickPocket"
1198
1217
  BACKSTAB = "backStab"
1199
1218
  TOOLS_OF_THE_TRADE = "toolsOfTrade"
1200
1219
  STEALTH = "stealth"
@@ -1229,3 +1248,387 @@ class TaskPriority(Enum):
1229
1248
  EASY = 1
1230
1249
  MEDIUM = 1.5
1231
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
@@ -0,0 +1,7 @@
1
+ # serializer version: 1
2
+ # name: test_login
3
+ HabiticaLoginResponse(data=LoginData(id=UUID('a380546a-94be-4b8e-8a0b-23e0d5c03303'), apiToken='cd0e5985-17de-4b4f-849e-5d506c5e4382', newUser=False, username='test-username', passwordResetCode=None), success=True, notifications=[], userV=None, appVersion='5.30.0')
4
+ # ---
5
+ # name: test_user
6
+ HabiticaUserResponse(data=UserData(id=UUID('c18e1853-bded-47a9-82e2-adfdad08894d'), preferences=PreferencesUser(hair=HairPreferences(color='red', base=3, bangs=1, beard=0, mustache=0, flower=1), emailNotifications=EmailNotificationsPreferences(unsubscribeFromAll=False, newPM=True, kickedGroup=True, wonChallenge=True, giftedGems=True, giftedSubscription=True, invitedParty=True, invitedGuild=True, questStarted=True, invitedQuest=True, importantAnnouncements=True, weeklyRecaps=True, onboarding=True, majorUpdates=True, subscriptionReminders=True, contentRelease=True), pushNotifications=PushNotificationsPreferences(unsubscribeFromAll=False, newPM=True, wonChallenge=True, giftedGems=True, giftedSubscription=True, invitedParty=True, invitedGuild=True, questStarted=True, invitedQuest=True, majorUpdates=True, mentionParty=True, mentionJoinedGuild=True, mentionUnjoinedGuild=True, partyActivity=True, contentRelease=True), suppressModals=SuppressModalsPreferences(levelUp=False, hatchPet=False, raisePet=False, streak=False), tasks=TasksPreferences(activeFilter=ActiveFilterTask(habit='all', daily='all', todo='remaining', reward='all'), groupByChallenge=False, confirmScoreNotes=False, mirrorGroupTasks=[]), dayStart=0, size='slim', hideHeader=False, skin='915533', shirt='blue', timezoneOffset=-120, sound='rosstavoTheme', chair='none', allocationMode='flat', autoEquip=True, costume=False, dateFormat='MM/dd/yyyy', sleep=False, stickyHeader=True, disableClasses=False, newTaskEdit=False, dailyDueDefaultView=False, advancedCollapsed=False, toolbarCollapsed=False, reverseChatOrder=False, developerMode=False, displayInviteToPartyWhenPartyIs1=True, background='violet', automaticAllocation=None, webhooks={}, improvementCategories=[], timezoneOffsetAtLastCron=None, language=<Language.DE: 'de'>), flags=FlagsUser(customizationsNotification=False, tour=TourFlags(intro=-1, classes=-1, stats=-1, tavern=-1, party=-1, guilds=-1, challenges=-1, market=-1, pets=-1, mounts=-1, hall=-1, equipment=-1, groupPlans=-1), showTour=True, tutorial=TutorialFlags(common=CommonTutorial(habits=True, dailies=True, todos=True, rewards=True, party=True, pets=True, gems=True, skills=True, classes=True, tavern=True, equipment=True, items=True, mounts=True, inbox=True, stats=True), ios=IosTutorial(addTask=False, editTask=False, deleteTask=False, filterTask=False, groupPets=False, inviteParty=False, reorderTask=False)), dropsEnabled=False, itemsEnabled=False, lastNewStuffRead='', rewrite=True, classSelected=False, rebirthEnabled=False, levelDrops={}, recaptureEmailsPhase=0, weeklyRecapEmailsPhase=0, lastWeeklyRecap=datetime.datetime(2024, 10, 19, 18, 43, 39, 782000, tzinfo=datetime.timezone.utc), communityGuidelinesAccepted=False, cronCount=0, welcomed=True, armoireEnabled=True, armoireOpened=False, armoireEmpty=False, cardReceived=False, warnedLowHealth=False, verifiedUsername=True, newStuff=False, thirdPartyTools=None, mathUpdates=None, lastFreeRebirth=None, chatRevoked=None, chatShadowMuted=None, lastWeeklyRecapDiscriminator=None, onboardingEmailsPhase=None), auth=AuthUser(local=LocalAuth(email='test@example.com', username='test', lowerCaseUsername='test', has_password=True), timestamps=LocalTimestamps(created=datetime.datetime(2024, 10, 19, 18, 43, 39, 782000, tzinfo=datetime.timezone.utc), loggedin=datetime.datetime(2024, 10, 19, 18, 43, 39, 782000, tzinfo=datetime.timezone.utc), updated=datetime.datetime(2024, 10, 19, 18, 44, 51, 37000, tzinfo=datetime.timezone.utc)), facebook={}, google={}, apple={}), achievements=AchievementsUser(ultimateGearSets=UltimateGearSetsAchievments(healer=False, wizard=False, rogue=False, warrior=False), streak=0, challenges=[], perfect=0, quests=QuestsAchievments(bewilder=None, burnout=None, stressbeast=None, harpy=None, atom3=None, vice3=None, vice1=None, gryphon=None, evilsanta2=None, evilsanta=None, dilatory_derby=None, dilatory=None, atom2=None, atom1=None, dysheartener=None), backToBasics=None, dustDevil=None, primedForPainting=None, completedTask=None, createdTask=None, fedPet=None, hatchedPet=None, purchasedEquipment=None, tickledPink=None, goodAsGold=None, boneCollector=None, seeingRed=None, violetsAreBlue=None, shadyCustomer=None, joinedGuild=None, joinedChallenge=None, partyUp=None), backer=BackerUser(tier=None, npc=None, tokensApplied=None), contributor=ContributorUser(contributions=None, level=None, text=None), permissions=PermissionsUser(fullAccess=None, news=None, userSupport=None, challengeAdmin=None, moderator=None, coupons=None), purchased=PurchasedUser(plan=PlanPurchased(consecutive=ConsecutivePlan(trinkets=0, gemCapExtra=0, offset=0, count=0), mysteryItems=[], gemsBought=0, extraMonths=0, dateUpdated=None, perkMonthCount=-1, quantity=1), txnCount=0, background={'violet': True}, shirt={}, hair={}, skin={}, ads=False, mobileChat=None), history=HistoryUser(todos=[], exp=[]), items=ItemsUser(gear=GearItems(equipped=EquippedGear(weapon='weapon_special_fall2024Warrior', armor='armor_special_fall2024Warrior', head='head_special_fall2024Warrior', shield='shield_special_fall2024Warrior', back='back_mystery_201402', headAccessory='headAccessory_special_pinkHeadband', eyewear='eyewear_special_pinkHalfMoon', body='body_mystery_202003'), costume=EquippedGear(weapon=None, armor='armor_base_0', head='head_base_0', shield='shield_base_0', back=None, headAccessory=None, eyewear=None, body=None), owned={'headAccessory_special_blackHeadband': True, 'headAccessory_special_blueHeadband': True, 'headAccessory_special_greenHeadband': True, 'headAccessory_special_pinkHeadband': True, 'headAccessory_special_redHeadband': True, 'headAccessory_special_whiteHeadband': True, 'headAccessory_special_yellowHeadband': True, 'eyewear_special_blackTopFrame': True, 'eyewear_special_blueTopFrame': True, 'eyewear_special_greenTopFrame': True, 'eyewear_special_pinkTopFrame': True, 'eyewear_special_redTopFrame': True, 'eyewear_special_whiteTopFrame': True, 'eyewear_special_yellowTopFrame': True, 'eyewear_special_blackHalfMoon': True, 'eyewear_special_blueHalfMoon': True, 'eyewear_special_greenHalfMoon': True, 'eyewear_special_pinkHalfMoon': True, 'eyewear_special_redHalfMoon': True, 'eyewear_special_whiteHalfMoon': True, 'eyewear_special_yellowHalfMoon': True}), special=SpecialItems(birthdayReceived=[], birthday=0, thankyouReceived=[], thankyou=0, greetingReceived=[], greeting=0, nyeReceived=[], nye=0, valentineReceived=[], valentine=0, seafoam=0, shinySeed=0, spookySparkles=0, snowball=0, congrats=0, congratsReceived=[], getwell=0, getwellReceived=[], goodluck=0, goodluckReceived=[]), lastDrop=LastDropItems(count=0, date=datetime.datetime(2024, 10, 19, 18, 43, 39, 784000, tzinfo=datetime.timezone.utc)), currentMount='Velociraptor-Base', currentPet='Rat-Shade', quests={'dustbunnies': 1}, mounts={}, food={}, hatchingPotions={}, eggs={}, pets={}), invitations=InvitationsUser(party={}, guilds=[], parties=[]), party=PartyUser(quest=QuestParty(progress=ProgressQuest(up=0.0, down=0.0, collect={}, collectedItems=0), RSVPNeeded=False, key=None, completed=None), order='level', orderAscending='ascending', _id=None), profile=ProfileUser(blurb=None, imageUrl=None, name='test'), stats=StatsUser(buffs=BuffsStats(Str=0, per=0, con=0, stealth=0, streaks=False, seafoam=False, shinySeed=False, snowball=False, spookySparkles=False, Int=0), training=TrainingStats(Str=0.0, per=0, con=0, Int=0), hp=50.0, mp=10.0, exp=0, gp=0.0, lvl=1, Class=<HabiticaClass.WARRIOR: 'warrior'>, points=0, Str=0, con=0, per=0, toNextLevel=25, maxHealth=50, maxMP=30, Int=0), notifications=[], tags=[TagsUser(id=UUID('5358c71f-fe0f-4583-84a3-109b70c699fd'), name='Arbeit', challenge=None, group=None), TagsUser(id=UUID('45870b8b-3e1e-4e13-a722-6afcacdf689f'), name='Training', challenge=None, group=None), TagsUser(id=UUID('3e49b9db-0cc5-4070-b860-e16f1e4806a4'), name='Gesundheit + Wohlbefinden', challenge=None, group=None), TagsUser(id=UUID('0d85319e-7b9c-43e2-81ed-2a706521d5e3'), name='Schule', challenge=None, group=None), TagsUser(id=UUID('b7cf86bf-97ac-42d3-b5d0-5ffe998b62b4'), name='Teams', challenge=None, group=None), TagsUser(id=UUID('c584d792-0a9b-4646-a6ca-a67bb6ef3c36'), name='Hausarbeiten', challenge=None, group=None), TagsUser(id=UUID('0602fd13-edf2-46a7-99c0-c5084046efbb'), name='Kreativität', challenge=None, group=None)], inbox=InboxUser(newMessages=0, optOut=False, blocks=[], messages={}), tasksOrder=TasksOrderUser(habits=[UUID('21b10675-e238-4462-be11-1a4f8012fc9c'), UUID('69909140-a920-4b83-b018-b9156760aca3'), UUID('7e0679cf-e9cd-4604-b429-d061ea16ce72'), UUID('270df162-d47d-488b-9c1d-4fc4f0e2b2d2'), UUID('80ad00b5-622e-4d1a-af4d-6a6c199f3571'), UUID('0712669d-8374-4211-84ea-715cbcbae9c1'), UUID('56dee98d-038a-43f3-a514-59b1ed9a52ee'), UUID('25df4765-754c-47b1-a011-83b31f70c4fb')], dailys=[UUID('e22e9ef0-15be-478a-81c7-f9fdbb4fac10'), UUID('2a74baa2-3acd-426c-bce4-6b4bb145a8d5'), UUID('e18d0899-7c59-41c0-affd-318b8850c3e5'), UUID('3d0cecac-73c1-462b-ab39-a39cea107e6b'), UUID('1ed21cd0-e6f5-4707-b1b4-fb376d462387'), UUID('9117ad5e-2cda-43e0-a07c-0b175d32c0eb')], todos=[UUID('ac13c62c-9375-4809-a357-0e42b01a1b43'), UUID('2388ccad-0387-4ea6-9968-2d10d994e903'), UUID('5c6efb91-6ae3-4cad-8b19-2dd2fc52a965'), UUID('80afe0b9-e367-4473-8117-b8bc7115f54a'), UUID('4cc21c36-886c-4a43-b580-877e958a37fd'), UUID('10a45996-7133-4ca1-84d8-abf1093d9dcc'), UUID('fb867459-ae50-4cee-9017-c13206c6dfa2')], rewards=[UUID('4b18ccf6-3934-43a5-a153-31af8b2a36d9')]), extra={}, pushDevices=[], webhooks=[], loginIncentives=0, invitesSent=0, pinnedItems=[PinnedItemsUser(path='gear.flat.weapon_warrior_0', Type='marketGear'), PinnedItemsUser(path='gear.flat.armor_warrior_1', Type='marketGear'), PinnedItemsUser(path='gear.flat.shield_warrior_1', Type='marketGear'), PinnedItemsUser(path='gear.flat.head_warrior_1', Type='marketGear'), PinnedItemsUser(path='potion', Type='potion'), PinnedItemsUser(path='armoire', Type='armoire')], pinnedItemsOrder=[], unpinnedItems=[], secret=None, balance=0.0, lastCron=datetime.datetime(2024, 10, 19, 18, 43, 39, 784000, tzinfo=datetime.timezone.utc), needsCron=False, challenges=[], guilds=[], newMessages={}), success=True, notifications=[], userV=5, appVersion='5.28.8')
7
+ # ---
@@ -1,7 +0,0 @@
1
- # serializer version: 1
2
- # name: test_login
3
- HabiticaLoginResponse(data=LoginData(id=UUID('a380546a-94be-4b8e-8a0b-23e0d5c03303'), apiToken='cd0e5985-17de-4b4f-849e-5d506c5e4382', newUser=False, username='test-username', passwordResetCode=None), success=True, notifications=[], userV=None, appVersion='5.30.0')
4
- # ---
5
- # name: test_user
6
- HabiticaUserResponse(data=UserData(id=UUID('c18e1853-bded-47a9-82e2-adfdad08894d'), preferences=PreferencesUser(hair=HairPreferences(color='red', base=3, bangs=1, beard=0, mustache=0, flower=1), emailNotifications=EmailNotificationsPreferences(unsubscribeFromAll=False, newPM=True, kickedGroup=True, wonChallenge=True, giftedGems=True, giftedSubscription=True, invitedParty=True, invitedGuild=True, questStarted=True, invitedQuest=True, importantAnnouncements=True, weeklyRecaps=True, onboarding=True, majorUpdates=True, subscriptionReminders=True, contentRelease=True), pushNotifications=PushNotificationsPreferences(unsubscribeFromAll=False, newPM=True, wonChallenge=True, giftedGems=True, giftedSubscription=True, invitedParty=True, invitedGuild=True, questStarted=True, invitedQuest=True, majorUpdates=True, mentionParty=True, mentionJoinedGuild=True, mentionUnjoinedGuild=True, partyActivity=True, contentRelease=True), suppressModals=SuppressModalsPreferences(levelUp=False, hatchPet=False, raisePet=False, streak=False), tasks=TasksPreferences(activeFilter=ActiveFilterTask(habit='all', daily='all', todo='remaining', reward='all'), groupByChallenge=False, confirmScoreNotes=False, mirrorGroupTasks=[]), dayStart=0, size='slim', hideHeader=False, skin='915533', shirt='blue', timezoneOffset=-120, sound='rosstavoTheme', chair='none', allocationMode='flat', autoEquip=True, costume=False, dateFormat='MM/dd/yyyy', sleep=False, stickyHeader=True, disableClasses=False, newTaskEdit=False, dailyDueDefaultView=False, advancedCollapsed=False, toolbarCollapsed=False, reverseChatOrder=False, developerMode=False, displayInviteToPartyWhenPartyIs1=True, background='violet', automaticAllocation=None, webhooks={}, improvementCategories=[], timezoneOffsetAtLastCron=None, language='de'), flags=FlagsUser(customizationsNotification=False, tour=TourFlags(intro=-1, classes=-1, stats=-1, tavern=-1, party=-1, guilds=-1, challenges=-1, market=-1, pets=-1, mounts=-1, hall=-1, equipment=-1, groupPlans=-1), showTour=True, tutorial=TutorialFlags(common=CommonTutorial(habits=True, dailies=True, todos=True, rewards=True, party=True, pets=True, gems=True, skills=True, classes=True, tavern=True, equipment=True, items=True, mounts=True, inbox=True, stats=True), ios=IosTutorial(addTask=False, editTask=False, deleteTask=False, filterTask=False, groupPets=False, inviteParty=False, reorderTask=False)), dropsEnabled=False, itemsEnabled=False, lastNewStuffRead='', rewrite=True, classSelected=False, rebirthEnabled=False, levelDrops={}, recaptureEmailsPhase=0, weeklyRecapEmailsPhase=0, lastWeeklyRecap=datetime.datetime(2024, 10, 19, 18, 43, 39, 782000, tzinfo=datetime.timezone.utc), communityGuidelinesAccepted=False, cronCount=0, welcomed=True, armoireEnabled=True, armoireOpened=False, armoireEmpty=False, cardReceived=False, warnedLowHealth=False, verifiedUsername=True, newStuff=False, thirdPartyTools=None, mathUpdates=None, lastFreeRebirth=None, chatRevoked=None, chatShadowMuted=None, lastWeeklyRecapDiscriminator=None, onboardingEmailsPhase=None), auth=AuthUser(local=LocalAuth(email='test@example.com', username='test', lowerCaseUsername='test', has_password=True), timestamps=LocalTimestamps(created=datetime.datetime(2024, 10, 19, 18, 43, 39, 782000, tzinfo=datetime.timezone.utc), loggedin=datetime.datetime(2024, 10, 19, 18, 43, 39, 782000, tzinfo=datetime.timezone.utc), updated=datetime.datetime(2024, 10, 19, 18, 44, 51, 37000, tzinfo=datetime.timezone.utc)), facebook={}, google={}, apple={}), achievements=AchievementsUser(ultimateGearSets=UltimateGearSetsAchievments(healer=False, wizard=False, rogue=False, warrior=False), streak=0, challenges=[], perfect=0, quests=QuestsAchievments(bewilder=None, burnout=None, stressbeast=None, harpy=None, atom3=None, vice3=None, vice1=None, gryphon=None, evilsanta2=None, evilsanta=None, dilatory_derby=None, dilatory=None, atom2=None, atom1=None, dysheartener=None), backToBasics=None, dustDevil=None, primedForPainting=None, completedTask=None, createdTask=None, fedPet=None, hatchedPet=None, purchasedEquipment=None, tickledPink=None, goodAsGold=None, boneCollector=None, seeingRed=None, violetsAreBlue=None, shadyCustomer=None, joinedGuild=None, joinedChallenge=None, partyUp=None), backer=BackerUser(tier=None, npc=None, tokensApplied=None), contributor=ContributorUser(contributions=None, level=None, text=None), permissions=PermissionsUser(fullAccess=None, news=None, userSupport=None, challengeAdmin=None, moderator=None, coupons=None), purchased=PurchasedUser(plan=PlanPurchased(consecutive=ConsecutivePlan(trinkets=0, gemCapExtra=0, offset=0, count=0), mysteryItems=[], gemsBought=0, extraMonths=0, dateUpdated=None, perkMonthCount=-1, quantity=1), txnCount=0, background={'violet': True}, shirt={}, hair={}, skin={}, ads=False, mobileChat=None), history=HistoryUser(todos=[], exp=[]), items=ItemsUser(gear=GearItems(equipped=EquippedGear(weapon='weapon_special_fall2024Warrior', armor='armor_special_fall2024Warrior', head='head_special_fall2024Warrior', shield='shield_special_fall2024Warrior', back='back_mystery_201402', headAccessory='headAccessory_special_pinkHeadband', eyewear='eyewear_special_pinkHalfMoon', body='body_mystery_202003'), costume=EquippedGear(weapon=None, armor='armor_base_0', head='head_base_0', shield='shield_base_0', back=None, headAccessory=None, eyewear=None, body=None), owned={'headAccessory_special_blackHeadband': True, 'headAccessory_special_blueHeadband': True, 'headAccessory_special_greenHeadband': True, 'headAccessory_special_pinkHeadband': True, 'headAccessory_special_redHeadband': True, 'headAccessory_special_whiteHeadband': True, 'headAccessory_special_yellowHeadband': True, 'eyewear_special_blackTopFrame': True, 'eyewear_special_blueTopFrame': True, 'eyewear_special_greenTopFrame': True, 'eyewear_special_pinkTopFrame': True, 'eyewear_special_redTopFrame': True, 'eyewear_special_whiteTopFrame': True, 'eyewear_special_yellowTopFrame': True, 'eyewear_special_blackHalfMoon': True, 'eyewear_special_blueHalfMoon': True, 'eyewear_special_greenHalfMoon': True, 'eyewear_special_pinkHalfMoon': True, 'eyewear_special_redHalfMoon': True, 'eyewear_special_whiteHalfMoon': True, 'eyewear_special_yellowHalfMoon': True}), special=SpecialItems(birthdayReceived=[], birthday=0, thankyouReceived=[], thankyou=0, greetingReceived=[], greeting=0, nyeReceived=[], nye=0, valentineReceived=[], valentine=0, seafoam=0, shinySeed=0, spookySparkles=0, snowball=0, congrats=0, congratsReceived=[], getwell=0, getwellReceived=[], goodluck=0, goodluckReceived=[]), lastDrop=LastDropItems(count=0, date=datetime.datetime(2024, 10, 19, 18, 43, 39, 784000, tzinfo=datetime.timezone.utc)), currentMount='Velociraptor-Base', currentPet='Rat-Shade', quests={'dustbunnies': 1}, mounts={}, food={}, hatchingPotions={}, eggs={}, pets={}), invitations=InvitationsUser(party={}, guilds=[], parties=[]), party=PartyUser(quest=QuestParty(progress=ProgressQuest(up=0.0, down=0.0, collect={}, collectedItems=0), RSVPNeeded=False, key=None, completed=None), order='level', orderAscending='ascending', _id=None), profile=ProfileUser(blurb=None, imageUrl=None, name='test'), stats=StatsUser(buffs=BuffsStats(Str=0, per=0, con=0, stealth=0, streaks=False, seafoam=False, shinySeed=False, snowball=False, spookySparkles=False, Int=0), training=TrainingStats(Str=0.0, per=0, con=0, Int=0), hp=50.0, mp=10.0, exp=0, gp=0.0, lvl=1, Class=<HabiticaClass.WARRIOR: 'warrior'>, points=0, Str=0, con=0, per=0, toNextLevel=25, maxHealth=50, maxMP=30, Int=0), notifications=[], tags=[TagsUser(id=UUID('5358c71f-fe0f-4583-84a3-109b70c699fd'), name='Arbeit', challenge=None, group=None), TagsUser(id=UUID('45870b8b-3e1e-4e13-a722-6afcacdf689f'), name='Training', challenge=None, group=None), TagsUser(id=UUID('3e49b9db-0cc5-4070-b860-e16f1e4806a4'), name='Gesundheit + Wohlbefinden', challenge=None, group=None), TagsUser(id=UUID('0d85319e-7b9c-43e2-81ed-2a706521d5e3'), name='Schule', challenge=None, group=None), TagsUser(id=UUID('b7cf86bf-97ac-42d3-b5d0-5ffe998b62b4'), name='Teams', challenge=None, group=None), TagsUser(id=UUID('c584d792-0a9b-4646-a6ca-a67bb6ef3c36'), name='Hausarbeiten', challenge=None, group=None), TagsUser(id=UUID('0602fd13-edf2-46a7-99c0-c5084046efbb'), name='Kreativität', challenge=None, group=None)], inbox=InboxUser(newMessages=0, optOut=False, blocks=[], messages={}), tasksOrder=TasksOrderUser(habits=[UUID('21b10675-e238-4462-be11-1a4f8012fc9c'), UUID('69909140-a920-4b83-b018-b9156760aca3'), UUID('7e0679cf-e9cd-4604-b429-d061ea16ce72'), UUID('270df162-d47d-488b-9c1d-4fc4f0e2b2d2'), UUID('80ad00b5-622e-4d1a-af4d-6a6c199f3571'), UUID('0712669d-8374-4211-84ea-715cbcbae9c1'), UUID('56dee98d-038a-43f3-a514-59b1ed9a52ee'), UUID('25df4765-754c-47b1-a011-83b31f70c4fb')], dailys=[UUID('e22e9ef0-15be-478a-81c7-f9fdbb4fac10'), UUID('2a74baa2-3acd-426c-bce4-6b4bb145a8d5'), UUID('e18d0899-7c59-41c0-affd-318b8850c3e5'), UUID('3d0cecac-73c1-462b-ab39-a39cea107e6b'), UUID('1ed21cd0-e6f5-4707-b1b4-fb376d462387'), UUID('9117ad5e-2cda-43e0-a07c-0b175d32c0eb')], todos=[UUID('ac13c62c-9375-4809-a357-0e42b01a1b43'), UUID('2388ccad-0387-4ea6-9968-2d10d994e903'), UUID('5c6efb91-6ae3-4cad-8b19-2dd2fc52a965'), UUID('80afe0b9-e367-4473-8117-b8bc7115f54a'), UUID('4cc21c36-886c-4a43-b580-877e958a37fd'), UUID('10a45996-7133-4ca1-84d8-abf1093d9dcc'), UUID('fb867459-ae50-4cee-9017-c13206c6dfa2')], rewards=[UUID('4b18ccf6-3934-43a5-a153-31af8b2a36d9')]), extra={}, pushDevices=[], webhooks=[], loginIncentives=0, invitesSent=0, pinnedItems=[PinnedItemsUser(path='gear.flat.weapon_warrior_0', Type='marketGear'), PinnedItemsUser(path='gear.flat.armor_warrior_1', Type='marketGear'), PinnedItemsUser(path='gear.flat.shield_warrior_1', Type='marketGear'), PinnedItemsUser(path='gear.flat.head_warrior_1', Type='marketGear'), PinnedItemsUser(path='potion', Type='potion'), PinnedItemsUser(path='armoire', Type='armoire')], pinnedItemsOrder=[], unpinnedItems=[], secret=None, balance=0.0, lastCron=datetime.datetime(2024, 10, 19, 18, 43, 39, 784000, tzinfo=datetime.timezone.utc), needsCron=False, challenges=[], guilds=[], newMessages={}), success=True, notifications=[], userV=5, appVersion='5.28.8')
7
- # ---
File without changes
File without changes
File without changes
File without changes
File without changes