xync-client 0.0.141__py3-none-any.whl → 0.0.155__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/xtype.py CHANGED
@@ -3,7 +3,7 @@ from typing import Literal
3
3
  from pydantic import BaseModel, model_validator
4
4
  from x_model.types import BaseUpd
5
5
  from xync_schema.enums import PmType
6
- from xync_schema.models import Country, Pm, Ex
6
+ from xync_schema.models import Country, Pm, Ex, CredEx
7
7
  from xync_schema.xtype import PmExBank
8
8
 
9
9
  from xync_client.pm_unifier import PmUni
@@ -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
+ pmex_exid: str = None # int
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
@@ -106,3 +108,23 @@ class BaseCad(BaseAdUpdate):
106
108
  side: Literal[0, 1] # 0 - покупка, 1 - продажа
107
109
  tokenId: str
108
110
  userId: str
111
+
112
+
113
+ class GetAds(BaseModel):
114
+ coin_id: int | str
115
+ cur_id: int | str
116
+ is_sell: bool
117
+ pm_ids: list[int | str] | None = None
118
+ amount: int | None = None
119
+
120
+
121
+ class AdUpd(BaseAdUpdate, GetAds):
122
+ price: float
123
+ pm_ids: list[int | str]
124
+ amount: float
125
+ premium: float | None = None
126
+ credexs: list[CredEx] | None = None
127
+ quantity: float | None = None
128
+
129
+ class Config:
130
+ arbitrary_types_allowed = True
@@ -1,6 +1,7 @@
1
1
  import json
2
2
  import logging
3
3
  import re
4
+ import traceback
4
5
  from datetime import datetime, timezone, timedelta
5
6
  from uuid import uuid4
6
7
 
@@ -8,12 +9,15 @@ import websockets
8
9
  from asyncio import run, sleep
9
10
  from decimal import Decimal
10
11
 
12
+ from bybit_p2p import P2P
11
13
  from playwright.async_api import async_playwright
12
14
  from pydantic import ValidationError
13
15
  from pyro_client.client.file import FileClient
14
16
  from tortoise.exceptions import IntegrityError
15
17
  from tortoise.timezone import now
16
18
  from tortoise.transactions import in_transaction
19
+ from xync_bot import XyncBot
20
+ from xync_client.Bybit.ex import ExClient
17
21
 
18
22
  from xync_client.Abc.PmAgent import PmAgentClient
19
23
  from xync_schema import models
@@ -28,18 +32,21 @@ from xync_client.Bybit.etype.order import (
28
32
  OrderFull,
29
33
  StatusApi,
30
34
  )
31
- from xync_client.loader import NET_TOKEN
35
+ from xync_client.loader import NET_TOKEN, PAY_TOKEN
32
36
  from xync_client.Abc.InAgent import BaseInAgentClient
33
- from xync_client.Bybit.agent import AgentClient
34
37
 
35
38
 
36
39
  class InAgentClient(BaseInAgentClient):
37
- agent_client: AgentClient
40
+ actor: models.Actor
41
+ agent: models.Agent
42
+ api: P2P
43
+ ex_client: ExClient
44
+ pm_clients: dict[int, PmAgentClient]
38
45
 
39
46
  async def start_listen(self):
40
- t = await self.agent_client.ott()
47
+ t = await self.ott()
41
48
  ts = int(float(t["time_now"]) * 1000)
42
- await self.ws_prv(self.agent_client.actor.agent.auth["deviceId"], t["result"], ts)
49
+ await self.ws_prv(self.agent.auth["deviceId"], t["result"], ts)
43
50
 
44
51
  # 3N: [T] - Уведомление об одобрении запроса на сделку
45
52
  async def request_accepted_notify(self) -> int: ... # id
@@ -75,7 +82,7 @@ class InAgentClient(BaseInAgentClient):
75
82
  while resp := await websocket.recv():
76
83
  if data := json.loads(resp):
77
84
  upd, order_db = None, None
78
- logging.info(f" {datetime.now().strftime('%H:%M:%S')} upd: {data.get('topic')}:{data.get('type')}")
85
+ logging.info(f" {now().strftime('%H:%M:%S')} upd: {data.get('topic')}:{data.get('type')}")
79
86
  match data.get("topic"):
80
87
  case "OTC_ORDER_STATUS":
81
88
  match data["type"]:
@@ -85,31 +92,32 @@ class InAgentClient(BaseInAgentClient):
85
92
  except ValidationError as e:
86
93
  logging.error(e)
87
94
  logging.error(data["data"])
88
- order = self.agent_client.api.get_order_details(orderId=upd.id)
95
+ order = self.api.get_order_details(orderId=upd.id)
89
96
  order = OrderFull.model_validate(order["result"])
90
97
  order_db = await models.Order.get_or_none(
91
98
  exid=order.id, ad__exid=order.itemId
92
- ) or await self.agent_client.create_order(order)
99
+ ) or await self.create_order(order)
93
100
  match upd.status:
94
101
  case StatusApi.created:
95
102
  logging.info(f"Order {order.id} created at {order.createDate}")
96
103
  # сразу уменьшаем доступный остаток монеты/валюты
97
104
  await self.money_upd(order_db)
98
105
  if upd.side: # я покупатель - ждем мою оплату
99
- dest = order.paymentTermList[0].accountNo
100
- if not re.match(r"^([PpРр])\d{7,10}\b", dest):
106
+ _dest = order.paymentTermList[0].accountNo
107
+ if not re.match(r"^([PpРр])\d{7,10}\b", _dest):
101
108
  continue
102
109
  await order_db.fetch_related("ad__pair_side__pair", "cred__pmcur__cur")
103
- await self.send_payment(order_db, dest)
110
+ await self.send_payment(order_db)
104
111
  case StatusApi.wait_for_buyer:
105
112
  if upd.side == 0: # ждем когда покупатель оплатит
106
113
  if not (pmacdx := await self.get_pma_by_cdex(order)):
107
114
  continue
108
115
  pma, cdx = pmacdx
109
- am, tid = pma.check_in(
116
+ am, tid = await pma.check_in(
110
117
  Decimal(order.amount),
111
118
  cdx.cred.pmcur.cur.ticker,
112
- datetime.fromtimestamp(float(order.createDate) / 1000),
119
+ # todo: почему в московском час.поясе?
120
+ datetime.fromtimestamp(float(order.transferDate) / 1000),
113
121
  )
114
122
  if not tid:
115
123
  logging.info(
@@ -133,44 +141,48 @@ class InAgentClient(BaseInAgentClient):
133
141
  continue
134
142
 
135
143
  # если висят незавершенные продажи с такой же суммой
136
- pos = (await self.agent_client.get_orders_active(1))["result"]
144
+ pos = (await self.get_orders_active(1))["result"]
137
145
  pos = [
138
146
  o
139
147
  for o in pos.get("items", [])
140
148
  if (
141
149
  o["amount"] == order.amount
142
150
  and o["id"] != upd.id
143
- and o.paymentTermList[0].accountNo
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"]
144
157
  == order.paymentTermList[0].accountNo
145
- and int(order.createDate) < int(o["createDate"]) + 3600 * 000
146
158
  )
147
159
  ]
148
160
  curex = await models.CurEx.get(
149
- cur__ticker=order.currencyId, ex=self.agent_client.ex_client.ex
161
+ cur__ticker=order.currencyId, ex=self.ex_client.ex
150
162
  )
151
163
  pos_db = await models.Order.filter(
152
164
  exid__not=order.id,
153
165
  cred_id=order_db.cred_id,
154
166
  amount=int(float(order.amount) * 10**curex.scale),
155
167
  status__not_in=[OrderStatus.completed, OrderStatus.canceled],
156
- created_at__gt=now() - timedelta(hours=1),
168
+ created_at__gt=now() - timedelta(minutes=15),
157
169
  )
158
170
  if pos or pos_db:
159
- await self.agent_client.ex_client.bot.send(
171
+ await self.ex_client.bot.send(
160
172
  f"[Duplicate amount!]"
161
173
  f"(https://www.bybit.com/ru-RU/p2p/orderList/{order.id})",
162
- self.agent_client.actor.person.user.username_id,
174
+ self.actor.person.user.username_id,
163
175
  )
164
176
  logging.warning("Duplicate amount!")
165
177
  continue
166
178
 
167
179
  # !!! ОТПРАВЛЯЕМ ДЕНЬГИ !!!
168
- self.agent_client.api.release_assets(orderId=upd.id)
180
+ self.api.release_assets(orderId=upd.id)
169
181
  logging.info(
170
- f"Order {order.id} created, paid before #{tid}:{am} at {order.createDate}, and RELEASED at {datetime.now()}"
182
+ f"Order {order.id} created, paid before #{tid}:{am} at {order.createDate}, and RELEASED at {now()}"
171
183
  )
172
184
  elif upd.side == 1: # я покупатель - ждем мою оплату
173
- continue # logging.warning(f"Order {order.id} PAID at {datetime.now()}: {int_am}")
185
+ continue # logging.warning(f"Order {order.id} PAID at {now()}: {int_am}")
174
186
  else:
175
187
  ...
176
188
  # todo: check is always canceling
@@ -229,14 +241,14 @@ class InAgentClient(BaseInAgentClient):
229
241
  case "RECEIVE":
230
242
  upd = Receive.model_validate(data["data"])
231
243
  if order_db := await models.Order.get_or_none(
232
- exid=upd.orderId, ad__maker__ex=self.agent_client.actor.ex
244
+ exid=upd.orderId, ad__maker__ex=self.actor.ex
233
245
  ).prefetch_related("ad__pair_side__pair", "cred__pmcur__cur"):
234
- im_taker = order_db.taker_id == self.agent_client.actor.id
246
+ im_taker = order_db.taker_id == self.actor.id
235
247
  im_buyer = order_db.ad.pair_side.is_sell == im_taker
236
248
  if order_db.ad.auto_msg != upd.message and upd.roleType == "user":
237
249
  msg, _ = await models.Msg.update_or_create(
238
250
  {
239
- "to_maker": upd.userId == self.agent_client.actor.exid and im_taker,
251
+ "to_maker": upd.userId == self.actor.exid and im_taker,
240
252
  "sent_at": datetime.fromtimestamp(float(upd.createDate) / 1000),
241
253
  },
242
254
  txt=upd.message,
@@ -245,14 +257,17 @@ class InAgentClient(BaseInAgentClient):
245
257
  if not upd.message:
246
258
  ...
247
259
  if im_buyer and (g := re.match(r"^[PpРр]\d{7,10}\b", upd.message)):
248
- await self.send_payment(order_db, g.group())
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)
249
264
  case "READ":
250
265
  upd = Read.model_validate(data["data"])
251
266
  # if upd.status not in (StatusWs.created, StatusWs.canceled, 10, StatusWs.completed):
252
267
  if upd.orderStatus in (
253
268
  StatusApi.wait_for_buyer,
254
269
  ): # todo: тут приходит ордер.статус=10, хотя покупатель еще не нажал оплачено
255
- order = self.agent_client.api.get_order_details(orderId=upd.orderId)["result"]
270
+ order = self.api.get_order_details(orderId=upd.orderId)["result"]
256
271
  order = OrderFull.model_validate(order)
257
272
 
258
273
  case "CLEAR":
@@ -282,52 +297,63 @@ class InAgentClient(BaseInAgentClient):
282
297
  if not upd:
283
298
  logging.warning(data, "NOT PROCESSED UPDATE")
284
299
 
285
- async def money_upd(self, order_db: models.Order):
300
+ async def money_upd(self, odb: models.Order):
286
301
  # обновляем остаток монеты
287
- await order_db.fetch_related("ad__pair_side__pair", "cred", "transfer")
288
- ass = await models.Asset.get(
289
- addr__coin_id=order_db.ad.pair_side.pair.coin_id, addr__actor=self.agent_client.actor
290
- )
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)
291
304
  # обновляем остаток валюты
292
- fiat = await models.Fiat.get(
293
- cred__person_id=self.agent_client.actor.person_id, cred__pmcur_id=order_db.cred.pmcur_id
294
- ).prefetch_related("cred__pmcur__pm")
295
- fee = round(order_db.amount * (fiat.cred.pmcur.pm.fee or 0) * 0.0001)
296
- im_seller = order_db.ad.pair_side.is_sell == (_im_maker := order_db.ad.maker_id == self.agent_client.actor.id)
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)
297
323
  # k = int(im_seller) * 2 - 1 # im_seller: 1, im_buyer: -1
298
- if order_db.status == OrderStatus.created:
324
+ if odb.status == OrderStatus.created:
299
325
  if im_seller:
300
- ass.free -= order_db.quantity
301
- ass.freeze += order_db.quantity
326
+ ass.free -= odb.quantity
327
+ ass.freeze += odb.quantity
302
328
  else: # я покупатель
303
- fiat.amount -= order_db.amount + fee
304
- elif order_db.status == OrderStatus.completed:
329
+ fiat.amount -= odb.amount + fee
330
+ elif odb.status == OrderStatus.completed:
305
331
  if im_seller:
306
- fiat.amount += order_db.amount
332
+ fiat.amount += odb.amount
307
333
  else: # я покупатель
308
- ass.free += order_db.quantity
309
- elif order_db.status == OrderStatus.canceled:
334
+ ass.free += odb.quantity
335
+ elif odb.status == OrderStatus.canceled:
310
336
  if im_seller:
311
- ass.free += order_db.quantity
312
- ass.freeze -= order_db.quantity
337
+ ass.free += odb.quantity
338
+ ass.freeze -= odb.quantity
313
339
  else: # я покупатель
314
- fiat.amount += order_db.amount + fee
340
+ fiat.amount += odb.amount + fee
315
341
  else:
316
- logging.exception(order_db.id, f"STATUS: {order_db.status.name}")
342
+ logging.exception(odb.id, f"STATUS: {odb.status.name}")
317
343
  await ass.save(update_fields=["free", "freeze"])
318
344
  await fiat.save(update_fields=["amount"])
319
- logging.info(f"Order #{order_db.id} {order_db.status.name}. Fiat: {fiat.amount}, Asset: {ass.free}")
345
+ logging.info(f"Order #{odb.id} {odb.status.name}. Fiat: {fiat.amount}, Asset: {ass.free}")
320
346
 
321
- async def send_payment(self, order_db: models.Order, dest):
347
+ async def send_payment(self, order_db: models.Order):
322
348
  if order_db.status != OrderStatus.created:
323
349
  return
324
350
  fmt_am = round(order_db.amount * 10**-2, 2)
325
351
  pma, cur = await self.get_pma_by_pmex(order_db)
326
352
  async with in_transaction():
327
353
  # отмечаем ордер на бирже "оплачен"
328
- pmex = await models.PmEx.get(pm_id=order_db.cred.pmcur.pm_id, ex=self.agent_client.actor.ex)
329
- credex = await models.CredEx.get(cred=order_db.cred, ex=self.agent_client.actor.ex)
330
- self.agent_client.api.mark_as_paid(
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(
331
357
  orderId=str(order_db.exid),
332
358
  paymentType=pmex.exid, # pmex.exid
333
359
  paymentId=str(credex.exid), # credex.exid
@@ -335,43 +361,43 @@ class InAgentClient(BaseInAgentClient):
335
361
  # проверяем не отправляли ли мы уже перевод по этому ордеру
336
362
  if t := await models.Transfer.get_or_none(order=order_db, amount=order_db.amount):
337
363
  await pma.bot.send(
338
- f"Order# {order_db.exid}: Double send {fmt_am}{cur} to {dest} #{t.pmid}!",
339
- self.agent_client.actor.person.user.username_id,
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}!"
340
369
  )
341
- raise Exception(f"Order# {order_db.exid}: Double send {fmt_am}{cur} to {dest} #{t.pmid}!")
342
370
 
343
371
  # ставим в бд статус "оплачен"
344
372
  order_db.status = OrderStatus.paid
345
373
  order_db.payed_at = datetime.now(timezone.utc)
346
374
  await order_db.save()
347
- # отправляем деньги
348
- tid, img, rest_amount = await pma.send(dest=dest, amount=fmt_am, cur=cur)
349
375
  # создаем перевод в бд
350
- t, _ = await models.Transfer.update_or_create({"order": order_db, "amount": order_db.amount}, pmid=tid)
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()
351
381
  await self.send_receipt(str(order_db.exid), tid) # отправляем продавцу чек
352
382
  logging.info(f"Order {order_db.exid} PAID at {datetime.now()}: {fmt_am}!")
353
383
 
354
384
  async def send_receipt(self, oexid: str, tid: int) -> tuple[PmAgentClient | None, models.CredEx] | None:
355
385
  try:
356
- if res := self.agent_client.api.upload_chat_file(upload_file=f"tmp/{tid}.png").get("result"):
386
+ if res := self.api.upload_chat_file(upload_file=f"tmp/{tid}.png").get("result"):
357
387
  await sleep(0.5)
358
- self.agent_client.api.send_chat_message(
359
- orderId=oexid, contentType="pic", message=res["url"], msgUuid=uuid4().hex
360
- )
388
+ self.api.send_chat_message(orderId=oexid, contentType="pic", message=res["url"], msgUuid=uuid4().hex)
361
389
  except Exception as e:
362
390
  logging.error(e)
363
391
  await sleep(0.5)
364
- self.agent_client.api.send_chat_message(
365
- orderId=oexid, contentType="str", message=f"#{tid}", msgUuid=uuid4().hex
366
- )
392
+ self.api.send_chat_message(orderId=oexid, contentType="str", message=f"#{tid}", msgUuid=uuid4().hex)
367
393
 
368
394
  async def get_pma_by_cdex(self, order: OrderFull) -> tuple[PmAgentClient | None, models.CredEx] | None:
369
395
  cdxs = await models.CredEx.filter(
370
- ex=self.agent_client.ex_client.ex,
396
+ ex=self.ex_client.ex,
371
397
  exid__in=[ptl.id for ptl in order.paymentTermList],
372
- cred__person=self.agent_client.actor.person,
398
+ cred__person=self.actor.person,
373
399
  ).prefetch_related("cred__pmcur__cur")
374
- pmas = [pma for cdx in cdxs if (pma := self.pmacs.get(cdx.cred.pmcur.pm_id))]
400
+ pmas = [pma for cdx in cdxs if (pma := self.pm_clients.get(cdx.cred.pmcur.pm_id))]
375
401
  if not len(pmas):
376
402
  # raise ValueError(order.paymentTermList, f"No pm_agents for {order.paymentTermList[0].paymentType}")
377
403
  return None
@@ -381,7 +407,7 @@ class InAgentClient(BaseInAgentClient):
381
407
  return pmas[0], cdxs[0]
382
408
 
383
409
  async def get_pma_by_pmex(self, order_db: models.Order) -> tuple[PmAgentClient, str]:
384
- pma = self.pmacs.get(order_db.cred.pmcur.pm_id)
410
+ pma = self.pm_clients.get(order_db.cred.pmcur.pm_id)
385
411
  if pma:
386
412
  return pma, order_db.cred.pmcur.cur.ticker
387
413
  logging.error(f"No pm_agents for {order_db.cred.pmcur.pm_id}")
@@ -396,34 +422,43 @@ async def main():
396
422
  from x_model import init_db
397
423
  from xync_client.loader import TORM
398
424
 
399
- _ = await init_db(TORM, True)
425
+ cn = await init_db(TORM, True)
400
426
  logging.basicConfig(level=logging.INFO)
401
427
 
402
- actor = (
403
- await models.Actor.filter(
404
- ex_id=4,
405
- agent__auth__isnull=False,
406
- person__user__status=UserStatus.ACTIVE,
407
- person__user__pm_agents__isnull=False,
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,
408
436
  )
409
- .prefetch_related("ex", "agent", "person__user__pm_agents__user", "person__user__pm_agents__pm")
437
+ .prefetch_related("actor__ex", "actor__person__user__gmail")
410
438
  .first()
411
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)
412
447
 
413
448
  async with FileClient(NET_TOKEN) as b:
414
449
  b: FileClient
415
- cl: InAgentClient = actor.in_client(b)
450
+ cl = InAgentClient(agent, b, bbot)
416
451
  # await cl.agent_client.export_my_ads()
417
452
  # payeer_cl = Client(actor.person.user.username_id)
418
- for pma in actor.person.user.pm_agents:
419
- pcl = pma.client()
420
- cl.pmacs[pma.pm_id] = await pcl.start(await async_playwright().start(), False)
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)
421
456
  try:
422
457
  _ = await cl.start_listen()
423
458
  except Exception as e:
424
- await b.send("😱Bybit InAgent CRASHED!!!😱", actor.person.user.username_id)
425
- await b.send(e.__repr__(), actor.person.user.username_id)
426
- await cl.agent_client.close()
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()
427
462
 
428
463
 
429
464
  if __name__ == "__main__":