Module genshin.client.components.calculator.client

Calculator client.

Classes

class CalculatorClient (cookies: ForwardRef('http.cookies.BaseCookie[Any]') | Mapping[Any, Any] | str | Sequence[ForwardRef('http.cookies.BaseCookie[Any]') | Mapping[Any, Any] | str] | None = None,
*,
authkey: str | None = None,
lang: str = 'en-us',
region: Region = Region.OVERSEAS,
proxy: str | None = None,
game: Game | None = None,
uid: int | None = None,
hoyolab_id: int | None = None,
device_id: str | None = None,
device_fp: str | None = None,
headers: Mapping[str, str] | Mapping[multidict._multidict.istr, str] | multidict._multidict.CIMultiDict | multidict._multidict.CIMultiDictProxy | Iterable[Tuple[str | multidict._multidict.istr, str]] | None = None,
cache: BaseCache | None = None,
debug: bool = False)
Expand source code
class CalculatorClient(base.BaseClient):
    """Calculator component."""

    async def request_calculator(
        self,
        endpoint: str,
        *,
        method: str = "POST",
        lang: typing.Optional[str] = None,
        params: typing.Optional[typing.Mapping[str, typing.Any]] = None,
        data: typing.Optional[typing.Mapping[str, typing.Any]] = None,
        headers: typing.Optional[aiohttp.typedefs.LooseHeaders] = None,
        **kwargs: typing.Any,
    ) -> typing.Mapping[str, typing.Any]:
        """Make a request towards the calculator endpoint."""
        params = dict(params or {})
        headers = base.parse_loose_headers(headers)

        base_url = routes.CALCULATOR_URL.get_url(self.region)
        url = base_url / endpoint

        if method == "GET":
            params["lang"] = lang or self.lang
            data = None
        else:
            data = dict(data or {})
            data["lang"] = lang or self.lang

        if self.region == types.Region.CHINESE:
            headers["referer"] = str(routes.CALCULATOR_REFERER_URL.get_url())
        update_task = asyncio.create_task(utility.update_characters_any(lang or self.lang, lenient=True))

        data = await self.request(url, method=method, params=params, data=data, headers=headers, **kwargs)

        try:
            await update_task
        except Exception as e:
            warnings.warn(f"Failed to update characters: {e!r}")

        return data

    async def _execute_calculator(
        self,
        data: typing.Mapping[str, typing.Any],
        *,
        lang: typing.Optional[str] = None,
    ) -> models.CalculatorResult:
        """Calculate the results of a builder."""
        data = await self.request_calculator("compute", lang=lang, data=data)
        return models.CalculatorResult(**data)

    async def _execute_furnishings_calculator(
        self,
        data: typing.Mapping[str, typing.Any],
        *,
        lang: typing.Optional[str] = None,
    ) -> models.CalculatorFurnishingResults:
        """Calculate the results of a builder."""
        data = await self.request_calculator("furniture/compute", lang=lang, data=data)
        return models.CalculatorFurnishingResults(**data)

    def calculator(self, *, lang: typing.Optional[str] = None) -> Calculator:
        """Create a calculator builder object."""
        return Calculator(self, lang=lang)

    def furnishings_calculator(self, *, lang: typing.Optional[str] = None) -> FurnishingCalculator:
        """Create a calculator builder object."""
        return FurnishingCalculator(self, lang=lang)

    async def _enable_calculator_sync(self, enabled: bool = True) -> None:
        """Enable data syncing in calculator."""
        await self.request_calculator("avatar/auth", method="POST", data=dict(avatar_auth=int(enabled)))

    async def _get_calculator_items(
        self,
        slug: str,
        filters: typing.Mapping[str, typing.Any],
        query: typing.Optional[str] = None,
        *,
        uid: typing.Optional[int] = None,
        is_all: bool = False,
        sync: bool = False,
        lang: typing.Optional[str] = None,
        autoauth: bool = True,
    ) -> typing.Sequence[typing.Mapping[str, typing.Any]]:
        """Get all items of a specific slug from a calculator."""
        endpoint = f"sync/{slug}/list" if sync else f"{slug}/list"

        if query:
            if any(filters.values()):
                raise TypeError("Cannot specify a query and filter at the same time")

            filters = dict(keywords=query, **filters)

        payload: dict[str, typing.Any] = dict(page=1, size=69420, is_all=is_all, **filters)

        if sync:
            uid = uid or await self._get_uid(types.Game.GENSHIN)
            payload["uid"] = uid
            payload["region"] = utility.recognize_genshin_server(uid)

        cache: typing.Optional[client_cache.CacheKey] = None
        if not any(filters.values()) and not sync:
            cache = client_cache.cache_key("calculator", slug=slug, lang=lang or self.lang)

        try:
            data = await self.request_calculator(endpoint, lang=lang, data=payload, cache=cache)
        except errors.GenshinException as e:
            if e.retcode != -502002:  # Sync not enabled
                raise
            if not autoauth:
                raise errors.GenshinException(e.response, "Calculator sync is not enabled") from e

            await self._enable_calculator_sync()
            data = await self.request_calculator(endpoint, lang=lang, data=payload, cache=cache)

        return data["list"]

    async def get_calculator_characters(
        self,
        *,
        query: typing.Optional[str] = None,
        elements: typing.Optional[typing.Sequence[int]] = None,
        weapon_types: typing.Optional[typing.Sequence[int]] = None,
        include_traveler: bool = False,
        sync: bool = False,
        uid: typing.Optional[int] = None,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorCharacter]:
        """Get all characters provided by the Enhancement Progression Calculator."""
        data = await self._get_calculator_items(
            "avatar",
            lang=lang,
            is_all=include_traveler,
            sync=sync,
            uid=uid,
            query=query,
            filters=dict(
                element_attr_ids=elements or [],
                weapon_cat_ids=weapon_types or [],
            ),
        )
        return [models.CalculatorCharacter(**i) for i in data]

    async def get_calculator_weapons(
        self,
        *,
        query: typing.Optional[str] = None,
        types: typing.Optional[typing.Sequence[int]] = None,
        rarities: typing.Optional[typing.Sequence[int]] = None,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorWeapon]:
        """Get all weapons provided by the Enhancement Progression Calculator."""
        data = await self._get_calculator_items(
            "weapon",
            lang=lang,
            query=query,
            filters=dict(
                weapon_cat_ids=types or [],
                weapon_levels=rarities or [],
            ),
        )
        return [models.CalculatorWeapon(**i) for i in data]

    async def get_calculator_artifacts(
        self,
        *,
        query: typing.Optional[str] = None,
        pos: int = 1,
        rarities: typing.Optional[typing.Sequence[int]] = None,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorArtifact]:
        """Get all artifacts provided by the Enhancement Progression Calculator."""
        data = await self._get_calculator_items(
            "reliquary",
            lang=lang,
            query=query,
            filters=dict(
                reliquary_cat_id=pos,
                reliquary_levels=rarities or [],
            ),
        )
        return [models.CalculatorArtifact(**i) for i in data]

    async def get_calculator_furnishings(
        self,
        *,
        types: typing.Optional[int] = None,
        rarities: typing.Optional[int] = None,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorFurnishing]:
        """Get all furnishings provided by the Enhancement Progression Calculator."""
        data = await self._get_calculator_items(
            "furniture",
            lang=lang,
            filters=dict(
                cat_id=types or 0,
                weapon_levels=rarities or 0,
            ),
        )
        return [models.CalculatorFurnishing(**i) for i in data]

    async def get_character_details(
        self,
        character: types.IDOr[genshin_models.BaseCharacter],
        *,
        uid: typing.Optional[int] = None,
        lang: typing.Optional[str] = None,
    ) -> models.CalculatorCharacterDetails:
        """Get the weapon, artifacts and talents of a character.

        Not related to the Battle Chronicle.
        This data is always private.
        """
        uid = uid or await self._get_uid(types.Game.GENSHIN)

        data = await self.request_calculator(
            "sync/avatar/detail",
            method="GET",
            lang=lang,
            params=dict(
                avatar_id=int(character),
                uid=uid,
                region=utility.recognize_genshin_server(uid),
            ),
        )
        return models.CalculatorCharacterDetails(**data)

    async def get_character_talents(
        self,
        character: types.IDOr[genshin_models.BaseCharacter],
        *,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorTalent]:
        """Get the talents of a character.

        This only gets the talent names, not their levels.
        Use `get_character_details` for precise information.
        """
        data = await self.request_calculator(
            "avatar/skill_list",
            method="GET",
            lang=lang,
            params=dict(avatar_id=int(character)),
        )
        return [models.CalculatorTalent(**i) for i in data["list"]]

    async def get_complete_artifact_set(
        self,
        artifact: types.IDOr[typing.Union[genshin_models.Artifact, genshin_models.CalculatorArtifact]],
        *,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorArtifact]:
        """Get all other artifacts that share a set with any given artifact.

        Doesn't return the artifact passed into this function.
        """
        data = await self.request_calculator(
            "reliquary/set",
            method="GET",
            lang=lang,
            params=dict(reliquary_id=int(artifact)),
            cache=client_cache.cache_key("calculator", slug="set", artifact=int(artifact), lang=lang or self.lang),
        )
        return [models.CalculatorArtifact(**i) for i in data["reliquary_list"]]

    async def _get_all_artifact_ids(self, artifact_id: int) -> typing.Sequence[int]:
        """Get all artifact ids in the same set as a given artifact id."""
        others = await self.get_complete_artifact_set(artifact_id)
        return [artifact_id] + [other.id for other in others]

    async def get_teapot_replica_blueprint(
        self,
        share_code: int,
        *,
        region: typing.Optional[str] = None,
        uid: typing.Optional[int] = None,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorFurnishing]:
        """Get furnishings used by a teapot replica blueprint."""
        if not region:
            uid = uid or await self._get_uid(types.Game.GENSHIN)
            region = utility.recognize_genshin_server(uid)

        data = await self.request_calculator(
            "furniture/blueprint",
            method="GET",
            lang=lang,
            params=dict(share_code=share_code, region=region),
            cache=client_cache.cache_key("calculator", slug="blueprint", share_code=share_code, lang=lang or self.lang),
        )
        return [models.CalculatorFurnishing(**i) for i in data["list"]]

    @deprecation.deprecated("await genshin.utility.update_characters_any()")
    async def update_character_names(self, *, lang: typing.Optional[str] = None) -> None:
        """Update stored db characters with the names from the calculator."""
        characters = await self.get_calculator_characters(lang=lang, include_traveler=True)

        for char in characters:
            icon = genshin_models.character._parse_icon(char.icon)
            dbchar = genshin_models.DBChar(
                char.id, icon, char.name, "" if "Player" in icon else char.element, char.rarity
            )

            genshin_models.CHARACTER_NAMES[lang or self.lang][dbchar.id] = dbchar

Calculator component.

Ancestors

Subclasses

Class variables

var logger : logging.Logger

Instance variables

var authkeys : dict[Game, str]
Expand source code
class CalculatorClient(base.BaseClient):
    """Calculator component."""

    async def request_calculator(
        self,
        endpoint: str,
        *,
        method: str = "POST",
        lang: typing.Optional[str] = None,
        params: typing.Optional[typing.Mapping[str, typing.Any]] = None,
        data: typing.Optional[typing.Mapping[str, typing.Any]] = None,
        headers: typing.Optional[aiohttp.typedefs.LooseHeaders] = None,
        **kwargs: typing.Any,
    ) -> typing.Mapping[str, typing.Any]:
        """Make a request towards the calculator endpoint."""
        params = dict(params or {})
        headers = base.parse_loose_headers(headers)

        base_url = routes.CALCULATOR_URL.get_url(self.region)
        url = base_url / endpoint

        if method == "GET":
            params["lang"] = lang or self.lang
            data = None
        else:
            data = dict(data or {})
            data["lang"] = lang or self.lang

        if self.region == types.Region.CHINESE:
            headers["referer"] = str(routes.CALCULATOR_REFERER_URL.get_url())
        update_task = asyncio.create_task(utility.update_characters_any(lang or self.lang, lenient=True))

        data = await self.request(url, method=method, params=params, data=data, headers=headers, **kwargs)

        try:
            await update_task
        except Exception as e:
            warnings.warn(f"Failed to update characters: {e!r}")

        return data

    async def _execute_calculator(
        self,
        data: typing.Mapping[str, typing.Any],
        *,
        lang: typing.Optional[str] = None,
    ) -> models.CalculatorResult:
        """Calculate the results of a builder."""
        data = await self.request_calculator("compute", lang=lang, data=data)
        return models.CalculatorResult(**data)

    async def _execute_furnishings_calculator(
        self,
        data: typing.Mapping[str, typing.Any],
        *,
        lang: typing.Optional[str] = None,
    ) -> models.CalculatorFurnishingResults:
        """Calculate the results of a builder."""
        data = await self.request_calculator("furniture/compute", lang=lang, data=data)
        return models.CalculatorFurnishingResults(**data)

    def calculator(self, *, lang: typing.Optional[str] = None) -> Calculator:
        """Create a calculator builder object."""
        return Calculator(self, lang=lang)

    def furnishings_calculator(self, *, lang: typing.Optional[str] = None) -> FurnishingCalculator:
        """Create a calculator builder object."""
        return FurnishingCalculator(self, lang=lang)

    async def _enable_calculator_sync(self, enabled: bool = True) -> None:
        """Enable data syncing in calculator."""
        await self.request_calculator("avatar/auth", method="POST", data=dict(avatar_auth=int(enabled)))

    async def _get_calculator_items(
        self,
        slug: str,
        filters: typing.Mapping[str, typing.Any],
        query: typing.Optional[str] = None,
        *,
        uid: typing.Optional[int] = None,
        is_all: bool = False,
        sync: bool = False,
        lang: typing.Optional[str] = None,
        autoauth: bool = True,
    ) -> typing.Sequence[typing.Mapping[str, typing.Any]]:
        """Get all items of a specific slug from a calculator."""
        endpoint = f"sync/{slug}/list" if sync else f"{slug}/list"

        if query:
            if any(filters.values()):
                raise TypeError("Cannot specify a query and filter at the same time")

            filters = dict(keywords=query, **filters)

        payload: dict[str, typing.Any] = dict(page=1, size=69420, is_all=is_all, **filters)

        if sync:
            uid = uid or await self._get_uid(types.Game.GENSHIN)
            payload["uid"] = uid
            payload["region"] = utility.recognize_genshin_server(uid)

        cache: typing.Optional[client_cache.CacheKey] = None
        if not any(filters.values()) and not sync:
            cache = client_cache.cache_key("calculator", slug=slug, lang=lang or self.lang)

        try:
            data = await self.request_calculator(endpoint, lang=lang, data=payload, cache=cache)
        except errors.GenshinException as e:
            if e.retcode != -502002:  # Sync not enabled
                raise
            if not autoauth:
                raise errors.GenshinException(e.response, "Calculator sync is not enabled") from e

            await self._enable_calculator_sync()
            data = await self.request_calculator(endpoint, lang=lang, data=payload, cache=cache)

        return data["list"]

    async def get_calculator_characters(
        self,
        *,
        query: typing.Optional[str] = None,
        elements: typing.Optional[typing.Sequence[int]] = None,
        weapon_types: typing.Optional[typing.Sequence[int]] = None,
        include_traveler: bool = False,
        sync: bool = False,
        uid: typing.Optional[int] = None,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorCharacter]:
        """Get all characters provided by the Enhancement Progression Calculator."""
        data = await self._get_calculator_items(
            "avatar",
            lang=lang,
            is_all=include_traveler,
            sync=sync,
            uid=uid,
            query=query,
            filters=dict(
                element_attr_ids=elements or [],
                weapon_cat_ids=weapon_types or [],
            ),
        )
        return [models.CalculatorCharacter(**i) for i in data]

    async def get_calculator_weapons(
        self,
        *,
        query: typing.Optional[str] = None,
        types: typing.Optional[typing.Sequence[int]] = None,
        rarities: typing.Optional[typing.Sequence[int]] = None,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorWeapon]:
        """Get all weapons provided by the Enhancement Progression Calculator."""
        data = await self._get_calculator_items(
            "weapon",
            lang=lang,
            query=query,
            filters=dict(
                weapon_cat_ids=types or [],
                weapon_levels=rarities or [],
            ),
        )
        return [models.CalculatorWeapon(**i) for i in data]

    async def get_calculator_artifacts(
        self,
        *,
        query: typing.Optional[str] = None,
        pos: int = 1,
        rarities: typing.Optional[typing.Sequence[int]] = None,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorArtifact]:
        """Get all artifacts provided by the Enhancement Progression Calculator."""
        data = await self._get_calculator_items(
            "reliquary",
            lang=lang,
            query=query,
            filters=dict(
                reliquary_cat_id=pos,
                reliquary_levels=rarities or [],
            ),
        )
        return [models.CalculatorArtifact(**i) for i in data]

    async def get_calculator_furnishings(
        self,
        *,
        types: typing.Optional[int] = None,
        rarities: typing.Optional[int] = None,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorFurnishing]:
        """Get all furnishings provided by the Enhancement Progression Calculator."""
        data = await self._get_calculator_items(
            "furniture",
            lang=lang,
            filters=dict(
                cat_id=types or 0,
                weapon_levels=rarities or 0,
            ),
        )
        return [models.CalculatorFurnishing(**i) for i in data]

    async def get_character_details(
        self,
        character: types.IDOr[genshin_models.BaseCharacter],
        *,
        uid: typing.Optional[int] = None,
        lang: typing.Optional[str] = None,
    ) -> models.CalculatorCharacterDetails:
        """Get the weapon, artifacts and talents of a character.

        Not related to the Battle Chronicle.
        This data is always private.
        """
        uid = uid or await self._get_uid(types.Game.GENSHIN)

        data = await self.request_calculator(
            "sync/avatar/detail",
            method="GET",
            lang=lang,
            params=dict(
                avatar_id=int(character),
                uid=uid,
                region=utility.recognize_genshin_server(uid),
            ),
        )
        return models.CalculatorCharacterDetails(**data)

    async def get_character_talents(
        self,
        character: types.IDOr[genshin_models.BaseCharacter],
        *,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorTalent]:
        """Get the talents of a character.

        This only gets the talent names, not their levels.
        Use `get_character_details` for precise information.
        """
        data = await self.request_calculator(
            "avatar/skill_list",
            method="GET",
            lang=lang,
            params=dict(avatar_id=int(character)),
        )
        return [models.CalculatorTalent(**i) for i in data["list"]]

    async def get_complete_artifact_set(
        self,
        artifact: types.IDOr[typing.Union[genshin_models.Artifact, genshin_models.CalculatorArtifact]],
        *,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorArtifact]:
        """Get all other artifacts that share a set with any given artifact.

        Doesn't return the artifact passed into this function.
        """
        data = await self.request_calculator(
            "reliquary/set",
            method="GET",
            lang=lang,
            params=dict(reliquary_id=int(artifact)),
            cache=client_cache.cache_key("calculator", slug="set", artifact=int(artifact), lang=lang or self.lang),
        )
        return [models.CalculatorArtifact(**i) for i in data["reliquary_list"]]

    async def _get_all_artifact_ids(self, artifact_id: int) -> typing.Sequence[int]:
        """Get all artifact ids in the same set as a given artifact id."""
        others = await self.get_complete_artifact_set(artifact_id)
        return [artifact_id] + [other.id for other in others]

    async def get_teapot_replica_blueprint(
        self,
        share_code: int,
        *,
        region: typing.Optional[str] = None,
        uid: typing.Optional[int] = None,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorFurnishing]:
        """Get furnishings used by a teapot replica blueprint."""
        if not region:
            uid = uid or await self._get_uid(types.Game.GENSHIN)
            region = utility.recognize_genshin_server(uid)

        data = await self.request_calculator(
            "furniture/blueprint",
            method="GET",
            lang=lang,
            params=dict(share_code=share_code, region=region),
            cache=client_cache.cache_key("calculator", slug="blueprint", share_code=share_code, lang=lang or self.lang),
        )
        return [models.CalculatorFurnishing(**i) for i in data["list"]]

    @deprecation.deprecated("await genshin.utility.update_characters_any()")
    async def update_character_names(self, *, lang: typing.Optional[str] = None) -> None:
        """Update stored db characters with the names from the calculator."""
        characters = await self.get_calculator_characters(lang=lang, include_traveler=True)

        for char in characters:
            icon = genshin_models.character._parse_icon(char.icon)
            dbchar = genshin_models.DBChar(
                char.id, icon, char.name, "" if "Player" in icon else char.element, char.rarity
            )

            genshin_models.CHARACTER_NAMES[lang or self.lang][dbchar.id] = dbchar
var cacheBaseCache
Expand source code
class CalculatorClient(base.BaseClient):
    """Calculator component."""

    async def request_calculator(
        self,
        endpoint: str,
        *,
        method: str = "POST",
        lang: typing.Optional[str] = None,
        params: typing.Optional[typing.Mapping[str, typing.Any]] = None,
        data: typing.Optional[typing.Mapping[str, typing.Any]] = None,
        headers: typing.Optional[aiohttp.typedefs.LooseHeaders] = None,
        **kwargs: typing.Any,
    ) -> typing.Mapping[str, typing.Any]:
        """Make a request towards the calculator endpoint."""
        params = dict(params or {})
        headers = base.parse_loose_headers(headers)

        base_url = routes.CALCULATOR_URL.get_url(self.region)
        url = base_url / endpoint

        if method == "GET":
            params["lang"] = lang or self.lang
            data = None
        else:
            data = dict(data or {})
            data["lang"] = lang or self.lang

        if self.region == types.Region.CHINESE:
            headers["referer"] = str(routes.CALCULATOR_REFERER_URL.get_url())
        update_task = asyncio.create_task(utility.update_characters_any(lang or self.lang, lenient=True))

        data = await self.request(url, method=method, params=params, data=data, headers=headers, **kwargs)

        try:
            await update_task
        except Exception as e:
            warnings.warn(f"Failed to update characters: {e!r}")

        return data

    async def _execute_calculator(
        self,
        data: typing.Mapping[str, typing.Any],
        *,
        lang: typing.Optional[str] = None,
    ) -> models.CalculatorResult:
        """Calculate the results of a builder."""
        data = await self.request_calculator("compute", lang=lang, data=data)
        return models.CalculatorResult(**data)

    async def _execute_furnishings_calculator(
        self,
        data: typing.Mapping[str, typing.Any],
        *,
        lang: typing.Optional[str] = None,
    ) -> models.CalculatorFurnishingResults:
        """Calculate the results of a builder."""
        data = await self.request_calculator("furniture/compute", lang=lang, data=data)
        return models.CalculatorFurnishingResults(**data)

    def calculator(self, *, lang: typing.Optional[str] = None) -> Calculator:
        """Create a calculator builder object."""
        return Calculator(self, lang=lang)

    def furnishings_calculator(self, *, lang: typing.Optional[str] = None) -> FurnishingCalculator:
        """Create a calculator builder object."""
        return FurnishingCalculator(self, lang=lang)

    async def _enable_calculator_sync(self, enabled: bool = True) -> None:
        """Enable data syncing in calculator."""
        await self.request_calculator("avatar/auth", method="POST", data=dict(avatar_auth=int(enabled)))

    async def _get_calculator_items(
        self,
        slug: str,
        filters: typing.Mapping[str, typing.Any],
        query: typing.Optional[str] = None,
        *,
        uid: typing.Optional[int] = None,
        is_all: bool = False,
        sync: bool = False,
        lang: typing.Optional[str] = None,
        autoauth: bool = True,
    ) -> typing.Sequence[typing.Mapping[str, typing.Any]]:
        """Get all items of a specific slug from a calculator."""
        endpoint = f"sync/{slug}/list" if sync else f"{slug}/list"

        if query:
            if any(filters.values()):
                raise TypeError("Cannot specify a query and filter at the same time")

            filters = dict(keywords=query, **filters)

        payload: dict[str, typing.Any] = dict(page=1, size=69420, is_all=is_all, **filters)

        if sync:
            uid = uid or await self._get_uid(types.Game.GENSHIN)
            payload["uid"] = uid
            payload["region"] = utility.recognize_genshin_server(uid)

        cache: typing.Optional[client_cache.CacheKey] = None
        if not any(filters.values()) and not sync:
            cache = client_cache.cache_key("calculator", slug=slug, lang=lang or self.lang)

        try:
            data = await self.request_calculator(endpoint, lang=lang, data=payload, cache=cache)
        except errors.GenshinException as e:
            if e.retcode != -502002:  # Sync not enabled
                raise
            if not autoauth:
                raise errors.GenshinException(e.response, "Calculator sync is not enabled") from e

            await self._enable_calculator_sync()
            data = await self.request_calculator(endpoint, lang=lang, data=payload, cache=cache)

        return data["list"]

    async def get_calculator_characters(
        self,
        *,
        query: typing.Optional[str] = None,
        elements: typing.Optional[typing.Sequence[int]] = None,
        weapon_types: typing.Optional[typing.Sequence[int]] = None,
        include_traveler: bool = False,
        sync: bool = False,
        uid: typing.Optional[int] = None,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorCharacter]:
        """Get all characters provided by the Enhancement Progression Calculator."""
        data = await self._get_calculator_items(
            "avatar",
            lang=lang,
            is_all=include_traveler,
            sync=sync,
            uid=uid,
            query=query,
            filters=dict(
                element_attr_ids=elements or [],
                weapon_cat_ids=weapon_types or [],
            ),
        )
        return [models.CalculatorCharacter(**i) for i in data]

    async def get_calculator_weapons(
        self,
        *,
        query: typing.Optional[str] = None,
        types: typing.Optional[typing.Sequence[int]] = None,
        rarities: typing.Optional[typing.Sequence[int]] = None,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorWeapon]:
        """Get all weapons provided by the Enhancement Progression Calculator."""
        data = await self._get_calculator_items(
            "weapon",
            lang=lang,
            query=query,
            filters=dict(
                weapon_cat_ids=types or [],
                weapon_levels=rarities or [],
            ),
        )
        return [models.CalculatorWeapon(**i) for i in data]

    async def get_calculator_artifacts(
        self,
        *,
        query: typing.Optional[str] = None,
        pos: int = 1,
        rarities: typing.Optional[typing.Sequence[int]] = None,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorArtifact]:
        """Get all artifacts provided by the Enhancement Progression Calculator."""
        data = await self._get_calculator_items(
            "reliquary",
            lang=lang,
            query=query,
            filters=dict(
                reliquary_cat_id=pos,
                reliquary_levels=rarities or [],
            ),
        )
        return [models.CalculatorArtifact(**i) for i in data]

    async def get_calculator_furnishings(
        self,
        *,
        types: typing.Optional[int] = None,
        rarities: typing.Optional[int] = None,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorFurnishing]:
        """Get all furnishings provided by the Enhancement Progression Calculator."""
        data = await self._get_calculator_items(
            "furniture",
            lang=lang,
            filters=dict(
                cat_id=types or 0,
                weapon_levels=rarities or 0,
            ),
        )
        return [models.CalculatorFurnishing(**i) for i in data]

    async def get_character_details(
        self,
        character: types.IDOr[genshin_models.BaseCharacter],
        *,
        uid: typing.Optional[int] = None,
        lang: typing.Optional[str] = None,
    ) -> models.CalculatorCharacterDetails:
        """Get the weapon, artifacts and talents of a character.

        Not related to the Battle Chronicle.
        This data is always private.
        """
        uid = uid or await self._get_uid(types.Game.GENSHIN)

        data = await self.request_calculator(
            "sync/avatar/detail",
            method="GET",
            lang=lang,
            params=dict(
                avatar_id=int(character),
                uid=uid,
                region=utility.recognize_genshin_server(uid),
            ),
        )
        return models.CalculatorCharacterDetails(**data)

    async def get_character_talents(
        self,
        character: types.IDOr[genshin_models.BaseCharacter],
        *,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorTalent]:
        """Get the talents of a character.

        This only gets the talent names, not their levels.
        Use `get_character_details` for precise information.
        """
        data = await self.request_calculator(
            "avatar/skill_list",
            method="GET",
            lang=lang,
            params=dict(avatar_id=int(character)),
        )
        return [models.CalculatorTalent(**i) for i in data["list"]]

    async def get_complete_artifact_set(
        self,
        artifact: types.IDOr[typing.Union[genshin_models.Artifact, genshin_models.CalculatorArtifact]],
        *,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorArtifact]:
        """Get all other artifacts that share a set with any given artifact.

        Doesn't return the artifact passed into this function.
        """
        data = await self.request_calculator(
            "reliquary/set",
            method="GET",
            lang=lang,
            params=dict(reliquary_id=int(artifact)),
            cache=client_cache.cache_key("calculator", slug="set", artifact=int(artifact), lang=lang or self.lang),
        )
        return [models.CalculatorArtifact(**i) for i in data["reliquary_list"]]

    async def _get_all_artifact_ids(self, artifact_id: int) -> typing.Sequence[int]:
        """Get all artifact ids in the same set as a given artifact id."""
        others = await self.get_complete_artifact_set(artifact_id)
        return [artifact_id] + [other.id for other in others]

    async def get_teapot_replica_blueprint(
        self,
        share_code: int,
        *,
        region: typing.Optional[str] = None,
        uid: typing.Optional[int] = None,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorFurnishing]:
        """Get furnishings used by a teapot replica blueprint."""
        if not region:
            uid = uid or await self._get_uid(types.Game.GENSHIN)
            region = utility.recognize_genshin_server(uid)

        data = await self.request_calculator(
            "furniture/blueprint",
            method="GET",
            lang=lang,
            params=dict(share_code=share_code, region=region),
            cache=client_cache.cache_key("calculator", slug="blueprint", share_code=share_code, lang=lang or self.lang),
        )
        return [models.CalculatorFurnishing(**i) for i in data["list"]]

    @deprecation.deprecated("await genshin.utility.update_characters_any()")
    async def update_character_names(self, *, lang: typing.Optional[str] = None) -> None:
        """Update stored db characters with the names from the calculator."""
        characters = await self.get_calculator_characters(lang=lang, include_traveler=True)

        for char in characters:
            icon = genshin_models.character._parse_icon(char.icon)
            dbchar = genshin_models.DBChar(
                char.id, icon, char.name, "" if "Player" in icon else char.element, char.rarity
            )

            genshin_models.CHARACTER_NAMES[lang or self.lang][dbchar.id] = dbchar
var cookie_managerBaseCookieManager
Expand source code
class CalculatorClient(base.BaseClient):
    """Calculator component."""

    async def request_calculator(
        self,
        endpoint: str,
        *,
        method: str = "POST",
        lang: typing.Optional[str] = None,
        params: typing.Optional[typing.Mapping[str, typing.Any]] = None,
        data: typing.Optional[typing.Mapping[str, typing.Any]] = None,
        headers: typing.Optional[aiohttp.typedefs.LooseHeaders] = None,
        **kwargs: typing.Any,
    ) -> typing.Mapping[str, typing.Any]:
        """Make a request towards the calculator endpoint."""
        params = dict(params or {})
        headers = base.parse_loose_headers(headers)

        base_url = routes.CALCULATOR_URL.get_url(self.region)
        url = base_url / endpoint

        if method == "GET":
            params["lang"] = lang or self.lang
            data = None
        else:
            data = dict(data or {})
            data["lang"] = lang or self.lang

        if self.region == types.Region.CHINESE:
            headers["referer"] = str(routes.CALCULATOR_REFERER_URL.get_url())
        update_task = asyncio.create_task(utility.update_characters_any(lang or self.lang, lenient=True))

        data = await self.request(url, method=method, params=params, data=data, headers=headers, **kwargs)

        try:
            await update_task
        except Exception as e:
            warnings.warn(f"Failed to update characters: {e!r}")

        return data

    async def _execute_calculator(
        self,
        data: typing.Mapping[str, typing.Any],
        *,
        lang: typing.Optional[str] = None,
    ) -> models.CalculatorResult:
        """Calculate the results of a builder."""
        data = await self.request_calculator("compute", lang=lang, data=data)
        return models.CalculatorResult(**data)

    async def _execute_furnishings_calculator(
        self,
        data: typing.Mapping[str, typing.Any],
        *,
        lang: typing.Optional[str] = None,
    ) -> models.CalculatorFurnishingResults:
        """Calculate the results of a builder."""
        data = await self.request_calculator("furniture/compute", lang=lang, data=data)
        return models.CalculatorFurnishingResults(**data)

    def calculator(self, *, lang: typing.Optional[str] = None) -> Calculator:
        """Create a calculator builder object."""
        return Calculator(self, lang=lang)

    def furnishings_calculator(self, *, lang: typing.Optional[str] = None) -> FurnishingCalculator:
        """Create a calculator builder object."""
        return FurnishingCalculator(self, lang=lang)

    async def _enable_calculator_sync(self, enabled: bool = True) -> None:
        """Enable data syncing in calculator."""
        await self.request_calculator("avatar/auth", method="POST", data=dict(avatar_auth=int(enabled)))

    async def _get_calculator_items(
        self,
        slug: str,
        filters: typing.Mapping[str, typing.Any],
        query: typing.Optional[str] = None,
        *,
        uid: typing.Optional[int] = None,
        is_all: bool = False,
        sync: bool = False,
        lang: typing.Optional[str] = None,
        autoauth: bool = True,
    ) -> typing.Sequence[typing.Mapping[str, typing.Any]]:
        """Get all items of a specific slug from a calculator."""
        endpoint = f"sync/{slug}/list" if sync else f"{slug}/list"

        if query:
            if any(filters.values()):
                raise TypeError("Cannot specify a query and filter at the same time")

            filters = dict(keywords=query, **filters)

        payload: dict[str, typing.Any] = dict(page=1, size=69420, is_all=is_all, **filters)

        if sync:
            uid = uid or await self._get_uid(types.Game.GENSHIN)
            payload["uid"] = uid
            payload["region"] = utility.recognize_genshin_server(uid)

        cache: typing.Optional[client_cache.CacheKey] = None
        if not any(filters.values()) and not sync:
            cache = client_cache.cache_key("calculator", slug=slug, lang=lang or self.lang)

        try:
            data = await self.request_calculator(endpoint, lang=lang, data=payload, cache=cache)
        except errors.GenshinException as e:
            if e.retcode != -502002:  # Sync not enabled
                raise
            if not autoauth:
                raise errors.GenshinException(e.response, "Calculator sync is not enabled") from e

            await self._enable_calculator_sync()
            data = await self.request_calculator(endpoint, lang=lang, data=payload, cache=cache)

        return data["list"]

    async def get_calculator_characters(
        self,
        *,
        query: typing.Optional[str] = None,
        elements: typing.Optional[typing.Sequence[int]] = None,
        weapon_types: typing.Optional[typing.Sequence[int]] = None,
        include_traveler: bool = False,
        sync: bool = False,
        uid: typing.Optional[int] = None,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorCharacter]:
        """Get all characters provided by the Enhancement Progression Calculator."""
        data = await self._get_calculator_items(
            "avatar",
            lang=lang,
            is_all=include_traveler,
            sync=sync,
            uid=uid,
            query=query,
            filters=dict(
                element_attr_ids=elements or [],
                weapon_cat_ids=weapon_types or [],
            ),
        )
        return [models.CalculatorCharacter(**i) for i in data]

    async def get_calculator_weapons(
        self,
        *,
        query: typing.Optional[str] = None,
        types: typing.Optional[typing.Sequence[int]] = None,
        rarities: typing.Optional[typing.Sequence[int]] = None,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorWeapon]:
        """Get all weapons provided by the Enhancement Progression Calculator."""
        data = await self._get_calculator_items(
            "weapon",
            lang=lang,
            query=query,
            filters=dict(
                weapon_cat_ids=types or [],
                weapon_levels=rarities or [],
            ),
        )
        return [models.CalculatorWeapon(**i) for i in data]

    async def get_calculator_artifacts(
        self,
        *,
        query: typing.Optional[str] = None,
        pos: int = 1,
        rarities: typing.Optional[typing.Sequence[int]] = None,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorArtifact]:
        """Get all artifacts provided by the Enhancement Progression Calculator."""
        data = await self._get_calculator_items(
            "reliquary",
            lang=lang,
            query=query,
            filters=dict(
                reliquary_cat_id=pos,
                reliquary_levels=rarities or [],
            ),
        )
        return [models.CalculatorArtifact(**i) for i in data]

    async def get_calculator_furnishings(
        self,
        *,
        types: typing.Optional[int] = None,
        rarities: typing.Optional[int] = None,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorFurnishing]:
        """Get all furnishings provided by the Enhancement Progression Calculator."""
        data = await self._get_calculator_items(
            "furniture",
            lang=lang,
            filters=dict(
                cat_id=types or 0,
                weapon_levels=rarities or 0,
            ),
        )
        return [models.CalculatorFurnishing(**i) for i in data]

    async def get_character_details(
        self,
        character: types.IDOr[genshin_models.BaseCharacter],
        *,
        uid: typing.Optional[int] = None,
        lang: typing.Optional[str] = None,
    ) -> models.CalculatorCharacterDetails:
        """Get the weapon, artifacts and talents of a character.

        Not related to the Battle Chronicle.
        This data is always private.
        """
        uid = uid or await self._get_uid(types.Game.GENSHIN)

        data = await self.request_calculator(
            "sync/avatar/detail",
            method="GET",
            lang=lang,
            params=dict(
                avatar_id=int(character),
                uid=uid,
                region=utility.recognize_genshin_server(uid),
            ),
        )
        return models.CalculatorCharacterDetails(**data)

    async def get_character_talents(
        self,
        character: types.IDOr[genshin_models.BaseCharacter],
        *,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorTalent]:
        """Get the talents of a character.

        This only gets the talent names, not their levels.
        Use `get_character_details` for precise information.
        """
        data = await self.request_calculator(
            "avatar/skill_list",
            method="GET",
            lang=lang,
            params=dict(avatar_id=int(character)),
        )
        return [models.CalculatorTalent(**i) for i in data["list"]]

    async def get_complete_artifact_set(
        self,
        artifact: types.IDOr[typing.Union[genshin_models.Artifact, genshin_models.CalculatorArtifact]],
        *,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorArtifact]:
        """Get all other artifacts that share a set with any given artifact.

        Doesn't return the artifact passed into this function.
        """
        data = await self.request_calculator(
            "reliquary/set",
            method="GET",
            lang=lang,
            params=dict(reliquary_id=int(artifact)),
            cache=client_cache.cache_key("calculator", slug="set", artifact=int(artifact), lang=lang or self.lang),
        )
        return [models.CalculatorArtifact(**i) for i in data["reliquary_list"]]

    async def _get_all_artifact_ids(self, artifact_id: int) -> typing.Sequence[int]:
        """Get all artifact ids in the same set as a given artifact id."""
        others = await self.get_complete_artifact_set(artifact_id)
        return [artifact_id] + [other.id for other in others]

    async def get_teapot_replica_blueprint(
        self,
        share_code: int,
        *,
        region: typing.Optional[str] = None,
        uid: typing.Optional[int] = None,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorFurnishing]:
        """Get furnishings used by a teapot replica blueprint."""
        if not region:
            uid = uid or await self._get_uid(types.Game.GENSHIN)
            region = utility.recognize_genshin_server(uid)

        data = await self.request_calculator(
            "furniture/blueprint",
            method="GET",
            lang=lang,
            params=dict(share_code=share_code, region=region),
            cache=client_cache.cache_key("calculator", slug="blueprint", share_code=share_code, lang=lang or self.lang),
        )
        return [models.CalculatorFurnishing(**i) for i in data["list"]]

    @deprecation.deprecated("await genshin.utility.update_characters_any()")
    async def update_character_names(self, *, lang: typing.Optional[str] = None) -> None:
        """Update stored db characters with the names from the calculator."""
        characters = await self.get_calculator_characters(lang=lang, include_traveler=True)

        for char in characters:
            icon = genshin_models.character._parse_icon(char.icon)
            dbchar = genshin_models.DBChar(
                char.id, icon, char.name, "" if "Player" in icon else char.element, char.rarity
            )

            genshin_models.CHARACTER_NAMES[lang or self.lang][dbchar.id] = dbchar
var custom_headers : multidict._multidict.CIMultiDict[str]
Expand source code
class CalculatorClient(base.BaseClient):
    """Calculator component."""

    async def request_calculator(
        self,
        endpoint: str,
        *,
        method: str = "POST",
        lang: typing.Optional[str] = None,
        params: typing.Optional[typing.Mapping[str, typing.Any]] = None,
        data: typing.Optional[typing.Mapping[str, typing.Any]] = None,
        headers: typing.Optional[aiohttp.typedefs.LooseHeaders] = None,
        **kwargs: typing.Any,
    ) -> typing.Mapping[str, typing.Any]:
        """Make a request towards the calculator endpoint."""
        params = dict(params or {})
        headers = base.parse_loose_headers(headers)

        base_url = routes.CALCULATOR_URL.get_url(self.region)
        url = base_url / endpoint

        if method == "GET":
            params["lang"] = lang or self.lang
            data = None
        else:
            data = dict(data or {})
            data["lang"] = lang or self.lang

        if self.region == types.Region.CHINESE:
            headers["referer"] = str(routes.CALCULATOR_REFERER_URL.get_url())
        update_task = asyncio.create_task(utility.update_characters_any(lang or self.lang, lenient=True))

        data = await self.request(url, method=method, params=params, data=data, headers=headers, **kwargs)

        try:
            await update_task
        except Exception as e:
            warnings.warn(f"Failed to update characters: {e!r}")

        return data

    async def _execute_calculator(
        self,
        data: typing.Mapping[str, typing.Any],
        *,
        lang: typing.Optional[str] = None,
    ) -> models.CalculatorResult:
        """Calculate the results of a builder."""
        data = await self.request_calculator("compute", lang=lang, data=data)
        return models.CalculatorResult(**data)

    async def _execute_furnishings_calculator(
        self,
        data: typing.Mapping[str, typing.Any],
        *,
        lang: typing.Optional[str] = None,
    ) -> models.CalculatorFurnishingResults:
        """Calculate the results of a builder."""
        data = await self.request_calculator("furniture/compute", lang=lang, data=data)
        return models.CalculatorFurnishingResults(**data)

    def calculator(self, *, lang: typing.Optional[str] = None) -> Calculator:
        """Create a calculator builder object."""
        return Calculator(self, lang=lang)

    def furnishings_calculator(self, *, lang: typing.Optional[str] = None) -> FurnishingCalculator:
        """Create a calculator builder object."""
        return FurnishingCalculator(self, lang=lang)

    async def _enable_calculator_sync(self, enabled: bool = True) -> None:
        """Enable data syncing in calculator."""
        await self.request_calculator("avatar/auth", method="POST", data=dict(avatar_auth=int(enabled)))

    async def _get_calculator_items(
        self,
        slug: str,
        filters: typing.Mapping[str, typing.Any],
        query: typing.Optional[str] = None,
        *,
        uid: typing.Optional[int] = None,
        is_all: bool = False,
        sync: bool = False,
        lang: typing.Optional[str] = None,
        autoauth: bool = True,
    ) -> typing.Sequence[typing.Mapping[str, typing.Any]]:
        """Get all items of a specific slug from a calculator."""
        endpoint = f"sync/{slug}/list" if sync else f"{slug}/list"

        if query:
            if any(filters.values()):
                raise TypeError("Cannot specify a query and filter at the same time")

            filters = dict(keywords=query, **filters)

        payload: dict[str, typing.Any] = dict(page=1, size=69420, is_all=is_all, **filters)

        if sync:
            uid = uid or await self._get_uid(types.Game.GENSHIN)
            payload["uid"] = uid
            payload["region"] = utility.recognize_genshin_server(uid)

        cache: typing.Optional[client_cache.CacheKey] = None
        if not any(filters.values()) and not sync:
            cache = client_cache.cache_key("calculator", slug=slug, lang=lang or self.lang)

        try:
            data = await self.request_calculator(endpoint, lang=lang, data=payload, cache=cache)
        except errors.GenshinException as e:
            if e.retcode != -502002:  # Sync not enabled
                raise
            if not autoauth:
                raise errors.GenshinException(e.response, "Calculator sync is not enabled") from e

            await self._enable_calculator_sync()
            data = await self.request_calculator(endpoint, lang=lang, data=payload, cache=cache)

        return data["list"]

    async def get_calculator_characters(
        self,
        *,
        query: typing.Optional[str] = None,
        elements: typing.Optional[typing.Sequence[int]] = None,
        weapon_types: typing.Optional[typing.Sequence[int]] = None,
        include_traveler: bool = False,
        sync: bool = False,
        uid: typing.Optional[int] = None,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorCharacter]:
        """Get all characters provided by the Enhancement Progression Calculator."""
        data = await self._get_calculator_items(
            "avatar",
            lang=lang,
            is_all=include_traveler,
            sync=sync,
            uid=uid,
            query=query,
            filters=dict(
                element_attr_ids=elements or [],
                weapon_cat_ids=weapon_types or [],
            ),
        )
        return [models.CalculatorCharacter(**i) for i in data]

    async def get_calculator_weapons(
        self,
        *,
        query: typing.Optional[str] = None,
        types: typing.Optional[typing.Sequence[int]] = None,
        rarities: typing.Optional[typing.Sequence[int]] = None,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorWeapon]:
        """Get all weapons provided by the Enhancement Progression Calculator."""
        data = await self._get_calculator_items(
            "weapon",
            lang=lang,
            query=query,
            filters=dict(
                weapon_cat_ids=types or [],
                weapon_levels=rarities or [],
            ),
        )
        return [models.CalculatorWeapon(**i) for i in data]

    async def get_calculator_artifacts(
        self,
        *,
        query: typing.Optional[str] = None,
        pos: int = 1,
        rarities: typing.Optional[typing.Sequence[int]] = None,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorArtifact]:
        """Get all artifacts provided by the Enhancement Progression Calculator."""
        data = await self._get_calculator_items(
            "reliquary",
            lang=lang,
            query=query,
            filters=dict(
                reliquary_cat_id=pos,
                reliquary_levels=rarities or [],
            ),
        )
        return [models.CalculatorArtifact(**i) for i in data]

    async def get_calculator_furnishings(
        self,
        *,
        types: typing.Optional[int] = None,
        rarities: typing.Optional[int] = None,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorFurnishing]:
        """Get all furnishings provided by the Enhancement Progression Calculator."""
        data = await self._get_calculator_items(
            "furniture",
            lang=lang,
            filters=dict(
                cat_id=types or 0,
                weapon_levels=rarities or 0,
            ),
        )
        return [models.CalculatorFurnishing(**i) for i in data]

    async def get_character_details(
        self,
        character: types.IDOr[genshin_models.BaseCharacter],
        *,
        uid: typing.Optional[int] = None,
        lang: typing.Optional[str] = None,
    ) -> models.CalculatorCharacterDetails:
        """Get the weapon, artifacts and talents of a character.

        Not related to the Battle Chronicle.
        This data is always private.
        """
        uid = uid or await self._get_uid(types.Game.GENSHIN)

        data = await self.request_calculator(
            "sync/avatar/detail",
            method="GET",
            lang=lang,
            params=dict(
                avatar_id=int(character),
                uid=uid,
                region=utility.recognize_genshin_server(uid),
            ),
        )
        return models.CalculatorCharacterDetails(**data)

    async def get_character_talents(
        self,
        character: types.IDOr[genshin_models.BaseCharacter],
        *,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorTalent]:
        """Get the talents of a character.

        This only gets the talent names, not their levels.
        Use `get_character_details` for precise information.
        """
        data = await self.request_calculator(
            "avatar/skill_list",
            method="GET",
            lang=lang,
            params=dict(avatar_id=int(character)),
        )
        return [models.CalculatorTalent(**i) for i in data["list"]]

    async def get_complete_artifact_set(
        self,
        artifact: types.IDOr[typing.Union[genshin_models.Artifact, genshin_models.CalculatorArtifact]],
        *,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorArtifact]:
        """Get all other artifacts that share a set with any given artifact.

        Doesn't return the artifact passed into this function.
        """
        data = await self.request_calculator(
            "reliquary/set",
            method="GET",
            lang=lang,
            params=dict(reliquary_id=int(artifact)),
            cache=client_cache.cache_key("calculator", slug="set", artifact=int(artifact), lang=lang or self.lang),
        )
        return [models.CalculatorArtifact(**i) for i in data["reliquary_list"]]

    async def _get_all_artifact_ids(self, artifact_id: int) -> typing.Sequence[int]:
        """Get all artifact ids in the same set as a given artifact id."""
        others = await self.get_complete_artifact_set(artifact_id)
        return [artifact_id] + [other.id for other in others]

    async def get_teapot_replica_blueprint(
        self,
        share_code: int,
        *,
        region: typing.Optional[str] = None,
        uid: typing.Optional[int] = None,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorFurnishing]:
        """Get furnishings used by a teapot replica blueprint."""
        if not region:
            uid = uid or await self._get_uid(types.Game.GENSHIN)
            region = utility.recognize_genshin_server(uid)

        data = await self.request_calculator(
            "furniture/blueprint",
            method="GET",
            lang=lang,
            params=dict(share_code=share_code, region=region),
            cache=client_cache.cache_key("calculator", slug="blueprint", share_code=share_code, lang=lang or self.lang),
        )
        return [models.CalculatorFurnishing(**i) for i in data["list"]]

    @deprecation.deprecated("await genshin.utility.update_characters_any()")
    async def update_character_names(self, *, lang: typing.Optional[str] = None) -> None:
        """Update stored db characters with the names from the calculator."""
        characters = await self.get_calculator_characters(lang=lang, include_traveler=True)

        for char in characters:
            icon = genshin_models.character._parse_icon(char.icon)
            dbchar = genshin_models.DBChar(
                char.id, icon, char.name, "" if "Player" in icon else char.element, char.rarity
            )

            genshin_models.CHARACTER_NAMES[lang or self.lang][dbchar.id] = dbchar
var uids : dict[Game, int]
Expand source code
class CalculatorClient(base.BaseClient):
    """Calculator component."""

    async def request_calculator(
        self,
        endpoint: str,
        *,
        method: str = "POST",
        lang: typing.Optional[str] = None,
        params: typing.Optional[typing.Mapping[str, typing.Any]] = None,
        data: typing.Optional[typing.Mapping[str, typing.Any]] = None,
        headers: typing.Optional[aiohttp.typedefs.LooseHeaders] = None,
        **kwargs: typing.Any,
    ) -> typing.Mapping[str, typing.Any]:
        """Make a request towards the calculator endpoint."""
        params = dict(params or {})
        headers = base.parse_loose_headers(headers)

        base_url = routes.CALCULATOR_URL.get_url(self.region)
        url = base_url / endpoint

        if method == "GET":
            params["lang"] = lang or self.lang
            data = None
        else:
            data = dict(data or {})
            data["lang"] = lang or self.lang

        if self.region == types.Region.CHINESE:
            headers["referer"] = str(routes.CALCULATOR_REFERER_URL.get_url())
        update_task = asyncio.create_task(utility.update_characters_any(lang or self.lang, lenient=True))

        data = await self.request(url, method=method, params=params, data=data, headers=headers, **kwargs)

        try:
            await update_task
        except Exception as e:
            warnings.warn(f"Failed to update characters: {e!r}")

        return data

    async def _execute_calculator(
        self,
        data: typing.Mapping[str, typing.Any],
        *,
        lang: typing.Optional[str] = None,
    ) -> models.CalculatorResult:
        """Calculate the results of a builder."""
        data = await self.request_calculator("compute", lang=lang, data=data)
        return models.CalculatorResult(**data)

    async def _execute_furnishings_calculator(
        self,
        data: typing.Mapping[str, typing.Any],
        *,
        lang: typing.Optional[str] = None,
    ) -> models.CalculatorFurnishingResults:
        """Calculate the results of a builder."""
        data = await self.request_calculator("furniture/compute", lang=lang, data=data)
        return models.CalculatorFurnishingResults(**data)

    def calculator(self, *, lang: typing.Optional[str] = None) -> Calculator:
        """Create a calculator builder object."""
        return Calculator(self, lang=lang)

    def furnishings_calculator(self, *, lang: typing.Optional[str] = None) -> FurnishingCalculator:
        """Create a calculator builder object."""
        return FurnishingCalculator(self, lang=lang)

    async def _enable_calculator_sync(self, enabled: bool = True) -> None:
        """Enable data syncing in calculator."""
        await self.request_calculator("avatar/auth", method="POST", data=dict(avatar_auth=int(enabled)))

    async def _get_calculator_items(
        self,
        slug: str,
        filters: typing.Mapping[str, typing.Any],
        query: typing.Optional[str] = None,
        *,
        uid: typing.Optional[int] = None,
        is_all: bool = False,
        sync: bool = False,
        lang: typing.Optional[str] = None,
        autoauth: bool = True,
    ) -> typing.Sequence[typing.Mapping[str, typing.Any]]:
        """Get all items of a specific slug from a calculator."""
        endpoint = f"sync/{slug}/list" if sync else f"{slug}/list"

        if query:
            if any(filters.values()):
                raise TypeError("Cannot specify a query and filter at the same time")

            filters = dict(keywords=query, **filters)

        payload: dict[str, typing.Any] = dict(page=1, size=69420, is_all=is_all, **filters)

        if sync:
            uid = uid or await self._get_uid(types.Game.GENSHIN)
            payload["uid"] = uid
            payload["region"] = utility.recognize_genshin_server(uid)

        cache: typing.Optional[client_cache.CacheKey] = None
        if not any(filters.values()) and not sync:
            cache = client_cache.cache_key("calculator", slug=slug, lang=lang or self.lang)

        try:
            data = await self.request_calculator(endpoint, lang=lang, data=payload, cache=cache)
        except errors.GenshinException as e:
            if e.retcode != -502002:  # Sync not enabled
                raise
            if not autoauth:
                raise errors.GenshinException(e.response, "Calculator sync is not enabled") from e

            await self._enable_calculator_sync()
            data = await self.request_calculator(endpoint, lang=lang, data=payload, cache=cache)

        return data["list"]

    async def get_calculator_characters(
        self,
        *,
        query: typing.Optional[str] = None,
        elements: typing.Optional[typing.Sequence[int]] = None,
        weapon_types: typing.Optional[typing.Sequence[int]] = None,
        include_traveler: bool = False,
        sync: bool = False,
        uid: typing.Optional[int] = None,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorCharacter]:
        """Get all characters provided by the Enhancement Progression Calculator."""
        data = await self._get_calculator_items(
            "avatar",
            lang=lang,
            is_all=include_traveler,
            sync=sync,
            uid=uid,
            query=query,
            filters=dict(
                element_attr_ids=elements or [],
                weapon_cat_ids=weapon_types or [],
            ),
        )
        return [models.CalculatorCharacter(**i) for i in data]

    async def get_calculator_weapons(
        self,
        *,
        query: typing.Optional[str] = None,
        types: typing.Optional[typing.Sequence[int]] = None,
        rarities: typing.Optional[typing.Sequence[int]] = None,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorWeapon]:
        """Get all weapons provided by the Enhancement Progression Calculator."""
        data = await self._get_calculator_items(
            "weapon",
            lang=lang,
            query=query,
            filters=dict(
                weapon_cat_ids=types or [],
                weapon_levels=rarities or [],
            ),
        )
        return [models.CalculatorWeapon(**i) for i in data]

    async def get_calculator_artifacts(
        self,
        *,
        query: typing.Optional[str] = None,
        pos: int = 1,
        rarities: typing.Optional[typing.Sequence[int]] = None,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorArtifact]:
        """Get all artifacts provided by the Enhancement Progression Calculator."""
        data = await self._get_calculator_items(
            "reliquary",
            lang=lang,
            query=query,
            filters=dict(
                reliquary_cat_id=pos,
                reliquary_levels=rarities or [],
            ),
        )
        return [models.CalculatorArtifact(**i) for i in data]

    async def get_calculator_furnishings(
        self,
        *,
        types: typing.Optional[int] = None,
        rarities: typing.Optional[int] = None,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorFurnishing]:
        """Get all furnishings provided by the Enhancement Progression Calculator."""
        data = await self._get_calculator_items(
            "furniture",
            lang=lang,
            filters=dict(
                cat_id=types or 0,
                weapon_levels=rarities or 0,
            ),
        )
        return [models.CalculatorFurnishing(**i) for i in data]

    async def get_character_details(
        self,
        character: types.IDOr[genshin_models.BaseCharacter],
        *,
        uid: typing.Optional[int] = None,
        lang: typing.Optional[str] = None,
    ) -> models.CalculatorCharacterDetails:
        """Get the weapon, artifacts and talents of a character.

        Not related to the Battle Chronicle.
        This data is always private.
        """
        uid = uid or await self._get_uid(types.Game.GENSHIN)

        data = await self.request_calculator(
            "sync/avatar/detail",
            method="GET",
            lang=lang,
            params=dict(
                avatar_id=int(character),
                uid=uid,
                region=utility.recognize_genshin_server(uid),
            ),
        )
        return models.CalculatorCharacterDetails(**data)

    async def get_character_talents(
        self,
        character: types.IDOr[genshin_models.BaseCharacter],
        *,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorTalent]:
        """Get the talents of a character.

        This only gets the talent names, not their levels.
        Use `get_character_details` for precise information.
        """
        data = await self.request_calculator(
            "avatar/skill_list",
            method="GET",
            lang=lang,
            params=dict(avatar_id=int(character)),
        )
        return [models.CalculatorTalent(**i) for i in data["list"]]

    async def get_complete_artifact_set(
        self,
        artifact: types.IDOr[typing.Union[genshin_models.Artifact, genshin_models.CalculatorArtifact]],
        *,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorArtifact]:
        """Get all other artifacts that share a set with any given artifact.

        Doesn't return the artifact passed into this function.
        """
        data = await self.request_calculator(
            "reliquary/set",
            method="GET",
            lang=lang,
            params=dict(reliquary_id=int(artifact)),
            cache=client_cache.cache_key("calculator", slug="set", artifact=int(artifact), lang=lang or self.lang),
        )
        return [models.CalculatorArtifact(**i) for i in data["reliquary_list"]]

    async def _get_all_artifact_ids(self, artifact_id: int) -> typing.Sequence[int]:
        """Get all artifact ids in the same set as a given artifact id."""
        others = await self.get_complete_artifact_set(artifact_id)
        return [artifact_id] + [other.id for other in others]

    async def get_teapot_replica_blueprint(
        self,
        share_code: int,
        *,
        region: typing.Optional[str] = None,
        uid: typing.Optional[int] = None,
        lang: typing.Optional[str] = None,
    ) -> typing.Sequence[models.CalculatorFurnishing]:
        """Get furnishings used by a teapot replica blueprint."""
        if not region:
            uid = uid or await self._get_uid(types.Game.GENSHIN)
            region = utility.recognize_genshin_server(uid)

        data = await self.request_calculator(
            "furniture/blueprint",
            method="GET",
            lang=lang,
            params=dict(share_code=share_code, region=region),
            cache=client_cache.cache_key("calculator", slug="blueprint", share_code=share_code, lang=lang or self.lang),
        )
        return [models.CalculatorFurnishing(**i) for i in data["list"]]

    @deprecation.deprecated("await genshin.utility.update_characters_any()")
    async def update_character_names(self, *, lang: typing.Optional[str] = None) -> None:
        """Update stored db characters with the names from the calculator."""
        characters = await self.get_calculator_characters(lang=lang, include_traveler=True)

        for char in characters:
            icon = genshin_models.character._parse_icon(char.icon)
            dbchar = genshin_models.DBChar(
                char.id, icon, char.name, "" if "Player" in icon else char.element, char.rarity
            )

            genshin_models.CHARACTER_NAMES[lang or self.lang][dbchar.id] = dbchar

Methods

def calculator(self, *, lang: typing.Optional[str] = None) ‑> Calculator
Expand source code
def calculator(self, *, lang: typing.Optional[str] = None) -> Calculator:
    """Create a calculator builder object."""
    return Calculator(self, lang=lang)

Create a calculator builder object.

def furnishings_calculator(self, *, lang: typing.Optional[str] = None) ‑> FurnishingCalculator
Expand source code
def furnishings_calculator(self, *, lang: typing.Optional[str] = None) -> FurnishingCalculator:
    """Create a calculator builder object."""
    return FurnishingCalculator(self, lang=lang)

Create a calculator builder object.

async def get_calculator_artifacts(self,
*,
query: typing.Optional[str] = None,
pos: int = 1,
rarities: typing.Optional[typing.Sequence[int]] = None,
lang: typing.Optional[str] = None) ‑> Sequence[CalculatorArtifact]
Expand source code
async def get_calculator_artifacts(
    self,
    *,
    query: typing.Optional[str] = None,
    pos: int = 1,
    rarities: typing.Optional[typing.Sequence[int]] = None,
    lang: typing.Optional[str] = None,
) -> typing.Sequence[models.CalculatorArtifact]:
    """Get all artifacts provided by the Enhancement Progression Calculator."""
    data = await self._get_calculator_items(
        "reliquary",
        lang=lang,
        query=query,
        filters=dict(
            reliquary_cat_id=pos,
            reliquary_levels=rarities or [],
        ),
    )
    return [models.CalculatorArtifact(**i) for i in data]

Get all artifacts provided by the Enhancement Progression Calculator.

async def get_calculator_characters(self,
*,
query: typing.Optional[str] = None,
elements: typing.Optional[typing.Sequence[int]] = None,
weapon_types: typing.Optional[typing.Sequence[int]] = None,
include_traveler: bool = False,
sync: bool = False,
uid: typing.Optional[int] = None,
lang: typing.Optional[str] = None) ‑> Sequence[CalculatorCharacter]
Expand source code
async def get_calculator_characters(
    self,
    *,
    query: typing.Optional[str] = None,
    elements: typing.Optional[typing.Sequence[int]] = None,
    weapon_types: typing.Optional[typing.Sequence[int]] = None,
    include_traveler: bool = False,
    sync: bool = False,
    uid: typing.Optional[int] = None,
    lang: typing.Optional[str] = None,
) -> typing.Sequence[models.CalculatorCharacter]:
    """Get all characters provided by the Enhancement Progression Calculator."""
    data = await self._get_calculator_items(
        "avatar",
        lang=lang,
        is_all=include_traveler,
        sync=sync,
        uid=uid,
        query=query,
        filters=dict(
            element_attr_ids=elements or [],
            weapon_cat_ids=weapon_types or [],
        ),
    )
    return [models.CalculatorCharacter(**i) for i in data]

Get all characters provided by the Enhancement Progression Calculator.

async def get_calculator_furnishings(self,
*,
types: typing.Optional[int] = None,
rarities: typing.Optional[int] = None,
lang: typing.Optional[str] = None) ‑> Sequence[CalculatorFurnishing]
Expand source code
async def get_calculator_furnishings(
    self,
    *,
    types: typing.Optional[int] = None,
    rarities: typing.Optional[int] = None,
    lang: typing.Optional[str] = None,
) -> typing.Sequence[models.CalculatorFurnishing]:
    """Get all furnishings provided by the Enhancement Progression Calculator."""
    data = await self._get_calculator_items(
        "furniture",
        lang=lang,
        filters=dict(
            cat_id=types or 0,
            weapon_levels=rarities or 0,
        ),
    )
    return [models.CalculatorFurnishing(**i) for i in data]

Get all furnishings provided by the Enhancement Progression Calculator.

async def get_calculator_weapons(self,
*,
query: typing.Optional[str] = None,
types: typing.Optional[typing.Sequence[int]] = None,
rarities: typing.Optional[typing.Sequence[int]] = None,
lang: typing.Optional[str] = None) ‑> Sequence[CalculatorWeapon]
Expand source code
async def get_calculator_weapons(
    self,
    *,
    query: typing.Optional[str] = None,
    types: typing.Optional[typing.Sequence[int]] = None,
    rarities: typing.Optional[typing.Sequence[int]] = None,
    lang: typing.Optional[str] = None,
) -> typing.Sequence[models.CalculatorWeapon]:
    """Get all weapons provided by the Enhancement Progression Calculator."""
    data = await self._get_calculator_items(
        "weapon",
        lang=lang,
        query=query,
        filters=dict(
            weapon_cat_ids=types or [],
            weapon_levels=rarities or [],
        ),
    )
    return [models.CalculatorWeapon(**i) for i in data]

Get all weapons provided by the Enhancement Progression Calculator.

async def get_character_details(self,
character: types.IDOr[genshin_models.BaseCharacter],
*,
uid: typing.Optional[int] = None,
lang: typing.Optional[str] = None) ‑> CalculatorCharacterDetails
Expand source code
async def get_character_details(
    self,
    character: types.IDOr[genshin_models.BaseCharacter],
    *,
    uid: typing.Optional[int] = None,
    lang: typing.Optional[str] = None,
) -> models.CalculatorCharacterDetails:
    """Get the weapon, artifacts and talents of a character.

    Not related to the Battle Chronicle.
    This data is always private.
    """
    uid = uid or await self._get_uid(types.Game.GENSHIN)

    data = await self.request_calculator(
        "sync/avatar/detail",
        method="GET",
        lang=lang,
        params=dict(
            avatar_id=int(character),
            uid=uid,
            region=utility.recognize_genshin_server(uid),
        ),
    )
    return models.CalculatorCharacterDetails(**data)

Get the weapon, artifacts and talents of a character.

Not related to the Battle Chronicle. This data is always private.

async def get_character_talents(self,
character: types.IDOr[genshin_models.BaseCharacter],
*,
lang: typing.Optional[str] = None) ‑> Sequence[CalculatorTalent]
Expand source code
async def get_character_talents(
    self,
    character: types.IDOr[genshin_models.BaseCharacter],
    *,
    lang: typing.Optional[str] = None,
) -> typing.Sequence[models.CalculatorTalent]:
    """Get the talents of a character.

    This only gets the talent names, not their levels.
    Use `get_character_details` for precise information.
    """
    data = await self.request_calculator(
        "avatar/skill_list",
        method="GET",
        lang=lang,
        params=dict(avatar_id=int(character)),
    )
    return [models.CalculatorTalent(**i) for i in data["list"]]

Get the talents of a character.

This only gets the talent names, not their levels. Use get_character_details for precise information.

async def get_complete_artifact_set(self,
artifact: types.IDOr[typing.Union[genshin_models.Artifact, genshin_models.CalculatorArtifact]],
*,
lang: typing.Optional[str] = None) ‑> Sequence[CalculatorArtifact]
Expand source code
async def get_complete_artifact_set(
    self,
    artifact: types.IDOr[typing.Union[genshin_models.Artifact, genshin_models.CalculatorArtifact]],
    *,
    lang: typing.Optional[str] = None,
) -> typing.Sequence[models.CalculatorArtifact]:
    """Get all other artifacts that share a set with any given artifact.

    Doesn't return the artifact passed into this function.
    """
    data = await self.request_calculator(
        "reliquary/set",
        method="GET",
        lang=lang,
        params=dict(reliquary_id=int(artifact)),
        cache=client_cache.cache_key("calculator", slug="set", artifact=int(artifact), lang=lang or self.lang),
    )
    return [models.CalculatorArtifact(**i) for i in data["reliquary_list"]]

Get all other artifacts that share a set with any given artifact.

Doesn't return the artifact passed into this function.

async def get_teapot_replica_blueprint(self,
share_code: int,
*,
region: typing.Optional[str] = None,
uid: typing.Optional[int] = None,
lang: typing.Optional[str] = None) ‑> Sequence[CalculatorFurnishing]
Expand source code
async def get_teapot_replica_blueprint(
    self,
    share_code: int,
    *,
    region: typing.Optional[str] = None,
    uid: typing.Optional[int] = None,
    lang: typing.Optional[str] = None,
) -> typing.Sequence[models.CalculatorFurnishing]:
    """Get furnishings used by a teapot replica blueprint."""
    if not region:
        uid = uid or await self._get_uid(types.Game.GENSHIN)
        region = utility.recognize_genshin_server(uid)

    data = await self.request_calculator(
        "furniture/blueprint",
        method="GET",
        lang=lang,
        params=dict(share_code=share_code, region=region),
        cache=client_cache.cache_key("calculator", slug="blueprint", share_code=share_code, lang=lang or self.lang),
    )
    return [models.CalculatorFurnishing(**i) for i in data["list"]]

Get furnishings used by a teapot replica blueprint.

async def request_calculator(self,
endpoint: str,
*,
method: str = 'POST',
lang: typing.Optional[str] = None,
params: typing.Optional[typing.Mapping[str, typing.Any]] = None,
data: typing.Optional[typing.Mapping[str, typing.Any]] = None,
headers: typing.Optional[aiohttp.typedefs.LooseHeaders] = None,
**kwargs: typing.Any) ‑> Mapping[str, Any]
Expand source code
async def request_calculator(
    self,
    endpoint: str,
    *,
    method: str = "POST",
    lang: typing.Optional[str] = None,
    params: typing.Optional[typing.Mapping[str, typing.Any]] = None,
    data: typing.Optional[typing.Mapping[str, typing.Any]] = None,
    headers: typing.Optional[aiohttp.typedefs.LooseHeaders] = None,
    **kwargs: typing.Any,
) -> typing.Mapping[str, typing.Any]:
    """Make a request towards the calculator endpoint."""
    params = dict(params or {})
    headers = base.parse_loose_headers(headers)

    base_url = routes.CALCULATOR_URL.get_url(self.region)
    url = base_url / endpoint

    if method == "GET":
        params["lang"] = lang or self.lang
        data = None
    else:
        data = dict(data or {})
        data["lang"] = lang or self.lang

    if self.region == types.Region.CHINESE:
        headers["referer"] = str(routes.CALCULATOR_REFERER_URL.get_url())
    update_task = asyncio.create_task(utility.update_characters_any(lang or self.lang, lenient=True))

    data = await self.request(url, method=method, params=params, data=data, headers=headers, **kwargs)

    try:
        await update_task
    except Exception as e:
        warnings.warn(f"Failed to update characters: {e!r}")

    return data

Make a request towards the calculator endpoint.

async def update_character_names(self, *, lang: typing.Optional[str] = None) ‑> None
Expand source code
@deprecation.deprecated("await genshin.utility.update_characters_any()")
async def update_character_names(self, *, lang: typing.Optional[str] = None) -> None:
    """Update stored db characters with the names from the calculator."""
    characters = await self.get_calculator_characters(lang=lang, include_traveler=True)

    for char in characters:
        icon = genshin_models.character._parse_icon(char.icon)
        dbchar = genshin_models.DBChar(
            char.id, icon, char.name, "" if "Player" in icon else char.element, char.rarity
        )

        genshin_models.CHARACTER_NAMES[lang or self.lang][dbchar.id] = dbchar

Update stored db characters with the names from the calculator.

Warning

This function is deprecated and will be removed in the following version. You can use await genshin.utility.update_characters_any() instead.

Inherited members