From 473f3817a9fe86e786d4edddc11ad52f9db49e3a Mon Sep 17 00:00:00 2001 From: offish Date: Mon, 24 Feb 2025 15:00:52 +0100 Subject: [PATCH 1/4] v2.3.0 --- .flake8 | 3 - .gitignore | 180 +++++++++- .vscode/settings.json | 9 + conftest.py | 11 + src/tf2_utils/__init__.py | 2 +- src/tf2_utils/backpack_tf.py | 9 +- src/tf2_utils/currency.py | 42 +-- src/tf2_utils/inventory.py | 8 +- src/tf2_utils/item.py | 2 +- src/tf2_utils/marketplace_tf.py | 5 +- src/tf2_utils/offer.py | 4 +- src/tf2_utils/prices_tf.py | 4 +- src/tf2_utils/schema.py | 12 +- src/tf2_utils/sku.py | 7 +- src/tf2_utils/sockets.py | 6 +- src/tf2_utils/utils.py | 4 +- tests/test_backpack_tf.py | 133 ++++--- tests/test_currency.py | 593 +++++++++++++++----------------- tests/test_inventory.py | 14 +- tests/test_item.py | 124 +++---- tests/test_marketplace_tf.py | 83 ++--- tests/test_offer.py | 31 +- tests/test_schema.py | 197 +++++------ tests/test_sku.py | 184 +++++----- tests/test_utils.py | 70 ++-- 25 files changed, 943 insertions(+), 794 deletions(-) delete mode 100644 .flake8 create mode 100644 .vscode/settings.json create mode 100644 conftest.py diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 96c59bf..0000000 --- a/.flake8 +++ /dev/null @@ -1,3 +0,0 @@ -[flake8] -max-line-length = 88 -extend-ignore = E203, E704, Q000 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8d593aa..0a0b60c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,177 @@ -.vscode -__pycache__ -jsons -config.py +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +#uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Other _build \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9c734d2 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "editor.formatOnSave": true, + "editor.defaultFormatter": "charliermarsh.ruff", + "[python]": { + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit" + } + } +} \ No newline at end of file diff --git a/conftest.py b/conftest.py new file mode 100644 index 0000000..d072e13 --- /dev/null +++ b/conftest.py @@ -0,0 +1,11 @@ +import pytest + + +@pytest.fixture +def marketplace_tf_api_key() -> str: + return "api_key" + + +@pytest.fixture +def backpack_tf_token() -> str: + return "backpack_tf_token" diff --git a/src/tf2_utils/__init__.py b/src/tf2_utils/__init__.py index 758865d..c629e00 100644 --- a/src/tf2_utils/__init__.py +++ b/src/tf2_utils/__init__.py @@ -1,7 +1,7 @@ # flake8: noqa __title__ = "tf2-utils" __author__ = "offish" -__version__ = "2.2.0" +__version__ = "2.3.0" __license__ = "MIT" from .sku import * diff --git a/src/tf2_utils/backpack_tf.py b/src/tf2_utils/backpack_tf.py index dfec4be..e1e13b8 100644 --- a/src/tf2_utils/backpack_tf.py +++ b/src/tf2_utils/backpack_tf.py @@ -1,12 +1,11 @@ -import requests - from dataclasses import dataclass, field from hashlib import md5 -from .schema import SchemaItemsUtils -from .sku import sku_to_quality, sku_is_craftable -from . import __title__ +import requests +from . import __title__ +from .schema import SchemaItemsUtils +from .sku import sku_is_craftable, sku_to_quality __all__ = [ "Currencies", diff --git a/src/tf2_utils/currency.py b/src/tf2_utils/currency.py index 9461117..e9cc392 100644 --- a/src/tf2_utils/currency.py +++ b/src/tf2_utils/currency.py @@ -24,7 +24,7 @@ def __init__( self.key_price = key_price self.item_is_not_pure = item_is_not_pure - self.__is_possible = False + self._is_possible = False self.their_scrap = 0 self.our_scrap = 0 self.their_overview = {} @@ -74,12 +74,8 @@ def get_pure_in_inventory(self, inventory: list[dict]) -> tuple[int, list[dict]] return scrap, metal - def is_possible(self) -> bool: - return self.__is_possible - - def __overview_to_items( - self, combination: list[str], inventory: list[dict] - ) -> list[dict]: + @staticmethod + def _overview_to_items(combination: list[str], inventory: list[dict]) -> list[dict]: items = [] for metal_name in combination: @@ -97,12 +93,12 @@ def __overview_to_items( return items def get_currencies(self) -> tuple[list[dict], list[dict]]: - assert self.__is_possible, "Currencies does not add up" + assert self._is_possible, "Currencies does not add up" - their_items = self.__overview_to_items( + their_items = self._overview_to_items( self.their_combination, self.their_inventory ) - our_items = self.__overview_to_items(self.our_combination, self.our_inventory) + our_items = self._overview_to_items(self.our_combination, self.our_inventory) return their_items, our_items @@ -125,7 +121,7 @@ def format_overview(pure: list[dict]) -> dict: return overview - def __pick_currencies(self, user: str) -> tuple[bool, list[str]]: + def _pick_currencies(self, user: str) -> tuple[bool, list[str]]: """ref, ref, scrap, rec""" overview = ( self.their_overview.copy() if user == "them" else self.our_overview.copy() @@ -166,7 +162,7 @@ def __pick_currencies(self, user: str) -> tuple[bool, list[str]]: return True, combination - def __does_add_up(self) -> bool: + def _adds_up(self) -> bool: their_value = 0 our_value = 0 @@ -184,9 +180,9 @@ def __does_add_up(self) -> bool: return their_value == our_value - def __set_combinations(self) -> bool: - success_our, our_combination = self.__pick_currencies("us") - success_their, their_combination = self.__pick_currencies("them") + def _set_combinations(self) -> bool: + success_our, our_combination = self._pick_currencies("us") + success_their, their_combination = self._pick_currencies("them") if not success_our or not success_their: return False @@ -196,7 +192,7 @@ def __set_combinations(self) -> bool: return True - def __has_enough(self) -> bool: + def _has_enough(self) -> bool: temp_price = self.scrap_price if self.item_is_not_pure: @@ -214,11 +210,11 @@ def calculate(self) -> None: self.their_overview = self.format_overview(their_pure) self.our_overview = self.format_overview(our_pure) - if not self.__has_enough(): + if not self._has_enough(): return - while self.__has_enough(): - success = self.__set_combinations() + while self._has_enough(): + success = self._set_combinations() if success: break @@ -227,8 +223,12 @@ def calculate(self) -> None: # try again, do this till we have either user does not have enough anymore self.scrap_price += 1 - if not self.__does_add_up(): + if not self._adds_up(): return # everything adds up and looks good - self.__is_possible = True + self._is_possible = True + + @property + def is_possible(self) -> bool: + return self._is_possible diff --git a/src/tf2_utils/inventory.py b/src/tf2_utils/inventory.py index 6e30ca0..6ed9db2 100644 --- a/src/tf2_utils/inventory.py +++ b/src/tf2_utils/inventory.py @@ -1,11 +1,11 @@ +import requests + +from .providers.custom import Custom +from .providers.steamapis import SteamApis from .providers.steamcommunity import SteamCommunity from .providers.steamsupply import SteamSupply -from .providers.steamapis import SteamApis -from .providers.custom import Custom from .sku import get_sku -import requests - def map_inventory(inventory: dict, add_skus: bool = False) -> list[dict]: """Matches classids and instanceids, merges these and diff --git a/src/tf2_utils/item.py b/src/tf2_utils/item.py index 1f871e9..a3dc246 100644 --- a/src/tf2_utils/item.py +++ b/src/tf2_utils/item.py @@ -1,4 +1,4 @@ -from tf2_data import QUALITIES, KILLSTREAKS, EXTERIORS +from tf2_data import EXTERIORS, KILLSTREAKS, QUALITIES class Item: diff --git a/src/tf2_utils/marketplace_tf.py b/src/tf2_utils/marketplace_tf.py index 6deb519..eef99b0 100644 --- a/src/tf2_utils/marketplace_tf.py +++ b/src/tf2_utils/marketplace_tf.py @@ -1,10 +1,9 @@ -from .schema import SchemaItemsUtils -from .sku import sku_to_quality_name, sku_is_craftable - import time import requests +from .schema import SchemaItemsUtils +from .sku import sku_is_craftable, sku_to_quality_name __all__ = ["MarketplaceTF", "MarketplaceTFException", "SKUDoesNotMatch", "NoAPIKey"] diff --git a/src/tf2_utils/offer.py b/src/tf2_utils/offer.py index d8e426e..8b85ad1 100644 --- a/src/tf2_utils/offer.py +++ b/src/tf2_utils/offer.py @@ -1,7 +1,7 @@ -from .utils import account_id_to_steam_id - import enum +from .utils import account_id_to_steam_id + class TradeOfferState(enum.IntEnum): Invalid = 1 diff --git a/src/tf2_utils/prices_tf.py b/src/tf2_utils/prices_tf.py index 8a8a323..882dabf 100644 --- a/src/tf2_utils/prices_tf.py +++ b/src/tf2_utils/prices_tf.py @@ -1,8 +1,8 @@ -import requests import time -from .utils import to_refined +import requests +from .utils import to_refined __all__ = [ "PricesTF", diff --git a/src/tf2_utils/schema.py b/src/tf2_utils/schema.py index 10f82a8..f2b0d88 100644 --- a/src/tf2_utils/schema.py +++ b/src/tf2_utils/schema.py @@ -1,15 +1,15 @@ +from tf2_data import EFFECTS, KILLSTREAKS, SchemaItems +from tf2_sku import to_sku + from .sku import ( - sku_to_defindex, - sku_to_quality_name, - sku_is_uncraftable, get_sku_effect, get_sku_killstreak, + sku_is_uncraftable, + sku_to_defindex, + sku_to_quality_name, strange_in_sku, ) -from tf2_data import SchemaItems, EFFECTS, KILLSTREAKS -from tf2_sku import to_sku - class SchemaItemsUtils(SchemaItems): def __init__( diff --git a/src/tf2_utils/sku.py b/src/tf2_utils/sku.py index 0d0a239..cf3b337 100644 --- a/src/tf2_utils/sku.py +++ b/src/tf2_utils/sku.py @@ -1,10 +1,9 @@ -from .item import Item +import re -from tf2_data import EFFECTS, COLORS, QUALITIES +from tf2_data import COLORS, EFFECTS, QUALITIES from tf2_sku import to_sku -import re - +from .item import Item __all__ = [ "get_sku_properties", diff --git a/src/tf2_utils/sockets.py b/src/tf2_utils/sockets.py index a655fec..ee823ac 100644 --- a/src/tf2_utils/sockets.py +++ b/src/tf2_utils/sockets.py @@ -1,10 +1,10 @@ -from .prices_tf import PricesTF - -from typing import Callable import json +from typing import Callable from websockets.sync.client import ClientConnection, connect +from .prices_tf import PricesTF + class BackpackTFSocket: URL = "wss://ws.backpack.tf/events" diff --git a/src/tf2_utils/utils.py b/src/tf2_utils/utils.py index 8de1b5b..1550d1b 100644 --- a/src/tf2_utils/utils.py +++ b/src/tf2_utils/utils.py @@ -1,6 +1,6 @@ -import struct -import math import json +import math +import struct __all__ = [ "to_scrap", diff --git a/tests/test_backpack_tf.py b/tests/test_backpack_tf.py index 9c909ce..7c41453 100644 --- a/tests/test_backpack_tf.py +++ b/tests/test_backpack_tf.py @@ -1,76 +1,71 @@ -from unittest import TestCase - -from .config import BACKPACK_TF_TOKEN from src.tf2_utils import BackpackTF, Currencies +bptf = None + + +def test_initiate_backpack_tf(backpack_tf_token: str) -> None: + global bptf + bptf = BackpackTF(backpack_tf_token, "76561198253325712") + + +def test_currencies() -> None: + assert Currencies(1, 1.5).__dict__ == {"keys": 1, "metal": 1.5} + assert Currencies().__dict__ == {"keys": 0, "metal": 0.0} + assert Currencies(**{"metal": 10.55}).__dict__ == {"keys": 0, "metal": 10.55} + + +def test_construct_listing_item() -> None: + assert bptf._construct_listing_item("263;6") == { + "baseName": "Ellis' Cap", + "craftable": True, + "quality": {"id": 6}, + "tradable": True, + } -class TestBackpackTF(TestCase): - def setUp(cls) -> None: - cls.bptf = BackpackTF(BACKPACK_TF_TOKEN, "76561198253325712") - def test_currencies(self): - self.assertEqual(Currencies(1, 1.5).__dict__, {"keys": 1, "metal": 1.5}) - self.assertEqual(Currencies().__dict__, {"keys": 0, "metal": 0.0}) - self.assertEqual( - Currencies(**{"metal": 10.55}).__dict__, {"keys": 0, "metal": 10.55} - ) +def test_construct_listing() -> None: + assert bptf._construct_listing( + "263;6", + "sell", + {"keys": 1, "metal": 1.55}, + "my description", + 13201231975, + ) == { + "buyout": True, + "offers": True, + "promoted": False, + "item": { + "baseName": "Ellis' Cap", + "craftable": True, + "quality": {"id": 6}, + "tradable": True, + }, + "currencies": {"keys": 1, "metal": 1.55}, + "details": "my description", + "id": 13201231975, + } - def test_construct_listing_item(self): - self.assertDictEqual( - self.bptf._construct_listing_item("263;6"), - { - "baseName": "Ellis' Cap", - "craftable": True, - "quality": {"id": 6}, - "tradable": True, - }, - ) + assert bptf._construct_listing( + "263;6", "buy", {"keys": 1, "metal": 1.55}, "my description" + ) == { + "buyout": True, + "offers": True, + "promoted": False, + "item": { + "baseName": "Ellis' Cap", + "craftable": True, + "quality": {"id": 6}, + "tradable": True, + }, + "currencies": {"keys": 1, "metal": 1.55}, + "details": "my description", + } - def test_construct_listing(self): - self.assertDictEqual( - self.bptf._construct_listing( - "263;6", - "sell", - {"keys": 1, "metal": 1.55}, - "my description", - 13201231975, - ), - { - "item": { - "baseName": "Ellis' Cap", - "craftable": True, - "quality": {"id": 6}, - "tradable": True, - }, - "currencies": {"keys": 1, "metal": 1.55}, - "details": "my description", - "id": 13201231975, - }, - ) - self.assertDictEqual( - self.bptf._construct_listing( - "263;6", "buy", {"keys": 1, "metal": 1.55}, "my description" - ), - { - "item": { - "baseName": "Ellis' Cap", - "craftable": True, - "quality": {"id": 6}, - "tradable": True, - }, - "currencies": {"keys": 1, "metal": 1.55}, - "details": "my description", - }, - ) - def test_hash_name(self): - self.assertEqual( - self.bptf._get_sku_item_hash("5021;6"), "d9f847ff5dfcf78576a9fca04cbf6c07" - ) - self.assertEqual( - self.bptf._get_sku_item_hash("30397;6"), "8010db121d19610e9bcbac47432bd78c" - ) - self.assertEqual( - self.bptf._get_item_hash("The Bruiser's Bandanna"), - "8010db121d19610e9bcbac47432bd78c", - ) +def test_hash_name() -> None: + assert bptf._get_sku_item_hash("5021;6") == "d9f847ff5dfcf78576a9fca04cbf6c07" + assert bptf._get_sku_item_hash("30397;6") == "8010db121d19610e9bcbac47432bd78c" + assert ( + bptf._get_item_hash("The Bruiser's Bandanna") + == "8010db121d19610e9bcbac47432bd78c" + ) diff --git a/tests/test_currency.py b/tests/test_currency.py index c0e05c8..339f96a 100644 --- a/tests/test_currency.py +++ b/tests/test_currency.py @@ -1,10 +1,7 @@ -from src.tf2_utils.inventory import map_inventory from src.tf2_utils.currency import CurrencyExchange +from src.tf2_utils.inventory import map_inventory from src.tf2_utils.utils import read_json_file -from unittest import TestCase - - INVENTORY = read_json_file("./tests/json/inventory.json") PICKED_METALS = read_json_file("./tests/json/picked_metals.json") @@ -20,320 +17,274 @@ ] -class TestCurrency(TestCase): - def test_utils(self): - c = CurrencyExchange( - [ - {"market_hash_name": "Refined Metal", "tags": TAG}, - {"market_hash_name": "Reclaimed Metal", "tags": TAG}, - {"market_hash_name": "Scrap Metal", "tags": TAG}, - {"market_hash_name": "Refined Metal", "tags": TAG}, - ], - [ - {"market_hash_name": "Refined Metal", "tags": TAG}, - {"market_hash_name": "Reclaimed Metal", "tags": TAG}, - ], - "buy", - 4, - KEY_RATE, - ) - c.calculate() - - self.assertEqual(22, c.their_scrap) - self.assertEqual(12, c.our_scrap) - - self.assertEqual( - { - "Mann Co. Supply Crate Key": 0, - "Refined Metal": 2, - "Reclaimed Metal": 1, - "Scrap Metal": 1, - }, - c.their_overview, - ) - self.assertEqual( - { - "Mann Co. Supply Crate Key": 0, - "Refined Metal": 1, - "Reclaimed Metal": 1, - "Scrap Metal": 0, - }, - c.our_overview, - ) - - self.assertFalse(c.is_possible()) - - def test_not_enough(self): - c = CurrencyExchange( - [ - {"market_hash_name": "Refined Metal", "tags": TAG}, - {"market_hash_name": "Refined Metal", "tags": TAG}, - {"market_hash_name": "Scrap Metal", "tags": TAG}, - ], - [], - "sell", - 20, - KEY_RATE, - ) - c.calculate() - - self.assertEqual(19, c.their_scrap) - self.assertFalse(c.is_possible()) - - def test_no_combination(self): - c = CurrencyExchange( - [ - {"market_hash_name": "Refined Metal", "tags": TAG}, - {"market_hash_name": "Refined Metal", "tags": TAG}, - ], - [ - {"market_hash_name": "Reclaimed Metal", "tags": TAG}, - {"market_hash_name": "Refined Metal", "tags": TAG}, - ], - "buy", - 17, # 1.88 - KEY_RATE, - ) - c.calculate() - - self.assertEqual(18, c.their_scrap) - self.assertEqual(12, c.our_scrap) - - # they have enough but there is no combo which would work - self.assertFalse(c.is_possible()) - - def test_possible(self): - c = CurrencyExchange( - [ - {"market_hash_name": "Refined Metal", "tags": TAG}, - {"market_hash_name": "Scrap Metal", "tags": TAG}, - {"market_hash_name": "Scrap Metal", "tags": TAG}, - {"market_hash_name": "Scrap Metal", "tags": TAG}, - {"market_hash_name": "Scrap Metal", "tags": TAG}, - {"market_hash_name": "Scrap Metal", "tags": TAG}, - {"market_hash_name": "Scrap Metal", "tags": TAG}, - ], - [ - {"market_hash_name": "Refined Metal", "tags": TAG}, - {"market_hash_name": "Reclaimed Metal", "tags": TAG}, - ], - "buy", - 4, - KEY_RATE, - ) - c.calculate() - - self.assertEqual(15, c.their_scrap) - self.assertEqual(12, c.our_scrap) - - self.assertEqual( - { - "Mann Co. Supply Crate Key": 0, - "Refined Metal": 1, - "Reclaimed Metal": 0, - "Scrap Metal": 6, - }, - c.their_overview, - ) - self.assertEqual( - { - "Mann Co. Supply Crate Key": 0, - "Refined Metal": 1, - "Reclaimed Metal": 1, - "Scrap Metal": 0, - }, - c.our_overview, - ) - - self.assertTrue(c.is_possible()) - - # 0.44, but we pay 1 ref - # that means they have to add 0.55 - self.assertEqual(["Refined Metal"], c.our_combination) - self.assertEqual( - [ - "Scrap Metal", - "Scrap Metal", - "Scrap Metal", - "Scrap Metal", - "Scrap Metal", - ], - c.their_combination, - ) - - def test_best_possible(self): - c = CurrencyExchange( - [ - {"market_hash_name": "Refined Metal", "tags": TAG}, - {"market_hash_name": "Scrap Metal", "tags": TAG}, - {"market_hash_name": "Mann Co. Supply Crate Key", "tags": TAG}, - {"market_hash_name": "Scrap Metal", "tags": TAG}, - {"market_hash_name": "Scrap Metal", "tags": TAG}, - {"market_hash_name": "Scrap Metal", "tags": TAG}, - {"market_hash_name": "Scrap Metal", "tags": TAG}, - {"market_hash_name": "Reclaimed Metal", "tags": TAG}, - {"market_hash_name": "Reclaimed Metal", "tags": TAG}, - ], - [ - {"market_hash_name": "Refined Metal", "tags": TAG}, - {"market_hash_name": "Refined Metal", "tags": TAG}, - ], - "sell", - 14, - KEY_RATE, - ) - c.calculate() - - self.assertEqual(KEY_RATE + 20, c.their_scrap) - self.assertEqual(18, c.our_scrap) - - self.assertEqual( - { - "Mann Co. Supply Crate Key": 1, - "Refined Metal": 1, - "Reclaimed Metal": 2, - "Scrap Metal": 5, - }, - c.their_overview, - ) - self.assertEqual( - { - "Mann Co. Supply Crate Key": 0, - "Refined Metal": 2, - "Reclaimed Metal": 0, - "Scrap Metal": 0, - }, - c.our_overview, - ) - - self.assertTrue(c.is_possible()) - - self.assertEqual( - [ - "Scrap Metal", - "Scrap Metal", - "Reclaimed Metal", - "Refined Metal", - ], - c.their_combination, - ) - self.assertEqual( - [], - c.our_combination, - ) - - def test_only_metal(self): - c = CurrencyExchange( - [ - {"market_hash_name": "Scrap Metal", "tags": TAG}, - {"market_hash_name": "Scrap Metal", "tags": TAG}, - {"market_hash_name": "Scrap Metal", "tags": TAG}, - {"market_hash_name": "Reclaimed Metal", "tags": TAG}, - {"market_hash_name": "Reclaimed Metal", "tags": TAG}, - ], - [ - {"market_hash_name": "Refined Metal", "tags": TAG}, - {"market_hash_name": "Refined Metal", "tags": TAG}, - ], - "buy", - 9, - KEY_RATE, - False, - ) - c.calculate() - - self.assertEqual(9, c.their_scrap) - self.assertEqual(18, c.our_scrap) - - self.assertEqual( - { - "Mann Co. Supply Crate Key": 0, - "Refined Metal": 0, - "Reclaimed Metal": 2, - "Scrap Metal": 3, - }, - c.their_overview, - ) - self.assertEqual( - { - "Mann Co. Supply Crate Key": 0, - "Refined Metal": 2, - "Reclaimed Metal": 0, - "Scrap Metal": 0, - }, - c.our_overview, - ) - - self.assertTrue(c.is_possible()) - - self.assertEqual( - [ - "Reclaimed Metal", - "Reclaimed Metal", - "Scrap Metal", - "Scrap Metal", - "Scrap Metal", - ], - c.their_combination, - ) - self.assertEqual( - ["Refined Metal"], - c.our_combination, - ) - - def test_real_inventory(self): - mapped_inventory = map_inventory(INVENTORY, True) - - c = CurrencyExchange( - mapped_inventory, - [], - "sell", - 15, - KEY_RATE, - ) - - c.calculate() - - self.assertEqual(591, c.their_scrap) - self.assertEqual(0, c.our_scrap) - - self.assertEqual( - { - "Mann Co. Supply Crate Key": 0, - "Refined Metal": 65, - "Reclaimed Metal": 1, - "Scrap Metal": 3, - }, - c.their_overview, - ) - self.assertEqual( - { - "Mann Co. Supply Crate Key": 0, - "Refined Metal": 0, - "Reclaimed Metal": 0, - "Scrap Metal": 0, - }, - c.our_overview, - ) - - self.assertTrue(c.is_possible()) - - self.assertEqual( - [ - "Reclaimed Metal", - "Scrap Metal", - "Scrap Metal", - "Scrap Metal", - "Refined Metal", - ], - c.their_combination, - ) - self.assertEqual( - [], - c.our_combination, - ) - - their_items, our_items = c.get_currencies() - - self.assertEqual(PICKED_METALS, their_items) - self.assertEqual([], our_items) +def test_utils() -> None: + c = CurrencyExchange( + [ + {"market_hash_name": "Refined Metal", "tags": TAG}, + {"market_hash_name": "Reclaimed Metal", "tags": TAG}, + {"market_hash_name": "Scrap Metal", "tags": TAG}, + {"market_hash_name": "Refined Metal", "tags": TAG}, + ], + [ + {"market_hash_name": "Refined Metal", "tags": TAG}, + {"market_hash_name": "Reclaimed Metal", "tags": TAG}, + ], + "buy", + 4, + KEY_RATE, + ) + c.calculate() + + assert 22 == c.their_scrap + assert 12 == c.our_scrap + + assert { + "Mann Co. Supply Crate Key": 0, + "Refined Metal": 2, + "Reclaimed Metal": 1, + "Scrap Metal": 1, + } == c.their_overview + assert { + "Mann Co. Supply Crate Key": 0, + "Refined Metal": 1, + "Reclaimed Metal": 1, + "Scrap Metal": 0, + } == c.our_overview + + assert not c.is_possible + + +def test_not_enough() -> None: + c = CurrencyExchange( + [ + {"market_hash_name": "Refined Metal", "tags": TAG}, + {"market_hash_name": "Refined Metal", "tags": TAG}, + {"market_hash_name": "Scrap Metal", "tags": TAG}, + ], + [], + "sell", + 20, + KEY_RATE, + ) + c.calculate() + + assert 19 == c.their_scrap + assert not c.is_possible + + +def test_no_combination() -> None: + c = CurrencyExchange( + [ + {"market_hash_name": "Refined Metal", "tags": TAG}, + {"market_hash_name": "Refined Metal", "tags": TAG}, + ], + [ + {"market_hash_name": "Reclaimed Metal", "tags": TAG}, + {"market_hash_name": "Refined Metal", "tags": TAG}, + ], + "buy", + 17, # 1.88 + KEY_RATE, + ) + c.calculate() + + assert 18 == c.their_scrap + assert 12 == c.our_scrap + + # they have enough but there is no combo which would work + assert not c.is_possible + + +def test_possible() -> None: + c = CurrencyExchange( + [ + {"market_hash_name": "Refined Metal", "tags": TAG}, + {"market_hash_name": "Scrap Metal", "tags": TAG}, + {"market_hash_name": "Scrap Metal", "tags": TAG}, + {"market_hash_name": "Scrap Metal", "tags": TAG}, + {"market_hash_name": "Scrap Metal", "tags": TAG}, + {"market_hash_name": "Scrap Metal", "tags": TAG}, + {"market_hash_name": "Scrap Metal", "tags": TAG}, + ], + [ + {"market_hash_name": "Refined Metal", "tags": TAG}, + {"market_hash_name": "Reclaimed Metal", "tags": TAG}, + ], + "buy", + 4, + KEY_RATE, + ) + c.calculate() + + assert 15 == c.their_scrap + assert 12 == c.our_scrap + + assert { + "Mann Co. Supply Crate Key": 0, + "Refined Metal": 1, + "Reclaimed Metal": 0, + "Scrap Metal": 6, + } == c.their_overview + assert { + "Mann Co. Supply Crate Key": 0, + "Refined Metal": 1, + "Reclaimed Metal": 1, + "Scrap Metal": 0, + } == c.our_overview + + assert c.is_possible + + # 0.44, but we pay 1 ref + # that means they have to add 0.55 + assert ["Refined Metal"] == c.our_combination + assert [ + "Scrap Metal", + "Scrap Metal", + "Scrap Metal", + "Scrap Metal", + "Scrap Metal", + ] == c.their_combination + + +def test_best_possible() -> None: + c = CurrencyExchange( + [ + {"market_hash_name": "Refined Metal", "tags": TAG}, + {"market_hash_name": "Scrap Metal", "tags": TAG}, + {"market_hash_name": "Mann Co. Supply Crate Key", "tags": TAG}, + {"market_hash_name": "Scrap Metal", "tags": TAG}, + {"market_hash_name": "Scrap Metal", "tags": TAG}, + {"market_hash_name": "Scrap Metal", "tags": TAG}, + {"market_hash_name": "Scrap Metal", "tags": TAG}, + {"market_hash_name": "Reclaimed Metal", "tags": TAG}, + {"market_hash_name": "Reclaimed Metal", "tags": TAG}, + ], + [ + {"market_hash_name": "Refined Metal", "tags": TAG}, + {"market_hash_name": "Refined Metal", "tags": TAG}, + ], + "sell", + 14, + KEY_RATE, + ) + c.calculate() + + assert KEY_RATE + 20 == c.their_scrap + assert 18 == c.our_scrap + + assert { + "Mann Co. Supply Crate Key": 1, + "Refined Metal": 1, + "Reclaimed Metal": 2, + "Scrap Metal": 5, + } == c.their_overview + assert { + "Mann Co. Supply Crate Key": 0, + "Refined Metal": 2, + "Reclaimed Metal": 0, + "Scrap Metal": 0, + } == c.our_overview + + assert c.is_possible + + assert [ + "Scrap Metal", + "Scrap Metal", + "Reclaimed Metal", + "Refined Metal", + ] == c.their_combination + assert [] == c.our_combination + + +def test_only_metal() -> None: + c = CurrencyExchange( + [ + {"market_hash_name": "Scrap Metal", "tags": TAG}, + {"market_hash_name": "Scrap Metal", "tags": TAG}, + {"market_hash_name": "Scrap Metal", "tags": TAG}, + {"market_hash_name": "Reclaimed Metal", "tags": TAG}, + {"market_hash_name": "Reclaimed Metal", "tags": TAG}, + ], + [ + {"market_hash_name": "Refined Metal", "tags": TAG}, + {"market_hash_name": "Refined Metal", "tags": TAG}, + ], + "buy", + 9, + KEY_RATE, + False, + ) + c.calculate() + + assert 9 == c.their_scrap + assert 18 == c.our_scrap + + assert { + "Mann Co. Supply Crate Key": 0, + "Refined Metal": 0, + "Reclaimed Metal": 2, + "Scrap Metal": 3, + } == c.their_overview + assert { + "Mann Co. Supply Crate Key": 0, + "Refined Metal": 2, + "Reclaimed Metal": 0, + "Scrap Metal": 0, + } == c.our_overview + + assert c.is_possible + + assert [ + "Reclaimed Metal", + "Reclaimed Metal", + "Scrap Metal", + "Scrap Metal", + "Scrap Metal", + ] == c.their_combination + assert ["Refined Metal"] == c.our_combination + + +def test_real_inventory() -> None: + mapped_inventory = map_inventory(INVENTORY, True) + + c = CurrencyExchange( + mapped_inventory, + [], + "sell", + 15, + KEY_RATE, + ) + + c.calculate() + + assert 591 == c.their_scrap + assert 0 == c.our_scrap + + assert { + "Mann Co. Supply Crate Key": 0, + "Refined Metal": 65, + "Reclaimed Metal": 1, + "Scrap Metal": 3, + } == c.their_overview + assert { + "Mann Co. Supply Crate Key": 0, + "Refined Metal": 0, + "Reclaimed Metal": 0, + "Scrap Metal": 0, + } == c.our_overview + + assert c.is_possible + + assert [ + "Reclaimed Metal", + "Scrap Metal", + "Scrap Metal", + "Scrap Metal", + "Refined Metal", + ] == c.their_combination + assert [] == c.our_combination + + their_items, our_items = c.get_currencies() + + assert PICKED_METALS == their_items + assert [] == our_items diff --git a/tests/test_inventory.py b/tests/test_inventory.py index 2735b09..8a12ca4 100644 --- a/tests/test_inventory.py +++ b/tests/test_inventory.py @@ -1,14 +1,12 @@ -from src.tf2_utils.utils import read_json_file from src.tf2_utils import map_inventory - -from unittest import TestCase +from src.tf2_utils.utils import read_json_file INVENTORY = read_json_file("./tests/json/bot_inventory.json") +inventory = map_inventory(INVENTORY, True) -class TestInventory(TestCase): - def setUp(cls) -> None: - cls.inventory = map_inventory(INVENTORY, True) +def test_inventory() -> None: + assert 500 == len(inventory) - def test_inventory(self): - self.assertEqual(500, len(self.inventory)) + for item in inventory: + assert "sku" in item diff --git a/tests/test_item.py b/tests/test_item.py index 8c110e0..03dca07 100644 --- a/tests/test_item.py +++ b/tests/test_item.py @@ -1,7 +1,5 @@ -from src.tf2_utils.utils import read_json_file from src.tf2_utils import Item, get_sku - -from unittest import TestCase +from src.tf2_utils.utils import read_json_file file_path = "./tests/json/{}.json" @@ -18,61 +16,65 @@ def get_item(file_name: str) -> Item: ELLIS_CAP = get_item("ellis_cap") -class TestUtils(TestCase): - def test_craft_hat(self): - is_craft_hat = ELLIS_CAP.is_craft_hat() - is_special = ELLIS_CAP.is_special() - is_painted = ELLIS_CAP.is_painted() - - self.assertTrue(is_craft_hat) - self.assertFalse(is_special) - self.assertFalse(is_painted) - - def test_uncraftable_hat(self): - is_craftable = UNCRAFTABLE_HAT.is_craftable() - is_craft_hat = UNCRAFTABLE_HAT.is_craft_hat() - is_uncraftable = UNCRAFTABLE_HAT.is_uncraftable() - - self.assertFalse(is_craftable) - self.assertFalse(is_craft_hat) - self.assertTrue(is_uncraftable) - - def test_painted(self): - is_special = PAINTED_HAT.is_special() - is_painted = PAINTED_HAT.is_painted() - paint = PAINTED_HAT.get_paint() - is_craft_hat = PAINTED_HAT.is_craft_hat() - - self.assertTrue(is_special) - self.assertTrue(is_painted) - self.assertEqual("Australium Gold", paint) - self.assertTrue(is_craft_hat) - - def test_spell(self): - is_special = SPELLED_ITEM.is_special() - has_spell = SPELLED_ITEM.has_spell() - is_strange = SPELLED_ITEM.is_strange() - # spell=SPELLED_ITEM.get_spell() - - self.assertTrue(is_special) - self.assertTrue(has_spell) - self.assertTrue(is_strange) - # self.assertEqual("Exorcism", spell) - - def test_unusual(self): - is_unusual = HONG_KONG_CONE.is_unusual() - effect = HONG_KONG_CONE.get_effect() - paint = HONG_KONG_CONE.get_paint() - is_craft_hat = HONG_KONG_CONE.is_craft_hat() - strange_in_name = HONG_KONG_CONE.has_strange_in_name() - - self.assertTrue(is_unusual) - self.assertEqual("Neutron Star", effect) - self.assertEqual("An Extraordinary Abundance of Tinge", paint) - self.assertFalse(is_craft_hat) - self.assertTrue(strange_in_name) - - def test_sku_from_item_class(self): - sku = get_sku(ELLIS_CAP) - - self.assertEqual("263;6", sku) +def test_craft_hat() -> None: + is_craft_hat = ELLIS_CAP.is_craft_hat() + is_special = ELLIS_CAP.is_special() + is_painted = ELLIS_CAP.is_painted() + + assert is_craft_hat + assert not is_special + assert not is_painted + + +def test_uncraftable_hat() -> None: + is_craftable = UNCRAFTABLE_HAT.is_craftable() + is_craft_hat = UNCRAFTABLE_HAT.is_craft_hat() + is_uncraftable = UNCRAFTABLE_HAT.is_uncraftable() + + assert not is_craftable + assert not is_craft_hat + assert is_uncraftable + + +def test_painted() -> None: + is_special = PAINTED_HAT.is_special() + is_painted = PAINTED_HAT.is_painted() + paint = PAINTED_HAT.get_paint() + is_craft_hat = PAINTED_HAT.is_craft_hat() + + assert is_special + assert is_painted + assert "Australium Gold" == paint + assert is_craft_hat + + +def test_spell() -> None: + is_special = SPELLED_ITEM.is_special() + has_spell = SPELLED_ITEM.has_spell() + is_strange = SPELLED_ITEM.is_strange() + # spell=SPELLED_ITEM.get_spell() + + assert is_special + assert has_spell + assert is_strange + # assert "Exorcism"==spell + + +def test_unusual() -> None: + is_unusual = HONG_KONG_CONE.is_unusual() + effect = HONG_KONG_CONE.get_effect() + paint = HONG_KONG_CONE.get_paint() + is_craft_hat = HONG_KONG_CONE.is_craft_hat() + strange_in_name = HONG_KONG_CONE.has_strange_in_name() + + assert is_unusual + assert "Neutron Star" == effect + assert "An Extraordinary Abundance of Tinge" == paint + assert not is_craft_hat + assert strange_in_name + + +def test_sku_from_item_class() -> None: + sku = get_sku(ELLIS_CAP) + + assert "263;6" == sku diff --git a/tests/test_marketplace_tf.py b/tests/test_marketplace_tf.py index bc97d51..9bf41ee 100644 --- a/tests/test_marketplace_tf.py +++ b/tests/test_marketplace_tf.py @@ -1,58 +1,59 @@ from src.tf2_utils import MarketplaceTF -from .config import MARKETPLACE_TF_API_KEY -from unittest import TestCase +mplc = None -class TestMarketplaceTF(TestCase): - def setUp(cls) -> None: - cls.mplc = MarketplaceTF(MARKETPLACE_TF_API_KEY) +def test_initiate_marketplace_tf(marketplace_tf_api_key: str) -> None: + global mplc + mplc = MarketplaceTF(marketplace_tf_api_key) - # def test_no_api_key(self): - # self.mplc.api_key = "" - # endpoints = self.mplc.get_endpoints() - # self.assertTrue("endpoints" in endpoints) - # self.assertRaises(NoAPIKey, self.mplc.get_bots) - # self.assertRaises(NoAPIKey, self.mplc.get_is_banned("76561198253325712")) - # self.mplc.api_key = MARKETPLACE_TF_API_KEY +def test_key_data() -> None: + price = mplc.fetch_item("5021;6") - # def test_get_bots(self): - # self.assertTrue(self.mplc.get_bots()["success"]) + assert mplc.get_sku() == "5021;6" + assert mplc.get_stock() > 1 + assert mplc.get_price() > 1.4 + assert mplc.get_highest_buy_order() < 2.5 + assert mplc.get_item_data() == price + assert mplc.get_lowest_price() == mplc.get_price() - # def test_get_bans(self): - # self.assertEqual("confern", self.mplc.get_name("76561198253325712")) - # self.assertTrue(self.mplc.get_is_seller("76561198253325712")) - # self.assertFalse(self.mplc.get_is_banned("76561198253325712")) - # self.assertEqual(self.mplc.get_seller_id("76561198253325712"), 195002) - # self.assertTrue(self.mplc.get_is_banned("76561198115857578")) - # self.assertFalse(self.mplc.get_is_seller("76561198115857578")) +# def test_no_api_key(self): +# self.mplc.api_key = "" +# endpoints = self.mplc.get_endpoints() - # def test_get_dashboard_items(self): - # dashboard = self.mplc.get_dashboard_items() +# self.assertTrue("endpoints" in endpoints) +# self.assertRaises(NoAPIKey, self.mplc.get_bots) +# self.assertRaises(NoAPIKey, self.mplc.get_is_banned("76561198253325712")) +# self.mplc.api_key = MARKETPLACE_TF_API_KEY - # self.assertTrue("items" in dashboard) - # self.assertTrue(dashboard["success"]) +# def test_get_bots(self): +# self.assertTrue(self.mplc.get_bots()["success"]) - # def test_get_sales(self): - # sales = self.mplc.get_sales() +# def test_get_bans(self): +# self.assertEqual("confern", self.mplc.get_name("76561198253325712")) +# self.assertTrue(self.mplc.get_is_seller("76561198253325712")) +# self.assertFalse(self.mplc.get_is_banned("76561198253325712")) +# self.assertEqual(self.mplc.get_seller_id("76561198253325712"), 195002) - # self.assertTrue("sales" in sales) - # self.assertTrue(sales["success"]) +# self.assertTrue(self.mplc.get_is_banned("76561198115857578")) +# self.assertFalse(self.mplc.get_is_seller("76561198115857578")) - # sales = self.mplc.get_sales(number=1) - # self.assertTrue(len(sales["sales"]) == 1) +# def test_get_dashboard_items(self): +# dashboard = self.mplc.get_dashboard_items() - # sales = self.mplc.get_sales(start_before=0) - # self.assertEqual(sales, {}) +# self.assertTrue("items" in dashboard) +# self.assertTrue(dashboard["success"]) - def test_key_data(self): - price = self.mplc.fetch_item("5021;6") +# def test_get_sales(self): +# sales = self.mplc.get_sales() - self.assertEqual(self.mplc.get_item_data(), price) - self.assertGreater(self.mplc.get_stock(), 1) - self.assertGreater(self.mplc.get_price(), 1.4) - self.assertLess(self.mplc.get_highest_buy_order(), 2.5) - self.assertEqual(self.mplc.get_lowest_price(), self.mplc.get_price()) - self.assertEqual(self.mplc.get_sku(), "5021;6") +# self.assertTrue("sales" in sales) +# self.assertTrue(sales["success"]) + +# sales = self.mplc.get_sales(number=1) +# self.assertTrue(len(sales["sales"]) == 1) + +# sales = self.mplc.get_sales(start_before=0) +# self.assertEqual(sales, {}) diff --git a/tests/test_offer.py b/tests/test_offer.py index ccf564c..a0479a8 100644 --- a/tests/test_offer.py +++ b/tests/test_offer.py @@ -1,25 +1,24 @@ -from src.tf2_utils.utils import read_json_file from src.tf2_utils import Offer - -from unittest import TestCase +from src.tf2_utils.utils import read_json_file OFFER = read_json_file("./tests/json/offer.json") offer = Offer(OFFER) -class TestUtils(TestCase): - def test_offer_state(self): - self.assertFalse(offer.is_active()) - self.assertFalse(offer.is_declined()) - self.assertTrue(offer.is_accepted()) +def test_offer_state() -> None: + assert not offer.is_active() + assert not offer.is_declined() + assert offer.is_accepted() + + +def test_offer_sides() -> None: + assert not offer.is_our_offer() + assert not offer.is_scam() + assert not offer.is_gift() + assert offer.is_two_sided() - def test_offer_sides(self): - self.assertFalse(offer.is_our_offer()) - self.assertFalse(offer.is_scam()) - self.assertFalse(offer.is_gift()) - self.assertTrue(offer.is_two_sided()) - def test_offer_partner(self): - self.assertFalse(offer.has_trade_hold()) - self.assertEqual("76561198253325712", offer.get_partner()) +def test_offer_partner() -> None: + assert not offer.has_trade_hold() + assert "76561198253325712" == offer.get_partner() diff --git a/tests/test_schema.py b/tests/test_schema.py index 7d14810..4e15b4b 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -1,102 +1,107 @@ from src.tf2_utils import SchemaItemsUtils -from unittest import TestCase +schema_items = SchemaItemsUtils() # uses local files by default -schema_items = SchemaItemsUtils() # uses local files by default +def test_map_defindex_names() -> None: + response = schema_items.map_defindex_name() + assert {} != response + + +def test_name_to_sku_tod() -> None: + sku = schema_items.name_to_sku("Uncraftable Tour of Duty Ticket") + assert "725;6;uncraftable" == sku + + +def test_name_to_sku_key() -> None: + sku = schema_items.name_to_sku("Mann Co. Supply Crate Key") + assert "5021;6" == sku + + +def test_name_to_sku_name_tag() -> None: + sku = schema_items.name_to_sku("Name Tag") + assert "5020;6" == sku + + +def test_name_to_sku_pure() -> None: + ref = schema_items.name_to_sku("Refined Metal") + rec = schema_items.name_to_sku("Reclaimed Metal") + scrap = schema_items.name_to_sku("Scrap Metal") + + assert "5002;6" == ref + assert "5001;6" == rec + assert "5000;6" == scrap + + +def test_name_to_sku_non_existing() -> None: + # this item does not exist + item_name = "Non-Craftable Strange Team Captain" + sku = schema_items.name_to_sku(item_name) + defindex = schema_items.name_to_defindex(item_name) + + assert "378;11;uncraftable" == sku + assert -1 == defindex + + +def test_name_to_sku_qualities() -> None: + unique = schema_items.name_to_sku("Team Captain") + genuine = schema_items.name_to_sku("Genuine Team Captain") + haunted = schema_items.name_to_sku("Haunted Team Captain") + strange = schema_items.name_to_sku("Strange Team Captain") + vintage = schema_items.name_to_sku("Vintage Team Captain") + collectors = schema_items.name_to_sku("Collector's Team Captain") + + assert "378;1" == genuine + assert "378;3" == vintage + assert "378;6" == unique + assert "378;11" == strange + assert "378;13" == haunted + assert "378;14" == collectors + + +def test_defindex_to_name() -> None: + assert "Tour of Duty Ticket" == schema_items.defindex_to_name(725) + assert "Mann Co. Supply Crate Key" == schema_items.defindex_to_name(5021) + assert "Random Craft Hat" == schema_items.defindex_to_name(-100) + assert "Random Craft Weapon" == schema_items.defindex_to_name(-50) + assert "Scrap Metal" == schema_items.defindex_to_name(5000) + assert "Reclaimed Metal" == schema_items.defindex_to_name(5001) + assert "Refined Metal" == schema_items.defindex_to_name(5002) + + +def test_sku_to_base_name_tod() -> None: + name = schema_items.sku_to_base_name("725;6;uncraftable") + assert "Tour of Duty Ticket" == name + + +def test_sku_to_name_tod() -> None: + name = schema_items.sku_to_name("725;6;uncraftable") + assert "Uncraftable Tour of Duty Ticket" == name + + +def test_sku_to_non_craftable_name() -> None: + name = schema_items.sku_to_name("725;6;uncraftable", use_uncraftable=False) + assert "Non-Craftable Tour of Duty Ticket" == name + + +def test_sku_to_name_key() -> None: + name = schema_items.sku_to_name("5021;6") + assert "Mann Co. Supply Crate Key" == name + + +def test_team_captain() -> None: + # craftable + assert schema_items.sku_to_base_name("378;6"), "Team Captain" + assert schema_items.sku_to_full_name("378;6"), "The Team Captain" + assert schema_items.sku_to_name("378;6"), "Team Captain" + # uncraftable + assert schema_items.sku_to_name("378;6;uncraftable"), "Uncraftable Team Captain" + # strange + assert schema_items.sku_to_base_name("378;11"), "Team Captain" + assert schema_items.sku_to_name("378;11"), "Strange Team Captain" -class TestUtils(TestCase): - def test_map_defindex_names(self): - response = schema_items.map_defindex_name() - self.assertNotEqual({}, response) - - def test_name_to_sku_tod(self): - sku = schema_items.name_to_sku("Uncraftable Tour of Duty Ticket") - self.assertEqual("725;6;uncraftable", sku) - - def test_name_to_sku_key(self): - sku = schema_items.name_to_sku("Mann Co. Supply Crate Key") - self.assertEqual("5021;6", sku) - - def test_name_to_sku_name_tag(self): - sku = schema_items.name_to_sku("Name Tag") - self.assertEqual("5020;6", sku) - - def test_name_to_sku_pure(self): - ref = schema_items.name_to_sku("Refined Metal") - rec = schema_items.name_to_sku("Reclaimed Metal") - scrap = schema_items.name_to_sku("Scrap Metal") - - self.assertEqual("5002;6", ref) - self.assertEqual("5001;6", rec) - self.assertEqual("5000;6", scrap) - - def test_name_to_sku_non_existing(self): - # this item does not exist - item_name = "Non-Craftable Strange Team Captain" - sku = schema_items.name_to_sku(item_name) - defindex = schema_items.name_to_defindex(item_name) - - self.assertEqual("378;11;uncraftable", sku) - self.assertEqual(-1, defindex) - - def test_name_to_sku_qualities(self): - unique = schema_items.name_to_sku("Team Captain") - genuine = schema_items.name_to_sku("Genuine Team Captain") - haunted = schema_items.name_to_sku("Haunted Team Captain") - strange = schema_items.name_to_sku("Strange Team Captain") - vintage = schema_items.name_to_sku("Vintage Team Captain") - collectors = schema_items.name_to_sku("Collector's Team Captain") - - self.assertEqual("378;1", genuine) - self.assertEqual("378;3", vintage) - self.assertEqual("378;6", unique) - self.assertEqual("378;11", strange) - self.assertEqual("378;13", haunted) - self.assertEqual("378;14", collectors) - - def test_defindex_to_name(self): - self.assertEqual("Tour of Duty Ticket", schema_items.defindex_to_name(725)) - self.assertEqual( - "Mann Co. Supply Crate Key", schema_items.defindex_to_name(5021) - ) - self.assertEqual("Random Craft Hat", schema_items.defindex_to_name(-100)) - self.assertEqual("Random Craft Weapon", schema_items.defindex_to_name(-50)) - self.assertEqual("Scrap Metal", schema_items.defindex_to_name(5000)) - self.assertEqual("Reclaimed Metal", schema_items.defindex_to_name(5001)) - self.assertEqual("Refined Metal", schema_items.defindex_to_name(5002)) - - def test_sku_to_base_name_tod(self): - name = schema_items.sku_to_base_name("725;6;uncraftable") - self.assertEqual("Tour of Duty Ticket", name) - - def test_sku_to_name_tod(self): - name = schema_items.sku_to_name("725;6;uncraftable") - self.assertEqual("Uncraftable Tour of Duty Ticket", name) - - def test_sku_to_non_craftable_name(self): - name = schema_items.sku_to_name("725;6;uncraftable", use_uncraftable=False) - self.assertEqual("Non-Craftable Tour of Duty Ticket", name) - - def test_sku_to_name_key(self): - name = schema_items.sku_to_name("5021;6") - self.assertEqual("Mann Co. Supply Crate Key", name) - - def test_team_captain(self): - # craftable - self.assertEqual(schema_items.sku_to_base_name("378;6"), "Team Captain") - self.assertEqual(schema_items.sku_to_full_name("378;6"), "The Team Captain") - self.assertEqual(schema_items.sku_to_name("378;6"), "Team Captain") - # uncraftable - self.assertEqual( - schema_items.sku_to_name("378;6;uncraftable"), "Uncraftable Team Captain" - ) - # strange - self.assertEqual(schema_items.sku_to_base_name("378;11"), "Team Captain") - self.assertEqual(schema_items.sku_to_name("378;11"), "Strange Team Captain") - - def test_image_equal(self): - ellis_cap = schema_items.defindex_to_image_url(263) - random_craft_hat = schema_items.sku_to_image_url("-100;6") - self.assertEqual(ellis_cap, random_craft_hat) +def test_image_equal() -> None: + ellis_cap = schema_items.defindex_to_image_url(263) + random_craft_hat = schema_items.sku_to_image_url("-100;6") + assert ellis_cap, random_craft_hat diff --git a/tests/test_sku.py b/tests/test_sku.py index a7f82af..7308d98 100644 --- a/tests/test_sku.py +++ b/tests/test_sku.py @@ -1,20 +1,18 @@ -from src.tf2_utils.utils import read_json_file +import pytest + from src.tf2_utils import ( + get_metal, get_sku, get_sku_properties, - sku_to_defindex, - sku_to_quality, - sku_is_uncraftable, + is_metal, + is_pure, is_sku, + sku_is_uncraftable, sku_to_color, - is_pure, - is_metal, - get_metal, + sku_to_defindex, + sku_to_quality, ) - -# TODO:add tests for the rest of the functions - -from unittest import TestCase +from src.tf2_utils.utils import read_json_file file_path = "./tests/json/{}.json" @@ -29,80 +27,90 @@ def get_item_dict(file_name: str) -> dict: ELLIS_CAP = get_item_dict("ellis_cap") -class TestUtils(TestCase): - def test_ellis_cap_sku(self): - sku = get_sku(ELLIS_CAP) - - # https://marketplace.tf/items/tf2/263;6 - self.assertEqual("263;6", sku) - - def test_ellis_cap_sku_properties(self): - sku = get_sku_properties(ELLIS_CAP) - - self.assertEqual( - { - "defindex": 263, - "quality": 6, - "australium": False, - "craftable": True, - "wear": -1, - "killstreak_tier": -1, - "festivized": False, - "strange": False, - }, - sku, - ) - - def test_crusaders_crossbow_sku(self): - sku = get_sku(CRUSADERS_CROSSBOW) - - # https://marketplace.tf/items/tf2/305;11;kt-3;festive - self.assertEqual("305;11;kt-3;festive", sku) - - def test_strange_unusual_hong_kong_cone(self): - sku = get_sku(HONG_KONG_CONE) - - # https://marketplace.tf/items/tf2/30177;5;u107;strange - self.assertEqual("30177;5;u107;strange", sku) - - def test_uncraftable_hat(self): - sku = get_sku(UNCRAFTABLE_HAT) - - # https://marketplace.tf/items/tf2/734;6;uncraftable - self.assertEqual("734;6;uncraftable", sku) - - def test_properties(self): - sku = "734;6;uncraftable" - - self.assertEqual(734, sku_to_defindex(sku)) - self.assertEqual(6, sku_to_quality(sku)) - self.assertTrue(sku_is_uncraftable(sku)) - - def test_is_sku(self): - self.assertTrue(is_sku("734;6;uncraftable")) - self.assertTrue(is_sku("something;text")) - self.assertFalse(is_sku("734")) - - def test_is_metal(self): - self.assertTrue(is_metal("5000;6")) - self.assertTrue(is_metal("5001;6")) - self.assertTrue(is_metal("5002;6")) - self.assertFalse(is_metal("5021;6")) - - def test_is_pure(self): - self.assertTrue(is_pure("5000;6")) - self.assertTrue(is_pure("5001;6")) - self.assertTrue(is_pure("5002;6")) - self.assertTrue(is_pure("5021;6")) - self.assertFalse(is_pure("5021;7")) - - def test_get_metal(self): - self.assertEqual(9, get_metal("5002;6")) - self.assertEqual(3, get_metal("5001;6")) - self.assertEqual(1, get_metal("5000;6")) - self.assertRaises(AssertionError, get_metal, "5021;6") - - def test_sku_to_color(self): - self.assertEqual("7D6D00", sku_to_color("734;6;uncraftable")) - self.assertEqual("4D7455", sku_to_color("30469;1")) - self.assertRaises(AssertionError, sku_to_color, "notsku") +def test_ellis_cap_sku() -> None: + sku = get_sku(ELLIS_CAP) + + # https://marketplace.tf/items/tf2/263;6 + assert "263;6" == sku + + +def test_ellis_cap_sku_properties() -> None: + sku = get_sku_properties(ELLIS_CAP) + + assert { + "defindex": 263, + "quality": 6, + "australium": False, + "craftable": True, + "wear": -1, + "killstreak_tier": -1, + "festivized": False, + "strange": False, + } == sku + + +def test_crusaders_crossbow_sku() -> None: + sku = get_sku(CRUSADERS_CROSSBOW) + + # https://marketplace.tf/items/tf2/305;11;kt-3;festive + assert "305;11;kt-3;festive" == sku + + +def test_strange_unusual_hong_kong_cone() -> None: + sku = get_sku(HONG_KONG_CONE) + + # https://marketplace.tf/items/tf2/30177;5;u107;strange + assert "30177;5;u107;strange" == sku + + +def test_uncraftable_hat() -> None: + sku = get_sku(UNCRAFTABLE_HAT) + + # https://marketplace.tf/items/tf2/734;6;uncraftable + assert "734;6;uncraftable" == sku + + +def test_properties() -> None: + sku = "734;6;uncraftable" + + assert 734, sku_to_defindex(sku) + assert 6, sku_to_quality(sku) + assert sku_is_uncraftable(sku) + + +def test_is_sku() -> None: + assert is_sku("734;6;uncraftable") + assert is_sku("something;text") + assert not is_sku("734") + + +def test_is_metal() -> None: + assert is_metal("5000;6") + assert is_metal("5001;6") + assert is_metal("5002;6") + assert not is_metal("5021;6") + + +def test_is_pure() -> None: + assert is_pure("5000;6") + assert is_pure("5001;6") + assert is_pure("5002;6") + assert is_pure("5021;6") + assert not is_pure("5021;7") + + +def test_get_metal() -> None: + assert 9, get_metal("5002;6") + assert 3, get_metal("5001;6") + assert 1, get_metal("5000;6") + + with pytest.raises(AssertionError): + get_metal("5021;6") + + +def test_sku_to_color() -> None: + assert "7D6D00" == sku_to_color("734;6;uncraftable") + assert "4D7455" == sku_to_color("30469;1") + + with pytest.raises(AssertionError): + sku_to_color("notsku") diff --git a/tests/test_utils.py b/tests/test_utils.py index 00726b2..e891ae9 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,54 +1,58 @@ from src.tf2_utils import ( account_id_to_steam_id, - steam_id_to_account_id, - to_scrap, - to_refined, - refinedify, get_account_id_from_trade_url, get_steam_id_from_trade_url, get_token_from_trade_url, + refinedify, + steam_id_to_account_id, + to_refined, + to_scrap, ) -from unittest import TestCase - STEAM_ID = "76561198253325712" ACCOUNT_ID = "293059984" -class TestUtils(TestCase): - def test_steam_id(self): - self.assertEqual(STEAM_ID, account_id_to_steam_id(ACCOUNT_ID)) - self.assertEqual(ACCOUNT_ID, steam_id_to_account_id(STEAM_ID)) - self.assertEqual(STEAM_ID, account_id_to_steam_id(int(ACCOUNT_ID))) - self.assertEqual(ACCOUNT_ID, steam_id_to_account_id(int(STEAM_ID))) +def test_steam_id() -> None: + assert STEAM_ID == account_id_to_steam_id(ACCOUNT_ID) + assert ACCOUNT_ID == steam_id_to_account_id(STEAM_ID) + assert STEAM_ID == account_id_to_steam_id(int(ACCOUNT_ID)) + assert ACCOUNT_ID == steam_id_to_account_id(int(STEAM_ID)) + + +def test_to_refined() -> None: + scrap = 43 + refined = to_refined(scrap) + + assert 4.77 == refined + + +def test_to_scrap() -> None: + refined = 2.44 + scrap = to_scrap(refined) - def test_to_refined(self): - scrap = 43 - refined = to_refined(scrap) + assert 22 == scrap - self.assertEqual(4.77, refined) - def test_to_scrap(self): - refined = 2.44 - scrap = to_scrap(refined) +def test_refinedify_up() -> None: + wrong_value = 32.53 + refined = refinedify(wrong_value) - self.assertEqual(22, scrap) + assert 32.55 == refined - def test_refinedify_up(self): - wrong_value = 32.53 - refined = refinedify(wrong_value) - self.assertEqual(32.55, refined) +def test_refinedify_down() -> None: + wrong_value = 12.47 + refined = refinedify(wrong_value) - def test_refinedify_down(self): - wrong_value = 12.47 - refined = refinedify(wrong_value) + assert 12.44 == refined - self.assertEqual(12.44, refined) - def test_trade_url(self): - trade_url = "https://steamcommunity.com/tradeoffer/new/?partner=293059984&token=0-l_idZR" # noqa +def test_trade_url() -> None: + trade_url = ( + "https://steamcommunity.com/tradeoffer/new/?partner=293059984&token=0-l_idZR" # noqa + ) - self.assertEqual(ACCOUNT_ID, get_account_id_from_trade_url(trade_url)) - self.assertEqual(STEAM_ID, get_steam_id_from_trade_url(trade_url)) - self.assertEqual("0-l_idZR", get_token_from_trade_url(trade_url)) + assert ACCOUNT_ID == get_account_id_from_trade_url(trade_url) + assert STEAM_ID == get_steam_id_from_trade_url(trade_url) + assert "0-l_idZR" == get_token_from_trade_url(trade_url) From 05f0ac2a06c500cd6707a27693dbb17c306baabf Mon Sep 17 00:00:00 2001 From: offish Date: Wed, 26 Feb 2025 14:41:39 +0100 Subject: [PATCH 2/4] move backpacktf to own package --- docs/index.rst | 1 - docs/pages/backpack_tf.rst | 12 -- docs/pages/sockets.rst | 6 - docs/usage/backpack_tf.py | 33 ----- docs/usage/backpack_tf_socket.py | 16 --- docs/usage/prices_tf_socket.py | 4 +- pyproject.toml | 2 +- src/tf2_utils/__init__.py | 3 +- src/tf2_utils/backpack_tf.py | 203 ------------------------------- src/tf2_utils/sockets.py | 50 +------- tests/test_backpack_tf.py | 71 ----------- 11 files changed, 5 insertions(+), 396 deletions(-) delete mode 100644 docs/pages/backpack_tf.rst delete mode 100644 docs/usage/backpack_tf.py delete mode 100644 docs/usage/backpack_tf_socket.py delete mode 100644 src/tf2_utils/backpack_tf.py delete mode 100644 tests/test_backpack_tf.py diff --git a/docs/index.rst b/docs/index.rst index f768b7a..f71a4df 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -7,7 +7,6 @@ Table of Contents :maxdepth: 2 - pages/backpack_tf pages/currency pages/inventory pages/item diff --git a/docs/pages/backpack_tf.rst b/docs/pages/backpack_tf.rst deleted file mode 100644 index f76de75..0000000 --- a/docs/pages/backpack_tf.rst +++ /dev/null @@ -1,12 +0,0 @@ -BackpackTF -========== - -Usage ------ - -.. literalinclude:: ../usage/backpack_tf.py - :language: python3 - -.. automodule:: src.tf2_utils.backpack_tf - :members: - :undoc-members: diff --git a/docs/pages/sockets.rst b/docs/pages/sockets.rst index 3b9340d..7324e79 100644 --- a/docs/pages/sockets.rst +++ b/docs/pages/sockets.rst @@ -1,12 +1,6 @@ Sockets ======= -BackpackTF Example ------------------- - -.. literalinclude:: ../usage/backpack_tf_socket.py - :language: python3 - PricesTF Example ---------------- diff --git a/docs/usage/backpack_tf.py b/docs/usage/backpack_tf.py deleted file mode 100644 index 502a7ad..0000000 --- a/docs/usage/backpack_tf.py +++ /dev/null @@ -1,33 +0,0 @@ -from tf2_utils import BackpackTF - -bptf = BackpackTF( - "token", - "steam_id", - "api key not needed as of now", - "superbot5000's user agent message", -) - -# will add the lightning icon and indicate that the user is a bot -bptf.register_user_agent() - -listing = bptf.create_listing( - "5021;6", "buy", {"metal": 62.11}, "buying keys for listed price :)" -) - -print(listing) - -asset_id = 11543535227 -listing = bptf.create_listing( - "30745;6", - "sell", - {"keys": 1, "metal": 2.11}, - "selling my Siberian Sweater as i dont want it anymore", - 11543535227, -) - -print(listing) - -bptf.delete_listing_by_asset_id(11543535227) # sell -bptf.delete_listing_by_sku("5021;6") # buy -# or -bptf.delete_all_listings() diff --git a/docs/usage/backpack_tf_socket.py b/docs/usage/backpack_tf_socket.py deleted file mode 100644 index bf2f695..0000000 --- a/docs/usage/backpack_tf_socket.py +++ /dev/null @@ -1,16 +0,0 @@ -from tf2_utils import BackpackTFSocket - - -def my_function(data: list[dict]): - print("got listings") - - for listing in data: - print("listing", listing) - - # your logic here - - -socket = BackpackTFSocket(my_function, solo_entries=False) -# if solo_entries is True, you'll get a single dict instead of a list of dicts - -socket.listen() diff --git a/docs/usage/prices_tf_socket.py b/docs/usage/prices_tf_socket.py index dcac7a0..49ed86c 100644 --- a/docs/usage/prices_tf_socket.py +++ b/docs/usage/prices_tf_socket.py @@ -1,4 +1,4 @@ -from tf2_utils import PricesTFSocket +from tf2_utils import PricesTFWebsocket def my_function(data: dict): @@ -10,7 +10,7 @@ def my_function(data: dict): # etc. your logic goes here -socket = PricesTFSocket(my_function) +socket = PricesTFWebsocket(my_function) socket.listen() diff --git a/pyproject.toml b/pyproject.toml index cddfbdc..e26245b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ classifiers = [ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ] -dependencies = ["tf2-sku", "tf2-data", "requests", "websocket-client"] +dependencies = ["tf2-sku", "tf2-data", "backpack-tf", "requests", "websocket-client"] dynamic = ["version"] [project.urls] diff --git a/src/tf2_utils/__init__.py b/src/tf2_utils/__init__.py index c629e00..4c7ccab 100644 --- a/src/tf2_utils/__init__.py +++ b/src/tf2_utils/__init__.py @@ -9,9 +9,8 @@ from .offer import Offer from .utils import * from .schema import SchemaItemsUtils -from .sockets import BackpackTFSocket, PricesTFSocket +from .sockets import PricesTFWebsocket from .prices_tf import * from .inventory import Inventory, map_inventory from .currency import CurrencyExchange from .marketplace_tf import * -from .backpack_tf import * diff --git a/src/tf2_utils/backpack_tf.py b/src/tf2_utils/backpack_tf.py deleted file mode 100644 index e1e13b8..0000000 --- a/src/tf2_utils/backpack_tf.py +++ /dev/null @@ -1,203 +0,0 @@ -from dataclasses import dataclass, field -from hashlib import md5 - -import requests - -from . import __title__ -from .schema import SchemaItemsUtils -from .sku import sku_is_craftable, sku_to_quality - -__all__ = [ - "Currencies", - "Enity", - # "ItemResolvable", - "ItemDocument", - "Listing", - "BackpackTF", -] - - -@dataclass -class Currencies: - keys: int = 0 - metal: float = 0.0 - - -@dataclass -class Enity: - name: str = "" - id: int = 0 - color: str = "" - - -@dataclass -class ItemDocument: - appid: int - baseName: str - defindex: int - id: str - imageUrl: str - marketName: str - name: str - # origin:None - originalId: str - price: dict - quality: Enity - summary: str - # class:list - slot: str - tradable: bool - craftable: bool - - -@dataclass -class ItemResolvable: - baseName: str - craftable: bool - quality: Enity - tradable: bool = True - - -@dataclass -class Listing: - id: str - steamid: str - appid: int - currencies: Currencies - value: dict - details: str - listedAt: int - bumpedAt: int - intent: str - count: int - status: str - source: str - item: ItemDocument - user: dict - userAgent: dict = field(default_factory=dict) - tradeOffersPreferred: bool = None - buyoutOnly: bool = None - - -class BackpackTFException(Exception): - pass - - -class NoTokenProvided(BackpackTFException): - pass - - -class NeedsAPIKey(BackpackTFException): - pass - - -def needs_token(func): - def wrapper(self, *args, **kwargs): - if not self.token: - raise NoTokenProvided("Set a token to use this method") - - return func(self, *args, **kwargs) - - return wrapper - - -class BackpackTF: - URL = "https://api.backpack.tf/api" - - def __init__( - self, token: str, steam_id: str, api_key: str = "", user_agent="listed with <3" - ) -> None: - self.token = token - self.steam_id = steam_id - self.api_key = api_key - self.user_agent = user_agent - self.user_token = None - self.schema = SchemaItemsUtils() - - self.__headers = {"User-Agent": f"{__title__} | {self.user_agent}"} - - @needs_token - def request(self, method: str, endpoint: str, params: dict = {}, **kwargs) -> dict: - params["token"] = self.token - response = requests.request( - method, self.URL + endpoint, params=params, headers=self.__headers, **kwargs - ) - return response.json() - - def _construct_listing_item(self, sku: str) -> dict: - return { - "baseName": self.schema.sku_to_base_name(sku), - "craftable": sku_is_craftable(sku), - "tradable": True, - "quality": {"id": sku_to_quality(sku)}, - } - - def _construct_listing( - self, sku: str, intent: str, currencies: dict, details: str, asset_id: int = 0 - ) -> dict: - if intent not in ["buy", "sell"]: - raise BackpackTFException(f"Invalid intent {intent} must be buy or sell") - - listing = { - "item": self._construct_listing_item(sku), - "buyout": True, - "offers": True, - "promoted": False, - "details": details, - "currencies": Currencies(**currencies).__dict__, - } - - if intent == "sell": - listing["id"] = asset_id - - return listing - - def get_listings(self, skip: int = 0, limit: int = 100) -> dict: - return self.request( - "GET", "/v2/classifieds/listings", {"skip": skip, "limit": limit} - ) - - def create_listing( - self, sku: str, intent: str, currencies: dict, details: str, asset_id: int = 0 - ) -> Listing: - listing = self._construct_listing(sku, intent, currencies, details, asset_id) - response = self.request("POST", "/v2/classifieds/listings", json=listing) - - return Listing(**response) - - def create_listings(self, listings: list[dict]) -> list[Listing]: - listings = [self._construct_listing(**listing) for listing in listings] - response = self.request("POST", "/v2/classifieds/listings/batch", json=listings) - return [Listing(**listing["result"]) for listing in response] - - def delete_all_listings(self) -> dict: - return self.request("DELETE", "/v2/classifieds/listings") - - def delete_listing(self, listing_id: str) -> dict: - return self.request("DELETE", f"/v2/classifieds/listings/{listing_id}") - - @staticmethod - def _get_item_hash(item_name: str) -> str: - return md5(item_name.encode()).hexdigest() - - def _get_sku_item_hash(self, sku: str) -> str: - item_name = self.schema.sku_to_full_name(sku) - return self._get_item_hash(item_name) - - def delete_listing_by_asset_id(self, asset_id: int) -> dict: - listing_id = f"440_{asset_id}" - return self.delete_listing(listing_id) - - def delete_listing_by_sku(self, sku: str) -> dict: - item_hash = self._get_sku_item_hash(sku) - listing_id = f"440_{self.steam_id}_{item_hash}" - return self.delete_listing(listing_id) - - def register_user_agent(self) -> dict: - return self.request("POST", "/agent/pulse") - - def get_user_agent_status(self) -> dict: - return self.request("POST", "/agent/status") - - def stop_user_agent(self) -> dict: - return self.request("POST", "/agent/stop") diff --git a/src/tf2_utils/sockets.py b/src/tf2_utils/sockets.py index ee823ac..2a8b6c0 100644 --- a/src/tf2_utils/sockets.py +++ b/src/tf2_utils/sockets.py @@ -6,55 +6,7 @@ from .prices_tf import PricesTF -class BackpackTFSocket: - URL = "wss://ws.backpack.tf/events" - - def __init__( - self, - callback: Callable[[dict | list[dict]], None], - solo_entries: bool = True, - headers: dict = {"batch-test": True}, - max_size: int | None = None, - settings: dict = {}, - ) -> None: - """ - Args: - callback: Function pointer where you want the data to end up - solo_entries: If data to callback should be solo entries or a batched list - headers: Additional headers to send to the socket - settings: Additional websocket settings as a dict to be unpacked - """ - self.callback = callback - self.solo_entries = solo_entries - self.headers = headers - self.max_size = max_size - self.settings = settings - - def process_messages(self, data: str) -> None: - messages = json.loads(data) - - if not self.solo_entries: - self.callback(messages) - return - - for message in messages: - payload = message["payload"] - self.callback(payload) - - def listen(self) -> None: - """Listen for messages from BackpackTF""" - with connect( - self.URL, - additional_headers=self.headers, - max_size=self.max_size, - **self.settings, - ) as websocket: - while True: - data = websocket.recv() - self.process_messages(data) - - -class PricesTFSocket: +class PricesTFWebsocket: URL = "wss://ws.prices.tf" def __init__( diff --git a/tests/test_backpack_tf.py b/tests/test_backpack_tf.py deleted file mode 100644 index 7c41453..0000000 --- a/tests/test_backpack_tf.py +++ /dev/null @@ -1,71 +0,0 @@ -from src.tf2_utils import BackpackTF, Currencies - -bptf = None - - -def test_initiate_backpack_tf(backpack_tf_token: str) -> None: - global bptf - bptf = BackpackTF(backpack_tf_token, "76561198253325712") - - -def test_currencies() -> None: - assert Currencies(1, 1.5).__dict__ == {"keys": 1, "metal": 1.5} - assert Currencies().__dict__ == {"keys": 0, "metal": 0.0} - assert Currencies(**{"metal": 10.55}).__dict__ == {"keys": 0, "metal": 10.55} - - -def test_construct_listing_item() -> None: - assert bptf._construct_listing_item("263;6") == { - "baseName": "Ellis' Cap", - "craftable": True, - "quality": {"id": 6}, - "tradable": True, - } - - -def test_construct_listing() -> None: - assert bptf._construct_listing( - "263;6", - "sell", - {"keys": 1, "metal": 1.55}, - "my description", - 13201231975, - ) == { - "buyout": True, - "offers": True, - "promoted": False, - "item": { - "baseName": "Ellis' Cap", - "craftable": True, - "quality": {"id": 6}, - "tradable": True, - }, - "currencies": {"keys": 1, "metal": 1.55}, - "details": "my description", - "id": 13201231975, - } - - assert bptf._construct_listing( - "263;6", "buy", {"keys": 1, "metal": 1.55}, "my description" - ) == { - "buyout": True, - "offers": True, - "promoted": False, - "item": { - "baseName": "Ellis' Cap", - "craftable": True, - "quality": {"id": 6}, - "tradable": True, - }, - "currencies": {"keys": 1, "metal": 1.55}, - "details": "my description", - } - - -def test_hash_name() -> None: - assert bptf._get_sku_item_hash("5021;6") == "d9f847ff5dfcf78576a9fca04cbf6c07" - assert bptf._get_sku_item_hash("30397;6") == "8010db121d19610e9bcbac47432bd78c" - assert ( - bptf._get_item_hash("The Bruiser's Bandanna") - == "8010db121d19610e9bcbac47432bd78c" - ) From 7c19a79013a55cb526811366e6a5c4f13535a002 Mon Sep 17 00:00:00 2001 From: offish Date: Sat, 1 Mar 2025 14:02:15 +0100 Subject: [PATCH 3/4] small restructure --- .gitignore | 3 - LICENSE | 2 +- README.rst | 13 +- conftest.py | 12 +- docs/index.rst | 2 +- .../{sockets.rst => prices_tf_websocket.rst} | 2 +- example.env | 2 + pyproject.toml | 2 +- src/tf2_utils/__init__.py | 15 +- src/tf2_utils/currency.py | 20 +-- src/tf2_utils/exceptions.py | 37 +++++ src/tf2_utils/inventory.py | 4 + src/tf2_utils/marketplace_tf.py | 84 ++++------- src/tf2_utils/prices_tf.py | 142 ++++++++---------- .../{sockets.py => prices_tf_websocket.py} | 43 +++--- src/tf2_utils/schema.py | 33 +--- src/tf2_utils/sku.py | 25 ++- tests/test_inventory.py | 9 +- tests/test_marketplace_tf.py | 70 +++++---- 19 files changed, 258 insertions(+), 262 deletions(-) rename docs/pages/{sockets.rst => prices_tf_websocket.rst} (71%) create mode 100644 example.env create mode 100644 src/tf2_utils/exceptions.py rename src/tf2_utils/{sockets.py => prices_tf_websocket.py} (51%) diff --git a/.gitignore b/.gitignore index 0a0b60c..0a19790 100644 --- a/.gitignore +++ b/.gitignore @@ -172,6 +172,3 @@ cython_debug/ # PyPI configuration file .pypirc - -# Other -_build \ No newline at end of file diff --git a/LICENSE b/LICENSE index 6ea9941..ff5ca19 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2024 offish +Copyright (c) 2019-2025 offish Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.rst b/README.rst index 7c43ec0..9374907 100644 --- a/README.rst +++ b/README.rst @@ -4,13 +4,14 @@ tf2-utils ``tf2-utils`` is a collection of tools and utilities for TF2 trading. Use 3rd party inventory providers, SKUs formatting, interact with various APIs, websockets and more. -``tf2-utils`` is built on top of `tf2-sku `_ and `tf2-data `_. +``tf2-utils`` is built on top of `tf2-sku `_, `tf2-data `_ +and `backpack-tf `_. ``tf2-utils`` is a key dependency of `tf2-express `_. Donate ------ -- BTC: ``bc1qntlxs7v76j0zpgkwm62f6z0spsvyezhcmsp0z2`` +- BTC: ``bc1q9gmh5x2g9s0pw3282a5ypr6ms8qvuxh3fd7afh`` - `Steam Trade Offer `_ You can reach me at `Steam `_, @@ -54,9 +55,9 @@ Updating .. code-block:: bash - pip install --upgrade tf2-utils tf2-sku tf2-data + pip install --upgrade tf2-utils tf2-sku tf2-data bptf # or - python -m pip install --upgrade tf2-utils tf2-sku tf2-data + python -m pip install --upgrade tf2-utils tf2-sku tf2-data bptf Development @@ -67,7 +68,7 @@ Testing .. code-block:: bash # tf2-utils/ - python -m unittest + pytest Documentation ~~~~~~~~~~~~~ @@ -85,7 +86,7 @@ License ------- MIT License -Copyright (c) 2019-2024 offish (`confern `_) +Copyright (c) 2019-2025 offish (`confern `_) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/conftest.py b/conftest.py index d072e13..63b2ec9 100644 --- a/conftest.py +++ b/conftest.py @@ -1,11 +1,19 @@ +from os import getenv + import pytest +from dotenv import load_dotenv + +assert load_dotenv() + +MARKETPLACE_TF_API_KEY = getenv("MARKETPLACE_TF_API_KEY") +BACKPACK_TF_TOKEN = getenv("BACKPACK_TF_TOKEN") @pytest.fixture def marketplace_tf_api_key() -> str: - return "api_key" + return MARKETPLACE_TF_API_KEY @pytest.fixture def backpack_tf_token() -> str: - return "backpack_tf_token" + return BACKPACK_TF_TOKEN diff --git a/docs/index.rst b/docs/index.rst index f71a4df..584f3f5 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -13,9 +13,9 @@ Table of Contents pages/marketplace_tf pages/offer pages/prices_tf + pages/prices_tf_websocket pages/schema pages/sku - pages/sockets pages/utils diff --git a/docs/pages/sockets.rst b/docs/pages/prices_tf_websocket.rst similarity index 71% rename from docs/pages/sockets.rst rename to docs/pages/prices_tf_websocket.rst index 7324e79..40ac0ea 100644 --- a/docs/pages/sockets.rst +++ b/docs/pages/prices_tf_websocket.rst @@ -7,6 +7,6 @@ PricesTF Example .. literalinclude:: ../usage/prices_tf_socket.py :language: python3 -.. automodule:: src.tf2_utils.sockets +.. automodule:: src.tf2_utils.prices_tf_websocket :members: :undoc-members: diff --git a/example.env b/example.env new file mode 100644 index 0000000..e8806d4 --- /dev/null +++ b/example.env @@ -0,0 +1,2 @@ +MARKETPLACE_TF_API_KEY=apikey +BACKPACK_TF_TOKEN=token \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index e26245b..404fdd7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ classifiers = [ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ] -dependencies = ["tf2-sku", "tf2-data", "backpack-tf", "requests", "websocket-client"] +dependencies = ["tf2-sku", "tf2-data", "bptf", "requests", "websocket-client"] dynamic = ["version"] [project.urls] diff --git a/src/tf2_utils/__init__.py b/src/tf2_utils/__init__.py index 4c7ccab..d6709f2 100644 --- a/src/tf2_utils/__init__.py +++ b/src/tf2_utils/__init__.py @@ -4,13 +4,14 @@ __version__ = "2.3.0" __license__ = "MIT" -from .sku import * +from .currency import CurrencyExchange +from .exceptions import * +from .inventory import Inventory, map_inventory from .item import Item +from .marketplace_tf import * from .offer import Offer -from .utils import * +from .prices_tf import PricesTF +from .prices_tf_websocket import PricesTFWebsocket from .schema import SchemaItemsUtils -from .sockets import PricesTFWebsocket -from .prices_tf import * -from .inventory import Inventory, map_inventory -from .currency import CurrencyExchange -from .marketplace_tf import * +from .sku import * +from .utils import * diff --git a/src/tf2_utils/currency.py b/src/tf2_utils/currency.py index e9cc392..fcd396f 100644 --- a/src/tf2_utils/currency.py +++ b/src/tf2_utils/currency.py @@ -33,21 +33,17 @@ def __init__( self.our_combination = [] # list of metal names def get_pure_value(self, name: str) -> int: - match name: - # refined - case "Mann Co. Supply Crate Key": - return self.key_price + if name == "Mann Co. Supply Crate Key": + return self.key_price - case "Refined Metal": - return 9 + if name == "Refined Metal": + return 9 - # reclaimed - case "Reclaimed Metal": - return 3 + if name == "Reclaimed Metal": + return 3 - # scrap - case "Scrap Metal": - return 1 + if name == "Scrap Metal": + return 1 raise ValueError(f"{name} is not pure") diff --git a/src/tf2_utils/exceptions.py b/src/tf2_utils/exceptions.py new file mode 100644 index 0000000..3b15395 --- /dev/null +++ b/src/tf2_utils/exceptions.py @@ -0,0 +1,37 @@ +# generic +class TF2UtilsError(Exception): + pass + + +class InvalidInventory(TF2UtilsError): + pass + + +# pricestf +class PricesTFError(TF2UtilsError): + pass + + +class InternalServerError(PricesTFError): + pass + + +class RateLimited(PricesTFError): + pass + + +class EmptyResponse(PricesTFError): + pass + + +# marketplacetf +class MarketplaceTFException(TF2UtilsError): + pass + + +class SKUDoesNotMatch(MarketplaceTFException): + pass + + +class NoAPIKey(MarketplaceTFException): + pass diff --git a/src/tf2_utils/inventory.py b/src/tf2_utils/inventory.py index 6ed9db2..b136c1d 100644 --- a/src/tf2_utils/inventory.py +++ b/src/tf2_utils/inventory.py @@ -1,5 +1,6 @@ import requests +from .exceptions import InvalidInventory from .providers.custom import Custom from .providers.steamapis import SteamApis from .providers.steamcommunity import SteamCommunity @@ -12,6 +13,9 @@ def map_inventory(inventory: dict, add_skus: bool = False) -> list[dict]: adds `sku` to each item entry if `add_skus` is enabled""" mapped_inventory = [] + if "assets" not in inventory: + raise InvalidInventory("No assets found in inventory") + for asset in inventory["assets"]: for desc in inventory["descriptions"]: if ( diff --git a/src/tf2_utils/marketplace_tf.py b/src/tf2_utils/marketplace_tf.py index eef99b0..9799c86 100644 --- a/src/tf2_utils/marketplace_tf.py +++ b/src/tf2_utils/marketplace_tf.py @@ -2,27 +2,14 @@ import requests +from .exceptions import NoAPIKey, SKUDoesNotMatch from .schema import SchemaItemsUtils from .sku import sku_is_craftable, sku_to_quality_name -__all__ = ["MarketplaceTF", "MarketplaceTFException", "SKUDoesNotMatch", "NoAPIKey"] - - -class MarketplaceTFException(Exception): - pass - - -class SKUDoesNotMatch(MarketplaceTFException): - pass - - -class NoAPIKey(MarketplaceTFException): - pass - def api_key_required(func): def wrapper(self, *args, **kwargs): - if self.api_key == "": + if self._api_key is None: raise NoAPIKey("No API key provided") return func(self, *args, **kwargs) @@ -31,18 +18,20 @@ def wrapper(self, *args, **kwargs): class MarketplaceTF: - def __init__(self, api_key: str = ""): - self.api_key = api_key - self.schema = SchemaItemsUtils() - self.data = {} + 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 = {}): + def _get_request(self, endpoint: str, params: dict = {}) -> dict: url = "https://marketplace.tf/api" + endpoint - if self.api_key: - params["key"] = self.api_key + 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: @@ -73,9 +62,9 @@ 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: float = 0.0) -> dict: - if start_before == 0.0: - start_before = time.time() + 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} @@ -89,23 +78,26 @@ def _format_url(item_name: str, quality: str, craftable: bool) -> str: 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) + item_name = self._schema.sku_to_base_name(sku) quality = sku_to_quality_name(sku) return self._format_url(item_name, quality, sku_is_craftable(sku)) @staticmethod - def _format_price_to_float(price: str) -> 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"] + 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 + return self._data def fetch_item(self, sku: str) -> dict: url = self._format_url_sku(sku) @@ -115,60 +107,44 @@ def fetch_item(self, sku: str) -> dict: if mptf_sku != sku: raise SKUDoesNotMatch(f"SKU {sku} does not match {mptf_sku}") - return self.data + return self._data def get_item_data(self) -> dict: - return self.data + return self._data def get_lowest_price(self) -> float: - price = self.data.get("lowest_price") - - if price is None: - return 0.0 - + 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") - - if price is None: - return 0.0 - + 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) + return self._data.get("num_for_sale", 0) def get_sku(self) -> str: - return self.data.get("sku", "") + return self._data.get("sku", "") def fetch_lowest_price(self, sku: str) -> float: - price = self.fetch_item_data(sku).get("lowest_price") - - if price is None: - return 0.0 - + 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) def fetch_highest_buy_order(self, sku: str) -> float: - price = self.fetch_item_data(sku).get("highest_buy_order") - - if price is None: - return 0.0 - + price = self.fetch_item(sku).get("highest_buy_order") return self._format_price_to_float(price) def fetch_buy_order(self, sku: str) -> float: return self.fetch_highest_buy_order(sku) def fetch_stock(self, sku: str) -> int: - return self.fetch_item_data(sku).get("num_for_sale", 0) + return self.fetch_item(sku).get("num_for_sale", 0) diff --git a/src/tf2_utils/prices_tf.py b/src/tf2_utils/prices_tf.py index 882dabf..6d64016 100644 --- a/src/tf2_utils/prices_tf.py +++ b/src/tf2_utils/prices_tf.py @@ -1,104 +1,65 @@ import time +from typing import Any import requests +from .exceptions import EmptyResponse, InternalServerError, PricesTFError, RateLimited from .utils import to_refined -__all__ = [ - "PricesTF", - "PricesTFError", - "RateLimited", - "EmptyResponse", - "InternalServerError", -] - - -class PricesTFError(Exception): - """General error""" - - -class InternalServerError(PricesTFError): - """Something went wrong""" - - -class RateLimited(PricesTFError): - """Rate limited""" - - -class EmptyResponse(PricesTFError): - """Response was empty""" - class PricesTF: URL = "https://api2.prices.tf" def __init__(self) -> None: - self.access_token = "" - self.header = {} + self._access_token = "" + self._headers = {} @staticmethod - def has_code(response, code: int) -> bool: - return response.get("statusCode") == code + def _format_price(data: dict) -> dict: + return { + "buy": { + "keys": data["buyKeys"], + "metal": to_refined(data["buyHalfScrap"] / 2), + }, + "sell": { + "keys": data["sellKeys"], + "metal": to_refined(data["sellHalfScrap"] / 2), + }, + } @staticmethod - def validate_response(response) -> None: + def _validate_response(response: dict[str, Any]) -> None: if not response: raise EmptyResponse("response from server was empty") - if PricesTF.has_code(response, 500): + status_code = response.get("statusCode") + + if status_code == 500: raise InternalServerError("there was an interal server error") - if PricesTF.has_code(response, 429): + if status_code == 429: raise RateLimited("currently ratelimited") - def __get(self, endpoint: str, params: dict = {}) -> dict: - response = requests.get(self.URL + endpoint, headers=self.header, params=params) + def _set_header(self, header: dict) -> None: + self._headers = header + def _get(self, endpoint: str, params: dict = {}) -> dict: + url = self.URL + endpoint + response = requests.get(url, headers=self._headers, params=params) res = response.json() + self._validate_response(res) - self.validate_response(res) return res - def __post(self, endpoint: str) -> tuple[dict, int]: - response = requests.post(self.URL + endpoint, headers=self.header) - + def _post(self, endpoint: str) -> tuple[dict, int]: + url = self.URL + endpoint + response = requests.post(url, headers=self._headers) res = response.json() + self._validate_response(res) - self.validate_response(res) return (res, response.status_code) - def __set_header(self, header: dict) -> None: - self.header = header - - def get_headers(self) -> dict: - return self.header - - def get_history( - self, sku: str, page: int = 1, limit: int = 100, order: str = "ASC" - ) -> dict: - return self.__get( - f"/history/{sku}", {"page": page, "limit": limit, "order": order} - ) - - def get_price(self, sku: str) -> dict: - return self.__get(f"/prices/{sku}") - - def get_prices(self, page: int, limit: int = 100, order: str = "DESC") -> dict: - return self.__get("/prices", {"page": page, "limit": limit, "order": order}) - - def format_price(self, data: dict) -> dict: - return { - "buy": { - "keys": data["buyKeys"], - "metal": to_refined(data["buyHalfScrap"] / 2), - }, - "sell": { - "keys": data["sellKeys"], - "metal": to_refined(data["sellHalfScrap"] / 2), - }, - } - - def get_prices_till_page( + def _get_prices_till_page( self, page_limit: int, print_rate_limit: bool = False ) -> dict: prices = {} @@ -122,7 +83,7 @@ def get_prices_till_page( raise PricesTFError("could not find any items in response") for item in response["items"]: - prices[item["sku"]] = self.format_price(item) + prices[item["sku"]] = self._format_price(item) current_page = response["meta"]["currentPage"] + 1 total_pages = response["meta"]["totalPages"] @@ -132,23 +93,46 @@ def get_prices_till_page( return prices + def get_history( + self, sku: str, page: int = 1, limit: int = 100, order: str = "ASC" + ) -> dict: + return self._get( + f"/history/{sku}", {"page": page, "limit": limit, "order": order} + ) + + def get_price(self, sku: str) -> dict: + return self._get(f"/prices/{sku}") + + def get_prices(self, page: int, limit: int = 100, order: str = "DESC") -> dict: + return self._get("/prices", {"page": page, "limit": limit, "order": order}) + def get_all_prices(self, print_rate_limit: bool = False) -> dict: - return self.get_prices_till_page(-1, print_rate_limit) + return self._get_prices_till_page(-1, print_rate_limit) def update_price(self, sku: str) -> tuple[dict, int]: - return self.__post(f"/prices/{sku}/refresh") + return self._post(f"/prices/{sku}/refresh") def request_access_token(self) -> None: - res, _ = self.__post("/auth/access") + res, _ = self._post("/auth/access") - self.validate_response(res) + self._validate_response(res) - access_token = res["accessToken"] - self.access_token = access_token + self._access_token = res["accessToken"] - self.__set_header( + self._set_header( { "accept": "application/json", - "Authorization": f"Bearer {self.access_token}", + "Authorization": f"Bearer {self._access_token}", } ) + + @property + def access_token(self) -> str: + if not self._access_token: + raise PricesTFError("Access token was never set!") + + return self._access_token + + @property + def headers(self) -> dict: + return self._headers diff --git a/src/tf2_utils/sockets.py b/src/tf2_utils/prices_tf_websocket.py similarity index 51% rename from src/tf2_utils/sockets.py rename to src/tf2_utils/prices_tf_websocket.py index 2a8b6c0..e4234b0 100644 --- a/src/tf2_utils/sockets.py +++ b/src/tf2_utils/prices_tf_websocket.py @@ -7,8 +7,6 @@ class PricesTFWebsocket: - URL = "wss://ws.prices.tf" - def __init__( self, callback: Callable[[dict], None], @@ -19,39 +17,38 @@ def __init__( callback: Function pointer where you want the data to end up settings: Additional websocket settings as a dict to be unpacked """ - self.callback = callback - self.prices_tf = PricesTF() - self.settings = settings + self._callback = callback + self._prices_tf = PricesTF() + self._settings = settings - def process_message(self, ws: ClientConnection, message: str) -> None: + def _process_message(self, ws: ClientConnection, message: str) -> None: data = json.loads(message) + if data.get("type") != "AUTH_REQUIRED": + self._callback(data) + return + # our auths are only valid for 10 minutes at a time # pricestf requests us to authenticate again - if data.get("type") == "AUTH_REQUIRED": - self.prices_tf.request_access_token() - ws.send( - json.dumps( - { - "type": "AUTH", - "data": {"accessToken": self.prices_tf.access_token}, - } - ) - ) - return + self._prices_tf.request_access_token() + + payload = { + "type": "AUTH", + "data": {"accessToken": self._prices_tf.access_token}, + } - self.callback(data) + ws.send(json.dumps(payload)) def listen(self) -> None: """Listen for messages from PricesTF.""" - self.prices_tf.request_access_token() - headers = self.prices_tf.get_headers() + self._prices_tf.request_access_token() + headers = self._prices_tf.headers with connect( - self.URL, + "wss://ws.prices.tf", additional_headers=headers, - **self.settings, + **self._settings, ) as websocket: while True: message = websocket.recv() - self.process_message(websocket, message) + self._process_message(websocket, message) diff --git a/src/tf2_utils/schema.py b/src/tf2_utils/schema.py index f2b0d88..6011b3f 100644 --- a/src/tf2_utils/schema.py +++ b/src/tf2_utils/schema.py @@ -1,4 +1,4 @@ -from tf2_data import EFFECTS, KILLSTREAKS, SchemaItems +from tf2_data import EFFECTS, KILLSTREAKS, QUALITIES, SchemaItems from tf2_sku import to_sku from .sku import ( @@ -51,17 +51,13 @@ def name_to_defindex(self, name: str) -> int: last_index = len(defindexes) - 1 - # could be first or last defindex - match name: - case "Mann Co. Supply Crate Key": - return defindexes[0] + if name == "Mann Co. Supply Crate Key": + return defindexes[0] - case "Name Tag": - return defindexes[last_index] + if name == "Name Tag": + return defindexes[last_index] - case _: - # use first defindex as default - return defindexes[0] + return defindexes[0] def defindex_to_image_url(self, defindex: int, large: bool = False) -> str: # random craft weapon => shotgun @@ -96,21 +92,8 @@ def name_to_sku(self, name: str) -> str: if part in ["Uncraftable", "Non-Craftable"]: craftable = False - match part: - case "Genuine": - quality = 1 - - case "Vintage": - quality = 3 - - case "Strange": - quality = 11 - - case "Haunted": - quality = 13 - - case "Collector's": - quality = 14 + if part in QUALITIES: + quality = QUALITIES[part] defindex_name = name diff --git a/src/tf2_utils/sku.py b/src/tf2_utils/sku.py index cf3b337..e69d702 100644 --- a/src/tf2_utils/sku.py +++ b/src/tf2_utils/sku.py @@ -43,7 +43,6 @@ def get_sku_properties(item: Item | dict) -> dict: "killstreak_tier": item.get_killstreak_id(), "festivized": item.is_festivized(), } - # TODO: add rest # "skin": "pk{}", # "target_defindex": "td-{}", # "craft_number": "n{}", @@ -76,18 +75,14 @@ def is_metal(sku: str) -> bool: def get_metal(sku: str) -> int: assert is_metal(sku), f"sku {sku} is not metal" - match sku: - # refined - case "5002;6": - return 9 + if sku == "5002;6": + return 9 - # reclaimed - case "5001;6": - return 3 + if sku == "5001;6": + return 3 - # scrap - case "5000;6": - return 1 + if sku == "5000;6": + return 1 def get_properties(sku: str) -> list[str]: @@ -100,15 +95,13 @@ def get_property(sku: str, index: int) -> str: return get_properties(sku)[index] -def get_property_by_key(sku: str, key: str) -> str: +def get_property_by_key(sku: str, key: str) -> str | None: for p in get_properties(sku): match = re.search(key + r"(\d+)", p) if match: return match.group(1) - return "" - def sku_to_defindex(sku: str) -> int: return int(get_property(sku, 0)) @@ -141,7 +134,7 @@ def strange_in_sku(sku: str) -> bool: def get_sku_killstreak(sku: str) -> int: value = get_property_by_key(sku, "kt-") - if not value: + if value is None: return -1 return int(value.replace("kt-", "")) @@ -150,7 +143,7 @@ def get_sku_killstreak(sku: str) -> int: def get_sku_effect(sku: str) -> int: value = get_property_by_key(sku, "u") - if not value: + if value is None: return -1 return int(value.replace("u", "")) diff --git a/tests/test_inventory.py b/tests/test_inventory.py index 8a12ca4..dff4347 100644 --- a/tests/test_inventory.py +++ b/tests/test_inventory.py @@ -1,4 +1,6 @@ -from src.tf2_utils import map_inventory +import pytest + +from src.tf2_utils import InvalidInventory, map_inventory from src.tf2_utils.utils import read_json_file INVENTORY = read_json_file("./tests/json/bot_inventory.json") @@ -10,3 +12,8 @@ def test_inventory() -> None: for item in inventory: assert "sku" in item + + +def test_raises_error() -> None: + with pytest.raises(InvalidInventory): + map_inventory({}, False) diff --git a/tests/test_marketplace_tf.py b/tests/test_marketplace_tf.py index 9bf41ee..0becb3a 100644 --- a/tests/test_marketplace_tf.py +++ b/tests/test_marketplace_tf.py @@ -3,9 +3,9 @@ mplc = None -def test_initiate_marketplace_tf(marketplace_tf_api_key: str) -> None: +def test_init() -> None: global mplc - mplc = MarketplaceTF(marketplace_tf_api_key) + mplc = MarketplaceTF() def test_key_data() -> None: @@ -19,41 +19,51 @@ def test_key_data() -> None: assert mplc.get_lowest_price() == mplc.get_price() -# def test_no_api_key(self): -# self.mplc.api_key = "" -# endpoints = self.mplc.get_endpoints() +# def test_no_api_key(marketplace_tf_api_key: str) -> None: +# global mplc +# mplc = MarketplaceTF() +# endpoints = mplc.get_endpoints() -# self.assertTrue("endpoints" in endpoints) -# self.assertRaises(NoAPIKey, self.mplc.get_bots) -# self.assertRaises(NoAPIKey, self.mplc.get_is_banned("76561198253325712")) -# self.mplc.api_key = MARKETPLACE_TF_API_KEY +# assert "endpoints" in endpoints -# def test_get_bots(self): -# self.assertTrue(self.mplc.get_bots()["success"]) +# with pytest.raises(NoAPIKey): +# mplc.get_bots() -# def test_get_bans(self): -# self.assertEqual("confern", self.mplc.get_name("76561198253325712")) -# self.assertTrue(self.mplc.get_is_seller("76561198253325712")) -# self.assertFalse(self.mplc.get_is_banned("76561198253325712")) -# self.assertEqual(self.mplc.get_seller_id("76561198253325712"), 195002) +# with pytest.raises(NoAPIKey): +# mplc.get_is_banned("76561198253325712") -# self.assertTrue(self.mplc.get_is_banned("76561198115857578")) -# self.assertFalse(self.mplc.get_is_seller("76561198115857578")) +# mplc._api_key = marketplace_tf_api_key -# def test_get_dashboard_items(self): -# dashboard = self.mplc.get_dashboard_items() -# self.assertTrue("items" in dashboard) -# self.assertTrue(dashboard["success"]) +# def test_get_bots() -> None: +# assert mplc.get_bots()["success"] -# def test_get_sales(self): -# sales = self.mplc.get_sales() -# self.assertTrue("sales" in sales) -# self.assertTrue(sales["success"]) +# def test_get_bans() -> None: +# assert "confern" == mplc.get_name("76561198253325712") +# assert mplc.get_is_seller("76561198253325712") +# assert not mplc.get_is_banned("76561198253325712") +# assert mplc.get_seller_id("76561198253325712") == 195002 -# sales = self.mplc.get_sales(number=1) -# self.assertTrue(len(sales["sales"]) == 1) +# assert mplc.get_is_banned("76561198115857578") +# assert not mplc.get_is_seller("76561198115857578") -# sales = self.mplc.get_sales(start_before=0) -# self.assertEqual(sales, {}) + +# def test_get_dashboard_items() -> None: +# dashboard = mplc.get_dashboard_items() + +# assert "items" in dashboard +# assert dashboard["success"] + + +# def test_get_sales() -> None: +# sales = mplc.get_sales() + +# assert "sales" in sales +# assert sales["success"] + +# sales = mplc.get_sales(number=1) +# assert len(sales["sales"]) == 1 + +# sales = mplc.get_sales(start_before=0) +# assert sales == {} From 0896afb595ee96c99bfa008b597fd6e2c307fb17 Mon Sep 17 00:00:00 2001 From: offish Date: Tue, 11 Mar 2025 14:28:09 +0100 Subject: [PATCH 4/4] minor changes --- docs/usage/currency.py | 3 +-- src/tf2_utils/inventory.py | 7 ++++++- src/tf2_utils/item.py | 2 +- src/tf2_utils/sku.py | 11 ++++++++--- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/docs/usage/currency.py b/docs/usage/currency.py index 42f57de..fd15263 100644 --- a/docs/usage/currency.py +++ b/docs/usage/currency.py @@ -1,6 +1,5 @@ from tf2_utils import CurrencyExchange, Inventory, map_inventory - inventory_provider = Inventory("steamcommunity") # Get our inventory of a user @@ -26,7 +25,7 @@ currency.calculate() -if not currency.is_possible(): +if not currency.is_possible: print("Trade is not possible") # either no combination worked, or someone did not have enough pure diff --git a/src/tf2_utils/inventory.py b/src/tf2_utils/inventory.py index b136c1d..0a44bc3 100644 --- a/src/tf2_utils/inventory.py +++ b/src/tf2_utils/inventory.py @@ -8,7 +8,9 @@ from .sku import get_sku -def map_inventory(inventory: dict, add_skus: bool = False) -> list[dict]: +def map_inventory( + inventory: dict, add_skus: bool = False, skip_untradable: bool = False +) -> list[dict]: """Matches classids and instanceids, merges these and adds `sku` to each item entry if `add_skus` is enabled""" mapped_inventory = [] @@ -18,6 +20,9 @@ def map_inventory(inventory: dict, add_skus: bool = False) -> list[dict]: for asset in inventory["assets"]: for desc in inventory["descriptions"]: + if skip_untradable and not desc["tradable"]: + continue + if ( asset["classid"] != desc["classid"] or asset["instanceid"] != desc["instanceid"] diff --git a/src/tf2_utils/item.py b/src/tf2_utils/item.py index a3dc246..b517b0b 100644 --- a/src/tf2_utils/item.py +++ b/src/tf2_utils/item.py @@ -9,7 +9,7 @@ def __init__(self, item: dict) -> None: self.tags = item.get("tags", []) def is_tf2(self) -> bool: - return self.item["appid"] == 440 + return int(self.item["appid"]) == 440 def is_tradable(self) -> bool: return self.item.get("tradable", 1) == 1 diff --git a/src/tf2_utils/sku.py b/src/tf2_utils/sku.py index e69d702..319a5eb 100644 --- a/src/tf2_utils/sku.py +++ b/src/tf2_utils/sku.py @@ -8,8 +8,9 @@ __all__ = [ "get_sku_properties", "is_sku", - "is_pure", + "is_key", "is_metal", + "is_pure", "get_metal", "get_properties", "get_property", @@ -64,14 +65,18 @@ def is_sku(item: str) -> bool: return item.find(";") != -1 -def is_pure(sku: str) -> bool: - return sku in ["5000;6", "5001;6", "5002;6", "5021;6"] +def is_key(sku: str) -> bool: + return sku == "5021;6" def is_metal(sku: str) -> bool: return sku in ["5000;6", "5001;6", "5002;6"] +def is_pure(sku: str) -> bool: + return is_metal(sku) or is_key(sku) + + def get_metal(sku: str) -> int: assert is_metal(sku), f"sku {sku} is not metal"