xync-client 0.0.25.dev91__tar.gz → 0.0.25.dev109__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 (100) hide show
  1. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/.gitignore +2 -1
  2. {xync_client-0.0.25.dev91/xync_client.egg-info → xync_client-0.0.25.dev109}/PKG-INFO +3 -2
  3. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/pyproject.toml +2 -2
  4. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/tests/TestEx.py +1 -1
  5. xync_client-0.0.25.dev109/xync_client/Abc/Agent.py +141 -0
  6. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/Abc/AuthTrait.py +3 -2
  7. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/Abc/Ex.py +33 -6
  8. xync_client-0.0.25.dev109/xync_client/Abc/InAgent.py +12 -0
  9. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/Abc/types.py +6 -2
  10. xync_client-0.0.25.dev109/xync_client/Binance/etype/ad.py +125 -0
  11. xync_client-0.0.25.dev109/xync_client/Binance/etype/pm.py +15 -0
  12. xync_client-0.0.25.dev109/xync_client/Binance/ex.py +139 -0
  13. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/BitGet/ex.py +25 -3
  14. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/Bybit/ex.py +3 -3
  15. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/Gate/ex.py +8 -0
  16. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/Htx/etype/ad.py +1 -1
  17. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/Mexc/ex.py +1 -1
  18. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/Okx/ex.py +8 -0
  19. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/TgWallet/agent.py +148 -138
  20. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/TgWallet/auth.py +3 -1
  21. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/TgWallet/ex.py +1 -1
  22. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/TgWallet/pyd.py +8 -2
  23. xync_client-0.0.25.dev109/xync_client/TgWallet/pyro.py +156 -0
  24. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/loader.py +3 -0
  25. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109/xync_client.egg-info}/PKG-INFO +3 -2
  26. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client.egg-info/SOURCES.txt +2 -1
  27. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client.egg-info/requires.txt +2 -1
  28. xync_client-0.0.25.dev91/tests/res.js +0 -26246
  29. xync_client-0.0.25.dev91/xync_client/Abc/Agent.py +0 -201
  30. xync_client-0.0.25.dev91/xync_client/Abc/InAgent.py +0 -6
  31. xync_client-0.0.25.dev91/xync_client/Binance/ex.py +0 -81
  32. xync_client-0.0.25.dev91/xync_client/TgWallet/pyro.py +0 -52
  33. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/.env.sample +0 -0
  34. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/.pre-commit-config.yaml +0 -0
  35. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/README.md +0 -0
  36. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/makefile +0 -0
  37. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/setup.cfg +0 -0
  38. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/tests/TestAgent.py +0 -0
  39. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/tests/TestAsset.py +0 -0
  40. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/tests/TestOrder.py +0 -0
  41. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/tests/_todo_refact/Binance/test_binance.py +0 -0
  42. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/tests/_todo_refact/Bybit/test_bybit.py +0 -0
  43. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/tests/_todo_refact/Bybit/test_bybit_p2p.py +0 -0
  44. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/tests/_todo_refact/Gate/test_gate.py +0 -0
  45. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/tests/_todo_refact/Htx/test_htx_p2p.py +0 -0
  46. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/tests/_todo_refact/Wallet/test_agent.py +0 -0
  47. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/tests/_todo_refact/Wallet/test_ex.py +0 -0
  48. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/tests/_todo_refact/__init__.py +0 -0
  49. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/tests/_todo_refact/_test_ex.py +0 -0
  50. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/Abc/Asset.py +0 -0
  51. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/Abc/Base.py +0 -0
  52. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/Abc/BaseTest.py +0 -0
  53. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/Abc/Order.py +0 -0
  54. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/Binance/__init__.py +0 -0
  55. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/Binance/binance_async.py +0 -0
  56. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/Binance/earn_api.py +0 -0
  57. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/Binance/exceptions.py +0 -0
  58. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/Binance/sapi.py +0 -0
  59. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/Binance/web_c2c.py +0 -0
  60. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/BingX/__init__.py +0 -0
  61. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/BingX/agent.py +0 -0
  62. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/BingX/base.py +0 -0
  63. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/BingX/etype/ad.py +0 -0
  64. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/BingX/etype/pm.py +0 -0
  65. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/BingX/ex.py +0 -0
  66. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/BingX/req.mjs +0 -0
  67. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/BingX/sign.js +0 -0
  68. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/BitGet/__init__.py +0 -0
  69. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/BitGet/agent.py +0 -0
  70. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/BitGet/etype/ad.py +0 -0
  71. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/BitGet/req.mjs +0 -0
  72. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/BitPapa/ex.py +0 -0
  73. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/Bybit/agent.py +0 -0
  74. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/Bybit/etype/ad.py +0 -0
  75. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/Bybit/web_earn.py +0 -0
  76. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/Bybit/web_p2p.py +0 -0
  77. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/Gate/etype/ad.py +0 -0
  78. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/Gate/premarket.py +0 -0
  79. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/Htx/agent.py +0 -0
  80. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/Htx/earn.py +0 -0
  81. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/Htx/etype/__init__.py +0 -0
  82. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/Htx/etype/cred.py +0 -0
  83. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/Htx/etype/pm.py +0 -0
  84. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/Htx/ex.py +0 -0
  85. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/KuCoin/etype/ad.py +0 -0
  86. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/KuCoin/etype/pm.py +0 -0
  87. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/KuCoin/ex.py +0 -0
  88. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/KuCoin/web.py +0 -0
  89. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/Mexc/etype/ad.py +0 -0
  90. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/Mexc/etype/pm.py +0 -0
  91. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/Okx/etype/ad.py +0 -0
  92. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/Okx/etype/pm.py +0 -0
  93. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/TgWallet/asset.py +0 -0
  94. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/TgWallet/inAgent.py +0 -0
  95. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/TgWallet/order.py +0 -0
  96. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/TgWallet/web.py +0 -0
  97. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/__init__.py +0 -0
  98. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client/pm_unifier.py +0 -0
  99. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client.egg-info/dependency_links.txt +0 -0
  100. {xync_client-0.0.25.dev91 → xync_client-0.0.25.dev109}/xync_client.egg-info/top_level.txt +0 -0
@@ -7,4 +7,5 @@ __pycache__
7
7
  /*.egg-info
8
8
  /build
9
9
  .vscode
10
- /xync_client/Gate/res.js
10
+ /xync_client/Gate/res.js
11
+ /tests/res.js
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xync-client
3
- Version: 0.0.25.dev91
3
+ Version: 0.0.25.dev109
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
@@ -12,9 +12,10 @@ Requires-Dist: msgspec
12
12
  Requires-Dist: python-binance
13
13
  Requires-Dist: pybit
14
14
  Requires-Dist: pyotp
15
- Requires-Dist: pyrogram
15
+ Requires-Dist: kurigram
16
16
  Requires-Dist: python-dotenv
17
17
  Requires-Dist: python-okx
18
+ Requires-Dist: tgcrypto
18
19
  Requires-Dist: xn-client>=0.0.3dev3
19
20
  Requires-Dist: xync-schema
20
21
  Provides-Extra: dev
@@ -14,10 +14,10 @@ dependencies = [
14
14
  "python-binance",
15
15
  "pybit",
16
16
  "pyotp",
17
- "pyrogram",
17
+ "kurigram",
18
18
  "python-dotenv",
19
19
  "python-okx",
20
- # "tgcrypto",
20
+ "tgcrypto",
21
21
  "xn-client>=0.0.3dev3",
22
22
  "xync-schema"
23
23
  ]
@@ -17,7 +17,7 @@ class TestEx(BaseTest):
17
17
  @pytest.fixture
18
18
  async def clients(self) -> list[BaseClient]:
19
19
  exs = await Ex.filter(status__gt=ExStatus.plan)
20
- [await ex.fetch_related("actors__agent") for ex in exs if ex.type_ == ExType.tg]
20
+ [await ex for ex in exs if ex.type_ == ExType.tg]
21
21
  clients: list[BaseExClient] = [ex.client() for ex in exs]
22
22
  yield clients
23
23
  [await cl.close() for cl in clients]
@@ -0,0 +1,141 @@
1
+ from abc import abstractmethod
2
+
3
+ from pydantic import BaseModel
4
+ from xync_schema import models
5
+ from xync_schema.models import OrderStatus, Coin, Cur, Ad, AdStatus, Agent, Cred
6
+ from xync_schema.types import BaseAd, AdSaleIn, AdBuyIn, BaseOrder
7
+
8
+ from xync_client.Abc.Ex import BaseExClient
9
+ from xync_client.Abc.Base import BaseClient
10
+ from xync_client.Abc.types import CredExOut
11
+
12
+
13
+ class BaseAgentClient(BaseClient):
14
+ def __init__(self, agent: Agent):
15
+ self.agent: Agent = agent
16
+ super().__init__(self.agent.actor.ex) # , "host_p2p"
17
+ self.ex_client: BaseExClient = self.ex.client()
18
+
19
+ # 0: Получшение ордеров в статусе status, по монете coin, в валюте coin, в направлении is_sell: bool
20
+ @abstractmethod
21
+ async def get_orders(
22
+ self, status: OrderStatus = OrderStatus.created, coin: Coin = None, cur: Cur = None, is_sell: bool = None
23
+ ) -> list: ...
24
+
25
+ # 1: [T] Запрос на старт сделки
26
+ @abstractmethod
27
+ async def order_request(self, order: BaseOrder) -> dict: ...
28
+
29
+ # async def start_order(self, order: Order) -> OrderOutClient:
30
+ # return OrderOutClient(self, order)
31
+
32
+ # 1N: [M] - Запрос мейкеру на сделку
33
+ @abstractmethod
34
+ async def order_request_ask(self) -> dict: ... # , ad: Ad, amount: float, pm: Pm, taker: Agent
35
+
36
+ # 2N: [M] - Уведомление об отмене запроса на сделку
37
+ @abstractmethod
38
+ async def request_canceled_notify(self) -> int: ... # id
39
+
40
+ # # # Cred
41
+ @property
42
+ @abstractmethod
43
+ def fiat_pyd(self) -> BaseModel.__class__: ...
44
+
45
+ @abstractmethod
46
+ def fiat_args2pyd(
47
+ self, exid: int | str, cur: str, detail: str, name: str, fid: int, typ: str, extra=None
48
+ ) -> fiat_pyd: ...
49
+
50
+ # 25: Список реквизитов моих платежных методов
51
+ @abstractmethod
52
+ async def creds(self) -> list: ... # {credex.exid: {cred}}
53
+
54
+ # Создание реквизита на бирже
55
+ async def cred_new(self, cred: models.Cred) -> CredExOut: ...
56
+
57
+ # await models.Actor.get_or_create({"name": cred.exid}, ex=self.ex_client.ex, exid=self.agent.actor.exid)
58
+ # cred_db: Cred = (await self.cred_pyd2db(cred, self.agent.user_id))[0]
59
+ # if not (credex := models.CredEx.get_or_none(cred=cred_db, ex=self.agent.ex)):
60
+ # credex, _ = models.CredEx.update_or_create({}, cred=cred_db, ex=self.agent.ex)
61
+ # return credex
62
+
63
+ # 27: Редактирование реквизита моего платежного метода
64
+ @abstractmethod
65
+ async def cred_upd(self, cred: models.Cred, exid: int) -> CredExOut: ...
66
+
67
+ # 28: Удаление реквизита моего платежного метода
68
+ @abstractmethod
69
+ async def cred_del(self, exid: int) -> int: ...
70
+
71
+ # # # Ad
72
+ # 29: Список моих объявлений
73
+ @abstractmethod
74
+ async def my_ads(self, status: AdStatus = None) -> list[BaseAd]: ...
75
+
76
+ # 30: Создание объявления
77
+ @abstractmethod
78
+ async def ad_new(self, ad: BaseAd) -> Ad: ...
79
+
80
+ # 31: Редактирование объявления
81
+ @abstractmethod
82
+ async def ad_upd(
83
+ self,
84
+ offer_id: int,
85
+ amount: int,
86
+ creds: list[Cred] = None,
87
+ price: float = None,
88
+ is_float: bool = None,
89
+ min_fiat: int = None,
90
+ details: str = None,
91
+ autoreply: str = None,
92
+ status: AdStatus = None,
93
+ ) -> Ad: ...
94
+
95
+ # 32: Удаление
96
+ @abstractmethod
97
+ async def ad_del(self, ad_id: int) -> bool: ...
98
+
99
+ # 33: Вкл/выкл объявления
100
+ @abstractmethod
101
+ async def ad_switch(self, offer_id: int, active: bool) -> bool: ...
102
+
103
+ # 34: Вкл/выкл всех объявлений
104
+ @abstractmethod
105
+ async def ads_switch(self, active: bool) -> bool: ...
106
+
107
+ # # # User
108
+ # 35: Получить объект юзера по его ид
109
+ @abstractmethod
110
+ async def get_user(self, user_id) -> dict: ...
111
+
112
+ # 36: Отправка сообщения юзеру с приложенным файлом
113
+ @abstractmethod
114
+ async def send_user_msg(self, msg: str, file=None) -> bool: ...
115
+
116
+ # 37: (Раз)Блокировать юзера
117
+ @abstractmethod
118
+ async def block_user(self, is_blocked: bool = True) -> bool: ...
119
+
120
+ # 38: Поставить отзыв юзеру
121
+ @abstractmethod
122
+ async def rate_user(self, positive: bool) -> bool: ...
123
+
124
+ # 39: Балансы моих монет
125
+ @abstractmethod
126
+ async def my_assets(self) -> dict: ...
127
+
128
+ # Сохранение объявления (с Pm/Cred-ами) в бд
129
+ async def ad_pydin2db(self, ad_pydin: AdSaleIn | AdBuyIn) -> Ad:
130
+ ad_db = await self.ex_client.ad_pydin2db(ad_pydin)
131
+ await ad_db.creds.add(*getattr(ad_pydin, "creds_", []))
132
+ return ad_db
133
+
134
+ # @staticmethod
135
+ # async def cred_e2db(cred_in: BaseUpd, banks: list[str] = None) -> bool:
136
+ # cred_db, _ = await models.Cred.update_or_create(**cred_in.df_unq())
137
+ # credex_in = models.CredEx.validate({"exid": cred_in.id, "cred_id": cred_db.id})
138
+ # credex_db, _ = await models.CredEx.update_or_create(**credex_in.df_unq())
139
+ # if banks: # only for SBP
140
+ # await cred_db.banks.add(*[await PmexBank.get(exid=b) for b in banks])
141
+ # return True
@@ -18,10 +18,11 @@ class BaseAuthTrait(Client):
18
18
  # noinspection PyUnresolvedReferences
19
19
  self.session.headers.update(auth_hdrs)
20
20
 
21
- async def _post(self, url: str, data: dict = None, data_key: str = None):
21
+ async def _post(self, url: str, data: dict = None, data_key: str = None, headers: dict = None):
22
22
  dt = {"json" if isinstance(data, dict) else "data": data}
23
23
  # noinspection PyUnresolvedReferences
24
- resp = await self.session.post(url, **dt, headers=self._prehook(data))
24
+ hdrs = {**self._prehook(data), **(headers or {})}
25
+ resp = await self.session.post(url, **dt, headers=hdrs)
25
26
  return await self._proc(resp, data_key, data)
26
27
 
27
28
  async def _proc(self, resp: ClientResponse, data_key: str = None, body: dict | str = None) -> dict | str:
@@ -8,7 +8,9 @@ from xync_schema import models
8
8
  from xync_schema.types import CurEx, CoinEx, BaseAd, BaseAdIn
9
9
 
10
10
  from xync_client.Abc.Base import BaseClient, MapOfIdsList
11
- from xync_client.Abc.types import PmEx, PmIn
11
+ from xync_client.Abc.types import PmEx
12
+ from xync_client.TgWallet.pyro import PyroClient
13
+ from xync_client.loader import bot
12
14
  from xync_client.pm_unifier import PmUnifier, PmUni
13
15
 
14
16
 
@@ -64,6 +66,7 @@ class BaseExClient(BaseClient):
64
66
 
65
67
  # Импорт Pm-ов (с Pmcur-, Pmex- и Pmcurex-ами) и валют (с Curex-ами) с биржи в бд
66
68
  async def set_pmcurexs(self):
69
+ PyroClient(bot)
67
70
  # Curs
68
71
  cur_pyds: dict[str, CurEx] = await self.curs()
69
72
  curs: dict[int | str, models.Cur] = {
@@ -127,11 +130,15 @@ class BaseExClient(BaseClient):
127
130
  uni = self.unifier_class(cntrs)
128
131
  for k, pm in pms_epyds.items():
129
132
  pmu: PmUni = uni(pm.name)
130
- country = await models.Country.get(name__iexact=cnt) if (cnt := pmu.country) else None
133
+ country_id = (
134
+ await models.Country.get(name__iexact=cnt).values_list("id", flat=True)
135
+ if (cnt := pmu.country)
136
+ else None
137
+ )
131
138
  if prev[2] == pm.name and pmu.country == prev[3]: # оригинальное имя не уникально на этой бирже
132
139
  logging.warning(f"Pm: '{pm.name}' duplicated with ids {prev[0]}: {k} on {self.ex.name}")
133
140
  # новый Pm не добавляем, а берем старый с этим названием
134
- pm_ = pms.get(prev[0], await models.Pm.get_or_none(norm=prev[1], country=country))
141
+ pm_ = pms.get(prev[0], await models.Pm.get_or_none(norm=prev[1], country_id=country_id))
135
142
  # и добавляем Pmex для него
136
143
  await models.Pmex.update_or_create({"name": pm.name}, ex=self.ex, exid=k, pm=pm_)
137
144
  elif (
@@ -142,11 +149,30 @@ class BaseExClient(BaseClient):
142
149
  )
143
150
  # новый Pm не добавляем, только Pmex для него
144
151
  # новый Pm не добавляем, а берем старый с этим названием
145
- pm_ = pms.get(prev[0], await models.Pm.get_or_none(norm=prev[1], country=country))
152
+ pm_ = pms.get(prev[0], await models.Pm.get_or_none(norm=prev[1], country_id=country_id))
146
153
  # и добавляем.обновляем Pmex для него
147
154
  await models.Pmex.update_or_create({"pm": pm_}, ex=self.ex, exid=k, name=pm.name)
148
155
  else:
149
- pmin = PmIn(**{**pmu.model_dump(exclude_none=True), "country": country, "typ": pm.typ, "logo": pm.logo})
156
+ pmin = models.Pm.validate({**pmu.model_dump(), "country_id": country_id, "typ": pm.typ})
157
+ # # logo
158
+ # if pm.logo and not await models.File.exists(name=pm.logo):
159
+ # if not pm.logo.startswith("https:"):
160
+ # if not pm.logo.startswith("/"):
161
+ # pm.logo = "/" + pm.logo
162
+ # pm.logo = "https://" + pm.logo
163
+ # async with ClientSession() as ss:
164
+ # resp = await ss.get(pm.logo)
165
+ # if resp.ok:
166
+ # byts = await resp.read()
167
+ # upf, ref = await pyro.save_file(byts, resp.content_type)
168
+ # await sleep(1)
169
+ # typ = FileType[resp.content_type.split("/")[-1]]
170
+ # file, _ = await models.File.update_or_create(
171
+ # {"ref": ref, "size": len(byts), "typ": typ}, name=pm.logo
172
+ # )
173
+ # # fil = await pyro.get_file(file.ref) # check
174
+ # pmin.logo_id = file.id
175
+ # # /logo
150
176
  try:
151
177
  pms[k], _ = await models.Pm.update_or_create(**pmin.df_unq())
152
178
  except (MultipleObjectsReturned, IntegrityError) as e:
@@ -208,7 +234,8 @@ class BaseExClient(BaseClient):
208
234
 
209
235
  # Сохранение чужого объявления (с Pm-ами) в бд
210
236
  async def ad_pydin2db(self, ad_pydin: BaseAdIn) -> models.Ad:
211
- ad_db, _ = await models.Ad.update_or_create(**ad_pydin.df_unq())
237
+ ad_in = models.Ad.validate(ad_pydin.model_dump())
238
+ ad_db, _ = await models.Ad.update_or_create(**ad_in.df_unq())
212
239
  if getattr(ad_pydin, "pms_", None): # if it ListItem, not Full One # todo: remove?
213
240
  await ad_db.pms.add(*ad_pydin.pms_)
214
241
  return ad_db
@@ -0,0 +1,12 @@
1
+ from abc import abstractmethod
2
+
3
+
4
+ class BaseInAgentClient:
5
+ def __init__(self): ...
6
+
7
+ @abstractmethod
8
+ async def start_listen(self) -> bool: ...
9
+
10
+ # 3N: [T] - Уведомление об одобрении запроса на сделку
11
+ @abstractmethod
12
+ async def request_accepted_notify(self) -> int: ... # id
@@ -1,5 +1,5 @@
1
1
  from pydantic import BaseModel
2
- from x_model.types import New
2
+ from x_model.types import BaseUpd
3
3
  from xync_schema.enums import PmType
4
4
  from xync_schema.models import Country, Pm, Ex
5
5
  from xync_schema.types import PmexBank
@@ -18,7 +18,7 @@ class PmEx(BaseModel, PmTrait):
18
18
  name: str
19
19
 
20
20
 
21
- class PmIn(New, PmUni, PmTrait):
21
+ class PmIn(BaseUpd, PmUni, PmTrait):
22
22
  _unq = "norm", "country"
23
23
  country: Country | None = None
24
24
 
@@ -34,3 +34,7 @@ class PmExIn(BaseModel):
34
34
 
35
35
  class Config:
36
36
  arbitrary_types_allowed = True
37
+
38
+
39
+ class CredExOut(BaseModel):
40
+ id: int
@@ -0,0 +1,125 @@
1
+ from typing import List, Optional, Dict, Any
2
+ from pydantic import BaseModel
3
+ from xync_schema.types import BaseAd
4
+
5
+
6
+ class TradeMethod(BaseModel):
7
+ iconUrlColor: Optional[str] = None
8
+ identifier: str
9
+ payAccount: Optional[str] = None
10
+ payBank: Optional[str] = None
11
+ payId: Optional[str] = None
12
+ payMethodId: str
13
+ paySubBank: Optional[str] = None
14
+ payType: str
15
+ tradeMethodBgColor: str
16
+ tradeMethodName: str
17
+ tradeMethodShortName: Optional[str] = None
18
+
19
+
20
+ class Adv(BaseModel):
21
+ abnormalStatusList: Optional[List[str]] = None
22
+ adAdditionalKycVerifyItems: Optional[List[Any]] = None
23
+ adTradeInstructionTagInfoRets: Optional[List[Any]] = None
24
+ advNo: str
25
+ advStatus: Optional[str] = None
26
+ advUpdateTime: Optional[str] = None
27
+ advVisibleRet: Optional[Dict[str, Any]] = None
28
+ allowTradeMerchant: Optional[bool] = None
29
+ amountAfterEditing: Optional[str] = None
30
+ asset: str
31
+ assetLogo: Optional[str] = None
32
+ assetScale: int
33
+ assetVo: Optional[Dict[str, Any]] = None
34
+ autoReplyMsg: Optional[str] = None
35
+ buyerBtcPositionLimit: Optional[str] = None
36
+ buyerKycLimit: Optional[str] = None
37
+ buyerRegDaysLimit: Optional[str] = None
38
+ classify: str
39
+ closeReason: Optional[str] = None
40
+ commissionRate: str
41
+ createTime: Optional[str] = None
42
+ currencyRate: Optional[str] = None
43
+ dynamicMaxSingleTransAmount: str
44
+ dynamicMaxSingleTransQuantity: str
45
+ fiatScale: int
46
+ fiatSymbol: str
47
+ fiatUnit: str
48
+ fiatVo: Optional[Dict[str, Any]] = None
49
+ initAmount: Optional[str] = None
50
+ inventoryType: Optional[str] = None
51
+ invisibleReason: Optional[str] = None
52
+ invisibleType: Optional[str] = None
53
+ isSafePayment: bool
54
+ isTradable: bool
55
+ launchCountry: Optional[List[str]] = None
56
+ maxSingleTransAmount: str
57
+ maxSingleTransQuantity: str
58
+ minFiatAmountForAdditionalKyc: Optional[str] = None
59
+ minSingleTransAmount: str
60
+ minSingleTransQuantity: str
61
+ minTakerFee: Optional[str] = None
62
+ nonTradableRegions: Optional[List[str]] = None
63
+ offlineReason: Optional[str] = None
64
+ payTimeLimit: int
65
+ price: str
66
+ priceFloatingRatio: Optional[str] = None
67
+ priceScale: int
68
+ priceType: Optional[str] = None
69
+ rateFloatingRatio: Optional[str] = None
70
+ remarks: Optional[str] = None
71
+ storeInformation: Optional[Dict[str, Any]] = None
72
+ surplusAmount: str
73
+ takerAdditionalKycRequired: int
74
+ takerCommissionRate: Optional[str] = None
75
+ tradableQuantity: str
76
+ tradeMethodCommissionRates: Optional[List[Any]] = None
77
+ tradeMethods: List[TradeMethod]
78
+ tradeType: str
79
+ userAllTradeCountMax: Optional[str] = None
80
+ userAllTradeCountMin: Optional[str] = None
81
+ userBuyTradeCountMax: Optional[str] = None
82
+ userBuyTradeCountMin: Optional[str] = None
83
+ userSellTradeCountMax: Optional[str] = None
84
+ userSellTradeCountMin: Optional[str] = None
85
+ userTradeCompleteCountMin: Optional[str] = None
86
+ userTradeCompleteRateFilterTime: Optional[str] = None
87
+ userTradeCompleteRateMin: Optional[str] = None
88
+ userTradeCountFilterTime: Optional[str] = None
89
+ userTradeType: Optional[str] = None
90
+ userTradeVolumeAsset: Optional[str] = None
91
+ userTradeVolumeFilterTime: Optional[str] = None
92
+ userTradeVolumeMax: Optional[str] = None
93
+ userTradeVolumeMin: Optional[str] = None
94
+
95
+
96
+ class Advertiser(BaseModel):
97
+ activeTimeInSecond: int
98
+ advConfirmTime: Optional[str] = None
99
+ badges: Optional[List[str]] = None
100
+ email: Optional[str] = None
101
+ isBlocked: bool
102
+ margin: Optional[str] = None
103
+ marginUnit: Optional[str] = None
104
+ mobile: Optional[str] = None
105
+ monthFinishRate: float
106
+ monthOrderCount: int
107
+ nickName: str
108
+ orderCount: Optional[int] = None
109
+ positiveRate: float
110
+ proMerchant: Optional[Dict[str, Any]] = None
111
+ realName: Optional[str] = None
112
+ registrationTime: Optional[str] = None
113
+ tagIconUrls: List[str]
114
+ userGrade: int
115
+ userIdentity: str
116
+ userNo: str
117
+ userType: str
118
+ vipLevel: Optional[int] = None
119
+
120
+
121
+ class Ad(BaseAd):
122
+ adv: Adv
123
+ advertiser: Advertiser
124
+ privilegeDesc: Optional[str] = None
125
+ privilegeType: int | None = None
@@ -0,0 +1,15 @@
1
+ from typing import Optional
2
+ from pydantic import BaseModel
3
+
4
+ class PmE(BaseModel):
5
+ id: Optional[int] = None
6
+ payMethodId: str = ""
7
+ payAccount: Optional[str] = None
8
+ payBank: Optional[str] = None
9
+ paySubBank: Optional[str] = None
10
+ payType: Optional[str] = None
11
+ identifier: str
12
+ iconUrlColor: str
13
+ tradeMethodName: str
14
+ tradeMethodShortName: Optional[str] = None
15
+ tradeMethodBgColor: str
@@ -0,0 +1,139 @@
1
+ from asyncio import run
2
+
3
+ from x_model import init_db
4
+
5
+ from xync_client.Abc.Ex import BaseExClient
6
+ from xync_client.Binance.etype import pm, ad
7
+ from xync_client.Abc.types import PmEx
8
+ from xync_client.Abc.Base import MapOfIdsList
9
+ from xync_client.loader import PG_DSN
10
+ from xync_client.pm_unifier import PmUnifier
11
+
12
+ from xync_schema.models import Ex
13
+ from xync_schema import types
14
+ from xync_schema import models
15
+
16
+
17
+ class ExClient(BaseExClient):
18
+ def __init__(self, ex: Ex):
19
+ # self.sapi = Sapi(*bkeys)
20
+ super().__init__(ex)
21
+
22
+ class BinanceUnifier(PmUnifier):
23
+ pm_map = {"Banque de développement local (BDL)": "Banque de développement local (BDL)"}
24
+
25
+ unifier_class = BinanceUnifier
26
+
27
+ async def _pms(self, cur) -> list[pm.PmE]:
28
+ json_data = {
29
+ "fiat": cur,
30
+ "classifies": [
31
+ "mass",
32
+ "profession",
33
+ "fiat_trade",
34
+ ],
35
+ }
36
+ pms = await self._post("/bapi/c2c/v2/public/c2c/adv/filter-conditions", data=json_data)
37
+ return [pm.PmE(**_pm) for _pm in pms["data"]["tradeMethods"]]
38
+
39
+ async def curs(self) -> dict[int, types.CurEx]:
40
+ curs = await self._post("/bapi/c2c/v1/friendly/c2c/trade-rule/fiat-list")
41
+ return {c["currencyCode"]: types.CurEx(exid=c["currencyCode"], ticker=c["currencyCode"], scale=c["currencyScale"]) for c in curs["data"]}
42
+
43
+ async def coins(self) -> dict[int, types.CoinEx]:
44
+ for cur in (await self.curs()).keys():
45
+ coins = (await self._post("/bapi/c2c/v2/friendly/c2c/portal/config", {"fiat": cur}))["data"]["areas"][0][
46
+ "tradeSides"
47
+ ][0]["assets"]
48
+ return {coin["asset"]: types.CoinEx(exid=coin["asset"], ticker=coin["asset"]) for coin in coins}
49
+
50
+ async def pairs(self) -> MapOfIdsList:
51
+ coins = (await self.coins()).keys()
52
+ curs = (await self.curs()).keys()
53
+ p = {cur: {c for c in coins} for cur in curs}
54
+ return p, p
55
+
56
+ async def pms(self, cur: models.Cur = None) -> dict[int | str, PmEx]:
57
+ all_pms = {}
58
+ for cur in (await self.curs()).values():
59
+ pms = await self._pms(cur.ticker)
60
+ for p in pms:
61
+ all_pms[p.identifier] = PmEx(exid=p.identifier, name=p.tradeMethodName, logo=p.iconUrlColor)
62
+ return all_pms
63
+
64
+ # 22: Cur -> [Pm] rels
65
+ async def cur_pms_map(self) -> MapOfIdsList: # {cur.exid: [pm.exid], [pm.exid]}
66
+ res = await self.curs()
67
+ mp = {c: await self._get_pms_for_cur(c) for c in res.keys()}
68
+ return mp
69
+
70
+ # # 22: Cur -> [Pm] rels
71
+ # async def cur_countries_map(self) -> dict[int, set[int]]: # {cur.exid: [pm.exid]}
72
+ # res = await self._get_pms_and_country_for_cur()
73
+ # wrong_pms = {4, 34, 212, 239, 363, 498, 548, 20009, 20010} # these ids not exist in pms
74
+ # return {c['currencyId']: set(c['supportPayments']) - wrong_pms for c in res['currency'] if c["supportPayments"]}
75
+
76
+ async def ads(self, coin_exid: str, cur_exid: str, is_sell: bool, pm_exids: list[str] = None) -> list[ad.Ad]:
77
+ data = {
78
+ "fiat": cur_exid,
79
+ "page": 1,
80
+ "rows": 10,
81
+ "tradeType": "BUY" if is_sell else "SELL",
82
+ "asset": coin_exid,
83
+ "countries": [],
84
+ "proMerchantAds": False,
85
+ "shieldMerchantAds": False,
86
+ "filterType": "all",
87
+ "periods": [],
88
+ "additionalKycVerifyFilter": 0,
89
+ "publisherType": "merchant",
90
+ "payTypes": pm_exids,
91
+ "classifies": [
92
+ "mass",
93
+ "profession",
94
+ "fiat_trade",
95
+ ],
96
+ "tradedWith": False,
97
+ "followed": False,
98
+ }
99
+ ads = await self._post("/bapi/c2c/v2/friendly/c2c/adv/search", data=data)
100
+ return [ad.Ad(id=_ad["adv"]["advNo"], price=_ad["adv"]["price"], **_ad) for _ad in ads["data"]]
101
+
102
+ async def _get_pms_for_cur(self, cur: str) -> ([str], [str]):
103
+ data = {"fiat": cur, "classifies": ["mass", "profession"]}
104
+ res = await self._post("/bapi/c2c/v2/public/c2c/adv/filter-conditions", data)
105
+ return [r["identifier"] for r in res["data"]["tradeMethods"]]
106
+ # , [
107
+ # r["scode"] for r in res["data"]["countries"] if r["scode"] != "ALL"
108
+ # ] # countries,tradeMethods,periods
109
+
110
+
111
+ # class Private(Public): # todo: base class: Public or Client?
112
+ # class Private(Client):
113
+ # # auth: dict =
114
+ # headers: dict = {
115
+ # "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.134 Safari/537.36",
116
+ # "Content-Type": "application/json",
117
+ # "clienttype": "web",
118
+ # }
119
+ #
120
+ # def seq_headers(self):
121
+ # return {
122
+ # "csrftoken": self.auth["tok"],
123
+ # "cookie": f'p20t=web.{self.id}.{self.auth["cook"]}',
124
+ # }
125
+
126
+
127
+ async def main():
128
+ _ = await init_db(PG_DSN, models)
129
+ ex = await Ex.get(name="Binance")
130
+ cl = ExClient(ex)
131
+ await cl.pairs()
132
+ await cl.pms()
133
+ await cl.ads("ETH", "GEL", False)
134
+ await cl.set_pmcurexs()
135
+ await cl.close()
136
+
137
+
138
+ if __name__ == "__main__":
139
+ run(main())
@@ -30,6 +30,15 @@ class ExClient(BaseExClient):
30
30
  "accept-language": "ru,en;q=0.9",
31
31
  "content-type": "application/json;charset=UTF-8",
32
32
  "deviceid": "883e1394d8a2278418b6f02804df16c4",
33
+ "devicelanguage": "ru_RU",
34
+ "gaclientid": "2005612572.1744132981",
35
+ "securitynew": "true",
36
+ "terminalcode": "534488ac72b9b87c3ba87725387639ac",
37
+ "terminaltype": "1",
38
+ "timezone": "GMT+03:00",
39
+ "tm": "1744133012568",
40
+ "uhti": "w17441330126555b55e2d8627",
41
+ "usenewpwdversion": "true",
33
42
  }
34
43
 
35
44
  async def _coin_cur_pms(self, trying: int = 0) -> dict:
@@ -52,12 +61,25 @@ class ExClient(BaseExClient):
52
61
 
53
62
  async def curs(self) -> dict[str, types.CurEx]:
54
63
  curs = (await self._coin_cur_pms())["fiatInfoRespList"]
55
- return {cur["fiatCode"]: types.CurEx(exid=cur["fiatCode"], ticker=cur["fiatCode"]) for cur in curs}
64
+ return {
65
+ cur["fiatCode"]: types.CurEx(
66
+ exid=cur["fiatCode"],
67
+ ticker=cur["fiatCode"],
68
+ scale=cur["fiatPrecision"],
69
+ minimum=int(float(cur["orderMinLimit"])),
70
+ )
71
+ for cur in curs
72
+ }
56
73
 
57
74
  async def coins(self) -> dict[str, types.CoinEx]:
58
75
  coins: list[dict] = (await self._coin_cur_pms())["coinInfoRespList"]
59
76
  return {
60
- coin["coinCode"]: types.CoinEx(exid=coin["coinCode"], ticker=coin["coinCode"], scale=coin["coinPrecision"])
77
+ coin["coinCode"]: types.CoinEx(
78
+ exid=coin["coinCode"],
79
+ ticker=coin["coinCode"],
80
+ scale=coin["coinPrecision"],
81
+ minimum=int(float(coin["orderMinLimit"])),
82
+ )
61
83
  for coin in coins
62
84
  }
63
85
 
@@ -72,7 +94,7 @@ class ExClient(BaseExClient):
72
94
  pms: list[dict] = (await self._coin_cur_pms())["fiatInfoRespList"]
73
95
  for pm in pms:
74
96
  for p in pm["paymethodInfo"]:
75
- pp[p["paymethodId"]] = PmEx(exid=p["paymethodId"], name=p["paymethodName"])
97
+ pp[p["paymethodId"]] = PmEx(exid=p["paymethodId"], name=p["paymethodName"], logo=p.get("iconUrl"))
76
98
  return pp
77
99
 
78
100
  async def ads(