xync-client 0.0.162__py3-none-any.whl → 0.0.172__py3-none-any.whl

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/Abc/Agent.py CHANGED
@@ -6,6 +6,7 @@ from typing import Literal
6
6
 
7
7
  from pydantic import BaseModel
8
8
  from pyro_client.client.file import FileClient
9
+ from tortoise.exceptions import IntegrityError
9
10
  from x_client import df_hdrs
10
11
  from x_client.aiohttp import Client as HttpClient
11
12
  from xync_bot import XyncBot
@@ -15,25 +16,36 @@ from xync_client.Abc.InAgent import BaseInAgentClient
15
16
 
16
17
  from xync_client.Bybit.etype.order import TakeAdReq
17
18
  from xync_schema import models
18
- from xync_schema.models import OrderStatus, Coin, Cur, Ad, AdStatus, Actor, Agent
19
- from xync_schema.xtype import BaseAd
19
+ from xync_schema.models import OrderStatus, Coin, Cur, Ad, Actor, Agent
20
+ from xync_schema import xtype
20
21
 
21
22
  from xync_client.Abc.Ex import BaseExClient
22
- from xync_client.Abc.xtype import CredExOut, BaseOrderReq, BaseAdUpdate, AdUpd, GetAds
23
+ from xync_client.Abc.xtype import (
24
+ BaseCredEx,
25
+ BaseOrderReq,
26
+ BaseAd,
27
+ AdUpdReq,
28
+ GetAdsReq,
29
+ BaseCredexsExidsTrait,
30
+ BaseOrderFull,
31
+ )
23
32
  from xync_client.Gmail import GmClient
24
33
 
25
34
 
26
- class BaseAgentClient(HttpClient, BaseInAgentClient):
35
+ class BaseAgentClient(HttpClient, BaseInAgentClient): # , metaclass=ABCMeta
27
36
  actor: Actor
28
37
  agent: Agent
29
38
  bbot: XyncBot
30
39
  fbot: FileClient
31
40
  ex_client: BaseExClient
32
- orders: dict[int, tuple[models.Order, BaseModel]] = {} # pending
41
+ orders: dict[int, tuple[models.Order, xtype.BaseOrder]] = {} # pending
33
42
  pm_clients: dict[int, PmAgentClient] # {pm_id: PmAgentClient}
34
43
  api: HttpClient
35
44
  cred_x2e: dict[int, int] = {}
36
- cred_e2x: dict[int, int] = {}
45
+ cred_e2x: dict[int, models.CredEx] = {}
46
+ order_x2e: dict[int, int] = {}
47
+ order_e2x: dict[int, int] = {}
48
+ cdx_cls: type[BaseCredEx]
37
49
 
38
50
  def __init__(
39
51
  self,
@@ -59,15 +71,30 @@ class BaseAgentClient(HttpClient, BaseInAgentClient):
59
71
 
60
72
  async def x2e_cred(self, cred_id: int) -> int: # cred.exid
61
73
  if not self.cred_x2e.get(cred_id):
62
- self.cred_x2e[cred_id] = (await models.CredEx.get(cred_id=cred_id)).exid
63
- self.cred_e2x[self.cred_x2e[cred_id]] = cred_id
74
+ credex = await models.CredEx.get(cred_id=cred_id)
75
+ self.cred_x2e[cred_id] = credex.exid
76
+ self.cred_e2x[credex.exid] = credex
64
77
  return self.cred_x2e[cred_id]
65
78
 
66
- async def e2x_cred(self, exid: int) -> int: # cred.id
67
- if not self.cred_e2x.get(exid):
68
- self.cred_e2x[exid] = (await models.CredEx.get(exid=exid, ex=self.ex_client.ex)).cred_id
69
- self.cred_x2e[self.cred_e2x[exid]] = exid
70
- return self.cred_e2x[exid]
79
+ async def e2x_cred(self, base_credex: BaseCredEx) -> models.CredEx: # cred.id
80
+ if not self.cred_e2x.get(base_credex.exid):
81
+ if not (credex := await models.CredEx.get_or_none(exid=base_credex.exid, ex=self.ex_client.ex)):
82
+ credex = await self.credex_save(base_credex)
83
+ self.cred_e2x[base_credex.exid] = credex
84
+ self.cred_x2e[credex.cred_id] = base_credex.exid
85
+ return self.cred_e2x[base_credex.exid]
86
+
87
+ async def x2e_order(self, order_id: int) -> int: # order.exid
88
+ if not self.order_x2e.get(order_id):
89
+ self.order_x2e[order_id] = (await models.Order[order_id]).exid
90
+ self.order_e2x[self.order_x2e[order_id]] = order_id
91
+ return self.order_x2e[order_id]
92
+
93
+ async def e2x_order(self, exid: int) -> int: # order.id
94
+ if not self.order_e2x.get(exid):
95
+ self.order_e2x[exid] = (await models.Order.get(exid=exid, taker__ex=self.ex_client.ex)).id
96
+ self.order_x2e[self.order_e2x[exid]] = exid
97
+ return self.order_e2x[exid]
71
98
 
72
99
  async def start(self):
73
100
  if self.agent.status & 1: # race
@@ -78,6 +105,115 @@ class BaseAgentClient(HttpClient, BaseInAgentClient):
78
105
  if self.agent.status & 2: # listen
79
106
  await self.start_listen()
80
107
 
108
+ @abstractmethod
109
+ async def _get_creds(self) -> list[BaseModel]: ...
110
+
111
+ async def get_creds(self) -> list[BaseCredEx]:
112
+ creds: list[BaseModel] = await self._get_creds()
113
+ return [self.cdx_cls.model_validate(cred, from_attributes=True) for cred in creds]
114
+
115
+ async def credex_save(self, cdx: BaseCredEx, pers_id: int = None, cur_id: int = None) -> models.CredEx | None:
116
+ pmex = None
117
+ if cred_old := await models.Cred.get_or_none(
118
+ credexs__exid=cdx.exid, credexs__ex=self.actor.ex
119
+ ).prefetch_related("pmcur"): # is old Cred
120
+ cur_id = cur_id or cred_old.pmcur.cur_id
121
+ elif not cur_id: # is new Cred
122
+ if cdx.curex_exid:
123
+ cur_id = (await models.CurEx.get(exid=cdx.curex_exid, ex=self.actor.ex)).cur_id
124
+ else:
125
+ pmex = await models.PmEx.get_or_none(exid=cdx.pmex_exid, ex=self.ex_client.ex).prefetch_related(
126
+ "pm__curs"
127
+ )
128
+ cur_id = (
129
+ pmex.pm.df_cur_id
130
+ or (await cdx.guess_cur(pmex.pm.curs) if len(pmex.pm.curs) != 1 else pmex.pm.curs[0].cur_id)
131
+ or (pmex.pm.country_id and (await pmex.pm.country).cur_id)
132
+ # or (ecdx.currencyBalance and await models.Cur.get_or_none(ticker=ecdx.currencyBalance[0])) # это че еще за хуйня?
133
+ )
134
+ if not cur_id:
135
+ raise ValueError(f"Set default cur for {pmex.name}")
136
+ pm_id = pmex and pmex.pm_id or await self.ex_client.e2x_pm(cdx.pmex_exid)
137
+ if not (pmcur := await models.PmCur.get_or_none(cur_id=cur_id, pm_id=pm_id)):
138
+ raise ValueError(f"No PmCur with cur#{cur_id} and pm#{cdx.pmex_exid}", 404)
139
+ try:
140
+ pers_id = pers_id or cdx.seller.exid and (await self.ex_client.e2x_actor(cdx.seller)).person_id
141
+ cred_db, _ = await models.Cred.update_or_create(
142
+ {"name": cdx.name, "extra": cdx.extra},
143
+ pmcur=pmcur,
144
+ person_id=pers_id,
145
+ detail=cdx.detail,
146
+ )
147
+ if not cred_db.ovr_pm_id and ("XyncPay" in cred_db.detail or "XyncPay" in cred_db.extra):
148
+ cred_db.ovr_pm_id = 0
149
+ await cred_db.save()
150
+ credex_db, _ = await models.CredEx.update_or_create(exid=cdx.exid, cred=cred_db, ex=self.actor.ex)
151
+ except IntegrityError as e:
152
+ raise e
153
+ return credex_db
154
+
155
+ # 25: Список реквизитов моих платежных методов
156
+ async def load_creds(self) -> list[models.CredEx]:
157
+ credexs_epyd: list[BaseCredEx] = await self.get_creds()
158
+ credexs: list[models.CredEx] = [await self.credex_save(f) for f in credexs_epyd]
159
+ return credexs
160
+
161
+ async def my_ad_save(
162
+ self,
163
+ bmad: BaseAd | BaseCredexsExidsTrait,
164
+ rname: str = None,
165
+ ) -> models.MyAd:
166
+ ad_db = await self.ex_client.ad_save(bmad)
167
+ mad_db, _ = await models.MyAd.update_or_create(ad=ad_db)
168
+ credexs = await models.CredEx.filter(ex_id=self.actor.ex_id, exid__in=bmad.credex_exids)
169
+ await mad_db.credexs.clear()
170
+ await mad_db.credexs.add(*credexs)
171
+ return mad_db
172
+
173
+ async def load_my_ads(self, only_active: bool = None) -> list[models.MyAd]: # upserted)
174
+ ads = await self.get_my_ads(True)
175
+ if not only_active:
176
+ ads += await self.get_my_ads(False)
177
+ return [await self.my_ad_save(ad) for ad in ads]
178
+
179
+ @abstractmethod
180
+ async def _get_order_full(self, order_exid: int) -> BaseOrderFull: ...
181
+
182
+ async def get_order_full(self, order_exid: int) -> xtype.BaseOrder:
183
+ eorder: BaseOrderFull = await self._get_order_full(order_exid)
184
+ _, cur_scale, __ = await self.ex_client.x2e_cur(await self.ex_client.e2x_cur(eorder.curex_exid))
185
+ _, coin_scale = await self.ex_client.x2e_coin(await self.ex_client.e2x_coin(eorder.coinex_exid))
186
+ ad = await self.ex_client.e2x_ad(eorder.ad_id)
187
+ credex = await self.e2x_cred(eorder.credex)
188
+ taker = await self.ex_client.e2x_actor(eorder.taker)
189
+ border = eorder.model_dump()
190
+ border.update(
191
+ ad_id=ad.id,
192
+ cred_id=credex.cred_id,
193
+ taker_id=taker.id,
194
+ amount=int(eorder.amount * 10**cur_scale),
195
+ quantity=int(eorder.quantity * 10**coin_scale),
196
+ )
197
+ return xtype.BaseOrder.model_validate(border)
198
+
199
+ async def load_order(self, order_exid: int, force_refresh: bool = False) -> tuple[models.Order, xtype.BaseOrder]:
200
+ if not self.orders.get(order_exid) or force_refresh:
201
+ order: xtype.BaseOrder = await self.get_order_full(order_exid)
202
+ if not (
203
+ order_db := await models.Order.get_or_none(
204
+ exid=order_exid, ad__maker__ex=self.actor.ex
205
+ ).prefetch_related("ad__pair_side__pair", "cred__pmcur__cur")
206
+ ):
207
+ order_db = await self.order_save(order)
208
+ self.orders[order_exid] = order_db, order
209
+ return self.orders[order_exid]
210
+
211
+ async def order_save(self, order: xtype.BaseOrder) -> models.Order:
212
+ order_in = models.Order.validate(order.model_dump())
213
+ odb, _ = await models.Order.update_or_create(**order_in.df_unq())
214
+ # await odb.fetch_related("ad") # todo: for what?
215
+ return odb
216
+
81
217
  async def racing(self, race: models.Race):
82
218
  pair = race.road.ad.pair_side.pair
83
219
  taker_side: int = not race.road.ad.pair_side.is_sell
@@ -116,7 +252,7 @@ class BaseAgentClient(HttpClient, BaseInAgentClient):
116
252
  asset = await models.Asset.get(addr__actor=self.actor, addr__coin_id=coinex.coin_id)
117
253
  volume = asset.free * 10**-coinex.scale
118
254
  volume = str(round(volume, coinex.scale))
119
- get_ads_req = GetAds(
255
+ get_ads_req = GetAdsReq(
120
256
  coin_id=pair.coin_id, cur_id=pair.cur_id, is_sell=bool(taker_side), pm_ids=pm_ids, amount=amt, limit=50
121
257
  )
122
258
  try:
@@ -363,10 +499,6 @@ class BaseAgentClient(HttpClient, BaseInAgentClient):
363
499
  self, exid: int | str, cur: str, detail: str, name: str, fid: int, typ: str, extra=None
364
500
  ) -> fiat_pyd: ...
365
501
 
366
- # 25: Список реквизитов моих платежных методов
367
- @abstractmethod
368
- async def creds(self) -> list[CredExOut]: ... # {credex.exid: {cred}}
369
-
370
502
  # Создание реквизита на бирже
371
503
  async def cred_new(self, cred: models.Cred) -> models.CredEx: ...
372
504
 
@@ -387,16 +519,16 @@ class BaseAgentClient(HttpClient, BaseInAgentClient):
387
519
  # # # Ad
388
520
  # 29: Список моих объявлений
389
521
  @abstractmethod
390
- async def my_ads(self, status: AdStatus = None) -> list[BaseAd]: ...
522
+ async def get_my_ads(self, status: bool = None) -> list[BaseAd | BaseCredexsExidsTrait]: ...
391
523
 
392
524
  @abstractmethod
393
- async def x2e_req_ad_upd(self, xreq: AdUpd) -> BaseAdUpdate: ...
525
+ async def x2e_req_ad_upd(self, xreq: AdUpdReq) -> BaseAd: ...
394
526
 
395
527
  # 30: Создание объявления
396
528
  @abstractmethod
397
529
  async def ad_new(self, ad: BaseAd) -> Ad: ...
398
530
 
399
- async def ad_upd(self, xreq: AdUpd) -> Ad:
531
+ async def ad_upd(self, xreq: AdUpdReq) -> Ad:
400
532
  xreq.credexs = await models.CredEx.filter(
401
533
  ex_id=self.actor.ex_id,
402
534
  cred__pmcur__pm_id__in=xreq.pm_ids,
@@ -409,7 +541,7 @@ class BaseAgentClient(HttpClient, BaseInAgentClient):
409
541
 
410
542
  # 31: Редактирование объявления
411
543
  @abstractmethod
412
- async def _ad_upd(self, ad: BaseAdUpdate) -> Ad: ...
544
+ async def _ad_upd(self, ad: BaseAd) -> Ad: ...
413
545
 
414
546
  # 32: Удаление
415
547
  @abstractmethod
xync_client/Abc/Ex.py CHANGED
@@ -6,7 +6,6 @@ from collections import defaultdict
6
6
  from difflib import SequenceMatcher
7
7
 
8
8
  from aiohttp import ClientSession, ClientResponse
9
- from google.protobuf.internal.wire_format import INT32_MAX
10
9
  from msgspec import Struct
11
10
  from pydantic import BaseModel
12
11
  from pyro_client.client.file import FileClient
@@ -15,10 +14,10 @@ from x_client.aiohttp import Client as HttpClient
15
14
  from xync_client.Bybit.etype.ad import AdsReq
16
15
  from xync_schema import models
17
16
  from xync_schema.enums import FileType
18
- from xync_schema.xtype import CurEx, CoinEx, BaseAd, BaseAdIn
17
+ from xync_schema import xtype
19
18
 
20
19
  from xync_client.Abc.AdLoader import AdLoader
21
- from xync_client.Abc.xtype import PmEx, MapOfIdsList, GetAds
20
+ from xync_client.Abc.xtype import PmEx, MapOfIdsList, GetAdsReq, BaseActor, BaseAd, BaseCounteragent
22
21
  from xync_client.pm_unifier import PmUnifier, PmUni
23
22
 
24
23
 
@@ -34,14 +33,14 @@ class BaseExClient(HttpClient, AdLoader):
34
33
  coin_e2x: dict[str, int] = {}
35
34
  cur_x2e: dict[int, tuple[str, int, int]] = {}
36
35
  cur_e2x: dict[str, int] = {}
37
- pm_x2e: dict[int, str] = {}
38
- pm_e2x: dict[str, int] = {}
36
+ pm_x2e: dict[int, str | int] = {}
37
+ pm_e2x: dict[str | int, int] = {}
39
38
  pairs_e2x: dict[int, dict[int, tuple[int, int]]] = defaultdict(defaultdict)
40
39
  pairs_x2e: dict[int, tuple[int, int, int]] = defaultdict(defaultdict)
41
40
  actor_x2e: dict[int, int] = {}
42
- actor_e2x: dict[int, int] = {}
41
+ actor_e2x: dict[int, models.Actor] = {}
43
42
  ad_x2e: dict[int, int] = {}
44
- ad_e2x: dict[int, int] = {}
43
+ ad_e2x: dict[int, models.Ad] = {}
45
44
 
46
45
  def __init__(
47
46
  self,
@@ -54,14 +53,14 @@ class BaseExClient(HttpClient, AdLoader):
54
53
  ):
55
54
  self.ex = ex
56
55
  self.bot = bot
57
- super().__init__(self.host or getattr(ex, attr), headers, cookies, proxy and proxy.str())
56
+ super().__init__(self.host or getattr(ex, attr), headers, cookies, proxy and str(proxy))
58
57
 
59
58
  @abstractmethod
60
59
  def pm_type_map(self, typ: models.PmEx) -> str: ...
61
60
 
62
61
  # 19: Список поддерживаемых валют тейкера
63
62
  @abstractmethod
64
- async def curs(self) -> dict[str, CurEx]: # {cur.ticker: cur}
63
+ async def curs(self) -> dict[str, xtype.CurEx]: # {cur.ticker: cur}
65
64
  ...
66
65
 
67
66
  # 20: Список платежных методов
@@ -74,9 +73,9 @@ class BaseExClient(HttpClient, AdLoader):
74
73
  async def cur_pms_map(self) -> MapOfIdsList: # {cur.exid: [pm.exid]}
75
74
  ...
76
75
 
77
- # 22: Список торгуемых монет (с ограничениям по валютам, если есть)
76
+ # 22: Список торгуемых монет (с ограничением по валютам, если есть)
78
77
  @abstractmethod
79
- async def coins(self) -> dict[str, CoinEx]: # {coin.ticker: coin}
78
+ async def coins(self) -> dict[str, xtype.CoinEx]: # {coin.ticker: coin}
80
79
  ...
81
80
 
82
81
  # 23: Список пар валюта/монет
@@ -118,7 +117,7 @@ class BaseExClient(HttpClient, AdLoader):
118
117
  self.pm_e2x[self.pm_x2e[pm_id]] = pm_id
119
118
  return self.pm_x2e[pm_id]
120
119
 
121
- async def e2x_pm(self, exid: str) -> int: # pm.id
120
+ async def e2x_pm(self, exid: str | int) -> int: # pm.id
122
121
  if not self.pm_e2x.get(exid):
123
122
  self.pm_e2x[exid] = (await models.PmEx.get(exid=exid, ex=self.ex)).pm_id
124
123
  self.pm_x2e[self.pm_e2x[exid]] = exid
@@ -153,15 +152,17 @@ class BaseExClient(HttpClient, AdLoader):
153
152
 
154
153
  async def x2e_actor(self, actor_id: int) -> int: # actor.exid
155
154
  if not self.actor_x2e.get(actor_id):
156
- self.actor_x2e[actor_id] = (await models.Actor[actor_id]).exid
157
- self.actor_e2x[self.actor_x2e[actor_id]] = actor_id
155
+ actor = await models.Actor[actor_id]
156
+ self.actor_x2e[actor_id] = actor.exid
157
+ self.actor_e2x[self.actor_x2e[actor_id]] = actor
158
158
  return self.actor_x2e[actor_id]
159
159
 
160
- async def e2x_actor(self, exid: int) -> int: # actor.id
161
- if not self.actor_e2x.get(exid):
162
- self.actor_e2x[exid] = (await models.Actor.get(exid=exid, ex=self.ex)).id
163
- self.actor_x2e[self.actor_e2x[exid]] = exid
164
- return self.actor_e2x[exid]
160
+ async def e2x_actor(self, base_actor: BaseActor) -> models.Actor:
161
+ if not self.actor_e2x.get(base_actor.exid):
162
+ actor = await self.actor_save(base_actor)
163
+ self.actor_e2x[base_actor.exid] = actor
164
+ self.actor_x2e[actor.id] = base_actor.exid
165
+ return self.actor_e2x[base_actor.exid]
165
166
 
166
167
  async def x2e_ad(self, ad_id: int) -> int: # ad.exid
167
168
  if not self.ad_x2e.get(ad_id):
@@ -169,15 +170,15 @@ class BaseExClient(HttpClient, AdLoader):
169
170
  self.ad_e2x[self.ad_x2e[ad_id]] = ad_id
170
171
  return self.ad_x2e[ad_id]
171
172
 
172
- async def e2x_ad(self, exid: int) -> int: # ad.id
173
- if not self.ad_e2x.get(exid):
174
- self.ad_e2x[exid] = (await models.Ad.get(exid=exid, maker__ex=self.ex)).id
175
- self.ad_x2e[self.ad_e2x[exid]] = exid
176
- return self.ad_e2x[exid]
173
+ async def e2x_ad(self, ad_exid: int) -> models.Ad: # todo: пока только для мейкерных, не создаются если нет в бд
174
+ if not self.ad_e2x.get(ad_exid):
175
+ ad = await models.Ad.get_or_none(exid=ad_exid, maker__ex=self.ex)
176
+ self.ad_e2x[ad_exid] = ad
177
+ self.ad_x2e[ad.id] = ad_exid
178
+ return self.ad_e2x[ad_exid]
177
179
 
178
180
  # 24: Список объяв по (buy/sell, cur, coin, pm)
179
- async def ads(self, xreq: GetAds, **kwargs) -> list[BaseAd]:
180
- self.ex.etype().ad.AdsReq
181
+ async def ads(self, xreq: GetAdsReq, **kwargs) -> list[xtype.BaseAd]:
181
182
  ereq = AdsReq(
182
183
  coin_id=(await self.x2e_coin(xreq.coin_id))[0],
183
184
  cur_id=(await self.x2e_cur(xreq.cur_id))[0],
@@ -195,11 +196,11 @@ class BaseExClient(HttpClient, AdLoader):
195
196
 
196
197
  # 42: Чужая объява по id
197
198
  @abstractmethod
198
- async def ad(self, ad_id: int) -> BaseAd: ...
199
+ async def ad(self, ad_id: int) -> xtype.BaseAd: ...
199
200
 
200
201
  # Преобразрование объекта объявления из формата биржи в формат xync
201
202
  @abstractmethod
202
- async def ad_epyd2pydin(self, ad: BaseAd) -> BaseAdIn: ... # my_uid: for MyAd
203
+ async def ad_epyd2pydin(self, ad: BaseAd) -> xtype.BaseAd: ... # my_uid: for MyAd
203
204
 
204
205
  # 99: Страны
205
206
  async def countries(self) -> list[Struct]:
@@ -208,7 +209,7 @@ class BaseExClient(HttpClient, AdLoader):
208
209
  # Импорт валют Cur-ов (с CurEx-ами)
209
210
  async def set_curs(self, cookies: dict = None) -> bool:
210
211
  # Curs
211
- cur_pyds: dict[str, CurEx] = await self.curs()
212
+ cur_pyds: dict[str, xtype.CurEx] = await self.curs()
212
213
  old_curs = {c.ticker: c.id for c in await models.Cur.all()}
213
214
  curs: dict[int | str, models.Cur] = {
214
215
  exid: (
@@ -330,7 +331,7 @@ class BaseExClient(HttpClient, AdLoader):
330
331
 
331
332
  # Импорт монет (с CoinEx-ами) с биржи в бд
332
333
  async def set_coins(self):
333
- coinexs: dict[str, CoinEx] = await self.coins()
334
+ coinexs: dict[str, xtype.CoinEx] = await self.coins()
334
335
  coins_db: dict[int, models.Coin] = {
335
336
  c.exid: (
336
337
  await models.Coin.update_or_create({"scale": c.scale or self.coin_scales[c.ticker]}, ticker=c.ticker)
@@ -352,10 +353,10 @@ class BaseExClient(HttpClient, AdLoader):
352
353
 
353
354
  # Импорт пар биржи в бд
354
355
  async def set_pairs(self):
355
- curs: dict[str, CurEx] = {
356
+ curs: dict[str, models.Cur] = {
356
357
  k: (await models.Cur.get_or_create(ticker=c.ticker))[0] for k, c in (await self.curs()).items()
357
358
  }
358
- coins: dict[str, CoinEx] = {
359
+ coins: dict[str, xtype.CoinEx] = {
359
360
  k: (await models.Coin.get_or_create(ticker=c.ticker))[0] for k, c in (await self.coins()).items()
360
361
  }
361
362
  prs: tuple[dict, dict] = await self.pairs()
@@ -394,7 +395,10 @@ class BaseExClient(HttpClient, AdLoader):
394
395
  proxy = await models.Proxy.filter(valid=True, country__short__not="US").order_by("-updated_at").first()
395
396
  cookies = self.session.cookie_jar.filter_cookies(self.session._base_url)
396
397
  self.session = ClientSession(
397
- self.session._base_url, headers=self.session.headers, cookies=cookies or None, proxy=proxy.str()
398
+ self.session._base_url,
399
+ headers=self.session.headers,
400
+ cookies=cookies or None,
401
+ proxy=proxy and str(proxy),
398
402
  )
399
403
  return await self.METHS[resp.method](self, resp.url.path, bp)
400
404
  return await super()._proc(resp, bp)
@@ -429,106 +433,82 @@ class BaseExClient(HttpClient, AdLoader):
429
433
  if ct := set(self.tree.keys()) & a:
430
434
  logging.exception(f"cycle cids: {ct}")
431
435
 
432
- async def person_name_update(self, name: str, exid: int) -> models.Person:
433
- if actor := await models.Actor.get_or_none(exid=exid, ex=self.ex).prefetch_related("person"):
434
- # мб уже есть персона с этим же name, note, tg_id
435
- if person := await models.Person.get_or_none(
436
- name=name, note=actor.person.note, tg_id=actor.person.tg_id, id__not=actor.person_id
437
- ):
438
- actor.person = person
439
- else:
440
- actor.person.name = name
441
- await actor.person.save()
442
- return actor.person
443
- # tmp dirty fix
444
- note = f"{self.ex.id}:{exid}"
445
- if person := await models.Person.get_or_none(note__startswith=note, name__not=name):
446
- person.name = name
447
- await person.save()
448
- return person
449
- try:
450
- return (await models.Person.update_or_create(name=name, note=note))[0]
451
- except OperationalError as e:
452
- raise e
453
- await models.Actor.create(person=person, exid=exid, ex=self.ex)
436
+ async def person_save(self, base_person: BaseActor) -> models.Person:
437
+ if not (person := await models.Person.get_or_none(note=(note := f"{self.ex.id}:{base_person.exid}"))):
438
+ name = base_person.name if isinstance(base_person, BaseCounteragent) else base_person.nick
439
+ person = await models.Person.create(name=name, note=note)
440
+ elif isinstance(base_person, BaseCounteragent) and not person.name:
441
+ # если персона не новая, но имени не было, а щас передано - обновим
442
+ person.name = base_person.name
443
+ await person.save(update_fields=["name"])
454
444
  return person
455
- # person = await models.Person.create(note=f'{actor.ex_id}:{actor.exid}:{name}') # no person for just ads with no orders
456
- # raise ValueError(f"Agent #{exid} not found")
457
445
 
458
- async def ad_load(
459
- self,
460
- pad: BaseAd,
461
- cid: int = None,
462
- ps: models.PairSide = None,
463
- maker: models.Actor = None,
464
- coinex: models.CoinEx = None,
465
- curex: models.CurEx = None,
466
- rname: str = None,
467
- ) -> models.Ad:
468
- # self.e2x_actor()
469
- if not maker:
470
- if not (maker := await models.Actor.get_or_none(exid=pad.userId, ex=self.ex)):
471
- person = await models.Person.create(name=rname, note=f"{self.ex.id}:{pad.userId}:{pad.nickName}")
472
- maker = await models.Actor.create(name=pad.nickName, person=person, exid=pad.userId, ex=self.ex)
473
- if rname:
474
- await self.person_name_update(rname, int(pad.userId))
475
- ps = ps or await models.PairSide.get_or_none(
476
- is_sell=pad.side,
477
- pair__coin__ticker=pad.tokenId,
478
- pair__cur__ticker=pad.currencyId,
479
- ).prefetch_related("pair")
480
- # if not ps or not ps.pair:
481
- # ... # THB/USDC: just for initial filling
482
- ad_upd = models.Ad.validate(pad.model_dump(by_alias=True))
483
- cur_scale = 10 ** (curex or await models.CurEx.get(cur_id=ps.pair.cur_id, ex=self.ex)).scale
484
- coin_scale = 10 ** (coinex or await models.CoinEx.get(coin_id=ps.pair.coin_id, ex=self.ex)).scale
485
- amt = int(float(pad.quantity) * float(pad.price) * cur_scale)
486
- mxf = pad.maxAmount and int(float(pad.maxAmount) * cur_scale)
487
- df_unq = ad_upd.df_unq(
488
- maker_id=maker.id,
489
- pair_side_id=ps.id,
490
- amount=min(amt, INT32_MAX),
491
- quantity=int(float(pad.quantity) * coin_scale),
492
- min_fiat=int(float(pad.minAmount) * cur_scale),
493
- max_fiat=min(mxf, INT32_MAX),
494
- price=int(float(pad.price) * cur_scale),
495
- premium=int(float(pad.premium) * 100),
496
- cond_id=cid,
497
- status=self.ad_status(ad_upd.status),
446
+ async def actor_save(self, base_actor: BaseActor) -> models.Actor:
447
+ if not (actor := await models.Actor.get_or_none(exid=base_actor.exid, ex=self.ex)):
448
+ # если это новый актор - делаем создание персоны со стороны биржи
449
+ person = await self.person_save(base_actor)
450
+ actor = await models.Actor.create(exid=base_actor.exid, name=base_actor.nick, person=person, ex=self.ex)
451
+ return actor
452
+
453
+ async def ad_save(self, base_ad: BaseAd) -> models.Ad:
454
+ if ad_db := await models.Ad.get_or_none(exid=base_ad.exid, maker__ex=self.ex):
455
+ ... # load new data
456
+ return ad_db
457
+ cond = await self.load_cond(base_ad.cond_txt, base_ad.maker_exid)
458
+ _, cur_scale, _cur_min = await self.x2e_cur(await self.e2x_cur(base_ad.curex_exid))
459
+ _, coin_scale = await self.x2e_coin(await self.e2x_coin(base_ad.coinex_exid))
460
+ badd = base_ad.model_dump()
461
+ badd.update(
462
+ amount=int((base_ad.amount or base_ad.quantity * base_ad.price) * 10**cur_scale),
463
+ max_fiat=int(base_ad.max_fiat * 10**cur_scale),
464
+ min_fiat=int(base_ad.min_fiat * 10**cur_scale),
465
+ premium=int(base_ad.premium * 100_00),
466
+ price=int(base_ad.price * 10**cur_scale),
467
+ quantity=int((base_ad.quantity or base_ad.amount / base_ad.price) * 10**coin_scale),
468
+ maker_id=(await self.e2x_actor(base_ad.maker)).id,
469
+ cond_id=cond and cond.id,
470
+ pair_side_id=await self.e2x_pair(base_ad.coinex_exid, base_ad.curex_exid, bool(base_ad.side.value)),
471
+ pms=await models.Pm.filter(pmexs__ex=self.ex, pmexs__exid__in=base_ad.pmex_exids),
498
472
  )
473
+ ad = xtype.BaseAd.model_validate(badd)
474
+ ad_upd = models.Ad.validate(ad.model_dump(), with_pk=False)
499
475
  try:
500
- ad_db, _ = await models.Ad.update_or_create(**df_unq)
476
+ ad_db, _ = await models.Ad.update_or_create(**ad_upd.df_unq())
501
477
  except OperationalError as e:
502
478
  raise e
503
479
  await ad_db.pms.clear()
504
- await ad_db.pms.add(*(await models.Pm.filter(pmexs__ex=self.ex, pmexs__exid__in=pad.payments)))
480
+ await ad_db.pms.add(*ad.pms)
505
481
  return ad_db
506
482
 
507
- async def cond_load( # todo: refact from Bybit Ad format to universal
508
- self,
509
- ad: BaseAd,
510
- ps: models.PairSide = None,
511
- force: bool = False,
512
- rname: str = None,
513
- coinex: models.CoinEx = None,
514
- curex: models.CurEx = None,
515
- pms_from_cond: bool = False,
516
- ) -> tuple[models.Ad, bool]:
483
+ async def load_cond(self, txt: str, maker_exid: int) -> models.Cond | None:
484
+ # если текст пустой
485
+ if not (cleaned := clean(txt)):
486
+ return None
487
+ # если точно такое условие уже есть в бд
488
+ if cid := {oc[0]: ci for ci, oc in self.all_conds.items()}.get(cleaned):
489
+ return await models.Cond[cid]
490
+ # создаем новое условие
491
+ return await self.cond_new(cleaned, {maker_exid})
492
+
493
+ async def old_load_cond(self, ad: BaseAd) -> models.Ad:
517
494
  _sim, cid = None, None
518
- ad_db = await models.Ad.get_or_none(exid=ad.id, maker__ex=self.ex).prefetch_related("cond")
495
+ ad_db = await models.Ad.get_or_none(exid=ad.exid, maker__ex=self.ex).prefetch_related("cond")
519
496
  # если точно такое условие уже есть в бд
520
- if not (cleaned := clean(ad.remark)) or (cid := {oc[0]: ci for ci, oc in self.all_conds.items()}.get(cleaned)):
497
+ if not (cleaned := clean(ad.cond_txt)) or (
498
+ cid := {oc[0]: ci for ci, oc in self.all_conds.items()}.get(cleaned)
499
+ ):
521
500
  # и объява с таким ид уже есть, но у нее другое условие
522
501
  if ad_db and ad_db.cond_id != cid:
502
+ old_cid = ad_db.cond_id
523
503
  # то обновляем ид ее условия
524
504
  ad_db.cond_id = cid
525
- await ad_db.save()
526
- logging.info(f"{ad.nickName} upd cond#{ad_db.cond_id}->{cid}")
527
- # old_cid = ad_db.cond_id # todo: solve race-condition, а пока что очищаем при каждом запуске
528
- # if not len((old_cond := await Cond.get(id=old_cid).prefetch_related('ads')).ads):
529
- # await old_cond.delete()
530
- # logging.warning(f"Cond#{old_cid} deleted!")
531
- return (ad_db or force and await self.ad_load(ad, cid, ps, coinex=coinex, curex=curex, rname=rname)), False
505
+ await ad_db.save(update_fields=["cond_id"])
506
+ logging.info(f"{ad.maker_name} upd cond#{ad_db.cond_id}->{cid}")
507
+ # если после переназначения объяве нового условия, со старым условием не осталось объяв, то удаляем его
508
+ if not len((old_cond := await models.Cond.get(id=old_cid).prefetch_related("ads")).ads):
509
+ await old_cond.delete()
510
+ logging.warning(f"Cond#{old_cid} deleted!")
511
+ return ad_db or await self.ad_save(ad)
532
512
  # если эта объява в таким ид уже есть в бд, но с другим условием (или без), а текущего условия еще нет в бд
533
513
  if ad_db:
534
514
  await ad_db.fetch_related("cond__ads", "maker")
@@ -540,11 +520,11 @@ class BaseExClient(HttpClient, AdLoader):
540
520
  {ra.maker_id for ra in rest_ads} - {ad_db.maker_id}
541
521
  ):
542
522
  # создадим новое условие и присвоим его только текущей объяве
543
- cond = await self.cond_new(cleaned, {int(ad.userId)})
523
+ cond = await self.cond_new(cleaned, {int(ad.maker_exid)})
544
524
  ad_db.cond_id = cond.id
545
- await ad_db.save()
546
- ad_db.cond = cond
547
- return ad_db, True
525
+ await ad_db.save(update_fields=["cond_id"])
526
+ ad_db.cond = cond # todo: а это зачем?
527
+ return ad_db
548
528
  # а если других объяв со старым условием этой обявы нет, либо они все этого же юзера
549
529
  # обновляем условие (в тч во всех ЕГО объявах)
550
530
  ad_db.cond.last_ver = ad_db.cond.raw_txt
@@ -556,12 +536,12 @@ class BaseExClient(HttpClient, AdLoader):
556
536
  await self.cond_upd(ad_db.cond, {ad_db.maker.exid})
557
537
  # и подправим коэфициенты похожести нового текста
558
538
  await self.fix_rel_sims(ad_db.cond_id, cleaned)
559
- return ad_db, False
560
-
561
- cond = await self.cond_new(cleaned, {int(ad.userId)})
562
- ad_db = await self.ad_load(ad, cond.id, ps, coinex=coinex, curex=curex, rname=rname)
539
+ return ad_db
540
+ # нет ни объяв ни таких условий еще в бд, все новое
541
+ cond = await self.cond_new(cleaned, {int(ad.maker_exid)})
542
+ ad_db = await self.ad_save(ad, cond.id)
563
543
  ad_db.cond = cond
564
- return ad_db, True
544
+ return ad_db
565
545
 
566
546
  async def cond_new(self, txt: str, uids: set[int]) -> models.Cond:
567
547
  new_cond, _ = await models.Cond.update_or_create(raw_txt=txt)