xync-client 0.0.145__tar.gz → 0.0.148__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.

Potentially problematic release.


This version of xync-client might be problematic. Click here for more details.

Files changed (125) hide show
  1. {xync_client-0.0.145/xync_client.egg-info → xync_client-0.0.148}/PKG-INFO +4 -1
  2. {xync_client-0.0.145 → xync_client-0.0.148}/pyproject.toml +3 -0
  3. xync_client-0.0.148/xync_client/Abc/AdLoader.py +285 -0
  4. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Abc/Agent.py +2 -0
  5. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Abc/Ex.py +11 -9
  6. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Abc/PmAgent.py +1 -1
  7. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Abc/xtype.py +3 -1
  8. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Bybit/InAgent.py +16 -11
  9. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Bybit/agent.py +181 -391
  10. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Bybit/etype/ad.py +1 -1
  11. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Bybit/etype/cred.py +1 -0
  12. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Bybit/etype/order.py +34 -16
  13. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Gmail/__init__.py +16 -3
  14. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Pms/Payeer/__init__.py +7 -5
  15. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Pms/Volet/__init__.py +1 -1
  16. {xync_client-0.0.145 → xync_client-0.0.148/xync_client.egg-info}/PKG-INFO +4 -1
  17. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client.egg-info/SOURCES.txt +1 -0
  18. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client.egg-info/requires.txt +3 -0
  19. {xync_client-0.0.145 → xync_client-0.0.148}/.env.sample +0 -0
  20. {xync_client-0.0.145 → xync_client-0.0.148}/.gitignore +0 -0
  21. {xync_client-0.0.145 → xync_client-0.0.148}/.pre-commit-config.yaml +0 -0
  22. {xync_client-0.0.145 → xync_client-0.0.148}/README.md +0 -0
  23. {xync_client-0.0.145 → xync_client-0.0.148}/__init__.py +0 -0
  24. {xync_client-0.0.145 → xync_client-0.0.148}/makefile +0 -0
  25. {xync_client-0.0.145 → xync_client-0.0.148}/setup.cfg +0 -0
  26. {xync_client-0.0.145 → xync_client-0.0.148}/tests/TestAgent.py +0 -0
  27. {xync_client-0.0.145 → xync_client-0.0.148}/tests/TestAsset.py +0 -0
  28. {xync_client-0.0.145 → xync_client-0.0.148}/tests/TestEx.py +0 -0
  29. {xync_client-0.0.145 → xync_client-0.0.148}/tests/TestOrder.py +0 -0
  30. {xync_client-0.0.145 → xync_client-0.0.148}/tests/_todo_refact/Binance/test_binance.py +0 -0
  31. {xync_client-0.0.145 → xync_client-0.0.148}/tests/_todo_refact/Bybit/test_bybit.py +0 -0
  32. {xync_client-0.0.145 → xync_client-0.0.148}/tests/_todo_refact/Bybit/test_bybit_p2p.py +0 -0
  33. {xync_client-0.0.145 → xync_client-0.0.148}/tests/_todo_refact/Gate/test_gate.py +0 -0
  34. {xync_client-0.0.145 → xync_client-0.0.148}/tests/_todo_refact/Wallet/test_agent.py +0 -0
  35. {xync_client-0.0.145 → xync_client-0.0.148}/tests/_todo_refact/Wallet/test_ex.py +0 -0
  36. {xync_client-0.0.145 → xync_client-0.0.148}/tests/_todo_refact/__init__.py +0 -0
  37. {xync_client-0.0.145 → xync_client-0.0.148}/tests/_todo_refact/_test_ex.py +0 -0
  38. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Abc/Asset.py +0 -0
  39. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Abc/Auth.py +0 -0
  40. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Abc/BaseTest.py +0 -0
  41. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Abc/Exception.py +0 -0
  42. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Abc/HasAbotUid.py +0 -0
  43. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Abc/InAgent.py +0 -0
  44. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Abc/Order.py +0 -0
  45. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Binance/__init__.py +0 -0
  46. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Binance/binance_async.py +0 -0
  47. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Binance/earn_api.py +0 -0
  48. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Binance/etype/ad.py +0 -0
  49. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Binance/etype/pm.py +0 -0
  50. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Binance/ex.py +0 -0
  51. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Binance/exceptions.py +0 -0
  52. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Binance/sapi.py +0 -0
  53. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Binance/web_c2c.py +0 -0
  54. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/BingX/__init__.py +0 -0
  55. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/BingX/agent.py +0 -0
  56. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/BingX/base.py +0 -0
  57. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/BingX/etype/ad.py +0 -0
  58. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/BingX/etype/pm.py +0 -0
  59. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/BingX/ex.py +0 -0
  60. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/BingX/req.mjs +0 -0
  61. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/BingX/sign.js +0 -0
  62. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/BitGet/__init__.py +0 -0
  63. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/BitGet/agent.py +0 -0
  64. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/BitGet/etype/ad.py +0 -0
  65. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/BitGet/ex.py +0 -0
  66. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/BitPapa/ex.py +0 -0
  67. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Bybit/ex.py +0 -0
  68. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Bybit/order.py +0 -0
  69. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Bybit/web_earn.py +0 -0
  70. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Bybit/web_p2p.py +0 -0
  71. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Bybit/ws.py +0 -0
  72. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Gate/etype/ad.py +0 -0
  73. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Gate/ex.py +0 -0
  74. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Gate/premarket.py +0 -0
  75. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Htx/agent.py +0 -0
  76. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Htx/earn.py +0 -0
  77. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Htx/etype/__init__.py +0 -0
  78. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Htx/etype/ad.py +0 -0
  79. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Htx/etype/cred.py +0 -0
  80. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Htx/etype/pm.py +0 -0
  81. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Htx/etype/test.py +0 -0
  82. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Htx/ex.py +0 -0
  83. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/KuCoin/etype/ad.py +0 -0
  84. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/KuCoin/etype/pm.py +0 -0
  85. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/KuCoin/ex.py +0 -0
  86. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/KuCoin/web.py +0 -0
  87. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Mexc/etype/ad.py +0 -0
  88. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Mexc/etype/pm.py +0 -0
  89. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Mexc/ex.py +0 -0
  90. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Okx/etype/ad.py +0 -0
  91. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Okx/etype/pm.py +0 -0
  92. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Okx/ex.py +0 -0
  93. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Pms/.gitignore +0 -0
  94. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Pms/Alfa/__init__.py +0 -0
  95. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Pms/Alfa/state.json +0 -0
  96. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Pms/MTS/__init__.py +0 -0
  97. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Pms/Ozon/__init__.py +0 -0
  98. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Pms/Payeer/.gitignore +0 -0
  99. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Pms/Payeer/api.py +0 -0
  100. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Pms/Payeer/login.py +0 -0
  101. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Pms/Sber/__init__.py +0 -0
  102. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Pms/Sber/utils.py +0 -0
  103. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Pms/Tinkoff/__init__.py +0 -0
  104. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Pms/Volet/_todo_req/req.mjs +0 -0
  105. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Pms/Volet/_todo_req/req.py +0 -0
  106. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Pms/Volet/api.py +0 -0
  107. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Pms/Volet/pl.py +0 -0
  108. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Pms/Xync/__main__.py +0 -0
  109. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Pms/Xync/ed.py +0 -0
  110. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/Pms/Yandex/__init__.py +0 -0
  111. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/TgWallet/agent.py +0 -0
  112. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/TgWallet/asset.py +0 -0
  113. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/TgWallet/auth.py +0 -0
  114. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/TgWallet/ex.py +0 -0
  115. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/TgWallet/inAgent.py +0 -0
  116. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/TgWallet/order.py +0 -0
  117. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/TgWallet/pyd.py +0 -0
  118. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/TgWallet/pyro.py +0 -0
  119. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/TgWallet/web.py +0 -0
  120. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/__init__.py +0 -0
  121. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/details.py +0 -0
  122. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/loader.py +0 -0
  123. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client/pm_unifier.py +0 -0
  124. {xync_client-0.0.145 → xync_client-0.0.148}/xync_client.egg-info/dependency_links.txt +0 -0
  125. {xync_client-0.0.145 → xync_client-0.0.148}/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.145
3
+ Version: 0.0.148
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
@@ -8,6 +8,9 @@ Requires-Python: >=3.11
8
8
  Requires-Dist: asynchuobi
9
9
  Requires-Dist: bs4
10
10
  Requires-Dist: bybit-p2p
11
+ Requires-Dist: google-api-python-client
12
+ Requires-Dist: google-auth-httplib2
13
+ Requires-Dist: google-auth-oauthlib
11
14
  Requires-Dist: requests-toolbelt
12
15
  Requires-Dist: msgspec
13
16
  Requires-Dist: python-binance
@@ -10,6 +10,9 @@ dependencies = [
10
10
  "asynchuobi",
11
11
  "bs4",
12
12
  "bybit-p2p",
13
+ "google-api-python-client",
14
+ "google-auth-httplib2",
15
+ "google-auth-oauthlib",
13
16
  "requests-toolbelt",
14
17
  "msgspec",
15
18
  "python-binance",
@@ -0,0 +1,285 @@
1
+ import logging
2
+ import re
3
+ from asyncio import sleep
4
+ from collections import defaultdict
5
+ from difflib import SequenceMatcher
6
+
7
+ from xync_schema import models
8
+ from xync_schema.xtype import BaseAd
9
+
10
+
11
+ class AdLoader:
12
+ ex: models.Ex
13
+ all_conds: dict[int, tuple[str, set[int]]] = {}
14
+ cond_sims: dict[int, int] = defaultdict(set)
15
+ rcond_sims: dict[int, set[int]] = defaultdict(set) # backward
16
+ tree: dict = {}
17
+
18
+ async def old_conds_load(self):
19
+ # пока не порешали рейс-кондишн, очищаем сиротские условия при каждом запуске
20
+ # [await c.delete() for c in await Cond.filter(ads__isnull=True)]
21
+ self.all_conds = {
22
+ c.id: (c.raw_txt, {a.maker.exid for a in c.ads})
23
+ for c in await models.Cond.all().prefetch_related("ads__maker")
24
+ }
25
+ for curr, old in await models.CondSim.filter().values_list("cond_id", "cond_rel_id"):
26
+ self.cond_sims[curr] = old
27
+ self.rcond_sims[old] |= {curr}
28
+
29
+ self.build_tree()
30
+ a = set()
31
+
32
+ def check_tree(tre):
33
+ for p, c in tre.items():
34
+ a.add(p)
35
+ check_tree(c)
36
+
37
+ for pr, ch in self.tree.items():
38
+ check_tree(ch)
39
+ if ct := set(self.tree.keys()) & a:
40
+ logging.exception(f"cycle cids: {ct}")
41
+
42
+ async def person_name_update(self, name: str, exid: int) -> models.Person:
43
+ if actor := await models.Actor.get_or_none(exid=exid, ex=self.ex).prefetch_related("person"):
44
+ actor.person.name = name
45
+ await actor.person.save()
46
+ return actor.person
47
+ # return await models.Person.create(note=f'{actor.ex_id}:{actor.exid}:{name}') # no person for just ads with no orders
48
+ raise ValueError(f"Agent #{exid} not found")
49
+
50
+ async def ad_load(
51
+ self,
52
+ pad: BaseAd,
53
+ cid: int = None,
54
+ ps: models.PairSide = None,
55
+ maker: models.Actor = None,
56
+ coinex: models.CoinEx = None,
57
+ curex: models.CurEx = None,
58
+ rname: str = None,
59
+ pms_from_cond: bool = False,
60
+ ) -> models.Ad:
61
+ if not maker:
62
+ if not (maker := await models.Actor.get_or_none(exid=pad.userId, ex=self.ex)):
63
+ person = await models.Person.create(name=rname, note=f"{self.ex.id}:{pad.userId}:{pad.nickName}")
64
+ maker = await models.Actor.create(name=pad.nickName, person=person, exid=pad.userId, ex=self.ex)
65
+ if rname:
66
+ await self.person_name_update(rname, int(pad.userId))
67
+ ps = ps or await models.PairSide.get_or_none(
68
+ is_sell=pad.side,
69
+ pair__coin__ticker=pad.tokenId,
70
+ pair__cur__ticker=pad.currencyId,
71
+ ).prefetch_related("pair")
72
+ # if not ps or not ps.pair:
73
+ # ... # THB/USDC: just for initial filling
74
+ ad_upd = models.Ad.validate(pad.model_dump(by_alias=True))
75
+ cur_scale = 10 ** (curex or await models.CurEx.get(coin_id=ps.pair.cur_id, ex=self.ex)).scale
76
+ coin_scale = 10 ** (coinex or await models.CoinEx.get(coin_id=ps.pair.coin_id, ex=self.ex)).scale
77
+ df_unq = ad_upd.df_unq(
78
+ maker_id=maker.id,
79
+ pair_side_id=ps.id,
80
+ amount=int(float(pad.quantity) * float(pad.price) * cur_scale),
81
+ quantity=int(float(pad.quantity) * coin_scale),
82
+ min_fiat=int(float(pad.minAmount) * cur_scale),
83
+ max_fiat=pad.maxAmount and int(float(pad.maxAmount) * cur_scale),
84
+ price=int(float(pad.price) * cur_scale),
85
+ premium=int(float(pad.premium) * 100),
86
+ cond_id=cid,
87
+ )
88
+ ad_db, _ = await models.Ad.update_or_create(**df_unq)
89
+ if not pms_from_cond:
90
+ await ad_db.pms.add(*(await models.Pm.filter(pmexs__ex=self.ex, pmexs__exid__in=pad.payments)))
91
+ return ad_db
92
+
93
+ async def cond_load( # todo: refact from Bybit Ad format to universal
94
+ self,
95
+ ad: BaseAd,
96
+ ps: models.PairSide = None,
97
+ force: bool = False,
98
+ rname: str = None,
99
+ coinex: models.CoinEx = None,
100
+ curex: models.CurEx = None,
101
+ pms_from_cond: bool = False,
102
+ ) -> tuple[models.Ad, bool]:
103
+ _sim, cid = None, None
104
+ ad_db = await models.Ad.get_or_none(exid=ad.id, maker__ex=self.ex).prefetch_related("cond")
105
+ # если точно такое условие уже есть в бд
106
+ if not (cleaned := clean(ad.remark)) or (cid := {oc[0]: ci for ci, oc in self.all_conds.items()}.get(cleaned)):
107
+ # и объява с таким ид уже есть, но у нее другое условие
108
+ if ad_db and ad_db.cond_id != cid:
109
+ # то обновляем ид ее условия
110
+ ad_db.cond_id = cid
111
+ await ad_db.save()
112
+ logging.info(f"{ad.nickName} upd cond#{ad_db.cond_id}->{cid}")
113
+ # old_cid = ad_db.cond_id # todo: solve race-condition, а пока что очищаем при каждом запуске
114
+ # if not len((old_cond := await Cond.get(id=old_cid).prefetch_related('ads')).ads):
115
+ # await old_cond.delete()
116
+ # logging.warning(f"Cond#{old_cid} deleted!")
117
+ return (
118
+ ad_db
119
+ or force
120
+ and await self.ad_load(
121
+ ad, cid, ps, coinex=coinex, curex=curex, rname=rname, pms_from_cond=pms_from_cond
122
+ )
123
+ ), False
124
+ # если эта объява в таким ид уже есть в бд, но с другим условием (или без), а текущего условия еще нет в бд
125
+ if ad_db:
126
+ await ad_db.fetch_related("cond__ads", "maker")
127
+ if not ad_db.cond_id or (
128
+ # у измененного условия этой объявы есть другие объявы?
129
+ (rest_ads := set(ad_db.cond.ads) - {ad_db})
130
+ and
131
+ # другие объявы этого условия принадлежат другим юзерам
132
+ {ra.maker_id for ra in rest_ads} - {ad_db.maker_id}
133
+ ):
134
+ # создадим новое условие и присвоим его только текущей объяве
135
+ cid = await self.cond_new(cleaned, {int(ad.userId)})
136
+ ad_db.cond_id = cid
137
+ await ad_db.save()
138
+
139
+ return ad_db, True
140
+ # а если других объяв со старым условием этой обявы нет, либо они все этого же юзера
141
+ # обновляем условие (в тч во всех ЕГО объявах)
142
+ ad_db.cond.last_ver = ad_db.cond.raw_txt
143
+ ad_db.cond.raw_txt = cleaned
144
+ await ad_db.cond.save()
145
+ await self.cond_upd(ad_db.cond, {ad_db.maker.exid})
146
+ # и подправим коэфициенты похожести нового текста
147
+ await self.fix_rel_sims(ad_db.cond_id, cleaned)
148
+ return ad_db, False
149
+
150
+ cid = await self.cond_new(cleaned, {int(ad.userId)})
151
+ return await self.ad_load(
152
+ ad, cid, ps, coinex=coinex, curex=curex, rname=rname, pms_from_cond=pms_from_cond
153
+ ), True
154
+
155
+ async def cond_new(self, txt: str, uids: set[int]) -> int:
156
+ new_cond, _ = await models.Cond.update_or_create(raw_txt=txt)
157
+ # и максимально похожую связь для нового условия (если есть >= 60%)
158
+ await self.cond_upd(new_cond, uids)
159
+ return new_cond.id
160
+
161
+ async def cond_upd(self, cond: models.Cond, uids: set[int]):
162
+ self.all_conds[cond.id] = cond.raw_txt, uids
163
+ # и максимально похожую связь для нового условия (если есть >= 60%)
164
+ old_cid, sim = await self.cond_get_max_sim(cond.id, cond.raw_txt, uids)
165
+ await self.actual_sim(cond.id, old_cid, sim)
166
+
167
+ def find_in_tree(self, cid: int, old_cid: int) -> bool:
168
+ if p := self.cond_sims.get(old_cid):
169
+ if p == cid:
170
+ return True
171
+ return self.find_in_tree(cid, p)
172
+ return False
173
+
174
+ async def cond_get_max_sim(self, cid: int, txt: str, uids: set[int]) -> tuple[int | None, int | None]:
175
+ # находим все старые тексты похожие на 90% и более
176
+ if len(txt) < 15:
177
+ return None, None
178
+ sims: dict[int, int] = {}
179
+ for old_cid, (old_txt, old_uids) in self.all_conds.items():
180
+ if len(old_txt) < 15 or uids == old_uids:
181
+ continue
182
+ elif not self.can_add_sim(cid, old_cid):
183
+ continue
184
+ if sim := get_sim(txt, old_txt):
185
+ sims[old_cid] = sim
186
+ # если есть, берем самый похожий из них
187
+ if sims:
188
+ old_cid, sim = max(sims.items(), key=lambda x: x[1])
189
+ await sleep(0.3)
190
+ return old_cid, sim
191
+ return None, None
192
+
193
+ def can_add_sim(self, cid: int, old_cid: int) -> bool:
194
+ if cid == old_cid:
195
+ return False
196
+ elif self.cond_sims.get(cid) == old_cid:
197
+ return False
198
+ elif self.find_in_tree(cid, old_cid):
199
+ return False
200
+ elif self.cond_sims.get(old_cid) == cid:
201
+ return False
202
+ elif cid in self.rcond_sims.get(old_cid, {}):
203
+ return False
204
+ elif old_cid in self.rcond_sims.get(cid, {}):
205
+ return False
206
+ return True
207
+
208
+ async def fix_rel_sims(self, cid: int, new_txt: str):
209
+ for rel_sim in await models.CondSim.filter(cond_rel_id=cid).prefetch_related("cond"):
210
+ if sim := get_sim(new_txt, rel_sim.cond.raw_txt):
211
+ rel_sim.similarity = sim
212
+ await rel_sim.save()
213
+ else:
214
+ await rel_sim.delete()
215
+
216
+ async def actual_cond(self):
217
+ for curr, old in await models.CondSim.all().values_list("cond_id", "cond_rel_id"):
218
+ self.cond_sims[curr] = old
219
+ self.rcond_sims[old] |= {curr}
220
+ for cid, (txt, uids) in self.all_conds.items():
221
+ old_cid, sim = await self.cond_get_max_sim(cid, txt, uids)
222
+ await self.actual_sim(cid, old_cid, sim)
223
+ # хз бля чо это ваще
224
+ # for ad_db in await models.Ad.filter(direction__pairex__ex=self.ex).prefetch_related("cond", "maker"):
225
+ # ad = Ad(id=str(ad_db.exid), userId=str(ad_db.maker.exid), remark=ad_db.cond.raw_txt)
226
+ # await self.cond_upsert(ad, force=True)
227
+
228
+ async def actual_sim(self, cid: int, old_cid: int, sim: int):
229
+ if not sim:
230
+ return
231
+ if old_sim := await models.CondSim.get_or_none(cond_id=cid):
232
+ if old_sim.cond_rel_id != old_cid:
233
+ if sim > old_sim.similarity:
234
+ logging.warning(f"R {cid}: {old_sim.similarity}->{sim} ({old_sim.cond_rel_id}->{old_cid})")
235
+ await old_sim.update_from_dict({"similarity": sim, "old_rel_id": old_cid}).save()
236
+ self._cond_sim_upd(cid, old_cid)
237
+ elif sim != old_sim.similarity:
238
+ logging.info(f"{cid}: {old_sim.similarity}->{sim}")
239
+ await old_sim.update_from_dict({"similarity": sim}).save()
240
+ else:
241
+ await models.CondSim.create(cond_id=cid, cond_rel_id=old_cid, similarity=sim)
242
+ self._cond_sim_upd(cid, old_cid)
243
+
244
+ def _cond_sim_upd(self, cid: int, old_cid: int):
245
+ if old_old_cid := self.cond_sims.get(cid): # если старый cid уже был в дереве:
246
+ self.rcond_sims[old_old_cid].remove(cid) # удаляем из обратного
247
+ self.cond_sims[cid] = old_cid # а в прямом он автоматом переопределится, даже если и был
248
+ self.rcond_sims[old_cid] |= {cid} # ну и в обратное добавим новый
249
+
250
+ def build_tree(self):
251
+ set(self.cond_sims.keys()) | set(self.cond_sims.values())
252
+ tree = defaultdict(dict)
253
+ # Группируем родителей по детям
254
+ for child, par in self.cond_sims.items():
255
+ tree[par] |= {child: {}} # todo: make from self.rcond_sim
256
+
257
+ # Строим дерево снизу вверх
258
+ def subtree(node):
259
+ if not node:
260
+ return node
261
+ for key in node:
262
+ subnode = tree.pop(key, {})
263
+ d = subtree(subnode)
264
+ node[key] |= d # actual tree rebuilding here!
265
+ return node # todo: refact?
266
+
267
+ # Находим корни / без родителей
268
+ roots = set(self.cond_sims.values()) - set(self.cond_sims.keys())
269
+ for root in roots:
270
+ _ = subtree(tree[root])
271
+
272
+ self.tree = tree
273
+
274
+
275
+ def get_sim(s1, s2) -> int:
276
+ sim = int((SequenceMatcher(None, s1, s2).ratio() - 0.6) * 10_000)
277
+ return sim if sim > 0 else 0
278
+
279
+
280
+ def clean(s) -> str:
281
+ clear = r"[^\w\s.,!?;:()\-]"
282
+ repeat = r"(.)\1{2,}"
283
+ s = re.sub(clear, "", s).lower()
284
+ s = re.sub(repeat, r"\1", s)
285
+ return s.replace("\n\n", "\n").replace(" ", " ").strip(" \n/.,!?-")
@@ -10,6 +10,7 @@ from xync_schema.xtype import BaseAd
10
10
 
11
11
  from xync_client.Abc.Ex import BaseExClient
12
12
  from xync_client.Abc.xtype import CredExOut, BaseOrderReq, BaseAdUpdate
13
+ from xync_client.Gmail import GmClient
13
14
 
14
15
 
15
16
  class BaseAgentClient(HttpClient):
@@ -28,6 +29,7 @@ class BaseAgentClient(HttpClient):
28
29
  self.fbot = fbot
29
30
  self.agent: Agent = agent
30
31
  self.actor: Actor = agent.actor
32
+ self.gmail = GmClient(agent.actor.person.user)
31
33
  super().__init__(self.actor.ex.host_p2p, headers, cookies)
32
34
  self.ex_client: BaseExClient = self.actor.ex.client(fbot)
33
35
 
@@ -11,11 +11,12 @@ from xync_schema import models
11
11
  from xync_schema.enums import FileType
12
12
  from xync_schema.xtype import CurEx, CoinEx, BaseAd, BaseAdIn
13
13
 
14
+ from xync_client.Abc.AdLoader import AdLoader
14
15
  from xync_client.Abc.xtype import PmEx, MapOfIdsList
15
16
  from xync_client.pm_unifier import PmUnifier, PmUni
16
17
 
17
18
 
18
- class BaseExClient(HttpClient):
19
+ class BaseExClient(HttpClient, AdLoader):
19
20
  cur_map: dict[int, str] = {}
20
21
  unifier_class: type = PmUnifier
21
22
  logo_pre_url: str
@@ -72,6 +73,7 @@ class BaseExClient(HttpClient):
72
73
  pm_exids: list[str | int] = None,
73
74
  amount: int = None,
74
75
  lim: int = None,
76
+ vm_filter: bool = False,
75
77
  ) -> list[BaseAd]: # {ad.id: ad}
76
78
  ...
77
79
 
@@ -250,14 +252,14 @@ class BaseExClient(HttpClient):
250
252
  return True
251
253
 
252
254
  # Сохранение чужого объявления (с Pm-ами) в бд
253
- async def ad_pydin2db(self, ad_pydin: BaseAdIn) -> models.Ad:
254
- dct = ad_pydin.model_dump()
255
- dct["exid"] = dct.pop("id")
256
- ad_in = models.Ad.validate(dct)
257
- ad_db, _ = await models.Ad.update_or_create(**ad_in.df_unq())
258
- await ad_db.credexs.add(*getattr(ad_pydin, "credexs_", []))
259
- await ad_db.pmexs.add(*getattr(ad_pydin, "pmexs_", []))
260
- return ad_db
255
+ # async def ad_pydin2db(self, ad_pydin: BaseAdIn) -> models.Ad:
256
+ # dct = ad_pydin.model_dump()
257
+ # dct["exid"] = dct.pop("id")
258
+ # ad_in = models.Ad.validate(dct)
259
+ # ad_db, _ = await models.Ad.update_or_create(**ad_in.df_unq())
260
+ # await ad_db.credexs.add(*getattr(ad_pydin, "credexs_", []))
261
+ # await ad_db.pmexs.add(*getattr(ad_pydin, "pmexs_", []))
262
+ # return ad_db
261
263
 
262
264
  async def file_upsert(self, url: str, ss: ClientSession = None) -> models.File:
263
265
  if not (file := await models.File.get_or_none(name__startswith=url.split("?")[0])):
@@ -32,7 +32,7 @@ class PmAgentClient(HasAbotUid, metaclass=ABCMeta):
32
32
  ubot: FileClient | UserClient = None
33
33
  page: Page
34
34
  pages: type(StrEnum) = Pages
35
- last_active: datetime = datetime.now()
35
+ last_active: datetime = now()
36
36
  with_userbot: bool = False
37
37
  _is_started: bool = False
38
38
 
@@ -53,7 +53,9 @@ class BaseOrderReq(BaseModel):
53
53
  asset_amount: float | None = None
54
54
  fiat_amount: float | None = None
55
55
 
56
- # todo: mv from to base to special ex class
56
+ pm_id: int = (None,)
57
+
58
+ # todo: mv from base to special ex class
57
59
  amount_is_fiat: bool = True
58
60
  is_sell: bool = None
59
61
  cur_exid: int | str = None
@@ -77,7 +77,7 @@ class InAgentClient(BaseInAgentClient):
77
77
  while resp := await websocket.recv():
78
78
  if data := json.loads(resp):
79
79
  upd, order_db = None, None
80
- logging.info(f" {datetime.now().strftime('%H:%M:%S')} upd: {data.get('topic')}:{data.get('type')}")
80
+ logging.info(f" {now().strftime('%H:%M:%S')} upd: {data.get('topic')}:{data.get('type')}")
81
81
  match data.get("topic"):
82
82
  case "OTC_ORDER_STATUS":
83
83
  match data["type"]:
@@ -108,10 +108,11 @@ class InAgentClient(BaseInAgentClient):
108
108
  if not (pmacdx := await self.get_pma_by_cdex(order)):
109
109
  continue
110
110
  pma, cdx = pmacdx
111
- am, tid = pma.check_in(
111
+ am, tid = await pma.check_in(
112
112
  Decimal(order.amount),
113
113
  cdx.cred.pmcur.cur.ticker,
114
- datetime.fromtimestamp(float(order.createDate) / 1000),
114
+ # todo: почему в московском час.поясе?
115
+ datetime.fromtimestamp(float(order.transferDate) / 1000),
115
116
  )
116
117
  if not tid:
117
118
  logging.info(
@@ -142,9 +143,13 @@ class InAgentClient(BaseInAgentClient):
142
143
  if (
143
144
  o["amount"] == order.amount
144
145
  and o["id"] != upd.id
145
- and int(order.createDate) < int(o["createDate"]) + 900 * 000
146
- # todo: get full_order from o, and cred or pm from full_order:
147
- # and o['paymentTermList'][0].accountNo == order.paymentTermList[0].accountNo
146
+ and int(order.createDate)
147
+ < int(o["createDate"]) + 15 * 60 * 1000
148
+ # get full_order from o, and cred or pm from full_order:
149
+ and self.agent_client.api.get_order_details(orderId=o["id"])[
150
+ "result"
151
+ ]["paymentTermList"][0]["accountNo"]
152
+ == order.paymentTermList[0].accountNo
148
153
  )
149
154
  ]
150
155
  curex = await models.CurEx.get(
@@ -155,7 +160,7 @@ class InAgentClient(BaseInAgentClient):
155
160
  cred_id=order_db.cred_id,
156
161
  amount=int(float(order.amount) * 10**curex.scale),
157
162
  status__not_in=[OrderStatus.completed, OrderStatus.canceled],
158
- created_at__gt=now() - timedelta(hours=1),
163
+ created_at__gt=now() - timedelta(minutes=15),
159
164
  )
160
165
  if pos or pos_db:
161
166
  await self.agent_client.ex_client.bot.send(
@@ -169,10 +174,10 @@ class InAgentClient(BaseInAgentClient):
169
174
  # !!! ОТПРАВЛЯЕМ ДЕНЬГИ !!!
170
175
  self.agent_client.api.release_assets(orderId=upd.id)
171
176
  logging.info(
172
- f"Order {order.id} created, paid before #{tid}:{am} at {order.createDate}, and RELEASED at {datetime.now()}"
177
+ f"Order {order.id} created, paid before #{tid}:{am} at {order.createDate}, and RELEASED at {now()}"
173
178
  )
174
179
  elif upd.side == 1: # я покупатель - ждем мою оплату
175
- continue # logging.warning(f"Order {order.id} PAID at {datetime.now()}: {int_am}")
180
+ continue # logging.warning(f"Order {order.id} PAID at {now()}: {int_am}")
176
181
  else:
177
182
  ...
178
183
  # todo: check is always canceling
@@ -416,7 +421,7 @@ async def main():
416
421
  actor__person__user__status=UserStatus.ACTIVE,
417
422
  actor__person__user__pm_agents__isnull=False,
418
423
  )
419
- .prefetch_related("actor__ex", "actor__person__user")
424
+ .prefetch_related("actor__ex", "actor__person__user__gmail")
420
425
  .first()
421
426
  )
422
427
  pm_agents = await models.PmAgent.filter(
@@ -434,7 +439,7 @@ async def main():
434
439
  # payeer_cl = Client(actor.person.user.username_id)
435
440
  for pma in pm_agents:
436
441
  pcl: PmAgentClient = pma.client(bbot)
437
- cl.pmacs[pma.pm_id] = await pcl.start(await async_playwright().start(), True)
442
+ cl.pmacs[pma.pm_id] = await pcl.start(await async_playwright().start(), False)
438
443
  try:
439
444
  _ = await cl.start_listen()
440
445
  except Exception as e: