nxtlo/aiobungie

View on GitHub
aiobungie/crates/character.py

Summary

Maintainability
A
0 mins
Test Coverage
# -*- coding: utf-8 -*-

# MIT License
#
# Copyright (c) 2020 - Present nxtlo
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

"""Standard implementation of Bungie Character and entities."""

from __future__ import annotations

__all__ = (
    "Character",
    "Dye",
    "MinimalEquipments",
    "RenderedData",
    "CustomizationOptions",
    "CharacterProgression",
)

import typing

import attrs

from aiobungie import url
from aiobungie.internal import helpers

if typing.TYPE_CHECKING:
    import collections.abc as collections
    import datetime

    from aiobungie import traits
    from aiobungie.crates import activity
    from aiobungie.crates import entity
    from aiobungie.crates import milestones as milestones_
    from aiobungie.crates import progressions as progressions_
    from aiobungie.crates import records
    from aiobungie.crates import season
    from aiobungie.internal import assets
    from aiobungie.internal import enums
    from aiobungie.internal import iterators


@attrs.frozen(kw_only=True)
class Dye:
    """Represents dyes rendered on a Destiny character."""

    channel_hash: int
    """The hash of the channel."""

    dye_hash: int
    """The dye's hash."""


@attrs.frozen(kw_only=True, repr=False)
class CustomizationOptions:
    """Raw data represents a character's customization options."""

    personality: int

    face: int

    skin_color: int

    lip_color: int

    eye_color: int

    hair_colors: collections.Sequence[int]

    feature_colors: collections.Sequence[int]

    decal_color: int

    wear_helmet: bool

    hair_index: int

    feature_index: int

    decal_index: int


@attrs.frozen(kw_only=True)
class MinimalEquipments:
    """Minimal information about a character's equipped items.

    This holds the items hash and collection of dyes.

    This is specifically used in CharacterRenderData profile component to render
    3D character object.
    """

    net: traits.Netrunner = attrs.field(repr=False, eq=False, hash=False)
    """A network state used for making external requests."""

    item_hash: int
    """The equipped items's hash."""

    dyes: collections.Collection[Dye]
    """An collection of the item rendering dyes"""

    @helpers.deprecated(
        since="0.2.10",
        removed_in="0.3.0",
        use_instead="{self}.net.request.fetch_inventory_item",
    )
    async def fetch_my_item(self) -> entity.InventoryEntity:
        """Fetch the inventory item definition of this equipment."""
        return await self.net.request.fetch_inventory_item(self.item_hash)


@attrs.frozen(kw_only=True)
class RenderedData:
    """Represents a character's rendered data profile component."""

    net: traits.Netrunner = attrs.field(repr=False, eq=False, hash=False)
    """A network state used for making external requests."""

    custom_dyes: collections.Collection[Dye]
    """A collection of the character's custom dyes."""

    customization: CustomizationOptions
    """Data about what character customization options you picked."""

    equipment: collections.Sequence[MinimalEquipments]
    """A sequence of minimal view of this character's equipment."""

    @helpers.deprecated(
        since="0.2.10",
        removed_in="0.3.0",
        use_instead="{self}.net.request.fetch_inventory_item",
        hint="You can fetch each item in {self}.equipment concurrently.",
    )
    async def fetch_my_items(
        self, *, limit: int | None = None
    ) -> collections.Collection[entity.InventoryEntity]:
        """Fetch the inventory item definition of all the equipment this component has.

        Other Parameters
        ----------
        limit : `int | None`
            An optional item limit to fetch. Default is the length of the equipment.

        Returns
        -------
        `collections.Collection[aiobungie.crates.InventoryEntity]`
            A collection of the fetched item definitions.
        """
        return await helpers.awaits(
            *(item.fetch_my_item() for item in self.equipment[:limit])
        )


@attrs.frozen(kw_only=True)
class CharacterProgression:
    """Represents a character progression profile component."""

    progressions: collections.Mapping[int, progressions_.Progression]
    """A Mapping from progression's hash to progression object."""

    factions: collections.Mapping[int, progressions_.Factions]
    """A Mapping from progression faction's hash to its faction object."""

    milestones: collections.Mapping[int, milestones_.Milestone]
    """A Mapping from the milestone's hash to a milestone object."""

    checklists: collections.Mapping[int, collections.Mapping[int, bool]]

    seasonal_artifact: season.CharacterScopedArtifact
    """Data related to your progress on the current season's artifact that can vary per character."""

    uninstanced_item_objectives: collections.Mapping[
        int, collections.Sequence[records.Objective]
    ]
    """A Mapping from an uninstanced inventory item hash to a sequence of its objectives."""

    # Still not sure if this field returned or not.
    # uninstanced_item_pers: collections.Mapping[int, ...]?


@attrs.frozen(kw_only=True)
class Character:
    """An implementation for a Bungie character."""

    net: traits.Netrunner = attrs.field(repr=False, eq=False, hash=False)
    """A network state used for making external requests."""

    id: int
    """Character's id"""

    member_id: int
    """The character's member id."""

    member_type: enums.MembershipType
    """The character's membership type."""

    light: int
    """Character's light"""

    gender: enums.Gender
    """Character's gender"""

    race: enums.Race
    """Character's race"""

    emblem: assets.Image | None
    """Character's emblem, If included."""

    emblem_icon: assets.Image | None
    """Character's emblem icon, If included."""

    emblem_hash: int | None
    """Character's emblem hash, If included."""

    last_played: datetime.datetime
    """Character's last played date."""

    total_played_time: int
    """Character's total played time in seconds."""

    class_type: enums.Class
    """Character's class."""

    title_hash: int | None
    """Character's equipped title hash."""

    level: int
    """Character's base level."""

    stats: collections.Mapping[enums.Stat, int]
    """A mapping of the character stats and its level."""

    @helpers.deprecated(
        since="0.2.10",
        removed_in="0.3.0",
        use_instead="{self}.net.request.fetch_activities",
    )
    async def fetch_activities(
        self,
        mode: enums.GameMode | int,
        *,
        page: int = 0,
        limit: int = 250,
    ) -> iterators.Iterator[activity.Activity]:
        """Fetch Destiny 2 activities for this character.

        Parameters
        ----------
        mode: `aiobungie.aiobungie.internal.enums.GameMode | int`
            Filters the Game Modes to fetch. i.e., Nightfall, Strike, Iron Banner, etc.

        Other Parameters
        ----------------
        page : `int`
            The page number. Default is `0`
        limit: `int`
            Limit the returned result. Default is `250` which's the max.

        Returns
        -------
        `aiobungie.Iterator[aiobungie.crates.Activity]`
            A iterator over the character's activities.

        Raises
        ------
        `aiobungie.MembershipTypeError`
            The provided membership type was invalid.
        """
        return await self.net.request.fetch_activities(
            self.member_id,
            self.id,
            mode,
            membership_type=self.member_type,
            page=page,
            limit=limit,
        )

    @helpers.deprecated(
        since="0.2.10",
        removed_in="0.3.0",
        use_instead="{self}.net.request.rest.transfer_item",
    )
    async def transfer_item(
        self,
        access_token: str,
        /,
        item_id: int,
        item_hash: int,
        *,
        vault: bool = False,
        stack_size: int = 1,
    ) -> None:
        """Transfer an item from / to your vault.

        Notes
        -----
        * This method requires OAuth2: MoveEquipDestinyItems scope.
        * This method requires both item instance ID and hash.

        Parameters
        ----------
        item_id : `int`
            The item instance ID you want to transfer.
        item_hash : `int`
            The item hash.

        Other Parameters
        ----------------
        stack_size : `int`
            The item stack size.
        vault : `bool`
            Whether to pill this item to your vault or not. Defaults to `False`.
        """
        await self.net.request.rest.transfer_item(
            access_token,
            item_id=item_id,
            character_id=self.id,
            item_hash=item_hash,
            member_type=self.member_type,
            vault=vault,
            stack_size=stack_size,
        )

    @helpers.deprecated(
        since="0.2.10",
        removed_in="0.3.0",
        use_instead="{self}.net.request.rest.pull_item",
    )
    async def pull_item(
        self,
        access_token: str,
        /,
        item_id: int,
        item_hash: int,
        *,
        vault: bool = False,
        stack_size: int = 1,
    ) -> None:
        """Pull an item from the postmaster to this character.

        Notes
        -----
        * This method requires OAuth2: MoveEquipDestinyItems scope.
        * This method requires both item instance ID and hash.

        Parameters
        ----------
        item_id : `int`
            The item instance ID to pull.
        item_hash : `int`
            The item hash.

        Other Parameters
        ----------------
        stack_size : `int`
            The item stack size.
        vault : `bool`
            Whether to pill this item to your vault or not. Defaults to `False`.
        """
        await self.net.request.rest.pull_item(
            access_token,
            item_id=item_id,
            character_id=self.id,
            item_hash=item_hash,
            member_type=self.member_type,
            vault=vault,
            stack_size=stack_size,
        )

    @helpers.deprecated(
        since="0.2.10",
        removed_in="0.3.0",
        use_instead="{self}.net.request.rest.equip_item",
    )
    async def equip_item(self, access_token: str, item_id: int, /) -> None:
        """Equip an item to this character.

        This requires the OAuth2: MoveEquipDestinyItems scope.

        Also You must have a valid Destiny account, and either be
        in a social space, in orbit or offline.

        Parameters
        ----------
        access_token : `str`
            The bearer access token associated with the bungie account.
        item_id : `int`
            The item instance ID.
        """
        await self.net.request.rest.equip_item(
            access_token,
            item_id=item_id,
            character_id=self.id,
            membership_type=self.member_type,
        )

    @helpers.deprecated(
        since="0.2.10",
        removed_in="0.3.0",
        use_instead="{self}.net.request.rest.equip_items",
    )
    async def equip_items(
        self, access_token: str, item_ids: collections.Sequence[int], /
    ) -> None:
        """Equip multiple items to this character.

        This requires the OAuth2: MoveEquipDestinyItems scope.
        Also You must have a valid Destiny account, and either be
        in a social space, in orbit or offline.

        Parameters
        ----------
        access_token : `str`
            The bearer access token associated with the bungie account.
        item_ids: `Sequence[int]`
            A list of item ids you want to equip for this character.
        """
        await self.net.request.rest.equip_items(
            access_token,
            item_ids=item_ids,
            character_id=self.id,
            membership_type=self.member_type,
        )

    @property
    def url(self) -> str:
        """A URL of the character at Bungie.net."""
        return f"{url.BASE}/en/Gear/{int(self.member_type)}/{self.member_id}/{self.id}"

    def __int__(self) -> int:
        return int(self.id)