Skip to content

Commit 768b161

Browse files
committed
working listing and agent feature
1 parent 449e476 commit 768b161

File tree

4 files changed

+148
-81
lines changed

4 files changed

+148
-81
lines changed

src/tf2_utils/backpack_tf.py

Lines changed: 80 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import requests
22

3-
from dataclasses import dataclass, asdict
3+
from dataclasses import dataclass, field
4+
5+
from .schema import SchemaItemsUtils
6+
from .sku import sku_to_quality, sku_is_craftable
7+
from . import __title__
48

59

610
__all__ = [
711
"Currencies",
812
"Enity",
9-
"ItemResolvable",
13+
# "ItemResolvable",
1014
"ItemDocument",
11-
"ListingResolvable",
1215
"Listing",
1316
"BackpackTF",
1417
]
@@ -22,55 +25,58 @@ class Currencies:
2225

2326
@dataclass
2427
class Enity:
25-
name: str
26-
id: int
27-
color: str
28-
29-
30-
@dataclass
31-
class ItemResolvable:
32-
item: str
33-
quality: str | int
34-
tradable: bool
35-
craftable: str
36-
priceindex: str
28+
name: str = ""
29+
id: int = 0
30+
color: str = ""
3731

3832

3933
@dataclass
4034
class ItemDocument:
35+
appid: int
4136
baseName: str
42-
name: str
37+
defindex: int
38+
id: str
4339
imageUrl: str
44-
quantity: int
40+
marketName: str
41+
name: str
42+
# origin:None
43+
originalId: str
44+
price: dict
4545
quality: Enity
46-
rarity: Enity
47-
paint: Enity
48-
particle: Enity
49-
elevatedQuality: Enity
46+
summary: str
47+
# class:list
48+
slot: str
49+
tradable: bool
50+
craftable: bool
5051

5152

5253
@dataclass
53-
class ListingResolvable:
54-
id: int # asset_id if this is set, its a sell order
55-
item: dict
56-
details: str
57-
currencies: Currencies
54+
class ItemResolvable:
55+
baseName: str
56+
craftable: bool
57+
quality: Enity
58+
tradable: bool = True
5859

5960

6061
@dataclass
6162
class Listing:
6263
id: str
63-
appid: int
64-
bumpedAt: str
65-
listedAt: str
66-
details: str
67-
intent: str
6864
steamid: str
65+
appid: int
6966
currencies: Currencies
70-
promoted: bool
67+
value: dict
7168
tradeOffersPreferred: bool
69+
buyoutOnly: bool
70+
details: str
71+
listedAt: int
72+
bumpedAt: int
73+
intent: str
7274
count: int
75+
status: str
76+
source: str
7377
item: ItemDocument
78+
user: dict
79+
userAgent: dict = field(default_factory=dict)
7480

7581

7682
class BackpackTFException(Exception):
@@ -82,63 +88,60 @@ class NeedsAPIKey(BackpackTFException):
8288

8389

8490
class BackpackTF:
85-
URL = "https://backpack.tf/api"
91+
URL = "https://api.backpack.tf/api"
8692

87-
def __init__(self, api_key: str) -> None:
93+
def __init__(
94+
self, token: str, api_key: str = "", user_agent="listed with <3"
95+
) -> None:
96+
self.token = token
8897
self.api_key = api_key
98+
self.user_agent = user_agent
8999
self.user_token = None
100+
self.schema = SchemaItemsUtils()
90101

91-
def _get_request(self, endpoint: str, params: dict = {}) -> dict:
92-
if self.api_key:
93-
params["apiKey"] = self.api_key
102+
self.__headers = {"User-Agent": f"{__title__} | {self.user_agent}"}
94103

95-
response = requests.get(self.URL + endpoint, params=params)
96-
return response.json()
97-
98-
def _post_request(self, endpoint: str, json: dict = {}) -> dict:
99-
if self.api_key:
100-
json["apiKey"] = self.api_key
101-
102-
response = requests.post(self.URL + endpoint, json=json)
103-
return response.json()
104-
105-
def _delete_request(self, endpoint: str, params: dict = {}) -> dict:
106-
if self.api_key:
107-
params["apiKey"] = self.api_key
108-
109-
response = requests.delete(self.URL + endpoint, params=params)
110-
return response.json()
111-
112-
def _patch_request(self, endpoint: str, params: dict = {}) -> dict:
113-
if self.api_key:
114-
params["apiKey"] = self.api_key
115-
116-
response = requests.patch(self.URL + endpoint, params=params)
104+
def request(self, method: str, endpoint: str, params: dict = {}, **kwargs) -> dict:
105+
params["token"] = self.token
106+
response = requests.request(
107+
method, self.URL + endpoint, params=params, headers=self.__headers, **kwargs
108+
)
117109
return response.json()
118110

119111
def get_listings(self, skip: int = 0, limit: int = 100) -> dict:
120112
return self._get_request(
121113
"/v2/classifieds/listings", {"skip": skip, "limit": limit}
122114
)
123115

124-
def create_listing(self, listing: ListingResolvable) -> Listing:
125-
return self._post_request("/v2/classifieds/listings", asdict(listing))
126-
127-
def make_listing(
128-
self, id: int, item: dict, details: str, currencies: dict
116+
def _construct_listing_item(self, sku: str) -> dict:
117+
return {
118+
"baseName": self.schema.sku_to_base_name(sku),
119+
"craftable": sku_is_craftable(sku),
120+
"tradable": True,
121+
"quality": {"id": sku_to_quality(sku)},
122+
}
123+
124+
def _construct_listing(
125+
self, sku: str, intent: str, currencies: dict, details: str, asset_id: int = 0
126+
) -> dict:
127+
listing = {
128+
"item": self._construct_listing_item(sku),
129+
"details": details,
130+
"currencies": Currencies(**currencies).__dict__,
131+
}
132+
133+
if intent == "sell":
134+
listing["id"] = asset_id
135+
136+
return listing
137+
138+
def create_listing(
139+
self, sku: str, intent: str, currencies: dict, details: str, asset_id: int = 0
129140
) -> Listing:
130-
listing = ListingResolvable(id, item, details, Currencies(**currencies))
131-
return self.create_listing(listing)
132-
141+
listing = self._construct_listing(sku, intent, currencies, details, asset_id)
142+
response = self.request("POST", "/v2/classifieds/listings", json=listing)
133143

134-
if __name__ == "__main__":
135-
# BackpackTF.create_listing(ListingResolvable(1, 1, 1, 1, 1))
144+
return Listing(**response)
136145

137-
# listin = ListingResolvable("1", {}, "my details", Currencies(1, 1.5))
138-
# listin = ListingResolvable(
139-
# "1", {}, "my details", Currencies(**{"keys": 1, "metal": 1.5})
140-
# )
141-
listin = ListingResolvable("1", {}, "my details", Currencies(**{"keys": 2}))
142-
print(asdict(listin))
143-
# curren = Currencies(1, 1.5)
144-
# print(curren.__dict__)
146+
def register_user_agent(self) -> dict:
147+
return self.request("POST", "/agent/pulse")

src/tf2_utils/schema.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ def defindex_to_name(self, defindex: int) -> str:
2626

2727
return self.defindex_names.get(str(defindex), "")
2828

29+
def defindex_to_full_name(self, defindex: int) -> str:
30+
return self.defindex_full_names.get(str(defindex), "")
31+
2932
def name_to_defindex(self, name: str) -> int:
3033
if name == "Random Craft Weapon":
3134
return -50
@@ -142,6 +145,10 @@ def sku_to_base_name(self, sku: str) -> str:
142145
defindex = sku_to_defindex(sku)
143146
return self.defindex_to_name(defindex)
144147

148+
def sku_to_full_name(self, sku: str) -> str:
149+
defindex = sku_to_defindex(sku)
150+
return self.defindex_to_full_name(defindex)
151+
145152
def sku_to_name(self, sku: str, use_uncraftable: bool = True) -> str:
146153
name = self.sku_to_base_name(sku)
147154
craftable = ""

src/tf2_utils/sockets.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from .prices_tf import PricesTF
22

33
import json
4+
from typing import Callable, Any
45

56
from websockets.sync.client import ClientConnection, connect
67

@@ -10,7 +11,7 @@ class BackpackTFSocket:
1011

1112
def __init__(
1213
self,
13-
callback,
14+
callback: Callable[[dict | list[dict]], None],
1415
solo_entries: bool = True,
1516
headers: dict = {"batch-test": True},
1617
max_size: int | None = None,
@@ -58,7 +59,7 @@ class PricesTFSocket:
5859

5960
def __init__(
6061
self,
61-
callback,
62+
callback: Callable[[dict], Any],
6263
settings: dict = {},
6364
) -> None:
6465
"""

tests/test_backpack_tf.py

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,64 @@
11
from unittest import TestCase
22

3-
from src.tf2_utils import BackpackTF
3+
from .config import BACKPACK_TF_API_KEY
4+
from src.tf2_utils import BackpackTF, Currencies
45

56

67
class TestBackpackTF(TestCase):
78
def setUp(cls) -> None:
8-
cls.bptf = BackpackTF("test")
9+
cls.bptf = BackpackTF(BACKPACK_TF_API_KEY)
10+
11+
def test_currencies(self):
12+
self.assertEqual(Currencies(1, 1.5).__dict__, {"keys": 1, "metal": 1.5})
13+
self.assertEqual(Currencies().__dict__, {"keys": 0, "metal": 0.0})
14+
self.assertEqual(
15+
Currencies(**{"metal": 10.55}).__dict__, {"keys": 0, "metal": 10.55}
16+
)
17+
18+
def test_construct_listing_item(self):
19+
self.assertDictEqual(
20+
self.bptf._construct_listing_item("263;6"),
21+
{
22+
"baseName": "Ellis' Cap",
23+
"craftable": True,
24+
"quality": {"id": 6},
25+
"tradable": True,
26+
},
27+
)
28+
29+
def test_construct_listing(self):
30+
self.assertDictEqual(
31+
self.bptf._construct_listing(
32+
"263;6",
33+
"sell",
34+
{"keys": 1, "metal": 1.55},
35+
"my description",
36+
13201231975,
37+
),
38+
{
39+
"item": {
40+
"baseName": "Ellis' Cap",
41+
"craftable": True,
42+
"quality": {"id": 6},
43+
"tradable": True,
44+
},
45+
"currencies": {"keys": 1, "metal": 1.55},
46+
"details": "my description",
47+
"id": 13201231975,
48+
},
49+
)
50+
self.assertDictEqual(
51+
self.bptf._construct_listing(
52+
"263;6", "buy", {"keys": 1, "metal": 1.55}, "my description"
53+
),
54+
{
55+
"item": {
56+
"baseName": "Ellis' Cap",
57+
"craftable": True,
58+
"quality": {"id": 6},
59+
"tradable": True,
60+
},
61+
"currencies": {"keys": 1, "metal": 1.55},
62+
"details": "my description",
63+
},
64+
)

0 commit comments

Comments
 (0)