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