aiobungie.framework
Implementation of the library's deserialization framework.
1# MIT License 2# Copyright (c) 2020 - Present nxtlo 3# 4# Permission is hereby granted, free of charge, to any person obtaining a copy 5# of this software and associated documentation files (the "Software"), to deal 6# in the Software without restriction, including without limitation the rights 7# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8# copies of the Software, and to permit persons to whom the Software is 9# furnished to do so, subject to the following conditions: 10# 11# The above copyright notice and this permission notice shall be included in all 12# copies or substantial portions of the Software. 13# 14# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20# SOFTWARE. 21 22"""Implementation of the library's deserialization framework. 23 24* `Framework` Is the default deserialization framework that's used within client impl's. 25* `Global` Is a pre-initialized instance of `Framework` that can be used at any scope. 26""" 27 28from __future__ import annotations 29 30__all__ = ("Framework", "Global") 31 32import typing 33 34import sain 35 36from aiobungie import api, builders, typedefs 37from aiobungie.crates import ( 38 activity, 39 application, 40 character, 41 clans, 42 components, 43 entity, 44 fireteams, 45 friends, 46 items, 47 milestones, 48 profile, 49 progressions, 50 records, 51 season, 52 user, 53) 54from aiobungie.internal import enums, time 55 56if typing.TYPE_CHECKING: 57 import collections.abc as collections 58 import datetime 59 60 # from aiobungie import traits 61 62 63class Framework(api.Framework): 64 """The base deserialization framework implementation. 65 66 This framework is used to deserialize JSON responses from the REST client and turning them into a `aiobungie.crates` Python classes. 67 68 Example 69 -------- 70 ```py 71 import aiobungie 72 73 from aiobungie import traits 74 from aiobungie import framework 75 76 class MyClient(traits.Deserialize): 77 def __init__(self) -> None: 78 self.rest = aiobungie.RESTClient("token") 79 self.my_name = "Fate怒" 80 self.my_code = 4275 81 82 @property # required method. 83 def framework(self) -> framework.Empty: 84 return framework.Global 85 86 async def my_memberships(self): 87 response = await self.rest.fetch_membership(self.my_name, self.my_code) 88 return self.factory.deserialize_destiny_memberships(response) 89 90 async def main() -> None: 91 client = MyClient() 92 async with client.rest: 93 print(await client.my_memberships()) 94 95 asyncio.run(main()) 96 ``` 97 """ 98 99 __slots__ = () 100 101 def __init__(self) -> None: 102 super().__init__() 103 104 def deserialize_bungie_user(self, data: typedefs.JSONObject) -> user.BungieUser: 105 return user.BungieUser( 106 id=int(data["membershipId"]), 107 created_at=time.clean_date(data["firstAccess"]), 108 name=data.get("cachedBungieGlobalDisplayName"), 109 is_deleted=data["isDeleted"], 110 about=data["about"], 111 updated_at=time.clean_date(data["lastUpdate"]), 112 psn_name=data.get("psnDisplayName", None), 113 stadia_name=data.get("stadiaDisplayName", None), 114 steam_name=data.get("steamDisplayName", None), 115 twitch_name=data.get("twitchDisplayName", None), 116 blizzard_name=data.get("blizzardDisplayName", None), 117 egs_name=data.get("egsDisplayName", None), 118 status=data["statusText"], 119 locale=data["locale"], 120 picture=builders.Image(path=data["profilePicturePath"]), 121 code=data.get("cachedBungieGlobalDisplayNameCode", None), 122 unique_name=data.get("uniqueName", None), 123 theme_id=int(data["profileTheme"]), 124 show_activity=bool(data["showActivity"]), 125 theme_name=data["profileThemeName"], 126 display_title=data["userTitleDisplay"], 127 profile_ban_expire=time.clean_date(data["profileBanExpire"]) 128 if "profileBanExpire" in data 129 else None, 130 ) 131 132 def deserialize_partial_bungie_user( 133 self, payload: typedefs.JSONObject 134 ) -> user.PartialBungieUser: 135 return user.PartialBungieUser( 136 types=tuple( 137 enums.MembershipType(type_) 138 for type_ in payload.get("applicableMembershipTypes", ()) 139 ), 140 name=payload["bungieGlobalDisplayName"] 141 if "bungieGlobalDisplayName" in payload 142 else payload.get("displayName"), 143 id=int(payload["membershipId"]), 144 crossave_override=enums.MembershipType(payload["crossSaveOverride"]), 145 is_public=payload["isPublic"], 146 icon=builders.Image(path=payload.get("iconPath", "")), 147 type=enums.MembershipType(payload["membershipType"]), 148 code=payload.get("bungieGlobalDisplayNameCode"), 149 ) 150 151 def deserialize_destiny_membership( 152 self, payload: typedefs.JSONObject 153 ) -> user.DestinyMembership: 154 name: str | None = None 155 if (raw_name := payload.get("bungieGlobalDisplayName")) is not None: 156 name = typedefs.unknown(raw_name) 157 158 return user.DestinyMembership( 159 id=int(payload["membershipId"]), 160 name=name, 161 code=payload.get("bungieGlobalDisplayNameCode", None), 162 last_seen_name=payload.get("LastSeenDisplayName") 163 or payload.get("displayName") # noqa: W503 164 or "", # noqa: W503 165 type=enums.MembershipType(payload["membershipType"]), 166 is_public=payload["isPublic"], 167 crossave_override=enums.MembershipType(payload["crossSaveOverride"]), 168 icon=builders.Image(path=payload.get("iconPath", "")), 169 types=tuple( 170 enums.MembershipType(type_) 171 for type_ in payload.get("applicableMembershipTypes", ()) 172 ), 173 ) 174 175 def deserialize_destiny_memberships( 176 self, data: typedefs.JSONArray 177 ) -> collections.Sequence[user.DestinyMembership]: 178 return tuple( 179 self.deserialize_destiny_membership(membership) for membership in data 180 ) 181 182 def deserialize_user(self, data: typedefs.JSONObject) -> user.User: 183 primary_membership_id: int | None = None 184 if raw_primary_id := data.get("primaryMembershipId"): 185 primary_membership_id = int(raw_primary_id) 186 187 return user.User( 188 bungie_user=self.deserialize_bungie_user(data["bungieNetUser"]), 189 memberships=self.deserialize_destiny_memberships( 190 data["destinyMemberships"] 191 ), 192 primary_membership_id=primary_membership_id, 193 ) 194 195 def deserialize_sanitized_membership( 196 self, payload: typedefs.JSONObject 197 ) -> user.SanitizedMembership: 198 return user.SanitizedMembership( 199 epic_games=payload.get("EgsId"), 200 psn=payload.get("PsnId"), 201 steam=payload.get("SteamId"), 202 xbox=payload.get("XboxId"), 203 twitch=payload.get("TwitchId"), 204 ) 205 206 def deserialize_searched_user( 207 self, payload: typedefs.JSONObject 208 ) -> user.SearchableDestinyUser: 209 code: int | None = None 210 if raw_code := payload.get("bungieGlobalDisplayNameCode"): 211 code = int(raw_code) 212 213 bungie_id: int | None = None 214 if raw_bungie_id := payload.get("bungieNetMembershipId"): 215 bungie_id = int(raw_bungie_id) 216 217 return user.SearchableDestinyUser( 218 name=typedefs.unknown(payload["bungieGlobalDisplayName"]), 219 code=code, 220 bungie_id=bungie_id, 221 memberships=self.deserialize_destiny_memberships( 222 payload["destinyMemberships"] 223 ), 224 ) 225 226 def deserialize_user_credentials( 227 self, payload: typedefs.JSONArray 228 ) -> collections.Sequence[user.UserCredentials]: 229 return tuple( 230 user.UserCredentials( 231 type=enums.CredentialType(int(credit["credentialType"])), 232 display_name=credit["credentialDisplayName"], 233 is_public=credit["isPublic"], 234 self_as_string=credit.get("credentialAsString"), 235 ) 236 for credit in payload 237 ) 238 239 def deserialize_user_themes( 240 self, payload: typedefs.JSONArray 241 ) -> collections.Sequence[user.UserThemes]: 242 return tuple( 243 user.UserThemes( 244 id=int(entry["userThemeId"]), 245 name=entry["userThemeName"] if "userThemeName" in entry else None, 246 description=entry["userThemeDescription"] 247 if "userThemeDescription" in entry 248 else None, 249 ) 250 for entry in payload 251 ) 252 253 def deserialize_group(self, payload: typedefs.JSONObject) -> clans.Group: 254 clan_info_obj: typedefs.JSONObject | None = None 255 if raw_clan_info := payload.get("clanInfo"): 256 clan_info_obj = clans.ClanInfo( 257 call_sign=raw_clan_info["clanCallsign"], 258 banner_data=raw_clan_info["clanBannerData"], 259 ) 260 return clans.Group( 261 group_id=int(payload["groupId"]), 262 name=payload["name"], 263 creation_date=time.clean_date(payload["creationDate"]), 264 about=payload["about"], 265 clan_info=clan_info_obj, 266 motto=payload.get("motto"), 267 member_count=int(payload["memberCount"]), 268 locale=payload["locale"], 269 membership_option=int(payload["membershipOption"]), 270 capabilities=int(payload["capabilities"]), 271 remote_group_id=int(payload["remoteGroupId"]) 272 if "remoteGroupId" in payload 273 else None, 274 group_type=enums.GroupType(int(payload["groupType"])), 275 avatar_path=builders.Image(payload["avatarPath"]), 276 theme=payload["theme"], 277 ) 278 279 def _deserialize_group_details( 280 self, 281 data: typedefs.JSONObject, 282 current_user_memberships: collections.Mapping[str, clans.ClanMember] 283 | None = None, 284 clan_founder: clans.ClanMember | None = None, 285 ) -> clans.Clan: 286 features = data["features"] 287 features_obj = clans.ClanFeatures( 288 max_members=features["maximumMembers"], 289 max_membership_types=features["maximumMembershipsOfGroupType"], 290 capabilities=features["capabilities"], 291 membership_types=features["membershipTypes"], 292 invite_permissions=features["invitePermissionOverride"], 293 update_banner_permissions=features["updateBannerPermissionOverride"], 294 update_culture_permissions=features["updateCulturePermissionOverride"], 295 join_level=features["joinLevel"], 296 ) 297 information: typedefs.JSONObject = data["clanInfo"] 298 progression: collections.Mapping[int, progressions.Progression] = { 299 int(prog_hash): self.deserialize_progressions(prog) 300 for prog_hash, prog in information["d2ClanProgressions"].items() 301 } 302 303 return clans.Clan( 304 id=int(data["groupId"]), 305 name=data["name"], 306 type=enums.GroupType(data["groupType"]), 307 created_at=time.clean_date(data["creationDate"]), 308 member_count=data["memberCount"], 309 motto=data["motto"], 310 about=data["about"], 311 is_public=data["isPublic"], 312 banner=builders.Image(path=data["bannerPath"]), 313 avatar=builders.Image(path=data["avatarPath"]), 314 tags=tuple(data["tags"]), 315 features=features_obj, 316 owner=clan_founder, 317 progressions=progression, 318 call_sign=information["clanCallsign"], 319 banner_data=information["clanBannerData"], 320 chat_security=data["chatSecurity"], 321 conversation_id=int(data["conversationId"]), 322 allow_chat=data["allowChat"], 323 theme=data["theme"], 324 current_user_membership=current_user_memberships, 325 ) 326 327 def deserialize_clan(self, payload: typedefs.JSONObject) -> clans.Clan: 328 current_user_map: collections.Mapping[str, clans.ClanMember] | None = None 329 if raw_current_user := payload.get("currentUserMemberMap"): 330 # This will get populated if only it was a GroupsV2.GroupResponse. 331 # GroupsV2.GetGroupsForMemberResponse doesn't have this field. 332 current_user_map = { 333 membership_type: self.deserialize_clan_member(membership) 334 for membership_type, membership in raw_current_user.items() 335 } 336 337 return self._deserialize_group_details( 338 data=payload["detail"], 339 clan_founder=self.deserialize_clan_member(payload["founder"]), 340 current_user_memberships=current_user_map, 341 ) 342 343 def deserialize_clan_member(self, data: typedefs.JSONObject, /) -> clans.ClanMember: 344 destiny_user = self.deserialize_destiny_membership(data["destinyUserInfo"]) 345 return clans.ClanMember( 346 last_seen_name=destiny_user.last_seen_name, 347 id=destiny_user.id, 348 name=destiny_user.name, 349 icon=destiny_user.icon, 350 last_online=time.from_timestamp(int(data["lastOnlineStatusChange"])), 351 group_id=int(data["groupId"]), 352 joined_at=time.clean_date(data["joinDate"]), 353 types=destiny_user.types, 354 is_public=destiny_user.is_public, 355 type=destiny_user.type, 356 code=destiny_user.code, 357 is_online=data["isOnline"], 358 crossave_override=destiny_user.crossave_override, 359 bungie_user=self.deserialize_partial_bungie_user(data["bungieNetUserInfo"]) 360 if "bungieNetUserInfo" in data 361 else None, 362 member_type=enums.ClanMemberType(int(data["memberType"])), 363 ) 364 365 def deserialize_clan_members( 366 self, data: typedefs.JSONObject, / 367 ) -> sain.Iterator[clans.ClanMember]: 368 return sain.Iter( 369 self.deserialize_clan_member(member) for member in data["results"] 370 ) 371 372 def deserialize_group_member( 373 self, payload: typedefs.JSONObject 374 ) -> clans.GroupMember: 375 member = payload["member"] 376 return clans.GroupMember( 377 join_date=time.clean_date(member["joinDate"]), 378 group_id=int(member["groupId"]), 379 member_type=enums.ClanMemberType(member["memberType"]), 380 is_online=member["isOnline"], 381 last_online=time.from_timestamp(int(member["lastOnlineStatusChange"])), 382 inactive_memberships=payload.get("areAllMembershipsInactive", None), 383 member=self.deserialize_destiny_membership(member["destinyUserInfo"]), 384 group=self._deserialize_group_details(payload["group"]), 385 ) 386 387 def _deserialize_clan_conversation( 388 self, payload: typedefs.JSONObject 389 ) -> clans.ClanConversation: 390 return clans.ClanConversation( 391 id=int(payload["conversationId"]), 392 group_id=int(payload["groupId"]), 393 name=typedefs.unknown(payload["chatName"]), 394 chat_enabled=payload["chatEnabled"], 395 security=payload["chatSecurity"], 396 ) 397 398 def deserialize_clan_conversations( 399 self, payload: typedefs.JSONArray 400 ) -> collections.Sequence[clans.ClanConversation]: 401 return tuple(self._deserialize_clan_conversation(conv) for conv in payload) 402 403 def deserialize_application_member( 404 self, payload: typedefs.JSONObject 405 ) -> application.ApplicationMember: 406 return application.ApplicationMember( 407 api_eula_version=payload["apiEulaVersion"], 408 role=payload["role"], 409 user=self.deserialize_partial_bungie_user(payload["user"]), 410 ) 411 412 def deserialize_application( 413 self, payload: typedefs.JSONObject 414 ) -> application.Application: 415 return application.Application( 416 id=int(payload["applicationId"]), 417 name=payload["name"], 418 origin=payload["origin"], 419 link=payload["link"], 420 status=payload["status"], 421 redirect_url=payload.get("redirectUrl", None), 422 created_at=time.clean_date(payload["creationDate"]), 423 status_changed=time.clean_date(payload["statusChanged"]), 424 published_at=time.clean_date(payload["firstPublished"]), 425 team=tuple( 426 self.deserialize_application_member(member) 427 for member in payload["team"] 428 ), 429 scope=payload.get("scope"), 430 ) 431 432 def _set_character_attrs(self, payload: typedefs.JSONObject) -> character.Character: 433 if raw_emblem_color := payload.get("emblemColor"): 434 emblem_color: tuple[int, int, int, int] = ( 435 int(raw_emblem_color["red"]), 436 int(raw_emblem_color["green"]), 437 int(raw_emblem_color["blue"]), 438 int(raw_emblem_color["alpha"]), 439 ) 440 else: 441 emblem_color = (0, 0, 0, 0) 442 443 return character.Character( 444 id=int(payload["characterId"]), 445 gender=enums.Gender(payload["genderType"]), 446 race=enums.Race(payload["raceType"]), 447 class_type=enums.Class(payload["classType"]), 448 emblem=builders.Image(path=payload["emblemBackgroundPath"]) 449 if "emblemBackgroundPath" in payload 450 else None, 451 emblem_icon=builders.Image(path=payload["emblemPath"]) 452 if "emblemPath" in payload 453 else None, 454 emblem_hash=int(payload["emblemHash"]) if "emblemHash" in payload else None, 455 last_played=time.clean_date(payload["dateLastPlayed"]), 456 total_played_time=int(payload["minutesPlayedTotal"]), 457 member_id=int(payload["membershipId"]), 458 member_type=enums.MembershipType(payload["membershipType"]), 459 level=payload["baseCharacterLevel"], 460 title_hash=payload.get("titleRecordHash", None), 461 light=payload["light"], 462 stats={enums.Stat(int(k)): v for k, v in payload["stats"].items()}, 463 emblem_color=emblem_color, 464 minutes_played_this_session=int(payload["minutesPlayedThisSession"]) 465 if "minutesPlayedThisSession" in payload 466 else 0, 467 percent_to_next_level=float(payload["percentToNextLevel"]), 468 ) 469 470 def deserialize_profile(self, payload: typedefs.JSONObject, /) -> profile.Profile: 471 return profile.Profile( 472 user=self.deserialize_destiny_membership(payload["userInfo"]), 473 last_played=time.clean_date(payload["dateLastPlayed"]), 474 versions_owned=enums.GameVersions(int(payload["versionsOwned"])), 475 character_ids=tuple( 476 int(character_id) for character_id in payload["characterIds"] 477 ), 478 season_hashes=tuple(payload["seasonHashes"]), 479 event_card_hashes=tuple(payload["eventCardHashesOwned"]), 480 season_hash=payload["currentSeasonHash"], 481 power_cap=payload["currentSeasonRewardPowerCap"], 482 guardian_rank=payload["currentGuardianRank"], 483 highest_guardian_rank=payload["lifetimeHighestGuardianRank"], 484 renewed_guardian_rank=payload["renewedGuardianRank"], 485 ) 486 487 def deserialize_profile_item( 488 self, payload: typedefs.JSONObject 489 ) -> profile.ProfileItemImpl: 490 instance_id: int | None = None 491 if raw_instance_id := payload.get("itemInstanceId"): 492 instance_id = int(raw_instance_id) 493 494 version_number: int | None = None 495 if raw_version := payload.get("versionNumber"): 496 version_number = int(raw_version) 497 498 transfer_status = enums.TransferStatus(payload["transferStatus"]) 499 500 return profile.ProfileItemImpl( 501 hash=payload["itemHash"], 502 quantity=payload["quantity"], 503 bind_status=enums.ItemBindStatus(payload["bindStatus"]), 504 location=enums.ItemLocation(payload["location"]), 505 bucket=payload["bucketHash"], 506 transfer_status=transfer_status, 507 lockable=payload["lockable"], 508 state=enums.ItemState(payload["state"]), 509 dismantle_permissions=payload["dismantlePermission"], 510 is_wrapper=payload["isWrapper"], 511 instance_id=instance_id, 512 version_number=version_number, 513 ornament_id=payload.get("overrideStyleItemHash"), 514 ) 515 516 def deserialize_objectives(self, payload: typedefs.JSONObject) -> records.Objective: 517 return records.Objective( 518 hash=payload["objectiveHash"], 519 visible=payload["visible"], 520 complete=payload["complete"], 521 completion_value=payload["completionValue"], 522 progress=payload.get("progress"), 523 destination_hash=payload.get("destinationHash"), 524 activity_hash=payload.get("activityHash"), 525 ) 526 527 # TODO: Remove **nodes and get it directly from the payload. 528 def deserialize_records( 529 self, 530 payload: typedefs.JSONObject, 531 scores: records.RecordScores | None = None, 532 **nodes: int, 533 ) -> records.Record: 534 objectives: collections.Sequence[records.Objective] | None = None 535 interval_objectives: collections.Sequence[records.Objective] | None = None 536 record_state: records.RecordState | int 537 538 record_state = records.RecordState(payload["state"]) 539 540 if raw_objs := payload.get("objectives"): 541 objectives = tuple(self.deserialize_objectives(obj) for obj in raw_objs) 542 543 if raw_interval_objs := payload.get("intervalObjectives"): 544 interval_objectives = tuple( 545 self.deserialize_objectives(obj) for obj in raw_interval_objs 546 ) 547 548 return records.Record( 549 scores=scores, 550 categories_node_hash=nodes.get("categories_hash"), 551 seals_node_hash=nodes.get("seals_hash"), 552 state=record_state, 553 objectives=objectives, 554 interval_objectives=interval_objectives, 555 redeemed_count=payload.get("intervalsRedeemedCount", 0), 556 completion_times=payload.get("completedCount", None), 557 reward_visibility=payload.get("rewardVisibility"), 558 ) 559 560 def deserialize_character_records( 561 self, 562 payload: typedefs.JSONObject, 563 scores: records.RecordScores | None = None, 564 record_hashes: collections.Sequence[int] = (), 565 ) -> records.CharacterRecord: 566 record = self.deserialize_records(payload, scores) 567 return records.CharacterRecord( 568 scores=scores, 569 categories_node_hash=record.categories_node_hash, 570 seals_node_hash=record.seals_node_hash, 571 state=record.state, 572 objectives=record.objectives, 573 interval_objectives=record.interval_objectives, 574 redeemed_count=payload.get("intervalsRedeemedCount", 0), 575 completion_times=payload.get("completedCount"), 576 reward_visibility=payload.get("rewardVisibility"), 577 record_hashes=record_hashes, 578 ) 579 580 def deserialize_character_dye(self, payload: typedefs.JSONObject) -> character.Dye: 581 return character.Dye( 582 channel_hash=payload["channelHash"], dye_hash=payload["dyeHash"] 583 ) 584 585 def deserialize_character_customization( 586 self, payload: typedefs.JSONObject 587 ) -> character.CustomizationOptions: 588 return character.CustomizationOptions( 589 personality=payload["personality"], 590 face=payload["face"], 591 skin_color=payload["skinColor"], 592 lip_color=payload["lipColor"], 593 eye_color=payload["eyeColor"], 594 hair_colors=payload.get("hairColors", ()), 595 feature_colors=payload.get("featureColors", ()), 596 decal_color=payload["decalColor"], 597 wear_helmet=payload["wearHelmet"], 598 hair_index=payload["hairIndex"], 599 feature_index=payload["featureIndex"], 600 decal_index=payload["decalIndex"], 601 ) 602 603 def deserialize_character_minimal_equipments( 604 self, payload: typedefs.JSONObject 605 ) -> character.MinimalEquipments: 606 if raw_dyes := payload.get("dyes"): 607 dyes = tuple(self.deserialize_character_dye(dye) for dye in raw_dyes) 608 else: 609 dyes = () 610 611 return character.MinimalEquipments(item_hash=payload["itemHash"], dyes=dyes) 612 613 def deserialize_character_render_data( 614 self, payload: typedefs.JSONObject, / 615 ) -> character.RenderedData: 616 return character.RenderedData( 617 customization=self.deserialize_character_customization( 618 payload["customization"] 619 ), 620 custom_dyes=tuple( 621 self.deserialize_character_dye(dye) 622 for dye in payload["customDyes"] 623 if dye 624 ), 625 equipment=tuple( 626 self.deserialize_character_minimal_equipments(equipment) 627 for equipment in payload["peerView"]["equipment"] 628 ), 629 ) 630 631 def deserialize_available_activity( 632 self, payload: typedefs.JSONObject 633 ) -> activity.AvailableActivity: 634 return activity.AvailableActivity( 635 hash=payload["activityHash"], 636 is_new=payload["isNew"], 637 is_completed=payload["isCompleted"], 638 is_visible=payload["isVisible"], 639 display_level=payload.get("displayLevel"), 640 recommended_light=payload.get("recommendedLight"), 641 difficulty=activity.Difficulty(payload["difficultyTier"]), 642 can_join=payload["canJoin"], 643 can_lead=payload["canLead"], 644 ) 645 646 def deserialize_character_activity( 647 self, payload: typedefs.JSONObject 648 ) -> activity.CharacterActivity: 649 current_mode = enums.GameMode.NONE 650 if raw_current_mode := payload.get("currentActivityModeType"): 651 current_mode = enums.GameMode(raw_current_mode) 652 653 if raw_current_modes := payload.get("currentActivityModeTypes"): 654 current_mode_types = tuple( 655 enums.GameMode(type_) for type_ in raw_current_modes 656 ) 657 else: 658 current_mode_types = () 659 660 return activity.CharacterActivity( 661 date_started=time.clean_date(payload["dateActivityStarted"]), 662 current_hash=payload["currentActivityHash"], 663 current_mode_hash=payload["currentActivityModeHash"], 664 current_mode=current_mode, 665 current_mode_hashes=payload.get("currentActivityModeHashes", ()), 666 current_mode_types=current_mode_types, 667 current_playlist_hash=payload.get("currentPlaylistActivityHash"), 668 last_story_hash=payload["lastCompletedStoryHash"], 669 available_activities=tuple( 670 self.deserialize_available_activity(activity_) 671 for activity_ in payload["availableActivities"] 672 ), 673 available_activity_interactables=tuple( 674 activity.InteractablesRef( 675 hash=interact["activityInteractableHash"], 676 element_index=interact["activityInteractableElementIndex"], 677 ) 678 for interact in payload["availableActivityInteractables"] 679 ), 680 ) 681 682 def deserialize_profile_items( 683 self, payload: typedefs.JSONObject, / 684 ) -> collections.Sequence[profile.ProfileItemImpl]: 685 return tuple(self.deserialize_profile_item(item) for item in payload["items"]) 686 687 def _deserialize_node(self, payload: typedefs.JSONObject) -> records.Node: 688 return records.Node( 689 state=int(payload["state"]), 690 objective=self.deserialize_objectives(payload["objective"]) 691 if "objective" in payload 692 else None, 693 progress_value=int(payload["progressValue"]), 694 completion_value=int(payload["completionValue"]), 695 record_category_score=int(payload["recordCategoryScore"]) 696 if "recordCategoryScore" in payload 697 else None, 698 ) 699 700 @staticmethod 701 def _deserialize_collectible(payload: typedefs.JSONObject) -> items.Collectible: 702 recent_collectibles: collections.Collection[int] | None = None 703 if raw_recent_collectibles := payload.get("recentCollectibleHashes"): 704 recent_collectibles = tuple( 705 int(item_hash) for item_hash in raw_recent_collectibles 706 ) 707 708 collectibles: dict[int, int] = {} 709 for item_hash, mapping in payload["collectibles"].items(): 710 collectibles[int(item_hash)] = int(mapping["state"]) 711 712 return items.Collectible( 713 recent_collectibles=recent_collectibles, 714 collectibles=collectibles, 715 collection_category_hash=int(payload["collectionCategoriesRootNodeHash"]), 716 collection_badges_hash=int(payload["collectionBadgesRootNodeHash"]), 717 ) 718 719 @staticmethod 720 def _deserialize_currencies( 721 payload: typedefs.JSONObject, 722 ) -> collections.Sequence[items.Currency]: 723 return tuple( 724 items.Currency(hash=int(item_hash), amount=int(amount)) 725 for item_hash, amount in payload["itemQuantities"].items() 726 ) 727 728 def deserialize_progressions( 729 self, payload: typedefs.JSONObject 730 ) -> progressions.Progression: 731 return progressions.Progression( 732 hash=int(payload["progressionHash"]), 733 level=int(payload["level"]), 734 cap=int(payload["levelCap"]), 735 daily_limit=int(payload["dailyLimit"]), 736 weekly_limit=int(payload["weeklyLimit"]), 737 current_progress=int(payload["currentProgress"]), 738 daily_progress=int(payload["dailyProgress"]), 739 needed=int(payload["progressToNextLevel"]), 740 next_level=int(payload["nextLevelAt"]), 741 ) 742 743 def _deserialize_factions( 744 self, payload: typedefs.JSONObject 745 ) -> progressions.Factions: 746 progs = self.deserialize_progressions(payload) 747 return progressions.Factions( 748 hash=progs.hash, 749 level=progs.level, 750 cap=progs.cap, 751 daily_limit=progs.daily_limit, 752 weekly_limit=progs.weekly_limit, 753 current_progress=progs.current_progress, 754 daily_progress=progs.daily_progress, 755 needed=progs.needed, 756 next_level=progs.next_level, 757 faction_hash=payload["factionHash"], 758 faction_vendor_hash=payload["factionVendorIndex"], 759 ) 760 761 def _deserialize_milestone_available_quest( 762 self, payload: typedefs.JSONObject 763 ) -> milestones.MilestoneQuest: 764 return milestones.MilestoneQuest( 765 item_hash=payload["questItemHash"], 766 status=self._deserialize_milestone_quest_status(payload["status"]), 767 ) 768 769 def _deserialize_milestone_activity( 770 self, payload: typedefs.JSONObject 771 ) -> milestones.MilestoneActivity: 772 phases: collections.Sequence[milestones.MilestoneActivityPhase] | None = None 773 if raw_phases := payload.get("phases"): 774 phases = tuple( 775 milestones.MilestoneActivityPhase( 776 is_completed=obj["complete"], hash=obj["phaseHash"] 777 ) 778 for obj in raw_phases 779 ) 780 781 return milestones.MilestoneActivity( 782 hash=payload["activityHash"], 783 challenges=tuple( 784 self.deserialize_objectives(obj["objective"]) 785 for obj in payload["challenges"] 786 ), 787 modifier_hashes=payload.get("modifierHashes"), 788 boolean_options=payload.get("booleanActivityOptions"), 789 phases=phases, 790 ) 791 792 def _deserialize_milestone_quest_status( 793 self, payload: typedefs.JSONObject 794 ) -> milestones.QuestStatus: 795 return milestones.QuestStatus( 796 quest_hash=payload["questHash"], 797 step_hash=payload["stepHash"], 798 step_objectives=tuple( 799 self.deserialize_objectives(objective) 800 for objective in payload["stepObjectives"] 801 ), 802 is_tracked=payload["tracked"], 803 is_completed=payload["completed"], 804 started=payload["started"], 805 item_instance_id=payload["itemInstanceId"], 806 vendor_hash=payload.get("vendorHash"), 807 is_redeemed=payload["redeemed"], 808 ) 809 810 def _deserialize_milestone_rewards( 811 self, payload: typedefs.JSONObject 812 ) -> milestones.MilestoneReward: 813 return milestones.MilestoneReward( 814 category_hash=payload["rewardCategoryHash"], 815 entries=tuple( 816 milestones.MilestoneRewardEntry( 817 entry_hash=entry["rewardEntryHash"], 818 is_earned=entry["earned"], 819 is_redeemed=entry["redeemed"], 820 ) 821 for entry in payload["entries"] 822 ), 823 ) 824 825 def deserialize_milestone( 826 self, payload: typedefs.JSONObject 827 ) -> milestones.Milestone: 828 start_date: datetime.datetime | None = None 829 if raw_start_date := payload.get("startDate"): 830 start_date = time.clean_date(raw_start_date) 831 832 end_date: datetime.datetime | None = None 833 if raw_end_date := payload.get("endDate"): 834 end_date = time.clean_date(raw_end_date) 835 836 rewards: collections.Collection[milestones.MilestoneReward] | None = None 837 if raw_rewards := payload.get("rewards"): 838 rewards = tuple( 839 self._deserialize_milestone_rewards(reward) for reward in raw_rewards 840 ) 841 842 activities: collections.Sequence[milestones.MilestoneActivity] | None = None 843 if raw_activities := payload.get("activities"): 844 activities = tuple( 845 self._deserialize_milestone_activity(active) 846 for active in raw_activities 847 ) 848 849 quests: collections.Sequence[milestones.MilestoneQuest] | None = None 850 if raw_quests := payload.get("availableQuests"): 851 quests = tuple( 852 self._deserialize_milestone_available_quest(quest) 853 for quest in raw_quests 854 ) 855 856 vendors: collections.Sequence[milestones.MilestoneVendor] | None = None 857 if raw_vendors := payload.get("vendors"): 858 vendors = tuple( 859 milestones.MilestoneVendor( 860 vendor_hash=vendor["vendorHash"], 861 preview_itemhash=vendor.get("previewItemHash"), 862 ) 863 for vendor in raw_vendors 864 ) 865 866 return milestones.Milestone( 867 hash=payload["milestoneHash"], 868 start_date=start_date, 869 end_date=end_date, 870 order=payload["order"], 871 rewards=rewards, 872 available_quests=quests, 873 activities=activities, 874 vendors=vendors, 875 ) 876 877 def _deserialize_artifact_tiers( 878 self, payload: typedefs.JSONObject 879 ) -> season.ArtifactTier: 880 return season.ArtifactTier( 881 hash=payload["tierHash"], 882 is_unlocked=payload["isUnlocked"], 883 points_to_unlock=payload["pointsToUnlock"], 884 items=tuple( 885 season.ArtifactTierItem( 886 hash=item["itemHash"], is_active=item["isActive"] 887 ) 888 for item in payload["items"] 889 ), 890 ) 891 892 def deserialize_characters( 893 self, payload: typedefs.JSONObject 894 ) -> collections.Mapping[int, character.Character]: 895 return { 896 int(char_id): self._set_character_attrs(char) 897 for char_id, char in payload["data"].items() 898 } 899 900 def deserialize_character( 901 self, payload: typedefs.JSONObject 902 ) -> character.Character: 903 return self._set_character_attrs(payload) 904 905 def deserialize_character_equipments( 906 self, payload: typedefs.JSONObject 907 ) -> collections.Mapping[int, collections.Sequence[profile.ProfileItemImpl]]: 908 return { 909 int(char_id): self.deserialize_profile_items(item) 910 for char_id, item in payload["data"].items() 911 } 912 913 def deserialize_character_activities( 914 self, payload: typedefs.JSONObject 915 ) -> collections.Mapping[int, activity.CharacterActivity]: 916 return { 917 int(char_id): self.deserialize_character_activity(data) 918 for char_id, data in payload["data"].items() 919 } 920 921 def deserialize_characters_render_data( 922 self, payload: typedefs.JSONObject 923 ) -> collections.Mapping[int, character.RenderedData]: 924 return { 925 int(char_id): self.deserialize_character_render_data(data) 926 for char_id, data in payload["data"].items() 927 } 928 929 def deserialize_character_progressions( 930 self, payload: typedefs.JSONObject 931 ) -> character.CharacterProgression: 932 progressions_ = { 933 int(prog_id): self.deserialize_progressions(prog) 934 for prog_id, prog in payload["progressions"].items() 935 } 936 937 factions = { 938 int(faction_id): self._deserialize_factions(faction) 939 for faction_id, faction in payload["factions"].items() 940 } 941 942 milestones_ = { 943 int(milestone_hash): self.deserialize_milestone(milestone) 944 for milestone_hash, milestone in payload["milestones"].items() 945 } 946 947 uninstanced_item_objectives = { 948 int(item_hash): [self.deserialize_objectives(ins) for ins in obj] 949 for item_hash, obj in payload["uninstancedItemObjectives"].items() 950 } 951 952 artifact = payload["seasonalArtifact"] 953 seasonal_artifact = season.CharacterScopedArtifact( 954 hash=artifact["artifactHash"], 955 points_used=artifact["pointsUsed"], 956 reset_count=artifact["resetCount"], 957 tiers=tuple( 958 self._deserialize_artifact_tiers(tier) for tier in artifact["tiers"] 959 ), 960 ) 961 checklists = payload["checklists"] 962 963 return character.CharacterProgression( 964 progressions=progressions_, 965 factions=factions, 966 checklists=checklists, 967 milestones=milestones_, 968 seasonal_artifact=seasonal_artifact, 969 uninstanced_item_objectives=uninstanced_item_objectives, 970 ) 971 972 def deserialize_character_progressions_mapping( 973 self, payload: typedefs.JSONObject 974 ) -> collections.Mapping[int, character.CharacterProgression]: 975 character_progressions: collections.MutableMapping[ 976 int, character.CharacterProgression 977 ] = {} 978 for char_id, data in payload["data"].items(): 979 character_progressions[int(char_id)] = ( 980 self.deserialize_character_progressions(data) 981 ) 982 return character_progressions 983 984 def _deserialize_character_loadout_item( 985 self, payload: typedefs.JSONObject 986 ) -> character.LoadoutItem: 987 return character.LoadoutItem( 988 instance_id=payload["itemInstanceId"], 989 plug_hashes=tuple(payload["plugItemHashes"]), 990 ) 991 992 def deserialize_character_loadout( 993 self, payload: typedefs.JSONObject 994 ) -> character.Loadout: 995 return character.Loadout( 996 color_hash=payload["colorHash"], 997 icon_hash=payload["iconHash"], 998 name_hash=payload["nameHash"], 999 items=tuple( 1000 self._deserialize_character_loadout_item(item) 1001 for item in payload["items"] 1002 ), 1003 ) 1004 1005 def deserialize_characters_records( 1006 self, 1007 payload: typedefs.JSONObject, 1008 ) -> collections.Mapping[int, records.CharacterRecord]: 1009 return { 1010 int(rec_id): self.deserialize_character_records( 1011 rec, record_hashes=payload.get("featuredRecordHashes", ()) 1012 ) 1013 for rec_id, rec in payload["records"].items() 1014 } 1015 1016 def deserialize_profile_records( 1017 self, payload: typedefs.JSONObject 1018 ) -> collections.Mapping[int, records.Record]: 1019 raw_profile_records = payload["data"] 1020 scores = records.RecordScores( 1021 current_score=raw_profile_records["score"], 1022 legacy_score=raw_profile_records["legacyScore"], 1023 lifetime_score=raw_profile_records["lifetimeScore"], 1024 ) 1025 return { 1026 int(record_id): self.deserialize_records( 1027 record, 1028 scores, 1029 categories_hash=raw_profile_records["recordCategoriesRootNodeHash"], 1030 seals_hash=raw_profile_records["recordSealsRootNodeHash"], 1031 ) 1032 for record_id, record in raw_profile_records["records"].items() 1033 } 1034 1035 def _deserialize_craftable_socket_plug( 1036 self, payload: typedefs.JSONObject 1037 ) -> items.CraftableSocketPlug: 1038 return items.CraftableSocketPlug( 1039 item_hash=int(payload["plugItemHash"]), 1040 failed_requirement_indexes=payload.get("failedRequirementIndexes", ()), 1041 ) 1042 1043 def _deserialize_craftable_socket( 1044 self, payload: typedefs.JSONObject 1045 ) -> items.CraftableSocket: 1046 if raw_plug := payload.get("plug"): 1047 plugs = tuple( 1048 self._deserialize_craftable_socket_plug(plug) for plug in raw_plug 1049 ) 1050 else: 1051 plugs = () 1052 1053 return items.CraftableSocket( 1054 plug_set_hash=int(payload["plugSetHash"]), plugs=plugs 1055 ) 1056 1057 def _deserialize_craftable_item( 1058 self, payload: typedefs.JSONObject 1059 ) -> items.CraftableItem: 1060 return items.CraftableItem( 1061 is_visible=payload["visible"], 1062 failed_requirement_indexes=payload.get("failedRequirementIndexes", ()), 1063 sockets=tuple( 1064 self._deserialize_craftable_socket(socket) 1065 for socket in payload["sockets"] 1066 ), 1067 ) 1068 1069 def deserialize_craftables_component( 1070 self, payload: typedefs.JSONObject 1071 ) -> components.CraftablesComponent: 1072 return components.CraftablesComponent( 1073 craftables={ 1074 int(item_id): self._deserialize_craftable_item(item) 1075 for item_id, item in payload["craftables"].items() 1076 if item is not None 1077 }, 1078 crafting_root_node_hash=payload["craftingRootNodeHash"], 1079 ) 1080 1081 def _deserialize_commendations_component( 1082 self, payload: typedefs.JSONObject 1083 ) -> components.Commendation: 1084 return components.Commendation( 1085 total_score=payload["totalScore"], 1086 node_percentages={ 1087 int(node_hash): node_percent 1088 for node_hash, node_percent in payload[ 1089 "commendationNodePercentagesByHash" 1090 ].items() 1091 }, 1092 score_detail_values=tuple(payload["scoreDetailValues"]), 1093 node_scores={ 1094 int(node_score_hash): node_score_value 1095 for node_score_hash, node_score_value in payload[ 1096 "commendationNodeScoresByHash" 1097 ].items() 1098 }, 1099 commendation_scores={ 1100 int(score_hash): score_value 1101 for score_hash, score_value in payload[ 1102 "commendationScoresByHash" 1103 ].items() 1104 }, 1105 ) 1106 1107 def deserialize_components( # noqa: C901 Too complex. 1108 self, payload: typedefs.JSONObject 1109 ) -> components.Component: 1110 # Due to how complex this method is, We'll stick to 1111 # typing.Optional here. 1112 1113 profile_: profile.Profile | None = None 1114 if raw_profile := payload.get("profile"): 1115 profile_ = self.deserialize_profile(raw_profile["data"]) 1116 1117 profile_progression: profile.ProfileProgression | None = None 1118 if raw_profile_progression := payload.get("profileProgression"): 1119 profile_progression = self.deserialize_profile_progression( 1120 raw_profile_progression 1121 ) 1122 1123 profile_currencies: typing.Optional[ 1124 collections.Sequence[profile.ProfileItemImpl] 1125 ] = None 1126 if raw_profile_currencies := payload.get("profileCurrencies"): 1127 if "data" in raw_profile_currencies: 1128 profile_currencies = self.deserialize_profile_items( 1129 raw_profile_currencies["data"] 1130 ) 1131 1132 profile_inventories: typing.Optional[ 1133 collections.Sequence[profile.ProfileItemImpl] 1134 ] = None 1135 if raw_profile_inventories := payload.get("profileInventory"): 1136 if "data" in raw_profile_inventories: 1137 profile_inventories = self.deserialize_profile_items( 1138 raw_profile_inventories["data"] 1139 ) 1140 1141 profile_records: typing.Optional[collections.Mapping[int, records.Record]] = ( 1142 None 1143 ) 1144 1145 if raw_profile_records_ := payload.get("profileRecords"): 1146 profile_records = self.deserialize_profile_records(raw_profile_records_) 1147 1148 characters: typing.Optional[collections.Mapping[int, character.Character]] = ( 1149 None 1150 ) 1151 if raw_characters := payload.get("characters"): 1152 characters = self.deserialize_characters(raw_characters) 1153 1154 character_records: typing.Optional[ 1155 collections.Mapping[int, records.CharacterRecord] 1156 ] = None 1157 1158 if raw_character_records := payload.get("characterRecords"): 1159 # Had to do it in two steps.. 1160 to_update = {} 1161 for _, data in raw_character_records["data"].items(): 1162 for record_id, record in data.items(): 1163 to_update[record_id] = record 1164 1165 character_records = { 1166 int(rec_id): self.deserialize_character_records( 1167 rec, record_hashes=to_update.get("featuredRecordHashes", ()) 1168 ) 1169 for rec_id, rec in to_update["records"].items() 1170 } 1171 1172 character_equipments: typing.Optional[ 1173 collections.Mapping[int, collections.Sequence[profile.ProfileItemImpl]] 1174 ] = None 1175 if raw_character_equips := payload.get("characterEquipment"): 1176 character_equipments = self.deserialize_character_equipments( 1177 raw_character_equips 1178 ) 1179 1180 character_inventories: typing.Optional[ 1181 collections.Mapping[int, collections.Sequence[profile.ProfileItemImpl]] 1182 ] = None 1183 if raw_character_inventories := payload.get("characterInventories"): 1184 if "data" in raw_character_inventories: 1185 character_inventories = self.deserialize_character_equipments( 1186 raw_character_inventories 1187 ) 1188 1189 character_activities: typing.Optional[ 1190 collections.Mapping[int, activity.CharacterActivity] 1191 ] = None 1192 if raw_char_acts := payload.get("characterActivities"): 1193 character_activities = self.deserialize_character_activities(raw_char_acts) 1194 1195 character_render_data: typing.Optional[ 1196 collections.Mapping[int, character.RenderedData] 1197 ] = None 1198 if raw_character_render_data := payload.get("characterRenderData"): 1199 character_render_data = self.deserialize_characters_render_data( 1200 raw_character_render_data 1201 ) 1202 1203 character_progressions: typing.Optional[ 1204 collections.Mapping[int, character.CharacterProgression] 1205 ] = None 1206 1207 if raw_character_progressions := payload.get("characterProgressions"): 1208 character_progressions = self.deserialize_character_progressions_mapping( 1209 raw_character_progressions 1210 ) 1211 1212 profile_string_vars: typing.Optional[collections.Mapping[int, int]] = None 1213 if raw_profile_string_vars := payload.get("profileStringVariables"): 1214 profile_string_vars = raw_profile_string_vars["data"]["integerValuesByHash"] 1215 1216 character_string_vars: typing.Optional[ 1217 collections.Mapping[int, collections.Mapping[int, int]] 1218 ] = None 1219 if raw_character_string_vars := payload.get("characterStringVariables"): 1220 character_string_vars = { 1221 int(char_id): data["integerValuesByHash"] 1222 for char_id, data in raw_character_string_vars["data"].items() 1223 } 1224 1225 metrics: typing.Optional[ 1226 collections.Sequence[ 1227 collections.Mapping[int, tuple[bool, records.Objective | None]] 1228 ] 1229 ] = None 1230 root_node_hash: int | None = None 1231 1232 if raw_metrics := payload.get("metrics"): 1233 root_node_hash = raw_metrics["data"]["metricsRootNodeHash"] 1234 metrics = tuple( 1235 { 1236 int(metrics_hash): ( 1237 data["invisible"], 1238 self.deserialize_objectives(data["objectiveProgress"]) 1239 if "objectiveProgress" in data 1240 else None, 1241 ) 1242 } 1243 for metrics_hash, data in raw_metrics["data"]["metrics"].items() 1244 ) 1245 transitory: fireteams.FireteamParty | None = None 1246 if raw_transitory := payload.get("profileTransitoryData"): 1247 if "data" in raw_transitory: 1248 transitory = self.deserialize_fireteam_party(raw_transitory["data"]) 1249 1250 item_components: components.ItemsComponent | None = None 1251 if raw_item_components := payload.get("itemComponents"): 1252 item_components = self.deserialize_items_component(raw_item_components) 1253 1254 profile_plugsets: typing.Optional[ 1255 collections.Mapping[int, collections.Sequence[items.PlugItemState]] 1256 ] = None 1257 1258 if raw_profile_plugs := payload.get("profilePlugSets"): 1259 profile_plugsets = { 1260 int(index): [self.deserialize_plug_item_state(state) for state in data] 1261 for index, data in raw_profile_plugs["data"]["plugs"].items() 1262 } 1263 1264 character_plugsets: typing.Optional[ 1265 collections.Mapping[ 1266 int, collections.Mapping[int, collections.Sequence[items.PlugItemState]] 1267 ] 1268 ] = None 1269 if raw_char_plugsets := payload.get("characterPlugSets"): 1270 character_plugsets = { 1271 int(char_id): { 1272 int(index): [ 1273 self.deserialize_plug_item_state(state) for state in data 1274 ] 1275 for index, data in inner["plugs"].items() 1276 } 1277 for char_id, inner in raw_char_plugsets["data"].items() 1278 } 1279 1280 character_collectibles: typing.Optional[ 1281 collections.Mapping[int, items.Collectible] 1282 ] = None 1283 if raw_character_collectibles := payload.get("characterCollectibles"): 1284 character_collectibles = { 1285 int(char_id): self._deserialize_collectible(data) 1286 for char_id, data in raw_character_collectibles["data"].items() 1287 } 1288 1289 profile_collectibles: items.Collectible | None = None 1290 if raw_profile_collectibles := payload.get("profileCollectibles"): 1291 profile_collectibles = self._deserialize_collectible( 1292 raw_profile_collectibles["data"] 1293 ) 1294 1295 profile_nodes: typing.Optional[collections.Mapping[int, records.Node]] = None 1296 if raw_profile_nodes := payload.get("profilePresentationNodes"): 1297 profile_nodes = { 1298 int(node_hash): self._deserialize_node(node) 1299 for node_hash, node in raw_profile_nodes["data"]["nodes"].items() 1300 } 1301 1302 character_nodes: typing.Optional[ 1303 collections.Mapping[int, collections.Mapping[int, records.Node]] 1304 ] = None 1305 if raw_character_nodes := payload.get("characterPresentationNodes"): 1306 character_nodes = { 1307 int(char_id): { 1308 int(node_hash): self._deserialize_node(node) 1309 for node_hash, node in each_character["nodes"].items() 1310 } 1311 for char_id, each_character in raw_character_nodes["data"].items() 1312 } 1313 1314 platform_silver: typing.Optional[ 1315 collections.Mapping[str, profile.ProfileItemImpl] 1316 ] = None 1317 if raw_platform_silver := payload.get("platformSilver"): 1318 if "data" in raw_platform_silver: 1319 platform_silver = { 1320 platform_name: self.deserialize_profile_item(item) 1321 for platform_name, item in raw_platform_silver["data"][ 1322 "platformSilver" 1323 ].items() 1324 } 1325 1326 character_currency_lookups: typing.Optional[ 1327 collections.Mapping[int, collections.Sequence[items.Currency]] 1328 ] = None 1329 if raw_char_lookups := payload.get("characterCurrencyLookups"): 1330 if "data" in raw_char_lookups: 1331 character_currency_lookups = { 1332 int(char_id): self._deserialize_currencies(currency) 1333 for char_id, currency in raw_char_lookups["data"].items() 1334 } 1335 1336 character_craftables: typing.Optional[ 1337 collections.Mapping[int, components.CraftablesComponent] 1338 ] = None 1339 if raw_character_craftables := payload.get("characterCraftables"): 1340 if "data" in raw_character_craftables: 1341 character_craftables = { 1342 int(char_id): self.deserialize_craftables_component(craftable) 1343 for char_id, craftable in raw_character_craftables["data"].items() 1344 } 1345 1346 character_loadouts: ( 1347 collections.Mapping[int, collections.Sequence[character.Loadout]] | None 1348 ) = None 1349 if raw_character_loadouts := payload.get("characterLoadouts"): 1350 if "data" in raw_character_loadouts: 1351 character_loadouts = { 1352 int(char_id): tuple( 1353 self.deserialize_character_loadout(loadout) 1354 for loadout in raw_loadouts["loadouts"] 1355 ) 1356 for char_id, raw_loadouts in raw_character_loadouts["data"].items() 1357 } 1358 1359 commendations: components.Commendation | None = None 1360 if ( 1361 raw_commendations := payload.get("profileCommendations") 1362 ) and "data" in raw_commendations: 1363 commendations = self._deserialize_commendations_component( 1364 raw_commendations["data"] 1365 ) 1366 1367 return components.Component( 1368 profiles=profile_, 1369 profile_progression=profile_progression, 1370 profile_currencies=profile_currencies, 1371 profile_inventories=profile_inventories, 1372 profile_records=profile_records, 1373 characters=characters, 1374 character_records=character_records, 1375 character_equipments=character_equipments, 1376 character_inventories=character_inventories, 1377 character_activities=character_activities, 1378 character_render_data=character_render_data, 1379 character_progressions=character_progressions, 1380 profile_string_variables=profile_string_vars, 1381 character_string_variables=character_string_vars, 1382 metrics=metrics, 1383 root_node_hash=root_node_hash, 1384 transitory=transitory, 1385 item_components=item_components, 1386 profile_plugsets=profile_plugsets, 1387 character_plugsets=character_plugsets, 1388 character_collectibles=character_collectibles, 1389 profile_collectibles=profile_collectibles, 1390 profile_nodes=profile_nodes, 1391 character_nodes=character_nodes, 1392 platform_silver=platform_silver, 1393 character_currency_lookups=character_currency_lookups, 1394 character_craftables=character_craftables, 1395 character_loadouts=character_loadouts, 1396 commendation=commendations, 1397 ) 1398 1399 def deserialize_items_component( 1400 self, payload: typedefs.JSONObject 1401 ) -> components.ItemsComponent: 1402 # Due to how complex this method is, We'll stick to typing.Optional. 1403 instances: typing.Optional[ 1404 collections.Sequence[collections.Mapping[int, items.ItemInstance]] 1405 ] = None 1406 if raw_instances := payload.get("instances"): 1407 instances = tuple( 1408 {int(ins_id): self.deserialize_instanced_item(item)} 1409 for ins_id, item in raw_instances["data"].items() 1410 ) 1411 1412 render_data: typing.Optional[ 1413 collections.Mapping[int, tuple[bool, dict[int, int]]] 1414 ] = None 1415 if raw_render_data := payload.get("renderData"): 1416 render_data = { 1417 int(ins_id): (data["useCustomDyes"], data["artRegions"]) 1418 for ins_id, data in raw_render_data["data"].items() 1419 } 1420 1421 stats: typing.Optional[collections.Mapping[int, items.ItemStatsView]] = None 1422 if raw_stats := payload.get("stats"): 1423 stats = {} 1424 for ins_id, stat in raw_stats["data"].items(): 1425 for _, items_ in stat.items(): 1426 stats[int(ins_id)] = self.deserialize_item_stats_view(items_) 1427 1428 sockets: typing.Optional[ 1429 collections.Mapping[int, collections.Sequence[items.ItemSocket]] 1430 ] = None 1431 if raw_sockets := payload.get("sockets"): 1432 sockets = { 1433 int(ins_id): tuple( 1434 self.deserialize_item_socket(socket) for socket in item["sockets"] 1435 ) 1436 for ins_id, item in raw_sockets["data"].items() 1437 } 1438 1439 objectives: typing.Optional[ 1440 collections.Mapping[int, collections.Sequence[records.Objective]] 1441 ] = None 1442 if raw_objectives := payload.get("objectives"): 1443 objectives = { 1444 int(ins_id): tuple( 1445 self.deserialize_objectives(objective) 1446 for objective in data["objectives"] 1447 ) 1448 for ins_id, data in raw_objectives["data"].items() 1449 } 1450 1451 perks: typing.Optional[ 1452 collections.Mapping[int, collections.Collection[items.ItemPerk]] 1453 ] = None 1454 if raw_perks := payload.get("perks"): 1455 perks = { 1456 int(ins_id): tuple( 1457 self.deserialize_item_perk(perk) for perk in item["perks"] 1458 ) 1459 for ins_id, item in raw_perks["data"].items() 1460 } 1461 1462 plug_states: typing.Optional[collections.Sequence[items.PlugItemState]] = None 1463 if raw_plug_states := payload.get("plugStates"): 1464 plug_states = tuple( 1465 self.deserialize_plug_item_state(plug) 1466 for _, plug in raw_plug_states["data"].items() 1467 ) 1468 1469 reusable_plugs: typing.Optional[ 1470 collections.Mapping[int, collections.Sequence[items.PlugItemState]] 1471 ] = None 1472 if raw_re_plugs := payload.get("reusablePlugs"): 1473 reusable_plugs = { 1474 int(ins_id): tuple( 1475 self.deserialize_plug_item_state(state) for state in inner 1476 ) 1477 for ins_id, plug in raw_re_plugs["data"].items() 1478 for inner in tuple(plug["plugs"].values()) 1479 } 1480 1481 plug_objectives: typing.Optional[ 1482 collections.Mapping[ 1483 int, collections.Mapping[int, collections.Collection[records.Objective]] 1484 ] 1485 ] = None 1486 if raw_plug_objectives := payload.get("plugObjectives"): 1487 plug_objectives = { 1488 int(ins_id): { 1489 int(obj_hash): tuple( 1490 self.deserialize_objectives(obj) for obj in objs 1491 ) 1492 for obj_hash, objs in inner["objectivesPerPlug"].items() 1493 } 1494 for ins_id, inner in raw_plug_objectives["data"].items() 1495 } 1496 1497 return components.ItemsComponent( 1498 sockets=sockets, 1499 stats=stats, 1500 render_data=render_data, 1501 instances=instances, 1502 objectives=objectives, 1503 perks=perks, 1504 plug_states=plug_states, 1505 reusable_plugs=reusable_plugs, 1506 plug_objectives=plug_objectives, 1507 ) 1508 1509 def deserialize_character_component( 1510 self, payload: typedefs.JSONObject 1511 ) -> components.CharacterComponent: 1512 character_: character.Character | None = None 1513 if raw_singular_character := payload.get("character"): 1514 character_ = self.deserialize_character(raw_singular_character["data"]) 1515 1516 inventory: typing.Optional[collections.Sequence[profile.ProfileItemImpl]] = None 1517 loadouts: collections.Sequence[character.Loadout] | None = None 1518 if raw_inventory := payload.get("inventory"): 1519 if "data" in raw_inventory: 1520 inventory = self.deserialize_profile_items(raw_inventory["data"]) 1521 1522 # The inventory component also returns the loadouts component 1523 if raw_loadouts := payload.get("loadouts"): 1524 if "data" in raw_loadouts: 1525 loadouts = tuple( 1526 self.deserialize_character_loadout(loadout) 1527 # very interesting nesting bungie... 1528 for loadout in raw_loadouts["data"]["loadouts"] 1529 ) 1530 1531 activities: activity.CharacterActivity | None = None 1532 if raw_activities := payload.get("activities"): 1533 activities = self.deserialize_character_activity(raw_activities["data"]) 1534 1535 equipment: typing.Optional[collections.Sequence[profile.ProfileItemImpl]] = None 1536 if raw_equipments := payload.get("equipment"): 1537 equipment = self.deserialize_profile_items(raw_equipments["data"]) 1538 1539 progressions_: character.CharacterProgression | None = None 1540 if raw_progressions := payload.get("progressions"): 1541 progressions_ = self.deserialize_character_progressions( 1542 raw_progressions["data"] 1543 ) 1544 1545 render_data: character.RenderedData | None = None 1546 if raw_render_data := payload.get("renderData"): 1547 render_data = self.deserialize_character_render_data( 1548 raw_render_data["data"] 1549 ) 1550 1551 character_records: typing.Optional[ 1552 collections.Mapping[int, records.CharacterRecord] 1553 ] = None 1554 if raw_char_records := payload.get("records"): 1555 character_records = self.deserialize_characters_records( 1556 raw_char_records["data"] 1557 ) 1558 1559 item_components: components.ItemsComponent | None = None 1560 if raw_item_components := payload.get("itemComponents"): 1561 item_components = self.deserialize_items_component(raw_item_components) 1562 1563 nodes: typing.Optional[collections.Mapping[int, records.Node]] = None 1564 if raw_nodes := payload.get("presentationNodes"): 1565 nodes = { 1566 int(node_hash): self._deserialize_node(node) 1567 for node_hash, node in raw_nodes["data"]["nodes"].items() 1568 } 1569 1570 collectibles: items.Collectible | None = None 1571 if raw_collectibles := payload.get("collectibles"): 1572 collectibles = self._deserialize_collectible(raw_collectibles["data"]) 1573 1574 currency_lookups: typing.Optional[collections.Sequence[items.Currency]] = None 1575 if raw_currencies := payload.get("currencyLookups"): 1576 if "data" in raw_currencies: 1577 currency_lookups = self._deserialize_currencies(raw_currencies) 1578 1579 return components.CharacterComponent( 1580 activities=activities, 1581 equipment=equipment, 1582 inventory=inventory, 1583 progressions=progressions_, 1584 render_data=render_data, 1585 character=character_, 1586 character_records=character_records, 1587 profile_records=None, 1588 item_components=item_components, 1589 currency_lookups=currency_lookups, 1590 collectibles=collectibles, 1591 nodes=nodes, 1592 loadouts=loadouts, 1593 ) 1594 1595 def _set_entity_attrs( 1596 self, payload: typedefs.JSONObject, *, key: str = "displayProperties" 1597 ) -> entity.Entity: 1598 properties = payload[key] 1599 name = typedefs.unknown(properties["name"]) 1600 description = typedefs.unknown(properties["description"]) 1601 1602 return entity.Entity( 1603 hash=payload["hash"], 1604 index=payload["index"], 1605 name=name, 1606 description=description, 1607 has_icon=properties["hasIcon"], 1608 icon=builders.Image(properties.get("icon")), 1609 ) 1610 1611 def deserialize_inventory_results( 1612 self, payload: typedefs.JSONObject 1613 ) -> sain.Iterator[entity.SearchableEntity]: 1614 return sain.Iter( 1615 entity.SearchableEntity( 1616 hash=data["hash"], 1617 entity_type=data["entityType"], 1618 weight=data["weight"], 1619 suggested_words=payload["suggestedWords"], 1620 name=data["displayProperties"]["name"], 1621 has_icon=data["displayProperties"]["hasIcon"], 1622 description=typedefs.unknown(data["displayProperties"]["description"]), 1623 icon=builders.Image(path=data["displayProperties"]["icon"]), 1624 ) 1625 for data in payload["results"]["results"] 1626 ) 1627 1628 def _deserialize_inventory_item_objects( 1629 self, payload: typedefs.JSONObject 1630 ) -> entity.InventoryEntityObjects: 1631 return entity.InventoryEntityObjects( 1632 action=payload.get("action"), 1633 set_data=payload.get("setData"), 1634 stats=payload.get("stats"), 1635 equipping_block=payload.get("equippingBlock"), 1636 translation_block=payload.get("translationBlock"), 1637 preview=payload.get("preview"), 1638 quality=payload.get("quality"), 1639 value=payload.get("value"), 1640 source_data=payload.get("sourceData"), 1641 objectives=payload.get("objectives"), 1642 plug=payload.get("plug"), 1643 metrics=payload.get("metrics"), 1644 gearset=payload.get("gearset"), 1645 sack=payload.get("sack"), 1646 sockets=payload.get("sockets"), 1647 summary=payload.get("summary"), 1648 talent_gird=payload.get("talentGrid"), 1649 investments_stats=payload.get("investmentStats"), 1650 perks=payload.get("perks"), 1651 animations=payload.get("animations", ()), 1652 links=payload.get("links", ()), 1653 ) 1654 1655 def deserialize_inventory_entity( # noqa: C901 Too complex. 1656 self, payload: typedefs.JSONObject, / 1657 ) -> entity.InventoryEntity: 1658 props = self._set_entity_attrs(payload) 1659 objects = self._deserialize_inventory_item_objects(payload) 1660 1661 collectible_hash: int | None = None 1662 if raw_collectible_hash := payload.get("collectibleHash"): 1663 collectible_hash = int(raw_collectible_hash) 1664 1665 secondary_icon: builders.Image | None = None 1666 if raw_second_icon := payload.get("secondaryIcon"): 1667 secondary_icon = builders.Image(path=raw_second_icon) 1668 1669 secondary_overlay: builders.Image | None = None 1670 if raw_second_overlay := payload.get("secondaryOverlay"): 1671 secondary_overlay = builders.Image(path=raw_second_overlay) 1672 1673 secondary_special: builders.Image | None = None 1674 if raw_second_special := payload.get("secondarySpecial"): 1675 secondary_special = builders.Image(path=raw_second_special) 1676 1677 screenshot: builders.Image | None = None 1678 if raw_screenshot := payload.get("screenshot"): 1679 screenshot = builders.Image(path=raw_screenshot) 1680 1681 watermark_icon: builders.Image | None = None 1682 if raw_watermark_icon := payload.get("iconWatermark"): 1683 watermark_icon = builders.Image(path=raw_watermark_icon) 1684 1685 watermark_shelved: builders.Image | None = None 1686 if raw_watermark_shelved := payload.get("iconWatermarkShelved"): 1687 watermark_shelved = builders.Image(path=raw_watermark_shelved) 1688 1689 about: str | None = None 1690 if raw_about := payload.get("flavorText"): 1691 about = raw_about 1692 1693 ui_item_style: str | None = None 1694 if raw_ui_style := payload.get("uiItemDisplayStyle"): 1695 ui_item_style = raw_ui_style 1696 1697 tier_and_name: str | None = None 1698 if raw_tier_and_name := payload.get("itemTypeAndTierDisplayName"): 1699 tier_and_name = raw_tier_and_name 1700 1701 type_name: str | None = None 1702 if raw_type_name := payload.get("itemTypeDisplayName"): 1703 type_name = raw_type_name 1704 1705 display_source: str | None = None 1706 if raw_display_source := payload.get("displaySource"): 1707 display_source = raw_display_source 1708 1709 lorehash: int | None = None 1710 if raw_lore_hash := payload.get("loreHash"): 1711 lorehash = int(raw_lore_hash) 1712 1713 summary_hash: int | None = None 1714 if raw_summary_hash := payload.get("summaryItemHash"): 1715 summary_hash = raw_summary_hash 1716 1717 breaker_type_hash: int | None = None 1718 if raw_breaker_type_hash := payload.get("breakerTypeHash"): 1719 breaker_type_hash = int(raw_breaker_type_hash) 1720 1721 damage_types: typing.Optional[collections.Sequence[int]] = None 1722 if raw_damage_types := payload.get("damageTypes"): 1723 damage_types = tuple(int(type_) for type_ in raw_damage_types) 1724 1725 damagetype_hashes: typing.Optional[collections.Sequence[int]] = None 1726 if raw_damagetype_hashes := payload.get("damageTypeHashes"): 1727 damagetype_hashes = tuple(int(type_) for type_ in raw_damagetype_hashes) 1728 1729 default_damagetype_hash: int | None = None 1730 if raw_defaultdmg_hash := payload.get("defaultDamageTypeHash"): 1731 default_damagetype_hash = int(raw_defaultdmg_hash) 1732 1733 emblem_objective_hash: int | None = None 1734 if raw_emblem_obj_hash := payload.get("emblemObjectiveHash"): 1735 emblem_objective_hash = int(raw_emblem_obj_hash) 1736 1737 tier_type: enums.TierType | None = None 1738 tier: enums.ItemTier | None = None 1739 bucket_hash: int | None = None 1740 recovery_hash: int | None = None 1741 tier_name: str | None = None 1742 isinstance_item: bool = False 1743 expire_tool_tip: str | None = None 1744 expire_in_orbit_message: str | None = None 1745 suppress_expiration: bool = False 1746 max_stack_size: int | None = None 1747 stack_label: str | None = None 1748 1749 if inventory := payload.get("inventory"): 1750 tier_type = enums.TierType(int(inventory["tierType"])) 1751 tier = enums.ItemTier(int(inventory["tierTypeHash"])) 1752 bucket_hash = int(inventory["bucketTypeHash"]) 1753 recovery_hash = int(inventory["recoveryBucketTypeHash"]) 1754 tier_name = inventory["tierTypeName"] 1755 isinstance_item = inventory["isInstanceItem"] 1756 suppress_expiration = inventory["suppressExpirationWhenObjectivesComplete"] 1757 max_stack_size = int(inventory["maxStackSize"]) 1758 1759 try: 1760 stack_label = inventory["stackUniqueLabel"] 1761 except KeyError: 1762 pass 1763 1764 if "traitHashes" in payload: 1765 trait_hashes = tuple( 1766 int(trait_hash) for trait_hash in payload["traitHashes"] 1767 ) 1768 else: 1769 trait_hashes = () 1770 1771 if "traitIds" in payload: 1772 trait_ids = tuple(trait_id for trait_id in payload["traitIds"]) 1773 else: 1774 trait_ids = () 1775 1776 return entity.InventoryEntity( 1777 collectible_hash=collectible_hash, 1778 name=props.name, 1779 about=about, 1780 emblem_objective_hash=emblem_objective_hash, 1781 suppress_expiration=suppress_expiration, 1782 max_stack_size=max_stack_size, 1783 stack_label=stack_label, 1784 tier=tier, 1785 tier_type=tier_type, 1786 tier_name=tier_name, 1787 bucket_hash=bucket_hash, 1788 recovery_bucket_hash=recovery_hash, 1789 isinstance_item=isinstance_item, 1790 expire_in_orbit_message=expire_in_orbit_message, 1791 expiration_tooltip=expire_tool_tip, 1792 lore_hash=lorehash, 1793 type_and_tier_name=tier_and_name, 1794 summary_hash=summary_hash, 1795 ui_display_style=ui_item_style, 1796 type_name=type_name, 1797 breaker_type_hash=breaker_type_hash, 1798 description=props.description, 1799 display_source=display_source, 1800 hash=props.hash, 1801 damage_types=damage_types, 1802 index=props.index, 1803 icon=props.icon, 1804 has_icon=props.has_icon, 1805 screenshot=screenshot, 1806 watermark_icon=watermark_icon, 1807 watermark_shelved=watermark_shelved, 1808 secondary_icon=secondary_icon, 1809 secondary_overlay=secondary_overlay, 1810 secondary_special=secondary_special, 1811 type=enums.ItemType(int(payload["itemType"])), 1812 category_hashes=tuple( 1813 int(hash_) for hash_ in payload["itemCategoryHashes"] 1814 ), 1815 item_class=enums.Class(int(payload["classType"])), 1816 sub_type=enums.ItemSubType(int(payload["itemSubType"])), 1817 breaker_type=int(payload["breakerType"]), 1818 default_damagetype=int(payload["defaultDamageType"]), 1819 default_damagetype_hash=default_damagetype_hash, 1820 damagetype_hashes=damagetype_hashes, 1821 tooltip_notifications=payload["tooltipNotifications"], 1822 not_transferable=payload["nonTransferrable"], 1823 allow_actions=payload["allowActions"], 1824 is_equippable=payload["equippable"], 1825 objects=objects, 1826 background_colors=payload.get("backgroundColor", {}), 1827 season_hash=payload.get("seasonHash"), 1828 has_postmaster_effect=payload["doesPostmasterPullHaveSideEffects"], 1829 trait_hashes=trait_hashes, 1830 trait_ids=trait_ids, 1831 ) 1832 1833 def deserialize_objective_entity( 1834 self, payload: typedefs.JSONObject, / 1835 ) -> entity.ObjectiveEntity: 1836 props = self._set_entity_attrs(payload) 1837 return entity.ObjectiveEntity( 1838 hash=props.hash, 1839 index=props.index, 1840 description=props.description, 1841 name=props.name, 1842 has_icon=props.has_icon, 1843 icon=props.icon, 1844 unlock_value_hash=payload["unlockValueHash"], 1845 completion_value=payload["completionValue"], 1846 scope=entity.GatingScope(int(payload["scope"])), 1847 location_hash=payload["locationHash"], 1848 allowed_negative_value=payload["allowNegativeValue"], 1849 allowed_value_change=payload["allowValueChangeWhenCompleted"], 1850 counting_downward=payload["isCountingDownward"], 1851 value_style=entity.ValueUIStyle(int(payload["valueStyle"])), 1852 progress_description=payload["progressDescription"], 1853 perks=payload["perks"], 1854 stats=payload["stats"], 1855 minimum_visibility=payload["minimumVisibilityThreshold"], 1856 allow_over_completion=payload["allowOvercompletion"], 1857 show_value_style=payload["showValueOnComplete"], 1858 display_only_objective=payload["isDisplayOnlyObjective"], 1859 complete_value_style=entity.ValueUIStyle( 1860 int(payload["completedValueStyle"]) 1861 ), 1862 progress_value_style=entity.ValueUIStyle( 1863 int(payload["inProgressValueStyle"]) 1864 ), 1865 ui_label=payload["uiLabel"], 1866 ui_style=entity.ObjectiveUIStyle(int(payload["uiStyle"])), 1867 ) 1868 1869 def _deserialize_activity_values( 1870 self, payload: typedefs.JSONObject, / 1871 ) -> activity.ActivityValues: 1872 team: int | None = None 1873 if raw_team := payload.get("team"): 1874 team = raw_team["basic"]["value"] 1875 return activity.ActivityValues( 1876 assists=payload["assists"]["basic"]["value"], 1877 deaths=payload["deaths"]["basic"]["value"], 1878 kills=payload["kills"]["basic"]["value"], 1879 is_completed=bool(payload["completed"]["basic"]["value"]), 1880 opponents_defeated=payload["opponentsDefeated"]["basic"]["value"], 1881 efficiency=payload["efficiency"]["basic"]["value"], 1882 kd_ratio=payload["killsDeathsRatio"]["basic"]["value"], 1883 kd_assists=payload["killsDeathsAssists"]["basic"]["value"], 1884 score=payload["score"]["basic"]["value"], 1885 duration=payload["activityDurationSeconds"]["basic"]["displayValue"], 1886 team=team, 1887 completion_reason=payload["completionReason"]["basic"]["displayValue"], 1888 fireteam_id=payload["fireteamId"]["basic"]["value"], 1889 start_seconds=payload["startSeconds"]["basic"]["value"], 1890 played_time=payload["timePlayedSeconds"]["basic"]["displayValue"], 1891 player_count=payload["playerCount"]["basic"]["value"], 1892 team_score=payload["teamScore"]["basic"]["value"], 1893 ) 1894 1895 def deserialize_activity( 1896 self, 1897 payload: typedefs.JSONObject, 1898 /, 1899 ) -> activity.Activity: 1900 period = time.clean_date(payload["period"]) 1901 details = payload["activityDetails"] 1902 ref_id = int(details["referenceId"]) 1903 instance_id = int(details["instanceId"]) 1904 mode = enums.GameMode(details["mode"]) 1905 modes = tuple(enums.GameMode(int(mode_)) for mode_ in details["modes"]) 1906 is_private = details["isPrivate"] 1907 membership_type = enums.MembershipType(int(details["membershipType"])) 1908 1909 # Since we're using the same fields for post activity method 1910 # this check is required since post activity doesn't values values 1911 values = self._deserialize_activity_values(payload["values"]) 1912 1913 return activity.Activity( 1914 hash=ref_id, 1915 instance_id=instance_id, 1916 mode=mode, 1917 modes=modes, 1918 is_private=is_private, 1919 membership_type=membership_type, 1920 occurred_at=period, 1921 values=values, 1922 ) 1923 1924 def deserialize_activities( 1925 self, payload: typedefs.JSONObject 1926 ) -> sain.Iterator[activity.Activity]: 1927 return sain.Iter( 1928 self.deserialize_activity(activity_) for activity_ in payload["activities"] 1929 ) 1930 1931 def deserialize_extended_weapon_values( 1932 self, payload: typedefs.JSONObject 1933 ) -> activity.ExtendedWeaponValues: 1934 assists: int | None = None 1935 if raw_assists := payload["values"].get("uniqueWeaponAssists"): 1936 assists = raw_assists["basic"]["value"] 1937 assists_damage: int | None = None 1938 1939 if raw_assists_damage := payload["values"].get("uniqueWeaponAssistDamage"): 1940 assists_damage = raw_assists_damage["basic"]["value"] 1941 1942 return activity.ExtendedWeaponValues( 1943 reference_id=int(payload["referenceId"]), 1944 kills=payload["values"]["uniqueWeaponKills"]["basic"]["value"], 1945 precision_kills=payload["values"]["uniqueWeaponPrecisionKills"]["basic"][ 1946 "value" 1947 ], 1948 assists=assists, 1949 assists_damage=assists_damage, 1950 precision_kills_percentage=( 1951 payload["values"]["uniqueWeaponKillsPrecisionKills"]["basic"]["value"], 1952 payload["values"]["uniqueWeaponKillsPrecisionKills"]["basic"][ 1953 "displayValue" 1954 ], 1955 ), 1956 ) 1957 1958 def _deserialize_extended_values( 1959 self, payload: typedefs.JSONObject 1960 ) -> activity.ExtendedValues: 1961 if raw_weapons := payload.get("weapons"): 1962 weapons = tuple( 1963 self.deserialize_extended_weapon_values(value) for value in raw_weapons 1964 ) 1965 else: 1966 weapons = () 1967 1968 return activity.ExtendedValues( 1969 precision_kills=payload["values"]["precisionKills"]["basic"]["value"], 1970 grenade_kills=payload["values"]["weaponKillsGrenade"]["basic"]["value"], 1971 melee_kills=payload["values"]["weaponKillsMelee"]["basic"]["value"], 1972 super_kills=payload["values"]["weaponKillsSuper"]["basic"]["value"], 1973 ability_kills=payload["values"]["weaponKillsAbility"]["basic"]["value"], 1974 weapons=weapons, 1975 ) 1976 1977 def deserialize_post_activity_player( 1978 self, payload: typedefs.JSONObject, / 1979 ) -> activity.PostActivityPlayer: 1980 player = payload["player"] 1981 1982 class_hash: int | None = None 1983 if (class_hash := player.get("classHash")) is not None: 1984 class_hash = class_hash 1985 1986 race_hash: int | None = None 1987 if (race_hash := player.get("raceHash")) is not None: 1988 race_hash = race_hash 1989 1990 gender_hash: int | None = None 1991 if (gender_hash := player.get("genderHash")) is not None: 1992 gender_hash = gender_hash 1993 1994 character_class: str | None = None 1995 if character_class := player.get("characterClass"): 1996 character_class = character_class 1997 1998 character_level: int | None = None 1999 if (character_level := player.get("characterLevel")) is not None: 2000 character_level = character_level 2001 2002 return activity.PostActivityPlayer( 2003 standing=int(payload["standing"]), 2004 score=int(payload["score"]["basic"]["value"]), 2005 character_id=payload["characterId"], 2006 destiny_user=self.deserialize_destiny_membership(player["destinyUserInfo"]), 2007 character_class=character_class, 2008 character_level=character_level, 2009 race_hash=race_hash, 2010 gender_hash=gender_hash, 2011 class_hash=class_hash, 2012 light_level=int(player["lightLevel"]), 2013 emblem_hash=int(player["emblemHash"]), 2014 values=self._deserialize_activity_values(payload["values"]), 2015 extended_values=self._deserialize_extended_values(payload["extended"]), 2016 ) 2017 2018 def _deserialize_post_activity_team( 2019 self, payload: typedefs.JSONObject 2020 ) -> activity.PostActivityTeam: 2021 return activity.PostActivityTeam( 2022 id=payload["teamId"], 2023 is_defeated=bool(payload["standing"]["basic"]["value"]), 2024 score=int(payload["score"]["basic"]["value"]), 2025 name=payload["teamName"], 2026 ) 2027 2028 def deserialize_post_activity( 2029 self, payload: typedefs.JSONObject 2030 ) -> activity.PostActivity: 2031 period = time.clean_date(payload["period"]) 2032 details = payload["activityDetails"] 2033 ref_id = int(details["referenceId"]) 2034 instance_id = int(details["instanceId"]) 2035 mode = enums.GameMode(details["mode"]) 2036 modes = tuple(enums.GameMode(int(mode_)) for mode_ in details["modes"]) 2037 is_private = details["isPrivate"] 2038 membership_type = enums.MembershipType(int(details["membershipType"])) 2039 return activity.PostActivity( 2040 hash=ref_id, 2041 membership_type=membership_type, 2042 instance_id=instance_id, 2043 mode=mode, 2044 modes=modes, 2045 is_private=is_private, 2046 occurred_at=period, 2047 starting_phase=int(payload["startingPhaseIndex"]), 2048 players=tuple( 2049 self.deserialize_post_activity_player(player) 2050 for player in payload["entries"] 2051 ), 2052 teams=tuple( 2053 self._deserialize_post_activity_team(team) for team in payload["teams"] 2054 ), 2055 ) 2056 2057 def _deserialize_aggregated_activity_values( 2058 self, payload: typedefs.JSONObject 2059 ) -> activity.AggregatedActivityValues: 2060 # This ID is always the same for all aggregated values. 2061 activity_id = int(payload["fastestCompletionMsForActivity"]["activityId"]) 2062 2063 return activity.AggregatedActivityValues( 2064 id=activity_id, 2065 fastest_completion_time=( 2066 int(payload["fastestCompletionMsForActivity"]["basic"]["value"]), 2067 payload["fastestCompletionMsForActivity"]["basic"]["displayValue"], 2068 ), 2069 completions=int(payload["activityCompletions"]["basic"]["value"]), 2070 kills=int(payload["activityKills"]["basic"]["value"]), 2071 deaths=int(payload["activityDeaths"]["basic"]["value"]), 2072 assists=int(payload["activityAssists"]["basic"]["value"]), 2073 seconds_played=( 2074 int(payload["activitySecondsPlayed"]["basic"]["value"]), 2075 payload["activitySecondsPlayed"]["basic"]["displayValue"], 2076 ), 2077 wins=int(payload["activityWins"]["basic"]["value"]), 2078 goals_missed=int(payload["activityGoalsMissed"]["basic"]["value"]), 2079 special_actions=int(payload["activitySpecialActions"]["basic"]["value"]), 2080 best_goals_hit=int(payload["activityBestGoalsHit"]["basic"]["value"]), 2081 best_single_score=int( 2082 payload["activityBestSingleGameScore"]["basic"]["value"] 2083 ), 2084 goals_hit=int(payload["activityGoalsHit"]["basic"]["value"]), 2085 special_score=int(payload["activitySpecialScore"]["basic"]["value"]), 2086 kd_assists=int(payload["activityKillsDeathsAssists"]["basic"]["value"]), 2087 kd_ratio=float( 2088 payload["activityKillsDeathsAssists"]["basic"]["displayValue"] 2089 ), 2090 precision_kills=int(payload["activityPrecisionKills"]["basic"]["value"]), 2091 ) 2092 2093 def deserialize_aggregated_activity( 2094 self, payload: typedefs.JSONObject 2095 ) -> activity.AggregatedActivity: 2096 return activity.AggregatedActivity( 2097 hash=int(payload["activityHash"]), 2098 values=self._deserialize_aggregated_activity_values(payload["values"]), 2099 ) 2100 2101 def deserialize_aggregated_activities( 2102 self, payload: typedefs.JSONObject 2103 ) -> sain.Iterator[activity.AggregatedActivity]: 2104 return sain.Iter( 2105 self.deserialize_aggregated_activity(activity) 2106 for activity in payload["activities"] 2107 ) 2108 2109 def deserialize_linked_profiles( 2110 self, payload: typedefs.JSONObject 2111 ) -> profile.LinkedProfile: 2112 bungie_user = self.deserialize_partial_bungie_user(payload["bnetMembership"]) 2113 2114 if raw_profile := payload.get("profiles"): 2115 profiles = tuple( 2116 self.deserialize_destiny_membership(p) for p in raw_profile 2117 ) 2118 else: 2119 profiles = () 2120 2121 error_profiles = () 2122 if raw_profiles_with_errors := payload.get("profilesWithErrors"): 2123 for raw_error_p in raw_profiles_with_errors: 2124 if "infoCard" in raw_error_p: 2125 error_profiles = tuple( 2126 self.deserialize_destiny_membership(error_p) 2127 for error_p in raw_error_p 2128 ) 2129 2130 return profile.LinkedProfile( 2131 bungie_user=bungie_user, 2132 profiles=profiles, 2133 profiles_with_errors=error_profiles, 2134 ) 2135 2136 def deserialize_clan_banners( 2137 self, payload: typedefs.JSONObject 2138 ) -> collections.Sequence[clans.ClanBanner]: 2139 if banners := payload.get("clanBannerDecals"): 2140 banner_obj = tuple( 2141 clans.ClanBanner( 2142 id=int(k), 2143 foreground=builders.Image(path=v["foregroundPath"]), 2144 background=builders.Image(path=v["backgroundPath"]), 2145 ) 2146 for k, v in banners.items() 2147 ) 2148 else: 2149 banner_obj = () 2150 return banner_obj 2151 2152 def deserialize_public_milestone_content( 2153 self, payload: typedefs.JSONObject 2154 ) -> milestones.MilestoneContent: 2155 if raw_categories := payload.get("itemCategories"): 2156 items_categories = tuple( 2157 milestones.MilestoneItems( 2158 title=item["title"], hashes=item["itemHashes"] 2159 ) 2160 for item in raw_categories 2161 ) 2162 else: 2163 items_categories = () 2164 2165 return milestones.MilestoneContent( 2166 about=typedefs.unknown(payload["about"]), 2167 status=typedefs.unknown(payload["status"]), 2168 tips=payload.get("tips", ()), 2169 items=items_categories, 2170 ) 2171 2172 def deserialize_friend(self, payload: typedefs.JSONObject, /) -> friends.Friend: 2173 bungie_user: user.BungieUser | None = None 2174 2175 if raw_bungie_user := payload.get("bungieNetUser"): 2176 bungie_user = self.deserialize_bungie_user(raw_bungie_user) 2177 2178 return friends.Friend( 2179 id=int(payload["lastSeenAsMembershipId"]), 2180 name=typedefs.unknown(payload.get("bungieGlobalDisplayName", "")), 2181 code=payload.get("bungieGlobalDisplayNameCode"), 2182 relationship=enums.Relationship(payload["relationship"]), 2183 user=bungie_user, 2184 online_status=enums.Presence(payload["onlineStatus"]), 2185 online_title=payload["onlineTitle"], 2186 type=enums.MembershipType(payload["lastSeenAsBungieMembershipType"]), 2187 ) 2188 2189 def deserialize_friends( 2190 self, payload: typedefs.JSONObject 2191 ) -> collections.Sequence[friends.Friend]: 2192 return tuple(self.deserialize_friend(friend) for friend in payload["friends"]) 2193 2194 def deserialize_friend_requests( 2195 self, payload: typedefs.JSONObject 2196 ) -> friends.FriendRequestView: 2197 if raw_incoming_requests := payload.get("incomingRequests"): 2198 incoming = tuple( 2199 self.deserialize_friend(incoming_request) 2200 for incoming_request in raw_incoming_requests 2201 ) 2202 else: 2203 incoming = () 2204 2205 if raw_outgoing_requests := payload.get("outgoingRequests"): 2206 outgoing = tuple( 2207 self.deserialize_friend(outgoing_request) 2208 for outgoing_request in raw_outgoing_requests 2209 ) 2210 else: 2211 outgoing = () 2212 2213 return friends.FriendRequestView(incoming=incoming, outgoing=outgoing) 2214 2215 def _set_fireteam_fields( 2216 self, payload: typedefs.JSONObject, total_results: int | None = None 2217 ) -> fireteams.Fireteam: 2218 activity_type = fireteams.FireteamActivity(payload["activityType"]) 2219 return fireteams.Fireteam( 2220 id=int(payload["fireteamId"]), 2221 group_id=int(payload["groupId"]), 2222 platform=fireteams.FireteamPlatform(payload["platform"]), 2223 is_immediate=payload["isImmediate"], 2224 activity_type=activity_type, 2225 owner_id=int(payload["ownerMembershipId"]), 2226 player_slot_count=payload["playerSlotCount"], 2227 available_player_slots=payload["availablePlayerSlotCount"], 2228 available_alternate_slots=payload["availableAlternateSlotCount"], 2229 title=payload["title"], 2230 date_created=time.clean_date(payload["dateCreated"]), 2231 is_public=payload["isPublic"], 2232 locale=fireteams.FireteamLanguage(payload["locale"]), 2233 is_valid=payload["isValid"], 2234 last_modified=time.clean_date(payload["datePlayerModified"]), 2235 date_modified=time.clean_date(payload["dateModified"]) 2236 if "dateModified" in payload 2237 else None, 2238 scheduled_time=time.clean_date(payload["scheduledTime"]) 2239 if "scheduledTime" in payload 2240 else None, 2241 total_results=total_results or 0, 2242 ) 2243 2244 def deserialize_fireteams( 2245 self, payload: typedefs.JSONObject 2246 ) -> collections.Sequence[fireteams.Fireteam]: 2247 if "results" in payload: 2248 fireteams_ = tuple( 2249 self._set_fireteam_fields( 2250 elem, total_results=int(payload["totalResults"]) 2251 ) 2252 for elem in payload["results"] 2253 ) 2254 else: 2255 fireteams_ = () 2256 return fireteams_ 2257 2258 def deserialize_fireteam_destiny_membership( 2259 self, payload: typedefs.JSONObject 2260 ) -> fireteams.FireteamUser: 2261 destiny_obj = self.deserialize_destiny_membership(payload) 2262 return fireteams.FireteamUser( 2263 id=destiny_obj.id, 2264 code=destiny_obj.code, 2265 icon=destiny_obj.icon, 2266 types=destiny_obj.types, 2267 type=destiny_obj.type, 2268 is_public=destiny_obj.is_public, 2269 crossave_override=destiny_obj.crossave_override, 2270 name=destiny_obj.name, 2271 last_seen_name=destiny_obj.last_seen_name, 2272 fireteam_display_name=payload["FireteamDisplayName"], 2273 fireteam_membership_id=enums.MembershipType( 2274 payload["FireteamMembershipType"] 2275 ), 2276 ) 2277 2278 def deserialize_fireteam_members( 2279 self, payload: typedefs.JSONObject, *, alternatives: bool = False 2280 ) -> collections.Sequence[fireteams.FireteamMember]: 2281 members_: list[fireteams.FireteamMember] = [] 2282 if members := payload.get("Members" if not alternatives else "Alternates"): 2283 for member in members: 2284 bungie_fields = self.deserialize_partial_bungie_user(member) 2285 members_fields = fireteams.FireteamMember( 2286 membership=self.deserialize_fireteam_destiny_membership(member), 2287 has_microphone=member["hasMicrophone"], 2288 character_id=int(member["characterId"]), 2289 date_joined=time.clean_date(member["dateJoined"]), 2290 last_platform_invite_date=time.clean_date( 2291 member["lastPlatformInviteAttemptDate"] 2292 ), 2293 last_platform_invite_result=int( 2294 member["lastPlatformInviteAttemptResult"] 2295 ), 2296 name=bungie_fields.name, 2297 id=bungie_fields.id, 2298 icon=bungie_fields.icon, 2299 is_public=bungie_fields.is_public, 2300 crossave_override=bungie_fields.crossave_override, 2301 types=bungie_fields.types, 2302 type=bungie_fields.type, 2303 code=bungie_fields.code, 2304 ) 2305 members_.append(members_fields) 2306 return tuple(members_) 2307 2308 def deserialize_available_fireteam( 2309 self, payload: typedefs.JSONObject 2310 ) -> fireteams.AvailableFireteam: 2311 fields = self._set_fireteam_fields(payload["Summary"]) 2312 return fireteams.AvailableFireteam( 2313 id=fields.id, 2314 group_id=fields.group_id, 2315 platform=fields.platform, 2316 activity_type=fields.activity_type, 2317 is_immediate=fields.is_immediate, 2318 is_public=fields.is_public, 2319 is_valid=fields.is_valid, 2320 owner_id=fields.owner_id, 2321 player_slot_count=fields.player_slot_count, 2322 available_player_slots=fields.available_player_slots, 2323 available_alternate_slots=fields.available_alternate_slots, 2324 title=fields.title, 2325 date_created=fields.date_created, 2326 locale=fields.locale, 2327 last_modified=fields.last_modified, 2328 total_results=fields.total_results, 2329 scheduled_time=fields.scheduled_time, 2330 date_modified=fields.date_modified, 2331 members=self.deserialize_fireteam_members(payload), 2332 alternatives=self.deserialize_fireteam_members(payload, alternatives=True), 2333 ) 2334 2335 def deserialize_available_fireteams( 2336 self, data: typedefs.JSONObject 2337 ) -> collections.Sequence[fireteams.AvailableFireteam]: 2338 if raw_results := data.get("results"): 2339 fireteam_results = tuple( 2340 self.deserialize_available_fireteam(f) for f in raw_results 2341 ) 2342 else: 2343 fireteam_results = () 2344 return fireteam_results 2345 2346 def deserialize_fireteam_party( 2347 self, payload: typedefs.JSONObject 2348 ) -> fireteams.FireteamParty: 2349 last_destination_hash: int | None = None 2350 if raw_dest_hash := payload.get("lastOrbitedDestinationHash"): 2351 last_destination_hash = int(raw_dest_hash) 2352 2353 return fireteams.FireteamParty( 2354 members=tuple( 2355 self._deserialize_fireteam_party_member(member) 2356 for member in payload["partyMembers"] 2357 ), 2358 activity=self._deserialize_fireteam_party_current_activity( 2359 payload["currentActivity"] 2360 ), 2361 settings=self._deserialize_fireteam_party_settings(payload["joinability"]), 2362 last_destination_hash=last_destination_hash, 2363 tracking=payload["tracking"], 2364 ) 2365 2366 def _deserialize_fireteam_party_member( 2367 self, payload: typedefs.JSONObject 2368 ) -> fireteams.FireteamPartyMember: 2369 status = fireteams.FireteamPartyMemberState(payload["status"]) 2370 2371 return fireteams.FireteamPartyMember( 2372 membership_id=int(payload["membershipId"]), 2373 emblem_hash=int(payload["emblemHash"]), 2374 status=status, 2375 display_name=payload["displayName"] if payload["displayName"] else None, 2376 ) 2377 2378 def _deserialize_fireteam_party_current_activity( 2379 self, payload: typedefs.JSONObject 2380 ) -> fireteams.FireteamPartyCurrentActivity: 2381 start_date: datetime.datetime | None = None 2382 if raw_start_date := payload.get("startTime"): 2383 start_date = time.clean_date(raw_start_date) 2384 2385 end_date: datetime.datetime | None = None 2386 if raw_end_date := payload.get("endTime"): 2387 end_date = time.clean_date(raw_end_date) 2388 return fireteams.FireteamPartyCurrentActivity( 2389 start_time=start_date, 2390 end_time=end_date, 2391 score=float(payload["score"]), 2392 highest_opposing_score=float(payload["highestOpposingFactionScore"]), 2393 opponents_count=int(payload["numberOfOpponents"]), 2394 player_count=int(payload["numberOfPlayers"]), 2395 ) 2396 2397 def _deserialize_fireteam_party_settings( 2398 self, payload: typedefs.JSONObject 2399 ) -> fireteams.FireteamPartySettings: 2400 closed_reasons = enums.ClosedReasons(payload["closedReasons"]) 2401 return fireteams.FireteamPartySettings( 2402 open_slots=int(payload["openSlots"]), 2403 privacy_setting=enums.PrivacySetting(int(payload["privacySetting"])), 2404 closed_reasons=closed_reasons, 2405 ) 2406 2407 def deserialize_seasonal_artifact( 2408 self, payload: typedefs.JSONObject 2409 ) -> season.Artifact: 2410 raw_artifact = payload["seasonalArtifact"] 2411 2412 points = raw_artifact["pointProgression"] 2413 points_prog = progressions.Progression( 2414 hash=points["progressionHash"], 2415 level=points["level"], 2416 cap=points["levelCap"], 2417 daily_limit=points["dailyLimit"], 2418 weekly_limit=points["weeklyLimit"], 2419 current_progress=points["currentProgress"], 2420 daily_progress=points["dailyProgress"], 2421 needed=points["progressToNextLevel"], 2422 next_level=points["nextLevelAt"], 2423 ) 2424 2425 bonus = raw_artifact["powerBonusProgression"] 2426 power_bonus_prog = progressions.Progression( 2427 hash=bonus["progressionHash"], 2428 level=bonus["level"], 2429 cap=bonus["levelCap"], 2430 daily_limit=bonus["dailyLimit"], 2431 weekly_limit=bonus["weeklyLimit"], 2432 current_progress=bonus["currentProgress"], 2433 daily_progress=bonus["dailyProgress"], 2434 needed=bonus["progressToNextLevel"], 2435 next_level=bonus["nextLevelAt"], 2436 ) 2437 return season.Artifact( 2438 hash=raw_artifact["artifactHash"], 2439 power_bonus=raw_artifact["powerBonus"], 2440 acquired_points=raw_artifact["pointsAcquired"], 2441 bonus=power_bonus_prog, 2442 points=points_prog, 2443 ) 2444 2445 def deserialize_profile_progression( 2446 self, payload: typedefs.JSONObject 2447 ) -> profile.ProfileProgression: 2448 return profile.ProfileProgression( 2449 artifact=self.deserialize_seasonal_artifact(payload["data"]), 2450 checklist={ 2451 int(check_id): checklists 2452 for check_id, checklists in payload["data"]["checklists"].items() 2453 }, 2454 ) 2455 2456 def deserialize_instanced_item( 2457 self, payload: typedefs.JSONObject 2458 ) -> items.ItemInstance: 2459 damage_type_hash: int | None = None 2460 if raw_damagetype_hash := payload.get("damageTypeHash"): 2461 damage_type_hash = int(raw_damagetype_hash) 2462 2463 required_hashes: typing.Optional[collections.Collection[int]] = None 2464 if raw_required_hashes := payload.get("unlockHashesRequiredToEquip"): 2465 required_hashes = tuple(int(raw_hash) for raw_hash in raw_required_hashes) 2466 2467 breaker_type: items.ItemBreakerType | None = None 2468 if raw_break_type := payload.get("breakerType"): 2469 breaker_type = items.ItemBreakerType(int(raw_break_type)) 2470 2471 breaker_type_hash: int | None = None 2472 if raw_break_type_hash := payload.get("breakerTypeHash"): 2473 breaker_type_hash = int(raw_break_type_hash) 2474 2475 energy: items.ItemEnergy | None = None 2476 if raw_energy := payload.get("energy"): 2477 energy = self.deserialize_item_energy(raw_energy) 2478 2479 primary_stats = None 2480 if raw_primary_stats := payload.get("primaryStat"): 2481 primary_stats = self.deserialize_item_stats_view(raw_primary_stats) 2482 2483 return items.ItemInstance( 2484 damage_type=enums.DamageType(int(payload["damageType"])), 2485 damage_type_hash=damage_type_hash, 2486 primary_stat=primary_stats, 2487 item_level=int(payload["itemLevel"]), 2488 quality=int(payload["quality"]), 2489 is_equipped=payload["isEquipped"], 2490 can_equip=payload["canEquip"], 2491 equip_required_level=int(payload["equipRequiredLevel"]), 2492 required_equip_unlock_hashes=required_hashes, 2493 cant_equip_reason=int(payload["cannotEquipReason"]), 2494 breaker_type=breaker_type, 2495 breaker_type_hash=breaker_type_hash, 2496 energy=energy, 2497 ) 2498 2499 def deserialize_item_energy(self, payload: typedefs.JSONObject) -> items.ItemEnergy: 2500 energy_hash: int | None = None 2501 if raw_energy_hash := payload.get("energyTypeHash"): 2502 energy_hash = int(raw_energy_hash) 2503 2504 return items.ItemEnergy( 2505 hash=energy_hash, 2506 type=items.ItemEnergyType(int(payload["energyType"])), 2507 capacity=int(payload["energyCapacity"]), 2508 used_energy=int(payload["energyUsed"]), 2509 unused_energy=int(payload["energyUnused"]), 2510 ) 2511 2512 def deserialize_item_perk(self, payload: typedefs.JSONObject) -> items.ItemPerk: 2513 perk_hash: int | None = None 2514 if raw_perk_hash := payload.get("perkHash"): 2515 perk_hash = int(raw_perk_hash) 2516 2517 return items.ItemPerk( 2518 hash=perk_hash, 2519 icon=builders.Image(path=payload["iconPath"]), 2520 is_active=payload["isActive"], 2521 is_visible=payload["visible"], 2522 ) 2523 2524 def deserialize_item_socket(self, payload: typedefs.JSONObject) -> items.ItemSocket: 2525 plug_hash: int | None = None 2526 if raw_plug_hash := payload.get("plugHash"): 2527 plug_hash = int(raw_plug_hash) 2528 2529 enable_fail_indexes: collections.Sequence[int] | None = None 2530 if raw_indexes := payload.get("enableFailIndexes"): 2531 enable_fail_indexes = tuple(int(index) for index in raw_indexes) 2532 2533 return items.ItemSocket( 2534 plug_hash=plug_hash, 2535 is_enabled=payload["isEnabled"], 2536 enable_fail_indexes=enable_fail_indexes, 2537 is_visible=payload.get("visible"), 2538 ) 2539 2540 def deserialize_item_stats_view( 2541 self, payload: typedefs.JSONObject 2542 ) -> items.ItemStatsView: 2543 return items.ItemStatsView( 2544 stat_hash=payload.get("statHash"), value=payload.get("value") 2545 ) 2546 2547 def deserialize_plug_item_state( 2548 self, payload: typedefs.JSONObject 2549 ) -> items.PlugItemState: 2550 item_hash: int | None = None 2551 if raw_item_hash := payload.get("plugItemHash"): 2552 item_hash = int(raw_item_hash) 2553 2554 insert_fail_indexes: collections.Sequence[int] | None = None 2555 if raw_fail_indexes := payload.get("insertFailIndexes"): 2556 insert_fail_indexes = tuple(int(k) for k in raw_fail_indexes) 2557 2558 enable_fail_indexes: collections.Sequence[int] | None = None 2559 if raw_enabled_indexes := payload.get("enableFailIndexes"): 2560 enable_fail_indexes = tuple(int(k) for k in raw_enabled_indexes) 2561 2562 return items.PlugItemState( 2563 item_hash=item_hash, 2564 insert_fail_indexes=insert_fail_indexes, 2565 enable_fail_indexes=enable_fail_indexes, 2566 is_enabled=payload["enabled"], 2567 can_insert=payload["canInsert"], 2568 ) 2569 2570 2571Global = Framework() 2572"""The global framework instance. 2573 2574This type is global constant that can be used to deserialize payloads 2575without the need to initialize a new instance of the framework. 2576"""
64class Framework(api.Framework): 65 """The base deserialization framework implementation. 66 67 This framework is used to deserialize JSON responses from the REST client and turning them into a `aiobungie.crates` Python classes. 68 69 Example 70 -------- 71 ```py 72 import aiobungie 73 74 from aiobungie import traits 75 from aiobungie import framework 76 77 class MyClient(traits.Deserialize): 78 def __init__(self) -> None: 79 self.rest = aiobungie.RESTClient("token") 80 self.my_name = "Fate怒" 81 self.my_code = 4275 82 83 @property # required method. 84 def framework(self) -> framework.Empty: 85 return framework.Global 86 87 async def my_memberships(self): 88 response = await self.rest.fetch_membership(self.my_name, self.my_code) 89 return self.factory.deserialize_destiny_memberships(response) 90 91 async def main() -> None: 92 client = MyClient() 93 async with client.rest: 94 print(await client.my_memberships()) 95 96 asyncio.run(main()) 97 ``` 98 """ 99 100 __slots__ = () 101 102 def __init__(self) -> None: 103 super().__init__() 104 105 def deserialize_bungie_user(self, data: typedefs.JSONObject) -> user.BungieUser: 106 return user.BungieUser( 107 id=int(data["membershipId"]), 108 created_at=time.clean_date(data["firstAccess"]), 109 name=data.get("cachedBungieGlobalDisplayName"), 110 is_deleted=data["isDeleted"], 111 about=data["about"], 112 updated_at=time.clean_date(data["lastUpdate"]), 113 psn_name=data.get("psnDisplayName", None), 114 stadia_name=data.get("stadiaDisplayName", None), 115 steam_name=data.get("steamDisplayName", None), 116 twitch_name=data.get("twitchDisplayName", None), 117 blizzard_name=data.get("blizzardDisplayName", None), 118 egs_name=data.get("egsDisplayName", None), 119 status=data["statusText"], 120 locale=data["locale"], 121 picture=builders.Image(path=data["profilePicturePath"]), 122 code=data.get("cachedBungieGlobalDisplayNameCode", None), 123 unique_name=data.get("uniqueName", None), 124 theme_id=int(data["profileTheme"]), 125 show_activity=bool(data["showActivity"]), 126 theme_name=data["profileThemeName"], 127 display_title=data["userTitleDisplay"], 128 profile_ban_expire=time.clean_date(data["profileBanExpire"]) 129 if "profileBanExpire" in data 130 else None, 131 ) 132 133 def deserialize_partial_bungie_user( 134 self, payload: typedefs.JSONObject 135 ) -> user.PartialBungieUser: 136 return user.PartialBungieUser( 137 types=tuple( 138 enums.MembershipType(type_) 139 for type_ in payload.get("applicableMembershipTypes", ()) 140 ), 141 name=payload["bungieGlobalDisplayName"] 142 if "bungieGlobalDisplayName" in payload 143 else payload.get("displayName"), 144 id=int(payload["membershipId"]), 145 crossave_override=enums.MembershipType(payload["crossSaveOverride"]), 146 is_public=payload["isPublic"], 147 icon=builders.Image(path=payload.get("iconPath", "")), 148 type=enums.MembershipType(payload["membershipType"]), 149 code=payload.get("bungieGlobalDisplayNameCode"), 150 ) 151 152 def deserialize_destiny_membership( 153 self, payload: typedefs.JSONObject 154 ) -> user.DestinyMembership: 155 name: str | None = None 156 if (raw_name := payload.get("bungieGlobalDisplayName")) is not None: 157 name = typedefs.unknown(raw_name) 158 159 return user.DestinyMembership( 160 id=int(payload["membershipId"]), 161 name=name, 162 code=payload.get("bungieGlobalDisplayNameCode", None), 163 last_seen_name=payload.get("LastSeenDisplayName") 164 or payload.get("displayName") # noqa: W503 165 or "", # noqa: W503 166 type=enums.MembershipType(payload["membershipType"]), 167 is_public=payload["isPublic"], 168 crossave_override=enums.MembershipType(payload["crossSaveOverride"]), 169 icon=builders.Image(path=payload.get("iconPath", "")), 170 types=tuple( 171 enums.MembershipType(type_) 172 for type_ in payload.get("applicableMembershipTypes", ()) 173 ), 174 ) 175 176 def deserialize_destiny_memberships( 177 self, data: typedefs.JSONArray 178 ) -> collections.Sequence[user.DestinyMembership]: 179 return tuple( 180 self.deserialize_destiny_membership(membership) for membership in data 181 ) 182 183 def deserialize_user(self, data: typedefs.JSONObject) -> user.User: 184 primary_membership_id: int | None = None 185 if raw_primary_id := data.get("primaryMembershipId"): 186 primary_membership_id = int(raw_primary_id) 187 188 return user.User( 189 bungie_user=self.deserialize_bungie_user(data["bungieNetUser"]), 190 memberships=self.deserialize_destiny_memberships( 191 data["destinyMemberships"] 192 ), 193 primary_membership_id=primary_membership_id, 194 ) 195 196 def deserialize_sanitized_membership( 197 self, payload: typedefs.JSONObject 198 ) -> user.SanitizedMembership: 199 return user.SanitizedMembership( 200 epic_games=payload.get("EgsId"), 201 psn=payload.get("PsnId"), 202 steam=payload.get("SteamId"), 203 xbox=payload.get("XboxId"), 204 twitch=payload.get("TwitchId"), 205 ) 206 207 def deserialize_searched_user( 208 self, payload: typedefs.JSONObject 209 ) -> user.SearchableDestinyUser: 210 code: int | None = None 211 if raw_code := payload.get("bungieGlobalDisplayNameCode"): 212 code = int(raw_code) 213 214 bungie_id: int | None = None 215 if raw_bungie_id := payload.get("bungieNetMembershipId"): 216 bungie_id = int(raw_bungie_id) 217 218 return user.SearchableDestinyUser( 219 name=typedefs.unknown(payload["bungieGlobalDisplayName"]), 220 code=code, 221 bungie_id=bungie_id, 222 memberships=self.deserialize_destiny_memberships( 223 payload["destinyMemberships"] 224 ), 225 ) 226 227 def deserialize_user_credentials( 228 self, payload: typedefs.JSONArray 229 ) -> collections.Sequence[user.UserCredentials]: 230 return tuple( 231 user.UserCredentials( 232 type=enums.CredentialType(int(credit["credentialType"])), 233 display_name=credit["credentialDisplayName"], 234 is_public=credit["isPublic"], 235 self_as_string=credit.get("credentialAsString"), 236 ) 237 for credit in payload 238 ) 239 240 def deserialize_user_themes( 241 self, payload: typedefs.JSONArray 242 ) -> collections.Sequence[user.UserThemes]: 243 return tuple( 244 user.UserThemes( 245 id=int(entry["userThemeId"]), 246 name=entry["userThemeName"] if "userThemeName" in entry else None, 247 description=entry["userThemeDescription"] 248 if "userThemeDescription" in entry 249 else None, 250 ) 251 for entry in payload 252 ) 253 254 def deserialize_group(self, payload: typedefs.JSONObject) -> clans.Group: 255 clan_info_obj: typedefs.JSONObject | None = None 256 if raw_clan_info := payload.get("clanInfo"): 257 clan_info_obj = clans.ClanInfo( 258 call_sign=raw_clan_info["clanCallsign"], 259 banner_data=raw_clan_info["clanBannerData"], 260 ) 261 return clans.Group( 262 group_id=int(payload["groupId"]), 263 name=payload["name"], 264 creation_date=time.clean_date(payload["creationDate"]), 265 about=payload["about"], 266 clan_info=clan_info_obj, 267 motto=payload.get("motto"), 268 member_count=int(payload["memberCount"]), 269 locale=payload["locale"], 270 membership_option=int(payload["membershipOption"]), 271 capabilities=int(payload["capabilities"]), 272 remote_group_id=int(payload["remoteGroupId"]) 273 if "remoteGroupId" in payload 274 else None, 275 group_type=enums.GroupType(int(payload["groupType"])), 276 avatar_path=builders.Image(payload["avatarPath"]), 277 theme=payload["theme"], 278 ) 279 280 def _deserialize_group_details( 281 self, 282 data: typedefs.JSONObject, 283 current_user_memberships: collections.Mapping[str, clans.ClanMember] 284 | None = None, 285 clan_founder: clans.ClanMember | None = None, 286 ) -> clans.Clan: 287 features = data["features"] 288 features_obj = clans.ClanFeatures( 289 max_members=features["maximumMembers"], 290 max_membership_types=features["maximumMembershipsOfGroupType"], 291 capabilities=features["capabilities"], 292 membership_types=features["membershipTypes"], 293 invite_permissions=features["invitePermissionOverride"], 294 update_banner_permissions=features["updateBannerPermissionOverride"], 295 update_culture_permissions=features["updateCulturePermissionOverride"], 296 join_level=features["joinLevel"], 297 ) 298 information: typedefs.JSONObject = data["clanInfo"] 299 progression: collections.Mapping[int, progressions.Progression] = { 300 int(prog_hash): self.deserialize_progressions(prog) 301 for prog_hash, prog in information["d2ClanProgressions"].items() 302 } 303 304 return clans.Clan( 305 id=int(data["groupId"]), 306 name=data["name"], 307 type=enums.GroupType(data["groupType"]), 308 created_at=time.clean_date(data["creationDate"]), 309 member_count=data["memberCount"], 310 motto=data["motto"], 311 about=data["about"], 312 is_public=data["isPublic"], 313 banner=builders.Image(path=data["bannerPath"]), 314 avatar=builders.Image(path=data["avatarPath"]), 315 tags=tuple(data["tags"]), 316 features=features_obj, 317 owner=clan_founder, 318 progressions=progression, 319 call_sign=information["clanCallsign"], 320 banner_data=information["clanBannerData"], 321 chat_security=data["chatSecurity"], 322 conversation_id=int(data["conversationId"]), 323 allow_chat=data["allowChat"], 324 theme=data["theme"], 325 current_user_membership=current_user_memberships, 326 ) 327 328 def deserialize_clan(self, payload: typedefs.JSONObject) -> clans.Clan: 329 current_user_map: collections.Mapping[str, clans.ClanMember] | None = None 330 if raw_current_user := payload.get("currentUserMemberMap"): 331 # This will get populated if only it was a GroupsV2.GroupResponse. 332 # GroupsV2.GetGroupsForMemberResponse doesn't have this field. 333 current_user_map = { 334 membership_type: self.deserialize_clan_member(membership) 335 for membership_type, membership in raw_current_user.items() 336 } 337 338 return self._deserialize_group_details( 339 data=payload["detail"], 340 clan_founder=self.deserialize_clan_member(payload["founder"]), 341 current_user_memberships=current_user_map, 342 ) 343 344 def deserialize_clan_member(self, data: typedefs.JSONObject, /) -> clans.ClanMember: 345 destiny_user = self.deserialize_destiny_membership(data["destinyUserInfo"]) 346 return clans.ClanMember( 347 last_seen_name=destiny_user.last_seen_name, 348 id=destiny_user.id, 349 name=destiny_user.name, 350 icon=destiny_user.icon, 351 last_online=time.from_timestamp(int(data["lastOnlineStatusChange"])), 352 group_id=int(data["groupId"]), 353 joined_at=time.clean_date(data["joinDate"]), 354 types=destiny_user.types, 355 is_public=destiny_user.is_public, 356 type=destiny_user.type, 357 code=destiny_user.code, 358 is_online=data["isOnline"], 359 crossave_override=destiny_user.crossave_override, 360 bungie_user=self.deserialize_partial_bungie_user(data["bungieNetUserInfo"]) 361 if "bungieNetUserInfo" in data 362 else None, 363 member_type=enums.ClanMemberType(int(data["memberType"])), 364 ) 365 366 def deserialize_clan_members( 367 self, data: typedefs.JSONObject, / 368 ) -> sain.Iterator[clans.ClanMember]: 369 return sain.Iter( 370 self.deserialize_clan_member(member) for member in data["results"] 371 ) 372 373 def deserialize_group_member( 374 self, payload: typedefs.JSONObject 375 ) -> clans.GroupMember: 376 member = payload["member"] 377 return clans.GroupMember( 378 join_date=time.clean_date(member["joinDate"]), 379 group_id=int(member["groupId"]), 380 member_type=enums.ClanMemberType(member["memberType"]), 381 is_online=member["isOnline"], 382 last_online=time.from_timestamp(int(member["lastOnlineStatusChange"])), 383 inactive_memberships=payload.get("areAllMembershipsInactive", None), 384 member=self.deserialize_destiny_membership(member["destinyUserInfo"]), 385 group=self._deserialize_group_details(payload["group"]), 386 ) 387 388 def _deserialize_clan_conversation( 389 self, payload: typedefs.JSONObject 390 ) -> clans.ClanConversation: 391 return clans.ClanConversation( 392 id=int(payload["conversationId"]), 393 group_id=int(payload["groupId"]), 394 name=typedefs.unknown(payload["chatName"]), 395 chat_enabled=payload["chatEnabled"], 396 security=payload["chatSecurity"], 397 ) 398 399 def deserialize_clan_conversations( 400 self, payload: typedefs.JSONArray 401 ) -> collections.Sequence[clans.ClanConversation]: 402 return tuple(self._deserialize_clan_conversation(conv) for conv in payload) 403 404 def deserialize_application_member( 405 self, payload: typedefs.JSONObject 406 ) -> application.ApplicationMember: 407 return application.ApplicationMember( 408 api_eula_version=payload["apiEulaVersion"], 409 role=payload["role"], 410 user=self.deserialize_partial_bungie_user(payload["user"]), 411 ) 412 413 def deserialize_application( 414 self, payload: typedefs.JSONObject 415 ) -> application.Application: 416 return application.Application( 417 id=int(payload["applicationId"]), 418 name=payload["name"], 419 origin=payload["origin"], 420 link=payload["link"], 421 status=payload["status"], 422 redirect_url=payload.get("redirectUrl", None), 423 created_at=time.clean_date(payload["creationDate"]), 424 status_changed=time.clean_date(payload["statusChanged"]), 425 published_at=time.clean_date(payload["firstPublished"]), 426 team=tuple( 427 self.deserialize_application_member(member) 428 for member in payload["team"] 429 ), 430 scope=payload.get("scope"), 431 ) 432 433 def _set_character_attrs(self, payload: typedefs.JSONObject) -> character.Character: 434 if raw_emblem_color := payload.get("emblemColor"): 435 emblem_color: tuple[int, int, int, int] = ( 436 int(raw_emblem_color["red"]), 437 int(raw_emblem_color["green"]), 438 int(raw_emblem_color["blue"]), 439 int(raw_emblem_color["alpha"]), 440 ) 441 else: 442 emblem_color = (0, 0, 0, 0) 443 444 return character.Character( 445 id=int(payload["characterId"]), 446 gender=enums.Gender(payload["genderType"]), 447 race=enums.Race(payload["raceType"]), 448 class_type=enums.Class(payload["classType"]), 449 emblem=builders.Image(path=payload["emblemBackgroundPath"]) 450 if "emblemBackgroundPath" in payload 451 else None, 452 emblem_icon=builders.Image(path=payload["emblemPath"]) 453 if "emblemPath" in payload 454 else None, 455 emblem_hash=int(payload["emblemHash"]) if "emblemHash" in payload else None, 456 last_played=time.clean_date(payload["dateLastPlayed"]), 457 total_played_time=int(payload["minutesPlayedTotal"]), 458 member_id=int(payload["membershipId"]), 459 member_type=enums.MembershipType(payload["membershipType"]), 460 level=payload["baseCharacterLevel"], 461 title_hash=payload.get("titleRecordHash", None), 462 light=payload["light"], 463 stats={enums.Stat(int(k)): v for k, v in payload["stats"].items()}, 464 emblem_color=emblem_color, 465 minutes_played_this_session=int(payload["minutesPlayedThisSession"]) 466 if "minutesPlayedThisSession" in payload 467 else 0, 468 percent_to_next_level=float(payload["percentToNextLevel"]), 469 ) 470 471 def deserialize_profile(self, payload: typedefs.JSONObject, /) -> profile.Profile: 472 return profile.Profile( 473 user=self.deserialize_destiny_membership(payload["userInfo"]), 474 last_played=time.clean_date(payload["dateLastPlayed"]), 475 versions_owned=enums.GameVersions(int(payload["versionsOwned"])), 476 character_ids=tuple( 477 int(character_id) for character_id in payload["characterIds"] 478 ), 479 season_hashes=tuple(payload["seasonHashes"]), 480 event_card_hashes=tuple(payload["eventCardHashesOwned"]), 481 season_hash=payload["currentSeasonHash"], 482 power_cap=payload["currentSeasonRewardPowerCap"], 483 guardian_rank=payload["currentGuardianRank"], 484 highest_guardian_rank=payload["lifetimeHighestGuardianRank"], 485 renewed_guardian_rank=payload["renewedGuardianRank"], 486 ) 487 488 def deserialize_profile_item( 489 self, payload: typedefs.JSONObject 490 ) -> profile.ProfileItemImpl: 491 instance_id: int | None = None 492 if raw_instance_id := payload.get("itemInstanceId"): 493 instance_id = int(raw_instance_id) 494 495 version_number: int | None = None 496 if raw_version := payload.get("versionNumber"): 497 version_number = int(raw_version) 498 499 transfer_status = enums.TransferStatus(payload["transferStatus"]) 500 501 return profile.ProfileItemImpl( 502 hash=payload["itemHash"], 503 quantity=payload["quantity"], 504 bind_status=enums.ItemBindStatus(payload["bindStatus"]), 505 location=enums.ItemLocation(payload["location"]), 506 bucket=payload["bucketHash"], 507 transfer_status=transfer_status, 508 lockable=payload["lockable"], 509 state=enums.ItemState(payload["state"]), 510 dismantle_permissions=payload["dismantlePermission"], 511 is_wrapper=payload["isWrapper"], 512 instance_id=instance_id, 513 version_number=version_number, 514 ornament_id=payload.get("overrideStyleItemHash"), 515 ) 516 517 def deserialize_objectives(self, payload: typedefs.JSONObject) -> records.Objective: 518 return records.Objective( 519 hash=payload["objectiveHash"], 520 visible=payload["visible"], 521 complete=payload["complete"], 522 completion_value=payload["completionValue"], 523 progress=payload.get("progress"), 524 destination_hash=payload.get("destinationHash"), 525 activity_hash=payload.get("activityHash"), 526 ) 527 528 # TODO: Remove **nodes and get it directly from the payload. 529 def deserialize_records( 530 self, 531 payload: typedefs.JSONObject, 532 scores: records.RecordScores | None = None, 533 **nodes: int, 534 ) -> records.Record: 535 objectives: collections.Sequence[records.Objective] | None = None 536 interval_objectives: collections.Sequence[records.Objective] | None = None 537 record_state: records.RecordState | int 538 539 record_state = records.RecordState(payload["state"]) 540 541 if raw_objs := payload.get("objectives"): 542 objectives = tuple(self.deserialize_objectives(obj) for obj in raw_objs) 543 544 if raw_interval_objs := payload.get("intervalObjectives"): 545 interval_objectives = tuple( 546 self.deserialize_objectives(obj) for obj in raw_interval_objs 547 ) 548 549 return records.Record( 550 scores=scores, 551 categories_node_hash=nodes.get("categories_hash"), 552 seals_node_hash=nodes.get("seals_hash"), 553 state=record_state, 554 objectives=objectives, 555 interval_objectives=interval_objectives, 556 redeemed_count=payload.get("intervalsRedeemedCount", 0), 557 completion_times=payload.get("completedCount", None), 558 reward_visibility=payload.get("rewardVisibility"), 559 ) 560 561 def deserialize_character_records( 562 self, 563 payload: typedefs.JSONObject, 564 scores: records.RecordScores | None = None, 565 record_hashes: collections.Sequence[int] = (), 566 ) -> records.CharacterRecord: 567 record = self.deserialize_records(payload, scores) 568 return records.CharacterRecord( 569 scores=scores, 570 categories_node_hash=record.categories_node_hash, 571 seals_node_hash=record.seals_node_hash, 572 state=record.state, 573 objectives=record.objectives, 574 interval_objectives=record.interval_objectives, 575 redeemed_count=payload.get("intervalsRedeemedCount", 0), 576 completion_times=payload.get("completedCount"), 577 reward_visibility=payload.get("rewardVisibility"), 578 record_hashes=record_hashes, 579 ) 580 581 def deserialize_character_dye(self, payload: typedefs.JSONObject) -> character.Dye: 582 return character.Dye( 583 channel_hash=payload["channelHash"], dye_hash=payload["dyeHash"] 584 ) 585 586 def deserialize_character_customization( 587 self, payload: typedefs.JSONObject 588 ) -> character.CustomizationOptions: 589 return character.CustomizationOptions( 590 personality=payload["personality"], 591 face=payload["face"], 592 skin_color=payload["skinColor"], 593 lip_color=payload["lipColor"], 594 eye_color=payload["eyeColor"], 595 hair_colors=payload.get("hairColors", ()), 596 feature_colors=payload.get("featureColors", ()), 597 decal_color=payload["decalColor"], 598 wear_helmet=payload["wearHelmet"], 599 hair_index=payload["hairIndex"], 600 feature_index=payload["featureIndex"], 601 decal_index=payload["decalIndex"], 602 ) 603 604 def deserialize_character_minimal_equipments( 605 self, payload: typedefs.JSONObject 606 ) -> character.MinimalEquipments: 607 if raw_dyes := payload.get("dyes"): 608 dyes = tuple(self.deserialize_character_dye(dye) for dye in raw_dyes) 609 else: 610 dyes = () 611 612 return character.MinimalEquipments(item_hash=payload["itemHash"], dyes=dyes) 613 614 def deserialize_character_render_data( 615 self, payload: typedefs.JSONObject, / 616 ) -> character.RenderedData: 617 return character.RenderedData( 618 customization=self.deserialize_character_customization( 619 payload["customization"] 620 ), 621 custom_dyes=tuple( 622 self.deserialize_character_dye(dye) 623 for dye in payload["customDyes"] 624 if dye 625 ), 626 equipment=tuple( 627 self.deserialize_character_minimal_equipments(equipment) 628 for equipment in payload["peerView"]["equipment"] 629 ), 630 ) 631 632 def deserialize_available_activity( 633 self, payload: typedefs.JSONObject 634 ) -> activity.AvailableActivity: 635 return activity.AvailableActivity( 636 hash=payload["activityHash"], 637 is_new=payload["isNew"], 638 is_completed=payload["isCompleted"], 639 is_visible=payload["isVisible"], 640 display_level=payload.get("displayLevel"), 641 recommended_light=payload.get("recommendedLight"), 642 difficulty=activity.Difficulty(payload["difficultyTier"]), 643 can_join=payload["canJoin"], 644 can_lead=payload["canLead"], 645 ) 646 647 def deserialize_character_activity( 648 self, payload: typedefs.JSONObject 649 ) -> activity.CharacterActivity: 650 current_mode = enums.GameMode.NONE 651 if raw_current_mode := payload.get("currentActivityModeType"): 652 current_mode = enums.GameMode(raw_current_mode) 653 654 if raw_current_modes := payload.get("currentActivityModeTypes"): 655 current_mode_types = tuple( 656 enums.GameMode(type_) for type_ in raw_current_modes 657 ) 658 else: 659 current_mode_types = () 660 661 return activity.CharacterActivity( 662 date_started=time.clean_date(payload["dateActivityStarted"]), 663 current_hash=payload["currentActivityHash"], 664 current_mode_hash=payload["currentActivityModeHash"], 665 current_mode=current_mode, 666 current_mode_hashes=payload.get("currentActivityModeHashes", ()), 667 current_mode_types=current_mode_types, 668 current_playlist_hash=payload.get("currentPlaylistActivityHash"), 669 last_story_hash=payload["lastCompletedStoryHash"], 670 available_activities=tuple( 671 self.deserialize_available_activity(activity_) 672 for activity_ in payload["availableActivities"] 673 ), 674 available_activity_interactables=tuple( 675 activity.InteractablesRef( 676 hash=interact["activityInteractableHash"], 677 element_index=interact["activityInteractableElementIndex"], 678 ) 679 for interact in payload["availableActivityInteractables"] 680 ), 681 ) 682 683 def deserialize_profile_items( 684 self, payload: typedefs.JSONObject, / 685 ) -> collections.Sequence[profile.ProfileItemImpl]: 686 return tuple(self.deserialize_profile_item(item) for item in payload["items"]) 687 688 def _deserialize_node(self, payload: typedefs.JSONObject) -> records.Node: 689 return records.Node( 690 state=int(payload["state"]), 691 objective=self.deserialize_objectives(payload["objective"]) 692 if "objective" in payload 693 else None, 694 progress_value=int(payload["progressValue"]), 695 completion_value=int(payload["completionValue"]), 696 record_category_score=int(payload["recordCategoryScore"]) 697 if "recordCategoryScore" in payload 698 else None, 699 ) 700 701 @staticmethod 702 def _deserialize_collectible(payload: typedefs.JSONObject) -> items.Collectible: 703 recent_collectibles: collections.Collection[int] | None = None 704 if raw_recent_collectibles := payload.get("recentCollectibleHashes"): 705 recent_collectibles = tuple( 706 int(item_hash) for item_hash in raw_recent_collectibles 707 ) 708 709 collectibles: dict[int, int] = {} 710 for item_hash, mapping in payload["collectibles"].items(): 711 collectibles[int(item_hash)] = int(mapping["state"]) 712 713 return items.Collectible( 714 recent_collectibles=recent_collectibles, 715 collectibles=collectibles, 716 collection_category_hash=int(payload["collectionCategoriesRootNodeHash"]), 717 collection_badges_hash=int(payload["collectionBadgesRootNodeHash"]), 718 ) 719 720 @staticmethod 721 def _deserialize_currencies( 722 payload: typedefs.JSONObject, 723 ) -> collections.Sequence[items.Currency]: 724 return tuple( 725 items.Currency(hash=int(item_hash), amount=int(amount)) 726 for item_hash, amount in payload["itemQuantities"].items() 727 ) 728 729 def deserialize_progressions( 730 self, payload: typedefs.JSONObject 731 ) -> progressions.Progression: 732 return progressions.Progression( 733 hash=int(payload["progressionHash"]), 734 level=int(payload["level"]), 735 cap=int(payload["levelCap"]), 736 daily_limit=int(payload["dailyLimit"]), 737 weekly_limit=int(payload["weeklyLimit"]), 738 current_progress=int(payload["currentProgress"]), 739 daily_progress=int(payload["dailyProgress"]), 740 needed=int(payload["progressToNextLevel"]), 741 next_level=int(payload["nextLevelAt"]), 742 ) 743 744 def _deserialize_factions( 745 self, payload: typedefs.JSONObject 746 ) -> progressions.Factions: 747 progs = self.deserialize_progressions(payload) 748 return progressions.Factions( 749 hash=progs.hash, 750 level=progs.level, 751 cap=progs.cap, 752 daily_limit=progs.daily_limit, 753 weekly_limit=progs.weekly_limit, 754 current_progress=progs.current_progress, 755 daily_progress=progs.daily_progress, 756 needed=progs.needed, 757 next_level=progs.next_level, 758 faction_hash=payload["factionHash"], 759 faction_vendor_hash=payload["factionVendorIndex"], 760 ) 761 762 def _deserialize_milestone_available_quest( 763 self, payload: typedefs.JSONObject 764 ) -> milestones.MilestoneQuest: 765 return milestones.MilestoneQuest( 766 item_hash=payload["questItemHash"], 767 status=self._deserialize_milestone_quest_status(payload["status"]), 768 ) 769 770 def _deserialize_milestone_activity( 771 self, payload: typedefs.JSONObject 772 ) -> milestones.MilestoneActivity: 773 phases: collections.Sequence[milestones.MilestoneActivityPhase] | None = None 774 if raw_phases := payload.get("phases"): 775 phases = tuple( 776 milestones.MilestoneActivityPhase( 777 is_completed=obj["complete"], hash=obj["phaseHash"] 778 ) 779 for obj in raw_phases 780 ) 781 782 return milestones.MilestoneActivity( 783 hash=payload["activityHash"], 784 challenges=tuple( 785 self.deserialize_objectives(obj["objective"]) 786 for obj in payload["challenges"] 787 ), 788 modifier_hashes=payload.get("modifierHashes"), 789 boolean_options=payload.get("booleanActivityOptions"), 790 phases=phases, 791 ) 792 793 def _deserialize_milestone_quest_status( 794 self, payload: typedefs.JSONObject 795 ) -> milestones.QuestStatus: 796 return milestones.QuestStatus( 797 quest_hash=payload["questHash"], 798 step_hash=payload["stepHash"], 799 step_objectives=tuple( 800 self.deserialize_objectives(objective) 801 for objective in payload["stepObjectives"] 802 ), 803 is_tracked=payload["tracked"], 804 is_completed=payload["completed"], 805 started=payload["started"], 806 item_instance_id=payload["itemInstanceId"], 807 vendor_hash=payload.get("vendorHash"), 808 is_redeemed=payload["redeemed"], 809 ) 810 811 def _deserialize_milestone_rewards( 812 self, payload: typedefs.JSONObject 813 ) -> milestones.MilestoneReward: 814 return milestones.MilestoneReward( 815 category_hash=payload["rewardCategoryHash"], 816 entries=tuple( 817 milestones.MilestoneRewardEntry( 818 entry_hash=entry["rewardEntryHash"], 819 is_earned=entry["earned"], 820 is_redeemed=entry["redeemed"], 821 ) 822 for entry in payload["entries"] 823 ), 824 ) 825 826 def deserialize_milestone( 827 self, payload: typedefs.JSONObject 828 ) -> milestones.Milestone: 829 start_date: datetime.datetime | None = None 830 if raw_start_date := payload.get("startDate"): 831 start_date = time.clean_date(raw_start_date) 832 833 end_date: datetime.datetime | None = None 834 if raw_end_date := payload.get("endDate"): 835 end_date = time.clean_date(raw_end_date) 836 837 rewards: collections.Collection[milestones.MilestoneReward] | None = None 838 if raw_rewards := payload.get("rewards"): 839 rewards = tuple( 840 self._deserialize_milestone_rewards(reward) for reward in raw_rewards 841 ) 842 843 activities: collections.Sequence[milestones.MilestoneActivity] | None = None 844 if raw_activities := payload.get("activities"): 845 activities = tuple( 846 self._deserialize_milestone_activity(active) 847 for active in raw_activities 848 ) 849 850 quests: collections.Sequence[milestones.MilestoneQuest] | None = None 851 if raw_quests := payload.get("availableQuests"): 852 quests = tuple( 853 self._deserialize_milestone_available_quest(quest) 854 for quest in raw_quests 855 ) 856 857 vendors: collections.Sequence[milestones.MilestoneVendor] | None = None 858 if raw_vendors := payload.get("vendors"): 859 vendors = tuple( 860 milestones.MilestoneVendor( 861 vendor_hash=vendor["vendorHash"], 862 preview_itemhash=vendor.get("previewItemHash"), 863 ) 864 for vendor in raw_vendors 865 ) 866 867 return milestones.Milestone( 868 hash=payload["milestoneHash"], 869 start_date=start_date, 870 end_date=end_date, 871 order=payload["order"], 872 rewards=rewards, 873 available_quests=quests, 874 activities=activities, 875 vendors=vendors, 876 ) 877 878 def _deserialize_artifact_tiers( 879 self, payload: typedefs.JSONObject 880 ) -> season.ArtifactTier: 881 return season.ArtifactTier( 882 hash=payload["tierHash"], 883 is_unlocked=payload["isUnlocked"], 884 points_to_unlock=payload["pointsToUnlock"], 885 items=tuple( 886 season.ArtifactTierItem( 887 hash=item["itemHash"], is_active=item["isActive"] 888 ) 889 for item in payload["items"] 890 ), 891 ) 892 893 def deserialize_characters( 894 self, payload: typedefs.JSONObject 895 ) -> collections.Mapping[int, character.Character]: 896 return { 897 int(char_id): self._set_character_attrs(char) 898 for char_id, char in payload["data"].items() 899 } 900 901 def deserialize_character( 902 self, payload: typedefs.JSONObject 903 ) -> character.Character: 904 return self._set_character_attrs(payload) 905 906 def deserialize_character_equipments( 907 self, payload: typedefs.JSONObject 908 ) -> collections.Mapping[int, collections.Sequence[profile.ProfileItemImpl]]: 909 return { 910 int(char_id): self.deserialize_profile_items(item) 911 for char_id, item in payload["data"].items() 912 } 913 914 def deserialize_character_activities( 915 self, payload: typedefs.JSONObject 916 ) -> collections.Mapping[int, activity.CharacterActivity]: 917 return { 918 int(char_id): self.deserialize_character_activity(data) 919 for char_id, data in payload["data"].items() 920 } 921 922 def deserialize_characters_render_data( 923 self, payload: typedefs.JSONObject 924 ) -> collections.Mapping[int, character.RenderedData]: 925 return { 926 int(char_id): self.deserialize_character_render_data(data) 927 for char_id, data in payload["data"].items() 928 } 929 930 def deserialize_character_progressions( 931 self, payload: typedefs.JSONObject 932 ) -> character.CharacterProgression: 933 progressions_ = { 934 int(prog_id): self.deserialize_progressions(prog) 935 for prog_id, prog in payload["progressions"].items() 936 } 937 938 factions = { 939 int(faction_id): self._deserialize_factions(faction) 940 for faction_id, faction in payload["factions"].items() 941 } 942 943 milestones_ = { 944 int(milestone_hash): self.deserialize_milestone(milestone) 945 for milestone_hash, milestone in payload["milestones"].items() 946 } 947 948 uninstanced_item_objectives = { 949 int(item_hash): [self.deserialize_objectives(ins) for ins in obj] 950 for item_hash, obj in payload["uninstancedItemObjectives"].items() 951 } 952 953 artifact = payload["seasonalArtifact"] 954 seasonal_artifact = season.CharacterScopedArtifact( 955 hash=artifact["artifactHash"], 956 points_used=artifact["pointsUsed"], 957 reset_count=artifact["resetCount"], 958 tiers=tuple( 959 self._deserialize_artifact_tiers(tier) for tier in artifact["tiers"] 960 ), 961 ) 962 checklists = payload["checklists"] 963 964 return character.CharacterProgression( 965 progressions=progressions_, 966 factions=factions, 967 checklists=checklists, 968 milestones=milestones_, 969 seasonal_artifact=seasonal_artifact, 970 uninstanced_item_objectives=uninstanced_item_objectives, 971 ) 972 973 def deserialize_character_progressions_mapping( 974 self, payload: typedefs.JSONObject 975 ) -> collections.Mapping[int, character.CharacterProgression]: 976 character_progressions: collections.MutableMapping[ 977 int, character.CharacterProgression 978 ] = {} 979 for char_id, data in payload["data"].items(): 980 character_progressions[int(char_id)] = ( 981 self.deserialize_character_progressions(data) 982 ) 983 return character_progressions 984 985 def _deserialize_character_loadout_item( 986 self, payload: typedefs.JSONObject 987 ) -> character.LoadoutItem: 988 return character.LoadoutItem( 989 instance_id=payload["itemInstanceId"], 990 plug_hashes=tuple(payload["plugItemHashes"]), 991 ) 992 993 def deserialize_character_loadout( 994 self, payload: typedefs.JSONObject 995 ) -> character.Loadout: 996 return character.Loadout( 997 color_hash=payload["colorHash"], 998 icon_hash=payload["iconHash"], 999 name_hash=payload["nameHash"], 1000 items=tuple( 1001 self._deserialize_character_loadout_item(item) 1002 for item in payload["items"] 1003 ), 1004 ) 1005 1006 def deserialize_characters_records( 1007 self, 1008 payload: typedefs.JSONObject, 1009 ) -> collections.Mapping[int, records.CharacterRecord]: 1010 return { 1011 int(rec_id): self.deserialize_character_records( 1012 rec, record_hashes=payload.get("featuredRecordHashes", ()) 1013 ) 1014 for rec_id, rec in payload["records"].items() 1015 } 1016 1017 def deserialize_profile_records( 1018 self, payload: typedefs.JSONObject 1019 ) -> collections.Mapping[int, records.Record]: 1020 raw_profile_records = payload["data"] 1021 scores = records.RecordScores( 1022 current_score=raw_profile_records["score"], 1023 legacy_score=raw_profile_records["legacyScore"], 1024 lifetime_score=raw_profile_records["lifetimeScore"], 1025 ) 1026 return { 1027 int(record_id): self.deserialize_records( 1028 record, 1029 scores, 1030 categories_hash=raw_profile_records["recordCategoriesRootNodeHash"], 1031 seals_hash=raw_profile_records["recordSealsRootNodeHash"], 1032 ) 1033 for record_id, record in raw_profile_records["records"].items() 1034 } 1035 1036 def _deserialize_craftable_socket_plug( 1037 self, payload: typedefs.JSONObject 1038 ) -> items.CraftableSocketPlug: 1039 return items.CraftableSocketPlug( 1040 item_hash=int(payload["plugItemHash"]), 1041 failed_requirement_indexes=payload.get("failedRequirementIndexes", ()), 1042 ) 1043 1044 def _deserialize_craftable_socket( 1045 self, payload: typedefs.JSONObject 1046 ) -> items.CraftableSocket: 1047 if raw_plug := payload.get("plug"): 1048 plugs = tuple( 1049 self._deserialize_craftable_socket_plug(plug) for plug in raw_plug 1050 ) 1051 else: 1052 plugs = () 1053 1054 return items.CraftableSocket( 1055 plug_set_hash=int(payload["plugSetHash"]), plugs=plugs 1056 ) 1057 1058 def _deserialize_craftable_item( 1059 self, payload: typedefs.JSONObject 1060 ) -> items.CraftableItem: 1061 return items.CraftableItem( 1062 is_visible=payload["visible"], 1063 failed_requirement_indexes=payload.get("failedRequirementIndexes", ()), 1064 sockets=tuple( 1065 self._deserialize_craftable_socket(socket) 1066 for socket in payload["sockets"] 1067 ), 1068 ) 1069 1070 def deserialize_craftables_component( 1071 self, payload: typedefs.JSONObject 1072 ) -> components.CraftablesComponent: 1073 return components.CraftablesComponent( 1074 craftables={ 1075 int(item_id): self._deserialize_craftable_item(item) 1076 for item_id, item in payload["craftables"].items() 1077 if item is not None 1078 }, 1079 crafting_root_node_hash=payload["craftingRootNodeHash"], 1080 ) 1081 1082 def _deserialize_commendations_component( 1083 self, payload: typedefs.JSONObject 1084 ) -> components.Commendation: 1085 return components.Commendation( 1086 total_score=payload["totalScore"], 1087 node_percentages={ 1088 int(node_hash): node_percent 1089 for node_hash, node_percent in payload[ 1090 "commendationNodePercentagesByHash" 1091 ].items() 1092 }, 1093 score_detail_values=tuple(payload["scoreDetailValues"]), 1094 node_scores={ 1095 int(node_score_hash): node_score_value 1096 for node_score_hash, node_score_value in payload[ 1097 "commendationNodeScoresByHash" 1098 ].items() 1099 }, 1100 commendation_scores={ 1101 int(score_hash): score_value 1102 for score_hash, score_value in payload[ 1103 "commendationScoresByHash" 1104 ].items() 1105 }, 1106 ) 1107 1108 def deserialize_components( # noqa: C901 Too complex. 1109 self, payload: typedefs.JSONObject 1110 ) -> components.Component: 1111 # Due to how complex this method is, We'll stick to 1112 # typing.Optional here. 1113 1114 profile_: profile.Profile | None = None 1115 if raw_profile := payload.get("profile"): 1116 profile_ = self.deserialize_profile(raw_profile["data"]) 1117 1118 profile_progression: profile.ProfileProgression | None = None 1119 if raw_profile_progression := payload.get("profileProgression"): 1120 profile_progression = self.deserialize_profile_progression( 1121 raw_profile_progression 1122 ) 1123 1124 profile_currencies: typing.Optional[ 1125 collections.Sequence[profile.ProfileItemImpl] 1126 ] = None 1127 if raw_profile_currencies := payload.get("profileCurrencies"): 1128 if "data" in raw_profile_currencies: 1129 profile_currencies = self.deserialize_profile_items( 1130 raw_profile_currencies["data"] 1131 ) 1132 1133 profile_inventories: typing.Optional[ 1134 collections.Sequence[profile.ProfileItemImpl] 1135 ] = None 1136 if raw_profile_inventories := payload.get("profileInventory"): 1137 if "data" in raw_profile_inventories: 1138 profile_inventories = self.deserialize_profile_items( 1139 raw_profile_inventories["data"] 1140 ) 1141 1142 profile_records: typing.Optional[collections.Mapping[int, records.Record]] = ( 1143 None 1144 ) 1145 1146 if raw_profile_records_ := payload.get("profileRecords"): 1147 profile_records = self.deserialize_profile_records(raw_profile_records_) 1148 1149 characters: typing.Optional[collections.Mapping[int, character.Character]] = ( 1150 None 1151 ) 1152 if raw_characters := payload.get("characters"): 1153 characters = self.deserialize_characters(raw_characters) 1154 1155 character_records: typing.Optional[ 1156 collections.Mapping[int, records.CharacterRecord] 1157 ] = None 1158 1159 if raw_character_records := payload.get("characterRecords"): 1160 # Had to do it in two steps.. 1161 to_update = {} 1162 for _, data in raw_character_records["data"].items(): 1163 for record_id, record in data.items(): 1164 to_update[record_id] = record 1165 1166 character_records = { 1167 int(rec_id): self.deserialize_character_records( 1168 rec, record_hashes=to_update.get("featuredRecordHashes", ()) 1169 ) 1170 for rec_id, rec in to_update["records"].items() 1171 } 1172 1173 character_equipments: typing.Optional[ 1174 collections.Mapping[int, collections.Sequence[profile.ProfileItemImpl]] 1175 ] = None 1176 if raw_character_equips := payload.get("characterEquipment"): 1177 character_equipments = self.deserialize_character_equipments( 1178 raw_character_equips 1179 ) 1180 1181 character_inventories: typing.Optional[ 1182 collections.Mapping[int, collections.Sequence[profile.ProfileItemImpl]] 1183 ] = None 1184 if raw_character_inventories := payload.get("characterInventories"): 1185 if "data" in raw_character_inventories: 1186 character_inventories = self.deserialize_character_equipments( 1187 raw_character_inventories 1188 ) 1189 1190 character_activities: typing.Optional[ 1191 collections.Mapping[int, activity.CharacterActivity] 1192 ] = None 1193 if raw_char_acts := payload.get("characterActivities"): 1194 character_activities = self.deserialize_character_activities(raw_char_acts) 1195 1196 character_render_data: typing.Optional[ 1197 collections.Mapping[int, character.RenderedData] 1198 ] = None 1199 if raw_character_render_data := payload.get("characterRenderData"): 1200 character_render_data = self.deserialize_characters_render_data( 1201 raw_character_render_data 1202 ) 1203 1204 character_progressions: typing.Optional[ 1205 collections.Mapping[int, character.CharacterProgression] 1206 ] = None 1207 1208 if raw_character_progressions := payload.get("characterProgressions"): 1209 character_progressions = self.deserialize_character_progressions_mapping( 1210 raw_character_progressions 1211 ) 1212 1213 profile_string_vars: typing.Optional[collections.Mapping[int, int]] = None 1214 if raw_profile_string_vars := payload.get("profileStringVariables"): 1215 profile_string_vars = raw_profile_string_vars["data"]["integerValuesByHash"] 1216 1217 character_string_vars: typing.Optional[ 1218 collections.Mapping[int, collections.Mapping[int, int]] 1219 ] = None 1220 if raw_character_string_vars := payload.get("characterStringVariables"): 1221 character_string_vars = { 1222 int(char_id): data["integerValuesByHash"] 1223 for char_id, data in raw_character_string_vars["data"].items() 1224 } 1225 1226 metrics: typing.Optional[ 1227 collections.Sequence[ 1228 collections.Mapping[int, tuple[bool, records.Objective | None]] 1229 ] 1230 ] = None 1231 root_node_hash: int | None = None 1232 1233 if raw_metrics := payload.get("metrics"): 1234 root_node_hash = raw_metrics["data"]["metricsRootNodeHash"] 1235 metrics = tuple( 1236 { 1237 int(metrics_hash): ( 1238 data["invisible"], 1239 self.deserialize_objectives(data["objectiveProgress"]) 1240 if "objectiveProgress" in data 1241 else None, 1242 ) 1243 } 1244 for metrics_hash, data in raw_metrics["data"]["metrics"].items() 1245 ) 1246 transitory: fireteams.FireteamParty | None = None 1247 if raw_transitory := payload.get("profileTransitoryData"): 1248 if "data" in raw_transitory: 1249 transitory = self.deserialize_fireteam_party(raw_transitory["data"]) 1250 1251 item_components: components.ItemsComponent | None = None 1252 if raw_item_components := payload.get("itemComponents"): 1253 item_components = self.deserialize_items_component(raw_item_components) 1254 1255 profile_plugsets: typing.Optional[ 1256 collections.Mapping[int, collections.Sequence[items.PlugItemState]] 1257 ] = None 1258 1259 if raw_profile_plugs := payload.get("profilePlugSets"): 1260 profile_plugsets = { 1261 int(index): [self.deserialize_plug_item_state(state) for state in data] 1262 for index, data in raw_profile_plugs["data"]["plugs"].items() 1263 } 1264 1265 character_plugsets: typing.Optional[ 1266 collections.Mapping[ 1267 int, collections.Mapping[int, collections.Sequence[items.PlugItemState]] 1268 ] 1269 ] = None 1270 if raw_char_plugsets := payload.get("characterPlugSets"): 1271 character_plugsets = { 1272 int(char_id): { 1273 int(index): [ 1274 self.deserialize_plug_item_state(state) for state in data 1275 ] 1276 for index, data in inner["plugs"].items() 1277 } 1278 for char_id, inner in raw_char_plugsets["data"].items() 1279 } 1280 1281 character_collectibles: typing.Optional[ 1282 collections.Mapping[int, items.Collectible] 1283 ] = None 1284 if raw_character_collectibles := payload.get("characterCollectibles"): 1285 character_collectibles = { 1286 int(char_id): self._deserialize_collectible(data) 1287 for char_id, data in raw_character_collectibles["data"].items() 1288 } 1289 1290 profile_collectibles: items.Collectible | None = None 1291 if raw_profile_collectibles := payload.get("profileCollectibles"): 1292 profile_collectibles = self._deserialize_collectible( 1293 raw_profile_collectibles["data"] 1294 ) 1295 1296 profile_nodes: typing.Optional[collections.Mapping[int, records.Node]] = None 1297 if raw_profile_nodes := payload.get("profilePresentationNodes"): 1298 profile_nodes = { 1299 int(node_hash): self._deserialize_node(node) 1300 for node_hash, node in raw_profile_nodes["data"]["nodes"].items() 1301 } 1302 1303 character_nodes: typing.Optional[ 1304 collections.Mapping[int, collections.Mapping[int, records.Node]] 1305 ] = None 1306 if raw_character_nodes := payload.get("characterPresentationNodes"): 1307 character_nodes = { 1308 int(char_id): { 1309 int(node_hash): self._deserialize_node(node) 1310 for node_hash, node in each_character["nodes"].items() 1311 } 1312 for char_id, each_character in raw_character_nodes["data"].items() 1313 } 1314 1315 platform_silver: typing.Optional[ 1316 collections.Mapping[str, profile.ProfileItemImpl] 1317 ] = None 1318 if raw_platform_silver := payload.get("platformSilver"): 1319 if "data" in raw_platform_silver: 1320 platform_silver = { 1321 platform_name: self.deserialize_profile_item(item) 1322 for platform_name, item in raw_platform_silver["data"][ 1323 "platformSilver" 1324 ].items() 1325 } 1326 1327 character_currency_lookups: typing.Optional[ 1328 collections.Mapping[int, collections.Sequence[items.Currency]] 1329 ] = None 1330 if raw_char_lookups := payload.get("characterCurrencyLookups"): 1331 if "data" in raw_char_lookups: 1332 character_currency_lookups = { 1333 int(char_id): self._deserialize_currencies(currency) 1334 for char_id, currency in raw_char_lookups["data"].items() 1335 } 1336 1337 character_craftables: typing.Optional[ 1338 collections.Mapping[int, components.CraftablesComponent] 1339 ] = None 1340 if raw_character_craftables := payload.get("characterCraftables"): 1341 if "data" in raw_character_craftables: 1342 character_craftables = { 1343 int(char_id): self.deserialize_craftables_component(craftable) 1344 for char_id, craftable in raw_character_craftables["data"].items() 1345 } 1346 1347 character_loadouts: ( 1348 collections.Mapping[int, collections.Sequence[character.Loadout]] | None 1349 ) = None 1350 if raw_character_loadouts := payload.get("characterLoadouts"): 1351 if "data" in raw_character_loadouts: 1352 character_loadouts = { 1353 int(char_id): tuple( 1354 self.deserialize_character_loadout(loadout) 1355 for loadout in raw_loadouts["loadouts"] 1356 ) 1357 for char_id, raw_loadouts in raw_character_loadouts["data"].items() 1358 } 1359 1360 commendations: components.Commendation | None = None 1361 if ( 1362 raw_commendations := payload.get("profileCommendations") 1363 ) and "data" in raw_commendations: 1364 commendations = self._deserialize_commendations_component( 1365 raw_commendations["data"] 1366 ) 1367 1368 return components.Component( 1369 profiles=profile_, 1370 profile_progression=profile_progression, 1371 profile_currencies=profile_currencies, 1372 profile_inventories=profile_inventories, 1373 profile_records=profile_records, 1374 characters=characters, 1375 character_records=character_records, 1376 character_equipments=character_equipments, 1377 character_inventories=character_inventories, 1378 character_activities=character_activities, 1379 character_render_data=character_render_data, 1380 character_progressions=character_progressions, 1381 profile_string_variables=profile_string_vars, 1382 character_string_variables=character_string_vars, 1383 metrics=metrics, 1384 root_node_hash=root_node_hash, 1385 transitory=transitory, 1386 item_components=item_components, 1387 profile_plugsets=profile_plugsets, 1388 character_plugsets=character_plugsets, 1389 character_collectibles=character_collectibles, 1390 profile_collectibles=profile_collectibles, 1391 profile_nodes=profile_nodes, 1392 character_nodes=character_nodes, 1393 platform_silver=platform_silver, 1394 character_currency_lookups=character_currency_lookups, 1395 character_craftables=character_craftables, 1396 character_loadouts=character_loadouts, 1397 commendation=commendations, 1398 ) 1399 1400 def deserialize_items_component( 1401 self, payload: typedefs.JSONObject 1402 ) -> components.ItemsComponent: 1403 # Due to how complex this method is, We'll stick to typing.Optional. 1404 instances: typing.Optional[ 1405 collections.Sequence[collections.Mapping[int, items.ItemInstance]] 1406 ] = None 1407 if raw_instances := payload.get("instances"): 1408 instances = tuple( 1409 {int(ins_id): self.deserialize_instanced_item(item)} 1410 for ins_id, item in raw_instances["data"].items() 1411 ) 1412 1413 render_data: typing.Optional[ 1414 collections.Mapping[int, tuple[bool, dict[int, int]]] 1415 ] = None 1416 if raw_render_data := payload.get("renderData"): 1417 render_data = { 1418 int(ins_id): (data["useCustomDyes"], data["artRegions"]) 1419 for ins_id, data in raw_render_data["data"].items() 1420 } 1421 1422 stats: typing.Optional[collections.Mapping[int, items.ItemStatsView]] = None 1423 if raw_stats := payload.get("stats"): 1424 stats = {} 1425 for ins_id, stat in raw_stats["data"].items(): 1426 for _, items_ in stat.items(): 1427 stats[int(ins_id)] = self.deserialize_item_stats_view(items_) 1428 1429 sockets: typing.Optional[ 1430 collections.Mapping[int, collections.Sequence[items.ItemSocket]] 1431 ] = None 1432 if raw_sockets := payload.get("sockets"): 1433 sockets = { 1434 int(ins_id): tuple( 1435 self.deserialize_item_socket(socket) for socket in item["sockets"] 1436 ) 1437 for ins_id, item in raw_sockets["data"].items() 1438 } 1439 1440 objectives: typing.Optional[ 1441 collections.Mapping[int, collections.Sequence[records.Objective]] 1442 ] = None 1443 if raw_objectives := payload.get("objectives"): 1444 objectives = { 1445 int(ins_id): tuple( 1446 self.deserialize_objectives(objective) 1447 for objective in data["objectives"] 1448 ) 1449 for ins_id, data in raw_objectives["data"].items() 1450 } 1451 1452 perks: typing.Optional[ 1453 collections.Mapping[int, collections.Collection[items.ItemPerk]] 1454 ] = None 1455 if raw_perks := payload.get("perks"): 1456 perks = { 1457 int(ins_id): tuple( 1458 self.deserialize_item_perk(perk) for perk in item["perks"] 1459 ) 1460 for ins_id, item in raw_perks["data"].items() 1461 } 1462 1463 plug_states: typing.Optional[collections.Sequence[items.PlugItemState]] = None 1464 if raw_plug_states := payload.get("plugStates"): 1465 plug_states = tuple( 1466 self.deserialize_plug_item_state(plug) 1467 for _, plug in raw_plug_states["data"].items() 1468 ) 1469 1470 reusable_plugs: typing.Optional[ 1471 collections.Mapping[int, collections.Sequence[items.PlugItemState]] 1472 ] = None 1473 if raw_re_plugs := payload.get("reusablePlugs"): 1474 reusable_plugs = { 1475 int(ins_id): tuple( 1476 self.deserialize_plug_item_state(state) for state in inner 1477 ) 1478 for ins_id, plug in raw_re_plugs["data"].items() 1479 for inner in tuple(plug["plugs"].values()) 1480 } 1481 1482 plug_objectives: typing.Optional[ 1483 collections.Mapping[ 1484 int, collections.Mapping[int, collections.Collection[records.Objective]] 1485 ] 1486 ] = None 1487 if raw_plug_objectives := payload.get("plugObjectives"): 1488 plug_objectives = { 1489 int(ins_id): { 1490 int(obj_hash): tuple( 1491 self.deserialize_objectives(obj) for obj in objs 1492 ) 1493 for obj_hash, objs in inner["objectivesPerPlug"].items() 1494 } 1495 for ins_id, inner in raw_plug_objectives["data"].items() 1496 } 1497 1498 return components.ItemsComponent( 1499 sockets=sockets, 1500 stats=stats, 1501 render_data=render_data, 1502 instances=instances, 1503 objectives=objectives, 1504 perks=perks, 1505 plug_states=plug_states, 1506 reusable_plugs=reusable_plugs, 1507 plug_objectives=plug_objectives, 1508 ) 1509 1510 def deserialize_character_component( 1511 self, payload: typedefs.JSONObject 1512 ) -> components.CharacterComponent: 1513 character_: character.Character | None = None 1514 if raw_singular_character := payload.get("character"): 1515 character_ = self.deserialize_character(raw_singular_character["data"]) 1516 1517 inventory: typing.Optional[collections.Sequence[profile.ProfileItemImpl]] = None 1518 loadouts: collections.Sequence[character.Loadout] | None = None 1519 if raw_inventory := payload.get("inventory"): 1520 if "data" in raw_inventory: 1521 inventory = self.deserialize_profile_items(raw_inventory["data"]) 1522 1523 # The inventory component also returns the loadouts component 1524 if raw_loadouts := payload.get("loadouts"): 1525 if "data" in raw_loadouts: 1526 loadouts = tuple( 1527 self.deserialize_character_loadout(loadout) 1528 # very interesting nesting bungie... 1529 for loadout in raw_loadouts["data"]["loadouts"] 1530 ) 1531 1532 activities: activity.CharacterActivity | None = None 1533 if raw_activities := payload.get("activities"): 1534 activities = self.deserialize_character_activity(raw_activities["data"]) 1535 1536 equipment: typing.Optional[collections.Sequence[profile.ProfileItemImpl]] = None 1537 if raw_equipments := payload.get("equipment"): 1538 equipment = self.deserialize_profile_items(raw_equipments["data"]) 1539 1540 progressions_: character.CharacterProgression | None = None 1541 if raw_progressions := payload.get("progressions"): 1542 progressions_ = self.deserialize_character_progressions( 1543 raw_progressions["data"] 1544 ) 1545 1546 render_data: character.RenderedData | None = None 1547 if raw_render_data := payload.get("renderData"): 1548 render_data = self.deserialize_character_render_data( 1549 raw_render_data["data"] 1550 ) 1551 1552 character_records: typing.Optional[ 1553 collections.Mapping[int, records.CharacterRecord] 1554 ] = None 1555 if raw_char_records := payload.get("records"): 1556 character_records = self.deserialize_characters_records( 1557 raw_char_records["data"] 1558 ) 1559 1560 item_components: components.ItemsComponent | None = None 1561 if raw_item_components := payload.get("itemComponents"): 1562 item_components = self.deserialize_items_component(raw_item_components) 1563 1564 nodes: typing.Optional[collections.Mapping[int, records.Node]] = None 1565 if raw_nodes := payload.get("presentationNodes"): 1566 nodes = { 1567 int(node_hash): self._deserialize_node(node) 1568 for node_hash, node in raw_nodes["data"]["nodes"].items() 1569 } 1570 1571 collectibles: items.Collectible | None = None 1572 if raw_collectibles := payload.get("collectibles"): 1573 collectibles = self._deserialize_collectible(raw_collectibles["data"]) 1574 1575 currency_lookups: typing.Optional[collections.Sequence[items.Currency]] = None 1576 if raw_currencies := payload.get("currencyLookups"): 1577 if "data" in raw_currencies: 1578 currency_lookups = self._deserialize_currencies(raw_currencies) 1579 1580 return components.CharacterComponent( 1581 activities=activities, 1582 equipment=equipment, 1583 inventory=inventory, 1584 progressions=progressions_, 1585 render_data=render_data, 1586 character=character_, 1587 character_records=character_records, 1588 profile_records=None, 1589 item_components=item_components, 1590 currency_lookups=currency_lookups, 1591 collectibles=collectibles, 1592 nodes=nodes, 1593 loadouts=loadouts, 1594 ) 1595 1596 def _set_entity_attrs( 1597 self, payload: typedefs.JSONObject, *, key: str = "displayProperties" 1598 ) -> entity.Entity: 1599 properties = payload[key] 1600 name = typedefs.unknown(properties["name"]) 1601 description = typedefs.unknown(properties["description"]) 1602 1603 return entity.Entity( 1604 hash=payload["hash"], 1605 index=payload["index"], 1606 name=name, 1607 description=description, 1608 has_icon=properties["hasIcon"], 1609 icon=builders.Image(properties.get("icon")), 1610 ) 1611 1612 def deserialize_inventory_results( 1613 self, payload: typedefs.JSONObject 1614 ) -> sain.Iterator[entity.SearchableEntity]: 1615 return sain.Iter( 1616 entity.SearchableEntity( 1617 hash=data["hash"], 1618 entity_type=data["entityType"], 1619 weight=data["weight"], 1620 suggested_words=payload["suggestedWords"], 1621 name=data["displayProperties"]["name"], 1622 has_icon=data["displayProperties"]["hasIcon"], 1623 description=typedefs.unknown(data["displayProperties"]["description"]), 1624 icon=builders.Image(path=data["displayProperties"]["icon"]), 1625 ) 1626 for data in payload["results"]["results"] 1627 ) 1628 1629 def _deserialize_inventory_item_objects( 1630 self, payload: typedefs.JSONObject 1631 ) -> entity.InventoryEntityObjects: 1632 return entity.InventoryEntityObjects( 1633 action=payload.get("action"), 1634 set_data=payload.get("setData"), 1635 stats=payload.get("stats"), 1636 equipping_block=payload.get("equippingBlock"), 1637 translation_block=payload.get("translationBlock"), 1638 preview=payload.get("preview"), 1639 quality=payload.get("quality"), 1640 value=payload.get("value"), 1641 source_data=payload.get("sourceData"), 1642 objectives=payload.get("objectives"), 1643 plug=payload.get("plug"), 1644 metrics=payload.get("metrics"), 1645 gearset=payload.get("gearset"), 1646 sack=payload.get("sack"), 1647 sockets=payload.get("sockets"), 1648 summary=payload.get("summary"), 1649 talent_gird=payload.get("talentGrid"), 1650 investments_stats=payload.get("investmentStats"), 1651 perks=payload.get("perks"), 1652 animations=payload.get("animations", ()), 1653 links=payload.get("links", ()), 1654 ) 1655 1656 def deserialize_inventory_entity( # noqa: C901 Too complex. 1657 self, payload: typedefs.JSONObject, / 1658 ) -> entity.InventoryEntity: 1659 props = self._set_entity_attrs(payload) 1660 objects = self._deserialize_inventory_item_objects(payload) 1661 1662 collectible_hash: int | None = None 1663 if raw_collectible_hash := payload.get("collectibleHash"): 1664 collectible_hash = int(raw_collectible_hash) 1665 1666 secondary_icon: builders.Image | None = None 1667 if raw_second_icon := payload.get("secondaryIcon"): 1668 secondary_icon = builders.Image(path=raw_second_icon) 1669 1670 secondary_overlay: builders.Image | None = None 1671 if raw_second_overlay := payload.get("secondaryOverlay"): 1672 secondary_overlay = builders.Image(path=raw_second_overlay) 1673 1674 secondary_special: builders.Image | None = None 1675 if raw_second_special := payload.get("secondarySpecial"): 1676 secondary_special = builders.Image(path=raw_second_special) 1677 1678 screenshot: builders.Image | None = None 1679 if raw_screenshot := payload.get("screenshot"): 1680 screenshot = builders.Image(path=raw_screenshot) 1681 1682 watermark_icon: builders.Image | None = None 1683 if raw_watermark_icon := payload.get("iconWatermark"): 1684 watermark_icon = builders.Image(path=raw_watermark_icon) 1685 1686 watermark_shelved: builders.Image | None = None 1687 if raw_watermark_shelved := payload.get("iconWatermarkShelved"): 1688 watermark_shelved = builders.Image(path=raw_watermark_shelved) 1689 1690 about: str | None = None 1691 if raw_about := payload.get("flavorText"): 1692 about = raw_about 1693 1694 ui_item_style: str | None = None 1695 if raw_ui_style := payload.get("uiItemDisplayStyle"): 1696 ui_item_style = raw_ui_style 1697 1698 tier_and_name: str | None = None 1699 if raw_tier_and_name := payload.get("itemTypeAndTierDisplayName"): 1700 tier_and_name = raw_tier_and_name 1701 1702 type_name: str | None = None 1703 if raw_type_name := payload.get("itemTypeDisplayName"): 1704 type_name = raw_type_name 1705 1706 display_source: str | None = None 1707 if raw_display_source := payload.get("displaySource"): 1708 display_source = raw_display_source 1709 1710 lorehash: int | None = None 1711 if raw_lore_hash := payload.get("loreHash"): 1712 lorehash = int(raw_lore_hash) 1713 1714 summary_hash: int | None = None 1715 if raw_summary_hash := payload.get("summaryItemHash"): 1716 summary_hash = raw_summary_hash 1717 1718 breaker_type_hash: int | None = None 1719 if raw_breaker_type_hash := payload.get("breakerTypeHash"): 1720 breaker_type_hash = int(raw_breaker_type_hash) 1721 1722 damage_types: typing.Optional[collections.Sequence[int]] = None 1723 if raw_damage_types := payload.get("damageTypes"): 1724 damage_types = tuple(int(type_) for type_ in raw_damage_types) 1725 1726 damagetype_hashes: typing.Optional[collections.Sequence[int]] = None 1727 if raw_damagetype_hashes := payload.get("damageTypeHashes"): 1728 damagetype_hashes = tuple(int(type_) for type_ in raw_damagetype_hashes) 1729 1730 default_damagetype_hash: int | None = None 1731 if raw_defaultdmg_hash := payload.get("defaultDamageTypeHash"): 1732 default_damagetype_hash = int(raw_defaultdmg_hash) 1733 1734 emblem_objective_hash: int | None = None 1735 if raw_emblem_obj_hash := payload.get("emblemObjectiveHash"): 1736 emblem_objective_hash = int(raw_emblem_obj_hash) 1737 1738 tier_type: enums.TierType | None = None 1739 tier: enums.ItemTier | None = None 1740 bucket_hash: int | None = None 1741 recovery_hash: int | None = None 1742 tier_name: str | None = None 1743 isinstance_item: bool = False 1744 expire_tool_tip: str | None = None 1745 expire_in_orbit_message: str | None = None 1746 suppress_expiration: bool = False 1747 max_stack_size: int | None = None 1748 stack_label: str | None = None 1749 1750 if inventory := payload.get("inventory"): 1751 tier_type = enums.TierType(int(inventory["tierType"])) 1752 tier = enums.ItemTier(int(inventory["tierTypeHash"])) 1753 bucket_hash = int(inventory["bucketTypeHash"]) 1754 recovery_hash = int(inventory["recoveryBucketTypeHash"]) 1755 tier_name = inventory["tierTypeName"] 1756 isinstance_item = inventory["isInstanceItem"] 1757 suppress_expiration = inventory["suppressExpirationWhenObjectivesComplete"] 1758 max_stack_size = int(inventory["maxStackSize"]) 1759 1760 try: 1761 stack_label = inventory["stackUniqueLabel"] 1762 except KeyError: 1763 pass 1764 1765 if "traitHashes" in payload: 1766 trait_hashes = tuple( 1767 int(trait_hash) for trait_hash in payload["traitHashes"] 1768 ) 1769 else: 1770 trait_hashes = () 1771 1772 if "traitIds" in payload: 1773 trait_ids = tuple(trait_id for trait_id in payload["traitIds"]) 1774 else: 1775 trait_ids = () 1776 1777 return entity.InventoryEntity( 1778 collectible_hash=collectible_hash, 1779 name=props.name, 1780 about=about, 1781 emblem_objective_hash=emblem_objective_hash, 1782 suppress_expiration=suppress_expiration, 1783 max_stack_size=max_stack_size, 1784 stack_label=stack_label, 1785 tier=tier, 1786 tier_type=tier_type, 1787 tier_name=tier_name, 1788 bucket_hash=bucket_hash, 1789 recovery_bucket_hash=recovery_hash, 1790 isinstance_item=isinstance_item, 1791 expire_in_orbit_message=expire_in_orbit_message, 1792 expiration_tooltip=expire_tool_tip, 1793 lore_hash=lorehash, 1794 type_and_tier_name=tier_and_name, 1795 summary_hash=summary_hash, 1796 ui_display_style=ui_item_style, 1797 type_name=type_name, 1798 breaker_type_hash=breaker_type_hash, 1799 description=props.description, 1800 display_source=display_source, 1801 hash=props.hash, 1802 damage_types=damage_types, 1803 index=props.index, 1804 icon=props.icon, 1805 has_icon=props.has_icon, 1806 screenshot=screenshot, 1807 watermark_icon=watermark_icon, 1808 watermark_shelved=watermark_shelved, 1809 secondary_icon=secondary_icon, 1810 secondary_overlay=secondary_overlay, 1811 secondary_special=secondary_special, 1812 type=enums.ItemType(int(payload["itemType"])), 1813 category_hashes=tuple( 1814 int(hash_) for hash_ in payload["itemCategoryHashes"] 1815 ), 1816 item_class=enums.Class(int(payload["classType"])), 1817 sub_type=enums.ItemSubType(int(payload["itemSubType"])), 1818 breaker_type=int(payload["breakerType"]), 1819 default_damagetype=int(payload["defaultDamageType"]), 1820 default_damagetype_hash=default_damagetype_hash, 1821 damagetype_hashes=damagetype_hashes, 1822 tooltip_notifications=payload["tooltipNotifications"], 1823 not_transferable=payload["nonTransferrable"], 1824 allow_actions=payload["allowActions"], 1825 is_equippable=payload["equippable"], 1826 objects=objects, 1827 background_colors=payload.get("backgroundColor", {}), 1828 season_hash=payload.get("seasonHash"), 1829 has_postmaster_effect=payload["doesPostmasterPullHaveSideEffects"], 1830 trait_hashes=trait_hashes, 1831 trait_ids=trait_ids, 1832 ) 1833 1834 def deserialize_objective_entity( 1835 self, payload: typedefs.JSONObject, / 1836 ) -> entity.ObjectiveEntity: 1837 props = self._set_entity_attrs(payload) 1838 return entity.ObjectiveEntity( 1839 hash=props.hash, 1840 index=props.index, 1841 description=props.description, 1842 name=props.name, 1843 has_icon=props.has_icon, 1844 icon=props.icon, 1845 unlock_value_hash=payload["unlockValueHash"], 1846 completion_value=payload["completionValue"], 1847 scope=entity.GatingScope(int(payload["scope"])), 1848 location_hash=payload["locationHash"], 1849 allowed_negative_value=payload["allowNegativeValue"], 1850 allowed_value_change=payload["allowValueChangeWhenCompleted"], 1851 counting_downward=payload["isCountingDownward"], 1852 value_style=entity.ValueUIStyle(int(payload["valueStyle"])), 1853 progress_description=payload["progressDescription"], 1854 perks=payload["perks"], 1855 stats=payload["stats"], 1856 minimum_visibility=payload["minimumVisibilityThreshold"], 1857 allow_over_completion=payload["allowOvercompletion"], 1858 show_value_style=payload["showValueOnComplete"], 1859 display_only_objective=payload["isDisplayOnlyObjective"], 1860 complete_value_style=entity.ValueUIStyle( 1861 int(payload["completedValueStyle"]) 1862 ), 1863 progress_value_style=entity.ValueUIStyle( 1864 int(payload["inProgressValueStyle"]) 1865 ), 1866 ui_label=payload["uiLabel"], 1867 ui_style=entity.ObjectiveUIStyle(int(payload["uiStyle"])), 1868 ) 1869 1870 def _deserialize_activity_values( 1871 self, payload: typedefs.JSONObject, / 1872 ) -> activity.ActivityValues: 1873 team: int | None = None 1874 if raw_team := payload.get("team"): 1875 team = raw_team["basic"]["value"] 1876 return activity.ActivityValues( 1877 assists=payload["assists"]["basic"]["value"], 1878 deaths=payload["deaths"]["basic"]["value"], 1879 kills=payload["kills"]["basic"]["value"], 1880 is_completed=bool(payload["completed"]["basic"]["value"]), 1881 opponents_defeated=payload["opponentsDefeated"]["basic"]["value"], 1882 efficiency=payload["efficiency"]["basic"]["value"], 1883 kd_ratio=payload["killsDeathsRatio"]["basic"]["value"], 1884 kd_assists=payload["killsDeathsAssists"]["basic"]["value"], 1885 score=payload["score"]["basic"]["value"], 1886 duration=payload["activityDurationSeconds"]["basic"]["displayValue"], 1887 team=team, 1888 completion_reason=payload["completionReason"]["basic"]["displayValue"], 1889 fireteam_id=payload["fireteamId"]["basic"]["value"], 1890 start_seconds=payload["startSeconds"]["basic"]["value"], 1891 played_time=payload["timePlayedSeconds"]["basic"]["displayValue"], 1892 player_count=payload["playerCount"]["basic"]["value"], 1893 team_score=payload["teamScore"]["basic"]["value"], 1894 ) 1895 1896 def deserialize_activity( 1897 self, 1898 payload: typedefs.JSONObject, 1899 /, 1900 ) -> activity.Activity: 1901 period = time.clean_date(payload["period"]) 1902 details = payload["activityDetails"] 1903 ref_id = int(details["referenceId"]) 1904 instance_id = int(details["instanceId"]) 1905 mode = enums.GameMode(details["mode"]) 1906 modes = tuple(enums.GameMode(int(mode_)) for mode_ in details["modes"]) 1907 is_private = details["isPrivate"] 1908 membership_type = enums.MembershipType(int(details["membershipType"])) 1909 1910 # Since we're using the same fields for post activity method 1911 # this check is required since post activity doesn't values values 1912 values = self._deserialize_activity_values(payload["values"]) 1913 1914 return activity.Activity( 1915 hash=ref_id, 1916 instance_id=instance_id, 1917 mode=mode, 1918 modes=modes, 1919 is_private=is_private, 1920 membership_type=membership_type, 1921 occurred_at=period, 1922 values=values, 1923 ) 1924 1925 def deserialize_activities( 1926 self, payload: typedefs.JSONObject 1927 ) -> sain.Iterator[activity.Activity]: 1928 return sain.Iter( 1929 self.deserialize_activity(activity_) for activity_ in payload["activities"] 1930 ) 1931 1932 def deserialize_extended_weapon_values( 1933 self, payload: typedefs.JSONObject 1934 ) -> activity.ExtendedWeaponValues: 1935 assists: int | None = None 1936 if raw_assists := payload["values"].get("uniqueWeaponAssists"): 1937 assists = raw_assists["basic"]["value"] 1938 assists_damage: int | None = None 1939 1940 if raw_assists_damage := payload["values"].get("uniqueWeaponAssistDamage"): 1941 assists_damage = raw_assists_damage["basic"]["value"] 1942 1943 return activity.ExtendedWeaponValues( 1944 reference_id=int(payload["referenceId"]), 1945 kills=payload["values"]["uniqueWeaponKills"]["basic"]["value"], 1946 precision_kills=payload["values"]["uniqueWeaponPrecisionKills"]["basic"][ 1947 "value" 1948 ], 1949 assists=assists, 1950 assists_damage=assists_damage, 1951 precision_kills_percentage=( 1952 payload["values"]["uniqueWeaponKillsPrecisionKills"]["basic"]["value"], 1953 payload["values"]["uniqueWeaponKillsPrecisionKills"]["basic"][ 1954 "displayValue" 1955 ], 1956 ), 1957 ) 1958 1959 def _deserialize_extended_values( 1960 self, payload: typedefs.JSONObject 1961 ) -> activity.ExtendedValues: 1962 if raw_weapons := payload.get("weapons"): 1963 weapons = tuple( 1964 self.deserialize_extended_weapon_values(value) for value in raw_weapons 1965 ) 1966 else: 1967 weapons = () 1968 1969 return activity.ExtendedValues( 1970 precision_kills=payload["values"]["precisionKills"]["basic"]["value"], 1971 grenade_kills=payload["values"]["weaponKillsGrenade"]["basic"]["value"], 1972 melee_kills=payload["values"]["weaponKillsMelee"]["basic"]["value"], 1973 super_kills=payload["values"]["weaponKillsSuper"]["basic"]["value"], 1974 ability_kills=payload["values"]["weaponKillsAbility"]["basic"]["value"], 1975 weapons=weapons, 1976 ) 1977 1978 def deserialize_post_activity_player( 1979 self, payload: typedefs.JSONObject, / 1980 ) -> activity.PostActivityPlayer: 1981 player = payload["player"] 1982 1983 class_hash: int | None = None 1984 if (class_hash := player.get("classHash")) is not None: 1985 class_hash = class_hash 1986 1987 race_hash: int | None = None 1988 if (race_hash := player.get("raceHash")) is not None: 1989 race_hash = race_hash 1990 1991 gender_hash: int | None = None 1992 if (gender_hash := player.get("genderHash")) is not None: 1993 gender_hash = gender_hash 1994 1995 character_class: str | None = None 1996 if character_class := player.get("characterClass"): 1997 character_class = character_class 1998 1999 character_level: int | None = None 2000 if (character_level := player.get("characterLevel")) is not None: 2001 character_level = character_level 2002 2003 return activity.PostActivityPlayer( 2004 standing=int(payload["standing"]), 2005 score=int(payload["score"]["basic"]["value"]), 2006 character_id=payload["characterId"], 2007 destiny_user=self.deserialize_destiny_membership(player["destinyUserInfo"]), 2008 character_class=character_class, 2009 character_level=character_level, 2010 race_hash=race_hash, 2011 gender_hash=gender_hash, 2012 class_hash=class_hash, 2013 light_level=int(player["lightLevel"]), 2014 emblem_hash=int(player["emblemHash"]), 2015 values=self._deserialize_activity_values(payload["values"]), 2016 extended_values=self._deserialize_extended_values(payload["extended"]), 2017 ) 2018 2019 def _deserialize_post_activity_team( 2020 self, payload: typedefs.JSONObject 2021 ) -> activity.PostActivityTeam: 2022 return activity.PostActivityTeam( 2023 id=payload["teamId"], 2024 is_defeated=bool(payload["standing"]["basic"]["value"]), 2025 score=int(payload["score"]["basic"]["value"]), 2026 name=payload["teamName"], 2027 ) 2028 2029 def deserialize_post_activity( 2030 self, payload: typedefs.JSONObject 2031 ) -> activity.PostActivity: 2032 period = time.clean_date(payload["period"]) 2033 details = payload["activityDetails"] 2034 ref_id = int(details["referenceId"]) 2035 instance_id = int(details["instanceId"]) 2036 mode = enums.GameMode(details["mode"]) 2037 modes = tuple(enums.GameMode(int(mode_)) for mode_ in details["modes"]) 2038 is_private = details["isPrivate"] 2039 membership_type = enums.MembershipType(int(details["membershipType"])) 2040 return activity.PostActivity( 2041 hash=ref_id, 2042 membership_type=membership_type, 2043 instance_id=instance_id, 2044 mode=mode, 2045 modes=modes, 2046 is_private=is_private, 2047 occurred_at=period, 2048 starting_phase=int(payload["startingPhaseIndex"]), 2049 players=tuple( 2050 self.deserialize_post_activity_player(player) 2051 for player in payload["entries"] 2052 ), 2053 teams=tuple( 2054 self._deserialize_post_activity_team(team) for team in payload["teams"] 2055 ), 2056 ) 2057 2058 def _deserialize_aggregated_activity_values( 2059 self, payload: typedefs.JSONObject 2060 ) -> activity.AggregatedActivityValues: 2061 # This ID is always the same for all aggregated values. 2062 activity_id = int(payload["fastestCompletionMsForActivity"]["activityId"]) 2063 2064 return activity.AggregatedActivityValues( 2065 id=activity_id, 2066 fastest_completion_time=( 2067 int(payload["fastestCompletionMsForActivity"]["basic"]["value"]), 2068 payload["fastestCompletionMsForActivity"]["basic"]["displayValue"], 2069 ), 2070 completions=int(payload["activityCompletions"]["basic"]["value"]), 2071 kills=int(payload["activityKills"]["basic"]["value"]), 2072 deaths=int(payload["activityDeaths"]["basic"]["value"]), 2073 assists=int(payload["activityAssists"]["basic"]["value"]), 2074 seconds_played=( 2075 int(payload["activitySecondsPlayed"]["basic"]["value"]), 2076 payload["activitySecondsPlayed"]["basic"]["displayValue"], 2077 ), 2078 wins=int(payload["activityWins"]["basic"]["value"]), 2079 goals_missed=int(payload["activityGoalsMissed"]["basic"]["value"]), 2080 special_actions=int(payload["activitySpecialActions"]["basic"]["value"]), 2081 best_goals_hit=int(payload["activityBestGoalsHit"]["basic"]["value"]), 2082 best_single_score=int( 2083 payload["activityBestSingleGameScore"]["basic"]["value"] 2084 ), 2085 goals_hit=int(payload["activityGoalsHit"]["basic"]["value"]), 2086 special_score=int(payload["activitySpecialScore"]["basic"]["value"]), 2087 kd_assists=int(payload["activityKillsDeathsAssists"]["basic"]["value"]), 2088 kd_ratio=float( 2089 payload["activityKillsDeathsAssists"]["basic"]["displayValue"] 2090 ), 2091 precision_kills=int(payload["activityPrecisionKills"]["basic"]["value"]), 2092 ) 2093 2094 def deserialize_aggregated_activity( 2095 self, payload: typedefs.JSONObject 2096 ) -> activity.AggregatedActivity: 2097 return activity.AggregatedActivity( 2098 hash=int(payload["activityHash"]), 2099 values=self._deserialize_aggregated_activity_values(payload["values"]), 2100 ) 2101 2102 def deserialize_aggregated_activities( 2103 self, payload: typedefs.JSONObject 2104 ) -> sain.Iterator[activity.AggregatedActivity]: 2105 return sain.Iter( 2106 self.deserialize_aggregated_activity(activity) 2107 for activity in payload["activities"] 2108 ) 2109 2110 def deserialize_linked_profiles( 2111 self, payload: typedefs.JSONObject 2112 ) -> profile.LinkedProfile: 2113 bungie_user = self.deserialize_partial_bungie_user(payload["bnetMembership"]) 2114 2115 if raw_profile := payload.get("profiles"): 2116 profiles = tuple( 2117 self.deserialize_destiny_membership(p) for p in raw_profile 2118 ) 2119 else: 2120 profiles = () 2121 2122 error_profiles = () 2123 if raw_profiles_with_errors := payload.get("profilesWithErrors"): 2124 for raw_error_p in raw_profiles_with_errors: 2125 if "infoCard" in raw_error_p: 2126 error_profiles = tuple( 2127 self.deserialize_destiny_membership(error_p) 2128 for error_p in raw_error_p 2129 ) 2130 2131 return profile.LinkedProfile( 2132 bungie_user=bungie_user, 2133 profiles=profiles, 2134 profiles_with_errors=error_profiles, 2135 ) 2136 2137 def deserialize_clan_banners( 2138 self, payload: typedefs.JSONObject 2139 ) -> collections.Sequence[clans.ClanBanner]: 2140 if banners := payload.get("clanBannerDecals"): 2141 banner_obj = tuple( 2142 clans.ClanBanner( 2143 id=int(k), 2144 foreground=builders.Image(path=v["foregroundPath"]), 2145 background=builders.Image(path=v["backgroundPath"]), 2146 ) 2147 for k, v in banners.items() 2148 ) 2149 else: 2150 banner_obj = () 2151 return banner_obj 2152 2153 def deserialize_public_milestone_content( 2154 self, payload: typedefs.JSONObject 2155 ) -> milestones.MilestoneContent: 2156 if raw_categories := payload.get("itemCategories"): 2157 items_categories = tuple( 2158 milestones.MilestoneItems( 2159 title=item["title"], hashes=item["itemHashes"] 2160 ) 2161 for item in raw_categories 2162 ) 2163 else: 2164 items_categories = () 2165 2166 return milestones.MilestoneContent( 2167 about=typedefs.unknown(payload["about"]), 2168 status=typedefs.unknown(payload["status"]), 2169 tips=payload.get("tips", ()), 2170 items=items_categories, 2171 ) 2172 2173 def deserialize_friend(self, payload: typedefs.JSONObject, /) -> friends.Friend: 2174 bungie_user: user.BungieUser | None = None 2175 2176 if raw_bungie_user := payload.get("bungieNetUser"): 2177 bungie_user = self.deserialize_bungie_user(raw_bungie_user) 2178 2179 return friends.Friend( 2180 id=int(payload["lastSeenAsMembershipId"]), 2181 name=typedefs.unknown(payload.get("bungieGlobalDisplayName", "")), 2182 code=payload.get("bungieGlobalDisplayNameCode"), 2183 relationship=enums.Relationship(payload["relationship"]), 2184 user=bungie_user, 2185 online_status=enums.Presence(payload["onlineStatus"]), 2186 online_title=payload["onlineTitle"], 2187 type=enums.MembershipType(payload["lastSeenAsBungieMembershipType"]), 2188 ) 2189 2190 def deserialize_friends( 2191 self, payload: typedefs.JSONObject 2192 ) -> collections.Sequence[friends.Friend]: 2193 return tuple(self.deserialize_friend(friend) for friend in payload["friends"]) 2194 2195 def deserialize_friend_requests( 2196 self, payload: typedefs.JSONObject 2197 ) -> friends.FriendRequestView: 2198 if raw_incoming_requests := payload.get("incomingRequests"): 2199 incoming = tuple( 2200 self.deserialize_friend(incoming_request) 2201 for incoming_request in raw_incoming_requests 2202 ) 2203 else: 2204 incoming = () 2205 2206 if raw_outgoing_requests := payload.get("outgoingRequests"): 2207 outgoing = tuple( 2208 self.deserialize_friend(outgoing_request) 2209 for outgoing_request in raw_outgoing_requests 2210 ) 2211 else: 2212 outgoing = () 2213 2214 return friends.FriendRequestView(incoming=incoming, outgoing=outgoing) 2215 2216 def _set_fireteam_fields( 2217 self, payload: typedefs.JSONObject, total_results: int | None = None 2218 ) -> fireteams.Fireteam: 2219 activity_type = fireteams.FireteamActivity(payload["activityType"]) 2220 return fireteams.Fireteam( 2221 id=int(payload["fireteamId"]), 2222 group_id=int(payload["groupId"]), 2223 platform=fireteams.FireteamPlatform(payload["platform"]), 2224 is_immediate=payload["isImmediate"], 2225 activity_type=activity_type, 2226 owner_id=int(payload["ownerMembershipId"]), 2227 player_slot_count=payload["playerSlotCount"], 2228 available_player_slots=payload["availablePlayerSlotCount"], 2229 available_alternate_slots=payload["availableAlternateSlotCount"], 2230 title=payload["title"], 2231 date_created=time.clean_date(payload["dateCreated"]), 2232 is_public=payload["isPublic"], 2233 locale=fireteams.FireteamLanguage(payload["locale"]), 2234 is_valid=payload["isValid"], 2235 last_modified=time.clean_date(payload["datePlayerModified"]), 2236 date_modified=time.clean_date(payload["dateModified"]) 2237 if "dateModified" in payload 2238 else None, 2239 scheduled_time=time.clean_date(payload["scheduledTime"]) 2240 if "scheduledTime" in payload 2241 else None, 2242 total_results=total_results or 0, 2243 ) 2244 2245 def deserialize_fireteams( 2246 self, payload: typedefs.JSONObject 2247 ) -> collections.Sequence[fireteams.Fireteam]: 2248 if "results" in payload: 2249 fireteams_ = tuple( 2250 self._set_fireteam_fields( 2251 elem, total_results=int(payload["totalResults"]) 2252 ) 2253 for elem in payload["results"] 2254 ) 2255 else: 2256 fireteams_ = () 2257 return fireteams_ 2258 2259 def deserialize_fireteam_destiny_membership( 2260 self, payload: typedefs.JSONObject 2261 ) -> fireteams.FireteamUser: 2262 destiny_obj = self.deserialize_destiny_membership(payload) 2263 return fireteams.FireteamUser( 2264 id=destiny_obj.id, 2265 code=destiny_obj.code, 2266 icon=destiny_obj.icon, 2267 types=destiny_obj.types, 2268 type=destiny_obj.type, 2269 is_public=destiny_obj.is_public, 2270 crossave_override=destiny_obj.crossave_override, 2271 name=destiny_obj.name, 2272 last_seen_name=destiny_obj.last_seen_name, 2273 fireteam_display_name=payload["FireteamDisplayName"], 2274 fireteam_membership_id=enums.MembershipType( 2275 payload["FireteamMembershipType"] 2276 ), 2277 ) 2278 2279 def deserialize_fireteam_members( 2280 self, payload: typedefs.JSONObject, *, alternatives: bool = False 2281 ) -> collections.Sequence[fireteams.FireteamMember]: 2282 members_: list[fireteams.FireteamMember] = [] 2283 if members := payload.get("Members" if not alternatives else "Alternates"): 2284 for member in members: 2285 bungie_fields = self.deserialize_partial_bungie_user(member) 2286 members_fields = fireteams.FireteamMember( 2287 membership=self.deserialize_fireteam_destiny_membership(member), 2288 has_microphone=member["hasMicrophone"], 2289 character_id=int(member["characterId"]), 2290 date_joined=time.clean_date(member["dateJoined"]), 2291 last_platform_invite_date=time.clean_date( 2292 member["lastPlatformInviteAttemptDate"] 2293 ), 2294 last_platform_invite_result=int( 2295 member["lastPlatformInviteAttemptResult"] 2296 ), 2297 name=bungie_fields.name, 2298 id=bungie_fields.id, 2299 icon=bungie_fields.icon, 2300 is_public=bungie_fields.is_public, 2301 crossave_override=bungie_fields.crossave_override, 2302 types=bungie_fields.types, 2303 type=bungie_fields.type, 2304 code=bungie_fields.code, 2305 ) 2306 members_.append(members_fields) 2307 return tuple(members_) 2308 2309 def deserialize_available_fireteam( 2310 self, payload: typedefs.JSONObject 2311 ) -> fireteams.AvailableFireteam: 2312 fields = self._set_fireteam_fields(payload["Summary"]) 2313 return fireteams.AvailableFireteam( 2314 id=fields.id, 2315 group_id=fields.group_id, 2316 platform=fields.platform, 2317 activity_type=fields.activity_type, 2318 is_immediate=fields.is_immediate, 2319 is_public=fields.is_public, 2320 is_valid=fields.is_valid, 2321 owner_id=fields.owner_id, 2322 player_slot_count=fields.player_slot_count, 2323 available_player_slots=fields.available_player_slots, 2324 available_alternate_slots=fields.available_alternate_slots, 2325 title=fields.title, 2326 date_created=fields.date_created, 2327 locale=fields.locale, 2328 last_modified=fields.last_modified, 2329 total_results=fields.total_results, 2330 scheduled_time=fields.scheduled_time, 2331 date_modified=fields.date_modified, 2332 members=self.deserialize_fireteam_members(payload), 2333 alternatives=self.deserialize_fireteam_members(payload, alternatives=True), 2334 ) 2335 2336 def deserialize_available_fireteams( 2337 self, data: typedefs.JSONObject 2338 ) -> collections.Sequence[fireteams.AvailableFireteam]: 2339 if raw_results := data.get("results"): 2340 fireteam_results = tuple( 2341 self.deserialize_available_fireteam(f) for f in raw_results 2342 ) 2343 else: 2344 fireteam_results = () 2345 return fireteam_results 2346 2347 def deserialize_fireteam_party( 2348 self, payload: typedefs.JSONObject 2349 ) -> fireteams.FireteamParty: 2350 last_destination_hash: int | None = None 2351 if raw_dest_hash := payload.get("lastOrbitedDestinationHash"): 2352 last_destination_hash = int(raw_dest_hash) 2353 2354 return fireteams.FireteamParty( 2355 members=tuple( 2356 self._deserialize_fireteam_party_member(member) 2357 for member in payload["partyMembers"] 2358 ), 2359 activity=self._deserialize_fireteam_party_current_activity( 2360 payload["currentActivity"] 2361 ), 2362 settings=self._deserialize_fireteam_party_settings(payload["joinability"]), 2363 last_destination_hash=last_destination_hash, 2364 tracking=payload["tracking"], 2365 ) 2366 2367 def _deserialize_fireteam_party_member( 2368 self, payload: typedefs.JSONObject 2369 ) -> fireteams.FireteamPartyMember: 2370 status = fireteams.FireteamPartyMemberState(payload["status"]) 2371 2372 return fireteams.FireteamPartyMember( 2373 membership_id=int(payload["membershipId"]), 2374 emblem_hash=int(payload["emblemHash"]), 2375 status=status, 2376 display_name=payload["displayName"] if payload["displayName"] else None, 2377 ) 2378 2379 def _deserialize_fireteam_party_current_activity( 2380 self, payload: typedefs.JSONObject 2381 ) -> fireteams.FireteamPartyCurrentActivity: 2382 start_date: datetime.datetime | None = None 2383 if raw_start_date := payload.get("startTime"): 2384 start_date = time.clean_date(raw_start_date) 2385 2386 end_date: datetime.datetime | None = None 2387 if raw_end_date := payload.get("endTime"): 2388 end_date = time.clean_date(raw_end_date) 2389 return fireteams.FireteamPartyCurrentActivity( 2390 start_time=start_date, 2391 end_time=end_date, 2392 score=float(payload["score"]), 2393 highest_opposing_score=float(payload["highestOpposingFactionScore"]), 2394 opponents_count=int(payload["numberOfOpponents"]), 2395 player_count=int(payload["numberOfPlayers"]), 2396 ) 2397 2398 def _deserialize_fireteam_party_settings( 2399 self, payload: typedefs.JSONObject 2400 ) -> fireteams.FireteamPartySettings: 2401 closed_reasons = enums.ClosedReasons(payload["closedReasons"]) 2402 return fireteams.FireteamPartySettings( 2403 open_slots=int(payload["openSlots"]), 2404 privacy_setting=enums.PrivacySetting(int(payload["privacySetting"])), 2405 closed_reasons=closed_reasons, 2406 ) 2407 2408 def deserialize_seasonal_artifact( 2409 self, payload: typedefs.JSONObject 2410 ) -> season.Artifact: 2411 raw_artifact = payload["seasonalArtifact"] 2412 2413 points = raw_artifact["pointProgression"] 2414 points_prog = progressions.Progression( 2415 hash=points["progressionHash"], 2416 level=points["level"], 2417 cap=points["levelCap"], 2418 daily_limit=points["dailyLimit"], 2419 weekly_limit=points["weeklyLimit"], 2420 current_progress=points["currentProgress"], 2421 daily_progress=points["dailyProgress"], 2422 needed=points["progressToNextLevel"], 2423 next_level=points["nextLevelAt"], 2424 ) 2425 2426 bonus = raw_artifact["powerBonusProgression"] 2427 power_bonus_prog = progressions.Progression( 2428 hash=bonus["progressionHash"], 2429 level=bonus["level"], 2430 cap=bonus["levelCap"], 2431 daily_limit=bonus["dailyLimit"], 2432 weekly_limit=bonus["weeklyLimit"], 2433 current_progress=bonus["currentProgress"], 2434 daily_progress=bonus["dailyProgress"], 2435 needed=bonus["progressToNextLevel"], 2436 next_level=bonus["nextLevelAt"], 2437 ) 2438 return season.Artifact( 2439 hash=raw_artifact["artifactHash"], 2440 power_bonus=raw_artifact["powerBonus"], 2441 acquired_points=raw_artifact["pointsAcquired"], 2442 bonus=power_bonus_prog, 2443 points=points_prog, 2444 ) 2445 2446 def deserialize_profile_progression( 2447 self, payload: typedefs.JSONObject 2448 ) -> profile.ProfileProgression: 2449 return profile.ProfileProgression( 2450 artifact=self.deserialize_seasonal_artifact(payload["data"]), 2451 checklist={ 2452 int(check_id): checklists 2453 for check_id, checklists in payload["data"]["checklists"].items() 2454 }, 2455 ) 2456 2457 def deserialize_instanced_item( 2458 self, payload: typedefs.JSONObject 2459 ) -> items.ItemInstance: 2460 damage_type_hash: int | None = None 2461 if raw_damagetype_hash := payload.get("damageTypeHash"): 2462 damage_type_hash = int(raw_damagetype_hash) 2463 2464 required_hashes: typing.Optional[collections.Collection[int]] = None 2465 if raw_required_hashes := payload.get("unlockHashesRequiredToEquip"): 2466 required_hashes = tuple(int(raw_hash) for raw_hash in raw_required_hashes) 2467 2468 breaker_type: items.ItemBreakerType | None = None 2469 if raw_break_type := payload.get("breakerType"): 2470 breaker_type = items.ItemBreakerType(int(raw_break_type)) 2471 2472 breaker_type_hash: int | None = None 2473 if raw_break_type_hash := payload.get("breakerTypeHash"): 2474 breaker_type_hash = int(raw_break_type_hash) 2475 2476 energy: items.ItemEnergy | None = None 2477 if raw_energy := payload.get("energy"): 2478 energy = self.deserialize_item_energy(raw_energy) 2479 2480 primary_stats = None 2481 if raw_primary_stats := payload.get("primaryStat"): 2482 primary_stats = self.deserialize_item_stats_view(raw_primary_stats) 2483 2484 return items.ItemInstance( 2485 damage_type=enums.DamageType(int(payload["damageType"])), 2486 damage_type_hash=damage_type_hash, 2487 primary_stat=primary_stats, 2488 item_level=int(payload["itemLevel"]), 2489 quality=int(payload["quality"]), 2490 is_equipped=payload["isEquipped"], 2491 can_equip=payload["canEquip"], 2492 equip_required_level=int(payload["equipRequiredLevel"]), 2493 required_equip_unlock_hashes=required_hashes, 2494 cant_equip_reason=int(payload["cannotEquipReason"]), 2495 breaker_type=breaker_type, 2496 breaker_type_hash=breaker_type_hash, 2497 energy=energy, 2498 ) 2499 2500 def deserialize_item_energy(self, payload: typedefs.JSONObject) -> items.ItemEnergy: 2501 energy_hash: int | None = None 2502 if raw_energy_hash := payload.get("energyTypeHash"): 2503 energy_hash = int(raw_energy_hash) 2504 2505 return items.ItemEnergy( 2506 hash=energy_hash, 2507 type=items.ItemEnergyType(int(payload["energyType"])), 2508 capacity=int(payload["energyCapacity"]), 2509 used_energy=int(payload["energyUsed"]), 2510 unused_energy=int(payload["energyUnused"]), 2511 ) 2512 2513 def deserialize_item_perk(self, payload: typedefs.JSONObject) -> items.ItemPerk: 2514 perk_hash: int | None = None 2515 if raw_perk_hash := payload.get("perkHash"): 2516 perk_hash = int(raw_perk_hash) 2517 2518 return items.ItemPerk( 2519 hash=perk_hash, 2520 icon=builders.Image(path=payload["iconPath"]), 2521 is_active=payload["isActive"], 2522 is_visible=payload["visible"], 2523 ) 2524 2525 def deserialize_item_socket(self, payload: typedefs.JSONObject) -> items.ItemSocket: 2526 plug_hash: int | None = None 2527 if raw_plug_hash := payload.get("plugHash"): 2528 plug_hash = int(raw_plug_hash) 2529 2530 enable_fail_indexes: collections.Sequence[int] | None = None 2531 if raw_indexes := payload.get("enableFailIndexes"): 2532 enable_fail_indexes = tuple(int(index) for index in raw_indexes) 2533 2534 return items.ItemSocket( 2535 plug_hash=plug_hash, 2536 is_enabled=payload["isEnabled"], 2537 enable_fail_indexes=enable_fail_indexes, 2538 is_visible=payload.get("visible"), 2539 ) 2540 2541 def deserialize_item_stats_view( 2542 self, payload: typedefs.JSONObject 2543 ) -> items.ItemStatsView: 2544 return items.ItemStatsView( 2545 stat_hash=payload.get("statHash"), value=payload.get("value") 2546 ) 2547 2548 def deserialize_plug_item_state( 2549 self, payload: typedefs.JSONObject 2550 ) -> items.PlugItemState: 2551 item_hash: int | None = None 2552 if raw_item_hash := payload.get("plugItemHash"): 2553 item_hash = int(raw_item_hash) 2554 2555 insert_fail_indexes: collections.Sequence[int] | None = None 2556 if raw_fail_indexes := payload.get("insertFailIndexes"): 2557 insert_fail_indexes = tuple(int(k) for k in raw_fail_indexes) 2558 2559 enable_fail_indexes: collections.Sequence[int] | None = None 2560 if raw_enabled_indexes := payload.get("enableFailIndexes"): 2561 enable_fail_indexes = tuple(int(k) for k in raw_enabled_indexes) 2562 2563 return items.PlugItemState( 2564 item_hash=item_hash, 2565 insert_fail_indexes=insert_fail_indexes, 2566 enable_fail_indexes=enable_fail_indexes, 2567 is_enabled=payload["enabled"], 2568 can_insert=payload["canInsert"], 2569 )
The base deserialization framework implementation.
This framework is used to deserialize JSON responses from the REST client and turning them into a aiobungie.crates
Python classes.
Example
import aiobungie
from aiobungie import traits
from aiobungie import framework
class MyClient(traits.Deserialize):
def __init__(self) -> None:
self.rest = aiobungie.RESTClient("token")
self.my_name = "Fate怒"
self.my_code = 4275
@property # required method.
def framework(self) -> framework.Empty:
return framework.Global
async def my_memberships(self):
response = await self.rest.fetch_membership(self.my_name, self.my_code)
return self.factory.deserialize_destiny_memberships(response)
async def main() -> None:
client = MyClient()
async with client.rest:
print(await client.my_memberships())
asyncio.run(main())
105 def deserialize_bungie_user(self, data: typedefs.JSONObject) -> user.BungieUser: 106 return user.BungieUser( 107 id=int(data["membershipId"]), 108 created_at=time.clean_date(data["firstAccess"]), 109 name=data.get("cachedBungieGlobalDisplayName"), 110 is_deleted=data["isDeleted"], 111 about=data["about"], 112 updated_at=time.clean_date(data["lastUpdate"]), 113 psn_name=data.get("psnDisplayName", None), 114 stadia_name=data.get("stadiaDisplayName", None), 115 steam_name=data.get("steamDisplayName", None), 116 twitch_name=data.get("twitchDisplayName", None), 117 blizzard_name=data.get("blizzardDisplayName", None), 118 egs_name=data.get("egsDisplayName", None), 119 status=data["statusText"], 120 locale=data["locale"], 121 picture=builders.Image(path=data["profilePicturePath"]), 122 code=data.get("cachedBungieGlobalDisplayNameCode", None), 123 unique_name=data.get("uniqueName", None), 124 theme_id=int(data["profileTheme"]), 125 show_activity=bool(data["showActivity"]), 126 theme_name=data["profileThemeName"], 127 display_title=data["userTitleDisplay"], 128 profile_ban_expire=time.clean_date(data["profileBanExpire"]) 129 if "profileBanExpire" in data 130 else None, 131 )
Deserialize a raw JSON Bungie.net user only payload into a user object.
This only returns the Bungie.net user and not the Destiny memberships.
Parameters
- data (
aiobungie.typedefs.JSONObject
): The JSON data/payload.
Returns
aiobungie.crates.BungieUser
: A Bungie user.
133 def deserialize_partial_bungie_user( 134 self, payload: typedefs.JSONObject 135 ) -> user.PartialBungieUser: 136 return user.PartialBungieUser( 137 types=tuple( 138 enums.MembershipType(type_) 139 for type_ in payload.get("applicableMembershipTypes", ()) 140 ), 141 name=payload["bungieGlobalDisplayName"] 142 if "bungieGlobalDisplayName" in payload 143 else payload.get("displayName"), 144 id=int(payload["membershipId"]), 145 crossave_override=enums.MembershipType(payload["crossSaveOverride"]), 146 is_public=payload["isPublic"], 147 icon=builders.Image(path=payload.get("iconPath", "")), 148 type=enums.MembershipType(payload["membershipType"]), 149 code=payload.get("bungieGlobalDisplayNameCode"), 150 )
Deserialize a raw JSON of a partial bungieNetUserInfo
.
A partial user is a bungie.net user payload with missing information from
the main BungieUser
object.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
aiobungie.crates.PartialBungieUser
: A partial bungie user.
152 def deserialize_destiny_membership( 153 self, payload: typedefs.JSONObject 154 ) -> user.DestinyMembership: 155 name: str | None = None 156 if (raw_name := payload.get("bungieGlobalDisplayName")) is not None: 157 name = typedefs.unknown(raw_name) 158 159 return user.DestinyMembership( 160 id=int(payload["membershipId"]), 161 name=name, 162 code=payload.get("bungieGlobalDisplayNameCode", None), 163 last_seen_name=payload.get("LastSeenDisplayName") 164 or payload.get("displayName") # noqa: W503 165 or "", # noqa: W503 166 type=enums.MembershipType(payload["membershipType"]), 167 is_public=payload["isPublic"], 168 crossave_override=enums.MembershipType(payload["crossSaveOverride"]), 169 icon=builders.Image(path=payload.get("iconPath", "")), 170 types=tuple( 171 enums.MembershipType(type_) 172 for type_ in payload.get("applicableMembershipTypes", ()) 173 ), 174 )
Deserialize a raw JSON of destinyUserInfo
destiny membership information.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
aiobungie.crates.user.DestinyMembership
: A Destiny 2 membership.
176 def deserialize_destiny_memberships( 177 self, data: typedefs.JSONArray 178 ) -> collections.Sequence[user.DestinyMembership]: 179 return tuple( 180 self.deserialize_destiny_membership(membership) for membership in data 181 )
Deserialize a raw JSON payload/array of destinyUserInfo
.
Parameters
- payload (
aiobungie.typedefs.JSONArray
): The JSON payload.
Returns
collections.Sequence[aiobungie.crates.user.DestinyMembership]
: A sequence of Destiny 2 memberships.
183 def deserialize_user(self, data: typedefs.JSONObject) -> user.User: 184 primary_membership_id: int | None = None 185 if raw_primary_id := data.get("primaryMembershipId"): 186 primary_membership_id = int(raw_primary_id) 187 188 return user.User( 189 bungie_user=self.deserialize_bungie_user(data["bungieNetUser"]), 190 memberships=self.deserialize_destiny_memberships( 191 data["destinyMemberships"] 192 ), 193 primary_membership_id=primary_membership_id, 194 )
Deserialize a raw JSON results of fetched user memberships and Bungie.net user its their id.
Parameters
- data (
aiobungie.typedefs.JSONObject
): The JSON data/payload.
Returns
aiobungie.crates.User
: A user object.
196 def deserialize_sanitized_membership( 197 self, payload: typedefs.JSONObject 198 ) -> user.SanitizedMembership: 199 return user.SanitizedMembership( 200 epic_games=payload.get("EgsId"), 201 psn=payload.get("PsnId"), 202 steam=payload.get("SteamId"), 203 xbox=payload.get("XboxId"), 204 twitch=payload.get("TwitchId"), 205 )
Deserialize a raw JSON payload of a sanitized Destiny 2 membership display names.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
aiobungie.crates.SanitizedMembership
: A sanitized Destiny 2 membership that contains the display names.
207 def deserialize_searched_user( 208 self, payload: typedefs.JSONObject 209 ) -> user.SearchableDestinyUser: 210 code: int | None = None 211 if raw_code := payload.get("bungieGlobalDisplayNameCode"): 212 code = int(raw_code) 213 214 bungie_id: int | None = None 215 if raw_bungie_id := payload.get("bungieNetMembershipId"): 216 bungie_id = int(raw_bungie_id) 217 218 return user.SearchableDestinyUser( 219 name=typedefs.unknown(payload["bungieGlobalDisplayName"]), 220 code=code, 221 bungie_id=bungie_id, 222 memberships=self.deserialize_destiny_memberships( 223 payload["destinyMemberships"] 224 ), 225 )
Deserialize the results of user search details.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
aiobungie.crates.SearchableDestinyUser
: The searched for Destiny 2 membership.
227 def deserialize_user_credentials( 228 self, payload: typedefs.JSONArray 229 ) -> collections.Sequence[user.UserCredentials]: 230 return tuple( 231 user.UserCredentials( 232 type=enums.CredentialType(int(credit["credentialType"])), 233 display_name=credit["credentialDisplayName"], 234 is_public=credit["isPublic"], 235 self_as_string=credit.get("credentialAsString"), 236 ) 237 for credit in payload 238 )
Deserialize a JSON array of Bungie user credentials.
Parameters
- payload (
aiobungie.typedefs.JSONArray
): The JSON payload.
Returns
collections.Sequence[aiobungie.crates.UserCredentials]
: A sequence of user's credentials.
240 def deserialize_user_themes( 241 self, payload: typedefs.JSONArray 242 ) -> collections.Sequence[user.UserThemes]: 243 return tuple( 244 user.UserThemes( 245 id=int(entry["userThemeId"]), 246 name=entry["userThemeName"] if "userThemeName" in entry else None, 247 description=entry["userThemeDescription"] 248 if "userThemeDescription" in entry 249 else None, 250 ) 251 for entry in payload 252 )
Deserialize a raw JSON array of Bungie user themes.
Parameters
- payload (
aiobungie.typedefs.JSONArray
): The JSON payload.
Returns
collections.Sequence[aiobungie.crates.user.UserThemes]
: A sequence of bungie user themes.
254 def deserialize_group(self, payload: typedefs.JSONObject) -> clans.Group: 255 clan_info_obj: typedefs.JSONObject | None = None 256 if raw_clan_info := payload.get("clanInfo"): 257 clan_info_obj = clans.ClanInfo( 258 call_sign=raw_clan_info["clanCallsign"], 259 banner_data=raw_clan_info["clanBannerData"], 260 ) 261 return clans.Group( 262 group_id=int(payload["groupId"]), 263 name=payload["name"], 264 creation_date=time.clean_date(payload["creationDate"]), 265 about=payload["about"], 266 clan_info=clan_info_obj, 267 motto=payload.get("motto"), 268 member_count=int(payload["memberCount"]), 269 locale=payload["locale"], 270 membership_option=int(payload["membershipOption"]), 271 capabilities=int(payload["capabilities"]), 272 remote_group_id=int(payload["remoteGroupId"]) 273 if "remoteGroupId" in payload 274 else None, 275 group_type=enums.GroupType(int(payload["groupType"])), 276 avatar_path=builders.Image(payload["avatarPath"]), 277 theme=payload["theme"], 278 )
Deserialize a GroupV2Card
payload into a Group
object.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): THe payload to deserialize
Returns
aiobungie.crates.Group
: A group object
328 def deserialize_clan(self, payload: typedefs.JSONObject) -> clans.Clan: 329 current_user_map: collections.Mapping[str, clans.ClanMember] | None = None 330 if raw_current_user := payload.get("currentUserMemberMap"): 331 # This will get populated if only it was a GroupsV2.GroupResponse. 332 # GroupsV2.GetGroupsForMemberResponse doesn't have this field. 333 current_user_map = { 334 membership_type: self.deserialize_clan_member(membership) 335 for membership_type, membership in raw_current_user.items() 336 } 337 338 return self._deserialize_group_details( 339 data=payload["detail"], 340 clan_founder=self.deserialize_clan_member(payload["founder"]), 341 current_user_memberships=current_user_map, 342 )
Deserialize a raw JSON payload of Destiny clan information.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
aiobungie.crates.Clan
: A Destiny clan object.
344 def deserialize_clan_member(self, data: typedefs.JSONObject, /) -> clans.ClanMember: 345 destiny_user = self.deserialize_destiny_membership(data["destinyUserInfo"]) 346 return clans.ClanMember( 347 last_seen_name=destiny_user.last_seen_name, 348 id=destiny_user.id, 349 name=destiny_user.name, 350 icon=destiny_user.icon, 351 last_online=time.from_timestamp(int(data["lastOnlineStatusChange"])), 352 group_id=int(data["groupId"]), 353 joined_at=time.clean_date(data["joinDate"]), 354 types=destiny_user.types, 355 is_public=destiny_user.is_public, 356 type=destiny_user.type, 357 code=destiny_user.code, 358 is_online=data["isOnline"], 359 crossave_override=destiny_user.crossave_override, 360 bungie_user=self.deserialize_partial_bungie_user(data["bungieNetUserInfo"]) 361 if "bungieNetUserInfo" in data 362 else None, 363 member_type=enums.ClanMemberType(int(data["memberType"])), 364 )
Deserialize a JSON payload of a clan member information.
Parameters
- data (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
aiobungie.crates.ClanMember
: A Destiny clan member.
366 def deserialize_clan_members( 367 self, data: typedefs.JSONObject, / 368 ) -> sain.Iterator[clans.ClanMember]: 369 return sain.Iter( 370 self.deserialize_clan_member(member) for member in data["results"] 371 )
Deserialize a JSON payload of a clan members information.
Parameters
- data (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
Iterator[aiobungie.crates.ClanMember]
: An iterator over the clan members of the deserialized payload.
373 def deserialize_group_member( 374 self, payload: typedefs.JSONObject 375 ) -> clans.GroupMember: 376 member = payload["member"] 377 return clans.GroupMember( 378 join_date=time.clean_date(member["joinDate"]), 379 group_id=int(member["groupId"]), 380 member_type=enums.ClanMemberType(member["memberType"]), 381 is_online=member["isOnline"], 382 last_online=time.from_timestamp(int(member["lastOnlineStatusChange"])), 383 inactive_memberships=payload.get("areAllMembershipsInactive", None), 384 member=self.deserialize_destiny_membership(member["destinyUserInfo"]), 385 group=self._deserialize_group_details(payload["group"]), 386 )
Deserialize a JSON payload of group information for a member.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
aiobungie.crates.GroupMember
: A group member object.
399 def deserialize_clan_conversations( 400 self, payload: typedefs.JSONArray 401 ) -> collections.Sequence[clans.ClanConversation]: 402 return tuple(self._deserialize_clan_conversation(conv) for conv in payload)
Deserialize a JSON array of a clan conversations information.
Parameters
- payload (
aiobungie.typedefs.JSONArray
): The JSON payload.
Returns
collections.Sequence[aiobungie.crates.ClanConversation]
: A sequence of clan conversations of the deserialized payload.
404 def deserialize_application_member( 405 self, payload: typedefs.JSONObject 406 ) -> application.ApplicationMember: 407 return application.ApplicationMember( 408 api_eula_version=payload["apiEulaVersion"], 409 role=payload["role"], 410 user=self.deserialize_partial_bungie_user(payload["user"]), 411 )
Deserialize a JSON payload of a Bungie developer portal application member.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
aiobungie.crates.application.ApplicationMember
: An application member.
413 def deserialize_application( 414 self, payload: typedefs.JSONObject 415 ) -> application.Application: 416 return application.Application( 417 id=int(payload["applicationId"]), 418 name=payload["name"], 419 origin=payload["origin"], 420 link=payload["link"], 421 status=payload["status"], 422 redirect_url=payload.get("redirectUrl", None), 423 created_at=time.clean_date(payload["creationDate"]), 424 status_changed=time.clean_date(payload["statusChanged"]), 425 published_at=time.clean_date(payload["firstPublished"]), 426 team=tuple( 427 self.deserialize_application_member(member) 428 for member in payload["team"] 429 ), 430 scope=payload.get("scope"), 431 )
Deserialize a JSON payload of Bungie Developer portal application information.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
aiobungie.crates.application.Application
: An application.
471 def deserialize_profile(self, payload: typedefs.JSONObject, /) -> profile.Profile: 472 return profile.Profile( 473 user=self.deserialize_destiny_membership(payload["userInfo"]), 474 last_played=time.clean_date(payload["dateLastPlayed"]), 475 versions_owned=enums.GameVersions(int(payload["versionsOwned"])), 476 character_ids=tuple( 477 int(character_id) for character_id in payload["characterIds"] 478 ), 479 season_hashes=tuple(payload["seasonHashes"]), 480 event_card_hashes=tuple(payload["eventCardHashesOwned"]), 481 season_hash=payload["currentSeasonHash"], 482 power_cap=payload["currentSeasonRewardPowerCap"], 483 guardian_rank=payload["currentGuardianRank"], 484 highest_guardian_rank=payload["lifetimeHighestGuardianRank"], 485 renewed_guardian_rank=payload["renewedGuardianRank"], 486 )
Deserialize a JSON payload of Bungie.net profile information.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
aiobungie.crates.Profile
: A profile object of the deserialized payload.
488 def deserialize_profile_item( 489 self, payload: typedefs.JSONObject 490 ) -> profile.ProfileItemImpl: 491 instance_id: int | None = None 492 if raw_instance_id := payload.get("itemInstanceId"): 493 instance_id = int(raw_instance_id) 494 495 version_number: int | None = None 496 if raw_version := payload.get("versionNumber"): 497 version_number = int(raw_version) 498 499 transfer_status = enums.TransferStatus(payload["transferStatus"]) 500 501 return profile.ProfileItemImpl( 502 hash=payload["itemHash"], 503 quantity=payload["quantity"], 504 bind_status=enums.ItemBindStatus(payload["bindStatus"]), 505 location=enums.ItemLocation(payload["location"]), 506 bucket=payload["bucketHash"], 507 transfer_status=transfer_status, 508 lockable=payload["lockable"], 509 state=enums.ItemState(payload["state"]), 510 dismantle_permissions=payload["dismantlePermission"], 511 is_wrapper=payload["isWrapper"], 512 instance_id=instance_id, 513 version_number=version_number, 514 ornament_id=payload.get("overrideStyleItemHash"), 515 )
Deserialize a JSON payload of a singular profile component item.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
aiobungie.crates.ProfileItemImpl
: Implementation of a Destiny 2 profile component item.
517 def deserialize_objectives(self, payload: typedefs.JSONObject) -> records.Objective: 518 return records.Objective( 519 hash=payload["objectiveHash"], 520 visible=payload["visible"], 521 complete=payload["complete"], 522 completion_value=payload["completionValue"], 523 progress=payload.get("progress"), 524 destination_hash=payload.get("destinationHash"), 525 activity_hash=payload.get("activityHash"), 526 )
Deserialize a JSON payload of an objective found in a record profile component.
Parameters
- payload (
aiobungie.internal.helpers.JsonObject
): The JSON payload.
Returns
aiobungie.crates.records.Objective
: A record objective object.
529 def deserialize_records( 530 self, 531 payload: typedefs.JSONObject, 532 scores: records.RecordScores | None = None, 533 **nodes: int, 534 ) -> records.Record: 535 objectives: collections.Sequence[records.Objective] | None = None 536 interval_objectives: collections.Sequence[records.Objective] | None = None 537 record_state: records.RecordState | int 538 539 record_state = records.RecordState(payload["state"]) 540 541 if raw_objs := payload.get("objectives"): 542 objectives = tuple(self.deserialize_objectives(obj) for obj in raw_objs) 543 544 if raw_interval_objs := payload.get("intervalObjectives"): 545 interval_objectives = tuple( 546 self.deserialize_objectives(obj) for obj in raw_interval_objs 547 ) 548 549 return records.Record( 550 scores=scores, 551 categories_node_hash=nodes.get("categories_hash"), 552 seals_node_hash=nodes.get("seals_hash"), 553 state=record_state, 554 objectives=objectives, 555 interval_objectives=interval_objectives, 556 redeemed_count=payload.get("intervalsRedeemedCount", 0), 557 completion_times=payload.get("completedCount", None), 558 reward_visibility=payload.get("rewardVisibility"), 559 )
Deserialize a JSON object of a profile record component.
Parameters
- payload (
aiobungie.internal.helpers.JsonObject
): The JSON object payload - scores (
records.RecordScores | None
): The records scores object. This exists only to keep the signature ofaiobungie.crates.CharacterRecord
with the record object. As it will always beNone
in that object. - **nodes (
int
): An int kwargs use to grab the node hashes while deserializing components.
Returns
aiobungie.records.Record
: A standard implementation of a profile record component.
561 def deserialize_character_records( 562 self, 563 payload: typedefs.JSONObject, 564 scores: records.RecordScores | None = None, 565 record_hashes: collections.Sequence[int] = (), 566 ) -> records.CharacterRecord: 567 record = self.deserialize_records(payload, scores) 568 return records.CharacterRecord( 569 scores=scores, 570 categories_node_hash=record.categories_node_hash, 571 seals_node_hash=record.seals_node_hash, 572 state=record.state, 573 objectives=record.objectives, 574 interval_objectives=record.interval_objectives, 575 redeemed_count=payload.get("intervalsRedeemedCount", 0), 576 completion_times=payload.get("completedCount"), 577 reward_visibility=payload.get("rewardVisibility"), 578 record_hashes=record_hashes, 579 )
Deserialize a JSON object of a profile character record component.
This almost does the same this as deserialize_records
but
has more fields which can only be found in a character record.
Parameters
- payload (
aiobungie.internal.helpers.JsonObject
): The JSON object payload
Returns
aiobungie.records.CharacterRecord
: A standard implementation of a profile character record component.
581 def deserialize_character_dye(self, payload: typedefs.JSONObject) -> character.Dye: 582 return character.Dye( 583 channel_hash=payload["channelHash"], dye_hash=payload["dyeHash"] 584 )
Deserialize a JSON payload of a character's dye information.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
aiobungie.crates.character.Dye
: Information about a character dye object.
586 def deserialize_character_customization( 587 self, payload: typedefs.JSONObject 588 ) -> character.CustomizationOptions: 589 return character.CustomizationOptions( 590 personality=payload["personality"], 591 face=payload["face"], 592 skin_color=payload["skinColor"], 593 lip_color=payload["lipColor"], 594 eye_color=payload["eyeColor"], 595 hair_colors=payload.get("hairColors", ()), 596 feature_colors=payload.get("featureColors", ()), 597 decal_color=payload["decalColor"], 598 wear_helmet=payload["wearHelmet"], 599 hair_index=payload["hairIndex"], 600 feature_index=payload["featureIndex"], 601 decal_index=payload["decalIndex"], 602 )
Deserialize a JSON payload of a character customization information found in character render data profile component.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
aiobungie.crates.character.CustomizationOptions
: Information about a character customs object.
604 def deserialize_character_minimal_equipments( 605 self, payload: typedefs.JSONObject 606 ) -> character.MinimalEquipments: 607 if raw_dyes := payload.get("dyes"): 608 dyes = tuple(self.deserialize_character_dye(dye) for dye in raw_dyes) 609 else: 610 dyes = () 611 612 return character.MinimalEquipments(item_hash=payload["itemHash"], dyes=dyes)
Deserialize a singular JSON peer view of equipment found in character render data profile component.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
aiobungie.crates.character.MinimalEquipments
: A minimal equipment object.
614 def deserialize_character_render_data( 615 self, payload: typedefs.JSONObject, / 616 ) -> character.RenderedData: 617 return character.RenderedData( 618 customization=self.deserialize_character_customization( 619 payload["customization"] 620 ), 621 custom_dyes=tuple( 622 self.deserialize_character_dye(dye) 623 for dye in payload["customDyes"] 624 if dye 625 ), 626 equipment=tuple( 627 self.deserialize_character_minimal_equipments(equipment) 628 for equipment in payload["peerView"]["equipment"] 629 ), 630 )
Deserialize a JSON payload of a profile character render data component.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
aiobungie.crates.RenderedData
: A character rendered data profile component.
632 def deserialize_available_activity( 633 self, payload: typedefs.JSONObject 634 ) -> activity.AvailableActivity: 635 return activity.AvailableActivity( 636 hash=payload["activityHash"], 637 is_new=payload["isNew"], 638 is_completed=payload["isCompleted"], 639 is_visible=payload["isVisible"], 640 display_level=payload.get("displayLevel"), 641 recommended_light=payload.get("recommendedLight"), 642 difficulty=activity.Difficulty(payload["difficultyTier"]), 643 can_join=payload["canJoin"], 644 can_lead=payload["canLead"], 645 )
Deserialize a JSON payload of an available activities.
This method is used to deserialize an array of aiobungie.crates.CharacterActivity.available_activities
.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
aiobungie.crates.AvailableActivity
: An available activity object.
647 def deserialize_character_activity( 648 self, payload: typedefs.JSONObject 649 ) -> activity.CharacterActivity: 650 current_mode = enums.GameMode.NONE 651 if raw_current_mode := payload.get("currentActivityModeType"): 652 current_mode = enums.GameMode(raw_current_mode) 653 654 if raw_current_modes := payload.get("currentActivityModeTypes"): 655 current_mode_types = tuple( 656 enums.GameMode(type_) for type_ in raw_current_modes 657 ) 658 else: 659 current_mode_types = () 660 661 return activity.CharacterActivity( 662 date_started=time.clean_date(payload["dateActivityStarted"]), 663 current_hash=payload["currentActivityHash"], 664 current_mode_hash=payload["currentActivityModeHash"], 665 current_mode=current_mode, 666 current_mode_hashes=payload.get("currentActivityModeHashes", ()), 667 current_mode_types=current_mode_types, 668 current_playlist_hash=payload.get("currentPlaylistActivityHash"), 669 last_story_hash=payload["lastCompletedStoryHash"], 670 available_activities=tuple( 671 self.deserialize_available_activity(activity_) 672 for activity_ in payload["availableActivities"] 673 ), 674 available_activity_interactables=tuple( 675 activity.InteractablesRef( 676 hash=interact["activityInteractableHash"], 677 element_index=interact["activityInteractableElementIndex"], 678 ) 679 for interact in payload["availableActivityInteractables"] 680 ), 681 )
Deserialize a JSON payload of character activity profile component.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
aiobungie.crates.CharacterActivity
: A character activities component object.
683 def deserialize_profile_items( 684 self, payload: typedefs.JSONObject, / 685 ) -> collections.Sequence[profile.ProfileItemImpl]: 686 return tuple(self.deserialize_profile_item(item) for item in payload["items"])
Deserialize a JSON payload of profile items component information.
This may deserialize profileInventories
or profileCurrencies
or any
other alternatives.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
collections.Sequence[aiobungie.crates.ProfileItemImpl]
: A profile component object that contains items of the deserialized payload.
729 def deserialize_progressions( 730 self, payload: typedefs.JSONObject 731 ) -> progressions.Progression: 732 return progressions.Progression( 733 hash=int(payload["progressionHash"]), 734 level=int(payload["level"]), 735 cap=int(payload["levelCap"]), 736 daily_limit=int(payload["dailyLimit"]), 737 weekly_limit=int(payload["weeklyLimit"]), 738 current_progress=int(payload["currentProgress"]), 739 daily_progress=int(payload["dailyProgress"]), 740 needed=int(payload["progressToNextLevel"]), 741 next_level=int(payload["nextLevelAt"]), 742 )
826 def deserialize_milestone( 827 self, payload: typedefs.JSONObject 828 ) -> milestones.Milestone: 829 start_date: datetime.datetime | None = None 830 if raw_start_date := payload.get("startDate"): 831 start_date = time.clean_date(raw_start_date) 832 833 end_date: datetime.datetime | None = None 834 if raw_end_date := payload.get("endDate"): 835 end_date = time.clean_date(raw_end_date) 836 837 rewards: collections.Collection[milestones.MilestoneReward] | None = None 838 if raw_rewards := payload.get("rewards"): 839 rewards = tuple( 840 self._deserialize_milestone_rewards(reward) for reward in raw_rewards 841 ) 842 843 activities: collections.Sequence[milestones.MilestoneActivity] | None = None 844 if raw_activities := payload.get("activities"): 845 activities = tuple( 846 self._deserialize_milestone_activity(active) 847 for active in raw_activities 848 ) 849 850 quests: collections.Sequence[milestones.MilestoneQuest] | None = None 851 if raw_quests := payload.get("availableQuests"): 852 quests = tuple( 853 self._deserialize_milestone_available_quest(quest) 854 for quest in raw_quests 855 ) 856 857 vendors: collections.Sequence[milestones.MilestoneVendor] | None = None 858 if raw_vendors := payload.get("vendors"): 859 vendors = tuple( 860 milestones.MilestoneVendor( 861 vendor_hash=vendor["vendorHash"], 862 preview_itemhash=vendor.get("previewItemHash"), 863 ) 864 for vendor in raw_vendors 865 ) 866 867 return milestones.Milestone( 868 hash=payload["milestoneHash"], 869 start_date=start_date, 870 end_date=end_date, 871 order=payload["order"], 872 rewards=rewards, 873 available_quests=quests, 874 activities=activities, 875 vendors=vendors, 876 )
930 def deserialize_character_progressions( 931 self, payload: typedefs.JSONObject 932 ) -> character.CharacterProgression: 933 progressions_ = { 934 int(prog_id): self.deserialize_progressions(prog) 935 for prog_id, prog in payload["progressions"].items() 936 } 937 938 factions = { 939 int(faction_id): self._deserialize_factions(faction) 940 for faction_id, faction in payload["factions"].items() 941 } 942 943 milestones_ = { 944 int(milestone_hash): self.deserialize_milestone(milestone) 945 for milestone_hash, milestone in payload["milestones"].items() 946 } 947 948 uninstanced_item_objectives = { 949 int(item_hash): [self.deserialize_objectives(ins) for ins in obj] 950 for item_hash, obj in payload["uninstancedItemObjectives"].items() 951 } 952 953 artifact = payload["seasonalArtifact"] 954 seasonal_artifact = season.CharacterScopedArtifact( 955 hash=artifact["artifactHash"], 956 points_used=artifact["pointsUsed"], 957 reset_count=artifact["resetCount"], 958 tiers=tuple( 959 self._deserialize_artifact_tiers(tier) for tier in artifact["tiers"] 960 ), 961 ) 962 checklists = payload["checklists"] 963 964 return character.CharacterProgression( 965 progressions=progressions_, 966 factions=factions, 967 checklists=checklists, 968 milestones=milestones_, 969 seasonal_artifact=seasonal_artifact, 970 uninstanced_item_objectives=uninstanced_item_objectives, 971 )
973 def deserialize_character_progressions_mapping( 974 self, payload: typedefs.JSONObject 975 ) -> collections.Mapping[int, character.CharacterProgression]: 976 character_progressions: collections.MutableMapping[ 977 int, character.CharacterProgression 978 ] = {} 979 for char_id, data in payload["data"].items(): 980 character_progressions[int(char_id)] = ( 981 self.deserialize_character_progressions(data) 982 ) 983 return character_progressions
993 def deserialize_character_loadout( 994 self, payload: typedefs.JSONObject 995 ) -> character.Loadout: 996 return character.Loadout( 997 color_hash=payload["colorHash"], 998 icon_hash=payload["iconHash"], 999 name_hash=payload["nameHash"], 1000 items=tuple( 1001 self._deserialize_character_loadout_item(item) 1002 for item in payload["items"] 1003 ), 1004 )
1006 def deserialize_characters_records( 1007 self, 1008 payload: typedefs.JSONObject, 1009 ) -> collections.Mapping[int, records.CharacterRecord]: 1010 return { 1011 int(rec_id): self.deserialize_character_records( 1012 rec, record_hashes=payload.get("featuredRecordHashes", ()) 1013 ) 1014 for rec_id, rec in payload["records"].items() 1015 }
1017 def deserialize_profile_records( 1018 self, payload: typedefs.JSONObject 1019 ) -> collections.Mapping[int, records.Record]: 1020 raw_profile_records = payload["data"] 1021 scores = records.RecordScores( 1022 current_score=raw_profile_records["score"], 1023 legacy_score=raw_profile_records["legacyScore"], 1024 lifetime_score=raw_profile_records["lifetimeScore"], 1025 ) 1026 return { 1027 int(record_id): self.deserialize_records( 1028 record, 1029 scores, 1030 categories_hash=raw_profile_records["recordCategoriesRootNodeHash"], 1031 seals_hash=raw_profile_records["recordSealsRootNodeHash"], 1032 ) 1033 for record_id, record in raw_profile_records["records"].items() 1034 }
1070 def deserialize_craftables_component( 1071 self, payload: typedefs.JSONObject 1072 ) -> components.CraftablesComponent: 1073 return components.CraftablesComponent( 1074 craftables={ 1075 int(item_id): self._deserialize_craftable_item(item) 1076 for item_id, item in payload["craftables"].items() 1077 if item is not None 1078 }, 1079 crafting_root_node_hash=payload["craftingRootNodeHash"], 1080 )
1108 def deserialize_components( # noqa: C901 Too complex. 1109 self, payload: typedefs.JSONObject 1110 ) -> components.Component: 1111 # Due to how complex this method is, We'll stick to 1112 # typing.Optional here. 1113 1114 profile_: profile.Profile | None = None 1115 if raw_profile := payload.get("profile"): 1116 profile_ = self.deserialize_profile(raw_profile["data"]) 1117 1118 profile_progression: profile.ProfileProgression | None = None 1119 if raw_profile_progression := payload.get("profileProgression"): 1120 profile_progression = self.deserialize_profile_progression( 1121 raw_profile_progression 1122 ) 1123 1124 profile_currencies: typing.Optional[ 1125 collections.Sequence[profile.ProfileItemImpl] 1126 ] = None 1127 if raw_profile_currencies := payload.get("profileCurrencies"): 1128 if "data" in raw_profile_currencies: 1129 profile_currencies = self.deserialize_profile_items( 1130 raw_profile_currencies["data"] 1131 ) 1132 1133 profile_inventories: typing.Optional[ 1134 collections.Sequence[profile.ProfileItemImpl] 1135 ] = None 1136 if raw_profile_inventories := payload.get("profileInventory"): 1137 if "data" in raw_profile_inventories: 1138 profile_inventories = self.deserialize_profile_items( 1139 raw_profile_inventories["data"] 1140 ) 1141 1142 profile_records: typing.Optional[collections.Mapping[int, records.Record]] = ( 1143 None 1144 ) 1145 1146 if raw_profile_records_ := payload.get("profileRecords"): 1147 profile_records = self.deserialize_profile_records(raw_profile_records_) 1148 1149 characters: typing.Optional[collections.Mapping[int, character.Character]] = ( 1150 None 1151 ) 1152 if raw_characters := payload.get("characters"): 1153 characters = self.deserialize_characters(raw_characters) 1154 1155 character_records: typing.Optional[ 1156 collections.Mapping[int, records.CharacterRecord] 1157 ] = None 1158 1159 if raw_character_records := payload.get("characterRecords"): 1160 # Had to do it in two steps.. 1161 to_update = {} 1162 for _, data in raw_character_records["data"].items(): 1163 for record_id, record in data.items(): 1164 to_update[record_id] = record 1165 1166 character_records = { 1167 int(rec_id): self.deserialize_character_records( 1168 rec, record_hashes=to_update.get("featuredRecordHashes", ()) 1169 ) 1170 for rec_id, rec in to_update["records"].items() 1171 } 1172 1173 character_equipments: typing.Optional[ 1174 collections.Mapping[int, collections.Sequence[profile.ProfileItemImpl]] 1175 ] = None 1176 if raw_character_equips := payload.get("characterEquipment"): 1177 character_equipments = self.deserialize_character_equipments( 1178 raw_character_equips 1179 ) 1180 1181 character_inventories: typing.Optional[ 1182 collections.Mapping[int, collections.Sequence[profile.ProfileItemImpl]] 1183 ] = None 1184 if raw_character_inventories := payload.get("characterInventories"): 1185 if "data" in raw_character_inventories: 1186 character_inventories = self.deserialize_character_equipments( 1187 raw_character_inventories 1188 ) 1189 1190 character_activities: typing.Optional[ 1191 collections.Mapping[int, activity.CharacterActivity] 1192 ] = None 1193 if raw_char_acts := payload.get("characterActivities"): 1194 character_activities = self.deserialize_character_activities(raw_char_acts) 1195 1196 character_render_data: typing.Optional[ 1197 collections.Mapping[int, character.RenderedData] 1198 ] = None 1199 if raw_character_render_data := payload.get("characterRenderData"): 1200 character_render_data = self.deserialize_characters_render_data( 1201 raw_character_render_data 1202 ) 1203 1204 character_progressions: typing.Optional[ 1205 collections.Mapping[int, character.CharacterProgression] 1206 ] = None 1207 1208 if raw_character_progressions := payload.get("characterProgressions"): 1209 character_progressions = self.deserialize_character_progressions_mapping( 1210 raw_character_progressions 1211 ) 1212 1213 profile_string_vars: typing.Optional[collections.Mapping[int, int]] = None 1214 if raw_profile_string_vars := payload.get("profileStringVariables"): 1215 profile_string_vars = raw_profile_string_vars["data"]["integerValuesByHash"] 1216 1217 character_string_vars: typing.Optional[ 1218 collections.Mapping[int, collections.Mapping[int, int]] 1219 ] = None 1220 if raw_character_string_vars := payload.get("characterStringVariables"): 1221 character_string_vars = { 1222 int(char_id): data["integerValuesByHash"] 1223 for char_id, data in raw_character_string_vars["data"].items() 1224 } 1225 1226 metrics: typing.Optional[ 1227 collections.Sequence[ 1228 collections.Mapping[int, tuple[bool, records.Objective | None]] 1229 ] 1230 ] = None 1231 root_node_hash: int | None = None 1232 1233 if raw_metrics := payload.get("metrics"): 1234 root_node_hash = raw_metrics["data"]["metricsRootNodeHash"] 1235 metrics = tuple( 1236 { 1237 int(metrics_hash): ( 1238 data["invisible"], 1239 self.deserialize_objectives(data["objectiveProgress"]) 1240 if "objectiveProgress" in data 1241 else None, 1242 ) 1243 } 1244 for metrics_hash, data in raw_metrics["data"]["metrics"].items() 1245 ) 1246 transitory: fireteams.FireteamParty | None = None 1247 if raw_transitory := payload.get("profileTransitoryData"): 1248 if "data" in raw_transitory: 1249 transitory = self.deserialize_fireteam_party(raw_transitory["data"]) 1250 1251 item_components: components.ItemsComponent | None = None 1252 if raw_item_components := payload.get("itemComponents"): 1253 item_components = self.deserialize_items_component(raw_item_components) 1254 1255 profile_plugsets: typing.Optional[ 1256 collections.Mapping[int, collections.Sequence[items.PlugItemState]] 1257 ] = None 1258 1259 if raw_profile_plugs := payload.get("profilePlugSets"): 1260 profile_plugsets = { 1261 int(index): [self.deserialize_plug_item_state(state) for state in data] 1262 for index, data in raw_profile_plugs["data"]["plugs"].items() 1263 } 1264 1265 character_plugsets: typing.Optional[ 1266 collections.Mapping[ 1267 int, collections.Mapping[int, collections.Sequence[items.PlugItemState]] 1268 ] 1269 ] = None 1270 if raw_char_plugsets := payload.get("characterPlugSets"): 1271 character_plugsets = { 1272 int(char_id): { 1273 int(index): [ 1274 self.deserialize_plug_item_state(state) for state in data 1275 ] 1276 for index, data in inner["plugs"].items() 1277 } 1278 for char_id, inner in raw_char_plugsets["data"].items() 1279 } 1280 1281 character_collectibles: typing.Optional[ 1282 collections.Mapping[int, items.Collectible] 1283 ] = None 1284 if raw_character_collectibles := payload.get("characterCollectibles"): 1285 character_collectibles = { 1286 int(char_id): self._deserialize_collectible(data) 1287 for char_id, data in raw_character_collectibles["data"].items() 1288 } 1289 1290 profile_collectibles: items.Collectible | None = None 1291 if raw_profile_collectibles := payload.get("profileCollectibles"): 1292 profile_collectibles = self._deserialize_collectible( 1293 raw_profile_collectibles["data"] 1294 ) 1295 1296 profile_nodes: typing.Optional[collections.Mapping[int, records.Node]] = None 1297 if raw_profile_nodes := payload.get("profilePresentationNodes"): 1298 profile_nodes = { 1299 int(node_hash): self._deserialize_node(node) 1300 for node_hash, node in raw_profile_nodes["data"]["nodes"].items() 1301 } 1302 1303 character_nodes: typing.Optional[ 1304 collections.Mapping[int, collections.Mapping[int, records.Node]] 1305 ] = None 1306 if raw_character_nodes := payload.get("characterPresentationNodes"): 1307 character_nodes = { 1308 int(char_id): { 1309 int(node_hash): self._deserialize_node(node) 1310 for node_hash, node in each_character["nodes"].items() 1311 } 1312 for char_id, each_character in raw_character_nodes["data"].items() 1313 } 1314 1315 platform_silver: typing.Optional[ 1316 collections.Mapping[str, profile.ProfileItemImpl] 1317 ] = None 1318 if raw_platform_silver := payload.get("platformSilver"): 1319 if "data" in raw_platform_silver: 1320 platform_silver = { 1321 platform_name: self.deserialize_profile_item(item) 1322 for platform_name, item in raw_platform_silver["data"][ 1323 "platformSilver" 1324 ].items() 1325 } 1326 1327 character_currency_lookups: typing.Optional[ 1328 collections.Mapping[int, collections.Sequence[items.Currency]] 1329 ] = None 1330 if raw_char_lookups := payload.get("characterCurrencyLookups"): 1331 if "data" in raw_char_lookups: 1332 character_currency_lookups = { 1333 int(char_id): self._deserialize_currencies(currency) 1334 for char_id, currency in raw_char_lookups["data"].items() 1335 } 1336 1337 character_craftables: typing.Optional[ 1338 collections.Mapping[int, components.CraftablesComponent] 1339 ] = None 1340 if raw_character_craftables := payload.get("characterCraftables"): 1341 if "data" in raw_character_craftables: 1342 character_craftables = { 1343 int(char_id): self.deserialize_craftables_component(craftable) 1344 for char_id, craftable in raw_character_craftables["data"].items() 1345 } 1346 1347 character_loadouts: ( 1348 collections.Mapping[int, collections.Sequence[character.Loadout]] | None 1349 ) = None 1350 if raw_character_loadouts := payload.get("characterLoadouts"): 1351 if "data" in raw_character_loadouts: 1352 character_loadouts = { 1353 int(char_id): tuple( 1354 self.deserialize_character_loadout(loadout) 1355 for loadout in raw_loadouts["loadouts"] 1356 ) 1357 for char_id, raw_loadouts in raw_character_loadouts["data"].items() 1358 } 1359 1360 commendations: components.Commendation | None = None 1361 if ( 1362 raw_commendations := payload.get("profileCommendations") 1363 ) and "data" in raw_commendations: 1364 commendations = self._deserialize_commendations_component( 1365 raw_commendations["data"] 1366 ) 1367 1368 return components.Component( 1369 profiles=profile_, 1370 profile_progression=profile_progression, 1371 profile_currencies=profile_currencies, 1372 profile_inventories=profile_inventories, 1373 profile_records=profile_records, 1374 characters=characters, 1375 character_records=character_records, 1376 character_equipments=character_equipments, 1377 character_inventories=character_inventories, 1378 character_activities=character_activities, 1379 character_render_data=character_render_data, 1380 character_progressions=character_progressions, 1381 profile_string_variables=profile_string_vars, 1382 character_string_variables=character_string_vars, 1383 metrics=metrics, 1384 root_node_hash=root_node_hash, 1385 transitory=transitory, 1386 item_components=item_components, 1387 profile_plugsets=profile_plugsets, 1388 character_plugsets=character_plugsets, 1389 character_collectibles=character_collectibles, 1390 profile_collectibles=profile_collectibles, 1391 profile_nodes=profile_nodes, 1392 character_nodes=character_nodes, 1393 platform_silver=platform_silver, 1394 character_currency_lookups=character_currency_lookups, 1395 character_craftables=character_craftables, 1396 character_loadouts=character_loadouts, 1397 commendation=commendations, 1398 )
Deserialize a JSON payload of Bungie.net profile components information.
Parameters
- payload (
aiobungie.internal.helpers.JsonObject
): The JSON payload.
Returns
aiobungie.crates.Component
: A component implementation that includes all other components of the deserialized payload.
1400 def deserialize_items_component( 1401 self, payload: typedefs.JSONObject 1402 ) -> components.ItemsComponent: 1403 # Due to how complex this method is, We'll stick to typing.Optional. 1404 instances: typing.Optional[ 1405 collections.Sequence[collections.Mapping[int, items.ItemInstance]] 1406 ] = None 1407 if raw_instances := payload.get("instances"): 1408 instances = tuple( 1409 {int(ins_id): self.deserialize_instanced_item(item)} 1410 for ins_id, item in raw_instances["data"].items() 1411 ) 1412 1413 render_data: typing.Optional[ 1414 collections.Mapping[int, tuple[bool, dict[int, int]]] 1415 ] = None 1416 if raw_render_data := payload.get("renderData"): 1417 render_data = { 1418 int(ins_id): (data["useCustomDyes"], data["artRegions"]) 1419 for ins_id, data in raw_render_data["data"].items() 1420 } 1421 1422 stats: typing.Optional[collections.Mapping[int, items.ItemStatsView]] = None 1423 if raw_stats := payload.get("stats"): 1424 stats = {} 1425 for ins_id, stat in raw_stats["data"].items(): 1426 for _, items_ in stat.items(): 1427 stats[int(ins_id)] = self.deserialize_item_stats_view(items_) 1428 1429 sockets: typing.Optional[ 1430 collections.Mapping[int, collections.Sequence[items.ItemSocket]] 1431 ] = None 1432 if raw_sockets := payload.get("sockets"): 1433 sockets = { 1434 int(ins_id): tuple( 1435 self.deserialize_item_socket(socket) for socket in item["sockets"] 1436 ) 1437 for ins_id, item in raw_sockets["data"].items() 1438 } 1439 1440 objectives: typing.Optional[ 1441 collections.Mapping[int, collections.Sequence[records.Objective]] 1442 ] = None 1443 if raw_objectives := payload.get("objectives"): 1444 objectives = { 1445 int(ins_id): tuple( 1446 self.deserialize_objectives(objective) 1447 for objective in data["objectives"] 1448 ) 1449 for ins_id, data in raw_objectives["data"].items() 1450 } 1451 1452 perks: typing.Optional[ 1453 collections.Mapping[int, collections.Collection[items.ItemPerk]] 1454 ] = None 1455 if raw_perks := payload.get("perks"): 1456 perks = { 1457 int(ins_id): tuple( 1458 self.deserialize_item_perk(perk) for perk in item["perks"] 1459 ) 1460 for ins_id, item in raw_perks["data"].items() 1461 } 1462 1463 plug_states: typing.Optional[collections.Sequence[items.PlugItemState]] = None 1464 if raw_plug_states := payload.get("plugStates"): 1465 plug_states = tuple( 1466 self.deserialize_plug_item_state(plug) 1467 for _, plug in raw_plug_states["data"].items() 1468 ) 1469 1470 reusable_plugs: typing.Optional[ 1471 collections.Mapping[int, collections.Sequence[items.PlugItemState]] 1472 ] = None 1473 if raw_re_plugs := payload.get("reusablePlugs"): 1474 reusable_plugs = { 1475 int(ins_id): tuple( 1476 self.deserialize_plug_item_state(state) for state in inner 1477 ) 1478 for ins_id, plug in raw_re_plugs["data"].items() 1479 for inner in tuple(plug["plugs"].values()) 1480 } 1481 1482 plug_objectives: typing.Optional[ 1483 collections.Mapping[ 1484 int, collections.Mapping[int, collections.Collection[records.Objective]] 1485 ] 1486 ] = None 1487 if raw_plug_objectives := payload.get("plugObjectives"): 1488 plug_objectives = { 1489 int(ins_id): { 1490 int(obj_hash): tuple( 1491 self.deserialize_objectives(obj) for obj in objs 1492 ) 1493 for obj_hash, objs in inner["objectivesPerPlug"].items() 1494 } 1495 for ins_id, inner in raw_plug_objectives["data"].items() 1496 } 1497 1498 return components.ItemsComponent( 1499 sockets=sockets, 1500 stats=stats, 1501 render_data=render_data, 1502 instances=instances, 1503 objectives=objectives, 1504 perks=perks, 1505 plug_states=plug_states, 1506 reusable_plugs=reusable_plugs, 1507 plug_objectives=plug_objectives, 1508 )
Deserialize a JSON objects within the itemComponents
key.`
1510 def deserialize_character_component( 1511 self, payload: typedefs.JSONObject 1512 ) -> components.CharacterComponent: 1513 character_: character.Character | None = None 1514 if raw_singular_character := payload.get("character"): 1515 character_ = self.deserialize_character(raw_singular_character["data"]) 1516 1517 inventory: typing.Optional[collections.Sequence[profile.ProfileItemImpl]] = None 1518 loadouts: collections.Sequence[character.Loadout] | None = None 1519 if raw_inventory := payload.get("inventory"): 1520 if "data" in raw_inventory: 1521 inventory = self.deserialize_profile_items(raw_inventory["data"]) 1522 1523 # The inventory component also returns the loadouts component 1524 if raw_loadouts := payload.get("loadouts"): 1525 if "data" in raw_loadouts: 1526 loadouts = tuple( 1527 self.deserialize_character_loadout(loadout) 1528 # very interesting nesting bungie... 1529 for loadout in raw_loadouts["data"]["loadouts"] 1530 ) 1531 1532 activities: activity.CharacterActivity | None = None 1533 if raw_activities := payload.get("activities"): 1534 activities = self.deserialize_character_activity(raw_activities["data"]) 1535 1536 equipment: typing.Optional[collections.Sequence[profile.ProfileItemImpl]] = None 1537 if raw_equipments := payload.get("equipment"): 1538 equipment = self.deserialize_profile_items(raw_equipments["data"]) 1539 1540 progressions_: character.CharacterProgression | None = None 1541 if raw_progressions := payload.get("progressions"): 1542 progressions_ = self.deserialize_character_progressions( 1543 raw_progressions["data"] 1544 ) 1545 1546 render_data: character.RenderedData | None = None 1547 if raw_render_data := payload.get("renderData"): 1548 render_data = self.deserialize_character_render_data( 1549 raw_render_data["data"] 1550 ) 1551 1552 character_records: typing.Optional[ 1553 collections.Mapping[int, records.CharacterRecord] 1554 ] = None 1555 if raw_char_records := payload.get("records"): 1556 character_records = self.deserialize_characters_records( 1557 raw_char_records["data"] 1558 ) 1559 1560 item_components: components.ItemsComponent | None = None 1561 if raw_item_components := payload.get("itemComponents"): 1562 item_components = self.deserialize_items_component(raw_item_components) 1563 1564 nodes: typing.Optional[collections.Mapping[int, records.Node]] = None 1565 if raw_nodes := payload.get("presentationNodes"): 1566 nodes = { 1567 int(node_hash): self._deserialize_node(node) 1568 for node_hash, node in raw_nodes["data"]["nodes"].items() 1569 } 1570 1571 collectibles: items.Collectible | None = None 1572 if raw_collectibles := payload.get("collectibles"): 1573 collectibles = self._deserialize_collectible(raw_collectibles["data"]) 1574 1575 currency_lookups: typing.Optional[collections.Sequence[items.Currency]] = None 1576 if raw_currencies := payload.get("currencyLookups"): 1577 if "data" in raw_currencies: 1578 currency_lookups = self._deserialize_currencies(raw_currencies) 1579 1580 return components.CharacterComponent( 1581 activities=activities, 1582 equipment=equipment, 1583 inventory=inventory, 1584 progressions=progressions_, 1585 render_data=render_data, 1586 character=character_, 1587 character_records=character_records, 1588 profile_records=None, 1589 item_components=item_components, 1590 currency_lookups=currency_lookups, 1591 collectibles=collectibles, 1592 nodes=nodes, 1593 loadouts=loadouts, 1594 )
Deserialize a JSON payload of Destiny 2 character component.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
aiobungie.crates.CharacterComponent
: A character component.
1612 def deserialize_inventory_results( 1613 self, payload: typedefs.JSONObject 1614 ) -> sain.Iterator[entity.SearchableEntity]: 1615 return sain.Iter( 1616 entity.SearchableEntity( 1617 hash=data["hash"], 1618 entity_type=data["entityType"], 1619 weight=data["weight"], 1620 suggested_words=payload["suggestedWords"], 1621 name=data["displayProperties"]["name"], 1622 has_icon=data["displayProperties"]["hasIcon"], 1623 description=typedefs.unknown(data["displayProperties"]["description"]), 1624 icon=builders.Image(path=data["displayProperties"]["icon"]), 1625 ) 1626 for data in payload["results"]["results"] 1627 )
Deserialize results of searched Destiny2 entities.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
Iterator[aiobungie.crates.SearchableEntity]
: An iterator over the found searched entities.
1656 def deserialize_inventory_entity( # noqa: C901 Too complex. 1657 self, payload: typedefs.JSONObject, / 1658 ) -> entity.InventoryEntity: 1659 props = self._set_entity_attrs(payload) 1660 objects = self._deserialize_inventory_item_objects(payload) 1661 1662 collectible_hash: int | None = None 1663 if raw_collectible_hash := payload.get("collectibleHash"): 1664 collectible_hash = int(raw_collectible_hash) 1665 1666 secondary_icon: builders.Image | None = None 1667 if raw_second_icon := payload.get("secondaryIcon"): 1668 secondary_icon = builders.Image(path=raw_second_icon) 1669 1670 secondary_overlay: builders.Image | None = None 1671 if raw_second_overlay := payload.get("secondaryOverlay"): 1672 secondary_overlay = builders.Image(path=raw_second_overlay) 1673 1674 secondary_special: builders.Image | None = None 1675 if raw_second_special := payload.get("secondarySpecial"): 1676 secondary_special = builders.Image(path=raw_second_special) 1677 1678 screenshot: builders.Image | None = None 1679 if raw_screenshot := payload.get("screenshot"): 1680 screenshot = builders.Image(path=raw_screenshot) 1681 1682 watermark_icon: builders.Image | None = None 1683 if raw_watermark_icon := payload.get("iconWatermark"): 1684 watermark_icon = builders.Image(path=raw_watermark_icon) 1685 1686 watermark_shelved: builders.Image | None = None 1687 if raw_watermark_shelved := payload.get("iconWatermarkShelved"): 1688 watermark_shelved = builders.Image(path=raw_watermark_shelved) 1689 1690 about: str | None = None 1691 if raw_about := payload.get("flavorText"): 1692 about = raw_about 1693 1694 ui_item_style: str | None = None 1695 if raw_ui_style := payload.get("uiItemDisplayStyle"): 1696 ui_item_style = raw_ui_style 1697 1698 tier_and_name: str | None = None 1699 if raw_tier_and_name := payload.get("itemTypeAndTierDisplayName"): 1700 tier_and_name = raw_tier_and_name 1701 1702 type_name: str | None = None 1703 if raw_type_name := payload.get("itemTypeDisplayName"): 1704 type_name = raw_type_name 1705 1706 display_source: str | None = None 1707 if raw_display_source := payload.get("displaySource"): 1708 display_source = raw_display_source 1709 1710 lorehash: int | None = None 1711 if raw_lore_hash := payload.get("loreHash"): 1712 lorehash = int(raw_lore_hash) 1713 1714 summary_hash: int | None = None 1715 if raw_summary_hash := payload.get("summaryItemHash"): 1716 summary_hash = raw_summary_hash 1717 1718 breaker_type_hash: int | None = None 1719 if raw_breaker_type_hash := payload.get("breakerTypeHash"): 1720 breaker_type_hash = int(raw_breaker_type_hash) 1721 1722 damage_types: typing.Optional[collections.Sequence[int]] = None 1723 if raw_damage_types := payload.get("damageTypes"): 1724 damage_types = tuple(int(type_) for type_ in raw_damage_types) 1725 1726 damagetype_hashes: typing.Optional[collections.Sequence[int]] = None 1727 if raw_damagetype_hashes := payload.get("damageTypeHashes"): 1728 damagetype_hashes = tuple(int(type_) for type_ in raw_damagetype_hashes) 1729 1730 default_damagetype_hash: int | None = None 1731 if raw_defaultdmg_hash := payload.get("defaultDamageTypeHash"): 1732 default_damagetype_hash = int(raw_defaultdmg_hash) 1733 1734 emblem_objective_hash: int | None = None 1735 if raw_emblem_obj_hash := payload.get("emblemObjectiveHash"): 1736 emblem_objective_hash = int(raw_emblem_obj_hash) 1737 1738 tier_type: enums.TierType | None = None 1739 tier: enums.ItemTier | None = None 1740 bucket_hash: int | None = None 1741 recovery_hash: int | None = None 1742 tier_name: str | None = None 1743 isinstance_item: bool = False 1744 expire_tool_tip: str | None = None 1745 expire_in_orbit_message: str | None = None 1746 suppress_expiration: bool = False 1747 max_stack_size: int | None = None 1748 stack_label: str | None = None 1749 1750 if inventory := payload.get("inventory"): 1751 tier_type = enums.TierType(int(inventory["tierType"])) 1752 tier = enums.ItemTier(int(inventory["tierTypeHash"])) 1753 bucket_hash = int(inventory["bucketTypeHash"]) 1754 recovery_hash = int(inventory["recoveryBucketTypeHash"]) 1755 tier_name = inventory["tierTypeName"] 1756 isinstance_item = inventory["isInstanceItem"] 1757 suppress_expiration = inventory["suppressExpirationWhenObjectivesComplete"] 1758 max_stack_size = int(inventory["maxStackSize"]) 1759 1760 try: 1761 stack_label = inventory["stackUniqueLabel"] 1762 except KeyError: 1763 pass 1764 1765 if "traitHashes" in payload: 1766 trait_hashes = tuple( 1767 int(trait_hash) for trait_hash in payload["traitHashes"] 1768 ) 1769 else: 1770 trait_hashes = () 1771 1772 if "traitIds" in payload: 1773 trait_ids = tuple(trait_id for trait_id in payload["traitIds"]) 1774 else: 1775 trait_ids = () 1776 1777 return entity.InventoryEntity( 1778 collectible_hash=collectible_hash, 1779 name=props.name, 1780 about=about, 1781 emblem_objective_hash=emblem_objective_hash, 1782 suppress_expiration=suppress_expiration, 1783 max_stack_size=max_stack_size, 1784 stack_label=stack_label, 1785 tier=tier, 1786 tier_type=tier_type, 1787 tier_name=tier_name, 1788 bucket_hash=bucket_hash, 1789 recovery_bucket_hash=recovery_hash, 1790 isinstance_item=isinstance_item, 1791 expire_in_orbit_message=expire_in_orbit_message, 1792 expiration_tooltip=expire_tool_tip, 1793 lore_hash=lorehash, 1794 type_and_tier_name=tier_and_name, 1795 summary_hash=summary_hash, 1796 ui_display_style=ui_item_style, 1797 type_name=type_name, 1798 breaker_type_hash=breaker_type_hash, 1799 description=props.description, 1800 display_source=display_source, 1801 hash=props.hash, 1802 damage_types=damage_types, 1803 index=props.index, 1804 icon=props.icon, 1805 has_icon=props.has_icon, 1806 screenshot=screenshot, 1807 watermark_icon=watermark_icon, 1808 watermark_shelved=watermark_shelved, 1809 secondary_icon=secondary_icon, 1810 secondary_overlay=secondary_overlay, 1811 secondary_special=secondary_special, 1812 type=enums.ItemType(int(payload["itemType"])), 1813 category_hashes=tuple( 1814 int(hash_) for hash_ in payload["itemCategoryHashes"] 1815 ), 1816 item_class=enums.Class(int(payload["classType"])), 1817 sub_type=enums.ItemSubType(int(payload["itemSubType"])), 1818 breaker_type=int(payload["breakerType"]), 1819 default_damagetype=int(payload["defaultDamageType"]), 1820 default_damagetype_hash=default_damagetype_hash, 1821 damagetype_hashes=damagetype_hashes, 1822 tooltip_notifications=payload["tooltipNotifications"], 1823 not_transferable=payload["nonTransferrable"], 1824 allow_actions=payload["allowActions"], 1825 is_equippable=payload["equippable"], 1826 objects=objects, 1827 background_colors=payload.get("backgroundColor", {}), 1828 season_hash=payload.get("seasonHash"), 1829 has_postmaster_effect=payload["doesPostmasterPullHaveSideEffects"], 1830 trait_hashes=trait_hashes, 1831 trait_ids=trait_ids, 1832 )
Deserialize a JSON payload of an inventory entity item information.
This can be any item from DestinyInventoryItemDefinition
definition.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
aiobungie.crates.InventoryEntity
: An entity item.
1834 def deserialize_objective_entity( 1835 self, payload: typedefs.JSONObject, / 1836 ) -> entity.ObjectiveEntity: 1837 props = self._set_entity_attrs(payload) 1838 return entity.ObjectiveEntity( 1839 hash=props.hash, 1840 index=props.index, 1841 description=props.description, 1842 name=props.name, 1843 has_icon=props.has_icon, 1844 icon=props.icon, 1845 unlock_value_hash=payload["unlockValueHash"], 1846 completion_value=payload["completionValue"], 1847 scope=entity.GatingScope(int(payload["scope"])), 1848 location_hash=payload["locationHash"], 1849 allowed_negative_value=payload["allowNegativeValue"], 1850 allowed_value_change=payload["allowValueChangeWhenCompleted"], 1851 counting_downward=payload["isCountingDownward"], 1852 value_style=entity.ValueUIStyle(int(payload["valueStyle"])), 1853 progress_description=payload["progressDescription"], 1854 perks=payload["perks"], 1855 stats=payload["stats"], 1856 minimum_visibility=payload["minimumVisibilityThreshold"], 1857 allow_over_completion=payload["allowOvercompletion"], 1858 show_value_style=payload["showValueOnComplete"], 1859 display_only_objective=payload["isDisplayOnlyObjective"], 1860 complete_value_style=entity.ValueUIStyle( 1861 int(payload["completedValueStyle"]) 1862 ), 1863 progress_value_style=entity.ValueUIStyle( 1864 int(payload["inProgressValueStyle"]) 1865 ), 1866 ui_label=payload["uiLabel"], 1867 ui_style=entity.ObjectiveUIStyle(int(payload["uiStyle"])), 1868 )
Deserialize a JSON payload of an objective entity information.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
aiobungie.crates.ObjectiveEntity
: An objective entity.
1896 def deserialize_activity( 1897 self, 1898 payload: typedefs.JSONObject, 1899 /, 1900 ) -> activity.Activity: 1901 period = time.clean_date(payload["period"]) 1902 details = payload["activityDetails"] 1903 ref_id = int(details["referenceId"]) 1904 instance_id = int(details["instanceId"]) 1905 mode = enums.GameMode(details["mode"]) 1906 modes = tuple(enums.GameMode(int(mode_)) for mode_ in details["modes"]) 1907 is_private = details["isPrivate"] 1908 membership_type = enums.MembershipType(int(details["membershipType"])) 1909 1910 # Since we're using the same fields for post activity method 1911 # this check is required since post activity doesn't values values 1912 values = self._deserialize_activity_values(payload["values"]) 1913 1914 return activity.Activity( 1915 hash=ref_id, 1916 instance_id=instance_id, 1917 mode=mode, 1918 modes=modes, 1919 is_private=is_private, 1920 membership_type=membership_type, 1921 occurred_at=period, 1922 values=values, 1923 )
Deserialize a JSON payload of an activity history information.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
aiobungie.crates.Activity
: An activity.
1925 def deserialize_activities( 1926 self, payload: typedefs.JSONObject 1927 ) -> sain.Iterator[activity.Activity]: 1928 return sain.Iter( 1929 self.deserialize_activity(activity_) for activity_ in payload["activities"] 1930 )
Deserialize a JSON payload of an array of activity history information.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
Iterator[aiobungie.crates.Activity]
: Am iterator over activity objects of the deserialized payload.
1932 def deserialize_extended_weapon_values( 1933 self, payload: typedefs.JSONObject 1934 ) -> activity.ExtendedWeaponValues: 1935 assists: int | None = None 1936 if raw_assists := payload["values"].get("uniqueWeaponAssists"): 1937 assists = raw_assists["basic"]["value"] 1938 assists_damage: int | None = None 1939 1940 if raw_assists_damage := payload["values"].get("uniqueWeaponAssistDamage"): 1941 assists_damage = raw_assists_damage["basic"]["value"] 1942 1943 return activity.ExtendedWeaponValues( 1944 reference_id=int(payload["referenceId"]), 1945 kills=payload["values"]["uniqueWeaponKills"]["basic"]["value"], 1946 precision_kills=payload["values"]["uniqueWeaponPrecisionKills"]["basic"][ 1947 "value" 1948 ], 1949 assists=assists, 1950 assists_damage=assists_damage, 1951 precision_kills_percentage=( 1952 payload["values"]["uniqueWeaponKillsPrecisionKills"]["basic"]["value"], 1953 payload["values"]["uniqueWeaponKillsPrecisionKills"]["basic"][ 1954 "displayValue" 1955 ], 1956 ), 1957 )
Deserialize values of extended weapons JSON object.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
aiobungie.crates.ExtendedWeaponValues
: Information about an extended weapon values.
1978 def deserialize_post_activity_player( 1979 self, payload: typedefs.JSONObject, / 1980 ) -> activity.PostActivityPlayer: 1981 player = payload["player"] 1982 1983 class_hash: int | None = None 1984 if (class_hash := player.get("classHash")) is not None: 1985 class_hash = class_hash 1986 1987 race_hash: int | None = None 1988 if (race_hash := player.get("raceHash")) is not None: 1989 race_hash = race_hash 1990 1991 gender_hash: int | None = None 1992 if (gender_hash := player.get("genderHash")) is not None: 1993 gender_hash = gender_hash 1994 1995 character_class: str | None = None 1996 if character_class := player.get("characterClass"): 1997 character_class = character_class 1998 1999 character_level: int | None = None 2000 if (character_level := player.get("characterLevel")) is not None: 2001 character_level = character_level 2002 2003 return activity.PostActivityPlayer( 2004 standing=int(payload["standing"]), 2005 score=int(payload["score"]["basic"]["value"]), 2006 character_id=payload["characterId"], 2007 destiny_user=self.deserialize_destiny_membership(player["destinyUserInfo"]), 2008 character_class=character_class, 2009 character_level=character_level, 2010 race_hash=race_hash, 2011 gender_hash=gender_hash, 2012 class_hash=class_hash, 2013 light_level=int(player["lightLevel"]), 2014 emblem_hash=int(player["emblemHash"]), 2015 values=self._deserialize_activity_values(payload["values"]), 2016 extended_values=self._deserialize_extended_values(payload["extended"]), 2017 )
Deserialize a JSON payload of a post activity player information.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
aiobungie.crates.PostActivityPlayer
: A post activity player object.
2029 def deserialize_post_activity( 2030 self, payload: typedefs.JSONObject 2031 ) -> activity.PostActivity: 2032 period = time.clean_date(payload["period"]) 2033 details = payload["activityDetails"] 2034 ref_id = int(details["referenceId"]) 2035 instance_id = int(details["instanceId"]) 2036 mode = enums.GameMode(details["mode"]) 2037 modes = tuple(enums.GameMode(int(mode_)) for mode_ in details["modes"]) 2038 is_private = details["isPrivate"] 2039 membership_type = enums.MembershipType(int(details["membershipType"])) 2040 return activity.PostActivity( 2041 hash=ref_id, 2042 membership_type=membership_type, 2043 instance_id=instance_id, 2044 mode=mode, 2045 modes=modes, 2046 is_private=is_private, 2047 occurred_at=period, 2048 starting_phase=int(payload["startingPhaseIndex"]), 2049 players=tuple( 2050 self.deserialize_post_activity_player(player) 2051 for player in payload["entries"] 2052 ), 2053 teams=tuple( 2054 self._deserialize_post_activity_team(team) for team in payload["teams"] 2055 ), 2056 )
Deserialize a JSON payload of a post activity information.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
aiobungie.crates.PostActivity
: A post activity object.
2094 def deserialize_aggregated_activity( 2095 self, payload: typedefs.JSONObject 2096 ) -> activity.AggregatedActivity: 2097 return activity.AggregatedActivity( 2098 hash=int(payload["activityHash"]), 2099 values=self._deserialize_aggregated_activity_values(payload["values"]), 2100 )
Deserialize a JSON payload of an aggregated activity.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
aiobungie.crates.AggregatedActivity
: An aggregated activity object.
2102 def deserialize_aggregated_activities( 2103 self, payload: typedefs.JSONObject 2104 ) -> sain.Iterator[activity.AggregatedActivity]: 2105 return sain.Iter( 2106 self.deserialize_aggregated_activity(activity) 2107 for activity in payload["activities"] 2108 )
Deserialize a JSON payload of an array of aggregated activities.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
Iterator[aiobungie.crates.AggregatedActivity]
: An iterator over aggregated activities objects.
2110 def deserialize_linked_profiles( 2111 self, payload: typedefs.JSONObject 2112 ) -> profile.LinkedProfile: 2113 bungie_user = self.deserialize_partial_bungie_user(payload["bnetMembership"]) 2114 2115 if raw_profile := payload.get("profiles"): 2116 profiles = tuple( 2117 self.deserialize_destiny_membership(p) for p in raw_profile 2118 ) 2119 else: 2120 profiles = () 2121 2122 error_profiles = () 2123 if raw_profiles_with_errors := payload.get("profilesWithErrors"): 2124 for raw_error_p in raw_profiles_with_errors: 2125 if "infoCard" in raw_error_p: 2126 error_profiles = tuple( 2127 self.deserialize_destiny_membership(error_p) 2128 for error_p in raw_error_p 2129 ) 2130 2131 return profile.LinkedProfile( 2132 bungie_user=bungie_user, 2133 profiles=profiles, 2134 profiles_with_errors=error_profiles, 2135 )
Deserialize a JSON payload of Bungie.net hard linked profile information.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
aiobungie.crates.LinkedProfile
: A hard linked profile.
2153 def deserialize_public_milestone_content( 2154 self, payload: typedefs.JSONObject 2155 ) -> milestones.MilestoneContent: 2156 if raw_categories := payload.get("itemCategories"): 2157 items_categories = tuple( 2158 milestones.MilestoneItems( 2159 title=item["title"], hashes=item["itemHashes"] 2160 ) 2161 for item in raw_categories 2162 ) 2163 else: 2164 items_categories = () 2165 2166 return milestones.MilestoneContent( 2167 about=typedefs.unknown(payload["about"]), 2168 status=typedefs.unknown(payload["status"]), 2169 tips=payload.get("tips", ()), 2170 items=items_categories, 2171 )
Deserialize a JSON payload of milestone content information.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
aiobungie.crates.MilestoneContent
: A milestone content.
2173 def deserialize_friend(self, payload: typedefs.JSONObject, /) -> friends.Friend: 2174 bungie_user: user.BungieUser | None = None 2175 2176 if raw_bungie_user := payload.get("bungieNetUser"): 2177 bungie_user = self.deserialize_bungie_user(raw_bungie_user) 2178 2179 return friends.Friend( 2180 id=int(payload["lastSeenAsMembershipId"]), 2181 name=typedefs.unknown(payload.get("bungieGlobalDisplayName", "")), 2182 code=payload.get("bungieGlobalDisplayNameCode"), 2183 relationship=enums.Relationship(payload["relationship"]), 2184 user=bungie_user, 2185 online_status=enums.Presence(payload["onlineStatus"]), 2186 online_title=payload["onlineTitle"], 2187 type=enums.MembershipType(payload["lastSeenAsBungieMembershipType"]), 2188 )
Deserialize a JSON payload of a Bungie friend information.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
aiobungie.crates.Friend
: A friend.
2190 def deserialize_friends( 2191 self, payload: typedefs.JSONObject 2192 ) -> collections.Sequence[friends.Friend]: 2193 return tuple(self.deserialize_friend(friend) for friend in payload["friends"])
Deserialize a JSON sequence of Bungie friends information.
This is usually used to deserialize the incoming/outgoing friend requests.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
collections.Sequence[aiobungie.crates.Friend]
: A sequence of friends.
2195 def deserialize_friend_requests( 2196 self, payload: typedefs.JSONObject 2197 ) -> friends.FriendRequestView: 2198 if raw_incoming_requests := payload.get("incomingRequests"): 2199 incoming = tuple( 2200 self.deserialize_friend(incoming_request) 2201 for incoming_request in raw_incoming_requests 2202 ) 2203 else: 2204 incoming = () 2205 2206 if raw_outgoing_requests := payload.get("outgoingRequests"): 2207 outgoing = tuple( 2208 self.deserialize_friend(outgoing_request) 2209 for outgoing_request in raw_outgoing_requests 2210 ) 2211 else: 2212 outgoing = () 2213 2214 return friends.FriendRequestView(incoming=incoming, outgoing=outgoing)
Deserialize a JSON sequence of Bungie friend requests information.
This is used for incoming/outgoing friend requests.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
collections.Sequence[aiobungie.crates.FriendRequestView]
: A sequence of incoming and outgoing friends.
2245 def deserialize_fireteams( 2246 self, payload: typedefs.JSONObject 2247 ) -> collections.Sequence[fireteams.Fireteam]: 2248 if "results" in payload: 2249 fireteams_ = tuple( 2250 self._set_fireteam_fields( 2251 elem, total_results=int(payload["totalResults"]) 2252 ) 2253 for elem in payload["results"] 2254 ) 2255 else: 2256 fireteams_ = () 2257 return fireteams_
Deserialize a JSON sequence of Bungie fireteams information.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
collections.Sequence[aiobungie.crates.Fireteam]
: A sequence of fireteam.
2259 def deserialize_fireteam_destiny_membership( 2260 self, payload: typedefs.JSONObject 2261 ) -> fireteams.FireteamUser: 2262 destiny_obj = self.deserialize_destiny_membership(payload) 2263 return fireteams.FireteamUser( 2264 id=destiny_obj.id, 2265 code=destiny_obj.code, 2266 icon=destiny_obj.icon, 2267 types=destiny_obj.types, 2268 type=destiny_obj.type, 2269 is_public=destiny_obj.is_public, 2270 crossave_override=destiny_obj.crossave_override, 2271 name=destiny_obj.name, 2272 last_seen_name=destiny_obj.last_seen_name, 2273 fireteam_display_name=payload["FireteamDisplayName"], 2274 fireteam_membership_id=enums.MembershipType( 2275 payload["FireteamMembershipType"] 2276 ), 2277 )
Deserialize a JSON payload of Bungie fireteam destiny users information.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
aiobungie.crates.FireteamUser
: A fireteam user.
2279 def deserialize_fireteam_members( 2280 self, payload: typedefs.JSONObject, *, alternatives: bool = False 2281 ) -> collections.Sequence[fireteams.FireteamMember]: 2282 members_: list[fireteams.FireteamMember] = [] 2283 if members := payload.get("Members" if not alternatives else "Alternates"): 2284 for member in members: 2285 bungie_fields = self.deserialize_partial_bungie_user(member) 2286 members_fields = fireteams.FireteamMember( 2287 membership=self.deserialize_fireteam_destiny_membership(member), 2288 has_microphone=member["hasMicrophone"], 2289 character_id=int(member["characterId"]), 2290 date_joined=time.clean_date(member["dateJoined"]), 2291 last_platform_invite_date=time.clean_date( 2292 member["lastPlatformInviteAttemptDate"] 2293 ), 2294 last_platform_invite_result=int( 2295 member["lastPlatformInviteAttemptResult"] 2296 ), 2297 name=bungie_fields.name, 2298 id=bungie_fields.id, 2299 icon=bungie_fields.icon, 2300 is_public=bungie_fields.is_public, 2301 crossave_override=bungie_fields.crossave_override, 2302 types=bungie_fields.types, 2303 type=bungie_fields.type, 2304 code=bungie_fields.code, 2305 ) 2306 members_.append(members_fields) 2307 return tuple(members_)
Deserialize a JSON sequence of Bungie fireteam members information.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload. - alternatives (
bool
): If set toTrue
, Then it will deserialize thealternatives
data in the payload. If not the it will just deserialize themembers
data.
Returns
collections.Sequence[aiobungie.crates.FireteamUser]
: A sequence of the fireteam members.
2309 def deserialize_available_fireteam( 2310 self, payload: typedefs.JSONObject 2311 ) -> fireteams.AvailableFireteam: 2312 fields = self._set_fireteam_fields(payload["Summary"]) 2313 return fireteams.AvailableFireteam( 2314 id=fields.id, 2315 group_id=fields.group_id, 2316 platform=fields.platform, 2317 activity_type=fields.activity_type, 2318 is_immediate=fields.is_immediate, 2319 is_public=fields.is_public, 2320 is_valid=fields.is_valid, 2321 owner_id=fields.owner_id, 2322 player_slot_count=fields.player_slot_count, 2323 available_player_slots=fields.available_player_slots, 2324 available_alternate_slots=fields.available_alternate_slots, 2325 title=fields.title, 2326 date_created=fields.date_created, 2327 locale=fields.locale, 2328 last_modified=fields.last_modified, 2329 total_results=fields.total_results, 2330 scheduled_time=fields.scheduled_time, 2331 date_modified=fields.date_modified, 2332 members=self.deserialize_fireteam_members(payload), 2333 alternatives=self.deserialize_fireteam_members(payload, alternatives=True), 2334 )
Deserialize a JSON payload of a sequence of/fireteam information.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
- An available fireteam object.
2336 def deserialize_available_fireteams( 2337 self, data: typedefs.JSONObject 2338 ) -> collections.Sequence[fireteams.AvailableFireteam]: 2339 if raw_results := data.get("results"): 2340 fireteam_results = tuple( 2341 self.deserialize_available_fireteam(f) for f in raw_results 2342 ) 2343 else: 2344 fireteam_results = () 2345 return fireteam_results
Deserialize a JSON payload sequence of fireteam objects.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
collections.Sequence[aiobungie.crates.fireteams.AvailableFireteam]
: A sequence of available fireteams.
2347 def deserialize_fireteam_party( 2348 self, payload: typedefs.JSONObject 2349 ) -> fireteams.FireteamParty: 2350 last_destination_hash: int | None = None 2351 if raw_dest_hash := payload.get("lastOrbitedDestinationHash"): 2352 last_destination_hash = int(raw_dest_hash) 2353 2354 return fireteams.FireteamParty( 2355 members=tuple( 2356 self._deserialize_fireteam_party_member(member) 2357 for member in payload["partyMembers"] 2358 ), 2359 activity=self._deserialize_fireteam_party_current_activity( 2360 payload["currentActivity"] 2361 ), 2362 settings=self._deserialize_fireteam_party_settings(payload["joinability"]), 2363 last_destination_hash=last_destination_hash, 2364 tracking=payload["tracking"], 2365 )
Deserialize a JSON payload of profileTransitory
component response.
Parameters
- payload (
aiobungie.typedefs.JSONObject
): The JSON payload.
Returns
aiobungie.crates.FireteamParty
: A fireteam party object of the current fireteam.
2408 def deserialize_seasonal_artifact( 2409 self, payload: typedefs.JSONObject 2410 ) -> season.Artifact: 2411 raw_artifact = payload["seasonalArtifact"] 2412 2413 points = raw_artifact["pointProgression"] 2414 points_prog = progressions.Progression( 2415 hash=points["progressionHash"], 2416 level=points["level"], 2417 cap=points["levelCap"], 2418 daily_limit=points["dailyLimit"], 2419 weekly_limit=points["weeklyLimit"], 2420 current_progress=points["currentProgress"], 2421 daily_progress=points["dailyProgress"], 2422 needed=points["progressToNextLevel"], 2423 next_level=points["nextLevelAt"], 2424 ) 2425 2426 bonus = raw_artifact["powerBonusProgression"] 2427 power_bonus_prog = progressions.Progression( 2428 hash=bonus["progressionHash"], 2429 level=bonus["level"], 2430 cap=bonus["levelCap"], 2431 daily_limit=bonus["dailyLimit"], 2432 weekly_limit=bonus["weeklyLimit"], 2433 current_progress=bonus["currentProgress"], 2434 daily_progress=bonus["dailyProgress"], 2435 needed=bonus["progressToNextLevel"], 2436 next_level=bonus["nextLevelAt"], 2437 ) 2438 return season.Artifact( 2439 hash=raw_artifact["artifactHash"], 2440 power_bonus=raw_artifact["powerBonus"], 2441 acquired_points=raw_artifact["pointsAcquired"], 2442 bonus=power_bonus_prog, 2443 points=points_prog, 2444 )
Deserialize a JSON payload of a Destiny 2 seasonal artifact information.
Parameters
- payload (
aiobungie.internal.helpers.JsonObject
): The JSON payload.
Returns
aiobungie.crates.Artifact
: A seasonal artifact.
2446 def deserialize_profile_progression( 2447 self, payload: typedefs.JSONObject 2448 ) -> profile.ProfileProgression: 2449 return profile.ProfileProgression( 2450 artifact=self.deserialize_seasonal_artifact(payload["data"]), 2451 checklist={ 2452 int(check_id): checklists 2453 for check_id, checklists in payload["data"]["checklists"].items() 2454 }, 2455 )
Deserialize a JSON payload of a profile progression component.
Parameters
- payload (
aiobungie.internal.helpers.JsonObject
): The JSON payload.
Returns
aiobungie.crates.ProfileProgression
: A profile progression component.
2457 def deserialize_instanced_item( 2458 self, payload: typedefs.JSONObject 2459 ) -> items.ItemInstance: 2460 damage_type_hash: int | None = None 2461 if raw_damagetype_hash := payload.get("damageTypeHash"): 2462 damage_type_hash = int(raw_damagetype_hash) 2463 2464 required_hashes: typing.Optional[collections.Collection[int]] = None 2465 if raw_required_hashes := payload.get("unlockHashesRequiredToEquip"): 2466 required_hashes = tuple(int(raw_hash) for raw_hash in raw_required_hashes) 2467 2468 breaker_type: items.ItemBreakerType | None = None 2469 if raw_break_type := payload.get("breakerType"): 2470 breaker_type = items.ItemBreakerType(int(raw_break_type)) 2471 2472 breaker_type_hash: int | None = None 2473 if raw_break_type_hash := payload.get("breakerTypeHash"): 2474 breaker_type_hash = int(raw_break_type_hash) 2475 2476 energy: items.ItemEnergy | None = None 2477 if raw_energy := payload.get("energy"): 2478 energy = self.deserialize_item_energy(raw_energy) 2479 2480 primary_stats = None 2481 if raw_primary_stats := payload.get("primaryStat"): 2482 primary_stats = self.deserialize_item_stats_view(raw_primary_stats) 2483 2484 return items.ItemInstance( 2485 damage_type=enums.DamageType(int(payload["damageType"])), 2486 damage_type_hash=damage_type_hash, 2487 primary_stat=primary_stats, 2488 item_level=int(payload["itemLevel"]), 2489 quality=int(payload["quality"]), 2490 is_equipped=payload["isEquipped"], 2491 can_equip=payload["canEquip"], 2492 equip_required_level=int(payload["equipRequiredLevel"]), 2493 required_equip_unlock_hashes=required_hashes, 2494 cant_equip_reason=int(payload["cannotEquipReason"]), 2495 breaker_type=breaker_type, 2496 breaker_type_hash=breaker_type_hash, 2497 energy=energy, 2498 )
Deserialize a JSON object into an instanced item.
Parameters
- payload (
aiobungie.internal.helpers.JsonObject
): The JSON payload.
Returns
aiobungie.crates.ItemInstance
: An instanced item object.
2500 def deserialize_item_energy(self, payload: typedefs.JSONObject) -> items.ItemEnergy: 2501 energy_hash: int | None = None 2502 if raw_energy_hash := payload.get("energyTypeHash"): 2503 energy_hash = int(raw_energy_hash) 2504 2505 return items.ItemEnergy( 2506 hash=energy_hash, 2507 type=items.ItemEnergyType(int(payload["energyType"])), 2508 capacity=int(payload["energyCapacity"]), 2509 used_energy=int(payload["energyUsed"]), 2510 unused_energy=int(payload["energyUnused"]), 2511 )
2513 def deserialize_item_perk(self, payload: typedefs.JSONObject) -> items.ItemPerk: 2514 perk_hash: int | None = None 2515 if raw_perk_hash := payload.get("perkHash"): 2516 perk_hash = int(raw_perk_hash) 2517 2518 return items.ItemPerk( 2519 hash=perk_hash, 2520 icon=builders.Image(path=payload["iconPath"]), 2521 is_active=payload["isActive"], 2522 is_visible=payload["visible"], 2523 )
2525 def deserialize_item_socket(self, payload: typedefs.JSONObject) -> items.ItemSocket: 2526 plug_hash: int | None = None 2527 if raw_plug_hash := payload.get("plugHash"): 2528 plug_hash = int(raw_plug_hash) 2529 2530 enable_fail_indexes: collections.Sequence[int] | None = None 2531 if raw_indexes := payload.get("enableFailIndexes"): 2532 enable_fail_indexes = tuple(int(index) for index in raw_indexes) 2533 2534 return items.ItemSocket( 2535 plug_hash=plug_hash, 2536 is_enabled=payload["isEnabled"], 2537 enable_fail_indexes=enable_fail_indexes, 2538 is_visible=payload.get("visible"), 2539 )
2548 def deserialize_plug_item_state( 2549 self, payload: typedefs.JSONObject 2550 ) -> items.PlugItemState: 2551 item_hash: int | None = None 2552 if raw_item_hash := payload.get("plugItemHash"): 2553 item_hash = int(raw_item_hash) 2554 2555 insert_fail_indexes: collections.Sequence[int] | None = None 2556 if raw_fail_indexes := payload.get("insertFailIndexes"): 2557 insert_fail_indexes = tuple(int(k) for k in raw_fail_indexes) 2558 2559 enable_fail_indexes: collections.Sequence[int] | None = None 2560 if raw_enabled_indexes := payload.get("enableFailIndexes"): 2561 enable_fail_indexes = tuple(int(k) for k in raw_enabled_indexes) 2562 2563 return items.PlugItemState( 2564 item_hash=item_hash, 2565 insert_fail_indexes=insert_fail_indexes, 2566 enable_fail_indexes=enable_fail_indexes, 2567 is_enabled=payload["enabled"], 2568 can_insert=payload["canInsert"], 2569 )
The global framework instance.
This type is global constant that can be used to deserialize payloads without the need to initialize a new instance of the framework.