xync-client 0.0.100__tar.gz → 0.0.155.dev2__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.
- {xync_client-0.0.100/xync_client.egg-info → xync_client-0.0.155.dev2}/PKG-INFO +7 -1
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/pyproject.toml +6 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/tests/TestEx.py +7 -7
- xync_client-0.0.155.dev2/xync_client/Abc/AdLoader.py +299 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Abc/Agent.py +94 -10
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Abc/Ex.py +37 -28
- xync_client-0.0.155.dev2/xync_client/Abc/HasAbotUid.py +10 -0
- xync_client-0.0.155.dev2/xync_client/Abc/InAgent.py +10 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Abc/PmAgent.py +42 -35
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Abc/xtype.py +24 -2
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Binance/ex.py +18 -2
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/BingX/ex.py +3 -3
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/BitGet/ex.py +2 -2
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Bybit/InAgent.py +232 -117
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Bybit/agent.py +586 -575
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Bybit/etype/ad.py +11 -56
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Bybit/etype/cred.py +29 -9
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Bybit/etype/order.py +55 -62
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Bybit/ex.py +17 -4
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Gate/ex.py +15 -5
- xync_client-0.0.155.dev2/xync_client/Gmail/__init__.py +137 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Htx/agent.py +148 -30
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Htx/etype/ad.py +18 -11
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Htx/ex.py +9 -11
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/KuCoin/ex.py +2 -2
- xync_client-0.0.155.dev2/xync_client/Mexc/agent.py +85 -0
- xync_client-0.0.155.dev2/xync_client/Mexc/api.py +636 -0
- xync_client-0.0.155.dev2/xync_client/Mexc/etype/order.py +639 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Mexc/ex.py +12 -10
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Okx/ex.py +2 -2
- xync_client-0.0.155.dev2/xync_client/Pms/Payeer/__init__.py +262 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Pms/Payeer/login.py +32 -16
- xync_client-0.0.155.dev2/xync_client/Pms/Volet/__init__.py +293 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Pms/Volet/api.py +17 -13
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/TgWallet/ex.py +2 -2
- xync_client-0.0.155.dev2/xync_client/details.py +44 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/loader.py +2 -1
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/pm_unifier.py +1 -1
- {xync_client-0.0.100 → xync_client-0.0.155.dev2/xync_client.egg-info}/PKG-INFO +7 -1
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client.egg-info/SOURCES.txt +6 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client.egg-info/requires.txt +6 -0
- xync_client-0.0.100/xync_client/Abc/InAgent.py +0 -21
- xync_client-0.0.100/xync_client/Gmail/__init__.py +0 -116
- xync_client-0.0.100/xync_client/Pms/Payeer/__init__.py +0 -166
- xync_client-0.0.100/xync_client/Pms/Volet/__init__.py +0 -239
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/.env.sample +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/.gitignore +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/.pre-commit-config.yaml +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/README.md +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/__init__.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/makefile +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/setup.cfg +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/tests/TestAgent.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/tests/TestAsset.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/tests/TestOrder.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/tests/_todo_refact/Binance/test_binance.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/tests/_todo_refact/Bybit/test_bybit.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/tests/_todo_refact/Bybit/test_bybit_p2p.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/tests/_todo_refact/Gate/test_gate.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/tests/_todo_refact/Wallet/test_agent.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/tests/_todo_refact/Wallet/test_ex.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/tests/_todo_refact/__init__.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/tests/_todo_refact/_test_ex.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Abc/Asset.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Abc/Auth.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Abc/BaseTest.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Abc/Exception.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Abc/Order.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Binance/__init__.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Binance/binance_async.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Binance/earn_api.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Binance/etype/ad.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Binance/etype/pm.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Binance/exceptions.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Binance/sapi.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Binance/web_c2c.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/BingX/__init__.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/BingX/agent.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/BingX/base.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/BingX/etype/ad.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/BingX/etype/pm.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/BingX/req.mjs +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/BingX/sign.js +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/BitGet/__init__.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/BitGet/agent.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/BitGet/etype/ad.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/BitPapa/ex.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Bybit/order.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Bybit/web_earn.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Bybit/web_p2p.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Bybit/ws.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Gate/etype/ad.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Gate/premarket.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Htx/earn.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Htx/etype/__init__.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Htx/etype/cred.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Htx/etype/pm.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Htx/etype/test.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/KuCoin/etype/ad.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/KuCoin/etype/pm.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/KuCoin/web.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Mexc/etype/ad.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Mexc/etype/pm.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Okx/etype/ad.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Okx/etype/pm.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Pms/.gitignore +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Pms/Alfa/__init__.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Pms/Alfa/state.json +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Pms/MTS/__init__.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Pms/Ozon/__init__.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Pms/Payeer/.gitignore +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Pms/Payeer/api.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Pms/Sber/__init__.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Pms/Sber/utils.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Pms/Tinkoff/__init__.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Pms/Volet/_todo_req/req.mjs +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Pms/Volet/_todo_req/req.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Pms/Volet/pl.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Pms/Xync/__main__.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Pms/Xync/ed.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/Pms/Yandex/__init__.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/TgWallet/agent.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/TgWallet/asset.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/TgWallet/auth.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/TgWallet/inAgent.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/TgWallet/order.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/TgWallet/pyd.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/TgWallet/pyro.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/TgWallet/web.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client/__init__.py +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/xync_client.egg-info/dependency_links.txt +0 -0
- {xync_client-0.0.100 → xync_client-0.0.155.dev2}/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.
|
|
3
|
+
Version: 0.0.155.dev2
|
|
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,12 +8,18 @@ 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
|
|
14
17
|
Requires-Dist: pybit
|
|
15
18
|
Requires-Dist: pyotp
|
|
19
|
+
Requires-Dist: pypng
|
|
16
20
|
Requires-Dist: kurigram
|
|
21
|
+
Requires-Dist: payeer-api
|
|
22
|
+
Requires-Dist: playwright
|
|
17
23
|
Requires-Dist: python-dotenv
|
|
18
24
|
Requires-Dist: python-okx
|
|
19
25
|
Requires-Dist: pyrogram-client
|
|
@@ -10,13 +10,19 @@ 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",
|
|
16
19
|
"pybit",
|
|
17
20
|
"pyotp",
|
|
21
|
+
"pypng",
|
|
18
22
|
# "kurigram @ git+https://github.com/mixartemev/pyrogram.git",
|
|
19
23
|
"kurigram",
|
|
24
|
+
"payeer-api",
|
|
25
|
+
"playwright",
|
|
20
26
|
"python-dotenv",
|
|
21
27
|
"python-okx",
|
|
22
28
|
"pyrogram-client",
|
|
@@ -84,13 +84,13 @@ class TestEx(BaseTest):
|
|
|
84
84
|
# logging.info(f"{client.ex.name}: {ExAction.cur_pms_map.name} - ok")
|
|
85
85
|
|
|
86
86
|
# 22
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
87
|
+
async def test_coins(self, clients: list[BaseExClient]):
|
|
88
|
+
for client in clients:
|
|
89
|
+
coins: dict[str, models.CoinEx] = await client.coins()
|
|
90
|
+
ok = self.is_dict_of_objects(coins, models.CoinEx)
|
|
91
|
+
t, _ = await models.ExStat.update_or_create({"ok": ok}, ex=client.ex, action=ExAction.coins)
|
|
92
|
+
assert t.ok, "No coins"
|
|
93
|
+
logging.info(f"{client.ex.name}: {ExAction.coins.name} - ok")
|
|
94
94
|
|
|
95
95
|
# # 23
|
|
96
96
|
# async def test_pairs(self, clients: list[BaseExClient]):
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import re
|
|
3
|
+
from asyncio import sleep
|
|
4
|
+
from collections import defaultdict
|
|
5
|
+
from difflib import SequenceMatcher
|
|
6
|
+
|
|
7
|
+
from tortoise.exceptions import OperationalError, IntegrityError
|
|
8
|
+
from xync_schema import models
|
|
9
|
+
from xync_schema.xtype import BaseAd
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AdLoader:
|
|
13
|
+
ex: models.Ex
|
|
14
|
+
all_conds: dict[int, tuple[str, set[int]]] = {}
|
|
15
|
+
cond_sims: dict[int, int] = defaultdict(set)
|
|
16
|
+
rcond_sims: dict[int, set[int]] = defaultdict(set) # backward
|
|
17
|
+
tree: dict = {}
|
|
18
|
+
|
|
19
|
+
async def old_conds_load(self):
|
|
20
|
+
# пока не порешали рейс-кондишн, очищаем сиротские условия при каждом запуске
|
|
21
|
+
# [await c.delete() for c in await Cond.filter(ads__isnull=True)]
|
|
22
|
+
self.all_conds = {
|
|
23
|
+
c.id: (c.raw_txt, {a.maker.exid for a in c.ads})
|
|
24
|
+
for c in await models.Cond.all().prefetch_related("ads__maker")
|
|
25
|
+
}
|
|
26
|
+
for curr, old in await models.CondSim.filter().values_list("cond_id", "cond_rel_id"):
|
|
27
|
+
self.cond_sims[curr] = old
|
|
28
|
+
self.rcond_sims[old] |= {curr}
|
|
29
|
+
|
|
30
|
+
self.build_tree()
|
|
31
|
+
a = set()
|
|
32
|
+
|
|
33
|
+
def check_tree(tre):
|
|
34
|
+
for p, c in tre.items():
|
|
35
|
+
a.add(p)
|
|
36
|
+
check_tree(c)
|
|
37
|
+
|
|
38
|
+
for pr, ch in self.tree.items():
|
|
39
|
+
check_tree(ch)
|
|
40
|
+
if ct := set(self.tree.keys()) & a:
|
|
41
|
+
logging.exception(f"cycle cids: {ct}")
|
|
42
|
+
|
|
43
|
+
async def person_name_update(self, name: str, exid: int) -> models.Person:
|
|
44
|
+
if actor := await models.Actor.get_or_none(exid=exid, ex=self.ex).prefetch_related("person"):
|
|
45
|
+
actor.person.name = name
|
|
46
|
+
await actor.person.save()
|
|
47
|
+
return actor.person
|
|
48
|
+
# tmp dirty fix
|
|
49
|
+
note = f"{self.ex.id}:{exid}"
|
|
50
|
+
if person := await models.Person.get_or_none(note__startswith=note):
|
|
51
|
+
person.name = name
|
|
52
|
+
await person.save()
|
|
53
|
+
return person
|
|
54
|
+
try:
|
|
55
|
+
return await models.Person.create(name=name, note=note)
|
|
56
|
+
except OperationalError as e:
|
|
57
|
+
raise e
|
|
58
|
+
await models.Actor.create(person=person, exid=exid, ex=self.ex)
|
|
59
|
+
return person
|
|
60
|
+
# person = await models.Person.create(note=f'{actor.ex_id}:{actor.exid}:{name}') # no person for just ads with no orders
|
|
61
|
+
# raise ValueError(f"Agent #{exid} not found")
|
|
62
|
+
|
|
63
|
+
async def ad_load(
|
|
64
|
+
self,
|
|
65
|
+
pad: BaseAd,
|
|
66
|
+
cid: int = None,
|
|
67
|
+
ps: models.PairSide = None,
|
|
68
|
+
maker: models.Actor = None,
|
|
69
|
+
coinex: models.CoinEx = None,
|
|
70
|
+
curex: models.CurEx = None,
|
|
71
|
+
rname: str = None,
|
|
72
|
+
) -> models.Ad:
|
|
73
|
+
if not maker:
|
|
74
|
+
if not (maker := await models.Actor.get_or_none(exid=pad.userId, ex=self.ex)):
|
|
75
|
+
person = await models.Person.create(name=rname, note=f"{self.ex.id}:{pad.userId}:{pad.nickName}")
|
|
76
|
+
maker = await models.Actor.create(name=pad.nickName, person=person, exid=pad.userId, ex=self.ex)
|
|
77
|
+
if rname:
|
|
78
|
+
await self.person_name_update(rname, int(pad.userId))
|
|
79
|
+
ps = ps or await models.PairSide.get_or_none(
|
|
80
|
+
is_sell=pad.side,
|
|
81
|
+
pair__coin__ticker=pad.tokenId,
|
|
82
|
+
pair__cur__ticker=pad.currencyId,
|
|
83
|
+
).prefetch_related("pair")
|
|
84
|
+
# if not ps or not ps.pair:
|
|
85
|
+
# ... # THB/USDC: just for initial filling
|
|
86
|
+
ad_upd = models.Ad.validate(pad.model_dump(by_alias=True))
|
|
87
|
+
cur_scale = 10 ** (curex or await models.CurEx.get(cur_id=ps.pair.cur_id, ex=self.ex)).scale
|
|
88
|
+
coin_scale = 10 ** (coinex or await models.CoinEx.get(coin_id=ps.pair.coin_id, ex=self.ex)).scale
|
|
89
|
+
amt = int(float(pad.quantity) * float(pad.price) * cur_scale)
|
|
90
|
+
mxf = pad.maxAmount and int(float(pad.maxAmount) * cur_scale)
|
|
91
|
+
df_unq = ad_upd.df_unq(
|
|
92
|
+
maker_id=maker.id,
|
|
93
|
+
pair_side_id=ps.id,
|
|
94
|
+
amount=amt if amt < 4_294_967_295 else 4_294_967_295,
|
|
95
|
+
quantity=int(float(pad.quantity) * coin_scale),
|
|
96
|
+
min_fiat=int(float(pad.minAmount) * cur_scale),
|
|
97
|
+
max_fiat=mxf if mxf < 4_294_967_295 else 4_294_967_295,
|
|
98
|
+
price=int(float(pad.price) * cur_scale),
|
|
99
|
+
premium=int(float(pad.premium) * 100),
|
|
100
|
+
cond_id=cid,
|
|
101
|
+
status=self.ad_status(ad_upd.status),
|
|
102
|
+
)
|
|
103
|
+
try:
|
|
104
|
+
ad_db, _ = await models.Ad.update_or_create(**df_unq)
|
|
105
|
+
except OperationalError as e:
|
|
106
|
+
raise e
|
|
107
|
+
await ad_db.pms.add(*(await models.Pm.filter(pmexs__ex=self.ex, pmexs__exid__in=pad.payments)))
|
|
108
|
+
return ad_db
|
|
109
|
+
|
|
110
|
+
async def cond_load( # todo: refact from Bybit Ad format to universal
|
|
111
|
+
self,
|
|
112
|
+
ad: BaseAd,
|
|
113
|
+
ps: models.PairSide = None,
|
|
114
|
+
force: bool = False,
|
|
115
|
+
rname: str = None,
|
|
116
|
+
coinex: models.CoinEx = None,
|
|
117
|
+
curex: models.CurEx = None,
|
|
118
|
+
pms_from_cond: bool = False,
|
|
119
|
+
) -> tuple[models.Ad, bool]:
|
|
120
|
+
_sim, cid = None, None
|
|
121
|
+
ad_db = await models.Ad.get_or_none(exid=ad.id, maker__ex=self.ex).prefetch_related("cond")
|
|
122
|
+
# если точно такое условие уже есть в бд
|
|
123
|
+
if not (cleaned := clean(ad.remark)) or (cid := {oc[0]: ci for ci, oc in self.all_conds.items()}.get(cleaned)):
|
|
124
|
+
# и объява с таким ид уже есть, но у нее другое условие
|
|
125
|
+
if ad_db and ad_db.cond_id != cid:
|
|
126
|
+
# то обновляем ид ее условия
|
|
127
|
+
ad_db.cond_id = cid
|
|
128
|
+
await ad_db.save()
|
|
129
|
+
logging.info(f"{ad.nickName} upd cond#{ad_db.cond_id}->{cid}")
|
|
130
|
+
# old_cid = ad_db.cond_id # todo: solve race-condition, а пока что очищаем при каждом запуске
|
|
131
|
+
# if not len((old_cond := await Cond.get(id=old_cid).prefetch_related('ads')).ads):
|
|
132
|
+
# await old_cond.delete()
|
|
133
|
+
# logging.warning(f"Cond#{old_cid} deleted!")
|
|
134
|
+
return (ad_db or force and await self.ad_load(ad, cid, ps, coinex=coinex, curex=curex, rname=rname)), False
|
|
135
|
+
# если эта объява в таким ид уже есть в бд, но с другим условием (или без), а текущего условия еще нет в бд
|
|
136
|
+
if ad_db:
|
|
137
|
+
await ad_db.fetch_related("cond__ads", "maker")
|
|
138
|
+
if not ad_db.cond_id or (
|
|
139
|
+
# у измененного условия этой объявы есть другие объявы?
|
|
140
|
+
(rest_ads := set(ad_db.cond.ads) - {ad_db})
|
|
141
|
+
and
|
|
142
|
+
# другие объявы этого условия принадлежат другим юзерам
|
|
143
|
+
{ra.maker_id for ra in rest_ads} - {ad_db.maker_id}
|
|
144
|
+
):
|
|
145
|
+
# создадим новое условие и присвоим его только текущей объяве
|
|
146
|
+
cond = await self.cond_new(cleaned, {int(ad.userId)})
|
|
147
|
+
ad_db.cond_id = cond.id
|
|
148
|
+
await ad_db.save()
|
|
149
|
+
ad_db.cond = cond
|
|
150
|
+
return ad_db, True
|
|
151
|
+
# а если других объяв со старым условием этой обявы нет, либо они все этого же юзера
|
|
152
|
+
# обновляем условие (в тч во всех ЕГО объявах)
|
|
153
|
+
ad_db.cond.last_ver = ad_db.cond.raw_txt
|
|
154
|
+
ad_db.cond.raw_txt = cleaned
|
|
155
|
+
try:
|
|
156
|
+
await ad_db.cond.save()
|
|
157
|
+
except IntegrityError as e:
|
|
158
|
+
raise e
|
|
159
|
+
await self.cond_upd(ad_db.cond, {ad_db.maker.exid})
|
|
160
|
+
# и подправим коэфициенты похожести нового текста
|
|
161
|
+
await self.fix_rel_sims(ad_db.cond_id, cleaned)
|
|
162
|
+
return ad_db, False
|
|
163
|
+
|
|
164
|
+
cond = await self.cond_new(cleaned, {int(ad.userId)})
|
|
165
|
+
ad_db = await self.ad_load(ad, cond.id, ps, coinex=coinex, curex=curex, rname=rname)
|
|
166
|
+
ad_db.cond = cond
|
|
167
|
+
return ad_db, True
|
|
168
|
+
|
|
169
|
+
async def cond_new(self, txt: str, uids: set[int]) -> models.Cond:
|
|
170
|
+
new_cond, _ = await models.Cond.update_or_create(raw_txt=txt)
|
|
171
|
+
# и максимально похожую связь для нового условия (если есть >= 60%)
|
|
172
|
+
await self.cond_upd(new_cond, uids)
|
|
173
|
+
return new_cond
|
|
174
|
+
|
|
175
|
+
async def cond_upd(self, cond: models.Cond, uids: set[int]):
|
|
176
|
+
self.all_conds[cond.id] = cond.raw_txt, uids
|
|
177
|
+
# и максимально похожую связь для нового условия (если есть >= 60%)
|
|
178
|
+
old_cid, sim = await self.cond_get_max_sim(cond.id, cond.raw_txt, uids)
|
|
179
|
+
await self.actual_sim(cond.id, old_cid, sim)
|
|
180
|
+
|
|
181
|
+
def find_in_tree(self, cid: int, old_cid: int) -> bool:
|
|
182
|
+
if p := self.cond_sims.get(old_cid):
|
|
183
|
+
if p == cid:
|
|
184
|
+
return True
|
|
185
|
+
return self.find_in_tree(cid, p)
|
|
186
|
+
return False
|
|
187
|
+
|
|
188
|
+
async def cond_get_max_sim(self, cid: int, txt: str, uids: set[int]) -> tuple[int | None, int | None]:
|
|
189
|
+
# находим все старые тексты похожие на 90% и более
|
|
190
|
+
if len(txt) < 15:
|
|
191
|
+
return None, None
|
|
192
|
+
sims: dict[int, int] = {}
|
|
193
|
+
for old_cid, (old_txt, old_uids) in self.all_conds.items():
|
|
194
|
+
if len(old_txt) < 15 or uids == old_uids:
|
|
195
|
+
continue
|
|
196
|
+
elif not self.can_add_sim(cid, old_cid):
|
|
197
|
+
continue
|
|
198
|
+
if sim := get_sim(txt, old_txt):
|
|
199
|
+
sims[old_cid] = sim
|
|
200
|
+
# если есть, берем самый похожий из них
|
|
201
|
+
if sims:
|
|
202
|
+
old_cid, sim = max(sims.items(), key=lambda x: x[1])
|
|
203
|
+
await sleep(0.3)
|
|
204
|
+
return old_cid, sim
|
|
205
|
+
return None, None
|
|
206
|
+
|
|
207
|
+
def can_add_sim(self, cid: int, old_cid: int) -> bool:
|
|
208
|
+
if cid == old_cid:
|
|
209
|
+
return False
|
|
210
|
+
elif self.cond_sims.get(cid) == old_cid:
|
|
211
|
+
return False
|
|
212
|
+
elif self.find_in_tree(cid, old_cid):
|
|
213
|
+
return False
|
|
214
|
+
elif self.cond_sims.get(old_cid) == cid:
|
|
215
|
+
return False
|
|
216
|
+
elif cid in self.rcond_sims.get(old_cid, {}):
|
|
217
|
+
return False
|
|
218
|
+
elif old_cid in self.rcond_sims.get(cid, {}):
|
|
219
|
+
return False
|
|
220
|
+
return True
|
|
221
|
+
|
|
222
|
+
async def fix_rel_sims(self, cid: int, new_txt: str):
|
|
223
|
+
for rel_sim in await models.CondSim.filter(cond_rel_id=cid).prefetch_related("cond"):
|
|
224
|
+
if sim := get_sim(new_txt, rel_sim.cond.raw_txt):
|
|
225
|
+
rel_sim.similarity = sim
|
|
226
|
+
await rel_sim.save()
|
|
227
|
+
else:
|
|
228
|
+
await rel_sim.delete()
|
|
229
|
+
|
|
230
|
+
async def actual_cond(self):
|
|
231
|
+
for curr, old in await models.CondSim.all().values_list("cond_id", "cond_rel_id"):
|
|
232
|
+
self.cond_sims[curr] = old
|
|
233
|
+
self.rcond_sims[old] |= {curr}
|
|
234
|
+
for cid, (txt, uids) in self.all_conds.items():
|
|
235
|
+
old_cid, sim = await self.cond_get_max_sim(cid, txt, uids)
|
|
236
|
+
await self.actual_sim(cid, old_cid, sim)
|
|
237
|
+
# хз бля чо это ваще
|
|
238
|
+
# for ad_db in await models.Ad.filter(direction__pairex__ex=self.ex).prefetch_related("cond", "maker"):
|
|
239
|
+
# ad = Ad(id=str(ad_db.exid), userId=str(ad_db.maker.exid), remark=ad_db.cond.raw_txt)
|
|
240
|
+
# await self.cond_upsert(ad, force=True)
|
|
241
|
+
|
|
242
|
+
async def actual_sim(self, cid: int, old_cid: int, sim: int):
|
|
243
|
+
if not sim:
|
|
244
|
+
return
|
|
245
|
+
if old_sim := await models.CondSim.get_or_none(cond_id=cid):
|
|
246
|
+
if old_sim.cond_rel_id != old_cid:
|
|
247
|
+
if sim > old_sim.similarity:
|
|
248
|
+
logging.warning(f"R {cid}: {old_sim.similarity}->{sim} ({old_sim.cond_rel_id}->{old_cid})")
|
|
249
|
+
await old_sim.update_from_dict({"similarity": sim, "old_rel_id": old_cid}).save()
|
|
250
|
+
self._cond_sim_upd(cid, old_cid)
|
|
251
|
+
elif sim != old_sim.similarity:
|
|
252
|
+
logging.info(f"{cid}: {old_sim.similarity}->{sim}")
|
|
253
|
+
await old_sim.update_from_dict({"similarity": sim}).save()
|
|
254
|
+
else:
|
|
255
|
+
await models.CondSim.create(cond_id=cid, cond_rel_id=old_cid, similarity=sim)
|
|
256
|
+
self._cond_sim_upd(cid, old_cid)
|
|
257
|
+
|
|
258
|
+
def _cond_sim_upd(self, cid: int, old_cid: int):
|
|
259
|
+
if old_old_cid := self.cond_sims.get(cid): # если старый cid уже был в дереве:
|
|
260
|
+
self.rcond_sims[old_old_cid].remove(cid) # удаляем из обратного
|
|
261
|
+
self.cond_sims[cid] = old_cid # а в прямом он автоматом переопределится, даже если и был
|
|
262
|
+
self.rcond_sims[old_cid] |= {cid} # ну и в обратное добавим новый
|
|
263
|
+
|
|
264
|
+
def build_tree(self):
|
|
265
|
+
set(self.cond_sims.keys()) | set(self.cond_sims.values())
|
|
266
|
+
tree = defaultdict(dict)
|
|
267
|
+
# Группируем родителей по детям
|
|
268
|
+
for child, par in self.cond_sims.items():
|
|
269
|
+
tree[par] |= {child: {}} # todo: make from self.rcond_sim
|
|
270
|
+
|
|
271
|
+
# Строим дерево снизу вверх
|
|
272
|
+
def subtree(node):
|
|
273
|
+
if not node:
|
|
274
|
+
return node
|
|
275
|
+
for key in node:
|
|
276
|
+
subnode = tree.pop(key, {})
|
|
277
|
+
d = subtree(subnode)
|
|
278
|
+
node[key] |= d # actual tree rebuilding here!
|
|
279
|
+
return node # todo: refact?
|
|
280
|
+
|
|
281
|
+
# Находим корни / без родителей
|
|
282
|
+
roots = set(self.cond_sims.values()) - set(self.cond_sims.keys())
|
|
283
|
+
for root in roots:
|
|
284
|
+
_ = subtree(tree[root])
|
|
285
|
+
|
|
286
|
+
self.tree = tree
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def get_sim(s1, s2) -> int:
|
|
290
|
+
sim = int((SequenceMatcher(None, s1, s2).ratio() - 0.6) * 10_000)
|
|
291
|
+
return sim if sim > 0 else 0
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def clean(s) -> str:
|
|
295
|
+
clear = r"[^\w\s.,!?;:()\-]"
|
|
296
|
+
repeat = r"(.)\1{2,}"
|
|
297
|
+
s = re.sub(clear, "", s).lower()
|
|
298
|
+
s = re.sub(repeat, r"\1", s)
|
|
299
|
+
return s.replace("\n\n", "\n").replace(" ", " ").strip(" \n/.,!?-")
|
|
@@ -1,21 +1,80 @@
|
|
|
1
1
|
from abc import abstractmethod
|
|
2
|
+
from asyncio.tasks import gather
|
|
3
|
+
from collections import defaultdict
|
|
2
4
|
|
|
5
|
+
from playwright.async_api import async_playwright
|
|
3
6
|
from pydantic import BaseModel
|
|
4
7
|
from pyro_client.client.file import FileClient
|
|
8
|
+
from x_client import df_hdrs
|
|
5
9
|
from x_client.aiohttp import Client as HttpClient
|
|
10
|
+
from xync_bot import XyncBot
|
|
11
|
+
from xync_client.Abc.PmAgent import PmAgentClient
|
|
12
|
+
from xync_schema.enums import UserStatus
|
|
13
|
+
|
|
14
|
+
from xync_client.Abc.InAgent import BaseInAgentClient
|
|
15
|
+
|
|
16
|
+
from xync_client.Bybit.etype.order import TakeAdReq
|
|
6
17
|
from xync_schema import models
|
|
7
|
-
from xync_schema.models import OrderStatus, Coin, Cur, Ad, AdStatus, Actor
|
|
18
|
+
from xync_schema.models import OrderStatus, Coin, Cur, Ad, AdStatus, Actor, Agent
|
|
8
19
|
from xync_schema.xtype import BaseAd
|
|
9
20
|
|
|
10
21
|
from xync_client.Abc.Ex import BaseExClient
|
|
11
|
-
from xync_client.Abc.xtype import CredExOut, BaseOrderReq, BaseAdUpdate
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
22
|
+
from xync_client.Abc.xtype import CredExOut, BaseOrderReq, BaseAdUpdate, AdUpd
|
|
23
|
+
from xync_client.Gmail import GmClient
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class BaseAgentClient(HttpClient, BaseInAgentClient):
|
|
27
|
+
actor: Actor
|
|
28
|
+
agent: Agent
|
|
29
|
+
bbot: XyncBot
|
|
30
|
+
fbot: FileClient
|
|
31
|
+
ex_client: BaseExClient
|
|
32
|
+
pm_clients: dict[int, PmAgentClient] # {pm_id: PmAgentClient}
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
agent: Agent,
|
|
37
|
+
ex_client: BaseExClient,
|
|
38
|
+
fbot: FileClient,
|
|
39
|
+
bbot: XyncBot,
|
|
40
|
+
headers: dict[str, str] = df_hdrs,
|
|
41
|
+
cookies: dict[str, str] = None,
|
|
42
|
+
):
|
|
43
|
+
self.bbot = bbot
|
|
44
|
+
self.fbot = fbot
|
|
45
|
+
self.agent: Agent = agent
|
|
46
|
+
self.actor: Actor = agent.actor
|
|
47
|
+
self.gmail = agent.actor.person.user.gmail and GmClient(agent.actor.person.user)
|
|
48
|
+
self.ex_client: BaseExClient = ex_client
|
|
49
|
+
self.pm_clients: dict[int, PmAgentClient] = defaultdict()
|
|
50
|
+
super().__init__(self.actor.ex.host_p2p, headers, cookies)
|
|
51
|
+
|
|
52
|
+
async def start(self, debug: bool = False):
|
|
53
|
+
tasks = []
|
|
54
|
+
if not self.is_started:
|
|
55
|
+
if self.agent.status & 1: # race
|
|
56
|
+
tasks.append(self.start_race())
|
|
57
|
+
|
|
58
|
+
if self.agent.status & 2: # in agent
|
|
59
|
+
if not self.pm_clients:
|
|
60
|
+
pm_agents = await models.PmAgent.filter(
|
|
61
|
+
active=True,
|
|
62
|
+
auth__isnull=False,
|
|
63
|
+
user__status=UserStatus.ACTIVE,
|
|
64
|
+
).prefetch_related("pm", "user__gmail")
|
|
65
|
+
# payeer_cl = Client(actor.person.user.username_id)
|
|
66
|
+
pw = await async_playwright().start()
|
|
67
|
+
browser = await pw.chromium.launch(
|
|
68
|
+
channel="chrome-beta" if debug else "chromium-headless-shell", headless=not debug
|
|
69
|
+
)
|
|
70
|
+
self.pm_clients = {pma.pm_id: pma.client(browser, self.bbot) for pma in pm_agents}
|
|
71
|
+
[tasks.append(pmcl.start()) for pmcl in self.pm_clients.values()]
|
|
72
|
+
# tasks.append(self.start_listen())
|
|
73
|
+
|
|
74
|
+
if self.agent.status & 4: # for further
|
|
75
|
+
...
|
|
76
|
+
self.is_started = True
|
|
77
|
+
return await gather(*tasks)
|
|
19
78
|
|
|
20
79
|
# 0: Получшение ордеров в статусе status, по монете coin, в валюте coin, в направлении is_sell: bool
|
|
21
80
|
@abstractmethod
|
|
@@ -78,9 +137,31 @@ class BaseAgentClient(HttpClient):
|
|
|
78
137
|
@abstractmethod
|
|
79
138
|
async def ad_new(self, ad: BaseAd) -> Ad: ...
|
|
80
139
|
|
|
140
|
+
async def ad_upd(self, ad_upd_req: AdUpd) -> Ad:
|
|
141
|
+
pmex_exids = await models.PmEx.filter(ex_id=self.actor.ex_id, pm_id__in=ad_upd_req.pm_ids).values_list(
|
|
142
|
+
"exid", flat=True
|
|
143
|
+
)
|
|
144
|
+
credexs = await models.CredEx.filter(
|
|
145
|
+
ex_id=self.actor.ex_id,
|
|
146
|
+
cred__pmcur__pm_id__in=ad_upd_req.pm_ids,
|
|
147
|
+
cred__pmcur__cur_id=ad_upd_req.cur_id,
|
|
148
|
+
cred__person_id=self.actor.person_id,
|
|
149
|
+
).prefetch_related("cred__pmcur")
|
|
150
|
+
coinex = await models.CoinEx.get(coin_id=ad_upd_req.coin_id, ex=self.ex_client.ex)
|
|
151
|
+
curex = await models.CurEx.get(cur_id=ad_upd_req.cur_id, ex=self.ex_client.ex)
|
|
152
|
+
# override
|
|
153
|
+
ad_upd_req.coin_id = coinex.exid
|
|
154
|
+
ad_upd_req.cur_id = curex.exid
|
|
155
|
+
ad_upd_req.pm_ids = pmex_exids
|
|
156
|
+
ad_upd_req.credexs = credexs
|
|
157
|
+
ad_upd_req.price = round(ad_upd_req.price, curex.scale)
|
|
158
|
+
ad_upd_req.amount = round(ad_upd_req.amount, curex.scale)
|
|
159
|
+
ad_upd_req.quantity = round(ad_upd_req.amount / ad_upd_req.price, coinex.scale)
|
|
160
|
+
return await self._ad_upd(ad_upd_req)
|
|
161
|
+
|
|
81
162
|
# 31: Редактирование объявления
|
|
82
163
|
@abstractmethod
|
|
83
|
-
async def
|
|
164
|
+
async def _ad_upd(self, ad: BaseAdUpdate) -> Ad: ...
|
|
84
165
|
|
|
85
166
|
# 32: Удаление
|
|
86
167
|
@abstractmethod
|
|
@@ -115,6 +196,9 @@ class BaseAgentClient(HttpClient):
|
|
|
115
196
|
@abstractmethod
|
|
116
197
|
async def my_assets(self) -> dict: ...
|
|
117
198
|
|
|
199
|
+
@abstractmethod
|
|
200
|
+
async def take_ad(self, req: TakeAdReq): ...
|
|
201
|
+
|
|
118
202
|
# Сохранение объявления (с Pm/Cred-ами) в бд
|
|
119
203
|
# async def ad_pydin2db(self, ad_pydin: AdSaleIn | AdBuyIn) -> Ad:
|
|
120
204
|
# ad_db = await self.ex_client.ad_pydin2db(ad_pydin)
|
|
@@ -11,11 +11,13 @@ 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.
|
|
14
|
+
from xync_client.Abc.AdLoader import AdLoader
|
|
15
|
+
from xync_client.Abc.xtype import PmEx, MapOfIdsList, GetAds
|
|
15
16
|
from xync_client.pm_unifier import PmUnifier, PmUni
|
|
16
17
|
|
|
17
18
|
|
|
18
|
-
class BaseExClient(HttpClient):
|
|
19
|
+
class BaseExClient(HttpClient, AdLoader):
|
|
20
|
+
host: str = None
|
|
19
21
|
cur_map: dict[int, str] = {}
|
|
20
22
|
unifier_class: type = PmUnifier
|
|
21
23
|
logo_pre_url: str
|
|
@@ -33,7 +35,7 @@ class BaseExClient(HttpClient):
|
|
|
33
35
|
):
|
|
34
36
|
self.ex = ex
|
|
35
37
|
self.bot = bot
|
|
36
|
-
super().__init__(getattr(ex, attr), headers, cookies, proxy and proxy.str())
|
|
38
|
+
super().__init__(self.host or getattr(ex, attr), headers, cookies, proxy and proxy.str())
|
|
37
39
|
|
|
38
40
|
@abstractmethod
|
|
39
41
|
def pm_type_map(self, typ: models.PmEx) -> str: ...
|
|
@@ -62,16 +64,19 @@ class BaseExClient(HttpClient):
|
|
|
62
64
|
@abstractmethod
|
|
63
65
|
async def pairs(self) -> tuple[MapOfIdsList, MapOfIdsList]: ...
|
|
64
66
|
|
|
67
|
+
async def _x2e_ads(self, req: GetAds) -> GetAds: # {ad.id: ad}
|
|
68
|
+
req.coin_id = await models.CoinEx.get(coin_id=req.coin_id, ex=self.ex).values_list("exid", flat=True)
|
|
69
|
+
req.cur_id = await models.CurEx.get(cur_id=req.cur_id, ex=self.ex).values_list("exid", flat=True)
|
|
70
|
+
req.pm_ids = await models.PmEx.filter(ex=self.ex, pm_id__in=req.pm_ids).values_list("exid", flat=True)
|
|
71
|
+
return req
|
|
72
|
+
|
|
65
73
|
# 24: Список объяв по (buy/sell, cur, coin, pm)
|
|
74
|
+
async def ads(self, req: GetAds, lim: int = None, vm_filter: bool = False, **kwargs) -> list[BaseAd]:
|
|
75
|
+
return await self._ads(await self._x2e_ads(req), lim, vm_filter, **kwargs)
|
|
76
|
+
|
|
66
77
|
@abstractmethod
|
|
67
|
-
async def
|
|
68
|
-
self,
|
|
69
|
-
coin_exid: str,
|
|
70
|
-
cur_exid: str,
|
|
71
|
-
is_sell: bool,
|
|
72
|
-
pm_exids: list[str | int] = None,
|
|
73
|
-
amount: int = None,
|
|
74
|
-
lim: int = None,
|
|
78
|
+
async def _ads(
|
|
79
|
+
self, req: GetAds, lim: int = None, vm_filter: bool = False, **kwargs
|
|
75
80
|
) -> list[BaseAd]: # {ad.id: ad}
|
|
76
81
|
...
|
|
77
82
|
|
|
@@ -91,9 +96,15 @@ class BaseExClient(HttpClient):
|
|
|
91
96
|
async def set_curs(self, cookies: dict = None) -> bool:
|
|
92
97
|
# Curs
|
|
93
98
|
cur_pyds: dict[str, CurEx] = await self.curs()
|
|
99
|
+
old_curs = {c.ticker: c.id for c in await models.Cur.all()}
|
|
94
100
|
curs: dict[int | str, models.Cur] = {
|
|
95
|
-
exid: (
|
|
96
|
-
|
|
101
|
+
exid: (
|
|
102
|
+
await models.Cur.update_or_create(
|
|
103
|
+
{"rate": cur_pyd.rate or 0, "id": old_curs.get(cur_pyd.ticker, await models.Cur.all().count() + 1)},
|
|
104
|
+
ticker=cur_pyd.ticker,
|
|
105
|
+
)
|
|
106
|
+
)[0]
|
|
107
|
+
for i, (exid, cur_pyd) in enumerate(cur_pyds.items())
|
|
97
108
|
}
|
|
98
109
|
curexs = [
|
|
99
110
|
models.CurEx(**c.model_dump(exclude_none=True), cur=curs[c.exid], ex=self.ex) for c in cur_pyds.values()
|
|
@@ -209,7 +220,7 @@ class BaseExClient(HttpClient):
|
|
|
209
220
|
coinexs: dict[str, CoinEx] = await self.coins()
|
|
210
221
|
coins_db: dict[int, models.Coin] = {
|
|
211
222
|
c.exid: (
|
|
212
|
-
await models.Coin.update_or_create({"scale": c.scale
|
|
223
|
+
await models.Coin.update_or_create({"scale": c.scale or self.coin_scales[c.ticker]}, ticker=c.ticker)
|
|
213
224
|
)[0]
|
|
214
225
|
for c in coinexs.values()
|
|
215
226
|
}
|
|
@@ -236,24 +247,22 @@ class BaseExClient(HttpClient):
|
|
|
236
247
|
}
|
|
237
248
|
prs: tuple[dict, dict] = await self.pairs()
|
|
238
249
|
for is_sell in (0, 1):
|
|
239
|
-
pss: list[models.PairSide] = []
|
|
240
250
|
for cur, coinz in prs[is_sell].items():
|
|
241
251
|
for coin in coinz:
|
|
242
252
|
pair, _ = await models.Pair.get_or_create(coin=coins[coin], cur=curs[cur])
|
|
243
253
|
# pairex, _ = await models.PairEx.get_or_create(pair=pair, ex=self.ex) # todo: разные ли комишки на покупку и продажу?
|
|
244
|
-
|
|
245
|
-
await models.PairSide.bulk_create(pss, ignore_conflicts=True)
|
|
254
|
+
await models.PairSide.update_or_create(is_sell=is_sell, pair=pair)
|
|
246
255
|
return True
|
|
247
256
|
|
|
248
257
|
# Сохранение чужого объявления (с Pm-ами) в бд
|
|
249
|
-
async def ad_pydin2db(self, ad_pydin: BaseAdIn) -> models.Ad:
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
258
|
+
# async def ad_pydin2db(self, ad_pydin: BaseAdIn) -> models.Ad:
|
|
259
|
+
# dct = ad_pydin.model_dump()
|
|
260
|
+
# dct["exid"] = dct.pop("id")
|
|
261
|
+
# ad_in = models.Ad.validate(dct)
|
|
262
|
+
# ad_db, _ = await models.Ad.update_or_create(**ad_in.df_unq())
|
|
263
|
+
# await ad_db.credexs.add(*getattr(ad_pydin, "credexs_", []))
|
|
264
|
+
# await ad_db.pmexs.add(*getattr(ad_pydin, "pmexs_", []))
|
|
265
|
+
# return ad_db
|
|
257
266
|
|
|
258
267
|
async def file_upsert(self, url: str, ss: ClientSession = None) -> models.File:
|
|
259
268
|
if not (file := await models.File.get_or_none(name__startswith=url.split("?")[0])):
|
|
@@ -267,12 +276,12 @@ class BaseExClient(HttpClient):
|
|
|
267
276
|
# fr = await pbot.get_file(file.ref) # check
|
|
268
277
|
return file
|
|
269
278
|
|
|
270
|
-
async def _proc(self, resp: ClientResponse,
|
|
279
|
+
async def _proc(self, resp: ClientResponse, bp: dict | str = None) -> dict | str:
|
|
271
280
|
if resp.status in (403,):
|
|
272
281
|
proxy = await models.Proxy.filter(valid=True, country__short__not="US").order_by("-updated_at").first()
|
|
273
282
|
cookies = self.session.cookie_jar.filter_cookies(self.session._base_url)
|
|
274
283
|
self.session = ClientSession(
|
|
275
284
|
self.session._base_url, headers=self.session.headers, cookies=cookies or None, proxy=proxy.str()
|
|
276
285
|
)
|
|
277
|
-
return await self.METHS[resp.method](self, resp.url.path, bp
|
|
278
|
-
return await super()._proc(resp,
|
|
286
|
+
return await self.METHS[resp.method](self, resp.url.path, bp)
|
|
287
|
+
return await super()._proc(resp, bp)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from PGram import Bot
|
|
2
|
+
from aiogram.types import Message
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class HasAbotUid:
|
|
6
|
+
abot: Bot
|
|
7
|
+
uid: int
|
|
8
|
+
|
|
9
|
+
async def receive(self, text: str, photo: bytes = None, video: bytes = None) -> Message:
|
|
10
|
+
return await self.abot.send(self.uid, txt=text, photo=photo, video=video)
|