xync-client 0.0.155__py3-none-any.whl → 0.0.156.dev18__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.
@@ -0,0 +1,194 @@
1
+ from decimal import Decimal
2
+ from typing import Literal
3
+
4
+ from pydantic import BaseModel, NonNegativeInt, Field, PositiveInt
5
+
6
+
7
+ class C2COrder(BaseModel):
8
+ cancelCountDown: NonNegativeInt
9
+ consultCancelCountDown: NonNegativeInt
10
+
11
+
12
+ class OrderItem(BaseModel):
13
+ orderId: int
14
+ counterpartUid: PositiveInt
15
+ counterpartNickName: str
16
+ counterpartIsOnline: bool
17
+ counterpartOrderCount: NonNegativeInt
18
+ counterpartLastTradeTime: NonNegativeInt # timestamp in ms
19
+ counterpartMerchantLevel: NonNegativeInt
20
+ counterpartThumbUp: NonNegativeInt
21
+ counterpartRocketAmount: NonNegativeInt | None
22
+ counterpartPrimeLevel: str = Field(pattern=r"^Prime\d+$")
23
+ counterpartRecentWithdrawTime: PositiveInt | None # timestamp in ms
24
+ side: Literal[0, 1] # 0=buy, 1=sell
25
+ tradeMode: Literal[1, 2] # предполагаемые значения
26
+ runMode: Literal[1, 2] # предполагаемые значения
27
+ quoteAssetName: str = Field(min_length=1, max_length=10)
28
+ cryptoAssetName: str = Field(min_length=1, max_length=10)
29
+ amount: Decimal = Field(gt=0, decimal_places=2)
30
+ quantity: Decimal = Field(gt=0)
31
+ quote: Decimal = Field(gt=0, decimal_places=2)
32
+ orderStatus: NonNegativeInt
33
+ c2cOrder: C2COrder = None
34
+ inNegotiation: bool
35
+
36
+
37
+ class ModelField(BaseModel):
38
+ fieldId: str
39
+ name: str
40
+ fieldType: Literal["payee", "pay_account", "bank", "sub_bank", "qr_code"]
41
+ index: PositiveInt
42
+ maxLength: int | None = None
43
+ required: bool | None = None
44
+ copyable: bool
45
+ remindWord: str | None = None
46
+ valueType: str | None = None
47
+ value: str
48
+ nameList: list | None = None
49
+ remindWordList: list | None = None
50
+
51
+
52
+ class PaymentMethod(BaseModel):
53
+ id: PositiveInt
54
+ userName: str = Field(min_length=1)
55
+ bankType: PositiveInt
56
+ bankNumber: str
57
+ bankName: str | None = None
58
+ bankAddress: str | None = None
59
+ qrCode: str | None = None
60
+ color: str = Field(pattern=r"^#[0-9A-F]{6}$")
61
+ payMethodName: str = Field(min_length=1)
62
+ paymentStatus: Literal[1]
63
+ modelFieldsList: list[ModelField]
64
+
65
+
66
+ class OrderInfo(BaseModel):
67
+ orderId: PositiveInt
68
+ orderNo: PositiveInt
69
+ uid: PositiveInt
70
+ nickName: str = Field(min_length=1)
71
+ realName: str | None = None
72
+ roleName: Literal["maker", "taker"]
73
+ side: Literal[0, 1]
74
+ runMode: Literal[1]
75
+ tradeMode: Literal[1]
76
+ liquidDivision: Literal[3]
77
+ sideName: Literal["buy", "sell"]
78
+ quoteAssetId: PositiveInt
79
+ quoteAssetType: Literal[1]
80
+ quoteAssetName: str = Field(min_length=1, max_length=10)
81
+ quoteAssetSymbol: str
82
+ cryptoAssetId: PositiveInt
83
+ cryptoAssetType: Literal[2]
84
+ cryptoAssetName: str = Field(min_length=1, max_length=10)
85
+ cryptoAssetSymbol: str
86
+ amount: Decimal = Field(gt=0, decimal_places=2)
87
+ quantity: Decimal = Field(gt=0)
88
+ quote: Decimal = Field(gt=0, decimal_places=2)
89
+ orderStatus: NonNegativeInt
90
+ gmtCreate: PositiveInt
91
+ gmtModified: PositiveInt
92
+ areaType: Literal[1]
93
+ appealCountDown: NonNegativeInt
94
+
95
+
96
+ class OtherInfo(BaseModel):
97
+ uid: PositiveInt
98
+ nickName: str = Field(min_length=1)
99
+ realName: str | None = None
100
+ gmtCreate: PositiveInt
101
+ merchantLevel: NonNegativeInt
102
+ realTradeCountBuy: NonNegativeInt
103
+ realTradeCountSell: NonNegativeInt
104
+ registerTime: PositiveInt
105
+ isPhoneBind: bool
106
+ marginAssetId: NonNegativeInt
107
+ marginAssetName: str | None = None
108
+ marginAmount: NonNegativeInt
109
+ appealMonthTimes: NonNegativeInt
110
+ appealMonthWinTimes: NonNegativeInt
111
+ isOnline: bool
112
+ isSeniorAuth: bool
113
+ orderCompleteRate: Decimal = Field(ge=0, le=100, decimal_places=2)
114
+ tradeMonthCount: NonNegativeInt
115
+ tradeCount: NonNegativeInt
116
+ releaseTime: NonNegativeInt
117
+ buyCompleteRate: Decimal = Field(ge=0, le=100, decimal_places=2)
118
+ buyCancelTimeAvg: Decimal = Field(ge=0, decimal_places=2)
119
+ thumbUp: NonNegativeInt
120
+ merchantTags: str | None = None
121
+ totalUserTradeCount: NonNegativeInt
122
+ totalTradeOrderCount: NonNegativeInt
123
+ totalTradeOrderCancelCount: NonNegativeInt
124
+ orderBuyCompleteRate: Decimal = Field(ge=0, le=100, decimal_places=2)
125
+ orderSellCompleteRate: Decimal = Field(ge=0, le=100, decimal_places=2)
126
+ totalOrderCompleteRate: Decimal = Field(ge=0, le=100, decimal_places=2)
127
+ counterpartRocketAmount: int | None = None
128
+ counterpartPrimeLevel: str = Field(pattern=r"^Prime\d+$")
129
+ counterpartRecentWithdrawTime: PositiveInt | None = None
130
+ counterpartOrderCount: NonNegativeInt
131
+ counterpartLastTradeTime: NonNegativeInt
132
+
133
+
134
+ class Fee(BaseModel):
135
+ feeType: Literal[1]
136
+ feeStatus: Literal[1, 2]
137
+ fee: Decimal = Field(ge=0)
138
+ feeName: str = Field(min_length=1)
139
+
140
+
141
+ class FeeInfo(BaseModel):
142
+ totalFee: Decimal = Field(ge=0)
143
+ feeList: list[Fee]
144
+
145
+
146
+ class OrderTag(BaseModel):
147
+ isSoonLock: Literal[1, 2]
148
+ isPremature: Literal[1, 2]
149
+ isAppeal: Literal[1, 2]
150
+ specialCancelFlag: Literal[1, 2]
151
+ isPhone: Literal[1, 2]
152
+ now: PositiveInt
153
+ isAppealPremature: Literal[1, 2]
154
+ isFollowed: bool
155
+ isShield: bool
156
+ negotiationStatus: int | None = None
157
+
158
+
159
+ class C2COrderDetail(BaseModel):
160
+ matchPayId: int | None = None
161
+ payTerm: PositiveInt
162
+ payCode: str | None = None
163
+ quote: Decimal = Field(gt=0, decimal_places=2)
164
+ amount: Decimal = Field(gt=0, decimal_places=2)
165
+ quantity: Decimal = Field(gt=0)
166
+ quoteAssetName: str = Field(min_length=1)
167
+ cryptoAssetName: str = Field(min_length=1)
168
+ buyPayAccount: PositiveInt | None
169
+ gmtPay: PositiveInt | None
170
+ gmtResetCancel: PositiveInt | None = None
171
+ orderStatus: NonNegativeInt
172
+ cancelCountDown: NonNegativeInt
173
+ consultCancelCountDown: NonNegativeInt
174
+ waitCompletedCountDown: NonNegativeInt
175
+ areaType: Literal[1]
176
+ acceptStatus: Literal[0, 1]
177
+ appCountDown: NonNegativeInt
178
+ appMaxCountDown: NonNegativeInt
179
+
180
+
181
+ class OrderSnapshot(BaseModel):
182
+ tradeInstructionStatus: Literal["INIT", "COMPLETED"] # добавь другие статусы
183
+
184
+
185
+ class OrderFull(BaseModel):
186
+ orderInfo: OrderInfo
187
+ otherInfo: OtherInfo
188
+ paymentMethod: list[PaymentMethod]
189
+ feeInfo: FeeInfo
190
+ orderTag: OrderTag
191
+ c2cOrder: C2COrderDetail
192
+ inNegotiation: bool
193
+ takerEvaluateStatus: Literal[0, 1]
194
+ orderSnapshot: OrderSnapshot
xync_client/Htx/ex.py CHANGED
@@ -42,22 +42,22 @@ class ExClient(BaseExClient):
42
42
  pair[rcurs[cur["name"]]] += [coen]
43
43
  return tuple(pairs.values())
44
44
 
45
- async def _ads(self, req: GetAds, lim: int = None, vm_filter: bool = False, **kwargs) -> list[ad.Resp]:
46
- params = {
47
- "coinId": req.coin_id,
48
- "currency": req.cur_id,
49
- "tradeType": "sell" if req.is_sell else "buy",
50
- "currPage": 1,
51
- "payMethod": ",".join(req.pm_ids) if req.pm_ids else 0,
52
- "acceptOrder": 0,
53
- "blockType": "general",
54
- "online": 1,
55
- "range": 0,
56
- "amount": req.amount or "",
57
- "onlyTradable": "false",
58
- "isFollowed": "false",
59
- }
60
- res = (await self._get("/-/x/otc/v1/data/trade-market", params))["data"]
45
+ async def x2e_req_ads(self, xreq: GetAds) -> ad.AdsReq:
46
+ coin_id, _ = await self.x2e_coin(xreq.coin_id)
47
+ cur_id, _, __ = await self.x2e_cur(xreq.cur_id)
48
+ ereq = ad.AdsReq(
49
+ coinId=int(coin_id),
50
+ currency=int(cur_id),
51
+ tradeType="sell" if xreq.is_sell else "buy",
52
+ payMethod=",".join([await self.x2e_pm(pm_id) for pm_id in xreq.pm_ids]),
53
+ )
54
+ if xreq.amount:
55
+ ereq.amount = str(xreq.amount)
56
+ # todo: all kwargs
57
+ return ereq
58
+
59
+ async def _ads(self, req: ad.AdsReq, lim: int = None, vm_filter: bool = False, **kwargs) -> list[ad.Resp]:
60
+ res = (await self._get("/-/x/otc/v1/data/trade-market", req.model_dump()))["data"]
61
61
  ads = [ad.Resp(**a) for a in res]
62
62
  return ads
63
63
 
xync_client/Mexc/agent.py CHANGED
@@ -1,25 +1,86 @@
1
- from asyncio import run
1
+ import json
2
+ import logging
3
+ from asyncio import run, sleep, create_task
2
4
  from hashlib import md5
5
+ from urllib.parse import quote
3
6
  from uuid import uuid4
4
7
 
8
+ import websockets
9
+ from blackboxprotobuf import protobuf_to_json
5
10
  from pyro_client.client.file import FileClient
6
11
  from xync_bot import XyncBot
12
+
13
+ from xync_client.Mexc.api import MEXCP2PApiClient
14
+ from xync_client.Mexc.etype import ad
15
+
16
+ from xync_client.Abc.xtype import GetAds, AdUpd
7
17
  from xync_client.Bybit.etype.order import TakeAdReq
18
+ from xync_client.Mexc.etype.order import OrderDetail
8
19
 
9
20
  from xync_client.loader import PAY_TOKEN, NET_TOKEN
10
21
  from xync_schema import models
11
- from xync_schema.enums import UserStatus
22
+ from xync_schema.enums import UserStatus, AgentStatus
12
23
 
13
24
  from xync_client.Abc.Agent import BaseAgentClient
14
25
 
15
26
 
16
27
  class AgentClient(BaseAgentClient):
17
- i: int = 0
28
+ i: int = 5
18
29
  headers = {
19
- "accept-language": "ru,en;q=0.9",
20
- "language:": "ru-RU",
21
- "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
30
+ # "Accept-Encoding": "gzip, deflate, br, zstd",
31
+ "Accept-Language": "ru,en;q=0.9",
32
+ "Language:": "ru-RU",
33
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36",
22
34
  }
35
+ api: MEXCP2PApiClient
36
+
37
+ @staticmethod
38
+ async def _heartbeat(ws):
39
+ """Фоновая задача для PING/PONG"""
40
+ while True:
41
+ await sleep(25)
42
+ await ws.send('{"method":"PING"}')
43
+
44
+ async def _ws_key(self):
45
+ self.i = 13 if self.i > 9999 else self.i + 1
46
+ hdrs = self.agent.auth["headers"] | {
47
+ "trochilus-trace-id": "c7831459-3bb2-4aa7-bf5d-9ff2adef0c08-0062",
48
+ "ucenter-token": self.agent.auth["cookies"]["u_id"],
49
+ }
50
+ # resp = get("https://www.mexc.com/ucenter/api/ws_token", headers=hdrs, cookies=self.agent.auth['cookies'])
51
+ resp = await self._get("/ucenter/api/ws_token", hdrs=hdrs)
52
+ self.wkk = resp["data"]["wsToken"]
53
+
54
+ async def ws_prv(self):
55
+ await self._ws_key()
56
+ url = f"wss://wbs.mexc.com/ws?wsToken={self.wkk}"
57
+ async with websockets.connect(url) as ws:
58
+ create_task(self._heartbeat(ws))
59
+ await ws.send('{"method":"SUBSCRIPTION","params":["otc@private.p2p.orders.pb"],"id":12}')
60
+ await ws.send('{"method":"SUBSCRIPTION","params":["common@private.risk.result.pb"],"id":11}')
61
+ while resp := await ws.recv():
62
+ try:
63
+ data: dict = json.loads(resp)
64
+ except UnicodeDecodeError:
65
+ msg, typedef = protobuf_to_json(resp)
66
+ data = json.loads(msg)
67
+ await self.recv(data)
68
+ if data.get("msg") == "PONG":
69
+ print(end="p")
70
+ else:
71
+ logging.warning(data)
72
+
73
+ async def recv(self, data: dict):
74
+ if data["1"] == "otc@private.p2p.orders.pb":
75
+ o = data["218"]["1"]
76
+ order: OrderDetail = (await self.api.get_order_detail(o["1"])).data
77
+ if order.side == "SELL":
78
+ if order.state == "NOT_PAID":
79
+ ...
80
+ elif order.side == "BUY":
81
+ if order.state == "PAID":
82
+ ...
83
+ ...
23
84
 
24
85
  async def _take_ad(self, req: TakeAdReq):
25
86
  self.i = 33 if self.i > 9998 else self.i + 2
@@ -39,7 +100,7 @@ class AgentClient(BaseAgentClient):
39
100
  "authVersion": "v2",
40
101
  "deviceId": auth["mtoken"],
41
102
  }
42
- res = await self._post("/api/verify/second_auth/risk/scene", json=data, hdrs=hdrs)
103
+ res = await self._post("/api/platform/p2p/api/verify/second_auth/risk/scene", json=data, hdrs=hdrs)
43
104
  data = {
44
105
  "amount": req.amount,
45
106
  "authVersion": "v2",
@@ -50,9 +111,36 @@ class AgentClient(BaseAgentClient):
50
111
  }
51
112
  self.i = 33 if self.i > 9999 else self.i + 1
52
113
  hdrs = self.headers | {"trochilus-trace-id": f"{uuid4()}-{self.i:04d}"}
53
- res = await self._post("/api/order/deal?mhash=" + auth["mhash"], data=auth | data, hdrs=hdrs)
114
+ res = await self._post("/api/platform/p2p/api/order/deal?mhash=" + auth["mhash"], data=auth | data, hdrs=hdrs)
54
115
  return res["data"]
55
116
 
117
+ async def x2e_req_ad_upd(self, xreq: AdUpd) -> ad.AdUpd:
118
+ coin_id, coin_scale = await self.ex_client.x2e_coin(xreq.coin_id)
119
+ cur_id, cur_scale, minimum = await self.ex_client.x2e_cur(xreq.cur_id)
120
+ ereq = ad.AdUpd(
121
+ id=xreq.id,
122
+ price=round(xreq.price, cur_scale),
123
+ coinId=coin_id,
124
+ currency=cur_id,
125
+ tradeType="SELL" if xreq.is_sell else "BUY",
126
+ deviceId=self.agent.auth["deviceId"],
127
+ payment=",".join([str(cdx.exid) for cdx in xreq.credexs]),
128
+ priceType=0,
129
+ minTradeLimit=minimum,
130
+ maxTradeLimit=round(xreq.max_amount or xreq.amount - 10**-cur_scale, cur_scale),
131
+ quantity=round(xreq.quantity or xreq.amount / xreq.price, coin_scale),
132
+ )
133
+ if xreq.cond:
134
+ ereq.autoResponse = quote(xreq.cond)
135
+ # todo: all kwargs
136
+ return ereq
137
+
138
+ async def _ad_upd(self, req: ad.AdUpd):
139
+ self.i = 33 if self.i > 9999 else self.i + 1
140
+ hdrs = self.headers | {"trochilus-trace-id": f"{uuid4()}-{self.i:04d}"}
141
+ res = await self._put("/api/platform/p2p/api/merchant/order", form_data=req.model_dump(), hdrs=hdrs)
142
+ return res["code"]
143
+
56
144
 
57
145
  async def main():
58
146
  from x_model import init_db
@@ -60,10 +148,11 @@ async def main():
60
148
 
61
149
  cn = await init_db(TORM, True)
62
150
 
151
+ ex = await models.Ex[12]
63
152
  agent = (
64
153
  await models.Agent.filter(
65
- actor__ex_id=12,
66
- active=True,
154
+ actor__ex=ex,
155
+ status__gte=AgentStatus.race,
67
156
  auth__isnull=False,
68
157
  actor__person__user__status=UserStatus.ACTIVE,
69
158
  actor__person__user__pm_agents__isnull=False,
@@ -71,12 +160,106 @@ async def main():
71
160
  .prefetch_related("actor__ex", "actor__person__user__gmail")
72
161
  .first()
73
162
  )
74
-
75
163
  bbot = XyncBot(PAY_TOKEN, cn)
76
164
  fbot = FileClient(NET_TOKEN)
165
+ ecl = ex.client(fbot)
166
+ cl: AgentClient = agent.client(ecl, fbot, bbot)
167
+ cl.api = MEXCP2PApiClient(agent.auth["key"], agent.auth["sec"])
168
+ create_task(cl.ws_prv())
169
+
170
+ while True:
171
+ bceil = 106
172
+ sceil = 124.98
173
+ breq = GetAds(coin_id=1, cur_id=1, is_sell=False, pm_ids=[366])
174
+ sreq = GetAds(coin_id=1, cur_id=1, is_sell=True, pm_ids=[366])
175
+ breq_upd = AdUpd(
176
+ id="a1574183931501582340", price=87, **{**breq.model_dump(), "amount": 11000.01}, max_amount=4370
177
+ ) # + 1 cent
178
+ sreq_upd = AdUpd(id="a1594624084590445568", price=150, **{**sreq.model_dump(), "amount": 30000.01})
179
+
180
+ await sleep(5)
181
+ bads: list[ad.Ad] = await cl.ex_client.ads(breq)
182
+ if bads[0].price >= sceil:
183
+ bad: ad.Ad = bads.pop(0)
184
+ await cl.bbot.send(
185
+ 193017646,
186
+ f"price: {bad.price}\nnick: {bad.merchant.nickName}\nmax:{bad.maxPayLimit}"
187
+ f"\nqty: {bad.availableQuantity} [{bad.minTradeLimit}-{bad.maxTradeLimit}]",
188
+ )
189
+ # am = min(bad.maxTradeLimit, max(10000.0, bad.minTradeLimit))
190
+ # req = TakeAdReq(
191
+ # ad_id=bad.exid,
192
+ # amount=am,
193
+ # pm_id=366,
194
+ # is_sell=False,
195
+ # coin_id=1,
196
+ # cur_id=1,
197
+ # )
198
+ # ord_resp: OrderResp = await cl.take_ad(req)
199
+
200
+ bads = [
201
+ a
202
+ for a in bads
203
+ if a.price <= bceil and a.availableQuantity > 20 and (a.maxTradeLimit - a.minTradeLimit > 1000)
204
+ ]
205
+ if len(bads) > 1:
206
+ if bads[0].merchant.nickName == cl.actor.name:
207
+ if round(bads[0].price - bads[1].price, 2) > 0.01:
208
+ breq_upd.price = bads[1].price + 0.01
209
+ if _ := await cl.ad_upd(breq_upd):
210
+ await sleep(5, print(_, flush=True))
211
+ print(end="!", flush=True)
212
+ elif bads[0].price != (trgt_price := bads[0].price + 0.01):
213
+ breq_upd.price = trgt_price
214
+ if _ := await cl.ad_upd(breq_upd):
215
+ await sleep(5, print(_, flush=True))
216
+ print(end="!", flush=True)
217
+
218
+ await sleep(5)
219
+ sads: list[ad.Ad] = await cl.ex_client.ads(sreq)
220
+ if sads[0].price <= bceil:
221
+ sad: ad.Ad = sads.pop(0)
222
+ await cl.bbot.send(
223
+ 193017646,
224
+ f"price: {sad.price}\nnick: {sad.merchant.nickName}\nmax:{sad.maxPayLimit}"
225
+ f"\nqty: {sad.availableQuantity} [{sad.minTradeLimit}-{sad.maxTradeLimit}]",
226
+ )
227
+ # am = min(sad.maxTradeLimit, max(10000.0, sad.minTradeLimit))
228
+ # req = TakeAdReq(
229
+ # ad_id=sad.exid,
230
+ # amount=am,
231
+ # pm_id=366,
232
+ # is_sell=False,
233
+ # coin_id=1,
234
+ # cur_id=1,
235
+ # )
236
+ # ord_resp: OrderResp = await cl.take_ad(req)
237
+
238
+ sads = [
239
+ a
240
+ for a in sads
241
+ if a.price >= sceil and a.availableQuantity > 20 and (a.maxTradeLimit - a.minTradeLimit > 1000)
242
+ ]
243
+ if len(sads) > 1:
244
+ if sads[0].merchant.nickName == cl.actor.name:
245
+ if round(sads[1].price - sads[0].price, 2) > 0.01:
246
+ sreq_upd.price = sads[1].price - 0.01
247
+ if _ := await cl.ad_upd(sreq_upd):
248
+ await sleep(15, print(_, flush=True))
249
+ continue
250
+ print(end="!", flush=True)
251
+ continue
252
+ elif sads[0].price > sceil:
253
+ sreq_upd.price = sads[0].price - 0.01
254
+ if _ := await cl.ad_upd(sreq_upd):
255
+ await sleep(15, print(_, flush=True))
256
+ continue
257
+ print(end="!", flush=True)
258
+ continue
259
+
260
+ print(end=".", flush=True)
77
261
 
78
- cl = agent.client(fbot, bbot)
79
- req = TakeAdReq(ad_id="a1574088909645125632", amount=500, pm_id=366, cur_="RUB", price=85.8, is_sell=True)
262
+ req = TakeAdReq(ad_id="a1574088909645125632", amount=500, pm_id=366, cur_id=1, price=85.8, is_sell=True)
80
263
  res = await cl.take_ad(req)
81
264
  print(res)
82
265