xync-client 0.0.155__py3-none-any.whl → 0.0.162__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.
@@ -1,465 +1,36 @@
1
- import json
2
- import logging
3
- import re
4
- import traceback
5
- from datetime import datetime, timezone, timedelta
6
- from uuid import uuid4
7
-
8
- import websockets
9
- from asyncio import run, sleep
10
- from decimal import Decimal
11
-
1
+ from asyncio import create_task
12
2
  from bybit_p2p import P2P
13
- from playwright.async_api import async_playwright
14
- from pydantic import ValidationError
15
3
  from pyro_client.client.file import FileClient
16
- from tortoise.exceptions import IntegrityError
17
- from tortoise.timezone import now
18
- from tortoise.transactions import in_transaction
19
4
  from xync_bot import XyncBot
5
+ from xync_schema.models import Agent
6
+
7
+ from xync_client.Bybit.agent import AgentClient
20
8
  from xync_client.Bybit.ex import ExClient
21
9
 
22
- from xync_client.Abc.PmAgent import PmAgentClient
23
10
  from xync_schema import models
24
- from xync_schema.enums import UserStatus, OrderStatus
25
11
 
26
12
  from xync_client.Bybit.etype.order import (
27
- StatusChange,
28
- CountDown,
29
- SellerCancelChange,
30
- Read,
31
- Receive,
32
- OrderFull,
33
- StatusApi,
13
+ OrderItem,
34
14
  )
35
- from xync_client.loader import NET_TOKEN, PAY_TOKEN
36
- from xync_client.Abc.InAgent import BaseInAgentClient
37
15
 
38
16
 
39
- class InAgentClient(BaseInAgentClient):
17
+ class InAgentClient(AgentClient):
40
18
  actor: models.Actor
41
19
  agent: models.Agent
42
20
  api: P2P
43
21
  ex_client: ExClient
44
- pm_clients: dict[int, PmAgentClient]
45
-
46
- async def start_listen(self):
47
- t = await self.ott()
48
- ts = int(float(t["time_now"]) * 1000)
49
- await self.ws_prv(self.agent.auth["deviceId"], t["result"], ts)
50
-
51
- # 3N: [T] - Уведомление об одобрении запроса на сделку
52
- async def request_accepted_notify(self) -> int: ... # id
53
-
54
- async def ws_prv(self, did: str, tok: str, ts: int):
55
- u = f"wss://ws2.bybit.com/private?appid=bybit&os=web&deviceid={did}&timestamp={ts}"
56
- async with websockets.connect(u) as websocket:
57
- auth_msg = json.dumps({"req_id": did, "op": "login", "args": [tok]})
58
- await websocket.send(auth_msg)
59
-
60
- sub_msg = json.dumps({"op": "subscribe", "args": ["FIAT_OTC_TOPIC", "FIAT_OTC_ONLINE_TOPIC"]})
61
- await websocket.send(sub_msg)
62
- sub_msg = json.dumps({"op": "input", "args": ["FIAT_OTC_TOPIC", '{"topic":"SUPER_DEAL"}']})
63
- await websocket.send(sub_msg)
64
- sub_msg = json.dumps({"op": "input", "args": ["FIAT_OTC_TOPIC", '{"topic":"OTC_ORDER_STATUS"}']})
65
- await websocket.send(sub_msg)
66
- sub_msg = json.dumps({"op": "input", "args": ["FIAT_OTC_TOPIC", '{"topic":"WEB_THREE_SELL"}']})
67
- await websocket.send(sub_msg)
68
- sub_msg = json.dumps({"op": "input", "args": ["FIAT_OTC_TOPIC", '{"topic":"APPEALED_CHANGE"}']})
69
- await websocket.send(sub_msg)
70
-
71
- sub_msg = json.dumps({"op": "subscribe", "args": ["fiat.cashier.order"]})
72
- await websocket.send(sub_msg)
73
- sub_msg = json.dumps({"op": "subscribe", "args": ["fiat.cashier.order-eftd-complete-privilege-event"]})
74
- await websocket.send(sub_msg)
75
- sub_msg = json.dumps({"op": "subscribe", "args": ["fiat.cashier.order-savings-product-event"]})
76
- await websocket.send(sub_msg)
77
- sub_msg = json.dumps({"op": "subscribe", "args": ["fiat.deal-core.order-savings-complete-event"]})
78
- await websocket.send(sub_msg)
79
-
80
- sub_msg = json.dumps({"op": "subscribe", "args": ["FIAT_OTC_TOPIC", "FIAT_OTC_ONLINE_TOPIC"]})
81
- await websocket.send(sub_msg)
82
- while resp := await websocket.recv():
83
- if data := json.loads(resp):
84
- upd, order_db = None, None
85
- logging.info(f" {now().strftime('%H:%M:%S')} upd: {data.get('topic')}:{data.get('type')}")
86
- match data.get("topic"):
87
- case "OTC_ORDER_STATUS":
88
- match data["type"]:
89
- case "STATUS_CHANGE":
90
- try:
91
- upd = StatusChange.model_validate(data["data"])
92
- except ValidationError as e:
93
- logging.error(e)
94
- logging.error(data["data"])
95
- order = self.api.get_order_details(orderId=upd.id)
96
- order = OrderFull.model_validate(order["result"])
97
- order_db = await models.Order.get_or_none(
98
- exid=order.id, ad__exid=order.itemId
99
- ) or await self.create_order(order)
100
- match upd.status:
101
- case StatusApi.created:
102
- logging.info(f"Order {order.id} created at {order.createDate}")
103
- # сразу уменьшаем доступный остаток монеты/валюты
104
- await self.money_upd(order_db)
105
- if upd.side: # я покупатель - ждем мою оплату
106
- _dest = order.paymentTermList[0].accountNo
107
- if not re.match(r"^([PpРр])\d{7,10}\b", _dest):
108
- continue
109
- await order_db.fetch_related("ad__pair_side__pair", "cred__pmcur__cur")
110
- await self.send_payment(order_db)
111
- case StatusApi.wait_for_buyer:
112
- if upd.side == 0: # ждем когда покупатель оплатит
113
- if not (pmacdx := await self.get_pma_by_cdex(order)):
114
- continue
115
- pma, cdx = pmacdx
116
- am, tid = await pma.check_in(
117
- Decimal(order.amount),
118
- cdx.cred.pmcur.cur.ticker,
119
- # todo: почему в московском час.поясе?
120
- datetime.fromtimestamp(float(order.transferDate) / 1000),
121
- )
122
- if not tid:
123
- logging.info(
124
- f"Order {order.id} created at {order.createDate}, not paid yet"
125
- )
126
- continue
127
- try:
128
- t, is_new = await models.Transfer.update_or_create(
129
- dict(
130
- amount=int(float(order.amount) * 100),
131
- order=order_db,
132
- ),
133
- pmid=tid,
134
- )
135
- except IntegrityError as e:
136
- logging.error(tid)
137
- logging.error(order)
138
- logging.exception(e)
139
-
140
- if not is_new: # если по этому платежу уже отпущен другая продажа
141
- continue
142
-
143
- # если висят незавершенные продажи с такой же суммой
144
- pos = (await self.get_orders_active(1))["result"]
145
- pos = [
146
- o
147
- for o in pos.get("items", [])
148
- if (
149
- o["amount"] == order.amount
150
- and o["id"] != upd.id
151
- and int(order.createDate)
152
- < int(o["createDate"]) + 15 * 60 * 1000
153
- # get full_order from o, and cred or pm from full_order:
154
- and self.api.get_order_details(orderId=o["id"])["result"][
155
- "paymentTermList"
156
- ][0]["accountNo"]
157
- == order.paymentTermList[0].accountNo
158
- )
159
- ]
160
- curex = await models.CurEx.get(
161
- cur__ticker=order.currencyId, ex=self.ex_client.ex
162
- )
163
- pos_db = await models.Order.filter(
164
- exid__not=order.id,
165
- cred_id=order_db.cred_id,
166
- amount=int(float(order.amount) * 10**curex.scale),
167
- status__not_in=[OrderStatus.completed, OrderStatus.canceled],
168
- created_at__gt=now() - timedelta(minutes=15),
169
- )
170
- if pos or pos_db:
171
- await self.ex_client.bot.send(
172
- f"[Duplicate amount!]"
173
- f"(https://www.bybit.com/ru-RU/p2p/orderList/{order.id})",
174
- self.actor.person.user.username_id,
175
- )
176
- logging.warning("Duplicate amount!")
177
- continue
178
-
179
- # !!! ОТПРАВЛЯЕМ ДЕНЬГИ !!!
180
- self.api.release_assets(orderId=upd.id)
181
- logging.info(
182
- f"Order {order.id} created, paid before #{tid}:{am} at {order.createDate}, and RELEASED at {now()}"
183
- )
184
- elif upd.side == 1: # я покупатель - ждем мою оплату
185
- continue # logging.warning(f"Order {order.id} PAID at {now()}: {int_am}")
186
- else:
187
- ...
188
- # todo: check is always canceling
189
- # await order_db.update_from_dict({"status": OrderStatus.canceled}).save()
190
- # logging.info(f"Order {order.id} canceled at {datetime.now()}")
191
-
192
- case StatusApi.wait_for_seller:
193
- if order_db.status == OrderStatus.paid:
194
- continue
195
- await order_db.update_from_dict(
196
- {
197
- "status": OrderStatus.paid,
198
- "payed_at": datetime.fromtimestamp(
199
- float(order.transferDate) / 1000
200
- ),
201
- }
202
- ).save()
203
- logging.info(f"Order {order.id} payed at {order_db.payed_at}")
204
-
205
- case StatusApi.appealed:
206
- # todo: appealed by WHO? щас наугад стоит by_seller
207
- await order_db.update_from_dict(
208
- {
209
- "status": OrderStatus.appealed_by_seller,
210
- "appealed_at": datetime.fromtimestamp(
211
- float(order.updateDate) / 1000
212
- ),
213
- }
214
- ).save()
215
- logging.info(f"Order {order.id} appealed at {order_db.appealed_at}")
216
-
217
- case StatusApi.canceled:
218
- await order_db.update_from_dict({"status": OrderStatus.canceled}).save()
219
- logging.info(f"Order {order.id} canceled at {datetime.now()}")
220
- await self.money_upd(order_db)
221
-
222
- case StatusApi.completed:
223
- await order_db.update_from_dict(
224
- {
225
- "status": OrderStatus.completed,
226
- "confirmed_at": datetime.fromtimestamp(
227
- float(order.updateDate) / 1000
228
- ),
229
- }
230
- ).save()
231
- await self.money_upd(order_db)
232
-
233
- case _:
234
- logging.warning(f"Order {order.id} UNKNOWN STATUS {datetime.now()}")
235
- case "COUNT_DOWN":
236
- upd = CountDown.model_validate(data["data"])
237
- case _:
238
- self.listen(data)
239
- case "OTC_USER_CHAT_MSG":
240
- match data["type"]:
241
- case "RECEIVE":
242
- upd = Receive.model_validate(data["data"])
243
- if order_db := await models.Order.get_or_none(
244
- exid=upd.orderId, ad__maker__ex=self.actor.ex
245
- ).prefetch_related("ad__pair_side__pair", "cred__pmcur__cur"):
246
- im_taker = order_db.taker_id == self.actor.id
247
- im_buyer = order_db.ad.pair_side.is_sell == im_taker
248
- if order_db.ad.auto_msg != upd.message and upd.roleType == "user":
249
- msg, _ = await models.Msg.update_or_create(
250
- {
251
- "to_maker": upd.userId == self.actor.exid and im_taker,
252
- "sent_at": datetime.fromtimestamp(float(upd.createDate) / 1000),
253
- },
254
- txt=upd.message,
255
- order=order_db,
256
- )
257
- if not upd.message:
258
- ...
259
- if im_buyer and (g := re.match(r"^[PpРр]\d{7,10}\b", upd.message)):
260
- if not order_db.cred.detail.startswith(dest := g.group()):
261
- order_db.cred.detail = dest
262
- await order_db.save()
263
- await self.send_payment(order_db)
264
- case "READ":
265
- upd = Read.model_validate(data["data"])
266
- # if upd.status not in (StatusWs.created, StatusWs.canceled, 10, StatusWs.completed):
267
- if upd.orderStatus in (
268
- StatusApi.wait_for_buyer,
269
- ): # todo: тут приходит ордер.статус=10, хотя покупатель еще не нажал оплачено
270
- order = self.api.get_order_details(orderId=upd.orderId)["result"]
271
- order = OrderFull.model_validate(order)
272
-
273
- case "CLEAR":
274
- continue
275
- case _:
276
- self.listen(data)
277
- case "OTC_USER_CHAT_MSG_V2":
278
- # match data["type"]:
279
- # case "RECEIVE":
280
- # upd = Receive.model_validate(data["data"])
281
- # case "READ":
282
- # upd = Read.model_validate(data["data"])
283
- # case "CLEAR":
284
- # pass
285
- # case _:
286
- # self.listen(data)
287
- continue
288
- case "SELLER_CANCEL_CHANGE":
289
- upd = SellerCancelChange.model_validate(data["data"])
290
- case None:
291
- if not data.get("success"):
292
- logging.error(data, "NOT SUCCESS!")
293
- else:
294
- continue # success login, subscribes, input
295
- case _:
296
- logging.warning(data, "UNKNOWN TOPIC")
297
- if not upd:
298
- logging.warning(data, "NOT PROCESSED UPDATE")
299
-
300
- async def money_upd(self, odb: models.Order):
301
- # обновляем остаток монеты
302
- await odb.fetch_related("ad__pair_side__pair", "ad__my_ad__credexs__cred__fiat", "cred__pmcur", "transfer")
303
- ass = await models.Asset.get(addr__coin_id=odb.ad.pair_side.pair.coin_id, addr__actor=self.actor)
304
- # обновляем остаток валюты
305
- im_maker = odb.ad.maker_id == self.actor.id
306
- im_seller = odb.ad.pair_side.is_sell == im_maker
307
- if im_maker:
308
- if _fiats := [cx.cred.fiat for cx in odb.ad.my_ad.credexs if cx.cred.fiat]:
309
- fiat = _fiats[0]
310
- await fiat.fetch_related("cred__pmcur__pm")
311
- else:
312
- raise ValueError(odb, "No Fiat")
313
- elif im_seller: # im taker
314
- fltr = dict(cred__person_id=self.actor.person_id)
315
- fltr |= (
316
- {"cred__ovr_pm_id": odb.cred.ovr_pm_id, "cred__pmcur__cur_id": odb.cred.pmcur.cur_id}
317
- if odb.cred.ovr_pm_id
318
- else {"cred__pmcur_id": odb.cred.pmcur_id}
319
- )
320
- if not (fiat := await models.Fiat.get_or_none(**fltr).prefetch_related("cred__pmcur__pm")):
321
- raise ValueError(odb, "No Fiat")
322
- fee = round(odb.amount * (fiat.cred.pmcur.pm.fee or 0) * 0.0001)
323
- # k = int(im_seller) * 2 - 1 # im_seller: 1, im_buyer: -1
324
- if odb.status == OrderStatus.created:
325
- if im_seller:
326
- ass.free -= odb.quantity
327
- ass.freeze += odb.quantity
328
- else: # я покупатель
329
- fiat.amount -= odb.amount + fee
330
- elif odb.status == OrderStatus.completed:
331
- if im_seller:
332
- fiat.amount += odb.amount
333
- else: # я покупатель
334
- ass.free += odb.quantity
335
- elif odb.status == OrderStatus.canceled:
336
- if im_seller:
337
- ass.free += odb.quantity
338
- ass.freeze -= odb.quantity
339
- else: # я покупатель
340
- fiat.amount += odb.amount + fee
341
- else:
342
- logging.exception(odb.id, f"STATUS: {odb.status.name}")
343
- await ass.save(update_fields=["free", "freeze"])
344
- await fiat.save(update_fields=["amount"])
345
- logging.info(f"Order #{odb.id} {odb.status.name}. Fiat: {fiat.amount}, Asset: {ass.free}")
346
-
347
- async def send_payment(self, order_db: models.Order):
348
- if order_db.status != OrderStatus.created:
349
- return
350
- fmt_am = round(order_db.amount * 10**-2, 2)
351
- pma, cur = await self.get_pma_by_pmex(order_db)
352
- async with in_transaction():
353
- # отмечаем ордер на бирже "оплачен"
354
- pmex = await models.PmEx.get(pm_id=order_db.cred.pmcur.pm_id, ex=self.actor.ex)
355
- credex = await models.CredEx.get(cred=order_db.cred, ex=self.actor.ex)
356
- self.api.mark_as_paid(
357
- orderId=str(order_db.exid),
358
- paymentType=pmex.exid, # pmex.exid
359
- paymentId=str(credex.exid), # credex.exid
360
- )
361
- # проверяем не отправляли ли мы уже перевод по этому ордеру
362
- if t := await models.Transfer.get_or_none(order=order_db, amount=order_db.amount):
363
- await pma.bot.send(
364
- f"Order# {order_db.exid}: Double send {fmt_am}{cur} to {order_db.cred.detail} #{t.pmid}!",
365
- self.actor.person.user.username_id,
366
- )
367
- raise Exception(
368
- f"Order# {order_db.exid}: Double send {fmt_am}{cur} to {order_db.cred.detail} #{t.pmid}!"
369
- )
370
-
371
- # ставим в бд статус "оплачен"
372
- order_db.status = OrderStatus.paid
373
- order_db.payed_at = datetime.now(timezone.utc)
374
- await order_db.save()
375
- # создаем перевод в бд
376
- t = models.Transfer(order=order_db, amount=order_db.amount, updated_at=now())
377
- # отправляем деньги
378
- tid, img = await pma.send(t)
379
- t.pmid = tid
380
- await t.save()
381
- await self.send_receipt(str(order_db.exid), tid) # отправляем продавцу чек
382
- logging.info(f"Order {order_db.exid} PAID at {datetime.now()}: {fmt_am}!")
383
-
384
- async def send_receipt(self, oexid: str, tid: int) -> tuple[PmAgentClient | None, models.CredEx] | None:
385
- try:
386
- if res := self.api.upload_chat_file(upload_file=f"tmp/{tid}.png").get("result"):
387
- await sleep(0.5)
388
- self.api.send_chat_message(orderId=oexid, contentType="pic", message=res["url"], msgUuid=uuid4().hex)
389
- except Exception as e:
390
- logging.error(e)
391
- await sleep(0.5)
392
- self.api.send_chat_message(orderId=oexid, contentType="str", message=f"#{tid}", msgUuid=uuid4().hex)
393
-
394
- async def get_pma_by_cdex(self, order: OrderFull) -> tuple[PmAgentClient | None, models.CredEx] | None:
395
- cdxs = await models.CredEx.filter(
396
- ex=self.ex_client.ex,
397
- exid__in=[ptl.id for ptl in order.paymentTermList],
398
- cred__person=self.actor.person,
399
- ).prefetch_related("cred__pmcur__cur")
400
- pmas = [pma for cdx in cdxs if (pma := self.pm_clients.get(cdx.cred.pmcur.pm_id))]
401
- if not len(pmas):
402
- # raise ValueError(order.paymentTermList, f"No pm_agents for {order.paymentTermList[0].paymentType}")
403
- return None
404
- elif len(pmas) > 1:
405
- logging.error(order.paymentTermList, f">1 pm_agents for {cdxs[0].cred.pmcur.pm_id}")
406
- else:
407
- return pmas[0], cdxs[0]
408
-
409
- async def get_pma_by_pmex(self, order_db: models.Order) -> tuple[PmAgentClient, str]:
410
- pma = self.pm_clients.get(order_db.cred.pmcur.pm_id)
411
- if pma:
412
- return pma, order_db.cred.pmcur.cur.ticker
413
- logging.error(f"No pm_agents for {order_db.cred.pmcur.pm_id}")
414
-
415
- @staticmethod
416
- def listen(data: dict | None):
417
- # print(data)
418
- ...
419
-
420
-
421
- async def main():
422
- from x_model import init_db
423
- from xync_client.loader import TORM
424
-
425
- cn = await init_db(TORM, True)
426
- logging.basicConfig(level=logging.INFO)
427
-
428
- agent = (
429
- await models.Agent.filter(
430
- actor__ex_id=4,
431
- status__in=[3],
432
- auth__isnull=False,
433
- actor__person__user__status=UserStatus.ACTIVE,
434
- actor__person__user__pm_agents__pm_id=366,
435
- actor__person_id=1,
436
- )
437
- .prefetch_related("actor__ex", "actor__person__user__gmail")
438
- .first()
439
- )
440
- pm_agents = await models.PmAgent.filter(
441
- active=True,
442
- auth__isnull=False,
443
- user__status=UserStatus.ACTIVE,
444
- ).prefetch_related("pm", "user__gmail")
445
-
446
- bbot = XyncBot(PAY_TOKEN, cn)
447
22
 
448
- async with FileClient(NET_TOKEN) as b:
449
- b: FileClient
450
- cl = InAgentClient(agent, b, bbot)
451
- # await cl.agent_client.export_my_ads()
452
- # payeer_cl = Client(actor.person.user.username_id)
453
- for pma in pm_agents:
454
- pcl: PmAgentClient = pma.client(bbot)
455
- cl.pm_clients[pma.pm_id] = await pcl.start(await async_playwright().start(), False)
456
- try:
457
- _ = await cl.start_listen()
458
- except Exception as e:
459
- await b.send("😱Bybit InAgent CRASHED!!!😱", agent.actor.person.user.username_id)
460
- await b.send(f"```\n{''.join(traceback.format_exception(e))}\n```", agent.actor.person.user.username_id)
461
- await cl.close()
23
+ orders: dict[int, models.Order] = {}
462
24
 
25
+ def __init__(self, agent: Agent, ex_client: ExClient, fbot: FileClient, bbot: XyncBot, **kwargs):
26
+ super().__init__(agent, ex_client, fbot, bbot, **kwargs)
27
+ create_task(self.load_pending_orders())
463
28
 
464
- if __name__ == "__main__":
465
- run(main())
29
+ async def load_pending_orders(self):
30
+ po: dict[int, OrderItem] = await self.get_pending_orders()
31
+ if isinstance(po, int): # если код ошибки вместо результата
32
+ raise ValueError(po)
33
+ self.orders = {o.exid: o for o in await models.Order.filter(exid__in=po.keys())}
34
+ for oid in po.keys() - self.orders.keys():
35
+ fo = self.api.get_order_details(orderId=oid)
36
+ self.orders[oid] = await self.create_order_db(fo)