Skip to content
Open
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
6 changes: 6 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ def get_item_data(file_name: str) -> dict:
SPELLED_ITEM = get_item_data("spelled_item")
PAINTED_HAT = get_item_data("painted_hat")
ELLIS_CAP = get_item_data("ellis_cap")
STRANGE_PART = get_item_data("strange_part")


@pytest.fixture
Expand Down Expand Up @@ -84,6 +85,11 @@ def ellis_cap() -> dict:
return ELLIS_CAP


@pytest.fixture
def strange_part() -> dict:
return STRANGE_PART


@pytest.fixture
async def aiohttp_session():
async with ClientSession() as session:
Expand Down
4 changes: 4 additions & 0 deletions docs/usage/inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
provider = Inventory() # (the same as above)
# using steamcommunity will not require an API key and fetches 5000 items at once

# SteamCommunity uses a hardcoded browser-style User-Agent.
# Referer is set to the target user's profile inventory page for every fetch.
# These headers do not guarantee that Steam's rate limits will be avoided.

# Get the inventory of a user
user_inventory = provider.fetch("76561198828172881")

Expand Down
2 changes: 1 addition & 1 deletion src/tf2_utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# flake8: noqa: F401, F403
__title__ = "tf2-utils"
__author__ = "offish"
__version__ = "2.4.1"
__version__ = "2.4.3"
__license__ = "MIT"

from .currency import CurrencyExchange
Expand Down
12 changes: 10 additions & 2 deletions src/tf2_utils/inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,19 @@ def __init__(
self.provider = i(api_key)
break

def fetch(self, steam_id: str, app_id: int = 440, context_id: int = 2) -> dict:
def fetch(
self,
steam_id: str,
app_id: int = 440,
context_id: int = 2,
start_assetid: str | None = None,
) -> dict:
url, params = self.provider.get_url_and_params(steam_id, app_id, context_id)
if start_assetid is not None:
url = f"{url}?start_assetid={start_assetid}"
response = requests.get(url, params=params, headers=self.provider.headers)

try:
return response.json()
except Exception as e:
except ValueError as e:
return {"success": False, "error": str(e)}
2 changes: 1 addition & 1 deletion src/tf2_utils/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def has_quality(self, quality: str) -> bool:
return self.get_quality() == quality

def has_strange_in_name(self) -> bool:
return "Strange" in self.name
return "Strange " in self.name and "Strange Part: " not in self.name

def has_vintage_in_name(self) -> bool:
return "Vintage" in self.name
Expand Down
34 changes: 34 additions & 0 deletions src/tf2_utils/providers/steamcommunity.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,44 @@
from requests.structures import CaseInsensitiveDict

from .provider import Provider


class SteamCommunity(Provider):
def __init__(self, api_key: str = "") -> None:
super().__init__(api_key)
self.headers = CaseInsensitiveDict(
{
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/131.0.0.0 Safari/537.36"
),
"Accept": (
"text/html,application/xhtml+xml,application/xml;q=0.9,"
"image/avif,image/webp,image/apng,*/*;q=0.8,"
"application/signed-exchange;v=b3;q=0.7"
),
"Accept-Language": "en-US,en;q=0.9,ja;q=0.8,ms;q=0.7",
"Accept-Encoding": "gzip, deflate, br, zstd",
"Cache-Control": "max-age=0",
"Connection": "keep-alive",
"Cookie": "",
"Host": "steamcommunity.com",
"Upgrade-Insecure-Requests": "1",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"Referer": "https://steamcommunity.com/",
}
)

def get_url_and_params(
self, steam_id: str, app_id: int, context_id: int
) -> tuple[str, dict]:
self.headers["Referer"] = (
f"https://steamcommunity.com/profiles/{steam_id}/inventory"
)
return (
f"https://steamcommunity.com/inventory/{steam_id}/{app_id}/{context_id}",
{"l": "english", "count": 2500},
Expand Down
29 changes: 29 additions & 0 deletions tests/test_inventory.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from unittest.mock import Mock, patch

import pytest

from src.tf2_utils import InvalidInventory, map_inventory
Expand Down Expand Up @@ -56,6 +58,33 @@ def test_steamapis_inventory_provider() -> None:
assert inventory.provider.api_key == "api_key"


@pytest.mark.parametrize("app_id,context_id", [(440, 2), (730, 3)])
def test_steamcommunity_inventory_request(app_id, context_id) -> None:
inventory = Inventory()
response = Mock()
response.json.return_value = INVENTORY

with patch("src.tf2_utils.inventory.requests.get", return_value=response) as get:
for steam_id in ["76561198253325712", "76561198828172881"]:
assert inventory.fetch(steam_id, app_id, context_id) is INVENTORY
get.assert_called_with(
f"https://steamcommunity.com/inventory/"
f"{steam_id}/{app_id}/{context_id}",
params={"l": "english", "count": 2500},
headers=inventory.provider.headers,
)
headers = get.call_args.kwargs["headers"]
assert headers["Referer"] == (
f"https://steamcommunity.com/profiles/{steam_id}/inventory"
)
assert headers["User-Agent"] == (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/130.0.0.0 Safari/537.36"
)
assert "Origin" not in headers


def test_steamcommunity_inventory_fetch(steam_id: str) -> None:
provider = Inventory("steamcommunity")
inventory = provider.fetch(steam_id)
Expand Down
41 changes: 40 additions & 1 deletion tests/test_providers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import pytest

from src.tf2_utils.providers.express_load import ExpressLoad
from src.tf2_utils.providers.steamcommunity import SteamCommunity


@pytest.mark.parametrize("steam_id", ["76561198253325712"])
def test_steam_community_inventory(steam_id: str) -> None:
provider = SteamCommunity()
url, params = provider.get_url_and_params(steam_id, 440, 2)
Expand All @@ -12,7 +15,43 @@ def test_steam_community_inventory(steam_id: str) -> None:
"l": "english",
"count": 2500,
}
assert provider.headers == {}
assert provider.headers["User-Agent"].startswith("Mozilla/5.0 ")
assert "Chrome/" in provider.headers["User-Agent"]
assert provider.headers["Referer"] == (
f"https://steamcommunity.com/profiles/{steam_id}/inventory"
)


def test_steam_community_api_key() -> None:
provider = SteamCommunity("api_key")

assert provider.api_key == "api_key"
assert provider.headers["User-Agent"] == (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/130.0.0.0 Safari/537.36"
)


def test_steam_community_headers_are_independent() -> None:
first = SteamCommunity()
second = SteamCommunity()
first.get_url_and_params("76561198253325712", 440, 2)

assert second.headers["User-Agent"].startswith("Mozilla/5.0 ")
assert "Referer" not in second.headers


def test_steam_community_referer_tracks_inventory() -> None:
provider = SteamCommunity()
for steam_id in ["76561198253325712", "76561198828172881"]:
url, params = provider.get_url_and_params(steam_id, 730, 2)
assert url == f"https://steamcommunity.com/inventory/{steam_id}/730/2"
assert params == {"l": "english", "count": 2500}
assert provider.headers["Referer"] == (
f"https://steamcommunity.com/profiles/{steam_id}/inventory"
)
assert len(provider.headers) == 2


def test_express_load_inventory(express_load_api_key: str, steam_id: str) -> None:
Expand Down
16 changes: 16 additions & 0 deletions tests/test_sku.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,25 @@ def test_ellis_cap_sku_properties(ellis_cap: dict) -> None:
}


def test_strange_part_sku_properties(strange_part: dict) -> None:
assert get_sku_properties(strange_part) == {
"defindex": 6012,
"quality": 6,
"australium": False,
"craftable": True,
"wear": -1,
"killstreak_tier": -1,
"festivized": False,
"strange": False,
}


def test_get_sku_items(
ellis_cap: dict,
crusaders_crossbow: dict,
hong_kong_cone: dict,
uncraftable_hat: dict,
strange_part: dict,
) -> None:
# https://marketplace.tf/items/tf2/263;6
assert get_sku(ellis_cap) == "263;6"
Expand All @@ -41,6 +55,8 @@ def test_get_sku_items(
assert get_sku(hong_kong_cone) == "30177;5;u107;strange"
# https://marketplace.tf/items/tf2/734;6;uncraftable
assert get_sku(uncraftable_hat) == "734;6;uncraftable"
# https://marketplace.tf/items/tf2/6012;6
assert get_sku(strange_part) == "6012;6"


def test_properties() -> None:
Expand Down
Loading