mrkts 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
mrkts/__init__.py ADDED
@@ -0,0 +1,6 @@
1
+ """Asynchronous Python client for the MRKT API."""
2
+
3
+ from .client import MrktClient
4
+ from .pool import MrktPool
5
+
6
+ __all__ = ["MrktClient", "MrktPool"]
mrkts/auth.py ADDED
@@ -0,0 +1,12 @@
1
+ async def get_token(init_data: str, *, proxy: str | None = None) -> str:
2
+ from .client import MrktClient
3
+
4
+ async with MrktClient(token=None, proxy=proxy) as client:
5
+ return await client.auth(init_data)
6
+
7
+
8
+ async def refresh_token(session: str, *, workdir: str = ".", proxy: str | None = None) -> str:
9
+ from .utils import get_init_data
10
+
11
+ init_data = await get_init_data(session, workdir=workdir, proxy=proxy)
12
+ return await get_token(init_data, proxy=proxy)
mrkts/client.py ADDED
@@ -0,0 +1,145 @@
1
+ """Main object of MRKT client"""
2
+
3
+ from importlib import import_module
4
+ import random
5
+ from aiohttp import ClientSession
6
+ from .auth import get_token, refresh_token
7
+
8
+ _preloads = {
9
+ "market": (".market.client", "MarketClient"),
10
+ "steam": (".steam.client", "SteamClient"),
11
+ "user": (".user", "User"),
12
+ }
13
+
14
+ class MrktClient:
15
+ def __init__(
16
+ self,
17
+ token: str | None = None,
18
+ *,
19
+ session: str | None = None,
20
+ preload: tuple[str, ...] | None = None,
21
+ workdir: str = ".",
22
+ proxy: str | None = None,
23
+ headers: dict | None = None,
24
+ ):
25
+ self.token = token
26
+ self._session = session
27
+ self.workdir = workdir
28
+ self.proxy = proxy
29
+ self.headers = headers or {}
30
+ self._closed = False
31
+ self.http: ClientSession | None = None
32
+ self.web = ClientSession | None = None
33
+
34
+ for name in preload or []:
35
+ self._preload(name)
36
+
37
+ def _setup_http(self):
38
+ chrome, edge = random.randint(120, 145), random.randint(120, 145)
39
+
40
+ headers = {
41
+ "accept": "application/json, text/plain, */*",
42
+ "accept-encoding": "gzip, deflate",
43
+ "accept-language": "ru,en;q=0.9,en-GB;q=0.8,en-US;q=0.7",
44
+ "content-type": "application/json",
45
+ "origin": "https://cdn.tgmrkt.io",
46
+ "priority": "u=1, i",
47
+ "referer": "https://cdn.tgmrkt.io/",
48
+ "sec-ch-ua": f'"Not=A?Brand";v="8", "Chromium";v="{chrome}", "Microsoft Edge";v="{edge}"',
49
+ "sec-ch-ua-mobile": "?0",
50
+ "sec-ch-ua-platform": random.choice(['"Windows"', '"macOS"', '"Linux"']),
51
+ "sec-fetch-dest": "empty",
52
+ "sec-fetch-mode": "cors",
53
+ "sec-fetch-site": "same-site",
54
+ "sec-fetch-storage-access": "active",
55
+ "user-agent": f"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{chrome}.0.0.0 Safari/537.36 Edg/{edge}.0.0.0",
56
+ }
57
+ if self.token:
58
+ headers["authorization"] = f"Bearer {self.token}"
59
+ headers["cookie"] = f"access_token={self.token}"
60
+ self.headers.update(headers)
61
+
62
+ self.http = ClientSession(
63
+ base_url="https://api.tgmrkt.io/api/v1/",
64
+ headers=self.headers,
65
+ proxy=self.proxy,
66
+ )
67
+ self._closed = False
68
+
69
+ async def auth(self, tgWebAppData: str) -> str:
70
+ if not self.http:
71
+ self._setup_http()
72
+ response = await self.http.post("auth", json={"data": tgWebAppData})
73
+ response.raise_for_status()
74
+ data = response.json()
75
+ token = data.get("token")
76
+ if not token:
77
+ raise ValueError(f"Auth failed: {data}")
78
+ return token
79
+
80
+ async def start(self) -> "MrktClient":
81
+ if not self.token and self._session:
82
+ self.token = await refresh_token(session=self._session, workdir=self.workdir, proxy=self.proxy)
83
+ if not self.http and self.token:
84
+ self._setup_http()
85
+ return self
86
+
87
+ @classmethod
88
+ async def session(
89
+ cls,
90
+ session: str,
91
+ *,
92
+ workdir: str = ".",
93
+ proxy: str | None = None,
94
+ **kwargs
95
+ ):
96
+ token = await refresh_token(session, workdir=workdir, proxy=proxy)
97
+ return cls(token=token, workdir=workdir, proxy=proxy, **kwargs)
98
+
99
+ @classmethod
100
+ async def init_data(
101
+ cls,
102
+ init_data: str,
103
+ *,
104
+ workdir: str = ".",
105
+ proxy: str | None = None,
106
+ impersonate: str = "chrome124",
107
+ **kwargs,
108
+ ) -> "MrktClient":
109
+ token = await get_token(init_data, proxy=proxy)
110
+ return cls(token=token, workdir=workdir, proxy=proxy, impersonate=impersonate, **kwargs)
111
+
112
+ def set_token(self, token: str) -> None:
113
+ self.token = token
114
+ self.headers["authorization"] = f"Bearer {token}"
115
+ self.headers["cookie"] = f"access_token={token}"
116
+
117
+ if self.http is None:
118
+ self._setup_http()
119
+ else:
120
+ self.http.headers.update({
121
+ "authorization": f"Bearer {token}",
122
+ "cookie": f"access_token={token}",
123
+ })
124
+
125
+ def _preload(self, name: str) -> None:
126
+ try:
127
+ module_name, class_name = _preloads[name]
128
+ except KeyError:
129
+ raise ValueError(f"Unknown preload: {name!r}") from None
130
+
131
+ module = import_module(module_name, package=__package__)
132
+ cls = getattr(module, class_name)
133
+
134
+ setattr(self, name, cls(self))
135
+
136
+ async def __aenter__(self):
137
+ return await self.start()
138
+
139
+ async def __aexit__(self, *args):
140
+ await self.close()
141
+
142
+ async def close(self):
143
+ if not self._closed and self.http:
144
+ await self.http.close()
145
+ self._closed = True
mrkts/market/client.py ADDED
@@ -0,0 +1,11 @@
1
+ '''Market object of MRKT client'''
2
+
3
+ from .gifts import Gifts
4
+ from .orders import Orders
5
+ from .offers import Offers
6
+
7
+ class MarketClient:
8
+ def __init__(self, client):
9
+ self.gifts = Gifts(client)
10
+ self.orders = Orders(client)
11
+ self.offers = Offers(client)
mrkts/market/gifts.py ADDED
@@ -0,0 +1,195 @@
1
+ '''Gifts object of MRKT client'''
2
+
3
+ class Gifts:
4
+ def __init__(self, client):
5
+ self._client = client
6
+
7
+ async def saling(
8
+ self,
9
+ *,
10
+ count: int = 20,
11
+ cursor: str = "",
12
+ collectionNames: list[str] | None = None,
13
+ modelNames: list[str] | None = None,
14
+ backdropNames: list[str] | None = None,
15
+ symbolNames: list[str] | None = None,
16
+ number: int | None = None,
17
+ isNew: bool | None = None,
18
+ isPremarket: bool | None = None,
19
+ luckyBuy: bool | None = None,
20
+ giftType: str | None = None,
21
+ craftable: bool | None = None,
22
+ isCrafted: bool | None = None,
23
+ tgCanBeCraftedFrom: bool | None = None,
24
+ removeSelfSales: bool | None = None,
25
+ isTransferable: bool | None = None,
26
+ availableForStaking: bool | None = None,
27
+ forGame: bool | None = None,
28
+ minPrice: int | None = None,
29
+ maxPrice: int | None = None,
30
+ ordering: str = "Price",
31
+ lowToHigh: bool = True,
32
+ query: str | None = None,
33
+ **kwargs
34
+ ) -> dict:
35
+ payload = {
36
+ "count": count,
37
+ "cursor": cursor,
38
+ "collectionNames": collectionNames or [],
39
+ "modelNames": modelNames or [],
40
+ "backdropNames": backdropNames or [],
41
+ "symbolNames": symbolNames or [],
42
+ "number": number,
43
+ "isNew": isNew,
44
+ "isPremarket": isPremarket,
45
+ "luckyBuy": luckyBuy,
46
+ "giftType": giftType,
47
+ "craftable": craftable,
48
+ "isCrafted": isCrafted,
49
+ "tgCanBeCraftedFrom": tgCanBeCraftedFrom,
50
+ "removeSelfSales": removeSelfSales,
51
+ "isTransferable": isTransferable,
52
+ "availableForStaking": availableForStaking,
53
+ "forGame": forGame,
54
+ "minPrice": minPrice,
55
+ "maxPrice": maxPrice,
56
+ "ordering": ordering,
57
+ "lowToHigh": lowToHigh,
58
+ "query": query,
59
+ **kwargs
60
+ }
61
+ payload = {k: v for k, v in payload.items() if v is not None}
62
+
63
+ response = await self._client.http.post("gifts/saling", json=payload)
64
+ response.raise_for_status()
65
+ return await response.json()
66
+
67
+ async def buy(self, *, ids: list):
68
+ response = await self._client.http.post("gifts/buy", json={"ids": ids})
69
+ response.raise_for_status()
70
+ return await response.json()
71
+
72
+ async def sale(self, *, ids: list, price: int):
73
+ response = await self._client.http.post("gifts/sale", json={"ids": ids, "price": price})
74
+ response.raise_for_status()
75
+ return await response.json()
76
+
77
+ async def collections(self) -> list[dict]:
78
+ response = await self._client.http.get("gifts/collections")
79
+ response.raise_for_status()
80
+ return await response.json()
81
+
82
+ async def models(self, collections: list[str]) -> list[dict]:
83
+ response = await self._client.http.post("gifts/models", json={"collections": collections})
84
+ response.raise_for_status()
85
+ return await response.json()
86
+
87
+ async def backdrops(self, collections: list[str]) -> list[dict]:
88
+ response = await self._client.http.post("gifts/backdrops", json={"collections": collections})
89
+ response.raise_for_status()
90
+ return await response.json()
91
+
92
+ async def inventory(
93
+ self,
94
+ *,
95
+ isListed: bool = False,
96
+ count: int = 20,
97
+ cursor: str = "",
98
+ collectionNames: list[str] | None = None,
99
+ modelNames: list[str] | None = None,
100
+ backdropNames: list[str] | None = None,
101
+ symbolNames: list[str] | None = None,
102
+ number: int | None = None,
103
+ isNew: bool | None = None,
104
+ isPremarket: bool | None = None,
105
+ luckyBuy: bool | None = None,
106
+ giftType: str | None = None,
107
+ craftable: bool | None = None,
108
+ isCrafted: bool | None = None,
109
+ tgCanBeCraftedFrom: bool | None = None,
110
+ removeSelfSales: bool | None = None,
111
+ isTransferable: bool | None = None,
112
+ availableForStaking: bool | None = None,
113
+ forGame: bool | None = None,
114
+ minPrice: int | None = None,
115
+ maxPrice: int | None = None,
116
+ ordering: str = "None",
117
+ lowToHigh: bool = False,
118
+ query: str | None = None,
119
+ **kwargs
120
+ ) -> dict:
121
+ payload = {
122
+ "isListed": isListed,
123
+ "count": count,
124
+ "cursor": cursor,
125
+ "collectionNames": collectionNames or [],
126
+ "modelNames": modelNames or [],
127
+ "backdropNames": backdropNames or [],
128
+ "symbolNames": symbolNames or [],
129
+ "number": number,
130
+ "isNew": isNew,
131
+ "isPremarket": isPremarket,
132
+ "luckyBuy": luckyBuy,
133
+ "giftType": giftType,
134
+ "craftable": craftable,
135
+ "isCrafted": isCrafted,
136
+ "tgCanBeCraftedFrom": tgCanBeCraftedFrom,
137
+ "removeSelfSales": removeSelfSales,
138
+ "isTransferable": isTransferable,
139
+ "availableForStaking": availableForStaking,
140
+ "forGame": forGame,
141
+ "minPrice": minPrice,
142
+ "maxPrice": maxPrice,
143
+ "ordering": ordering,
144
+ "lowToHigh": lowToHigh,
145
+ "query": query,
146
+ **kwargs
147
+ }
148
+ payload = {k: v for k, v in payload.items() if v is not None}
149
+
150
+ response = await self._client.http.post("gifts", json=payload)
151
+ response.raise_for_status()
152
+ return await response.json()
153
+
154
+ async def history(self, *, limit: int = 20, type: str = "Gift", **kwargs) -> dict:
155
+ response = await self._client.http.get("history", json={"limit": limit, "type": type, **kwargs})
156
+ response.raise_for_status()
157
+ return await response.json()
158
+
159
+ async def feed(
160
+ self,
161
+ *,
162
+ count: int = 20,
163
+ cursor: str = "",
164
+ collectionNames: list[str] | None = None,
165
+ modelNames: list[str] | None = None,
166
+ backdropNames: list[str] | None = None,
167
+ number: int | None = None,
168
+ type: list[str] | None = None, # "listing", "sale", "return", "unlisting", "changeprice", "withdraw"
169
+ minPrice: int | None = None,
170
+ maxPrice: int | None = None,
171
+ ordering: str = "Latest",
172
+ lowToHigh: bool = False,
173
+ query: str | None = None,
174
+ **kwargs
175
+ ) -> dict:
176
+ payload = {
177
+ "count": count,
178
+ "cursor": cursor,
179
+ "collectionNames": collectionNames or [],
180
+ "modelNames": modelNames or [],
181
+ "backdropNames": backdropNames or [],
182
+ "number": number,
183
+ "type": type or [],
184
+ "minPrice": minPrice,
185
+ "maxPrice": maxPrice,
186
+ "ordering": ordering,
187
+ "lowToHigh": lowToHigh,
188
+ "query": query,
189
+ **kwargs
190
+ }
191
+ payload = {k: v for k, v in payload.items() if v is not None}
192
+
193
+ response = await self._client.http.post("feed", json=payload)
194
+ response.raise_for_status()
195
+ return await response.json()
mrkts/market/offers.py ADDED
@@ -0,0 +1,29 @@
1
+ """Offers object of MRKT client"""
2
+
3
+ class Offers:
4
+ def __init__(self, client):
5
+ self._client = client
6
+
7
+ async def create(self, price: int, giftSaleId: str):
8
+ payload = {
9
+ "price": price,
10
+ "giftSaleId": giftSaleId
11
+ }
12
+ response = await self._client.http.post("offers/create", json=payload)
13
+ response.raise_for_status()
14
+ return await response.json()
15
+
16
+ async def cancel(self, offerId: str):
17
+ response = await self._client.http.post(f"offers/cancel?offerId={offerId}")
18
+ response.raise_for_status()
19
+ return await response.json()
20
+
21
+ async def activities(self, *, count: int = 20, isActive: bool = True, filters: list = ["Offers", "ChannelOffers", "GiftsCollectionOffers"]):
22
+ response = await self._client.http.get("activities/with-totals", params={"count": count, "isActive": str(isActive).lower(), "filters": filters})
23
+ response.raise_for_status()
24
+ return await response.json()
25
+
26
+ async def myOffersByGift(self, id: str, count: int = 20):
27
+ response = await self._client.http.get(f"offers/by-gift/{id}?count={count}")
28
+ response.raise_for_status()
29
+ return await response.json()
mrkts/market/orders.py ADDED
@@ -0,0 +1,155 @@
1
+ """Orders object of MRKT client"""
2
+ from aiohttp import ClientResponseError
3
+
4
+ class Orders:
5
+ def __init__(self, client):
6
+ self._client = client
7
+
8
+
9
+ async def create(
10
+ self,
11
+ *,
12
+ collectionName: str = None,
13
+ modelName: str = None,
14
+ backdropName: str = None,
15
+ symbolName: str = None,
16
+ priceMinNanoTONs: int = 500000000,
17
+ priceMaxNanoTONs: int = 1000000000,
18
+ quantity: int = 1,
19
+ isTransferable: bool = False,
20
+ **kwargs
21
+ ):
22
+ payload = {
23
+ "collectionName": collectionName,
24
+ "modelName": modelName,
25
+ "backdropName": backdropName,
26
+ "symbolName": symbolName,
27
+ "priceMinNanoTONs": priceMinNanoTONs,
28
+ "priceMaxNanoTONs": priceMaxNanoTONs,
29
+ "quantity": quantity,
30
+ "isTransferable": isTransferable,
31
+ **kwargs
32
+ }
33
+ response = await self._client.http.post("orders/create", json=payload)
34
+ response.raise_for_status()
35
+ return await response.json()
36
+
37
+
38
+ async def cancel(self, orderId: str):
39
+ try:
40
+ response = await self._client.http.post(f"orders/cancel/{orderId}")
41
+ response.raise_for_status()
42
+ return True
43
+ except ClientResponseError:
44
+ return False
45
+ except Exception as e:
46
+ raise e
47
+
48
+
49
+ async def list(
50
+ self,
51
+ *,
52
+ collectionNames: list[str],
53
+ modelNames: list[str] = [],
54
+ backdropNames: list[str] = [],
55
+ symbolNames: list[str] = [],
56
+ availableForStaking: bool = None,
57
+ minPrice: float = None,
58
+ maxPrice: float = None,
59
+ ordering: str = "Price",
60
+ lowToHigh: bool = False,
61
+ query: str = None,
62
+ cursor: str = "",
63
+ count: int = 20,
64
+ **kwargs
65
+ ) -> list[dict]:
66
+ payload = {
67
+ "collectionNames": collectionNames,
68
+ "modelNames": modelNames,
69
+ "backdropNames": backdropNames,
70
+ "symbolNames": symbolNames,
71
+ "availableForStaking": availableForStaking,
72
+ "minPrice": minPrice,
73
+ "maxPrice": maxPrice,
74
+ "ordering": ordering,
75
+ "lowToHigh": lowToHigh,
76
+ "query": query,
77
+ "cursor": cursor,
78
+ "count": count,
79
+ **kwargs
80
+ }
81
+ response = await self._client.http.post("orders", json=payload)
82
+ response.raise_for_status()
83
+ return await response.json()
84
+
85
+
86
+ async def fill(self, *, orderId: str, giftIds: str):
87
+ response = await self._client.http.post("orders/fill", json={"orderId": orderId, "giftIds": giftIds})
88
+ response.raise_for_status()
89
+ return await response.json()
90
+
91
+
92
+ async def getMyOrders(
93
+ self,
94
+ *,
95
+ collectionNames: list = [],
96
+ modelNames: list = [],
97
+ backdropNames: list = [],
98
+ symbolNames: list = [],
99
+ availableForStaking: bool = None,
100
+ minPrice: float = None,
101
+ maxPrice: float = None,
102
+ ordering: str = "PublicationTime",
103
+ lowToHigh: bool = False,
104
+ query: str = None,
105
+ cursor: str = "",
106
+ count: int = 20,
107
+ **kwargs
108
+ ):
109
+ payload = {
110
+ "collectionNames": collectionNames,
111
+ "modelNames": modelNames,
112
+ "backdropNames": backdropNames,
113
+ "symbolNames": symbolNames,
114
+ "availableForStaking": availableForStaking,
115
+ "minPrice": minPrice,
116
+ "maxPrice": maxPrice,
117
+ "ordering": ordering,
118
+ "lowToHigh": lowToHigh,
119
+ "query": query,
120
+ "cursor": cursor,
121
+ "count": count,
122
+ **kwargs
123
+ }
124
+ response = await self._client.http.post("orders/get-my-orders", json=payload)
125
+ response.raise_for_status()
126
+ return await response.json()
127
+
128
+
129
+ async def allCollectionTop(self):
130
+ response = await self._client.http.get("orders/all-collection-top")
131
+ response.raise_for_status()
132
+ return await response.json()
133
+
134
+
135
+ async def giftsHistory(self, *, st_from: str = "", limit: int = 20, **kwargs):
136
+ payload = {
137
+ "from": st_from,
138
+ "limit": limit,
139
+ **kwargs
140
+ }
141
+ response = await self._client.http.post("orders/gifts-history", json=payload)
142
+ response.raise_for_status()
143
+ return await response.json()
144
+
145
+
146
+ async def top(self, *, collectionName: str, modelName: str = None, backdropName: str = None, symbolName: str = None):
147
+ payload = {
148
+ "collectionName": collectionName,
149
+ "modelName": modelName,
150
+ "backdropName": backdropName,
151
+ "symbolName": symbolName
152
+ }
153
+ response = await self._client.http.post("orders/top", json=payload)
154
+ response.raise_for_status()
155
+ return await response.json()
mrkts/pool.py ADDED
@@ -0,0 +1,101 @@
1
+ import asyncio
2
+ from typing import *
3
+
4
+ from .client import MrktClient
5
+ from .utils import get_tokens
6
+
7
+
8
+ class MrktPool:
9
+ def __init__(
10
+ self,
11
+ *,
12
+ tokens: list[str] | None = None,
13
+ sessions: list[str] | None = None,
14
+ workdir: str = ".",
15
+ tasks: list[tuple[str, dict]] | None = None,
16
+ proxy: str | None = None,
17
+ unlimited: bool = False
18
+ ):
19
+ self.clients: list["MrktClient"] = []
20
+ self._tasks = tasks
21
+ self.proxy = proxy
22
+ self.workdir = workdir
23
+ self._kwargs = {
24
+ "proxy": self.proxy,
25
+ "workdir": self.workdir
26
+ }
27
+ self._unlimited = unlimited
28
+
29
+ if tokens and sessions:
30
+ raise ValueError("Client can not run with tokens and sessions both")
31
+
32
+ if tokens:
33
+ self.clients = [MrktClient(token=token, **self._kwargs) for token in tokens]
34
+ elif sessions:
35
+ self._load_sessions(sessions)
36
+ else:
37
+ raise ValueError("Client can not run without tokens or sessions")
38
+
39
+ @classmethod
40
+ async def from_sessions(
41
+ cls,
42
+ sessions: list[str],
43
+ *,
44
+ workdir: str = ".",
45
+ proxy: str | None = None,
46
+ concurrency: int = 5,
47
+ **kwargs: Any,
48
+ ) -> "MrktPool":
49
+ tokens = await get_tokens(
50
+ sessions,
51
+ workdir=workdir,
52
+ proxy=proxy,
53
+ concurrency=concurrency,
54
+ )
55
+ return cls(
56
+ tokens=tokens,
57
+ workdir=workdir,
58
+ proxy=proxy,
59
+ **kwargs,
60
+ )
61
+
62
+ def _load_sessions(self, sessions: list[str]):
63
+ async def load():
64
+ tokens = await get_tokens(sessions, workdir=self.workdir, proxy=self.proxy)
65
+ self.clients = [MrktClient(token=t, **self._kwargs) for t in tokens]
66
+
67
+ self._token_future = load()
68
+
69
+ async def start(self):
70
+ if not self.clients and hasattr(self, "_token_future"):
71
+ await self._token_future
72
+ self.clients = [MrktClient(token=t, **self._kwargs) for t in self.clients]
73
+
74
+ async def map(
75
+ self,
76
+ coro_func: Callable[[MrktClient], Awaitable[Any]],
77
+ *,
78
+ concurrency: int = 5,
79
+ ) -> list[Any | Exception]:
80
+ if concurrency < 1 and not self._unlimited:
81
+ raise ValueError("concurrency must be greater than zero")
82
+
83
+ sem = asyncio.Semaphore(concurrency)
84
+
85
+ async def worker(client):
86
+ async with sem:
87
+ return await coro_func(client)
88
+
89
+ return await asyncio.gather(*(worker(c) for c in self.clients), return_exceptions=True)
90
+
91
+ async def close(self):
92
+ if self.clients:
93
+ await asyncio.gather(*[c.close() for c in self.clients], return_exceptions=True)
94
+ self.clients.clear()
95
+
96
+ async def __aenter__(self) -> "MrktPool":
97
+ await self.start()
98
+ return self
99
+
100
+ async def __aexit__(self, *args: Any) -> None:
101
+ await self.close()