xync-client 0.0.143__py3-none-any.whl → 0.0.145__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.

Potentially problematic release.


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

xync_client/Abc/Agent.py CHANGED
@@ -3,8 +3,9 @@ from abc import abstractmethod
3
3
  from pydantic import BaseModel
4
4
  from pyro_client.client.file import FileClient
5
5
  from x_client.aiohttp import Client as HttpClient
6
+ from xync_bot import XyncBot
6
7
  from xync_schema import models
7
- from xync_schema.models import OrderStatus, Coin, Cur, Ad, AdStatus, Actor
8
+ from xync_schema.models import OrderStatus, Coin, Cur, Ad, AdStatus, Actor, Agent
8
9
  from xync_schema.xtype import BaseAd
9
10
 
10
11
  from xync_client.Abc.Ex import BaseExClient
@@ -12,10 +13,23 @@ from xync_client.Abc.xtype import CredExOut, BaseOrderReq, BaseAdUpdate
12
13
 
13
14
 
14
15
  class BaseAgentClient(HttpClient):
15
- def __init__(self, actor: Actor, bot: FileClient, headers: dict[str, str] = None, cookies: dict[str, str] = None):
16
- self.actor: Actor = actor
17
- super().__init__(actor.ex.host_p2p, headers, cookies)
18
- self.ex_client: BaseExClient = self.actor.ex.client(bot)
16
+ bbot: XyncBot
17
+ fbot: FileClient
18
+
19
+ def __init__(
20
+ self,
21
+ agent: Agent,
22
+ fbot: FileClient,
23
+ bbot: XyncBot,
24
+ headers: dict[str, str] = None,
25
+ cookies: dict[str, str] = None,
26
+ ):
27
+ self.bbot = bbot
28
+ self.fbot = fbot
29
+ self.agent: Agent = agent
30
+ self.actor: Actor = agent.actor
31
+ super().__init__(self.actor.ex.host_p2p, headers, cookies)
32
+ self.ex_client: BaseExClient = self.actor.ex.client(fbot)
19
33
 
20
34
  # 0: Получшение ордеров в статусе status, по монете coin, в валюте coin, в направлении is_sell: bool
21
35
  @abstractmethod
@@ -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)
@@ -1,8 +1,10 @@
1
1
  from abc import abstractmethod
2
2
 
3
3
  from pyro_client.client.file import FileClient
4
+ from xync_bot import XyncBot
5
+
4
6
  from xync_client.Abc.PmAgent import PmAgentClient
5
- from xync_schema.models import Actor
7
+ from xync_schema.models import Agent
6
8
 
7
9
  from xync_client.Abc.Agent import BaseAgentClient
8
10
 
@@ -10,8 +12,8 @@ from xync_client.Abc.Agent import BaseAgentClient
10
12
  class BaseInAgentClient:
11
13
  pmacs: dict[int, PmAgentClient] = {}
12
14
 
13
- def __init__(self, actor: Actor, bot: FileClient):
14
- self.agent_client: BaseAgentClient = actor.client(bot)
15
+ def __init__(self, agent: Agent, fbot: FileClient, bbot: XyncBot):
16
+ self.agent_client: BaseAgentClient = agent.client(fbot, bbot)
15
17
 
16
18
  @abstractmethod
17
19
  async def start_listen(self) -> bool: ...
@@ -4,52 +4,57 @@ from datetime import datetime, timedelta
4
4
  from decimal import Decimal
5
5
  from enum import StrEnum
6
6
 
7
- from playwright.async_api import Page, Playwright
7
+ from PGram import Bot
8
+ from playwright.async_api import Page, Playwright, Browser
8
9
  from pyro_client.client.file import FileClient
9
10
  from pyro_client.client.user import UserClient
11
+ from tortoise.timezone import now
10
12
 
11
- from xync_client.loader import NET_TOKEN
12
13
  from xync_schema.enums import UserStatus
13
- from xync_schema.models import PmAgent, User
14
+ from xync_schema.models import PmAgent, User, Transfer
15
+
16
+ from xync_client.Abc.HasAbotUid import HasAbotUid
14
17
 
15
18
 
16
19
  class LoginFailedException(Exception): ...
17
20
 
18
21
 
19
- class PmAgentClient(metaclass=ABCMeta):
22
+ class PmAgentClient(HasAbotUid, metaclass=ABCMeta):
20
23
  class Pages(StrEnum):
21
24
  base = "https://host"
22
25
  LOGIN = base + "login"
23
26
  SEND = base + "send"
24
27
  OTP_LOGIN = base + "login/otp"
25
28
 
29
+ browser: Browser
26
30
  norm: str
27
31
  agent: PmAgent
28
- bot: FileClient | UserClient
32
+ ubot: FileClient | UserClient = None
29
33
  page: Page
30
34
  pages: type(StrEnum) = Pages
31
- last_page: int = 0
32
35
  last_active: datetime = datetime.now()
36
+ with_userbot: bool = False
33
37
  _is_started: bool = False
34
38
 
35
- async def start(self, pw: Playwright, headed: bool = False, userbot: bool = False) -> "PmAgentClient":
36
- bot = FileClient(NET_TOKEN)
37
- self.bot = UserClient(self.uid, bot) if userbot else bot
38
- await self.bot.start()
39
+ async def start(self, pw: Playwright, headed: bool = False) -> "PmAgentClient":
40
+ if self.with_userbot:
41
+ self.ubot = UserClient(self.uid)
42
+ await self.ubot.start()
39
43
 
40
44
  self.browser = await pw.chromium.launch(
41
- channel="chromium" if headed else "chromium-headless-shell", headless=not headed
45
+ channel="chrome-beta" if headed else "chromium-headless-shell", headless=not headed
42
46
  )
47
+ # noinspection PyTypeChecker
43
48
  context = await self.browser.new_context(storage_state=self.agent.state)
44
49
  self.page = await context.new_page()
45
- await self.page.goto(self.pages.SEND) # Оптимистично переходим сразу на страницу отправки
50
+ await self.page.goto(self.pages.SEND, wait_until="commit") # Оптимистично переходим сразу на страницу отправки
46
51
  if self.page.url.startswith(self.pages.LOGIN): # Если перебросило на страницу логина
47
52
  await self._login() # Логинимся
48
53
  if not self.page.url.startswith(self.pages.SEND): # Если в итоге не удалось попасть на отправку
49
- await self.bot.send(self.norm + " not logged in!", self.uid, photo=await self.page.screenshot())
54
+ await self.receive(self.norm + " not logged in!", photo=await self.page.screenshot())
50
55
  raise LoginFailedException(f"User {self.agent.user_id} has not logged in")
51
56
  loop = get_running_loop()
52
- self.last_active = datetime.now()
57
+ self.last_active = now()
53
58
  loop.create_task(self._idle()) # Бесконечно пасёмся в фоне на странице отправки, что бы куки не протухли
54
59
  self._is_started = True
55
60
  return self
@@ -59,18 +64,21 @@ class PmAgentClient(metaclass=ABCMeta):
59
64
  async def _idle(self): # todo: не мешать другим процессам, обновлять на другой вкладке?
60
65
  while (await User.get(username_id=self.uid)).status >= UserStatus.ACTIVE:
61
66
  await self.page.wait_for_timeout(30 * 1000)
62
- if self.last_active < datetime.now() - timedelta(minutes=1):
63
- await self.page.reload()
64
- self.last_active = datetime.now()
65
- await self.bot.send(self.norm + " stoped", self.uid)
67
+ if self.last_active < now() - timedelta(minutes=1):
68
+ await self.page.reload(wait_until="commit")
69
+ self.last_active = now()
70
+ await self.receive(self.norm + " stoped")
66
71
  await self.stop()
67
72
 
68
73
  async def stop(self):
69
74
  # save state
75
+ # noinspection PyTypeChecker
70
76
  self.agent.state = await self.page.context.storage_state()
71
77
  await self.agent.save()
72
78
  # closing
73
- await self.bot.stop()
79
+ await self.abot.stop()
80
+ if self.ubot:
81
+ await self.ubot.stop()
74
82
  await self.page.context.close()
75
83
  await self.page.context.browser.close()
76
84
  self._is_started = False
@@ -79,15 +87,18 @@ class PmAgentClient(metaclass=ABCMeta):
79
87
  async def _login(self): ...
80
88
 
81
89
  @abstractmethod
82
- async def send(self, dest, amount: int, cur: str) -> tuple[int, bytes, float]: ...
90
+ async def send(self, t: Transfer) -> tuple[int, bytes] | float: ...
83
91
 
84
92
  @abstractmethod # проверка поступления определенной суммы за последние пол часа (минимум), return точную сумму
85
- async def check_in(self, amount: int | Decimal | float, cur: str, tid: str | int = None) -> float | None: ...
93
+ async def check_in(
94
+ self, amount: int | Decimal | float, cur: str, dt: datetime, tid: str | int = None
95
+ ) -> float | None: ...
86
96
 
87
97
  @abstractmethod # видео входа в аккаунт, и переход в историю поступлений за последние сутки (минимум)
88
98
  async def proof(self) -> bytes: ...
89
99
 
90
- def __init__(self, agent: PmAgent):
100
+ def __init__(self, agent: PmAgent, abot: Bot):
91
101
  self.agent = agent
102
+ self.abot = abot
92
103
  self.uid = agent.user.username_id
93
104
  self.norm = agent.pm.norm
@@ -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
 
@@ -14,6 +15,7 @@ from pyro_client.client.file import FileClient
14
15
  from tortoise.exceptions import IntegrityError
15
16
  from tortoise.timezone import now
16
17
  from tortoise.transactions import in_transaction
18
+ from xync_bot import XyncBot
17
19
 
18
20
  from xync_client.Abc.PmAgent import PmAgentClient
19
21
  from xync_schema import models
@@ -28,7 +30,7 @@ from xync_client.Bybit.etype.order import (
28
30
  OrderFull,
29
31
  StatusApi,
30
32
  )
31
- from xync_client.loader import NET_TOKEN
33
+ from xync_client.loader import NET_TOKEN, PAY_TOKEN
32
34
  from xync_client.Abc.InAgent import BaseInAgentClient
33
35
  from xync_client.Bybit.agent import AgentClient
34
36
 
@@ -39,7 +41,7 @@ class InAgentClient(BaseInAgentClient):
39
41
  async def start_listen(self):
40
42
  t = await self.agent_client.ott()
41
43
  ts = int(float(t["time_now"]) * 1000)
42
- await self.ws_prv(self.agent_client.actor.agent.auth["deviceId"], t["result"], ts)
44
+ await self.ws_prv(self.agent_client.agent.auth["deviceId"], t["result"], ts)
43
45
 
44
46
  # 3N: [T] - Уведомление об одобрении запроса на сделку
45
47
  async def request_accepted_notify(self) -> int: ... # id
@@ -96,11 +98,11 @@ class InAgentClient(BaseInAgentClient):
96
98
  # сразу уменьшаем доступный остаток монеты/валюты
97
99
  await self.money_upd(order_db)
98
100
  if upd.side: # я покупатель - ждем мою оплату
99
- dest = order.paymentTermList[0].accountNo
100
- if not re.match(r"^([PpРр])\d{7,10}\b", dest):
101
+ _dest = order.paymentTermList[0].accountNo
102
+ if not re.match(r"^([PpРр])\d{7,10}\b", _dest):
101
103
  continue
102
104
  await order_db.fetch_related("ad__pair_side__pair", "cred__pmcur__cur")
103
- await self.send_payment(order_db, dest)
105
+ await self.send_payment(order_db)
104
106
  case StatusApi.wait_for_buyer:
105
107
  if upd.side == 0: # ждем когда покупатель оплатит
106
108
  if not (pmacdx := await self.get_pma_by_cdex(order)):
@@ -140,9 +142,9 @@ class InAgentClient(BaseInAgentClient):
140
142
  if (
141
143
  o["amount"] == order.amount
142
144
  and o["id"] != upd.id
143
- and o.paymentTermList[0].accountNo
144
- == order.paymentTermList[0].accountNo
145
- and int(order.createDate) < int(o["createDate"]) + 3600 * 000
145
+ and int(order.createDate) < int(o["createDate"]) + 900 * 000
146
+ # todo: get full_order from o, and cred or pm from full_order:
147
+ # and o['paymentTermList'][0].accountNo == order.paymentTermList[0].accountNo
146
148
  )
147
149
  ]
148
150
  curex = await models.CurEx.get(
@@ -245,7 +247,10 @@ class InAgentClient(BaseInAgentClient):
245
247
  if not upd.message:
246
248
  ...
247
249
  if im_buyer and (g := re.match(r"^[PpРр]\d{7,10}\b", upd.message)):
248
- await self.send_payment(order_db, g.group())
250
+ if not order_db.cred.detail.startswith(dest := g.group()):
251
+ order_db.cred.detail = dest
252
+ await order_db.save()
253
+ await self.send_payment(order_db)
249
254
  case "READ":
250
255
  upd = Read.model_validate(data["data"])
251
256
  # if upd.status not in (StatusWs.created, StatusWs.canceled, 10, StatusWs.completed):
@@ -318,7 +323,7 @@ class InAgentClient(BaseInAgentClient):
318
323
  await fiat.save(update_fields=["amount"])
319
324
  logging.info(f"Order #{order_db.id} {order_db.status.name}. Fiat: {fiat.amount}, Asset: {ass.free}")
320
325
 
321
- async def send_payment(self, order_db: models.Order, dest):
326
+ async def send_payment(self, order_db: models.Order):
322
327
  if order_db.status != OrderStatus.created:
323
328
  return
324
329
  fmt_am = round(order_db.amount * 10**-2, 2)
@@ -335,19 +340,23 @@ class InAgentClient(BaseInAgentClient):
335
340
  # проверяем не отправляли ли мы уже перевод по этому ордеру
336
341
  if t := await models.Transfer.get_or_none(order=order_db, amount=order_db.amount):
337
342
  await pma.bot.send(
338
- f"Order# {order_db.exid}: Double send {fmt_am}{cur} to {dest} #{t.pmid}!",
343
+ f"Order# {order_db.exid}: Double send {fmt_am}{cur} to {order_db.cred.detail} #{t.pmid}!",
339
344
  self.agent_client.actor.person.user.username_id,
340
345
  )
341
- raise Exception(f"Order# {order_db.exid}: Double send {fmt_am}{cur} to {dest} #{t.pmid}!")
346
+ raise Exception(
347
+ f"Order# {order_db.exid}: Double send {fmt_am}{cur} to {order_db.cred.detail} #{t.pmid}!"
348
+ )
342
349
 
343
350
  # ставим в бд статус "оплачен"
344
351
  order_db.status = OrderStatus.paid
345
352
  order_db.payed_at = datetime.now(timezone.utc)
346
353
  await order_db.save()
347
- # отправляем деньги
348
- tid, img, rest_amount = await pma.send(dest=dest, amount=fmt_am, cur=cur)
349
354
  # создаем перевод в бд
350
- t, _ = await models.Transfer.update_or_create({"order": order_db, "amount": order_db.amount}, pmid=tid)
355
+ t = models.Transfer(order=order_db, amount=order_db.amount, updated_at=now())
356
+ # отправляем деньги
357
+ tid, img = await pma.send(t)
358
+ t.pmid = tid
359
+ await t.save()
351
360
  await self.send_receipt(str(order_db.exid), tid) # отправляем продавцу чек
352
361
  logging.info(f"Order {order_db.exid} PAID at {datetime.now()}: {fmt_am}!")
353
362
 
@@ -396,33 +405,41 @@ async def main():
396
405
  from x_model import init_db
397
406
  from xync_client.loader import TORM
398
407
 
399
- _ = await init_db(TORM, True)
408
+ cn = await init_db(TORM, True)
400
409
  logging.basicConfig(level=logging.INFO)
401
410
 
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,
411
+ agent = (
412
+ await models.Agent.filter(
413
+ actor__ex_id=4,
414
+ active=True,
415
+ auth__isnull=False,
416
+ actor__person__user__status=UserStatus.ACTIVE,
417
+ actor__person__user__pm_agents__isnull=False,
408
418
  )
409
- .prefetch_related("ex", "agent", "person__user__pm_agents__user", "person__user__pm_agents__pm")
419
+ .prefetch_related("actor__ex", "actor__person__user")
410
420
  .first()
411
421
  )
422
+ pm_agents = await models.PmAgent.filter(
423
+ active=True,
424
+ auth__isnull=False,
425
+ user__status=UserStatus.ACTIVE,
426
+ ).prefetch_related("pm", "user__gmail")
427
+
428
+ bbot = XyncBot(PAY_TOKEN, cn)
412
429
 
413
430
  async with FileClient(NET_TOKEN) as b:
414
431
  b: FileClient
415
- cl: InAgentClient = actor.in_client(b)
432
+ cl: InAgentClient = agent.in_client(b, bbot)
416
433
  # await cl.agent_client.export_my_ads()
417
434
  # 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)
435
+ for pma in pm_agents:
436
+ pcl: PmAgentClient = pma.client(bbot)
437
+ cl.pmacs[pma.pm_id] = await pcl.start(await async_playwright().start(), True)
421
438
  try:
422
439
  _ = await cl.start_listen()
423
440
  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)
441
+ await b.send("😱Bybit InAgent CRASHED!!!😱", agent.actor.person.user.username_id)
442
+ await b.send(f"```\n{''.join(traceback.format_exception(e))}\n```", agent.actor.person.user.username_id)
426
443
  await cl.agent_client.close()
427
444
 
428
445
 
@@ -24,10 +24,11 @@ from tortoise.signals import post_save
24
24
  from urllib3.exceptions import ReadTimeoutError
25
25
  from x_model import init_db
26
26
  from x_model.func import ArrayAgg
27
+ from xync_bot import XyncBot
27
28
  from xync_schema import models
28
29
  from xync_schema.enums import OrderStatus
29
30
 
30
- from xync_schema.models import Actor, Cond, CondSim, PmCur, PairSide
31
+ from xync_schema.models import Actor, Cond, CondSim, PmCur, PairSide, Agent
31
32
 
32
33
  from xync_client.Abc.Agent import BaseAgentClient
33
34
  from xync_client.Abc.xtype import BaseOrderReq, FlatDict
@@ -43,7 +44,7 @@ from xync_client.Bybit.etype.order import (
43
44
  Message,
44
45
  Status,
45
46
  )
46
- from xync_client.loader import TORM, NET_TOKEN
47
+ from xync_client.loader import TORM, NET_TOKEN, PAY_TOKEN
47
48
 
48
49
 
49
50
  class NoMakerException(Exception):
@@ -87,9 +88,9 @@ class AgentClient(BaseAgentClient): # Bybit client
87
88
  rcond_sims: dict[int, set[int]] = defaultdict(set) # backward
88
89
  tree: dict = {}
89
90
 
90
- def __init__(self, actor: Actor, bot: FileClient, **kwargs):
91
- super().__init__(actor, bot, **kwargs)
92
- self.api = P2P(testnet=False, api_key=actor.agent.auth["key"], api_secret=actor.agent.auth["sec"])
91
+ def __init__(self, agent: Agent, fbot: FileClient, bbot: XyncBot, **kwargs):
92
+ super().__init__(agent, fbot, bbot, **kwargs)
93
+ self.api = P2P(testnet=False, api_key=agent.auth["key"], api_secret=agent.auth["sec"])
93
94
  self.hist: dict = None
94
95
  self.completed_orders: list[int] = None
95
96
 
@@ -1248,7 +1249,7 @@ class ExcCode(IntEnum):
1248
1249
 
1249
1250
  async def main():
1250
1251
  logging.basicConfig(level=logging.INFO)
1251
- _ = await init_db(TORM)
1252
+ cn = await init_db(TORM)
1252
1253
 
1253
1254
  @post_save(models.Race)
1254
1255
  async def race_upserted(
@@ -1262,12 +1263,14 @@ async def main():
1262
1263
  ...
1263
1264
 
1264
1265
  actor = (
1265
- await models.Actor.filter(ex_id=4, agent__isnull=False).prefetch_related("ex", "agent", "person__user").first()
1266
+ await models.Agent.filter(actor__ex_id=4, auth__isnull=False, active=True)
1267
+ .prefetch_related("actor__ex", "actor__person__user")
1268
+ .first()
1266
1269
  )
1267
1270
  filebot = FileClient(NET_TOKEN)
1268
1271
  await filebot.start()
1269
1272
  # b.add_handler(MessageHandler(cond_start_handler, command("cond")))
1270
- cl: AgentClient = actor.client(filebot)
1273
+ cl: AgentClient = actor.client(filebot, XyncBot(PAY_TOKEN, cn))
1271
1274
 
1272
1275
  # await cl.ex_client.set_pairs()
1273
1276
  # await cl.ex_client.set_pms()
@@ -1,113 +1,121 @@
1
- from playwright.async_api import Page, Playwright
2
- from pyro_client.client.file import FileClient
3
- from pyro_client.client.user import UserClient
1
+ import logging
2
+ import pickle
3
+ import re
4
+ from base64 import urlsafe_b64decode
5
+ from datetime import datetime
6
+
7
+ from google.auth.transport.requests import Request
8
+ from google_auth_oauthlib.flow import InstalledAppFlow
9
+ from googleapiclient.discovery import Resource, build
10
+ from requests import get
4
11
  from xync_schema.models import User, Gmail
5
12
 
6
- from xync_client.loader import NET_TOKEN
13
+ from xync_client.Abc.HasAbotUid import HasAbotUid
14
+ from xync_client.loader import TORM
15
+
16
+ # Область доступа
17
+ SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"]
18
+
19
+
20
+ class GmClient(HasAbotUid):
21
+ service: Resource
22
+
23
+ def __init__(self, user: User):
24
+ """Авторизация и создание сервиса Gmail API"""
25
+ creds = None
26
+ # Файл token.pickle хранит токены доступа пользователя
27
+ if user.gmail.token:
28
+ creds = pickle.loads(user.gmail.token)
7
29
 
30
+ # Если нет валидных credentials, запрашиваем авторизацию
31
+ if not creds or not creds.valid:
32
+ if creds and creds.expired and creds.refresh_token:
33
+ creds.refresh(Request())
34
+ else:
35
+ flow = InstalledAppFlow.from_client_config(user.gmail.auth, SCOPES)
36
+ creds = flow.run_local_server(port=0)
8
37
 
9
- class GmClient:
10
- uid: int
11
- user: User
12
- page: Page
13
- bot: UserClient = None
14
- HOME = "https://mail.google.com/mail/u/0/"
38
+ # Сохраняем credentials для следующего запуска
39
+ user.gmail.token = pickle.dumps(creds)
15
40
 
16
- def __init__(self, uid: int):
17
- self.uid = uid
41
+ self.service = build("gmail", "v1", credentials=creds)
42
+ self.uid = user.username_id
18
43
 
19
- async def start(self, pw: Playwright, headed: bool = False):
20
- self.user = await User.get(username_id=self.uid).prefetch_related("gmail")
21
- if not self.user.gmail:
22
- self.user.gmail = await Gmail.create(
23
- login=input(f"{self.user.last_name} gmail login:"), password=input("password:")
44
+ def _get_last_email(self, sender_email, subject_keyword=None):
45
+ """
46
+ Получить последнее письмо от определенного отправителя
47
+
48
+ Args:
49
+ sender_email: email отправителя (например, 'example@gmail.com')
50
+ subject_keyword: ключевое слово в теме (опционально)
51
+ """
52
+
53
+ def _get_email_body(payload):
54
+ """Извлечь текст письма из payload"""
55
+ if "body" in payload and "data" in payload["body"]:
56
+ return urlsafe_b64decode(payload["body"]["data"]).decode("utf-8")
57
+ return ""
58
+
59
+ # Формируем поисковый запрос
60
+ query = f"from:{sender_email}"
61
+ if subject_keyword:
62
+ query += f" subject:{subject_keyword}"
63
+
64
+ # Ищем письма с этим запросом
65
+ results = (
66
+ self.service.users()
67
+ .messages()
68
+ .list(
69
+ userId="me",
70
+ q=query,
71
+ maxResults=1, # Только последнее письмо
24
72
  )
25
- browser = await pw.chromium.launch(
26
- channel="chrome" if headed else "chromium-headless-shell",
27
- headless=not headed,
28
- args=[
29
- "--disable-blink-features=AutomationControlled",
30
- "--no-sandbox",
31
- "--disable-web-security",
32
- "--disable-infobars",
33
- "--disable-extensions",
34
- "--start-maximized",
35
- ],
73
+ .execute()
36
74
  )
37
- context = await browser.new_context(storage_state=self.user.gmail.auth, locale="en")
38
- self.page = await context.new_page()
39
- # go home
40
- await self.page.goto(self.HOME, timeout=62000)
41
- if not self.page.url.startswith(self.HOME):
42
- if await ( # ваще с 0 заходим
43
- sgn_btn := self.page.locator(
44
- 'header a[href^="https://accounts.google.com/AccountChooser/signinchooser"]:visible',
45
- has_text="sign",
46
- )
47
- ).count():
48
- await sgn_btn.click()
49
- if self.page.url.startswith("https://accounts.google.com/v3/signin/accountchooser"): # надо выбрать акк
50
- await self.page.locator("li").first.click()
51
- # если предлагает залогиниться
52
- elif await self.page.locator("h1#headingText", has_text="Sign In").count():
53
- await self.page.fill("input[type=email]", self.user.gmail.login)
54
- await self.page.locator("button", has_text="Next").click()
55
- # осталось ввести пороль:
56
- await self.page.fill("input[type=password]", self.user.gmail.password)
57
- await self.page.locator("#passwordNext").click()
58
- await self.page.wait_for_timeout(3000)
59
- if self.page.url.startswith("https://accounts.google.com/v3/signin/challenge/dp"):
60
- await self.load_bot()
61
- await self.bot.receive("Аппрувни гмейл, у тебя 1.5 минуты", photo=await self.page.screenshot())
62
- await self.page.wait_for_url(lambda u: u.startswith(self.HOME), timeout=90 * 1000) # убеждаемся что мы в почте
63
- self.user.gmail.auth = await self.page.context.storage_state()
64
- await self.user.gmail.save()
65
-
66
- async def mail_confirm(self):
67
- lang = await self.page.get_attribute("html", "lang")
68
- labs = {
69
- "ru": "Оповещения",
70
- "en-US": "Updates",
71
- }
72
- tab = self.page.get_by_role("heading").get_by_label(labs[lang]).last
73
- await tab.click()
74
- rows = self.page.locator("tbody>>nth=4 >> tr")
75
- row = rows.get_by_text("Volet.com").and_(rows.get_by_text("Please Confirm Withdrawal"))
76
- if not await row.count():
77
- await self.bot.receive("А нет запросов от волета", photo=await self.page.screenshot())
78
-
79
- await row.click()
80
- await self.page.wait_for_load_state()
81
- btn = self.page.locator('a[href^="https://account.volet.com/verify/"]', has_text="confirm").first
82
- await btn.click()
83
-
84
- async def load_bot(self):
85
- if not self.bot:
86
- bot = FileClient(NET_TOKEN)
87
- self.bot = UserClient(self.uid, bot)
88
- if not self.bot.is_connected:
89
- await self.bot.start()
90
-
91
- async def stop(self):
92
- if self.bot and self.bot.is_connected: # todo: do not stop if
93
- await self.bot.stop(False)
94
- await self.page.context.close()
95
- await self.page.context.browser.close()
75
+
76
+ if not (messages := results.get("messages", [])):
77
+ logging.warning("Письма не найдены")
78
+ return None
79
+
80
+ # Получаем полную информацию о письме
81
+ message_id = messages[0]["id"]
82
+ message = self.service.users().messages().get(userId="me", id=message_id, format="full").execute()
83
+
84
+ # Извлекаем заголовки
85
+ headers = message["payload"]["headers"]
86
+ subject = next((h["value"] for h in headers if h["name"] == "Subject"), "Нет темы")
87
+ from_email = next((h["value"] for h in headers if h["name"] == "From"), "Неизвестно")
88
+ date = next((h["value"] for h in headers if h["name"] == "Date"), "Неизвестно")
89
+
90
+ # Извлекаем текст письма
91
+ body = _get_email_body(message["payload"])
92
+
93
+ return {"id": message_id, "subject": subject, "from": from_email, "date": date, "body": body}
94
+
95
+ async def mail_confirm(self, amount: float, dt: datetime):
96
+ if email := self._get_last_email("Volet.com", "Please Confirm Withdrawal"):
97
+ date = datetime.strptime(email["date"].split(",")[1].split(" +")[0], "%d %b %Y %H:%M:%S")
98
+ if match := re.search(r"Amount: <b>([\d.]+) [A-Z]{3}</b>", email["body"]):
99
+ amt = float(match.group(1))
100
+ if match := re.search(r"https://account\.volet\.com/verify/([a-f0-9-]+)", email["body"]):
101
+ token = match.group(1)
102
+
103
+ if email and amount == amt and date > dt and token:
104
+ get(f"https://account.volet.com/verify/{token}")
105
+ return True
106
+
107
+ await self.receive("А нет запросов от волета")
108
+ return False
96
109
 
97
110
 
98
111
  async def _test():
99
112
  from x_model import init_db
100
- from xync_schema import TORM
101
-
102
- _ = await init_db(TORM, True)
103
- uid = 193017646
104
- gmc = GmClient(uid)
105
- try:
106
- await gmc.start(True)
107
- except TimeoutError as te:
108
- raise te
109
- finally:
110
- await gmc.stop()
113
+
114
+ _ = await init_db(TORM)
115
+
116
+ gm = await Gmail.get(id=1).prefetch_related("user__username")
117
+ gmc = GmClient(gm)
118
+ await gmc.mail_confirm(amount=90, dt=datetime.now())
111
119
 
112
120
 
113
121
  if __name__ == "__main__":
@@ -11,6 +11,7 @@ from os import urandom
11
11
  from time import sleep
12
12
  from urllib.parse import urlencode
13
13
 
14
+ from PGram import Bot
14
15
  from asyncpg.pgproto.pgproto import timedelta
15
16
  from cryptography.hazmat.primitives import padding
16
17
  from cryptography.hazmat.primitives.ciphers import Cipher
@@ -18,10 +19,13 @@ from cryptography.hazmat.primitives.ciphers.algorithms import AES
18
19
  from cryptography.hazmat.primitives.ciphers.modes import CBC
19
20
  from payeer_api import PayeerAPI
20
21
  from playwright.async_api import async_playwright, Playwright, Error
22
+
23
+ # noinspection PyProtectedMember
21
24
  from playwright._impl._errors import TimeoutError
22
- from xync_schema.models import TopUp, TopUpAble, PmAgent
25
+ from xync_bot import XyncBot
26
+ from xync_schema.models import TopUp, TopUpAble, PmAgent, Transfer
23
27
 
24
- from xync_client.loader import TORM
28
+ from xync_client.loader import TORM, PAY_TOKEN
25
29
 
26
30
  from xync_client.Abc.PmAgent import PmAgentClient
27
31
  from xync_client.Pms.Payeer.login import login
@@ -64,9 +68,10 @@ class Client(PmAgentClient):
64
68
  norm: str = "payeer"
65
69
  pages: type(StrEnum) = Pages
66
70
  api: PayeerAPI
71
+ with_userbot: bool = False
67
72
 
68
- def __init__(self, agent: PmAgent):
69
- super().__init__(agent)
73
+ def __init__(self, agent: PmAgent, abot: XyncBot):
74
+ super().__init__(agent, abot)
70
75
  if api_id := self.agent.auth.get("api_id"):
71
76
  self.api = PayeerAPI(self.agent.auth["email"], api_id, self.agent.auth["api_sec"])
72
77
 
@@ -74,7 +79,7 @@ class Client(PmAgentClient):
74
79
  await login(self.agent)
75
80
  for cookie in self.agent.state["cookies"]:
76
81
  await self.page.context.add_cookies([cookie])
77
- await self.page.goto(self.pages.SEND)
82
+ await self.page.goto(self.pages.SEND, wait_until="commit")
78
83
 
79
84
  @staticmethod
80
85
  def form_redirect(topup: TopUp) -> tuple[str, dict | None]:
@@ -131,20 +136,22 @@ class Client(PmAgentClient):
131
136
  "ts": datetime.strptime(ti["dateCreate"], "%d.%m.%Y %H:%M:%S") - timedelta(hours=3),
132
137
  }
133
138
 
134
- async def send(self, dest: str, amount: int, cur: str) -> tuple[int, bytes, int] | int:
139
+ async def send(self, t: Transfer) -> tuple[str, bytes] | float:
140
+ dest, cur = t.order.cred.detail, t.order.cred.pmcur.cur.ticker
141
+ amount = round(t.order.amount * 10**-t.order.cred.pmcur.cur.scale, t.order.cred.pmcur.cur.scale)
135
142
  self.last_active = datetime.now()
136
143
  page = self.page
137
144
  if not page.url.startswith(self.pages.SEND):
138
145
  try:
139
- await page.goto(self.pages.SEND)
146
+ await page.goto(self.pages.SEND, wait_until="commit")
140
147
  except (TimeoutError, Error):
141
148
  await login(self.agent)
142
149
  for cookie in self.agent.state["cookies"]:
143
150
  await page.context.add_cookies([cookie])
144
151
  sleep(0.5)
145
- await page.goto("https://payeer.com/en/account/send/")
152
+ await page.goto("https://payeer.com/en/account/send/", wait_until="commit")
146
153
  has_amount = float(self.api.get_balance()[cur]["DOSTUPNO"])
147
- if float(amount) <= has_amount:
154
+ if amount <= has_amount:
148
155
  sleep(0.1)
149
156
  await page.locator('input[name="param_ACCOUNT_NUMBER"]').fill(dest)
150
157
  await page.locator("select[name=curr_receive]").select_option(value=cur)
@@ -166,42 +173,41 @@ class Client(PmAgentClient):
166
173
  except TimeoutError as _:
167
174
  logging.error("Repeat!")
168
175
  sleep(0.5)
169
- return await self.send(dest, amount, cur)
176
+ return await self.send(t)
170
177
  if await page.locator('.note_txt:has-text("successfully completed")').count():
171
178
  transaction = await page.locator(".note_txt").all_text_contents()
172
- trans_num = int(transaction[0].replace("Transaction #", "").split()[0])
173
- await page.goto("https://payeer.com/ru/account/history/")
179
+ trans_num = transaction[0].replace("Transaction #", "").split()[0]
180
+ await page.goto("https://payeer.com/ru/account/history/", wait_until="commit")
174
181
  await page.click(f".history-id-{trans_num} a.link")
175
182
  sleep(1)
176
183
  receipt = await page.query_selector(".ui-dialog.ui-corner-all")
177
- return trans_num, await receipt.screenshot(path=f"tmp/{trans_num}.png"), int(has_amount - amount)
184
+ return trans_num, await receipt.screenshot(path=f"tmp/{trans_num}.png")
178
185
  else:
179
- await self.bot.send("Payeer хз", self.uid, photo=await self.page.screenshot())
186
+ await self.receive("Payeer хз", photo=await self.page.screenshot())
180
187
  return -1
181
188
  else:
182
- await self.bot.send(
189
+ await self.receive(
183
190
  f"Payeer no have {amount}, only {has_amount}{cur} to {dest}",
184
- self.uid,
185
191
  photo=await self.page.screenshot(),
186
192
  )
187
193
  return has_amount
188
194
 
189
195
  def check_in(
190
- self, amount: Decimal | int | float, cur: str, tme: datetime = None, tid: str | int = None
196
+ self, amount: Decimal | int | float, cur: str, dt: datetime = None, tid: str | int = None
191
197
  ) -> tuple[Decimal | None, int | None]:
192
198
  history = self.api.history(type="incoming", append=tid, count=3)
193
199
  if tid:
194
200
  return (t := history.get(tid)) and Decimal(t["creditedAmount"])
195
- t = [
201
+ ts: list[dict] = [
196
202
  h
197
203
  for h in history.values()
198
204
  if (
199
205
  amount <= Decimal(h["creditedAmount"]) <= ceil(amount)
200
206
  and h["creditedCurrency"] == cur
201
- and datetime.fromisoformat(h["date"]) > tme - timedelta(minutes=1)
207
+ and datetime.fromisoformat(h["date"]) > dt - timedelta(minutes=1)
202
208
  )
203
209
  ]
204
- if not (t := t and t[0]):
210
+ if not (t := ts and ts[0]):
205
211
  return None, None
206
212
  return (
207
213
  amount <= (am := Decimal(t["creditedAmount"])) <= ceil(amount) and t["creditedCurrency"] == cur
@@ -219,7 +225,8 @@ async def main(uid: int):
219
225
  )
220
226
  if not agent:
221
227
  raise Exception(f"No active user #{uid} with agent for volet!")
222
- pyr = agent.client()
228
+ abot = Bot(PAY_TOKEN)
229
+ pyr = agent.client(abot)
223
230
  playwright: Playwright = await async_playwright().start()
224
231
  try:
225
232
  dest, amount, cur = "P79619335", 4, "RUB"
@@ -236,14 +243,14 @@ async def main(uid: int):
236
243
  res = pyr.check_in(3, cur, datetime.now())
237
244
 
238
245
  if len(res) > 1 and isinstance(res[1], bytes):
239
- await pyr.bot.send(f"Transaction #{res[0]}", uid, photo=res[1])
246
+ await pyr.receive(f"Transaction #{res[0]}", photo=res[1])
240
247
  elif res[0] > 0:
241
- await pyr.bot.send(f"Sreen of transaction #{res[0]} failed", uid, photo=await pyr.page.screenshot())
248
+ await pyr.receive(f"Sreen of transaction #{res[0]} failed", photo=await pyr.page.screenshot())
242
249
  else:
243
- await pyr.bot.send(f"Sending {amount} {cur} to {dest} FAILED", uid, photo=await pyr.page.screenshot())
250
+ await pyr.receive(f"Sending {amount} {cur} to {dest} FAILED", photo=await pyr.page.screenshot())
244
251
 
245
252
  except TimeoutError as te:
246
- await pyr.bot.send(repr(te), uid, photo=await pyr.page.screenshot())
253
+ await pyr.receive(repr(te), photo=await pyr.page.screenshot())
247
254
  raise te
248
255
  # finally:
249
256
  # await pyr.stop()
@@ -12,7 +12,7 @@ async def login(agent: PmAgent):
12
12
  options.add_argument("--disable-blink-features=AutomationControlled")
13
13
  options.add_argument("--no-sandbox")
14
14
  options.add_argument("--disable-dev-shm-usage")
15
- options.add_argument("--headless=new") # for Chrome >= 109
15
+ # options.add_argument("--headless=new") # for Chrome >= 109
16
16
  options.add_argument("--disable-renderer-backgrounding")
17
17
  options.add_argument("--disable-background-timer-throttling")
18
18
  options.add_argument("--disable-backgrounding-occluded-windows")
@@ -28,7 +28,11 @@ async def login(agent: PmAgent):
28
28
  options.add_argument("--window-size=1920,1080")
29
29
  options.add_argument("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
30
30
 
31
- driver = uc.Chrome(options=options, headless=True)
31
+ driver = uc.Chrome(
32
+ options=options,
33
+ headless=False,
34
+ browser_executable_path="/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta",
35
+ )
32
36
  wait = WebDriverWait(driver, timeout=10)
33
37
  try:
34
38
  driver.get("https://payeer.com/en/auth")
@@ -1,10 +1,12 @@
1
1
  import logging
2
2
  import re
3
- from asyncio import run, ensure_future
3
+ from asyncio import run
4
+ from datetime import datetime
4
5
  from decimal import Decimal
5
6
  from enum import StrEnum
6
7
  from hashlib import sha256
7
8
 
9
+ from PGram import Bot
8
10
  from playwright.async_api import async_playwright, Page, Locator, Position, Playwright # , FloatRect
9
11
  from pyotp import TOTP
10
12
 
@@ -12,12 +14,15 @@ from pyotp import TOTP
12
14
  from playwright._impl._errors import TimeoutError
13
15
  from pyro_client.client.user import UserClient
14
16
  from pyrogram.handlers import MessageHandler
17
+ from tortoise.timezone import now
18
+ from xync_schema import models
15
19
  from xync_schema.enums import UserStatus
16
- from xync_schema.models import Cur, User, PmAgent, Cred, PmCur, Fiat, TopUp
20
+ from xync_schema.models import Cur, User, PmAgent, Cred, PmCur, Fiat, TopUp, Transfer
17
21
 
18
22
  from xync_client.Abc.PmAgent import PmAgentClient
19
23
  from xync_client.Gmail import GmClient
20
24
  from xync_client.Pms.Volet.api import APIClient
25
+ from xync_client.loader import PAY_TOKEN
21
26
 
22
27
 
23
28
  class CaptchaException(Exception): ...
@@ -58,24 +63,28 @@ class Client(PmAgentClient):
58
63
  # HOME = base + "pages/transaction"
59
64
  SEND = base + "pages/transfer/wallet"
60
65
 
61
- async def check_in(self, amount: int | Decimal | float, cur: str, tid: str | int = None) -> float | None:
62
- pass
66
+ async def check_in(
67
+ self, amount: int | Decimal | float, cur: str, dt: datetime, tid: str | int = None
68
+ ) -> float | None:
69
+ return await (self.api.check_by_id(tid) if tid else self.api.check_by_amount(amount, cur))
63
70
 
64
71
  async def proof(self) -> bytes:
65
72
  pass
66
73
 
67
74
  uid: int
68
75
  agent: PmAgent
69
- bot: UserClient
76
+ abot: Bot
77
+ ubot: UserClient
70
78
  api: APIClient
71
79
  page: Page
72
80
  gmail: GmClient
73
81
  norm: str = "payeer"
74
82
  pages: type(StrEnum) = Pages
83
+ with_userbot: bool = True
75
84
 
76
- def __init__(self, agent: PmAgent):
77
- super().__init__(agent)
78
- self.gmail = GmClient(self.uid)
85
+ def __init__(self, agent: PmAgent, abot: Bot):
86
+ super().__init__(agent, abot)
87
+ self.gmail = GmClient(agent.user)
79
88
  self.api = APIClient(self.agent.auth["api"], self.agent.auth["password"], self.agent.auth["login"])
80
89
 
81
90
  @staticmethod
@@ -113,77 +122,92 @@ class Client(PmAgentClient):
113
122
  "ts": t["updatedTime"],
114
123
  }
115
124
 
116
- async def start(self, pw: Playwright, headed: bool = False):
117
- ensure_future(self.gmail.start(pw, False))
118
- return await super().start(pw, False, True)
119
-
120
125
  async def wait_for_code(self, uid: int, topic: str, hg: tuple[MessageHandler, int]) -> str:
121
- code = await self.bot.wait_from(uid, topic, hg)
126
+ code = await self.ubot.wait_from(uid, topic, hg)
122
127
  return code and code[-6:]
123
128
 
124
129
  async def _login(self):
125
130
  ll = self.page.locator("input#j_username")
126
- await ll.fill("mixartemev@gmail.com")
127
- await self.page.locator("input#j_password").fill("mixfixX98")
128
- await self.page.wait_for_timeout(200)
131
+ await ll.fill(self.agent.auth["login"])
132
+ await self.page.locator("input#j_password").fill(self.agent.auth["password"])
133
+ await self.page.wait_for_timeout(300)
129
134
  await ll.click()
135
+ await ll.press(key="ArrowLeft")
136
+ await ll.blur()
130
137
  volet_bot_id, topic = 243630567, "otp_login"
131
138
  await self.page.locator("input#loginToAdvcashButton", has_text="log in").hover()
132
- hg = self.bot.subscribe_for(volet_bot_id, topic) # 243630567 - is volet bot
139
+ hg = self.ubot.subscribe_for(volet_bot_id, topic)
133
140
  await self.page.locator("input#loginToAdvcashButton:not([disabled])", has_text="log in").click()
134
141
  await self.page.wait_for_url(self.pages.OTP_LOGIN)
135
142
  if not (code := await self.wait_for_code(volet_bot_id, topic, hg)):
136
- await self.bot.receive("no login code", photo=await self.page.screenshot())
143
+ await self.ubot.receive("no login code", photo=await self.page.screenshot())
137
144
  raise NoCodeException(self.agent.user_id)
138
145
  await self.page.locator("input#otpId").fill(code)
139
146
  await self.page.click("input#checkOtpButton")
140
- await self.page.wait_for_url(self.pages.SEND)
147
+ await self.page.wait_for_url(self.pages.SEND, wait_until="domcontentloaded")
148
+ # save state
149
+ # noinspection PyTypeChecker
150
+ self.agent.state = await self.page.context.storage_state()
151
+ await self.agent.save()
141
152
 
142
- async def send(self, dest: str, amount: float, cur: str) -> tuple[int, bytes, float]:
153
+ async def send(self, t: Transfer) -> tuple[str, bytes] | float:
154
+ dest, cur = t.order.cred.detail, t.order.cred.pmcur.cur.ticker
155
+ amount = round(t.order.amount * 10**-t.order.cred.pmcur.cur.scale, t.order.cred.pmcur.cur.scale)
156
+ self.last_active = now()
143
157
  curs_map = {"RUB": "Ruble"}
144
-
145
- await self.go(self.pages.SEND)
158
+ await self.go(self.pages.SEND, False)
146
159
  await self.page.click("[class=combobox-account]")
147
- await self.page.click(f'[class=rf-ulst-itm] b:has-text("{curs_map[cur]}") ")')
148
- await self.page.wait_for_timeout(200)
160
+ await self.page.click(f'[class=rf-ulst-itm] b:has-text("{curs_map[cur]}")')
161
+ await self.page.wait_for_selector(f"#srcCurrency:has-text('{cur}')")
149
162
  await self.page.fill("#srcAmount", str(amount))
150
- await self.page.fill("#destWalletId", dest)
151
- await self.page.wait_for_timeout(300)
163
+ dw = self.page.locator("#destWalletId")
164
+ await dw.fill(dest)
165
+ await dw.blur()
166
+ await self.page.wait_for_selector(f"#destCurrency:has-text('{cur}')")
167
+ volet_bot_id, topic = 243630567, "otp_send"
168
+ hg = self.ubot.subscribe_for(volet_bot_id, topic)
152
169
  await self.page.locator("form#mainForm input[type=submit]", has_text="continue").click()
153
170
  # todo: check success confirming
154
171
  if otp := self.agent.auth.get("otp"):
155
172
  totp = TOTP(otp)
156
173
  code = totp.now()
157
174
  elif self.agent.user.username.session:
158
- if not (code := await self.wait_for_code("send")):
159
- if 1: # todo: need main confirm
160
- await self.gmail.mail_confirm()
161
- await self.bot.receive("no send trans code", photo=await self.page.screenshot())
175
+ if not (code := await self.wait_for_code(volet_bot_id, topic, hg)):
176
+ if 1: # todo: Is mail_confirm required?
177
+ if _mcr := await self.gmail.mail_confirm(amount, t.updated_at):
178
+ ...
179
+ # todo: click Continue
180
+ if not (code := await self.wait_for_code(volet_bot_id, topic, hg)):
181
+ code = await self.wait_for_code(volet_bot_id, topic, hg)
182
+ if not code:
183
+ await self.receive("no send trans code", photo=await self.page.screenshot())
162
184
  raise NoCodeException(self.agent.user_id)
163
185
  else:
164
186
  raise OtpNotSetException(self.agent.user_id)
165
187
  await self.page.fill("#securityValue", code)
166
188
  await self.page.locator("input[type=submit]", has_text="confirm").click()
167
189
  await self.page.wait_for_url(self.pages.SEND)
190
+ tid = await self.page.text_content("ul.p-confirmation-info dl.success>dd")
168
191
  await self.page.get_by_role("heading").click()
169
- slip = await self.page.screenshot(clip={"x": 440, "y": 205, "width": 420, "height": 360})
170
- await self.bot.receive(f"{amount} to {dest} sent", photo=slip)
192
+ slip = await self.page.screenshot(clip={"x": 440, "y": 205, "width": 440, "height": 415})
193
+ await self.receive(f"{amount} to {dest} sent", photo=slip)
194
+ return tid, slip
171
195
 
172
- async def go(self, url: Pages):
196
+ async def go(self, url: Pages, commit: bool = True):
173
197
  try:
174
- await self.page.goto(url)
198
+ await self.page.goto(url, wait_until="commit" if commit else "domcontentloaded")
175
199
  if len(await self.page.content()) < 1000: # todo: fix captcha symptom
176
200
  await self.captcha_click()
177
201
  except Exception as e:
178
- await self.bot.receive(repr(e), photo=await self.page.screenshot())
202
+ await self.receive(repr(e), photo=await self.page.screenshot())
179
203
  raise e
180
204
 
181
205
  async def send_cap_help(self, xcap: Locator):
182
206
  if await xcap.count():
183
207
  bb = await xcap.bounding_box(timeout=2000)
184
208
  byts = await self.page.screenshot(clip=bb)
185
- await self.bot.receive("put x, y", photo=byts)
186
- txt = await self.bot.bot.wait_from(self.bot.me.id, "xy", timeout=59)
209
+ await self.receive("put x, y", photo=byts)
210
+ txt = await self.ubot.wait_from(self.uid, "xy", timeout=59) # todo: fix
187
211
  for xy in txt.split(";"):
188
212
  px, py = xy
189
213
  x, y = bb["x"] + bb["width"] * int(px) / 100, bb["y"] + bb["height"] * int(py) / 100
@@ -202,7 +226,7 @@ class Client(PmAgentClient):
202
226
  if await xcap.count():
203
227
  await self.send_cap_help(xcap)
204
228
  try:
205
- await self.page.wait_for_url(lambda url: url != captcha_url)
229
+ await self.page.wait_for_url(lambda url: url != captcha_url, wait_until="commit")
206
230
  except TimeoutError: # if page no changed -> captcha is undone
207
231
  await self.page.screenshot()
208
232
  raise CaptchaException(self.page.url)
@@ -233,41 +257,36 @@ class Client(PmAgentClient):
233
257
  ]
234
258
  [await Fiat.update_or_create({"amount": amount}, cred=cred) for cred, amount in creds]
235
259
 
236
- async def stop(self):
237
- # save state
238
- self.agent.state = await self.page.context.storage_state()
239
- await self.agent.save()
240
- # closing
241
- await self.bot.stop()
242
- await self.gmail.stop()
243
- await self.page.context.close()
244
- await self.page.context.browser.close()
245
-
246
260
 
247
261
  async def _test():
248
262
  from x_model import init_db
249
263
  from xync_client.loader import TORM
250
264
 
251
265
  _ = await init_db(TORM, True)
252
- logging.basicConfig(level=logging.DEBUG)
253
- uid = 193017646
266
+ logging.basicConfig(level=logging.INFO)
267
+ abot = Bot(PAY_TOKEN)
254
268
  playwright: Playwright = await async_playwright().start()
255
- agent = await PmAgent.get_or_none(pm__norm="volet", user__username_id=uid).prefetch_related(
256
- "user__username__session"
257
- )
258
- if not agent:
259
- raise Exception(f"No active user #{uid} with agent for volet!")
260
269
 
261
- va = agent.client()
262
270
  try:
263
- await va.start(playwright)
264
- await va.send("alena.artemeva25@gmail.com", 7.98)
265
- await va.wait_for_payments()
271
+ o = await models.Order.create(ad_id=7, exid=1, amount=900, cred_id=522, taker_id=419)
272
+ await o.fetch_related("cred__pmcur__cur", "ad")
273
+ pma = await models.PmAgent.get(
274
+ active=True,
275
+ auth__isnull=False,
276
+ pm_id=o.cred.pmcur.pm_id,
277
+ user__person__actors=o.ad.maker_id,
278
+ user__status=UserStatus.ACTIVE,
279
+ ).prefetch_related("pm", "user__gmail", "user__username__session")
280
+ t = models.Transfer(amount=9, created_at=now(), order=o)
281
+ pcl: Client = pma.client(abot)
282
+ pcl = await pcl.start(playwright, True, True)
283
+ await pcl.send(t)
284
+ await pcl.wait_for_payments()
266
285
  except TimeoutError as te:
267
- await va.bot.receive(repr(te), photo=await va.page.screenshot())
286
+ await pcl.receive(repr(te), photo=await pcl.page.screenshot())
268
287
  raise te
269
288
  finally:
270
- await va.stop()
289
+ await pcl.stop()
271
290
 
272
291
 
273
292
  if __name__ == "__main__":
@@ -88,16 +88,17 @@ class APIClient:
88
88
  async def check_by_amount(self, amount: decimal, cur: str = "RUB", timeout: int = 5 * 60, past: int = 0):
89
89
  hist: list = self.make_request("history", {"transactionDirection": "INCOMING", "count": 3, "from": 0})
90
90
  if int(hist[0].amount) == int(amount):
91
- return hist[0]
91
+ return hist[0]["amount"], hist[0]["id"]
92
92
  await sleep(period)
93
93
  past += period
94
94
  if past < timeout:
95
95
  return await self.check_by_amount(amount, cur, timeout, past)
96
- return False
96
+ return None, None
97
97
 
98
98
  def check_by_id(self, tid: str):
99
- t: dict = self.make_request("findTransaction", tid)
100
- return t
99
+ if t := self.make_request("findTransaction", tid):
100
+ return t["amount"], t["id"]
101
+ return None, None
101
102
 
102
103
 
103
104
  async def main():
xync_client/loader.py CHANGED
@@ -5,6 +5,7 @@ from xync_schema import models
5
5
  load_dotenv()
6
6
 
7
7
  NET_TOKEN = env("NET_TOKEN")
8
+ PAY_TOKEN = env("PAY_TOKEN")
8
9
  PG_DSN = f"postgres://{env('POSTGRES_USER')}:{env('POSTGRES_PASSWORD')}@{env('POSTGRES_HOST', 'xyncdbs')}:{env('POSTGRES_PORT', 5432)}/{env('POSTGRES_DB', env('POSTGRES_USER'))}"
9
10
  TORM = {
10
11
  "connections": {"default": PG_DSN},
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xync-client
3
- Version: 0.0.143
3
+ Version: 0.0.145
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
@@ -1,16 +1,17 @@
1
1
  xync_client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  xync_client/details.py,sha256=21itVPCgAtaYRR1H9J9oYudj95gafcFjExUN6QL17OI,1330
3
- xync_client/loader.py,sha256=zhslCMgtgp-xINPG4qQwhI1ee5Ap1vuoC8oK7GyhyMw,571
3
+ xync_client/loader.py,sha256=hxf8ob50DO7r_qjr2qBoO7IyjkXeHHzVQ63YjXerjoU,600
4
4
  xync_client/pm_unifier.py,sha256=T2Xh-tvcu114P2YBI6RK_XDiaIhyq6ABMrXDuXPlx7A,6541
5
- xync_client/Abc/Agent.py,sha256=OJaJ1RIMDYAS4xeefeXxVIVI0EKnMczQtrO35MLzqr4,5390
5
+ xync_client/Abc/Agent.py,sha256=S2f8VEejC3LHT7oUJhilZCYuhcQVREX-3uzfc06ZyO4,5633
6
6
  xync_client/Abc/Asset.py,sha256=hlgyFaU9byr2N2r8Heh-_ICx49SKuKxfRTUA4yQWmEw,454
7
7
  xync_client/Abc/Auth.py,sha256=OPQXN7_XYQZP9431ylFksd6JDusbKG8N_1g6CXTZ6yY,1495
8
8
  xync_client/Abc/BaseTest.py,sha256=vaAs5Z4HYV7k_C3zQz6JKO75s2hXtVbBI3-0Srkzv5Q,2388
9
9
  xync_client/Abc/Ex.py,sha256=n41-XCjoIV-KpC_lK3jO049tQKbFmE0eDU3SDlgZTws,12986
10
10
  xync_client/Abc/Exception.py,sha256=Sts7RpP370NBdjaH_cyXDdHtjge8zXNUGWCrKw49Zyk,482
11
- xync_client/Abc/InAgent.py,sha256=svKGATUM0c9YIDDEVLc-NxpUNWqZoVr5PjxoxK64RKs,650
11
+ xync_client/Abc/HasAbotUid.py,sha256=LsTHHjMHBauCwJoqgDa9Lx4R6xsDOHfsN4jM539Bpqg,279
12
+ xync_client/Abc/InAgent.py,sha256=XLf5czbxxEimsIIe653buoP7OsWZD6mc2w37q4TkNd0,703
12
13
  xync_client/Abc/Order.py,sha256=7-FGIJu5z9aYi0A_eJV4F-cp_6Mz_izNpefexDQZvHw,2428
13
- xync_client/Abc/PmAgent.py,sha256=wS1ZQs-hcC8ICDLcc9EfyNBKrM_ZDpJolAg-l_5ro-8,4094
14
+ xync_client/Abc/PmAgent.py,sha256=5ihM_TY40SzHT4lZ_BDYtO1lqQoVAO3aXy_W8hRICHs,4359
14
15
  xync_client/Abc/xtype.py,sha256=o1JEzWmEXCPddtlqWZ6HRTZTKX6SAnvsztbASj21zOQ,2584
15
16
  xync_client/Binance/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
17
  xync_client/Binance/binance_async.py,sha256=LP2DZaHwkfsp_4Tjvetb-1ntjQtJfODF0OgZpoPx4KU,2688
@@ -34,8 +35,8 @@ xync_client/BitGet/agent.py,sha256=YVs3bDY0OcEJGU7m2A8chzO6PFhWDnQQrA-E6MVkBBg,3
34
35
  xync_client/BitGet/ex.py,sha256=uEvvXuLaJv8o8BFi0bMA3XyBuTfVDWagAjLOHZl-xlE,3765
35
36
  xync_client/BitGet/etype/ad.py,sha256=fysSW47wGYjSOPUqY864z857AJz4gjN-nOkI1Jxd27U,1838
36
37
  xync_client/BitPapa/ex.py,sha256=U-RRB_RSOtErfRgxOZYWegZ_td_uZO37YKo3Jxchf_w,912
37
- xync_client/Bybit/InAgent.py,sha256=QQTRWcZXhNO-AZi9wfCAA-x24FIRL00_f4T5xCVgC10,25324
38
- xync_client/Bybit/agent.py,sha256=fz76f-6g81TnFoFqWX0AqMaKvnYzgYKfhpkckIOM0cM,61769
38
+ xync_client/Bybit/InAgent.py,sha256=50c9LGJuxe2T3qOL_2qNWIesJHmlmaj-oSx8dDnk9sY,26002
39
+ xync_client/Bybit/agent.py,sha256=wGYzvHGuKomNfEvFmosXQVscx9VXlzexc1Ic8sm-wA4,61894
39
40
  xync_client/Bybit/ex.py,sha256=3oARvReBoDs90FzQY31-L-q_YU-TIRbvWB7z4lwESsA,4715
40
41
  xync_client/Bybit/order.py,sha256=H4UIb8hxFGnw1hZuSbr0yZ4qeaCOIZOMc6jEst0ycBs,1713
41
42
  xync_client/Bybit/web_earn.py,sha256=qjqS10xlFc8r40IhDdPZ0LxA2dFEGbvBGXdsrUUJCMo,3019
@@ -47,7 +48,7 @@ xync_client/Bybit/etype/order.py,sha256=rPXw9eHJW3afVPQDRiooUkHfQhmqJ5fTdZ4vKdO6
47
48
  xync_client/Gate/ex.py,sha256=QbhB3u7TWnvVGD-AknB2nay6KZjEXQ-1JT9UacX4sWI,3735
48
49
  xync_client/Gate/premarket.py,sha256=IW-CgkmNJePJR2j_NRfULNKTePMX35XlhldqdiO76zY,2138
49
50
  xync_client/Gate/etype/ad.py,sha256=-EwtFcOWWvtE6UjaOdsuXWDTCVjAIRK0kSEsqPP4Yls,1296
50
- xync_client/Gmail/__init__.py,sha256=YYMDDstX8f_LVGLvqmNJi07I8BMqWhfAhLZuFQKcqcA,4687
51
+ xync_client/Gmail/__init__.py,sha256=wNEUA1L9lvLtYoIv2fHEsb6msO9oB9QTF2OkzuCPMcM,4778
51
52
  xync_client/Htx/agent.py,sha256=rOURgTeY9TsA-IzT78l5-Ze91i0x1PY683mrX38MSgs,7356
52
53
  xync_client/Htx/earn.py,sha256=jL6eRwytZEMRom_3bFm1DYthi_GFg-E1Mm3ZDXENHSg,2386
53
54
  xync_client/Htx/ex.py,sha256=GuWK5lA_MvtSd-0DhKf2MAstKvZMHhc3BIiZSgdwZv4,6074
@@ -72,14 +73,14 @@ xync_client/Pms/Alfa/state.json,sha256=MKE6vl-JsJO9PNCVqoQgBgYZTgYkHCas7USwl8QFt
72
73
  xync_client/Pms/MTS/__init__.py,sha256=P_E7W46IZEk8RsEgl7H1xV3JplMT5l9vYQYTYyNbyQ8,2101
73
74
  xync_client/Pms/Ozon/__init__.py,sha256=EvQZDSPv0fOT2hNCTP44nXHOIEQvP5bQf_7HVLiZc2I,4123
74
75
  xync_client/Pms/Payeer/.gitignore,sha256=sWORdRp8ROppV2CsMEDJ3M_SokrNWCf8b1hlaNs64hg,12
75
- xync_client/Pms/Payeer/__init__.py,sha256=xN3L-IURWJ6oYXq85qu34u0OZEQOXkshgeEuMG29qg8,9584
76
+ xync_client/Pms/Payeer/__init__.py,sha256=fxYbRHq1x1pCKQuo-blydZBWrYMSebuYZjRrD6k-e-E,9905
76
77
  xync_client/Pms/Payeer/api.py,sha256=bb8qrlPYyWafel1VR-2nate6xBeRZAVciFJblHygfAs,549
77
- xync_client/Pms/Payeer/login.py,sha256=W5FAA0reW5x2hSh8sBIWmR38VcYhwvrn1R64IAtWHVw,2921
78
+ xync_client/Pms/Payeer/login.py,sha256=GyNwB-GKE_1nlkbZJ0KNI-EnCT0j_S9ogFdn-ufb-zU,3053
78
79
  xync_client/Pms/Sber/__init__.py,sha256=dxQfd9ZPhFTc_C4xrwaxrV6p0SijDCLNzBeUv3oQG38,4926
79
80
  xync_client/Pms/Sber/utils.py,sha256=gIeJspwvoBbOBt-fjxwW4WDHPoL2Evs8LVufsjrFOfo,1870
80
81
  xync_client/Pms/Tinkoff/__init__.py,sha256=ZyLvBEUn-vh-85oPUUDS586AHgvx3c-mkQE3yBQtbw8,5580
81
- xync_client/Pms/Volet/__init__.py,sha256=v-45Qu1MgUuMfRXnwvzsEDA3s6AJ0-50B_J3vHS0Hws,10728
82
- xync_client/Pms/Volet/api.py,sha256=okqppqT7M5E6sswu-xz3L4Y9-ngefBPbFtywRi6A7Xo,3595
82
+ xync_client/Pms/Volet/__init__.py,sha256=Zn15NJRZH9pwwVjiR1Ao77MN6mTaA45B42fr0NJweno,12047
83
+ xync_client/Pms/Volet/api.py,sha256=6_dH2rzmyyvha3PeoiZdSltiAzKDWn8roSUJOAErX4M,3673
83
84
  xync_client/Pms/Volet/pl.py,sha256=l7lvUrpjFoObXPHaseOIAcSbkNqJdpy6OLDutxYJH3U,2451
84
85
  xync_client/Pms/Volet/_todo_req/req.mjs,sha256=ut3Jw37rL5lY7SskjZ9f1l0VE33tuP-PZEYUTcJMc2I,817
85
86
  xync_client/Pms/Volet/_todo_req/req.py,sha256=mKvdPrb-lkQ98Ws92_oBKu5yqyU8Krxy9XwuIhdsBao,1570
@@ -95,7 +96,7 @@ xync_client/TgWallet/order.py,sha256=BOmBx5WWfJv0-_-A8DcR-Xd8utqO_VTmSqSegm0cteQ
95
96
  xync_client/TgWallet/pyd.py,sha256=Ys3E8b3RLuyQ26frWT0F0BorkNxVpxnd18tY4Gp9dik,5636
96
97
  xync_client/TgWallet/pyro.py,sha256=2K7QWdo48k4MbbgQt90gdz_HiPck69Njm4xaMjIVgoo,1440
97
98
  xync_client/TgWallet/web.py,sha256=kDcv9SKKQPe91mw1qJBpbuyKYCAmZdfdHJylHumLBVU,1608
98
- xync_client-0.0.143.dist-info/METADATA,sha256=ihkIz-z4Ih0vWS2ohLWk4clvAKj4IbviGfNeFVHUJaU,1037
99
- xync_client-0.0.143.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
100
- xync_client-0.0.143.dist-info/top_level.txt,sha256=bmYEVIIrD3v7yFwH-X15pEfRvzhuAdfsAZ2igvNI4O8,12
101
- xync_client-0.0.143.dist-info/RECORD,,
99
+ xync_client-0.0.145.dist-info/METADATA,sha256=Czpg727G7V7cQcbZ7mDkx0Egv3bf3xtoSgH5K1Y8QgU,1037
100
+ xync_client-0.0.145.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
101
+ xync_client-0.0.145.dist-info/top_level.txt,sha256=bmYEVIIrD3v7yFwH-X15pEfRvzhuAdfsAZ2igvNI4O8,12
102
+ xync_client-0.0.145.dist-info/RECORD,,