xync-client 0.0.25.dev39__tar.gz → 0.0.25.dev41__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 (84) hide show
  1. {xync_client-0.0.25.dev39/xync_client.egg-info → xync_client-0.0.25.dev41}/PKG-INFO +1 -1
  2. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/tests/TestAgent.py +12 -1
  3. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/tests/TestEx.py +21 -22
  4. xync_client-0.0.25.dev41/xync_client/Abc/Ex.py +361 -0
  5. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Htx/agent.py +3 -2
  6. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Htx/etype/ad.py +4 -3
  7. xync_client-0.0.25.dev41/xync_client/Htx/ex.py +157 -0
  8. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/TgWallet/ex.py +17 -16
  9. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41/xync_client.egg-info}/PKG-INFO +1 -1
  10. xync_client-0.0.25.dev39/xync_client/Abc/Ex.py +0 -203
  11. xync_client-0.0.25.dev39/xync_client/Htx/ex.py +0 -91
  12. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/.env.sample +0 -0
  13. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/.gitignore +0 -0
  14. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/.pre-commit-config.yaml +0 -0
  15. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/README.md +0 -0
  16. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/makefile +0 -0
  17. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/pyproject.toml +0 -0
  18. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/setup.cfg +0 -0
  19. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/tests/TestAsset.py +0 -0
  20. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/tests/TestOrder.py +0 -0
  21. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/tests/_todo_refact/Binance/test_binance.py +0 -0
  22. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/tests/_todo_refact/Bybit/test_bybit.py +0 -0
  23. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/tests/_todo_refact/Bybit/test_bybit_p2p.py +0 -0
  24. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/tests/_todo_refact/Gate/test_gate.py +0 -0
  25. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/tests/_todo_refact/Htx/test_htx_p2p.py +0 -0
  26. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/tests/_todo_refact/Wallet/test_agent.py +0 -0
  27. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/tests/_todo_refact/Wallet/test_ex.py +0 -0
  28. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/tests/_todo_refact/__init__.py +0 -0
  29. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/tests/_todo_refact/_test_ex.py +0 -0
  30. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Abc/Agent.py +0 -0
  31. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Abc/Asset.py +0 -0
  32. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Abc/AuthTrait.py +0 -0
  33. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Abc/Base.py +0 -0
  34. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Abc/BaseTest.py +0 -0
  35. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Abc/InAgent.py +0 -0
  36. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Abc/Order.py +0 -0
  37. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Binance/__init__.py +0 -0
  38. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Binance/binance_async.py +0 -0
  39. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Binance/earn_api.py +0 -0
  40. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Binance/ex.py +0 -0
  41. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Binance/exceptions.py +0 -0
  42. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Binance/sapi.py +0 -0
  43. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Binance/web_c2c.py +0 -0
  44. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/BingX/__init__.py +0 -0
  45. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/BingX/agent.py +0 -0
  46. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/BingX/base.py +0 -0
  47. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/BingX/ex.py +0 -0
  48. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/BingX/pyd.py +0 -0
  49. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/BingX/req.mjs +0 -0
  50. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/BingX/sign.js +0 -0
  51. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/BingX/test/main.py +0 -0
  52. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/BitGet/__init__.py +0 -0
  53. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/BitGet/agent.py +0 -0
  54. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/BitGet/ex.py +0 -0
  55. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/BitGet/req.mjs +0 -0
  56. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Bybit/agent.py +0 -0
  57. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Bybit/ex.py +0 -0
  58. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Bybit/web_earn.py +0 -0
  59. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Bybit/web_p2p.py +0 -0
  60. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Gate/ex.py +0 -0
  61. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Gate/premarket.py +0 -0
  62. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Htx/earn.py +0 -0
  63. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Htx/etype/__init__.py +0 -0
  64. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Htx/etype/cred.py +0 -0
  65. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Htx/etype/pm.py +0 -0
  66. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/KuCoin/pub.py +0 -0
  67. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/KuCoin/web.py +0 -0
  68. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Okx/ex.py +0 -0
  69. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Okx/pyd.py +0 -0
  70. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/TgWallet/agent.py +0 -0
  71. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/TgWallet/asset.py +0 -0
  72. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/TgWallet/auth.py +0 -0
  73. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/TgWallet/inAgent.py +0 -0
  74. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/TgWallet/order.py +0 -0
  75. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/TgWallet/pyd.py +0 -0
  76. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/TgWallet/pyro.py +0 -0
  77. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/TgWallet/web.py +0 -0
  78. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/__init__.py +0 -0
  79. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/loader.py +0 -0
  80. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/pyro.py +0 -0
  81. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client.egg-info/SOURCES.txt +0 -0
  82. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client.egg-info/dependency_links.txt +0 -0
  83. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client.egg-info/requires.txt +0 -0
  84. {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: xync-client
3
- Version: 0.0.25.dev39
3
+ Version: 0.0.25.dev41
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
@@ -3,7 +3,7 @@ import logging
3
3
  import pytest
4
4
  from xync_schema.enums import ExStatus, ExAction
5
5
  from xync_schema.models import Ex, TestEx, Fiat, Ad, Coin, Cur
6
- from xync_schema.pydantic import FiatNew
6
+ from xync_schema.types import FiatNew, BaseAd
7
7
 
8
8
  from xync_client.Abc.Base import DictOfDicts, ListOfDicts
9
9
  from xync_client.Abc.BaseTest import BaseTest
@@ -25,6 +25,17 @@ class TestAgent(BaseTest):
25
25
  yield clients
26
26
  [(await taker.close(), await maker.close()) for taker, maker in clients]
27
27
 
28
+ # 42
29
+ async def test_ad(self, clients: list[BaseExClient]):
30
+ for client in clients:
31
+ if not self.ad.get(client.ex.id):
32
+ await self.test_ads(clients)
33
+ ad: BaseAd = await client.ad(self.ad[client.ex.id].id)
34
+ ok = isinstance(ad, BaseAd)
35
+ t, _ = await TestEx.update_or_create({"ok": ok}, ex=client.ex, action=ExAction.ad)
36
+ assert t.ok, "No ad"
37
+ logging.info(f"{client.ex.name}: {ExAction.ad.name} - ok")
38
+
28
39
  # 0
29
40
  async def test_get_orders(self, clients: list[BaseAgentClient]):
30
41
  for taker, maker in clients:
@@ -1,19 +1,21 @@
1
1
  import logging
2
2
 
3
3
  import pytest
4
- from xync_schema.pydantic import PmPyd, BaseAd, CurEpyd, CoinEpyd
4
+ from xync_schema.types import Pm, BaseAd, CurE, CoinE
5
5
 
6
6
  from xync_client.Abc.BaseTest import BaseTest
7
7
  from xync_schema.enums import ExStatus, ExType, ExAction
8
8
  from xync_schema.models import Ex, TestEx as ExTest
9
9
 
10
- from xync_client.Abc.Base import BaseClient, FlatDict, MapOfIdsList
10
+ from xync_client.Abc.Base import BaseClient, MapOfIdsList
11
11
  from xync_client.Abc.Ex import BaseExClient
12
12
 
13
13
 
14
14
  @pytest.mark.asyncio(loop_scope="session")
15
15
  class TestEx(BaseTest):
16
- ad: dict[str, BaseAd] = {}
16
+ coins: dict[int, dict[str, CoinE]] = {}
17
+ curs: dict[int, dict[str, CurE]] = {}
18
+ ad: dict[int, BaseAd] = {}
17
19
 
18
20
  @pytest.fixture
19
21
  async def clients(self) -> list[BaseClient]:
@@ -26,17 +28,18 @@ class TestEx(BaseTest):
26
28
  # 19
27
29
  async def test_curs(self, clients: list[BaseExClient]):
28
30
  for client in clients:
29
- curs: list[CurEpyd] = await client.curs()
30
- ok = self.is_list_of_objects(curs, CurEpyd)
31
+ curs: dict[str, CurE] = await client.curs()
32
+ ok = self.is_dict_of_objects(curs, CurE)
31
33
  t, _ = await ExTest.update_or_create({"ok": ok}, ex=client.ex, action=ExAction.curs)
32
34
  assert t.ok, "No curs"
35
+ self.curs[client.ex.id] = curs
33
36
  logging.info(f"{client.ex.name}: {ExAction.curs.name} - ok")
34
37
 
35
38
  # 20
36
39
  async def test_pms(self, clients: list[BaseExClient]):
37
40
  for client in clients:
38
- pms: dict[int | str, PmPyd] = await client.pms()
39
- ok = self.is_dict_of_objects(pms, PmPyd)
41
+ pms: dict[int | str, Pm] = await client.pms()
42
+ ok = self.is_dict_of_objects(pms, Pm)
40
43
  t, _ = await ExTest.update_or_create({"ok": ok}, ex=client.ex, action=ExAction.pms)
41
44
  assert t.ok, "No pms"
42
45
  logging.info(f"{client.ex.name}: {ExAction.pms.name} - ok")
@@ -53,17 +56,18 @@ class TestEx(BaseTest):
53
56
  # 22
54
57
  async def test_coins(self, clients: list[BaseExClient]):
55
58
  for client in clients:
56
- coins: FlatDict = await client.coins()
57
- ok = self.is_list_of_objects(coins, CoinEpyd)
59
+ coins: dict[str, CoinE] = await client.coins()
60
+ ok = self.is_dict_of_objects(coins, CoinE)
58
61
  t, _ = await ExTest.update_or_create({"ok": ok}, ex=client.ex, action=ExAction.coins)
59
62
  assert t.ok, "No coins"
63
+ self.coins[client.ex.id] = coins
60
64
  logging.info(f"{client.ex.name}: {ExAction.coins.name} - ok")
61
65
 
62
66
  # 23
63
67
  async def test_pairs(self, clients: list[BaseExClient]):
64
68
  for client in clients:
65
- pairs: MapOfIdsList = await client.pairs()
66
- ok = self.is_map_of_ids(pairs)
69
+ pairs_buy, pairs_sell = await client.pairs()
70
+ ok = self.is_map_of_ids(pairs_buy) and self.is_map_of_ids(pairs_sell)
67
71
  t, _ = await ExTest.update_or_create({"ok": ok}, ex=client.ex, action=ExAction.pairs)
68
72
  assert t.ok, "No coins"
69
73
  logging.info(f"{client.ex.name}: {ExAction.pairs.name} - ok")
@@ -71,18 +75,13 @@ class TestEx(BaseTest):
71
75
  # 24
72
76
  async def test_ads(self, clients: list[BaseExClient]):
73
77
  for client in clients:
74
- ads: list[BaseAd] = await client.ads("USDT", "USD", False)
78
+ self.coins[client.ex.id] = self.coins.get(client.ex.id) or await client.coins()
79
+ self.curs[client.ex.id] = self.curs.get(client.ex.id) or await client.curs()
80
+ coin: int | str = self.coins[client.ex.id]["USDT"].exid
81
+ cur: int | str = self.curs[client.ex.id]["EUR"].exid
82
+ ads: list[BaseAd] = await client.ads(coin, cur, False)
75
83
  ok = self.is_list_of_objects(ads, BaseAd)
76
84
  t, _ = await ExTest.update_or_create({"ok": ok}, ex=client.ex, action=ExAction.ads)
77
85
  assert t.ok, "No ads"
78
- self.ad[client.ex.name] = ads[0] # for further use in test_ad
86
+ self.ad[client.ex.id] = ads[0] # for further use in test_ad
79
87
  logging.info(f"{client.ex.name}: {ExAction.ads.name} - ok")
80
-
81
- # 42
82
- async def test_ad(self, clients: list[BaseExClient]):
83
- for client in clients:
84
- ad: BaseAd = await client.ad(self.ad.get(client.ex.name, (await client.ads("USDT", "RUB", False))[0]).id)
85
- ok = isinstance(ad, BaseAd)
86
- t, _ = await ExTest.update_or_create({"ok": ok}, ex=client.ex, action=ExAction.ad)
87
- assert t.ok, "No ad"
88
- logging.info(f"{client.ex.name}: {ExAction.ad.name} - ok")
@@ -0,0 +1,361 @@
1
+ import logging
2
+ import re
3
+ from abc import abstractmethod
4
+
5
+ import msgspec
6
+ from msgspec import Struct
7
+ from tortoise.exceptions import MultipleObjectsReturned, IntegrityError
8
+ from x_model.models import PydIn
9
+ from xync_schema import types
10
+ from xync_schema import models
11
+
12
+ from xync_client.Abc.Base import BaseClient, MapOfIdsList
13
+
14
+
15
+ class PmUni(PydIn):
16
+ _unq: list[str] = ["norm", "country"]
17
+
18
+ norm: str
19
+ acronym: str = None
20
+ country: str = None
21
+ alias: str = None
22
+ extra: str = None
23
+ bank: bool = None
24
+
25
+
26
+ class PmUnifier:
27
+ pms: dict[str, PmUni] = {} # {origin: normalized}
28
+
29
+ pm_map: dict[str, str] = {
30
+ "Юmoney": "YooMoney",
31
+ # "Local Bank (R-Green)": "Sberbank",
32
+ # "Local Bank (S-Green)": "Sberbank",
33
+ # "Local Card (Red)": "Alfa-Bank",
34
+ # "Local Card (Yellow)": "Tinkoff",
35
+ # "Local Card M-redTS": "MTS-bank",
36
+ # "Local Card-Green": "Sberbank",
37
+ # "Local Card-Yellow": "Tinkoff",
38
+ # "GTB Bank (Guarantee Trust Bank)": "GTBank",
39
+ }
40
+ re_bank = [
41
+ r"^bank (?!of )|bank$",
42
+ r" banka$",
43
+ r" bankas$",
44
+ r" bankası$",
45
+ r" banca$",
46
+ r"^banco(?! de | del )| banco$",
47
+ ]
48
+ re_extra = [
49
+ r"\(card\)$|\bpay$|\bmoney$|\bwallet$|\bcash$",
50
+ r"b\.s\.c\.|k\.s\.c|s\.a$| sv$| gt$",
51
+ ]
52
+ re_glut = [
53
+ r"\.io$|\.com$",
54
+ r"l'|d’|d'",
55
+ ]
56
+ i18n_map = {
57
+ # " ": " ",
58
+ "nationale": "national",
59
+ "а": "a",
60
+ "á": "a",
61
+ "â": "a",
62
+ "о": "o",
63
+ "ó": "o",
64
+ "ō": "o",
65
+ "ú": "u",
66
+ "ü": "u",
67
+ "ų": "u",
68
+ "с": "c",
69
+ "č": "c",
70
+ "ç": "c",
71
+ "é": "e",
72
+ "è": "e",
73
+ "ş": "s",
74
+ "š": "s",
75
+ "ř": "r",
76
+ "í": "i",
77
+ }
78
+ rms = " -:`'’′"
79
+
80
+ def __init__(self, countries: list[str]):
81
+ self.cts = countries
82
+
83
+ def countries(self, name: str):
84
+ for ct in self.cts:
85
+ # Если имя кончается на "Название_страны" в скобках
86
+ if (
87
+ ct
88
+ and self.pms[name].norm.endswith((ct + ")", ct))
89
+ and not self.pms[name].norm.endswith((" of " + ct, " of the " + ct, " and " + ct, " de " + ct))
90
+ ):
91
+ self.pms[name].norm = self.pms[name].norm.replace(ct, "")
92
+ self.pms[name].country = ct
93
+ self.clear(name)
94
+ return
95
+
96
+ def extra(self, name: str):
97
+ for r in self.re_extra:
98
+ if match := re.search(r, self.pms[name].norm):
99
+ self.pms[name].norm = self.pms[name].norm.replace(match.group(), "")
100
+ self.pms[name].extra = match.group()
101
+ self.clear(name)
102
+ return
103
+
104
+ def alias(self, name: str):
105
+ if match := re.search(r"\(.+\)$", self.pms[name].norm):
106
+ self.pms[name].norm = self.pms[name].norm.replace(match.group(), "")
107
+ self.pms[name].alias = match.group()[1:-1]
108
+ self.clear(name)
109
+ return
110
+
111
+ def bank(self, name: str):
112
+ for r in self.re_bank:
113
+ if match := re.search(r, self.pms[name].norm):
114
+ self.pms[name].norm = self.pms[name].norm.replace(match.group(), "")
115
+ self.pms[name].bank = True
116
+ self.clear(name)
117
+ return
118
+
119
+ def acro(self, name: str):
120
+ acr = "".join(
121
+ wrd[0]
122
+ for wrd in self.pms[name].norm.split(" ")
123
+ if not wrd.isupper()
124
+ and not wrd.startswith(("(", "the"))
125
+ and len(wrd) > 2
126
+ or (len(wrd) > 1 and wrd.istitle())
127
+ ).upper()
128
+ if len(acr) >= 2 and (
129
+ f"({acr})" in self.pms[name].norm
130
+ or self.pms[name].norm.startswith((acr + " ", acr + ":"))
131
+ or self.pms[name].norm.endswith(" " + acr)
132
+ ):
133
+ self.pms[name].norm = self.pms[name].norm.replace(acr, "", 1).replace("()", "", 1).strip()
134
+ self.pms[name].acronym = acr
135
+ self.clear(name)
136
+
137
+ def slim(self, name: str):
138
+ for rm in self.re_glut:
139
+ self.pms[name].norm = re.sub(rm, "", self.pms[name].norm)
140
+
141
+ def i18n(self, name: str):
142
+ for src, trgt in self.i18n_map.items():
143
+ self.pms[name].norm = self.pms[name].norm.replace(src, trgt)
144
+
145
+ def clear(self, name: str):
146
+ self.pms[name].norm = self.pms[name].norm.replace("()", "").replace(" ", " ").strip(" -:")
147
+
148
+ def __call__(self, s: str) -> PmUni:
149
+ # если в словаре замен есть текущее назвние - меняем, иначе берем строку до запятой
150
+ self.pms[s] = PmUni(norm=self.pm_map.get(s, s.split(",")[0]))
151
+ # вырезаем мусорные добавки
152
+ self.slim(s)
153
+ # заменяем локальные символы на англ:
154
+ self.i18n(s)
155
+ # находим и вырезаем аббревиатуру, если есть
156
+ self.acro(s)
157
+ # уменьшаем все буквы
158
+ self.pms[s].norm = self.pms[s].norm.lower()
159
+ # находим и вырезаем страны, если есть
160
+ self.countries(s)
161
+ self.bank(s)
162
+ self.extra(s)
163
+ self.alias(s)
164
+ self.bank(s)
165
+ self.countries(s)
166
+ self.extra(s)
167
+ # вырезаем каждый символ rms
168
+ [self.pms[s].norm.replace(rm, "") for rm in self.rms]
169
+
170
+ return self.pms[s]
171
+
172
+
173
+ class BaseExClient(BaseClient):
174
+ @abstractmethod
175
+ def pm_type_map(self, type_: models.Pmex) -> str: ...
176
+
177
+ # 19: Список поддерживаемых валют тейкера
178
+ @abstractmethod
179
+ async def curs(self) -> dict[str, types.CurE]: # {cur.ticker: cur}
180
+ ...
181
+
182
+ # 20: Список платежных методов
183
+ @abstractmethod
184
+ async def pms(self, cur: models.Cur = None) -> dict[int | str, types.Pm]: # {pm.exid: pm}
185
+ ...
186
+
187
+ # 21: Список платежных методов по каждой валюте
188
+ @abstractmethod
189
+ async def cur_pms_map(self) -> MapOfIdsList: # {cur.exid: [pm.exid]}
190
+ ...
191
+
192
+ # 22: Список торгуемых монет (с ограничениям по валютам, если есть)
193
+ @abstractmethod
194
+ async def coins(self) -> dict[str, types.CoinE]: # {coin.ticker: coin}
195
+ ...
196
+
197
+ # 23: Список пар валюта/монет
198
+ @abstractmethod
199
+ async def pairs(self) -> tuple[MapOfIdsList, MapOfIdsList]: ...
200
+
201
+ # 24: Список объяв по (buy/sell, cur, coin, pm)
202
+ @abstractmethod
203
+ async def ads(
204
+ self, coin_exid: str, cur_exid: str, is_sell: bool, pm_exids: list[str | int] = None, amount: int = None
205
+ ) -> list[types.BaseAd]: # {ad.id: ad}
206
+ ...
207
+
208
+ # 42: Чужая объява по id
209
+ @abstractmethod
210
+ async def ad(self, ad_id: int) -> types.BaseAd: ...
211
+
212
+ # Преобразрование объекта объявления из формата биржи в формат xync
213
+ @abstractmethod
214
+ async def ad_epyd2pydin(self, ad: types.BaseAd) -> types.BaseAdIn: ... # my_uid: for MyAd
215
+
216
+ # 99: Страны
217
+ async def countries(self) -> list[Struct]:
218
+ return []
219
+
220
+ # Импорт Pm-ов (с Pmcur-, Pmex- и Pmcurex-ами) и валют (с Curex-ами) с биржи в бд
221
+ async def set_pmcurexs(self):
222
+ # Curs
223
+ cur_pyds: dict[str, types.CurE] = await self.curs()
224
+ curs: dict[int | str, models.Cur] = {
225
+ cur_pyd.exid: (await models.Cur.update_or_create({"rate": cur_pyd.rate}, ticker=tkr))[0]
226
+ for tkr, cur_pyd in cur_pyds.items()
227
+ }
228
+ curexs = [models.Curex(**c.model_dump(), cur=curs[c.exid], ex=self.ex) for c in cur_pyds.values()]
229
+ # Curex
230
+ await models.Curex.bulk_create(
231
+ curexs, update_fields=["minimum", "rounding_scale"], on_conflict=["cur_id", "ex_id"]
232
+ )
233
+
234
+ countries = await self.countries()
235
+
236
+ cur_map = {
237
+ 172: "CNY",
238
+ 8: "KRW",
239
+ 25: "MMK",
240
+ }
241
+ for c in countries:
242
+ if c.cur_id not in curs:
243
+ cur, _ = await models.Cur.get_or_create(id=c.cur_id, ticker=cur_map[c.cur_id])
244
+ c.cur_id = cur.id
245
+ # Country preparing
246
+ # countries = sorted(
247
+ # (c for c in countries if c.code not in (999, 9999, 441624, 999999)), key=lambda x: x.name
248
+ # ) # sort and filter
249
+ cnts = {
250
+ "BosniaandHerzegovina": "BA",
251
+ "Brunei": "BN",
252
+ "Congo": "CD",
253
+ "Djibouti": "DJ",
254
+ "Guinea": "GN",
255
+ "Iraq": "IQ",
256
+ "Kyrgyzstan": "KG",
257
+ "ComorosIslands": "KM",
258
+ "Liberia": "LR",
259
+ "Libya": "LY",
260
+ "Yemen": "YE",
261
+ "Zimbabwe": "ZW",
262
+ "United States of America": "US",
263
+ "Lebanon": "LB",
264
+ "Central African Republic": "XA",
265
+ "Laos": "LA",
266
+ "Tanzania": "TZ",
267
+ "Bangladesh": "BD",
268
+ }
269
+ [setattr(c, "short", cnts.get(c.name, c.short)) for c in countries] # add missed shortNames
270
+ # Countries create
271
+ cntrs: [models.Country] = [models.Country(**msgspec.to_builtins(c)) for c in countries]
272
+ # ids only for HTX
273
+ await models.Country.bulk_create(cntrs, ignore_conflicts=True)
274
+ # todo: curexcountry
275
+
276
+ # Pms
277
+ pms_epyds: dict[int | str, types.Pm] = {
278
+ k: v for k, v in sorted((await self.pms()).items(), key=lambda x: x[1].name)
279
+ } # sort by name
280
+ pms: dict[int | str, models.Pm] = dict({})
281
+ prev = 0, "", "" # id, normd-name, orig-name
282
+ cntrs = [c.lower() for c in await models.Country.all().values_list("name", flat=True)]
283
+ uni = PmUnifier(cntrs)
284
+ for k, pm in pms_epyds.items():
285
+ pmu: PmUni = uni(pm.name)
286
+ country = await models.Country.get(name__iexact=cnt) if (cnt := pmu.country) else None
287
+ if prev[2] == pm.name and pmu.country == prev[3]: # оригинальное имя не уникально на этой бирже
288
+ logging.warning(f"Pm: '{pm.name}' duplicated with ids {prev[0]}: {k} on {self.ex.name}")
289
+ # новый Pm не добавляем, а берем старый с этим названием
290
+ pm_ = pms.get(prev[0], await models.Pm.get_or_none(norm=prev[1], country=country))
291
+ # и добавляем Pmex для него
292
+ await models.Pmex.update_or_create({"name": pm.name}, ex=self.ex, exid=k, pm=pm_)
293
+ elif (
294
+ prev[1] == pmu.norm and pmu.country == prev[3]
295
+ ): # 2 разных оригинальных имени на этой бирже совпали при нормализации
296
+ logging.error(
297
+ f"Pm: {pm.name}&{prev[2]} overnormd as {pmu.norm} with ids {prev[0]}: {k} on {self.ex.name}"
298
+ )
299
+ # новый Pm не добавляем, только Pmex для него
300
+ # новый Pm не добавляем, а берем старый с этим названием
301
+ pm_ = pms.get(prev[0], await models.Pm.get_or_none(norm=prev[1], country=country))
302
+ # и добавляем.обновляем Pmex для него
303
+ await models.Pmex.update_or_create({"pm": pm_}, ex=self.ex, exid=k, name=pm.name)
304
+ else:
305
+ # todo: add logo and all other
306
+ d = {**pmu.df_unq(), "country": country}
307
+ if pm.logo:
308
+ d["defaults"]["logo"] = pm.logo
309
+ if pm.type_:
310
+ d["defaults"]["type_"] = pm.type_
311
+ try:
312
+ pms[k], _ = await models.Pm.update_or_create(**d)
313
+ except (MultipleObjectsReturned, IntegrityError) as e:
314
+ print(d)
315
+ raise e
316
+ prev = k, pmu.norm, pm.name, pmu.country
317
+ # Pmexs
318
+ pmexs = [models.Pmex(exid=k, ex=self.ex, pm=pm, name=pms_epyds[k].name) for k, pm in pms.items()]
319
+ await models.Pmex.bulk_create(pmexs, on_conflict=["ex_id", "exid"], update_fields=["pm_id", "name", "name_"])
320
+ # Pmex banks
321
+ for k, pm in pms_epyds.items():
322
+ if banks := pm.banks:
323
+ pmex = await models.Pmex.get(ex=self.ex, exid=k) # pm=pms[k],
324
+ for b in banks:
325
+ await models.PmexBank.update_or_create({"name": b.name}, exid=b.exid, pmex=pmex)
326
+
327
+ cur2pms = await self.cur_pms_map()
328
+ # # Link PayMethods with currencies
329
+ pmcurs = set()
330
+ for cur_id, exids in cur2pms.items():
331
+ for exid in exids:
332
+ pmcurs.add(
333
+ (
334
+ await models.Pmcur.update_or_create(
335
+ cur=curs[cur_id],
336
+ pm=pms.get(exid)
337
+ or (await models.Pmex.get(ex=self.ex, exid=exid).prefetch_related("pm")).pm,
338
+ )
339
+ )[0]
340
+ )
341
+ # pmcurexs = [Pmcurex(pmcur=pmcur, ex=self.ex) for pmcur in pmcurs]
342
+ # await Pmcurex.bulk_create(pmcurexs)
343
+
344
+ # Импорт монет (с Coinex-ами) с биржи в бд
345
+ async def set_coinexs(self):
346
+ coins: dict[str, types.CoinE] = await self.coins()
347
+ coins_db: dict[int, models.Coin] = {
348
+ c.exid: (await models.Coin.update_or_create(ticker=c.ticker))[0] for c in coins.values()
349
+ }
350
+ coinexs: list[models.Coinex] = [
351
+ models.Coinex(coin=coins_db[c.exid], ex=self.ex, exid=c.exid, minimum=c.minimum) for c in coins.values()
352
+ ]
353
+ await models.Coinex.bulk_create(coinexs, update_fields=["minimum"], on_conflict=["coin_id", "ex_id"])
354
+
355
+ # Сохранение чужого объявления (с Pm-ами) в бд
356
+ async def ad_pydin2db(self, ad_pydin: types.BaseAdIn) -> models.Ad:
357
+ df, unq = ad_pydin.args()
358
+ ad_db, _ = await models.Ad.update_or_create(df, **unq)
359
+ if getattr(ad_pydin, "pms_", None): # if it ListItem, not Full One # todo: remove?
360
+ await ad_db.pms.add(*ad_pydin.pms_)
361
+ return ad_db
@@ -83,8 +83,9 @@ class Private(BaseAgentClient):
83
83
  async def ads_switch(self) -> bool:
84
84
  pass
85
85
 
86
- async def get_user(self, user_id) -> dict:
87
- pass
86
+ async def get_user(self, user_id: int) -> dict:
87
+ user = (await self._get(f"/-/x/otc/v1/user/{user_id}/info"))["data"]
88
+ return user
88
89
 
89
90
  async def send_user_msg(self, msg: str, file=None) -> bool:
90
91
  pass
@@ -1,6 +1,7 @@
1
1
  from typing import Literal
2
2
 
3
3
  from pydantic import BaseModel
4
+ from xync_schema.types import BaseAd
4
5
 
5
6
 
6
7
  class TradeRule(BaseModel):
@@ -56,7 +57,7 @@ class PayName(BaseModel):
56
57
  id: int
57
58
 
58
59
 
59
- class Resp(BaseModel):
60
+ class Resp(BaseAd):
60
61
  blockType: int
61
62
  chargeType: bool
62
63
  coinId: int
@@ -71,12 +72,12 @@ class Resp(BaseModel):
71
72
  labelName: str | None = None
72
73
  maxTradeLimit: str
73
74
  merchantLevel: int
74
- merchantTags: str | None = None
75
+ merchantTags: list[str] | None
75
76
  minTradeLimit: str
76
77
  orderCompleteRate: str
77
78
  payMethod: str
78
79
  payMethods: list[PayMethod]
79
- payName: list[PayName] # приходит массив объектов внутри строки
80
+ payName: str # list[PayName] # приходит массив объектов внутри строки
80
81
  payTerm: int
81
82
  price: str
82
83
  seaViewRoom: str | None = None