xync-client 0.0.43.dev32__tar.gz → 0.0.43.dev35__tar.gz

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 (109) hide show
  1. {xync_client-0.0.43.dev32/xync_client.egg-info → xync_client-0.0.43.dev35}/PKG-INFO +1 -1
  2. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Abc/InAgent.py +3 -3
  3. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Bybit/InAgent.py +10 -10
  4. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Bybit/agent.py +34 -21
  5. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Pms/Volet/__init__.py +43 -28
  6. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/loader.py +2 -1
  7. xync_client-0.0.43.dev35/xync_client/pg_storage.py +276 -0
  8. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35/xync_client.egg-info}/PKG-INFO +1 -1
  9. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client.egg-info/SOURCES.txt +1 -0
  10. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/.env.sample +0 -0
  11. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/.gitignore +0 -0
  12. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/.pre-commit-config.yaml +0 -0
  13. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/README.md +0 -0
  14. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/makefile +0 -0
  15. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/pyproject.toml +0 -0
  16. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/setup.cfg +0 -0
  17. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/tests/TestAgent.py +0 -0
  18. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/tests/TestAsset.py +0 -0
  19. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/tests/TestEx.py +0 -0
  20. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/tests/TestOrder.py +0 -0
  21. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/tests/_todo_refact/Binance/test_binance.py +0 -0
  22. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/tests/_todo_refact/Bybit/test_bybit.py +0 -0
  23. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/tests/_todo_refact/Bybit/test_bybit_p2p.py +0 -0
  24. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/tests/_todo_refact/Gate/test_gate.py +0 -0
  25. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/tests/_todo_refact/Htx/test_htx_p2p.py +0 -0
  26. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/tests/_todo_refact/Wallet/test_agent.py +0 -0
  27. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/tests/_todo_refact/Wallet/test_ex.py +0 -0
  28. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/tests/_todo_refact/__init__.py +0 -0
  29. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/tests/_todo_refact/_test_ex.py +0 -0
  30. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Abc/Agent.py +0 -0
  31. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Abc/Asset.py +0 -0
  32. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Abc/AuthTrait.py +0 -0
  33. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Abc/Base.py +0 -0
  34. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Abc/BaseTest.py +0 -0
  35. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Abc/Ex.py +0 -0
  36. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Abc/Order.py +0 -0
  37. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Abc/types.py +0 -0
  38. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Binance/__init__.py +0 -0
  39. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Binance/binance_async.py +0 -0
  40. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Binance/earn_api.py +0 -0
  41. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Binance/etype/ad.py +0 -0
  42. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Binance/etype/pm.py +0 -0
  43. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Binance/ex.py +0 -0
  44. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Binance/exceptions.py +0 -0
  45. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Binance/sapi.py +0 -0
  46. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Binance/web_c2c.py +0 -0
  47. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/BingX/__init__.py +0 -0
  48. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/BingX/agent.py +0 -0
  49. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/BingX/base.py +0 -0
  50. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/BingX/etype/ad.py +0 -0
  51. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/BingX/etype/pm.py +0 -0
  52. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/BingX/ex.py +0 -0
  53. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/BingX/req.mjs +0 -0
  54. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/BingX/sign.js +0 -0
  55. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/BitGet/__init__.py +0 -0
  56. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/BitGet/agent.py +0 -0
  57. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/BitGet/etype/ad.py +0 -0
  58. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/BitGet/ex.py +0 -0
  59. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/BitGet/req.mjs +0 -0
  60. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/BitPapa/ex.py +0 -0
  61. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Bybit/etype/ad.py +0 -0
  62. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Bybit/etype/cred.py +0 -0
  63. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Bybit/etype/order.py +0 -0
  64. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Bybit/ex.py +0 -0
  65. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Bybit/web_earn.py +0 -0
  66. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Bybit/web_p2p.py +0 -0
  67. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Bybit/ws.py +0 -0
  68. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Gate/etype/ad.py +0 -0
  69. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Gate/ex.py +0 -0
  70. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Gate/premarket.py +0 -0
  71. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Htx/agent.py +0 -0
  72. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Htx/earn.py +0 -0
  73. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Htx/etype/__init__.py +0 -0
  74. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Htx/etype/ad.py +0 -0
  75. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Htx/etype/cred.py +0 -0
  76. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Htx/etype/pm.py +0 -0
  77. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Htx/etype/test.py +0 -0
  78. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Htx/ex.py +0 -0
  79. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/KuCoin/etype/ad.py +0 -0
  80. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/KuCoin/etype/pm.py +0 -0
  81. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/KuCoin/ex.py +0 -0
  82. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/KuCoin/web.py +0 -0
  83. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Mexc/etype/ad.py +0 -0
  84. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Mexc/etype/pm.py +0 -0
  85. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Mexc/ex.py +0 -0
  86. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Okx/etype/ad.py +0 -0
  87. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Okx/etype/pm.py +0 -0
  88. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Okx/ex.py +0 -0
  89. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Pms/Tinkoff/__init__.py +0 -0
  90. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Pms/Tinkoff/state.json +0 -0
  91. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Pms/Tinkoff/storage.json +0 -0
  92. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Pms/Volet/_todo_req/req.mjs +0 -0
  93. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Pms/Volet/_todo_req/req.py +0 -0
  94. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/Pms/Volet/api.py +0 -0
  95. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/TgWallet/agent.py +0 -0
  96. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/TgWallet/asset.py +0 -0
  97. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/TgWallet/auth.py +0 -0
  98. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/TgWallet/ex.py +0 -0
  99. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/TgWallet/inAgent.py +0 -0
  100. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/TgWallet/order.py +0 -0
  101. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/TgWallet/pyd.py +0 -0
  102. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/TgWallet/pyro.py +0 -0
  103. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/TgWallet/web.py +0 -0
  104. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/__init__.py +0 -0
  105. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/pm_unifier.py +0 -0
  106. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client/pyro.py +0 -0
  107. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client.egg-info/dependency_links.txt +0 -0
  108. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client.egg-info/requires.txt +0 -0
  109. {xync_client-0.0.43.dev32 → xync_client-0.0.43.dev35}/xync_client.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xync-client
3
- Version: 0.0.43.dev32
3
+ Version: 0.0.43.dev35
4
4
  Author-email: Mike Artemiev <mixartemev@gmail.com>
5
5
  Project-URL: Homepage, https://gitlab.com/XyncNet/client
6
6
  Project-URL: Repository, https://gitlab.com/XyncNet/client
@@ -1,13 +1,13 @@
1
1
  from abc import abstractmethod
2
2
 
3
- from xync_schema.models import Agent
3
+ from xync_schema.models import Actor
4
4
 
5
5
  from xync_client.Abc.Agent import BaseAgentClient
6
6
 
7
7
 
8
8
  class BaseInAgentClient:
9
- def __init__(self, agent: Agent):
10
- self.agent_client: BaseAgentClient = agent.client()
9
+ def __init__(self, actor: Actor):
10
+ self.agent_client: BaseAgentClient = actor.client()
11
11
 
12
12
  @abstractmethod
13
13
  async def start_listen(self) -> bool: ...
@@ -6,8 +6,7 @@ from xync_schema import models
6
6
  from xync_client.Abc.InAgent import BaseInAgentClient
7
7
  from xync_client.Bybit.agent import AgentClient
8
8
  from xync_client.Bybit.ws import prv
9
- from xync_client.TgWallet.pyro import PyroClient
10
- from xync_client.loader import PG_DSN, bot
9
+ from xync_client.loader import PG_DSN
11
10
 
12
11
 
13
12
  class InAgentClient(BaseInAgentClient):
@@ -16,7 +15,7 @@ class InAgentClient(BaseInAgentClient):
16
15
  async def start_listen(self):
17
16
  t = await self.agent_client.ott()
18
17
  ts = int(float(t["time_now"]) * 1000)
19
- await prv(self.agent_client.agent.auth["deviceId"], t["result"], ts, listen)
18
+ await prv(self.agent_client.actor.agent.auth["deviceId"], t["result"], ts, listen)
20
19
 
21
20
  # 3N: [T] - Уведомление об одобрении запроса на сделку
22
21
  async def request_accepted_notify(self) -> int: ... # id
@@ -28,13 +27,14 @@ def listen(data: dict):
28
27
 
29
28
  async def main():
30
29
  _ = await init_db(PG_DSN, models, True)
31
- pbot = PyroClient(bot)
32
- await pbot.app.start()
33
- await pbot.app.create_channel("tc")
34
- await pbot.app.stop()
35
-
36
- agent = await models.Agent.filter(actor__ex_id=9, auth__isnull=False).prefetch_related("actor__ex").first()
37
- cl: InAgentClient = agent.in_client()
30
+ # pbot = PyroClient(bot)
31
+ # await pbot.app.start()
32
+ # await pbot.app.create_channel("tc")
33
+ # await pbot.app.stop()
34
+
35
+ actor = await models.Actor.filter(ex_id=9, agent__auth__isnull=False).prefetch_related("ex", "agent").first()
36
+ cl: InAgentClient = actor.in_client()
37
+ _ = await cl.start_listen()
38
38
  await cl.agent_client.close()
39
39
 
40
40
 
@@ -381,6 +381,24 @@ class AgentClient(BaseAgentClient): # Bybit client
381
381
  print("-" if mad.side else "+", end=req.price, flush=True)
382
382
  await sleep(60)
383
383
 
384
+ def overprice_filter(self, ads: list[Ad], ceil: float, k: Literal[-1, 1]):
385
+ # вырезаем ads с ценами выше потолка
386
+ if ads and (ceil - float(ads[0].price)) * k > 0:
387
+ if int(ads[0].userId) != self.actor.exid:
388
+ ads.pop(0)
389
+ self.overprice_filter(ads, ceil, k)
390
+
391
+ def get_cad(self, ads: list[Ad], ceil: float, k: Literal[-1, 1], place: int, cur_plc: int) -> Ad:
392
+ # чью цену будем обгонять, предыдущей или слещующей объявы?
393
+ cad: Ad = ads[place] if cur_plc > place else ads[cur_plc]
394
+ # а цена обгоняемой объявы не выше нашего потолка?
395
+ if (float(cad.price) - ceil) * k <= 0:
396
+ # тогда берем следующую
397
+ ads.pop(place)
398
+ cad = self.get_cad(ads, ceil, k, place, cur_plc)
399
+ # todo: добавить фильтр по лимитам min-max
400
+ return cad
401
+
384
402
  async def battle(
385
403
  self,
386
404
  coinex: models.Coinex,
@@ -409,28 +427,27 @@ class AgentClient(BaseAgentClient): # Bybit client
409
427
 
410
428
  while self.actor.person.user.status > 0:
411
429
  ads: list[Ad] = await self.ads(coinex, curex, is_sell, list(creds.keys()))
412
- overprice_filter(ads, ceil, k)
430
+ self.overprice_filter(ads, ceil, k)
431
+ if not ads:
432
+ print(coinex.exid, curex.exid, is_sell, "no ads!")
433
+ await sleep(15)
434
+ continue
413
435
  cur_plc = [i for i, ad in enumerate(ads) if int(ad.userId) == self.actor.exid][0]
414
436
  mad: Ad = ads.pop(cur_plc)
415
437
  if not ads:
416
438
  await sleep(60)
417
439
  continue
418
- # чью цену будем обгонять, предыдущей или слещующей объявы?
419
- cad: Ad = ads[place] if cur_plc > place else ads[cur_plc]
420
- # а цена обгоняемой объявы не выше нашего потолка?
421
- if (float(cad.price) - ceil) * k <= 0:
422
- # тогда берем следующую
423
- cad = ads[cur_plc]
440
+ cad = self.get_cad(ads, ceil, k, place, cur_plc)
424
441
  new_price = f"%.{curex.cur.scale}f" % round(float(cad.price) - k * step(mad, cad), curex.cur.scale)
425
442
  if mad.price == new_price: # Если нужная цена и так уже стоит
426
443
  print(end="v" if is_sell else "^", flush=True)
427
- await sleep(2)
444
+ await sleep(3)
428
445
  continue
429
446
  if cad.priceType: # Если цена конкурента плавающая, то повышаем себе не цену, а %
430
447
  new_premium = str(round(float(cad.premium) - k * step(mad, cad), 2))
431
448
  if mad.premium == new_premium: # Если нужный % и так уже стоит
432
449
  print(end="v" if is_sell else "^", flush=True)
433
- await sleep(2)
450
+ await sleep(3)
434
451
  continue
435
452
  mad.premium = new_premium
436
453
  mad.priceType = cad.priceType
@@ -439,6 +456,7 @@ class AgentClient(BaseAgentClient): # Bybit client
439
456
  req = AdUpdateRequest.model_validate({**mad.model_dump(), "price": new_price, "paymentIds": credex_ids})
440
457
  try:
441
458
  _res = self.ad_upd(req)
459
+ print("-" if is_sell else "+", end=req.price, flush=True)
442
460
  except FailedRequestError as e:
443
461
  if ExcCode(e.status_code) == ExcCode.FixPriceLimit:
444
462
  if limits := re.search(
@@ -455,13 +473,14 @@ class AgentClient(BaseAgentClient): # Bybit client
455
473
  req.quantity = round(asset.free - (asset.freeze or 0) - (asset.lock or 0), coinex.coin.scale)
456
474
  _res = self.ad_upd(req)
457
475
  elif ExcCode(e.status_code) == ExcCode.RareLimit:
458
- await sleep(120)
476
+ await sleep(192)
477
+ elif ExcCode(e.status_code) == ExcCode.Timestamp:
478
+ await sleep(2)
459
479
  else:
460
480
  raise e
461
481
  except (ReadTimeoutError, ConnectionDoesNotExistError):
462
482
  logging.warning("Connection failed. Restarting..")
463
- print("-" if is_sell else "+", end=req.price, flush=True)
464
- await sleep(60)
483
+ await sleep(42)
465
484
 
466
485
 
467
486
  def step(mad, cad) -> float:
@@ -473,13 +492,6 @@ def step(mad, cad) -> float:
473
492
  )
474
493
 
475
494
 
476
- def overprice_filter(ads: list[Ad], ceil: float, k: Literal[-1, 1]):
477
- # вырезаем ads с ценами выше потолка
478
- if ads and (ceil - float(ads[0].price)) * k > 0:
479
- ads.pop(0)
480
- overprice_filter(ads, ceil, k)
481
-
482
-
483
495
  def listen(data: dict):
484
496
  print(data)
485
497
 
@@ -494,11 +506,12 @@ class ExcCode(IntEnum):
494
506
  FixPriceLimit = 912120022
495
507
  RareLimit = 912120050
496
508
  InsufficientAmount = 912120024
509
+ Timestamp = 10002
497
510
 
498
511
 
499
512
  async def main():
500
513
  _ = await init_db(PG_DSN, models, True)
501
- logging.basicConfig(level=logging.DEBUG)
514
+ logging.basicConfig(level=logging.INFO)
502
515
  actor = (
503
516
  await models.Actor.filter(ex_id=9, agent__isnull=False).prefetch_related("ex", "agent", "person__user").first()
504
517
  )
@@ -514,7 +527,7 @@ async def main():
514
527
  # )
515
528
  # await cl.set_creds()
516
529
  await gather(
517
- cl.battle(usdt, rub, False, ["volet"], 83.79), # гонка в стакане покупки - мы продаем
530
+ cl.battle(usdt, rub, False, ["volet"], 83.88), # гонка в стакане покупки - мы продаем
518
531
  cl.battle(usdt, rub, True, ["volet"], 82), # гонка в стакане продажи - мы покупаем
519
532
  cl.battle(eth, rub, False, ["volet"], 160_000),
520
533
  cl.battle(eth, rub, True, ["volet"], 144_000),
@@ -1,3 +1,4 @@
1
+ import logging
1
2
  import re
2
3
  from asyncio import run, sleep
3
4
  from enum import StrEnum
@@ -5,7 +6,7 @@ from os.path import dirname
5
6
  from typing import Literal
6
7
 
7
8
  from aiogram.types import BufferedInputFile
8
- from playwright.async_api import async_playwright, Page
9
+ from playwright.async_api import async_playwright, Page, FloatRect, Locator
9
10
  from pyotp import TOTP
10
11
  from pyrogram import filters
11
12
  from pyrogram.handlers import MessageHandler
@@ -14,8 +15,8 @@ from playwright._impl._errors import TimeoutError
14
15
  from xync_schema.enums import UserStatus
15
16
  from xync_schema.models import User, PmAgent
16
17
 
17
- from xync_client.TgWallet.pyro import PyroClient
18
- from xync_client.loader import bot
18
+ from xync_client.pyro import PyroClient
19
+ from xync_client.loader import bot, dp
19
20
 
20
21
 
21
22
  class ExtraCaptchaException(Exception): ...
@@ -57,7 +58,8 @@ async def report(uid: int, byts: bytes, msg: str, exc: bool = True):
57
58
 
58
59
  class Client:
59
60
  agent: PmAgent
60
- pbot: PyroClient
61
+ bbot: PyroClient
62
+ ubot: PyroClient
61
63
  page: Page
62
64
  gpage: Page
63
65
 
@@ -70,10 +72,10 @@ class Client:
70
72
  async def start(self, headed: bool = False):
71
73
  self.agent = await PmAgent.get(user_id=self.uid, user__status__gt=0, pm__norm="volet").prefetch_related("user")
72
74
 
73
- self.pbot = PyroClient(self.agent)
74
- await self.pbot.app.start()
75
+ self.ubot, self.bbot = PyroClient(self.agent.user), PyroClient(bot)
76
+ await self.ubot.app.start(), await self.bbot.app.start()
75
77
  self.msg_listener = MessageHandler(self.got_msg, filters.chat(["ProtectimusBot"]))
76
- self.pbot.app.add_handler(self.msg_listener)
78
+ self.ubot.app.add_handler(self.msg_listener)
77
79
 
78
80
  playwright = await async_playwright().start()
79
81
  browser = await playwright.chromium.launch(
@@ -113,7 +115,7 @@ class Client:
113
115
  await self.page.click("input#checkOtpButton")
114
116
  await self.page.wait_for_url(Pages.HOME)
115
117
 
116
- async def wait_for_code(self, typ: Literal["login", "send"], past: int = 0, timeout: int = 5) -> str:
118
+ async def wait_for_code(self, typ: Literal["login", "send", "cap_xy"], past: int = 0, timeout: int = 5) -> str:
117
119
  while past < timeout:
118
120
  if code := self.msgs.pop(f"otp_{typ}", None):
119
121
  return code
@@ -124,7 +126,7 @@ class Client:
124
126
  async def got_msg(self, _, msg: Message):
125
127
  if "Your OTP code:" in msg.text:
126
128
  self.msgs["otp_login"] = msg.text[-6:]
127
- if "Confirmation code:" in msg.text:
129
+ elif "Confirmation code:" in msg.text:
128
130
  self.msgs["otp_send"] = msg.text[-6:]
129
131
  elif "Status: Completed. Sender:" in msg.text:
130
132
  self.msgs["got_payment"] = parse_transaction_info(msg.text)
@@ -137,7 +139,7 @@ class Client:
137
139
  await self.page.fill("#srcAmount", str(amount))
138
140
  await self.page.fill("#destWalletId", dest)
139
141
  await self.page.wait_for_timeout(300)
140
- await self.page.locator("input[type=submit]", has_text="continue").click()
142
+ await self.page.locator("form#mainForm input[type=submit]", has_text="continue").click()
141
143
  if otp := self.agent.auth.get("otp"):
142
144
  totp = TOTP(otp)
143
145
  code = totp.now()
@@ -157,23 +159,16 @@ class Client:
157
159
 
158
160
  async def gmail_page(self):
159
161
  gp = await self.page.context.new_page()
160
- await gp.goto(Pages.GMH, timeout=20000)
162
+ await gp.goto(Pages.GMH, timeout=30000)
161
163
  if not gp.url.startswith(Pages.GMH):
162
- # ваще с 0 заходим
163
- if await (
164
+ if await ( # ваще с 0 заходим
164
165
  sgn_btn := gp.locator(
165
166
  'header a[href^="https://accounts.google.com/AccountChooser/signinchooser"]:visible',
166
- has_text="sign in",
167
+ has_text="sign",
167
168
  )
168
169
  ).count():
169
170
  await sgn_btn.click()
170
- # если надо выбрать акк
171
- lang = await gp.get_attribute("html", "lang")
172
- sgn = {
173
- "ru": "Выберите аккаунт",
174
- "en": "Choose an account",
175
- }
176
- if await gp.locator("h1#headingText", has_text=sgn[lang]).count():
171
+ if gp.url.startswith("https://accounts.google.com/v3/signin/accountchooser"): # если надо выбрать акк
177
172
  await gp.locator("li").first.click()
178
173
  # если предлагает залогиниться
179
174
  elif await gp.locator("h1#headingText", has_text="Sign In").count():
@@ -181,8 +176,7 @@ class Client:
181
176
  await gp.locator("button", has_text="Next").click()
182
177
  # осталось ввести пороль:
183
178
  await gp.fill("input[type=password]", self.agent.user.gmail_auth["password"])
184
- nxt = {"ru": "Далее", "en": "Next"}
185
- await gp.locator("button", has_text=nxt[lang]).click()
179
+ await gp.locator("#passwordNext").click()
186
180
  await report(self.uid, await gp.screenshot(), "Аппрувни гмейл, у тебя 1.5 минуты", False)
187
181
  await gp.wait_for_url(lambda u: u.startswith(Pages.GMH), timeout=90 * 1000) # убеждаемся что мы в почте
188
182
  self.gpage = gp
@@ -212,13 +206,32 @@ class Client:
212
206
  except Exception as e:
213
207
  await report(self.uid, await self.page.screenshot(), repr(e))
214
208
 
209
+ async def send_cap_help(self, xcap: Locator):
210
+ bb = await xcap.bounding_box(timeout=2000)
211
+ byts = await self.page.screenshot(clip=bb)
212
+ infile = BufferedInputFile(byts, "cap_xy.png")
213
+ await self.bbot.send_img("put x, y", byts)
214
+ self.bbot.app.storage.user_id()
215
+ await bot.send_photo(uid, infile, caption=msg)
216
+ await report(self.uid, byts, "x, y", False)
217
+ dp.message.register(self.got_cap_xy)
218
+
219
+ async def got_cap_xy(self, _, msg):
220
+ self.msgs["typ_cap_xy"] = msg.text.split(",")
221
+
215
222
  async def captcha_click(self):
216
223
  captcha_url = self.page.url
217
224
  cbx = self.page.frame_locator("#main-iframe").frame_locator("iframe").first.locator("div#checkbox")
218
225
  await cbx.wait_for(state="visible"), await self.page.wait_for_timeout(500)
219
226
  await cbx.click(delay=94)
227
+ xcap = self.page.frame_locator("#main-iframe").frame_locator("iframe").last.locator("div.challenge-view")
228
+ if await xcap.count():
229
+ await self.send_cap_help(xcap)
230
+ x, y = await self.wait_for_code("cap_xy", timeout=59)
231
+
220
232
  try:
221
233
  await self.page.wait_for_url(lambda url: url != captcha_url)
234
+
222
235
  except TimeoutError: # if page no changed -> captcha is undone
223
236
  await self.page.screenshot(path=dirname(__file__) + "/xtr_captcha.png")
224
237
  raise ExtraCaptchaException(self.page.url)
@@ -236,20 +249,22 @@ class Client:
236
249
  # closing
237
250
  await self.page.context.close()
238
251
  await self.page.context.browser.close()
239
- self.pbot.app.remove_handler(self.msg_listener)
240
- await self.pbot.app.stop()
252
+ self.ubot.app.remove_handler(self.msg_listener)
253
+ await self.ubot.app.stop(), await self.bbot.app.stop()
241
254
 
242
255
 
243
- async def _test(uid: int, dest: str, amount):
256
+ async def _test():
244
257
  from x_model import init_db
245
258
  from xync_client.loader import PG_DSN
246
259
  from xync_schema import models
247
260
 
248
261
  _ = await init_db(PG_DSN, models, True)
262
+ logging.basicConfig(level=logging.DEBUG)
263
+ uid = 193017646
249
264
  va = Client(uid)
250
265
  try:
251
266
  await va.start(True)
252
- await va.send(dest, amount)
267
+ await va.send("alena.artemeva25@gmail.com", 8.3456)
253
268
  await va.wait_for_payments()
254
269
  except TimeoutError as te:
255
270
  await report(uid, await va.page.screenshot(), repr(te))
@@ -257,4 +272,4 @@ async def _test(uid: int, dest: str, amount):
257
272
 
258
273
 
259
274
  if __name__ == "__main__":
260
- run(_test(7807393311, "alena.artemeva25@gmail.com", 8.3456))
275
+ run(_test())
@@ -1,4 +1,4 @@
1
- from aiogram import Bot
1
+ from aiogram import Bot, Dispatcher
2
2
  from dotenv import load_dotenv
3
3
  from os import getenv as env
4
4
 
@@ -11,6 +11,7 @@ if not (TOKEN := env("TOKEN")):
11
11
  logging.info(TOKEN := env("TOKEN"))
12
12
 
13
13
  bot: Bot = Bot(token=TOKEN)
14
+ dp: Dispatcher = Dispatcher()
14
15
  PG_DSN = f"postgres://{env('POSTGRES_USER')}:{env('POSTGRES_PASSWORD')}@{env('POSTGRES_HOST', 'xyncdbs')}:{env('POSTGRES_PORT', 5432)}/{env('POSTGRES_DB', env('POSTGRES_USER'))}"
15
16
  TG_API_ID = env("TG_API_ID")
16
17
  TG_API_HASH = env("TG_API_HASH")
@@ -0,0 +1,276 @@
1
+ import time
2
+
3
+ from pyrogram import Client, raw, utils
4
+ from pyrogram.storage import Storage
5
+ from tortoise import fields
6
+ from tortoise.signals import pre_save
7
+ from x_model.models import Model
8
+ from xync_schema.models import User
9
+
10
+
11
+ class Session(Model):
12
+ name = fields.CharField(255, primary_key=True)
13
+ dc_id = fields.IntField(null=False)
14
+ api_id = fields.IntField(null=False)
15
+ test_mode = fields.BooleanField(null=True)
16
+ auth_key = fields.BinaryField()
17
+ date = fields.IntField(null=False)
18
+ user: fields.OneToOneRelation[User] = fields.OneToOneField("models.User", "sessions")
19
+ user_id: int
20
+ is_bot = fields.BooleanField(null=True)
21
+
22
+
23
+ class Peer(Model):
24
+ session: fields.ForeignKeyRelation[Session] = fields.OneToOneField("models.Session", "peers", primary_key=True)
25
+ id: int = fields.BigIntField(True)
26
+ access_hash: int = fields.BigIntField()
27
+ type = fields.CharField(255)
28
+ phone_number = fields.CharField(255)
29
+ last_update_on: int = fields.BigIntField()
30
+
31
+
32
+ @pre_save(Peer)
33
+ async def person(_meta, peer: Peer, _db, _updated: dict) -> None:
34
+ if not peer.last_update_on:
35
+ peer.last_update_on = int(time.time())
36
+
37
+
38
+ class UpdateState(Model):
39
+ session: fields.ForeignKeyRelation[Session] = fields.OneToOneField("models.Session", "update_states", primary_key=True)
40
+ pts = fields.IntField()
41
+ qts = fields.IntField()
42
+ date = fields.IntField()
43
+ seq = fields.IntField()
44
+
45
+
46
+ class Version(Model):
47
+ id: None
48
+ number = fields.IntField(primary_key=True)
49
+
50
+
51
+ def get_input_peer(peer_id: int, access_hash: int, peer_type: str):
52
+ if peer_type in ["user", "bot"]:
53
+ return raw.types.InputPeerUser(
54
+ user_id=peer_id,
55
+ access_hash=access_hash
56
+ )
57
+
58
+ if peer_type == "group":
59
+ return raw.types.InputPeerChat(
60
+ chat_id=-peer_id
61
+ )
62
+
63
+ if peer_type in ["channel", "supergroup"]:
64
+ return raw.types.InputPeerChannel(
65
+ channel_id=utils.get_channel_id(peer_id),
66
+ access_hash=access_hash
67
+ )
68
+
69
+ raise ValueError(f"Invalid peer type: {peer_type}")
70
+
71
+
72
+ class MultiPostgresStorage(Storage):
73
+ VERSION = 1
74
+ USERNAME_TTL = 8 * 60 * 60
75
+
76
+ def __init__(self, client: Client, database: dict):
77
+ super().__init__(client.name)
78
+ self.cn = f"postgresql+asyncpg://{database['db_user']}:{database['db_pass']}@{database['db_host']}:{database['db_port']}/{database['db_name']}"
79
+ self.name = client.name
80
+
81
+ async def open(self):
82
+ if not await Session.filter(session_name=self.name).exists():
83
+ _ = await Session.create(
84
+ session_name=self.name,
85
+ dc_id=None,
86
+ api_id=None,
87
+ test_mode=None,
88
+ auth_key=None,
89
+ date=int(time.time()),
90
+ user_id=None,
91
+ is_bot=None
92
+ )
93
+
94
+ async def save(self):
95
+ await self.date(int(time.time()))
96
+
97
+ async def close(self):
98
+ await self.cn.close()
99
+
100
+ async def delete(self):
101
+ # await UpdateState.filter(session__name=self.name).delete()
102
+ # delete(UsernameModel).where(UsernameModel.session_name == self.name)
103
+ # await Peer.filter(session__name=self.name).delete() # no need if delete cascade
104
+ await Session.filter(session__name=self.name).delete()
105
+
106
+ async def update_peers(self, peers: list[tuple[int, int, str, str]]):
107
+ for peer in peers:
108
+ existing_peer = await Peer.get_or_none(session__name=self.name, id=peer[0])
109
+
110
+ if existing_peer:
111
+ existing_peer.access_hash = peer[1]
112
+ existing_peer.type = peer[2]
113
+ existing_peer.phone_number = peer[3]
114
+ else:
115
+ new_peer = await Peer.create(
116
+ session__name=self.name, # todo: pk ?
117
+ id=peer[0],
118
+ access_hash=peer[1],
119
+ type=peer[2],
120
+ phone_number=peer[3]
121
+ )
122
+
123
+ async def update_usernames(self, usernames: list[tuple[int, list[str]]]):
124
+ for telegram_id, _ in usernames:
125
+ await session.execute(
126
+ delete(UsernameModel).where(UsernameModel.session_name == self.name,
127
+ UsernameModel.id == telegram_id)
128
+ )
129
+
130
+ for telegram_id, user_list in usernames:
131
+ for username in user_list:
132
+ User
133
+ new_username = UsernameModel(session_name=self.name, id=telegram_id, username=username)
134
+ session.add(new_username)
135
+
136
+ await session.commit()
137
+
138
+ async def get_peer_by_id(self, peer_id_or_username):
139
+ async with self.session_maker() as session:
140
+ if isinstance(peer_id_or_username, int):
141
+ peer = await session.execute(
142
+ select(PeerModel).filter_by(session_name=self.name, id=peer_id_or_username)
143
+ )
144
+ peer = peer.scalar_one_or_none()
145
+ if peer is None:
146
+ raise KeyError(f"ID not found: {peer_id_or_username}")
147
+ return get_input_peer(peer.id, peer.access_hash, peer.type)
148
+ elif isinstance(peer_id_or_username, str):
149
+ r = await session.execute(
150
+ select(
151
+ PeerModel.id,
152
+ PeerModel.access_hash,
153
+ PeerModel.type,
154
+ PeerModel.last_update_on
155
+ )
156
+ .join(UsernameModel, UsernameModel.id == PeerModel.id)
157
+ .filter(UsernameModel.username == peer_id_or_username,
158
+ UsernameModel.session_name == self.name,
159
+ PeerModel.session_name == self.name)
160
+ .order_by(PeerModel.last_update_on.desc())
161
+ )
162
+ r = r.fetchone()
163
+ if r is None:
164
+ raise KeyError(f"Username not found: {peer_id_or_username}")
165
+ if len(r) == 4:
166
+ peer_id, access_hash, peer_type, last_update_on = r
167
+ else:
168
+ raise ValueError(f"The result does not contain the expected tuple of values. Received: {r}")
169
+ if last_update_on:
170
+ if abs(time.time() - last_update_on) > self.USERNAME_TTL:
171
+ raise KeyError(f"Username expired: {peer_id_or_username}")
172
+ return get_input_peer(peer_id, access_hash, peer_type)
173
+
174
+ else:
175
+ raise ValueError("peer_id_or_username must be an integer (ID) or string (Username).")
176
+
177
+ async def get_peer_by_username(self, username: str):
178
+ async with self.session_maker() as session:
179
+ peer_alias = aliased(PeerModel)
180
+ username_alias = aliased(UsernameModel)
181
+ r = await session.execute(
182
+ select(peer_alias.id, peer_alias.access_hash, peer_alias.type, peer_alias.last_update_on)
183
+ .join(username_alias, username_alias.id == peer_alias.id)
184
+ .filter(username_alias.username == username, username_alias.session_name == self.name)
185
+ .order_by(peer_alias.last_update_on.desc())
186
+ )
187
+ r = r.fetchone()
188
+ if r is None:
189
+ raise KeyError(f"Username not found: {username}")
190
+
191
+ peer_id, access_hash, peer_type, last_update_on = r
192
+ return get_input_peer(peer_id, access_hash, peer_type)
193
+
194
+ async def update_state(self, value: Tuple[int, int, int, int, int] = object):
195
+ async with self.session_maker() as session:
196
+ if value == object:
197
+ result = await session.execute(
198
+ select(UpdateStateModel).filter_by(session_name=self.name)
199
+ )
200
+ return result.scalars().all()
201
+ else:
202
+ if isinstance(value, int):
203
+ await session.execute(
204
+ delete(UpdateStateModel)
205
+ .where(UpdateStateModel.session_name == self.name, UpdateStateModel.id == value)
206
+ )
207
+ else:
208
+ state = await session.execute(
209
+ select(UpdateStateModel).filter_by(session_name=self.name, id=value[0])
210
+ )
211
+ state_instance = state.scalar_one_or_none()
212
+
213
+ if state_instance:
214
+ state_instance.pts = value[1]
215
+ state_instance.qts = value[2]
216
+ state_instance.date = value[3]
217
+ state_instance.seq = value[4]
218
+ else:
219
+ state_instance = UpdateStateModel(
220
+ id=value[0],
221
+ session_name=self.name,
222
+ pts=value[1],
223
+ qts=value[2],
224
+ date=value[3],
225
+ seq=value[4]
226
+ )
227
+ session.add(state_instance)
228
+
229
+ await session.commit()
230
+
231
+ async def get_peer_by_phone_number(self, phone_number: str):
232
+ if not (peer := await Peer.filter(
233
+ session__name=self.name, phone_number=phone_number
234
+ ).values_list("id", "access_hash", "type")):
235
+ raise KeyError(f"Phone number not found: {phone_number}")
236
+ return get_input_peer(*peer)
237
+
238
+ async def _get(self, attr: str):
239
+ return await Session.get(name=self.name).values_list(attr, flat=True)
240
+
241
+ async def _set(self, attr: str, value: Any):
242
+ await Session.update_or_create({attr: value}, name=self.name)
243
+
244
+ async def _accessor(self, attr: str, value: Any = object):
245
+ if value == object:
246
+ return await self._get(attr)
247
+ else:
248
+ await self._set(attr, value)
249
+
250
+ async def dc_id(self, value: int = object):
251
+ return await self._accessor('dc_id', value)
252
+
253
+ async def api_id(self, value: int = object):
254
+ return await self._accessor('api_id', value)
255
+
256
+ async def test_mode(self, value: bool = object):
257
+ return await self._accessor('test_mode', value)
258
+
259
+ async def auth_key(self, value: bytes = object):
260
+ return await self._accessor('auth_key', value)
261
+
262
+ async def date(self, value: int = object):
263
+ return await self._accessor('date', value)
264
+
265
+ async def user_id(self, value: int = object):
266
+ return await self._accessor('user_id', value)
267
+
268
+ async def is_bot(self, value: bool = object):
269
+ return await self._accessor('is_bot', value)
270
+
271
+ async def version(self, value: int = object):
272
+ if value == object:
273
+ ver = await Version.first()
274
+ return ver.number
275
+ else:
276
+ await Version.update_or_create({"number": value})
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xync-client
3
- Version: 0.0.43.dev32
3
+ Version: 0.0.43.dev35
4
4
  Author-email: Mike Artemiev <mixartemev@gmail.com>
5
5
  Project-URL: Homepage, https://gitlab.com/XyncNet/client
6
6
  Project-URL: Repository, https://gitlab.com/XyncNet/client
@@ -19,6 +19,7 @@ tests/_todo_refact/Wallet/test_agent.py
19
19
  tests/_todo_refact/Wallet/test_ex.py
20
20
  xync_client/__init__.py
21
21
  xync_client/loader.py
22
+ xync_client/pg_storage.py
22
23
  xync_client/pm_unifier.py
23
24
  xync_client/pyro.py
24
25
  xync_client.egg-info/PKG-INFO