xync-client 0.0.141__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.
Files changed (40) hide show
  1. xync_client/Abc/AdLoader.py +5 -0
  2. xync_client/Abc/Agent.py +354 -8
  3. xync_client/Abc/Ex.py +432 -25
  4. xync_client/Abc/HasAbotUid.py +10 -0
  5. xync_client/Abc/InAgent.py +0 -11
  6. xync_client/Abc/PmAgent.py +34 -26
  7. xync_client/Abc/xtype.py +57 -3
  8. xync_client/Bybit/InAgent.py +233 -409
  9. xync_client/Bybit/agent.py +844 -777
  10. xync_client/Bybit/etype/__init__.py +0 -0
  11. xync_client/Bybit/etype/ad.py +54 -86
  12. xync_client/Bybit/etype/cred.py +29 -9
  13. xync_client/Bybit/etype/order.py +75 -103
  14. xync_client/Bybit/ex.py +35 -48
  15. xync_client/Gmail/__init__.py +119 -98
  16. xync_client/Htx/agent.py +213 -40
  17. xync_client/Htx/etype/ad.py +40 -16
  18. xync_client/Htx/etype/order.py +194 -0
  19. xync_client/Htx/ex.py +17 -19
  20. xync_client/Mexc/agent.py +268 -0
  21. xync_client/Mexc/api.py +1255 -0
  22. xync_client/Mexc/etype/ad.py +52 -1
  23. xync_client/Mexc/etype/order.py +354 -0
  24. xync_client/Mexc/ex.py +34 -22
  25. xync_client/Okx/1.py +14 -0
  26. xync_client/Okx/agent.py +39 -0
  27. xync_client/Okx/ex.py +8 -8
  28. xync_client/Pms/Payeer/agent.py +396 -0
  29. xync_client/Pms/Payeer/login.py +1 -59
  30. xync_client/Pms/Payeer/trade.py +58 -0
  31. xync_client/Pms/Volet/__init__.py +82 -63
  32. xync_client/Pms/Volet/api.py +5 -4
  33. xync_client/loader.py +2 -0
  34. xync_client/pm_unifier.py +1 -1
  35. {xync_client-0.0.141.dist-info → xync_client-0.0.156.dev18.dist-info}/METADATA +5 -1
  36. {xync_client-0.0.141.dist-info → xync_client-0.0.156.dev18.dist-info}/RECORD +38 -29
  37. xync_client/Pms/Payeer/__init__.py +0 -253
  38. xync_client/Pms/Payeer/api.py +0 -25
  39. {xync_client-0.0.141.dist-info → xync_client-0.0.156.dev18.dist-info}/WHEEL +0 -0
  40. {xync_client-0.0.141.dist-info → xync_client-0.0.156.dev18.dist-info}/top_level.txt +0 -0
@@ -1,113 +1,134 @@
1
- from playwright.async_api import Page, Playwright
2
- from pyro_client.client.file import FileClient
3
- from pyro_client.client.user import UserClient
1
+ import logging
2
+ import pickle
3
+ import re
4
+ from base64 import urlsafe_b64decode
5
+ from datetime import datetime
6
+
7
+ from google.auth.transport.requests import Request
8
+ from google_auth_oauthlib.flow import InstalledAppFlow
9
+ from googleapiclient.discovery import Resource, build
10
+ from requests import get
4
11
  from xync_schema.models import User, Gmail
5
12
 
6
- from xync_client.loader import NET_TOKEN
13
+ from xync_client.Abc.HasAbotUid import HasAbotUid
14
+ from xync_client.loader import TORM
15
+
16
+ # Область доступа
17
+ SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"]
18
+
19
+
20
+ class GmClient(HasAbotUid):
21
+ service: Resource
22
+
23
+ def __init__(self, user: User):
24
+ """Авторизация и создание сервиса Gmail API"""
25
+ creds = None
26
+ # Файл token.pickle хранит токены доступа пользователя
27
+ if user.gmail.token:
28
+ creds = pickle.loads(user.gmail.token)
29
+
30
+ # Если нет валидных credentials, запрашиваем авторизацию
31
+ if not creds or not creds.valid:
32
+ if creds and creds.expired and creds.refresh_token:
33
+ creds.refresh(Request())
34
+ else:
35
+ flow = InstalledAppFlow.from_client_config(user.gmail.auth, SCOPES)
36
+ creds = flow.run_local_server(port=0)
7
37
 
38
+ # Сохраняем credentials для следующего запуска
39
+ user.gmail.token = pickle.dumps(creds)
8
40
 
9
- class GmClient:
10
- uid: int
11
- user: User
12
- page: Page
13
- bot: UserClient = None
14
- HOME = "https://mail.google.com/mail/u/0/"
41
+ self.service = build("gmail", "v1", credentials=creds)
42
+ self.uid = user.username_id
15
43
 
16
- def __init__(self, uid: int):
17
- self.uid = uid
44
+ def _get_last_email(self, sender_email, subject_keyword=None):
45
+ """
46
+ Получить последнее письмо от определенного отправителя
18
47
 
19
- async def start(self, pw: Playwright, headed: bool = False):
20
- self.user = await User.get(username_id=self.uid).prefetch_related("gmail")
21
- if not self.user.gmail:
22
- self.user.gmail = await Gmail.create(
23
- login=input(f"{self.user.last_name} gmail login:"), password=input("password:")
48
+ Args:
49
+ sender_email: email отправителя (например, 'example@gmail.com')
50
+ subject_keyword: ключевое слово в теме (опционально)
51
+ """
52
+
53
+ def _get_email_body(payload):
54
+ """Извлечь текст письма из payload"""
55
+ if "body" in payload and "data" in payload["body"]:
56
+ return urlsafe_b64decode(payload["body"]["data"]).decode("utf-8")
57
+ return ""
58
+
59
+ # Формируем поисковый запрос
60
+ query = f"from:{sender_email}"
61
+ if subject_keyword:
62
+ query += f" subject:{subject_keyword}"
63
+
64
+ # Ищем письма с этим запросом
65
+ results = (
66
+ self.service.users()
67
+ .messages()
68
+ .list(
69
+ userId="me",
70
+ q=query,
71
+ maxResults=1, # Только последнее письмо
24
72
  )
25
- browser = await pw.chromium.launch(
26
- channel="chrome",
27
- headless=not headed,
28
- args=[
29
- "--disable-blink-features=AutomationControlled",
30
- "--no-sandbox",
31
- "--disable-web-security",
32
- "--disable-infobars",
33
- "--disable-extensions",
34
- "--start-maximized",
35
- ],
73
+ .execute()
36
74
  )
37
- context = await browser.new_context(storage_state=self.user.gmail.auth, locale="en")
38
- self.page = await context.new_page()
39
- # go home
40
- await self.page.goto(self.HOME, timeout=62000)
41
- if not self.page.url.startswith(self.HOME):
42
- if await ( # ваще с 0 заходим
43
- sgn_btn := self.page.locator(
44
- 'header a[href^="https://accounts.google.com/AccountChooser/signinchooser"]:visible',
45
- has_text="sign",
46
- )
47
- ).count():
48
- await sgn_btn.click()
49
- if self.page.url.startswith("https://accounts.google.com/v3/signin/accountchooser"): # надо выбрать акк
50
- await self.page.locator("li").first.click()
51
- # если предлагает залогиниться
52
- elif await self.page.locator("h1#headingText", has_text="Sign In").count():
53
- await self.page.fill("input[type=email]", self.user.gmail.login)
54
- await self.page.locator("button", has_text="Next").click()
55
- # осталось ввести пороль:
56
- await self.page.fill("input[type=password]", self.user.gmail.password)
57
- await self.page.locator("#passwordNext").click()
58
- await self.page.wait_for_timeout(3000)
59
- if self.page.url.startswith("https://accounts.google.com/v3/signin/challenge/dp"):
60
- await self.load_bot()
61
- await self.bot.receive("Аппрувни гмейл, у тебя 1.5 минуты", photo=await self.page.screenshot())
62
- await self.page.wait_for_url(lambda u: u.startswith(self.HOME), timeout=90 * 1000) # убеждаемся что мы в почте
63
- self.user.gmail.auth = await self.page.context.storage_state()
64
- await self.user.gmail.save()
65
-
66
- async def mail_confirm(self):
67
- lang = await self.page.get_attribute("html", "lang")
68
- labs = {
69
- "ru": "Оповещения",
70
- "en-US": "Updates",
71
- }
72
- tab = self.page.get_by_role("heading").get_by_label(labs[lang]).last
73
- await tab.click()
74
- rows = self.page.locator("tbody>>nth=4 >> tr")
75
- row = rows.get_by_text("Volet.com").and_(rows.get_by_text("Please Confirm Withdrawal"))
76
- if not await row.count():
77
- await self.bot.receive("А нет запросов от волета", photo=await self.page.screenshot())
78
-
79
- await row.click()
80
- await self.page.wait_for_load_state()
81
- btn = self.page.locator('a[href^="https://account.volet.com/verify/"]', has_text="confirm").first
82
- await btn.click()
83
-
84
- async def load_bot(self):
85
- if not self.bot:
86
- bot = FileClient(NET_TOKEN)
87
- self.bot = UserClient(self.uid, bot)
88
- if not self.bot.is_connected:
89
- await self.bot.start()
90
-
91
- async def stop(self):
92
- if self.bot and self.bot.is_connected: # todo: do not stop if
93
- await self.bot.stop(False)
94
- await self.page.context.close()
95
- await self.page.context.browser.close()
75
+
76
+ if not (messages := results.get("messages", [])):
77
+ logging.warning("Письма не найдены")
78
+ return None
79
+
80
+ # Получаем полную информацию о письме
81
+ message_id = messages[0]["id"]
82
+ message = self.service.users().messages().get(userId="me", id=message_id, format="full").execute()
83
+
84
+ # Извлекаем заголовки
85
+ headers = message["payload"]["headers"]
86
+ subject = next((h["value"] for h in headers if h["name"] == "Subject"), "Нет темы")
87
+ from_email = next((h["value"] for h in headers if h["name"] == "From"), "Неизвестно")
88
+ date = next((h["value"] for h in headers if h["name"] == "Date"), "Неизвестно")
89
+
90
+ # Извлекаем текст письма
91
+ body = _get_email_body(message["payload"])
92
+
93
+ return {"id": message_id, "subject": subject, "from": from_email, "date": date, "body": body}
94
+
95
+ async def volet_confirm(self, amount: float, dt: datetime):
96
+ if email := self._get_last_email("noreply@volet.com", "Please Confirm Withdrawal"): # "Volet.com"
97
+ date = datetime.strptime(email["date"].split(",")[1].split(" +")[0], "%d %b %Y %H:%M:%S")
98
+ if match := re.search(r"Amount: <b>([\d.]+) [A-Z]{3}</b>", email["body"]):
99
+ amt = float(match.group(1))
100
+ if match := re.search(r"https://account\.volet\.com/verify/([a-f0-9-]+)", email["body"]):
101
+ token = match.group(1)
102
+
103
+ if email and amount == amt and date > dt and token:
104
+ get(f"https://account.volet.com/verify/{token}")
105
+ return True
106
+
107
+ await self.receive("А нет запросов от волета")
108
+ return False
109
+
110
+ async def bybit_code(self, dt: datetime) -> str | None:
111
+ if email := self._get_last_email("Bybit", "[Bybit]Security Code for Your Bybit Account"):
112
+ date = datetime.strptime(email["date"].split(",")[1].split(" +")[0], "%d %b %Y %H:%M:%S")
113
+ if match := re.search(r'<span style="font-size:28pt;color:#ff9c2e">(\d{6})</span>', email["body"]):
114
+ code = match.group(1)
115
+
116
+ if email and date > dt and code:
117
+ get(f"https://account.volet.com/verify/{code}")
118
+ return code
119
+
120
+ await self.receive("А нет запросов от волета")
121
+ return None
96
122
 
97
123
 
98
124
  async def _test():
99
125
  from x_model import init_db
100
- from xync_schema import TORM
101
-
102
- _ = await init_db(TORM, True)
103
- uid = 193017646
104
- gmc = GmClient(uid)
105
- try:
106
- await gmc.start(True)
107
- except TimeoutError as te:
108
- raise te
109
- finally:
110
- await gmc.stop()
126
+
127
+ _ = await init_db(TORM)
128
+
129
+ gm = await Gmail.get(id=1).prefetch_related("user__username")
130
+ gmc = GmClient(gm)
131
+ await gmc.volet_confirm(amount=90, dt=datetime.now())
111
132
 
112
133
 
113
134
  if __name__ == "__main__":
xync_client/Htx/agent.py CHANGED
@@ -1,18 +1,29 @@
1
+ import re
1
2
  from json import dumps
3
+ from time import time
4
+ from urllib.parse import quote
2
5
 
6
+ from aiohttp import ClientResponse
7
+ from pyro_client.client.file import FileClient
8
+ from x_client import df_hdrs
3
9
  from x_client.aiohttp import Client
10
+ from xync_bot import XyncBot
11
+
12
+ from xync_client.Abc.xtype import AdUpd, GetAds
13
+ from xync_client.Htx.etype.order import OrderItem, OrderFull
14
+ from xync_client.loader import NET_TOKEN, PAY_TOKEN, TORM
15
+
4
16
  from xync_schema.enums import AdStatus, PmType, OrderStatus
5
- from xync_schema.models import Pm, Coin, Cur, Ad, Order
6
17
  from xync_schema import models
7
18
  from xync_client.Abc.Agent import BaseAgentClient
8
- from xync_client.Htx.etype import test
19
+ from xync_client.Htx.etype import test, ad
9
20
 
10
21
  import logging
11
22
 
12
23
  url_ads_req = "https://otc-cf.huobi.com/v1/data/trade-market"
13
24
  url_ads_web = "https://www.huobi.com/en-us/fiat-crypto/trade/"
14
25
  url_my_ads = "https://otc-api.trygofast.com/v1/data/trade-list?pageSize=50"
15
- url_my_ad = "https://www.huobi.com/-/x/otc/v1/otc/trade/" # + id
26
+ url_my_ad = "/-/x/otc/v1/otc/trade/" # + id
16
27
  url_my_bals = "https://www.huobi.com/-/x/otc/v1/capital/balance"
17
28
  url_paccs = "https://www.huobi.com/-/x/otc/v1/user/receipt-account"
18
29
 
@@ -22,9 +33,19 @@ class Public(Client):
22
33
 
23
34
 
24
35
  class AgentClient(BaseAgentClient):
25
- headers = {
26
- "portal": "web",
27
- }
36
+ headers = {"portal": "web"} | df_hdrs
37
+
38
+ async def login(self):
39
+ t = int(time() * 1000)
40
+ resp = await self._get("/-/x/uc/uc/open/ticket/get", {"t": t})
41
+ if resp.get("data") is None:
42
+ ...
43
+ if ticket := resp["data"].get("ticket"):
44
+ resp = await self._post("/-/x/otc/v1/user/login", form_data={"ticket": ticket, "type": "WEB"})
45
+ if resp["success"]:
46
+ self.agent.auth["headers"]["Token"] = resp["data"]
47
+ await self.agent.save(update_fields=["auth"])
48
+ self.session.headers["Token"] = resp["data"]
28
49
 
29
50
  async def creds(self) -> list[test.CredEpyd]:
30
51
  resp = await self._get("/-/x/otc/v1/user/receipt-account")
@@ -46,7 +67,7 @@ class AgentClient(BaseAgentClient):
46
67
 
47
68
  async def cred_new(self, cred: models.Cred) -> models.CredEx:
48
69
  pmcur = await cred.pmcur
49
- exid = str(await models.PmEx.get(pm_id=pmcur.pm_id, ex=self.ex_client.ex).values_list("exid", flat=True))
70
+ exid = str(await models.PmEx.get(pm_id=pmcur.pmex_exid, ex=self.ex_client.ex).values_list("exid", flat=True))
50
71
  field_map = {
51
72
  "payee": "name",
52
73
  "bank": "extra",
@@ -71,7 +92,7 @@ class AgentClient(BaseAgentClient):
71
92
 
72
93
  async def cred_upd(self, cred: models.Cred, exid: int) -> models.CredEx:
73
94
  pmcur = await cred.pmcur
74
- _exid = str(await models.PmEx.get(pm_id=pmcur.pm_id, ex=self.ex_client.ex).values_list("exid", flat=True))
95
+ _exid = str(await models.PmEx.get(pm_id=pmcur.pmex_exid, ex=self.ex_client.ex).values_list("exid", flat=True))
75
96
  field_map = {
76
97
  "payee": "name",
77
98
  "bank": "extra",
@@ -95,14 +116,40 @@ class AgentClient(BaseAgentClient):
95
116
 
96
117
  # 0
97
118
  async def get_orders(
98
- self, stauts: OrderStatus = OrderStatus.created, coin: Coin = None, cur: Cur = None, is_sell: bool = None
99
- ) -> list[Order]:
100
- pass
119
+ self,
120
+ stauts: OrderStatus = OrderStatus.created,
121
+ coin: models.Coin = None,
122
+ cur: models.Cur = None,
123
+ is_sell: bool = None,
124
+ ) -> list[OrderItem]:
125
+ resp = await self._get("/-/x/otc/v1/trade/order/process", {"needTradeCount": "true"})
126
+ if resp["success"]:
127
+ return [OrderItem(**o) for o in resp["data"]]
128
+ return []
129
+
130
+ async def get_order(self, oid: int) -> OrderFull | None:
131
+ resp = await self._get("/-/x/otc/v1/trade/order", {"orderId": oid})
132
+ if resp["success"]:
133
+ o = OrderFull(**resp["data"])
134
+ return o
135
+ return None
136
+
137
+ async def recv(self, order: OrderItem):
138
+ if order.orderStatus:
139
+ ...
140
+ else:
141
+ ...
142
+
143
+ async def start_listen(self):
144
+ """Фоновая задача для ловли входящих ордеров"""
145
+ while True:
146
+ [await self.recv(o) for o in await self.get_orders()]
147
+ await sleep(9)
101
148
 
102
149
  async def order_request(self, ad_id: int, amount: float) -> dict:
103
150
  pass
104
151
 
105
- async def my_fiats(self, cur: Cur = None) -> list[dict]:
152
+ async def my_fiats(self, cur: models.Cur = None) -> list[dict]:
106
153
  pass
107
154
 
108
155
  # async def fiat_new(self, fiat: FiatNew) -> Fiat.pyd():
@@ -124,30 +171,94 @@ class AgentClient(BaseAgentClient):
124
171
 
125
172
  async def ad_new(
126
173
  self,
127
- coin: Coin,
128
- cur: Cur,
174
+ coin: models.Coin,
175
+ cur: models.Cur,
129
176
  is_sell: bool,
130
- pms: list[Pm],
177
+ pms: list[models.Pm],
131
178
  price: float,
132
179
  is_float: bool = True,
133
180
  min_fiat: int = None,
134
181
  details: str = None,
135
182
  autoreply: str = None,
136
183
  status: AdStatus = AdStatus.active,
137
- ) -> Ad:
184
+ ) -> models.Ad:
138
185
  pass
139
186
 
140
- async def ad_upd(
141
- self,
142
- pms: [Pm] = None,
143
- price: float = None,
144
- is_float: bool = None,
145
- min_fiat: int = None,
146
- details: str = None,
147
- autoreply: str = None,
148
- status: AdStatus = None,
149
- ) -> bool:
150
- pass
187
+ async def x2e_req_ad_upd(self, xreq: AdUpd) -> ad.AdsUpd:
188
+ creds = [
189
+ ad.TradeRule(
190
+ content="Payment method-%s",
191
+ contentCode="PAY",
192
+ hint="Please enter",
193
+ inputType=0,
194
+ inputValue=cx.cred.detail,
195
+ sort=1,
196
+ title="【Payment related】",
197
+ titleValue=(await models.PmEx.get(pm_id=cx.cred.pmcur.pm_id, ex=self.ex_client.ex)).name,
198
+ )
199
+ for cx in xreq.credexs
200
+ ]
201
+ trade_rules = ad.TradeRulesV2(
202
+ [
203
+ *creds,
204
+ ad.TradeRule(
205
+ content="",
206
+ contentCode="MERCHANT",
207
+ hint="Please enter",
208
+ inputType=0,
209
+ inputValue="",
210
+ sort=4,
211
+ title="【Merchant Tips】",
212
+ ),
213
+ ]
214
+ ).model_dump_json(exclude_none=True)
215
+ coin_id, coin_scale = await self.ex_client.x2e_coin(xreq.coin_id)
216
+ cur_id, cur_scale, minimum = await self.ex_client.x2e_cur(xreq.cur_id)
217
+ return ad.AdsUpd(
218
+ id=xreq.id,
219
+ tradeType=int(xreq.is_sell),
220
+ coinId=int(coin_id),
221
+ currency=int(cur_id),
222
+ minTradeLimit=minimum,
223
+ maxTradeLimit=round(xreq.amount - 10**-cur_scale, cur_scale),
224
+ tradeCount=round(xreq.quantity or xreq.amount / xreq.price, coin_scale),
225
+ password=self.agent.auth["pass"],
226
+ payTerm=15,
227
+ premium=0.00,
228
+ isFixed="on",
229
+ fixedPrice=round(xreq.price, cur_scale),
230
+ isAutoReply="off",
231
+ takerAcceptOrder=0,
232
+ isPayCode="off",
233
+ receiveAccounts=",".join([str(cx.exid) for cx in xreq.credexs]),
234
+ deviation=0,
235
+ isTakerLimit="on",
236
+ takerIsMerchant="on",
237
+ takerRealLevel="off",
238
+ takerIsPhoneBind="off",
239
+ takerIsPayment="on",
240
+ blockType=1,
241
+ session=1,
242
+ chargeType=False,
243
+ apiVersion=4,
244
+ channel="web",
245
+ tradeRulesV2=quote(trade_rules),
246
+ )
247
+
248
+ async def _ad_upd(self, req: ad.AdsUpd, hdrs: dict[str, str] = None) -> dict:
249
+ res = await self._post(self.url_my_ad + str(req.id), form_data=req.model_dump(exclude_none=True), hdrs=hdrs)
250
+ if res["code"] == 200:
251
+ return res["data"]
252
+ elif res["code"] == 605:
253
+ hdrs = {"x-dialog-trace-id": res["extend"]["traceId"]}
254
+ return await self._ad_upd(req, hdrs)
255
+ elif res["code"] == 1010:
256
+ if (match := re.search(r"Available amount ([\d.]+)", res["message"])) and (qty := match.group(1)):
257
+ req.tradeCount = float(qty)
258
+ return await self._ad_upd(req, hdrs)
259
+ elif res["code"] == 401:
260
+ raise Exception(res)
261
+ raise BaseException(res)
151
262
 
152
263
  async def ad_del(self) -> bool:
153
264
  pass
@@ -182,32 +293,94 @@ class AgentClient(BaseAgentClient):
182
293
  base_url = ""
183
294
  middle_url = ""
184
295
 
185
- htok: str = "Ev5lFfAvxDU2MA9BJ-Mc4U6zZG3Wb6qsp3Tx2fz6GIoY-uOP2m0-gvjE57ad1qDF"
186
-
187
296
  url_ads_req = "https://otc-cf.huobi.com/v1/data/trade-market"
188
297
  url_my_ads = "https://otc-api.trygofast.com/v1/data/trade-list?pageSize=50"
189
- url_my_ad = "https://www.huobi.com/-/x/otc/v1/otc/trade/" # + id
298
+ url_my_ad = "/-/x/otc/v1/otc/trade/" # + id
190
299
  url_my_bals = "https://www.huobi.com/-/x/otc/v1/capital/balance"
191
300
  url_paccs = "https://www.huobi.com/-/x/otc/v1/user/receipt-account"
192
301
 
302
+ async def _proc(self, resp: ClientResponse, bp: dict | str = None) -> dict | str:
303
+ if (await resp.json()).get("code") == 401:
304
+ await self.login()
305
+ return await self.METHS[resp.method](self, resp.url.path, bp)
306
+ return await super()._proc(resp, bp)
307
+
193
308
 
194
309
  async def _test():
195
310
  from x_model import init_db
196
- from xync_schema import TORM
197
311
 
198
- _ = await init_db(TORM, True)
199
- actor = (
200
- await models.Actor.filter(ex_id=15, agent__isnull=False).prefetch_related("ex", "agent", "person__user").first()
312
+ cn = await init_db(TORM, True)
313
+ ex = await models.Ex[9]
314
+ filebot = FileClient(NET_TOKEN)
315
+ ecl = ex.client(filebot)
316
+ agent = (
317
+ await models.Agent.filter(actor__ex=ex, auth__isnull=False)
318
+ .prefetch_related(
319
+ "actor__ex",
320
+ "actor__person__user__gmail",
321
+ "actor__my_ads__my_ad__race",
322
+ "actor__my_ads__pair_side__pair__cur",
323
+ "actor__my_ads__pms",
324
+ )
325
+ .first()
201
326
  )
202
- cl: AgentClient = actor.client()
203
- cred = await models.Cred[89]
204
- _ = await cl.cred_new(cred)
205
- _creds = await cl.creds()
327
+ cl: AgentClient = agent.client(ecl, filebot, XyncBot(PAY_TOKEN, cn))
328
+ # cred = await models.Cred[89]
329
+ # _ = await cl.cred_new(cred)
330
+ # _creds = await cl.creds()
206
331
  # _ = await cl.cred_del(16984748)
207
- await cl.close()
332
+
333
+ while True:
334
+ breq = GetAds(coin_id=1, cur_id=1, is_sell=False, pm_ids=[366])
335
+ sreq = GetAds(coin_id=1, cur_id=1, is_sell=True, pm_ids=[366])
336
+ breq_upd = AdUpd(id=1185713, price=87.01, **{**breq.model_dump(), "amount": 100000.01})
337
+ sreq_upd = AdUpd(id=1188929, price=98.99, **{**sreq.model_dump(), "amount": 200000.01})
338
+
339
+ bads: list[ad.Resp] = await cl.ex_client.ads(breq)
340
+ sads: list[ad.Resp] = await cl.ex_client.ads(sreq)
341
+ bceil = 101.11
342
+ sceil = 151
343
+ bads = [a for a in bads if a.price < bceil and a.tradeCount > 10 and (a.maxTradeLimit - a.minTradeLimit > 800)]
344
+ sads = [a for a in sads if a.price > sceil and a.tradeCount > 10 and (a.maxTradeLimit - a.minTradeLimit > 800)]
345
+
346
+ if len(bads) > 1:
347
+ if bads[0].uid == cl.actor.exid:
348
+ if round(bads[0].price - bads[1].price, 2) > 0.01:
349
+ breq_upd.price = bads[1].price + 0.01
350
+ await cl.ad_upd(breq_upd)
351
+ print(end="!", flush=True)
352
+ elif bads[0].price < bceil:
353
+ breq_upd.price = bads[0].price + 0.01
354
+ await cl.ad_upd(breq_upd)
355
+ print(end="!", flush=True)
356
+
357
+ if len(sads) > 1:
358
+ if sads[0].uid == cl.actor.exid:
359
+ if round(sads[1].price - sads[0].price, 2) > 0.01:
360
+ sreq_upd.price = sads[1].price - 0.01
361
+ await cl.ad_upd(sreq_upd)
362
+ print(end="!", flush=True)
363
+ elif sads[0].price > sceil:
364
+ sreq_upd.price = sads[0].price - 0.01
365
+ await cl.ad_upd(sreq_upd)
366
+ print(end="!", flush=True)
367
+
368
+ if (pos := await cl.get_orders()) and (po := pos.pop(0)):
369
+ if po.side: # is_sell
370
+ po.amount # check
371
+ else: # buy
372
+ order: OrderFull = await cl.get_order(po.orderId)
373
+ if ps := [pm.bankNumber for pm in order.paymentMethod if pm.bankType in [24]]:
374
+ if match := re.search(r"^[PpРр]\d{7,10}\b", ps[0]):
375
+ match.group()
376
+
377
+ print(end=".", flush=True)
378
+ await sleep(9)
379
+
380
+ await cl.stop()
208
381
 
209
382
 
210
383
  if __name__ == "__main__":
211
- from asyncio import run
384
+ from asyncio import run, sleep
212
385
 
213
386
  run(_test())