aiobungie.framework

Implementation of the library's deserialization framework.

  • Framework Is the default deserialization framework that's used within client impl's.
  • Global Is a pre-initialized instance of Framework that can be used at any scope.
   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"""
class Framework(aiobungie.api.framework.Framework):
  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())
def deserialize_bungie_user(self, data: Mapping[str, typing.Any]) -> aiobungie.crates.BungieUser:
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
Returns
def deserialize_partial_bungie_user( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.PartialBungieUser:
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
Returns
def deserialize_destiny_membership( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.DestinyMembership:
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
Returns
def deserialize_destiny_memberships( self, data: Sequence[Mapping[str, typing.Any]]) -> Sequence[aiobungie.crates.DestinyMembership]:
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
Returns
def deserialize_user(self, data: Mapping[str, typing.Any]) -> aiobungie.crates.User:
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
Returns
def deserialize_sanitized_membership( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.user.SanitizedMembership:
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
Returns
  • aiobungie.crates.SanitizedMembership: A sanitized Destiny 2 membership that contains the display names.
def deserialize_searched_user( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.SearchableDestinyUser:
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
Returns
def deserialize_user_credentials( self, payload: Sequence[Mapping[str, typing.Any]]) -> Sequence[aiobungie.crates.UserCredentials]:
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
Returns
def deserialize_user_themes( self, payload: Sequence[Mapping[str, typing.Any]]) -> Sequence[aiobungie.crates.UserThemes]:
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
Returns
def deserialize_group(self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.Group:
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
Returns
def deserialize_clan(self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.Clan:
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
Returns
def deserialize_clan_member( self, data: Mapping[str, typing.Any], /) -> aiobungie.crates.ClanMember:
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
Returns
def deserialize_clan_members( self, data: Mapping[str, typing.Any], /) -> sain.iter.Iterator[aiobungie.crates.ClanMember]:
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
Returns
def deserialize_group_member( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.GroupMember:
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
Returns
def deserialize_clan_conversations( self, payload: Sequence[Mapping[str, typing.Any]]) -> Sequence[aiobungie.crates.ClanConversation]:
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
Returns
def deserialize_application_member( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.application.ApplicationMember:
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
Returns
  • aiobungie.crates.application.ApplicationMember: An application member.
def deserialize_application( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.Application:
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
Returns
def deserialize_profile( self, payload: Mapping[str, typing.Any], /) -> aiobungie.crates.Profile:
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
Returns
def deserialize_profile_item( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.ProfileItemImpl:
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
Returns
def deserialize_objectives( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.Objective:
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
def deserialize_records( self, payload: Mapping[str, typing.Any], scores: aiobungie.crates.RecordScores | None = None, **nodes: int) -> aiobungie.crates.Record:
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 of aiobungie.crates.CharacterRecord with the record object. As it will always be None 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.
def deserialize_character_records( self, payload: Mapping[str, typing.Any], scores: aiobungie.crates.RecordScores | None = None, record_hashes: Sequence[int] = ()) -> aiobungie.crates.CharacterRecord:
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.
def deserialize_character_dye( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.Dye:
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
Returns
def deserialize_character_customization( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.CustomizationOptions:
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
Returns
def deserialize_character_minimal_equipments( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.MinimalEquipments:
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
Returns
def deserialize_character_render_data( self, payload: Mapping[str, typing.Any], /) -> aiobungie.crates.RenderedData:
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
Returns
def deserialize_available_activity( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.AvailableActivity:
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
Returns
def deserialize_character_activity( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.CharacterActivity:
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
Returns
def deserialize_profile_items( self, payload: Mapping[str, typing.Any], /) -> Sequence[aiobungie.crates.ProfileItemImpl]:
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
Returns
def deserialize_progressions( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.Progression:
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        )
def deserialize_milestone( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.Milestone:
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        )
def deserialize_characters( self, payload: Mapping[str, typing.Any]) -> Mapping[int, aiobungie.crates.Character]:
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        }
def deserialize_character( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.Character:
901    def deserialize_character(
902        self, payload: typedefs.JSONObject
903    ) -> character.Character:
904        return self._set_character_attrs(payload)
def deserialize_character_equipments( self, payload: Mapping[str, typing.Any]) -> Mapping[int, Sequence[aiobungie.crates.ProfileItemImpl]]:
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        }
def deserialize_character_activities( self, payload: Mapping[str, typing.Any]) -> Mapping[int, aiobungie.crates.CharacterActivity]:
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        }
def deserialize_characters_render_data( self, payload: Mapping[str, typing.Any]) -> Mapping[int, aiobungie.crates.RenderedData]:
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        }
def deserialize_character_progressions( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.CharacterProgression:
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        )
def deserialize_character_progressions_mapping( self, payload: Mapping[str, typing.Any]) -> Mapping[int, aiobungie.crates.CharacterProgression]:
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
def deserialize_character_loadout( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.Loadout:
 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        )
def deserialize_characters_records( self, payload: Mapping[str, typing.Any]) -> Mapping[int, aiobungie.crates.CharacterRecord]:
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        }
def deserialize_profile_records( self, payload: Mapping[str, typing.Any]) -> Mapping[int, aiobungie.crates.Record]:
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        }
def deserialize_craftables_component( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.CraftablesComponent:
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        )
def deserialize_components( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.Component:
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
def deserialize_items_component( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.ItemsComponent:
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.`

def deserialize_character_component( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.CharacterComponent:
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
Returns
def deserialize_inventory_results( self, payload: Mapping[str, typing.Any]) -> sain.iter.Iterator[aiobungie.crates.SearchableEntity]:
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
Returns
def deserialize_inventory_entity( self, payload: Mapping[str, typing.Any], /) -> aiobungie.crates.InventoryEntity:
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
Returns
def deserialize_objective_entity( self, payload: Mapping[str, typing.Any], /) -> aiobungie.crates.ObjectiveEntity:
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
Returns
def deserialize_activity( self, payload: Mapping[str, typing.Any], /) -> aiobungie.crates.Activity:
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
Returns
def deserialize_activities( self, payload: Mapping[str, typing.Any]) -> sain.iter.Iterator[aiobungie.crates.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
Returns
def deserialize_extended_weapon_values( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.ExtendedWeaponValues:
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
Returns
def deserialize_post_activity_player( self, payload: Mapping[str, typing.Any], /) -> aiobungie.crates.PostActivityPlayer:
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
Returns
def deserialize_post_activity( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.PostActivity:
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
Returns
def deserialize_aggregated_activity( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.AggregatedActivity:
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
Returns
def deserialize_aggregated_activities( self, payload: Mapping[str, typing.Any]) -> sain.iter.Iterator[aiobungie.crates.AggregatedActivity]:
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
Returns
def deserialize_linked_profiles( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.LinkedProfile:
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
Returns
def deserialize_clan_banners( self, payload: Mapping[str, typing.Any]) -> Sequence[aiobungie.crates.ClanBanner]:
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

Deserialize a JSON array of a clan banners information.

Parameters
Returns
def deserialize_public_milestone_content( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.MilestoneContent:
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
Returns
def deserialize_friend( self, payload: Mapping[str, typing.Any], /) -> aiobungie.crates.Friend:
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
Returns
def deserialize_friends( self, payload: Mapping[str, typing.Any]) -> Sequence[aiobungie.crates.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
Returns
def deserialize_friend_requests( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.FriendRequestView:
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
Returns
def deserialize_fireteams( self, payload: Mapping[str, typing.Any]) -> Sequence[aiobungie.crates.Fireteam]:
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
Returns
def deserialize_fireteam_destiny_membership( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.FireteamUser:
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
Returns
def deserialize_fireteam_members( self, payload: Mapping[str, typing.Any], *, alternatives: bool = False) -> Sequence[aiobungie.crates.FireteamMember]:
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 to True, Then it will deserialize the alternatives data in the payload. If not the it will just deserialize the members data.
Returns
def deserialize_available_fireteam( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.AvailableFireteam:
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
Returns
  • An available fireteam object.
def deserialize_available_fireteams( self, data: Mapping[str, typing.Any]) -> Sequence[aiobungie.crates.AvailableFireteam]:
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
Returns
def deserialize_fireteam_party( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.FireteamParty:
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
Returns
def deserialize_seasonal_artifact( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.Artifact:
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
def deserialize_profile_progression( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.ProfileProgression:
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
def deserialize_instanced_item( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.ItemInstance:
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
def deserialize_item_energy( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.ItemEnergy:
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        )
def deserialize_item_perk( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.ItemPerk:
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        )
def deserialize_item_socket( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.ItemSocket:
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        )
def deserialize_item_stats_view( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.ItemStatsView:
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        )
def deserialize_plug_item_state( self, payload: Mapping[str, typing.Any]) -> aiobungie.crates.PlugItemState:
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        )
Global = <Framework object>

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.