Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from pathlib import Path

import pytest
from aiohttp import ClientSession
from dotenv import load_dotenv

from src.tf2_utils.utils import read_json_file
Expand Down Expand Up @@ -81,3 +82,9 @@ def painted_hat() -> dict:
@pytest.fixture
def ellis_cap() -> dict:
return ELLIS_CAP


@pytest.fixture
async def aiohttp_session():
async with ClientSession() as session:
yield session
2 changes: 2 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[pytest]
asyncio_mode = auto
21 changes: 3 additions & 18 deletions src/tf2_utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,16 @@
# flake8: noqa: F401, F403
__title__ = "tf2-utils"
__author__ = "offish"
__version__ = "2.3.5"
__version__ = "2.4.0"
__license__ = "MIT"

from .currency import CurrencyExchange
from .exceptions import InvalidInventory, TF2UtilsError
from .inventory import Inventory, map_inventory
from .item import Item
from .item_name import *
from .marketplace_tf import (
MarketplaceTF,
MarketplaceTFException,
NoAPIKey,
SKUDoesNotMatch,
)
from .marketplace_tf import MarketplaceTF
from .offer import Offer
from .prices_tf import (
EmptyResponse,
InternalServerError,
PricesTF,
PricesTFError,
RateLimited,
UnauthorizedError,
)
from .prices_tf_websocket import PricesTFWebsocket
from .schema import SchemaItemsUtils
from .sku import *
from .utils import *

# flake8: noqa: F401, F403
3 changes: 3 additions & 0 deletions src/tf2_utils/instances.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .schema import SchemaItemsUtils

schema = SchemaItemsUtils()
164 changes: 24 additions & 140 deletions src/tf2_utils/marketplace_tf.py
Original file line number Diff line number Diff line change
@@ -1,162 +1,46 @@
import time
from aiohttp import ClientSession

import requests

from .exceptions import TF2UtilsError
from .schema import SchemaItemsUtils
from .instances import schema
from .sku import sku_is_craftable, sku_to_quality_name


class MarketplaceTFException(TF2UtilsError):
pass


class SKUDoesNotMatch(MarketplaceTFException):
pass


class NoAPIKey(MarketplaceTFException):
pass


def api_key_required(func):
def wrapper(self, *args, **kwargs):
if self._api_key is None:
raise NoAPIKey("No API key provided")

return func(self, *args, **kwargs)

return wrapper


class MarketplaceTF:
def __init__(self, api_key: str = None):
self._api_key = api_key
self._schema = SchemaItemsUtils()
self._data = {}

def _get_request(self, endpoint: str, params: dict = {}) -> dict:
url = "https://marketplace.tf/api" + endpoint

if self._api_key is not None:
params["key"] = self._api_key

response = requests.get(url, params=params)
response.raise_for_status()

return response.json()

def get_endpoints(self) -> dict:
return self._get_request("/Meta/GetEndpoints/v1")

@api_key_required
def get_bots(self) -> dict:
return self._get_request("/Bots/GetBots/v2")

@api_key_required
def get_bans(self, steam_id: str) -> dict:
return self._get_request("/Bans/GetUserBan/v2", {"steamid": steam_id})

def get_is_banned(self, steam_id: str) -> bool:
return self.get_bans(steam_id)["result"][0]["banned"]

def get_name(self, steam_id: str) -> str:
return self.get_bans(steam_id)["result"][0]["name"]

def get_is_seller(self, steam_id: str) -> bool:
return self.get_bans(steam_id)["result"][0]["seller"]

def get_seller_id(self, steam_id: str) -> int:
return self.get_bans(steam_id)["result"][0]["id"]

@api_key_required
def get_dashboard_items(self) -> dict:
return self._get_request("/Seller/GetDashboardItems/v2")

@api_key_required
def get_sales(self, number: int = 100, start_before: int = 0) -> dict:
if start_before == 0:
start_before = int(time.time())

return self._get_request(
"/Seller/GetSales/v2", {"num": number, "start_before": start_before}
)
def __init__(self, session: ClientSession) -> None:
self.session = session

@staticmethod
def _format_url(item_name: str, quality: str, craftable: bool) -> str:
def format_url(item_name: str, quality: str, craftable: bool) -> str:
url = "https://api.backpack.tf/item/get_third_party_prices"
craftable = "Craftable" if craftable else "Non-Craftable"

return f"{url}/{quality}/{item_name}/Tradable/{craftable}"

def _format_url_sku(self, sku: str) -> str:
item_name = self._schema.sku_to_base_name(sku)
def format_url_sku(self, sku: str) -> str:
item_name = schema.sku_to_base_name(sku)
quality = sku_to_quality_name(sku)

return self._format_url(item_name, quality, sku_is_craftable(sku))
return self.format_url(item_name, quality, sku_is_craftable(sku))

@staticmethod
def _format_price_to_float(price: str | float) -> float:
def format_price_to_float(price: str | float) -> float:
if isinstance(price, float):
return price

return float(price.replace("$", ""))

def _set_data(self, data: dict) -> None:
self._data = data["prices"]["mp"]

def fetch_item_raw(self, item_name: str, quality: str, craftable: bool) -> dict:
url = self._format_url(item_name, quality, craftable)
self._set_data(requests.get(url).json())

return self._data

def fetch_item(self, sku: str) -> dict:
url = self._format_url_sku(sku)
self._set_data(requests.get(url).json())
mptf_sku = self.get_sku()

if mptf_sku != sku:
raise SKUDoesNotMatch(f"SKU {sku} does not match {mptf_sku}")

return self._data

def get_item_data(self) -> dict:
return self._data

def get_lowest_price(self) -> float:
price = self._data.get("lowest_price", 0.0)
return self._format_price_to_float(price)

def get_price(self) -> float:
return self.get_lowest_price()

def get_highest_buy_order(self) -> float:
price = self._data.get("highest_buy_order", 0.0)
return self._format_price_to_float(price)

def get_buy_order(self) -> float:
return self.get_highest_buy_order()

def get_stock(self) -> int:
return self._data.get("num_for_sale", 0)

def get_sku(self) -> str:
return self._data.get("sku", "")

def fetch_lowest_price(self, sku: str) -> float:
price = self.fetch_item(sku).get("lowest_price", 0.0)
return self._format_price_to_float(price)

def fetch_price(self, sku: str) -> float:
return self.fetch_lowest_price(sku)
async def fetch_item(self, sku: str) -> dict:
url = self.format_url_sku(sku)

def fetch_highest_buy_order(self, sku: str) -> float:
price = self.fetch_item(sku).get("highest_buy_order")
return self._format_price_to_float(price)
async with self.session.get(url) as resp:
resp.raise_for_status()
data = await resp.json()

def fetch_buy_order(self, sku: str) -> float:
return self.fetch_highest_buy_order(sku)
prices = data["prices"]["mp"]
highest_buy_order = prices["highest_buy_order"]
lowest_price = prices["lowest_price"]
stock = int(prices["num_for_sale"])

def fetch_stock(self, sku: str) -> int:
return self.fetch_item(sku).get("num_for_sale", 0)
return {
"sku": prices["sku"],
"highest_buy_order": self.format_price_to_float(highest_buy_order),
"lowest_price": self.format_price_to_float(lowest_price),
"stock": stock,
}
Loading