├── __init__.py
├── models
├── __init__.py
├── .DS_Store
├── __pycache__
│ ├── me.cpython-311.pyc
│ ├── item.cpython-311.pyc
│ ├── auction.cpython-311.pyc
│ ├── listing.cpython-311.pyc
│ ├── seller.cpython-311.pyc
│ ├── __init__.cpython-311.pyc
│ ├── reference.cpython-311.pyc
│ ├── stickers.cpython-311.pyc
│ ├── buy_orders.cpython-311.pyc
│ └── statistics.cpython-311.pyc
├── stall.py
├── buy_orders.py
├── reference.py
├── statistics.py
├── stickers.py
├── auction.py
├── seller.py
├── listing.py
├── item.py
└── me.py
├── .gitignore
├── .idea
├── .gitignore
├── vcs.xml
├── inspectionProfiles
│ └── profiles_settings.xml
├── misc.xml
├── modules.xml
└── csfloat_api.iml
├── README.md
└── csfloat_client.py
/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/models/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /testttt.py
2 | __pycache__/
3 | *.pyc
4 | .DS_Store
5 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/models/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rushifakami/csfloat_api/HEAD/models/.DS_Store
--------------------------------------------------------------------------------
/models/__pycache__/me.cpython-311.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rushifakami/csfloat_api/HEAD/models/__pycache__/me.cpython-311.pyc
--------------------------------------------------------------------------------
/models/__pycache__/item.cpython-311.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rushifakami/csfloat_api/HEAD/models/__pycache__/item.cpython-311.pyc
--------------------------------------------------------------------------------
/models/__pycache__/auction.cpython-311.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rushifakami/csfloat_api/HEAD/models/__pycache__/auction.cpython-311.pyc
--------------------------------------------------------------------------------
/models/__pycache__/listing.cpython-311.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rushifakami/csfloat_api/HEAD/models/__pycache__/listing.cpython-311.pyc
--------------------------------------------------------------------------------
/models/__pycache__/seller.cpython-311.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rushifakami/csfloat_api/HEAD/models/__pycache__/seller.cpython-311.pyc
--------------------------------------------------------------------------------
/models/__pycache__/__init__.cpython-311.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rushifakami/csfloat_api/HEAD/models/__pycache__/__init__.cpython-311.pyc
--------------------------------------------------------------------------------
/models/__pycache__/reference.cpython-311.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rushifakami/csfloat_api/HEAD/models/__pycache__/reference.cpython-311.pyc
--------------------------------------------------------------------------------
/models/__pycache__/stickers.cpython-311.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rushifakami/csfloat_api/HEAD/models/__pycache__/stickers.cpython-311.pyc
--------------------------------------------------------------------------------
/models/__pycache__/buy_orders.cpython-311.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rushifakami/csfloat_api/HEAD/models/__pycache__/buy_orders.cpython-311.pyc
--------------------------------------------------------------------------------
/models/__pycache__/statistics.cpython-311.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rushifakami/csfloat_api/HEAD/models/__pycache__/statistics.cpython-311.pyc
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/csfloat_api.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/models/stall.py:
--------------------------------------------------------------------------------
1 | from typing import List, Dict, Any, Optional
2 | from .listing import Listing
3 |
4 |
5 | class Stall:
6 | def __init__(self, *, data: Dict[str, Any]) -> None:
7 | listings_data = data.get('data')
8 | if listings_data is None:
9 | self._listings = []
10 | else:
11 | self._listings = [Listing(data=item) for item in listings_data]
12 |
13 | @property
14 | def listings(self) -> List[Listing]:
15 | return self._listings
16 |
--------------------------------------------------------------------------------
/models/buy_orders.py:
--------------------------------------------------------------------------------
1 | from typing import Optional, Dict, Any
2 |
3 | class BuyOrders:
4 | __slots__ = (
5 | "_id",
6 | "_created_at",
7 | "_expression",
8 | "_qty",
9 | "_price"
10 | )
11 |
12 | def __init__(self, *, data: Dict[str, Any]):
13 | self._id = data.get("id")
14 | self._created_at = data.get("created_at")
15 | self._expression = data.get("expression")
16 | self._qty = data.get("qty")
17 | self._price = data.get("price")
18 |
19 | @property
20 | def id(self) -> Optional[str]:
21 | return self._id
22 |
23 | @property
24 | def created_at(self) -> Optional[str]:
25 | return self._created_at
26 |
27 | @property
28 | def expression(self) -> Optional[str]:
29 | return self._expression
30 |
31 | @property
32 | def qty(self) -> Optional[int]:
33 | return self._qty
34 |
35 | @property
36 | def price(self) -> Optional[int]:
37 | return self._price
--------------------------------------------------------------------------------
/models/reference.py:
--------------------------------------------------------------------------------
1 | from typing import Dict, Any, Optional
2 |
3 |
4 | class Reference:
5 | __slots__ = (
6 | "_base_price",
7 | "_float_factor",
8 | "_predicted_price",
9 | "_quantity",
10 | "_last_updated"
11 | )
12 |
13 | def __init__(self, *, data: Dict[str, Any]):
14 | self._base_price = data.get("base_price")
15 | self._float_factor = data.get("float_factor")
16 | self._predicted_price = data.get("predicted_price")
17 | self._quantity = data.get("quantity")
18 | self._last_updated = data.get("last_updated")
19 |
20 | @property
21 | def base_price(self) -> Optional[float]:
22 | return self._base_price
23 |
24 | @property
25 | def float_factor(self) -> Optional[float]:
26 | return self._float_factor
27 |
28 | @property
29 | def predicted_price(self) -> Optional[float]:
30 | return self._predicted_price
31 |
32 | @property
33 | def quantity(self) -> Optional[int]:
34 | return self._quantity
35 |
36 | @property
37 | def last_updated(self) -> Optional[str]:
38 | return self._last_updated
39 |
--------------------------------------------------------------------------------
/models/statistics.py:
--------------------------------------------------------------------------------
1 | from typing import Dict, Any, Optional
2 |
3 |
4 | class Statistics:
5 | __slots__ = (
6 | "_median_trade_time",
7 | "_total_avoided_trades",
8 | "_total_failed_trades",
9 | "_total_trades",
10 | "_total_verified_trades"
11 | )
12 |
13 | def __init__(self, *, data: Dict[str, Any]):
14 | self._median_trade_time = data.get("median_trade_time")
15 | self._total_avoided_trades = data.get("total_avoided_trades")
16 | self._total_failed_trades = data.get("total_failed_trades")
17 | self._total_trades = data.get("total_trades")
18 | self._total_verified_trades = data.get("total_verified_trades")
19 |
20 | @property
21 | def median_trade_time(self) -> Optional[float]:
22 | return self._median_trade_time
23 |
24 | @property
25 | def total_avoided_trades(self) -> Optional[int]:
26 | return self._total_avoided_trades
27 |
28 | @property
29 | def total_failed_trades(self) -> Optional[int]:
30 | return self._total_failed_trades
31 |
32 | @property
33 | def total_trades(self) -> Optional[int]:
34 | return self._total_trades
35 |
36 | @property
37 | def total_verified_trades(self) -> Optional[int]:
38 | return self._total_verified_trades
39 |
--------------------------------------------------------------------------------
/models/stickers.py:
--------------------------------------------------------------------------------
1 | from typing import Dict, Any, Optional
2 |
3 |
4 | class StickerReference:
5 | __slots__ = (
6 | "_price",
7 | "_quantity",
8 | "_updated_at"
9 | )
10 |
11 | def __init__(self, *, data: Dict[str, Any]):
12 | self._price = data.get("price", "")
13 | self._quantity = data.get("quantity")
14 | self._updated_at = data.get("updated_at")
15 |
16 | @property
17 | def price(self) -> Optional[float]:
18 | return self._price
19 |
20 | @property
21 | def quantity(self) -> Optional[int]:
22 | return self._quantity
23 |
24 | @property
25 | def updated_at(self) -> Optional[str]:
26 | return self._updated_at
27 |
28 |
29 | class Sticker:
30 | __slots__ = (
31 | "_stickerId",
32 | "_slot",
33 | "_wear",
34 | "_offset_x",
35 | "_offset_y",
36 | "_icon_url",
37 | "_name",
38 | "_reference"
39 | )
40 |
41 | def __init__(self, *, data: Dict[str, Any]):
42 | self._stickerId = data.get("stickerId")
43 | self._slot = data.get("slot")
44 | self._wear = data.get("wear")
45 | self._offset_x = data.get("offset_x")
46 | self._offset_y = data.get("offset_y")
47 | self._icon_url = data.get("icon_url")
48 | self._name = data.get("name")
49 | self._reference = data.get("reference")
50 |
51 | @property
52 | def stickerId(self) -> Optional[int]:
53 | return self._stickerId
54 |
55 | @property
56 | def slot(self) -> Optional[int]:
57 | return self._slot
58 |
59 | @property
60 | def wear(self) -> Optional[float]:
61 | return self._wear
62 |
63 | @property
64 | def icon_url(self) -> Optional[str]:
65 | return self._icon_url
66 |
67 | @property
68 | def name(self) -> Optional[str]:
69 | return self._name
70 |
71 | @property
72 | def reference(self) -> Optional[StickerReference]:
73 | if self._reference is not None:
74 | return StickerReference(data=self._reference)
75 |
--------------------------------------------------------------------------------
/models/auction.py:
--------------------------------------------------------------------------------
1 | from typing import Dict, Any, Optional
2 |
3 |
4 | class TopBid:
5 | __slots__ = (
6 | "_id",
7 | "_created_at",
8 | "_price",
9 | "_contract_id",
10 | "_state",
11 | "_active",
12 | "_obfuscated_buyer_id"
13 | )
14 |
15 | def __init__(self, *, data: Dict[str, Any]):
16 | self._id = data.get("id")
17 | self._created_at = data.get("created_at")
18 | self._price = data.get("price")
19 | self._contract_id = data.get("contract_id")
20 | self._state = data.get("state")
21 | self._active = data.get("active")
22 | self._obfuscated_buyer_id = data.get("obfuscated_buyer_id")
23 |
24 | @property
25 | def id(self) -> Optional[int]:
26 | return self._id
27 |
28 | @property
29 | def created_at(self) -> Optional[str]:
30 | return self._created_at
31 |
32 | @property
33 | def price(self) -> Optional[int]:
34 | return self._price
35 |
36 | @property
37 | def contract_id(self) -> Optional[str]:
38 | return self._contract_id
39 |
40 | @property
41 | def state(self) -> Optional[str]:
42 | return self._state
43 |
44 | @property
45 | def obfuscated_buyer_id(self) -> Optional[str]:
46 | return self._obfuscated_buyer_id
47 |
48 |
49 | class AuctionDetails:
50 | __slots__ = (
51 | "_reserve_price",
52 | "_top_bid",
53 | "_expires_at",
54 | "_min_next_bid",
55 | )
56 |
57 | def __init__(self, *, data: Dict[str, Any]):
58 | self._reserve_price = data.get("reserve_price")
59 | self._top_bid = data.get("top_bid")
60 | self._expires_at = data.get("expires_at")
61 | self._min_next_bid = data.get("min_next_bid")
62 |
63 | @property
64 | def reserve_price(self) -> Optional[float]:
65 | return self._reserve_price
66 |
67 | @property
68 | def top_bid(self) -> Optional[TopBid]:
69 | return TopBid(data=self._top_bid)
70 |
71 | @property
72 | def expires_at(self) -> Optional[str]:
73 | return self._expires_at
74 |
75 | @property
76 | def min_next_bid(self) -> Optional[float]:
77 | return self._min_next_bid
78 |
--------------------------------------------------------------------------------
/models/seller.py:
--------------------------------------------------------------------------------
1 | from typing import Dict, Any, Optional
2 | from .statistics import Statistics
3 |
4 |
5 | class Seller:
6 | __slots__ = (
7 | "_avatar",
8 | "_away",
9 | "_flags",
10 | "_has_valid_steam_api_key",
11 | "_obfuscated_id",
12 | "_online",
13 | "_stall_public",
14 | "_statistics",
15 | "_steam_id",
16 | "_username",
17 | "_verification_mode"
18 | )
19 |
20 | def __init__(self, *, data: Dict[str, Any]):
21 | self._avatar = data.get("avatar")
22 | self._away = data.get("away")
23 | self._flags = data.get("flags")
24 | self._has_valid_steam_api_key = data.get("has_valid_steam_api_key")
25 | self._obfuscated_id = data.get("obfuscated_id")
26 | self._online = data.get("online")
27 | self._stall_public = data.get("stall_public")
28 | self._statistics = data.get("statistics")
29 | self._steam_id = data.get("steam_id")
30 | self._username = data.get("username")
31 | self._verification_mode = data.get("verification_mode")
32 |
33 | @property
34 | def avatar(self) -> Optional[str]:
35 | return self._avatar
36 |
37 | @property
38 | def away(self) -> Optional[bool]:
39 | return self._away
40 |
41 | @property
42 | def flags(self) -> Optional[int]:
43 | return self._flags
44 |
45 | @property
46 | def has_valid_steam_api_key(self) -> Optional[bool]:
47 | return self._has_valid_steam_api_key
48 |
49 | @property
50 | def obfuscated_id(self) -> Optional[int]:
51 | return self._obfuscated_id
52 |
53 | @property
54 | def online(self) -> Optional[bool]:
55 | return self._online
56 |
57 | @property
58 | def stall_public(self) -> Optional[bool]:
59 | return self._stall_public
60 |
61 | @property
62 | def statistics(self) -> Optional[Statistics]:
63 | return Statistics(data=self._statistics)
64 |
65 | @property
66 | def steam_id(self) -> Optional[int]:
67 | return self._steam_id
68 |
69 | @property
70 | def username(self) -> Optional[str]:
71 | return self._username
72 |
73 | @property
74 | def verification_mode(self) -> Optional[str]:
75 | return self._verification_mode
76 |
--------------------------------------------------------------------------------
/models/listing.py:
--------------------------------------------------------------------------------
1 | from typing import Dict, Any, Optional
2 | from .seller import Seller
3 | from .reference import Reference
4 | from .item import Item
5 | from .auction import AuctionDetails
6 |
7 |
8 | class Listing:
9 | __slots__ = (
10 | "_id",
11 | "_created_at",
12 | "_type",
13 | "_price",
14 | "_description",
15 | "_state",
16 | "_seller",
17 | "_reference",
18 | "_item",
19 | "_is_seller",
20 | "_min_offer_price",
21 | "_max_offer_discount",
22 | "_is_watchlisted",
23 | "_watchers",
24 | "_auction_details",
25 | "_sold_at"
26 | )
27 |
28 | def __init__(self, *, data: Dict[str, Any]) -> None:
29 | self._id = data.get("id")
30 | self._created_at = data.get("created_at")
31 | self._type = data.get("type")
32 | self._price = data.get("price")
33 | self._description = data.get("description")
34 | self._state = data.get("state")
35 | self._seller = data.get("seller")
36 | self._reference = data.get("reference")
37 | self._item = data.get("item")
38 | self._is_seller = data.get("is_seller")
39 | self._min_offer_price = data.get("min_offer_price")
40 | self._max_offer_discount = data.get("max_offer_discount")
41 | self._is_watchlisted = data.get("is_watchlisted")
42 | self._watchers = data.get("watchers")
43 | self._auction_details = data.get("auction_details")
44 | self._sold_at = data.get("sold_at")
45 |
46 | @property
47 | def id(self) -> Optional[int]:
48 | return self._id
49 |
50 | @property
51 | def created_at(self) -> Optional[str]:
52 | return self._created_at
53 |
54 | @property
55 | def type(self) -> Optional[str]:
56 | return self._type
57 |
58 | @property
59 | def price(self) -> Optional[float]:
60 | return self._price
61 |
62 | @property
63 | def description(self) -> Optional[str]:
64 | return self._description
65 |
66 | @property
67 | def state(self) -> Optional[str]:
68 | return self._state
69 |
70 | @property
71 | def seller(self) -> Optional[Seller]:
72 | return Seller(data=self._seller)
73 |
74 | @property
75 | def reference(self) -> Optional[Reference]:
76 | return Reference(data=self._reference)
77 |
78 | @property
79 | def item(self) -> Optional[Item]:
80 | return Item(data=self._item)
81 |
82 | @property
83 | def is_seller(self) -> Optional[bool]:
84 | return self._is_seller
85 |
86 | @property
87 | def min_offer_price(self) -> Optional[float]:
88 | return self._min_offer_price
89 |
90 | @property
91 | def max_offer_discount(self) -> Optional[float]:
92 | return self._max_offer_discount
93 |
94 | @property
95 | def is_watchlisted(self) -> Optional[bool]:
96 | return self._is_watchlisted
97 |
98 | @property
99 | def watchers(self) -> Optional[int]:
100 | return self._watchers
101 |
102 | @property
103 | def auction_details(self) -> Optional[AuctionDetails]:
104 | return AuctionDetails(data=self._auction_details)
105 |
106 | @property
107 | def sold_at(self) -> Optional[str]:
108 | return self._sold_at
109 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CSFloat Async API Client (Unofficial)
2 |
3 | An **unofficial**, **asynchronous** Python library for interacting with the [CSFloat](https://csfloat.com) API. This library provides full support for programmatic access to listings, buy orders, user data, exchange rates, and more. All prices returned by the server are in **cents**.
4 |
5 | ## Key Features
6 |
7 | * **Asynchronous design**: Built on `aiohttp` for non-blocking I/O.
8 | * **Fetch listings**: Retrieve detailed listings with filters (price in cents, float, rarity, etc.).
9 | * **Buy orders**: Get and manage buy orders for specific items.
10 | * **User information**: Access your own profile, trades, and stall data.
11 | * **Listing management**: Create, delete, and modify listings and buy orders.
12 | * **Proxy support**: Optional SOCKS4/5 and HTTP(S) proxy support.
13 | * **Error handling**: Clear exceptions with descriptive messages.
14 |
15 | ## Installation
16 |
17 | Install via pip:
18 |
19 | ```bash
20 | pip install csfloat_api
21 | ```
22 |
23 | ## Quick Start
24 |
25 | ```python
26 | import asyncio
27 | from csfloat_api.csfloat_client import Client
28 |
29 | async def main():
30 | async with Client(api_key="YOUR_API_KEY") as client:
31 | # Fetch up to 50 listings priced between $1.00 and $10.00 (i.e., 100–1000 cents)
32 | listings = await client.get_all_listings(min_price=100, max_price=1000)
33 | for listing in listings["listings"]:
34 | print(f"ID: {listing.id}, Price: {listing.price} cents, Float: {listing.item.float_value}")
35 |
36 | # Create a buy order for an item
37 | buy_order = await client.create_buy_order(
38 | market_hash_name="AK-47 | Redline (Field-Tested)",
39 | max_price=5000, # 5000 cents = $50.00
40 | quantity=1
41 | )
42 | print(buy_order)
43 |
44 | asyncio.run(main())
45 | ```
46 |
47 | ## Session Management
48 |
49 | The `Client` class manages an underlying HTTP session using `aiohttp.ClientSession`. To ensure that resources are properly released, it is important to close the session when you are done using the client.
50 |
51 | ### Using `async with`
52 |
53 | The recommended way to use the `Client` is with an `async with` statement, which automatically closes the session when the block is exited:
54 |
55 | ```python
56 | async with Client(api_key="YOUR_API_KEY") as client:
57 | # Use the client...
58 | ```
59 |
60 | ### Manual Session Closing
61 |
62 | If you cannot use `async with`, you can manually close the session using the `close` method:
63 |
64 | ```python
65 | client = Client(api_key="YOUR_API_KEY")
66 | try:
67 | # Use the client...
68 | finally:
69 | await client.close()
70 | ```
71 |
72 | Failing to close the session may result in warnings about unclosed client sessions and potential resource leaks.
73 |
74 | ## Core Methods
75 |
76 | * `get_exchange_rates()` – Retrieve current exchange rates.
77 | * `get_all_listings(...)` – List items with optional filters (prices in cents).
78 | * `get_specific_listing(listing_id)` – Get detailed info for a specific listing.
79 | * `get_buy_orders(listing_id)` – Retrieve buy orders for a listing.
80 | * `get_my_buy_orders(...)` – List your own buy orders.
81 | * `get_me()` – Fetch authenticated user profile.
82 | * `get_stall(user_id)` – Get a user's stall (listed items).
83 | * `create_listing(asset_id, price, ...)` – Create a new listing (price in cents).
84 | * `create_buy_order(market_hash_name, max_price, quantity)` – Place a buy order.
85 | * `make_offer(listing_id, price)` – Make an offer on a listing.
86 | * `buy_now(total_price, listing_id)` – Instantly buy one or more listings.
87 | * `delete_buy_order(id)` – Cancel an existing buy order.
88 |
89 | For a full list of methods and parameters, refer to the library's source code.
90 |
91 | ## Proxy Support
92 |
93 | Optionally provide a proxy URL in the constructor:
94 |
95 | ```python
96 | client = Client(
97 | api_key="YOUR_API_KEY",
98 | proxy="socks5://user:pass@host:port"
99 | )
100 | ```
101 |
102 | ## Error Handling
103 |
104 | The client raises exceptions for HTTP errors with clear messages:
105 |
106 | * **401** Unauthorized – Invalid API key.
107 | * **404** Not Found – Resource missing.
108 | * **429** Too Many Requests – Rate limit exceeded.
109 | * **500** Internal Server Error – Server-side issue.
110 |
111 | ## Contributing
112 |
113 | Contributions are welcome! Please submit issues and pull requests on the [GitHub repository](https://github.com/Rushifakami/csfloat_api).
114 |
115 | ## License
116 |
117 | This project is licensed under the [MIT License](https://opensource.org/licenses/MIT).
--------------------------------------------------------------------------------
/models/item.py:
--------------------------------------------------------------------------------
1 | from typing import Dict, Any, List, Optional
2 | from .stickers import Sticker
3 |
4 |
5 | class Item:
6 | __slots__ = (
7 | "_asset_id",
8 | "_def_index",
9 | "_paint_index",
10 | "_paint_seed",
11 | "_float_value",
12 | "_icon_url",
13 | "_d_param",
14 | "_is_stattrak",
15 | "_is_souvenir",
16 | "_rarity",
17 | "_quality",
18 | "_market_hash_name",
19 | "_low_rank",
20 | "_stickers",
21 | "_tradable",
22 | "_inspect_link",
23 | "_has_screenshot",
24 | "_cs2_screenshot_id",
25 | "_cs2_screenshot_at",
26 | "_is_commodity",
27 | "_type",
28 | "_rarity_name",
29 | "_type_name",
30 | "_item_name",
31 | "_wear_name",
32 | "_description",
33 | "_collection",
34 | "_badges",
35 | "_serialized_inspect",
36 | "_gs_sig"
37 | )
38 |
39 | def __init__(self, *, data: Dict[str, Any]):
40 | self._asset_id = data.get("asset_id")
41 | self._def_index = data.get("def_index")
42 | self._paint_index = data.get("paint_index")
43 | self._paint_seed = data.get("paint_seed")
44 | self._float_value = data.get("float_value")
45 | self._icon_url = data.get("icon_url")
46 | self._d_param = data.get("d_param")
47 | self._is_stattrak = data.get("is_stattrak")
48 | self._is_souvenir = data.get("is_souvenir")
49 | self._rarity = data.get("rarity")
50 | self._quality = data.get("quality")
51 | self._market_hash_name = data.get("market_hash_name")
52 | self._low_rank = data.get("low_rank")
53 | self._stickers = data.get("stickers")
54 | self._tradable = data.get("tradable")
55 | self._inspect_link = data.get("inspect_link")
56 | self._has_screenshot = data.get("has_screenshot")
57 | self._cs2_screenshot_id = data.get("cs2_screenshot_id")
58 | self._cs2_screenshot_at = data.get("cs2_screenshot_at")
59 | self._is_commodity = data.get("is_commodity")
60 | self._type = data.get("type")
61 | self._rarity_name = data.get("rarity_name")
62 | self._type_name = data.get("rarity_name")
63 | self._item_name = data.get("item_name")
64 | self._wear_name = data.get("wear_name")
65 | self._description = data.get("description")
66 | self._collection = data.get("collection")
67 | self._badges = data.get("badges")
68 | self._serialized_inspect = data.get("serialized_inspect")
69 | self._gs_sig = data.get("gs_sig")
70 |
71 | @property
72 | def asset_id(self) -> Optional[int]:
73 | return self._asset_id
74 |
75 | @property
76 | def def_index(self) -> Optional[int]:
77 | return self._def_index
78 |
79 | @property
80 | def paint_index(self) -> Optional[float]:
81 | return self._paint_index
82 |
83 | @property
84 | def paint_seed(self) -> Optional[float]:
85 | return self._paint_seed
86 |
87 | @property
88 | def float_value(self) -> Optional[float]:
89 | return self._float_value
90 |
91 | @property
92 | def icon_url(self) -> Optional[str]:
93 | return self._icon_url
94 |
95 | @property
96 | def d_param(self) -> Optional[int]:
97 | return self._d_param
98 |
99 | @property
100 | def is_stattrak(self) -> Optional[bool]:
101 | return self._is_stattrak
102 |
103 | @property
104 | def is_souvenir(self) -> Optional[bool]:
105 | return self._is_souvenir
106 |
107 | @property
108 | def rarity(self) -> Optional[str]:
109 | return self._rarity
110 |
111 | @property
112 | def quality(self) -> Optional[int]:
113 | return self._quality
114 |
115 | @property
116 | def market_hash_name(self) -> Optional[str]:
117 | return self._market_hash_name
118 |
119 | @property
120 | def low_rank(self) -> Optional[int]:
121 | return self._low_rank
122 |
123 | @property
124 | def stickers(self) -> Optional[List[Sticker]]:
125 | if self._stickers is not None:
126 | return [Sticker(data=sticker) for sticker in self._stickers]
127 |
128 | @property
129 | def tradable(self) -> Optional[bool]:
130 | return self._tradable
131 |
132 | @property
133 | def inspect_link(self) -> Optional[str]:
134 | return self._inspect_link
135 |
136 | @property
137 | def has_screenshot(self) -> Optional[bool]:
138 | return self._has_screenshot
139 |
140 | @property
141 | def cs2_screenshot_id(self) -> Optional[int]:
142 | return self._cs2_screenshot_id
143 |
144 | @property
145 | def cs2_screenshot_at(self) -> Optional[str]:
146 | return self._cs2_screenshot_at
147 |
148 | @property
149 | def is_commodity(self) -> Optional[bool]:
150 | return self._is_commodity
151 |
152 | @property
153 | def type(self) -> Optional[str]:
154 | return self._type
155 |
156 | @property
157 | def rarity_name(self) -> Optional[str]:
158 | return self._rarity_name
159 |
160 | @property
161 | def type_name(self) -> Optional[str]:
162 | return self._type_name
163 |
164 | @property
165 | def item_name(self) -> Optional[str]:
166 | return self._item_name
167 |
168 | @property
169 | def wear_name(self) -> Optional[str]:
170 | return self._wear_name
171 |
172 | @property
173 | def description(self) -> Optional[str]:
174 | return self._description
175 |
176 | @property
177 | def collection(self) -> Optional[str]:
178 | return self._collection
179 |
180 | @property
181 | def badges(self) -> Optional[list]:
182 | return self._badges
183 |
184 | @property
185 | def serialized_inspect(self) -> Optional[str]:
186 | return self._serialized_inspect
187 |
188 | @property
189 | def gs_sig(self) -> Optional[str]:
190 | return self._gs_sig
191 |
--------------------------------------------------------------------------------
/models/me.py:
--------------------------------------------------------------------------------
1 | from typing import Optional, Dict, Any
2 |
3 |
4 | class Statistics:
5 | __slots__ = (
6 | "_total_sales",
7 | "_total_purchases",
8 | "_median_trade_time",
9 | "_total_avoided_trades",
10 | "_total_failed_trades",
11 | "_total_verified_trades",
12 | "_total_trades"
13 | )
14 |
15 | def __init__(self, *, data: Dict[str, Any]):
16 | self._total_sales = data.get("total_sales")
17 | self._total_purchases = data.get("total_purchases")
18 | self._median_trade_time = data.get("median_trade_time")
19 | self._total_avoided_trades = data.get("total_avoided_trades")
20 | self._total_failed_trades = data.get("total_failed_trades")
21 | self._total_verified_trades = data.get("total_verified_trades")
22 | self._total_trades = data.get("total_trades")
23 |
24 | @property
25 | def total_sales(self) -> Optional[int]:
26 | return self._total_sales
27 |
28 | @property
29 | def total_purchases(self) -> Optional[int]:
30 | return self._total_purchases
31 |
32 | @property
33 | def median_trade_time(self) -> Optional[int]:
34 | return self._median_trade_time
35 |
36 | @property
37 | def total_avoided_trades(self) -> Optional[int]:
38 | return self._total_avoided_trades
39 |
40 | @property
41 | def total_failed_trades(self) -> Optional[int]:
42 | return self._total_failed_trades
43 |
44 | @property
45 | def total_verified_trades(self) -> Optional[int]:
46 | return self._total_verified_trades
47 |
48 | @property
49 | def total_trades(self) -> Optional[int]:
50 | return self._total_trades
51 |
52 |
53 | class Preferences:
54 | __slots__ = (
55 | "_offers_enabled",
56 | "_max_offer_discount"
57 | )
58 |
59 | def __init__(self, *, data: Dict[str, Any]):
60 | self._offers_enabled = data.get("offers_enabled")
61 | self._max_offer_discount = data.get("max_offer_discount")
62 |
63 | @property
64 | def offers_enabled(self) -> Optional[bool]:
65 | return self._offers_enabled
66 |
67 | @property
68 | def max_offer_discount(self) -> Optional[int]:
69 | return self._max_offer_discount
70 |
71 |
72 | class FirebaseMessaging:
73 | __slots__ = (
74 | "_platform",
75 | "_last_updated"
76 | )
77 |
78 | def __init__(self, *, data: Dict[str, Any]):
79 | self._platform = data.get("platform")
80 | self._last_updated = data.get("last_updated")
81 |
82 | @property
83 | def platform(self) -> Optional[int]:
84 | return self._platform
85 |
86 | @property
87 | def last_updated(self) -> Optional[str]:
88 | return self._last_updated
89 |
90 |
91 | class User:
92 | __slots__ = (
93 | "_steam_id",
94 | "_username",
95 | "_flags",
96 | "_avatar",
97 | "_background_url",
98 | "_email",
99 | "_balance",
100 | "_pending_balance",
101 | "_stall_public",
102 | "_away",
103 | "_trade_token",
104 | "_payment_accounts",
105 | "_api_key",
106 | "_statistics",
107 | "_preferences",
108 | "_know_your_customer",
109 | "_extension_setup_at",
110 | "_firebase_messaging",
111 | "_stripe_connect",
112 | "_has_valid_steam_api_key",
113 | "_obfuscated_id",
114 | "_online",
115 | "_fee",
116 | "_withdraw_fee",
117 | "_subscriptions",
118 | "_has_2fa"
119 | )
120 |
121 | def __init__(self, *, data: Dict[str, Any]):
122 | self._steam_id = data.get("steam_id")
123 | self._username = data.get("username")
124 | self._flags = data.get("flags")
125 | self._avatar = data.get("avatar")
126 | self._background_url = data.get("background_url")
127 | self._email = data.get("email")
128 | self._balance = data.get("balance")
129 | self._pending_balance = data.get("pending_balance")
130 | self._stall_public = data.get("stall_public")
131 | self._away = data.get("away")
132 | self._trade_token = data.get("trade_token")
133 | self._payment_accounts = data.get("payment_accounts")
134 | self._api_key = data.get("api_key", "")
135 | self._statistics = data.get("statistics")
136 | self._preferences = data.get("preferences")
137 | self._know_your_customer = data.get("know_your_customer")
138 | self._extension_setup_at = data.get("extension_setup_at")
139 | self._firebase_messaging = data.get("firebase_messaging")
140 | self._stripe_connect = data.get("stripe_connect")
141 | self._has_valid_steam_api_key = data.get("has_valid_steam_api_key")
142 | self._obfuscated_id = data.get("obfuscated_id")
143 | self._online = data.get("online")
144 | self._fee = data.get("fee")
145 | self._withdraw_fee = data.get("withdraw_fee")
146 | self._subscriptions = data.get("subscriptions")
147 | self._has_2fa = data.get("has_2fa")
148 |
149 | @property
150 | def steam_id(self) -> Optional[str]:
151 | return self._steam_id
152 |
153 | @property
154 | def username(self) -> Optional[str]:
155 | return self._username
156 |
157 | @property
158 | def flags(self) -> Optional[int]:
159 | return self._flags
160 |
161 | @property
162 | def avatar(self) -> Optional[str]:
163 | return self._avatar
164 |
165 | @property
166 | def background_url(self) -> Optional[str]:
167 | return self._background_url
168 |
169 | @property
170 | def email(self) -> Optional[str]:
171 | return self._email
172 |
173 | @property
174 | def balance(self) -> Optional[int]:
175 | return self._balance
176 |
177 | @property
178 | def pending_balance(self) -> Optional[int]:
179 | return self._pending_balance
180 |
181 | @property
182 | def stall_public(self) -> Optional[bool]:
183 | return self._stall_public
184 |
185 | @property
186 | def away(self) -> Optional[bool]:
187 | return self._away
188 |
189 | @property
190 | def trade_token(self) -> Optional[str]:
191 | return self._trade_token
192 |
193 | @property
194 | def payment_accounts(self) -> Optional[dict]:
195 | return self._payment_accounts
196 |
197 | @property
198 | def api_key(self) -> Optional[str]:
199 | return self._api_key
200 |
201 | @property
202 | def statistics(self) -> Optional[Statistics]:
203 | return Statistics(data=self._statistics)
204 |
205 | @property
206 | def preferences(self) -> Optional[Preferences]:
207 | return Preferences(data=self._preferences)
208 |
209 | @property
210 | def know_your_customer(self) -> Optional[str]:
211 | return self._know_your_customer
212 |
213 | @property
214 | def extension_setup_at(self) -> Optional[str]:
215 | return self._extension_setup_at
216 |
217 | @property
218 | def firebase_messaging(self) -> Optional[FirebaseMessaging]:
219 | return FirebaseMessaging(data=self._firebase_messaging)
220 |
221 | @property
222 | def stripe_connect(self) -> Optional[set]:
223 | return self._stripe_connect
224 |
225 | @property
226 | def has_valid_steam_api_key(self) -> Optional[bool]:
227 | return self._has_valid_steam_api_key
228 |
229 | @property
230 | def obfuscated_id(self) -> Optional[str]:
231 | return self._obfuscated_id
232 |
233 | @property
234 | def online(self) -> Optional[bool]:
235 | return self._online
236 |
237 | @property
238 | def fee(self) -> Optional[float]:
239 | return self._fee
240 |
241 | @property
242 | def withdraw_fee(self) -> Optional[float]:
243 | return self._withdraw_fee
244 |
245 | @property
246 | def subscriptions(self) -> Optional[list]:
247 | return self._subscriptions
248 |
249 | @property
250 | def has_2fa(self) -> Optional[bool]:
251 | return self._has_2fa
252 |
253 |
254 | class Me:
255 | __slots__ = (
256 | "_user",
257 | "_pending_offers",
258 | "_actionable_trades"
259 | )
260 |
261 | def __init__(self, *, data: Dict[str, Any]):
262 | self._user = data.get("user")
263 | self._pending_offers = data.get("pending_offers")
264 | self._actionable_trades = data.get("actionable_trades")
265 |
266 | @property
267 | def user(self) -> Optional[User]:
268 | return User(data=self._user)
269 |
270 | @property
271 | def pending_offers(self) -> Optional[int]:
272 | return self._pending_offers
273 |
274 | @property
275 | def actionable_trades(self) -> Optional[int]:
276 | return self._actionable_trades
277 |
--------------------------------------------------------------------------------
/csfloat_client.py:
--------------------------------------------------------------------------------
1 | import aiohttp
2 | import re
3 | from aiohttp_socks.connector import ProxyConnector
4 | from typing import Iterable, Union, Optional, Dict, List
5 | from .models.listing import Listing
6 | from .models.buy_orders import BuyOrders
7 | from .models.me import Me
8 | from .models.stall import Stall
9 |
10 | __all__ = "Client"
11 |
12 | _API_URL = 'https://csfloat.com/api/v1'
13 |
14 |
15 | class Client:
16 | _SUPPORTED_METHODS = ('GET', 'POST', 'DELETE', 'PATCH')
17 | ERROR_MESSAGES = {
18 | 401: 'Unauthorized -- Your API key is wrong.',
19 | 403: 'Forbidden -- The requested resource is hidden for administrators only.',
20 | 404: 'Not Found -- The specified resource could not be found.',
21 | 405: 'Method Not Allowed -- You tried to access a resource with an invalid method.',
22 | 406: 'Not Acceptable -- You requested a format that isn\'t json.',
23 | 410: 'Gone -- The requested resource has been removed from our servers.',
24 | 418: 'I\'m a teapot.',
25 | 429: 'Too Many Requests -- You\'re requesting too many resources! Slow down!',
26 | 500: 'Internal Server Error -- We had a problem with our server. Try again later.',
27 | 503: 'Service Unavailable -- We\'re temporarily offline for maintenance. Please try again later.',
28 | }
29 |
30 | __slots__ = (
31 | "API_KEY",
32 | "proxy",
33 | "_headers",
34 | "_connector",
35 | "_session"
36 | )
37 |
38 | def __init__(self, api_key: str, proxy: str = None) -> None:
39 | self.API_KEY = api_key
40 | self.proxy = proxy
41 | self._validate_proxy()
42 | self._headers = {
43 | 'Authorization': self.API_KEY
44 | }
45 | self._connector = ProxyConnector.from_url(self.proxy, ttl_dns_cache=300) if self.proxy else aiohttp.TCPConnector(
46 | resolver=aiohttp.resolver.AsyncResolver(),
47 | limit_per_host=50
48 | )
49 | self._session = aiohttp.ClientSession(connector=self._connector, headers=self._headers)
50 |
51 | async def __aenter__(self):
52 | return self
53 |
54 | async def __aexit__(self, exc_type, exc, tb):
55 | await self._session.close()
56 |
57 | async def close(self):
58 | await self._session.close()
59 |
60 | def _validate_proxy(self) -> None:
61 | """Validates the proxy URL format.
62 |
63 | Raises:
64 | ValueError: If the proxy URL format is invalid or the port is out of range.
65 | """
66 | if not self.proxy:
67 | return # Proxy is not set, which is acceptable
68 | # Regular expression to check the format: socks5://user:pass@host:port, etc.
69 | pattern = r'^(socks5|socks4|http|https)://(\w+:\w+@)?[\w.-]+:\d+$'
70 | if not re.match(pattern, self.proxy):
71 | raise ValueError(
72 | f"Invalid proxy URL format: {self.proxy}. Expected format like 'socks5://user:pass@host:port'")
73 | # Check the port
74 | port = self.proxy.split(':')[-1]
75 | if not port.isdigit() or not (1 <= int(port) <= 65535):
76 | raise ValueError(f"Invalid port in proxy URL: {port}")
77 |
78 | async def _request(self, method: str, parameters: str, json_data=None) -> Optional[dict]:
79 | if method not in self._SUPPORTED_METHODS:
80 | raise ValueError('Unsupported HTTP method.')
81 |
82 | url = f'{_API_URL}{parameters}'
83 |
84 | async with self._session.request(method=method, url=url, ssl=False, json=json_data) as response:
85 | if response.status in self.ERROR_MESSAGES:
86 | raise Exception(self.ERROR_MESSAGES[response.status])
87 |
88 | if response.status != 200:
89 | try:
90 | error_details = await response.json()
91 | except Exception:
92 | error_details = await response.text()
93 | raise Exception(f'Error: {response.status}\nResponse Body: {error_details}')
94 |
95 | if response.content_type != 'application/json':
96 | raise Exception(f"Expected JSON, got {response.content_type}")
97 |
98 | return await response.json()
99 |
100 | def _validate_category(self, category: int) -> None:
101 | if category not in (0, 1, 2, 3):
102 | raise ValueError(f'Unknown category parameter "{category}"')
103 |
104 | def _validate_sort_by(self, sort_by: str) -> None:
105 | valid_sort_by = (
106 | 'lowest_price', 'highest_price', 'most_recent', 'expires_soon',
107 | 'lowest_float', 'highest_float', 'best_deal', 'highest_discount',
108 | 'float_rank', 'num_bids'
109 | )
110 | if sort_by not in valid_sort_by:
111 | raise ValueError(f'Unknown sort_by parameter "{sort_by}"')
112 |
113 | def _validate_type(self, type_: str) -> None:
114 | if type_ not in ('buy_now', 'auction'):
115 | raise ValueError(f'Unknown type parameter "{type_}"')
116 |
117 | def _validate_role(self, role: str) -> None:
118 | valid_roles = ("seller", "buyer")
119 | if role not in valid_roles:
120 | raise ValueError(f'Unknown role parameter: {role}')
121 |
122 | async def get_exchange_rates(self) -> Optional[dict]:
123 | parameters = "/meta/exchange-rates"
124 | method = "GET"
125 |
126 | response = await self._request(method=method, parameters=parameters)
127 | return response
128 |
129 | async def get_me(self, *, raw_response: bool = False) -> Optional[Me]:
130 | parameters = "/me"
131 | method = "GET"
132 |
133 | response = await self._request(method=method, parameters=parameters)
134 |
135 | if raw_response:
136 | return response
137 |
138 | return Me(data=response)
139 |
140 | async def get_transactions(self, page: int = 0, limit: int = 10):
141 | parameters = f"/me/transactions?page={page}&limit={limit}&order=desc"
142 | method = "GET"
143 | response = await self._request(method=method, parameters=parameters)
144 | return response
145 |
146 | async def get_account_standing(self):
147 | parameters = "/me/account-standing"
148 | method = "GET"
149 | response = await self._request(method=method, parameters=parameters)
150 | return response
151 |
152 | async def get_location(self) -> Optional[dict]:
153 | parameters = "/meta/location"
154 | method = "GET"
155 |
156 | response = await self._request(method=method, parameters=parameters)
157 | return response
158 |
159 | async def get_pending_trades(
160 | self, limit: int = 500, page: int = 0
161 | ) -> Optional[dict]:
162 | parameters = f"/me/trades?state=pending&limit={limit}&page={page}"
163 | method = "GET"
164 |
165 | response = await self._request(method=method, parameters=parameters)
166 | return response
167 |
168 | async def get_similar(
169 | self, *, listing_id: int, raw_response: bool = False
170 | ) -> Union[Iterable[Listing], dict]:
171 | parameters = f"/listings/{listing_id}/similar"
172 | method = "GET"
173 |
174 | response = await self._request(method=method, parameters=parameters)
175 |
176 | if raw_response:
177 | return response
178 |
179 | listings = [
180 | Listing(data=item) for item in response
181 | ]
182 |
183 | return listings
184 |
185 | async def get_buy_orders(
186 | self, *, listing_id: int, limit: int = 10, raw_response: bool = False
187 | ) -> Optional[list[BuyOrders]]:
188 | parameters = f"/listings/{listing_id}/buy-orders?limit={limit}"
189 | method = "GET"
190 |
191 | response = await self._request(method=method, parameters=parameters)
192 |
193 | if raw_response:
194 | return response
195 |
196 | listings = [
197 | BuyOrders(data=item) for item in response
198 | ]
199 |
200 | return listings
201 |
202 | async def get_my_buy_orders(self, *, page: int = 0, limit: int = 10):
203 | parameters = f"/me/buy-orders?page={page}&limit={limit}&order=desc"
204 | method = "GET"
205 | response = await self._request(method=method, parameters=parameters)
206 | return response
207 |
208 | async def get_sales(self, market_hash_name: str, paint_index: int = None):
209 | parameters = f"/history/{market_hash_name}/sales"
210 | if paint_index is not None:
211 | parameters += f"?paint_index={paint_index}"
212 | method = "GET"
213 | response = await self._request(method=method, parameters=parameters)
214 | return response
215 |
216 | async def get_all_listings(
217 | self,
218 | *,
219 | min_price: Optional[int] = None,
220 | max_price: Optional[int] = None,
221 | cursor: Optional[str] = None,
222 | limit: int = 50,
223 | sort_by: str = 'best_deal',
224 | category: int = 0,
225 | def_index: Optional[Union[int, Iterable[int]]] = None,
226 | min_float: Optional[float] = None,
227 | max_float: Optional[float] = None,
228 | rarity: Optional[str] = None,
229 | paint_seed: Optional[int] = None,
230 | paint_index: Optional[int] = None,
231 | user_id: Optional[str] = None,
232 | collection: Optional[str] = None,
233 | market_hash_name: Optional[str] = None,
234 | type_: str = 'buy_now',
235 | raw_response: bool = False
236 | ) -> Union[Dict[str, Union[List[Listing], Optional[str]]], dict]:
237 | """
238 | :param min_price: Only include listings have a price higher than this (in cents)
239 | :param max_price: Only include listings have a price lower than this (in cents)
240 | :param cursor: Cursor to get the next page (issued by the server)
241 | :param limit: How many listings to return. Max of 50
242 | :param sort_by: How to order the listings
243 | :param category: Can be one of: 0 = any, 1 = normal, 2 = stattrak, 3 = souvenir
244 | :param def_index: Only include listings that have one of the given def index(es)
245 | :param min_float: Only include listings that have a float higher than this
246 | :param max_float: Only include listings that have a float lower than this
247 | :param rarity: Only include listings that have this rarity
248 | :param paint_seed: Only include listings that have this paint seed
249 | :param paint_index: Only include listings that have this paint index
250 | :param user_id: Only include listings from this SteamID64
251 | :param collection: Only include listings from this collection
252 | :param market_hash_name: Only include listings that have this market hash name
253 | :param type_: Either buy_now or auction
254 | :param raw_response: Returns the raw response from the API
255 | :return: If raw_response is True, returns the full response as a dict.
256 | Otherwise, returns a dict with the following structure:
257 | {
258 | "listings": List[Listing], # list of parsed listing objects
259 | "cursor": Optional[str] # cursor string for pagination
260 | }
261 | """
262 | self._validate_category(category)
263 | self._validate_sort_by(sort_by)
264 | self._validate_type(type_)
265 |
266 | parameters = (
267 | f'/listings?limit={limit}&sort_by={sort_by}'
268 | f'&category={category}&type={type_}'
269 | )
270 | if cursor is not None:
271 | parameters += f'&cursor={cursor}'
272 | if min_price is not None:
273 | parameters += f'&min_price={min_price}'
274 | if max_price is not None:
275 | parameters += f'&max_price={max_price}'
276 | if def_index is not None:
277 | if isinstance(def_index, Iterable) and not isinstance(def_index, str):
278 | def_index = ','.join(map(str, def_index))
279 | parameters += f'&def_index={def_index}'
280 | if min_float is not None:
281 | parameters += f'&min_float={min_float}'
282 | if max_float is not None:
283 | parameters += f'&max_float={max_float}'
284 | if rarity is not None:
285 | parameters += f'&rarity={rarity}'
286 | if paint_seed is not None:
287 | parameters += f'&paint_seed={paint_seed}'
288 | if paint_index is not None:
289 | parameters += f'&paint_index={paint_index}'
290 | if user_id is not None:
291 | parameters += f'&user_id={user_id}'
292 | if collection is not None:
293 | parameters += f'&collection={collection}'
294 | if market_hash_name is not None:
295 | parameters += f'&market_hash_name={market_hash_name}'
296 |
297 | method = 'GET'
298 | response = await self._request(method=method, parameters=parameters)
299 |
300 | if raw_response:
301 | return response
302 | listings = [Listing(data=item) for item in response["data"]]
303 | return {"listings": listings, "cursor": response.get("cursor")}
304 |
305 | async def get_specific_listing(
306 | self, listing_id: int, *, raw_response: bool = False
307 | ) -> Union[Listing, dict]:
308 | parameters = f'/listings/{listing_id}'
309 | method = 'GET'
310 |
311 | response = await self._request(method=method, parameters=parameters)
312 |
313 | if raw_response:
314 | return response
315 |
316 | return Listing(data=response)
317 |
318 | async def get_stall(
319 | self, user_id: int, *, limit: int = 40, raw_response: bool = False
320 | ) -> Optional[Stall]:
321 | """
322 | :param user_id: The ID of the user whose stall information is to be retrieved
323 | :param limit: The maximum number of listings to return. Defaults to 40.
324 | :param raw_response: Returns the raw response from the API
325 | :return: Optional[Stall]: A Stall object containing the user's listings if `raw_response` is False.
326 | """
327 | parameters = f'/users/{user_id}/stall?limit={limit}'
328 | method = 'GET'
329 |
330 | response = await self._request(method=method, parameters=parameters)
331 |
332 | if raw_response:
333 | return response
334 |
335 | return Stall(data=response)
336 |
337 | async def get_inventory(self):
338 | parameters = f"/me/inventory"
339 | method = "GET"
340 | response = await self._request(method=method, parameters=parameters)
341 | return response
342 |
343 | async def get_watchlist(self, limit: int = 40):
344 | parameters = f"/me/watchlist?limit={limit}"
345 | method = "GET"
346 | response = await self._request(method=method, parameters=parameters)
347 | return response
348 |
349 | async def get_offers(self, limit: int = 40):
350 | parameters = f"/me/offers-timeline?limit={limit}"
351 | method = "GET"
352 | response = await self._request(method=method, parameters=parameters)
353 | return response
354 |
355 | async def get_trade_history(self, role: str = "seller", limit: int = 30, page: int = 0):
356 | self._validate_role(role)
357 | parameters = f"/me/trades?role={role}&state=failed,cancelled,verified&limit={limit}&page={page}"
358 | method = "GET"
359 | response = await self._request(method=method, parameters=parameters)
360 | return response
361 |
362 | async def delete_listing(self, *, listing_id: int):
363 | parameters = f"/listings/{listing_id}"
364 | method = "DELETE"
365 | response = await self._request(method=method, parameters=parameters)
366 | return response
367 |
368 | async def delete_buy_order(self, *, id: int):
369 | parameters = f"/buy-orders/{id}"
370 | method = "DELETE"
371 | response = await self._request(method=method, parameters=parameters)
372 | return response
373 |
374 | async def delete_watchlist(self, *, id: int):
375 | parameters = f"/listings/{id}/watchlist"
376 | method = "DELETE"
377 | response = await self._request(method=method, parameters=parameters)
378 | return response
379 |
380 |
381 | async def create_listing(
382 | self,
383 | *,
384 | asset_id: str,
385 | price: float,
386 | type_: str = "buy_now",
387 | max_offer_discount: Optional[int] = None,
388 | reserve_price: Optional[float] = None,
389 | duration_days: Optional[int] = None,
390 | description: str = "",
391 | private: bool = False,
392 | ) -> Optional[dict]:
393 | """
394 | :param asset_id: The ID of the item to list
395 | :param price: The buy_now price or the current bid or reserve price on an auction
396 | :param type_: Either 'buy_now' or 'auction' (default: 'buy_now')
397 | :param max_offer_discount: The max discount for an offer (optional)
398 | :param reserve_price: The starting price for an auction (required if type is 'auction')
399 | :param duration_days: The auction duration in days (required if type is 'auction')
400 | :param description: User-defined description (optional)
401 | :param private: If true, will hide the listing from public searches (optional)
402 | :return: The response from the API
403 | """
404 | self._validate_type(type_)
405 |
406 | parameters = "/listings"
407 | method = "POST"
408 |
409 | json_data = {
410 | "asset_id": asset_id,
411 | "price": price,
412 | "type": type_,
413 | "description": description,
414 | "private": private
415 | }
416 |
417 | # Add optional parameters if provided
418 | if max_offer_discount is not None:
419 | json_data["max_offer_discount"] = max_offer_discount
420 | if reserve_price is not None:
421 | json_data["reserve_price"] = reserve_price
422 | if duration_days is not None:
423 | json_data["duration_days"] = duration_days
424 |
425 | response = await self._request(method=method, parameters=parameters, json_data=json_data)
426 | return response
427 |
428 | async def create_buy_order(
429 | self,
430 | *,
431 | market_hash_name: str,
432 | max_price: int,
433 | quantity: int
434 | ) -> Optional[dict]:
435 | parameters = "/buy-orders"
436 | method = "POST"
437 | json_data = {
438 | "market_hash_name": market_hash_name,
439 | "max_price": max_price,
440 | "quantity": quantity
441 | }
442 | response = await self._request(method=method, parameters=parameters, json_data=json_data)
443 | return response
444 |
445 | async def make_offer(
446 | self, *, listing_id: int, price: int
447 | ) -> Optional[dict]:
448 | parameters = "/offers"
449 | method = "POST"
450 | json_data = {
451 | "contract_id": str(listing_id),
452 | "price": price,
453 | "cancel_previous_offer": False
454 | }
455 | response = await self._request(method=method, parameters=parameters, json_data=json_data)
456 | return response
457 |
458 | async def buy_now(
459 | self, *, total_price: int, listing_id: str
460 | ) -> Optional[dict]:
461 | parameters = "/listings/buy"
462 | method = "POST"
463 |
464 | json_data = {
465 | "total_price": total_price,
466 | "contract_ids": [str(listing_id)]
467 | }
468 | response = await self._request(method=method, parameters=parameters, json_data=json_data)
469 | return response
470 |
471 | async def accept_sale(self, *, trade_ids: list[str]):
472 | parameters = "trades/bulk/accept"
473 | method = "POST"
474 |
475 | json_data = {
476 | "trade_ids": trade_ids
477 | }
478 |
479 | response = await self._request(method=method, parameters=parameters, json_data=json_data)
480 | return response
481 |
482 | async def update_listing_price(self, *, listing_id: int, price: int):
483 | parameters = f"/listings/{listing_id}"
484 | method = "PATCH"
485 |
486 | json_data = {
487 | "price": price
488 | }
489 | response = await self._request(method=method, parameters=parameters, json_data=json_data)
490 | return response
491 |
--------------------------------------------------------------------------------