Source code for csbluegem.types

"""
Copyright 2024-present fretgfr

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.
"""

from __future__ import annotations

import datetime
from dataclasses import dataclass
from enum import Enum
from typing import TYPE_CHECKING, List, Optional, TypedDict

from .utils import parse_epoch, utcnow

if TYPE_CHECKING:
    from typing_extensions import NotRequired

__all__ = (
    "Screenshots",
    "PatternDataScreenshots",
    "PatternDataExtra",
    "PatternData",
    "SearchMeta",
    "Sale",
    "SearchResponse",
    "Origin",
    "FilterType",
    "Filter",
    "Order",
    "ItemType",
    "Currency",
    "SortKey",
    "Item",
)


class _APISearchMetaDict(TypedDict):
    size: int
    total: int


class _APIPatternDataDict(TypedDict):
    backside_blue: float
    backside_contour_blue: int
    backside_contour_purple: int
    backside_gold: float
    backside_purple: float
    playside_blue: float
    playside_contour_blue: int
    playside_contour_purple: float
    playside_gold: float
    playside_purple: float
    pattern: NotRequired[int]
    quantity: NotRequired[int]
    screenshots: NotRequired[_APIPatternDataScreenshots]
    extra: NotRequired[_APIPatternDataExtras]


class _APISearchScreenshotsDict(TypedDict):
    inspect: Optional[str]
    inspect_playside: Optional[str]
    inspect_backside: Optional[str]


class _APISearchSaleDict(TypedDict):
    sale_id: str
    origin: Origin
    buff_id: int
    date: str
    pattern: int
    wear: float
    price: float
    epoch: int
    steam_inspect_link: str
    type: str
    screenshots: _APISearchScreenshotsDict
    pattern_data: NotRequired[_APIPatternDataDict]
    csfloat: str


class _APIPatternDataScreenshots(TypedDict):
    csbluegem_screenshot: str
    aq_oiled: str


class _APIPatternDataExtras(TypedDict):
    similar_playside: str
    similar_backside: str
    csfloat_link: str
    search: str


class _APISearchResponseDict(TypedDict):
    meta: _APISearchMetaDict
    sales: List[_APISearchSaleDict]


class _APIPatternDataResponseDict(TypedDict):
    meta: _APISearchMetaDict
    data: List[_APIPatternDataDict]


[docs] @dataclass class Screenshots: """Screenshots for a :class:`~csbluegem.types.Sale`. Attributes ---------- inspect: Optional[:class:`str`] A url to an inspect link. Always returns a url for an inspect link. This will be None if no inspect links are available. For CSFloat based sales, this returns the playside inspect link. inspect_playside: Optional[:class:`str`] A url to the playside inspect link. Only applicable for CSFloat sales. inspect_backside: Optional[:class:`str`] A url to the backside inspect link. Only applicable for CSFloat sales. """ __slots__ = ("_inspect", "inspect_playside", "inspect_backside") _inspect: Optional[str] inspect_playside: Optional[str] inspect_backside: Optional[str] @classmethod def _from_data(cls, data: _APISearchScreenshotsDict, /): inspect = data["inspect"] inspect_playside = data["inspect_playside"] inspect_backside = data["inspect_backside"] return cls(inspect, inspect_playside, inspect_backside) @property def inspect(self) -> Optional[str]: """Returns an inspect link no matter where the underlying :class:`~csbluegem.types.Sale` originated. For CSFloat based Sales, this will return the playside inspect link. If no screenshots are available, this property will return None. """ if self._inspect: return self._inspect if self.inspect_playside: return self.inspect_playside return None
[docs] @dataclass class PatternDataScreenshots: """Screenshots that may be associated with this :class:`~csbluegem.types.PatternData`. Only available when using :meth:`~csbluegem.client.Client.pattern_data`. Attributes ---------- csbluegem_screenshot: :class:`str` A screenshot provided by CSBlueGem aq_oiled: :class:`str` A screenshot provided from another source. """ __slots__ = ("csbluegem_screenshot", "aq_oiled") csbluegem_screenshot: str aq_oiled: str @classmethod def _from_data(cls, data: _APIPatternDataScreenshots, /): return cls(**data)
[docs] @dataclass class PatternDataExtra: """Extra information provided for pattern data. Only available when using :meth:`~csbluegem.client.Client.pattern_data`. Attributes ---------- similar_playside: :class:`str` A URL to an image of an item with a similar playside. similar_backside: :class:`str` A URL to an image of an item with a similar backside. csfloat_link: :class:`str` The CSFloat database query for this pattern. search: :class:`str` A URL to a search for this pattern. """ __slots__ = ("similar_playside", "similar_backside", "csfloat_link", "search") similar_playside: str similar_backside: str csfloat_link: str search: str @classmethod def _from_data(cls, data: _APIPatternDataExtras, /): return cls(**data)
[docs] @dataclass class PatternData: """Data for a pattern on CSBlueGem. Attributes ---------- backside_blue: :class:`float` The percentage of blue visible on the back side. backside_contour_blue: :class:`int` The number of individual blue sections visible on the back side. backside_contour_purple: :class:`int` The number of individual purple sections visible on the back side. backside_gold: :class:`float` The percentage of gold visible on the back side. backside_purple: :class:`float` The percentage of purple visible on the back side. playside_blue: :class:`float` The percentage of blue visible on the back side. playside_contour_blue: :class:`int` The number of individual blue sections visible on the play side. playside_contour_purple: :class:`int` The number of individual purple sections visible on the play side. playside_gold: :class:`float` The percentage of gold visible on the play side. playside_purple: :class:`float` The percentage of purple visible on the play side. pattern: Optional[:class:`int`] The pattern represented by this PatternData. quantity: Optional[:class:`int`] The number of sales attributed to this PatternData. screenshots: Optional[:class:`~csbluegem.types.PatternDataScreenshots`] Example screenshots of the pattern associated with this PatternData on in game items. extra: Optional[:class:`~csbluegem.types.PatternDataExtra`] Extra information about this PatternData. """ __slots__ = ( "backside_blue", "backside_contour_blue", "backside_contour_purple", "backside_gold", "backside_purple", "playside_blue", "playside_contour_blue", "playside_contour_purple", "playside_gold", "playside_purple", "pattern", "quantity", "screenshots", "extra", ) backside_blue: float backside_contour_blue: int backside_contour_purple: int backside_gold: float backside_purple: float playside_blue: float playside_contour_blue: int playside_contour_purple: float playside_gold: float playside_purple: float pattern: Optional[int] quantity: Optional[int] screenshots: Optional[PatternDataScreenshots] extra: Optional[PatternDataExtra] @classmethod def _from_data(cls, data: _APIPatternDataDict, /): backside_blue = data["backside_blue"] backside_contour_blue = data["backside_contour_blue"] backside_contour_purple = data["backside_contour_purple"] backside_gold = data["backside_gold"] backside_purple = data["backside_purple"] playside_blue = data["playside_blue"] playside_contour_blue = data["playside_contour_blue"] playside_contour_purple = data["playside_contour_purple"] playside_gold = data["playside_gold"] playside_purple = data["playside_purple"] pattern = data.get("pattern") quantity = data.get("quantity") screenshots_dict = data.get("screenshots") extra_dict = data.get("extra") screenshots = PatternDataScreenshots._from_data(screenshots_dict) if screenshots_dict is not None else None extra = PatternDataExtra._from_data(extra_dict) if extra_dict is not None else None return cls( backside_blue, backside_contour_blue, backside_contour_purple, backside_gold, backside_purple, playside_blue, playside_contour_blue, playside_contour_purple, playside_gold, playside_purple, pattern, quantity, screenshots, extra, )
[docs] @dataclass class SearchMeta: """Metadata about the search. Attributes ---------- size: :class:`int` The number of items returned. total: :class:`int` The total number of items available. """ __slots__ = ("size", "total") size: int total: int @classmethod def _from_data(cls, data: _APISearchMetaDict, /): size = data["size"] total = data["total"] return cls(size, total)
[docs] @dataclass class Sale: """Represents a record of sale from CSBlueGem Attributes ---------- buff_id: :class:`int` The id of the item on Buff. csfloat: :class:`str` A link to the item on CSFloat. wear: :class:`float` The float of the item. type: :class:`~csbluegem.types.ItemType` The type of the item. pattern: :class:`int` The pattern of the item. timestamp: :class:`datetime.datetime` When the sale occurred. steam_inspect_link: :class:`str` The inspect link for this item. origin: :class:`~csbluegem.types.Origin` Where the sale data originated. pattern_data: Optional[:class:`~csbluegem.types.PatternData`] The pattern data for the item, if available. screenshots: :class:`~csbluegem.types.Screenshots` Screenshot data for the item. """ __slots__ = ( "buff_id", "csfloat", "wear", "type", "pattern", "price", "timestamp", "steam_inspect_link", "origin", "pattern_data", "screenshots", ) buff_id: int csfloat: str wear: float type: ItemType pattern: int price: float timestamp: datetime.datetime steam_inspect_link: str origin: Origin pattern_data: Optional[PatternData] screenshots: Screenshots @classmethod def _from_data(cls, data: _APISearchSaleDict): buff_id = data["buff_id"] csfloat = data["csfloat"] wear = data["wear"] type = ItemType(data["type"]) api_pattern = data["pattern"] price = data["price"] timestamp = parse_epoch(data["epoch"]) steam_inspect_link = data["steam_inspect_link"] origin = Origin(data["origin"]) raw_pattern_data: Optional[_APIPatternDataDict] = data.get("pattern_data") pattern_data = PatternData._from_data(raw_pattern_data) if raw_pattern_data is not None else None raw_screenshots_data: _APISearchScreenshotsDict = data["screenshots"] screenshots = Screenshots._from_data(raw_screenshots_data) return cls( buff_id, csfloat, wear, type, api_pattern, price, timestamp, steam_inspect_link, origin, pattern_data, screenshots, ) @property def float(self): """The float of the item.""" return self.wear @property def date(self) -> datetime.date: """The date this item was sold.""" return self.timestamp.date() @property def epoch(self) -> float: """The epoch the item was sold.""" return self.timestamp.timestamp() @property def days_since(self) -> int: """Returns the number of days since this Sale.""" return (utcnow() - self.timestamp).days @property def is_stattrak(self) -> bool: """Whether the item was stattrak""" return self.type is ItemType.StatTrak
[docs] @dataclass class SearchResponse: """Represents a response to a search query. Attributes ---------- meta: :class:`~csbluegem.types.SearchMeta` Metadata about the query. sales: List[:class:`~csbluegem.types.Sale`] The sales that were returned. """ __slots__ = ("meta", "sales") meta: SearchMeta sales: List[Sale] @classmethod def _from_data(cls, data: _APISearchResponseDict): meta = SearchMeta._from_data(data["meta"]) sales = [Sale._from_data(d) for d in data["sales"]] return cls(meta, sales)
[docs] @dataclass class PatternDataResponse: """Represents a response to a pattern data query. Attributes ---------- meta: :class:`~csbluegem.types.SearchMeta` Metadata about the query. data: List[:class:`~csbluegem.types.PatternData`] The pattern datas that were returned. """ __slots__ = ("meta", "pattern_data") meta: SearchMeta pattern_data: List[PatternData] @classmethod def _from_data(cls, api_data: _APIPatternDataResponseDict): meta = SearchMeta._from_data(api_data["meta"]) pattern_data = [PatternData._from_data(d) for d in api_data["data"]] return cls(meta, pattern_data)
[docs] class Origin(Enum): """Where a :class:`~csbluegem.types.Sale` originated from.""" # fmt: off Buff = "Buff" CSFloat = "CSFloat" SkinBid = "SkinBid" BroSkins = "BroSkins" Skinport = "Skinport" C5Game = "c5game" # fmt: on def __str__(self) -> str: """Returns the value of the member.""" return self.value
[docs] class FilterType(Enum): """What a :class:`~csbluegem.types.Filter` should filter by.""" # fmt: off PlaysideBlue = "playside_blue" PlaysidePurple = "playside_purple" PlaysideGold = "playside_gold" BacksideBlue = "backside_blue" BacksidePurple = "backside_purple" BacksideGold = "backside_gold"
# fmt: on
[docs] class Filter: """A filter for searches. Attributes ---------- type: :class:`~csbluegem.types.FilterType` What kind of filter. min: :class:`float` The minimum value for this filter. max: :class:`float` The maximum value for this filter. """ __slots__ = ("type", "min", "max") def __init__(self, type: FilterType, min: float, max: float): self.type = type self.min = min self.max = max
[docs] def is_valid(self) -> bool: """Whether or not this Filter is valid. Returns ------- :class:`bool` Filter validity. """ return (0 <= self.min < 100 and 0 < self.max <= 100) and self.max > self.min
def __str__(self) -> str: return f"{self.type.value} {self.min}-{self.max}" def __repr__(self) -> str: return f"<{self.__class__.__name__} type={self.type} min={self.min} max={self.max}>"
[docs] class Order(Enum): """How query results should be ordered.""" # fmt: off Asc = "ASC" Desc = "DESC"
# fmt: on
[docs] class ItemType(Enum): """The type of an item.""" # fmt: off StatTrak = "stattrak" Normal = "normal"
# fmt: on
[docs] class Currency(Enum): """Available currencies for use in the API.""" USD = "USD" EUR = "EUR" JPY = "JPY" GBP = "GBP" CNY = "CNY" AUD = "AUD" CAD = "CAD"
[docs] class SortKey(Enum): """How the results of a query should be sorted.""" # fmt: off PlaysideBlue = "playside_blue" PlaysidePurple = "playside_purple" PlaysideGold = "playside_gold" BacksideBlue = "backside_blue" BacksidePurple = "backside_purple" BacksideGold = "backside_gold" PlaysideContourBlue = "playside_contour_blue" PlaysideContourPurple = "playside_contour_purple" BacksideContourBlue = "backside_contour_blue" BacksideContourPurple = "backside_contour_purple" Pattern = "pattern" Wear = "wear" Date = "date" Price = "price"
# fmt: on
[docs] class Item(Enum): """Items that can be queried from the API.""" # fmt: off AK47 = "AK-47" Bayonet = "Bayonet" BowieKnife = "Bowie Knife" ButterflyKnife = "Butterfly Knife" ClassicKnife = "Classic Knife" DesertEagle_Heat_Treated = "Desert Eagle" FalchionKnife = "Falchion Knife" FiveSeveN = "Five-SeveN" FiveSeveN_Heat_Treated = "Five-SeveN Heat Treated" FlipKnife = "Flip Knife" GutKnife = "Gut Knife" HuntsmanKnife = "Huntsman Knife" HydraGloves = "Hydra Gloves" Karambit = "Karambit" KukriKnife = "Kukri Knife" M9Bayonet = "M9 Bayonet" MAC10 = "MAC-10" NavajaKnife = "Navaja Knife" NomadKnife = "Nomad Knife" ParacordKnife = "Paracord Knife" ShadowDaggers = "Shadow Daggers" SkeletonKnife = "Skeleton Knife" StilettoKnife = "Stiletto Knife" SurvivalKnife = "Survival Knife" TalonKnife = "Talon Knife" UrsusKnife = "Ursus Knife"
# fmt: on