xync-client 0.0.144__py3-none-any.whl → 0.0.147__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 +21 -5
- xync_client/Abc/HasAbotUid.py +10 -0
- xync_client/Abc/InAgent.py +5 -3
- xync_client/Abc/PmAgent.py +34 -23
- xync_client/Abc/xtype.py +3 -1
- xync_client/Bybit/InAgent.py +55 -33
- xync_client/Bybit/agent.py +110 -53
- xync_client/Bybit/etype/order.py +34 -16
- xync_client/Gmail/__init__.py +119 -98
- xync_client/Pms/Payeer/__init__.py +37 -28
- xync_client/Pms/Payeer/login.py +6 -2
- xync_client/Pms/Volet/__init__.py +80 -61
- xync_client/Pms/Volet/api.py +5 -4
- xync_client/loader.py +1 -0
- {xync_client-0.0.144.dist-info → xync_client-0.0.147.dist-info}/METADATA +1 -1
- {xync_client-0.0.144.dist-info → xync_client-0.0.147.dist-info}/RECORD +18 -17
- {xync_client-0.0.144.dist-info → xync_client-0.0.147.dist-info}/WHEEL +0 -0
- {xync_client-0.0.144.dist-info → xync_client-0.0.147.dist-info}/top_level.txt +0 -0
xync_client/Bybit/agent.py
CHANGED
|
@@ -24,13 +24,14 @@ 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
|
-
from xync_client.Abc.xtype import
|
|
34
|
+
from xync_client.Abc.xtype import FlatDict, BaseOrderReq
|
|
34
35
|
from xync_client.Bybit.etype.ad import AdPostRequest, AdUpdateRequest, Ad, AdStatus
|
|
35
36
|
from xync_client.Bybit.etype.cred import CredEpyd
|
|
36
37
|
from xync_client.Bybit.etype.order import (
|
|
@@ -42,8 +43,10 @@ from xync_client.Bybit.etype.order import (
|
|
|
42
43
|
OrderFull,
|
|
43
44
|
Message,
|
|
44
45
|
Status,
|
|
46
|
+
OrderSellRequest,
|
|
47
|
+
TakeAdReq,
|
|
45
48
|
)
|
|
46
|
-
from xync_client.loader import TORM, NET_TOKEN
|
|
49
|
+
from xync_client.loader import TORM, NET_TOKEN, PAY_TOKEN
|
|
47
50
|
|
|
48
51
|
|
|
49
52
|
class NoMakerException(Exception):
|
|
@@ -87,9 +90,9 @@ class AgentClient(BaseAgentClient): # Bybit client
|
|
|
87
90
|
rcond_sims: dict[int, set[int]] = defaultdict(set) # backward
|
|
88
91
|
tree: dict = {}
|
|
89
92
|
|
|
90
|
-
def __init__(self,
|
|
91
|
-
super().__init__(
|
|
92
|
-
self.api = P2P(testnet=False, api_key=
|
|
93
|
+
def __init__(self, agent: Agent, fbot: FileClient, bbot: XyncBot, **kwargs):
|
|
94
|
+
super().__init__(agent, fbot, bbot, **kwargs)
|
|
95
|
+
self.api = P2P(testnet=False, api_key=agent.auth["key"], api_secret=agent.auth["sec"])
|
|
93
96
|
self.hist: dict = None
|
|
94
97
|
self.completed_orders: list[int] = None
|
|
95
98
|
|
|
@@ -272,20 +275,39 @@ class AgentClient(BaseAgentClient): # Bybit client
|
|
|
272
275
|
security_risk_token = data["result"]["securityRiskToken"]
|
|
273
276
|
return security_risk_token
|
|
274
277
|
|
|
275
|
-
def _check_2fa(self, risk_token):
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
278
|
+
async def _check_2fa(self, risk_token) -> int:
|
|
279
|
+
cres = await self._post("/user/public/risk/components", {"risk_token": risk_token})
|
|
280
|
+
if cres["ret_msg"] != "success":
|
|
281
|
+
raise HTTPException("get")
|
|
282
|
+
cres = cres["result"]["component_list"]
|
|
283
|
+
res = await self._post(
|
|
284
|
+
"/user/public/risk/verify",
|
|
285
|
+
{
|
|
286
|
+
"risk_token": risk_token,
|
|
287
|
+
"component_list": {c["component_id"]: self.__get_2fa(c["component_id"]) for c in cres},
|
|
288
|
+
},
|
|
283
289
|
)
|
|
284
290
|
if res["ret_msg"] != "success":
|
|
285
|
-
|
|
286
|
-
sleep(5)
|
|
287
|
-
self._check_2fa(risk_token)
|
|
288
|
-
return res
|
|
291
|
+
logging.error("Wrong 2fa, wait 5 secs and retry..")
|
|
292
|
+
await sleep(5)
|
|
293
|
+
await self._check_2fa(risk_token)
|
|
294
|
+
return res["ret_code"]
|
|
295
|
+
|
|
296
|
+
async def __get_2fa(self, typ: Literal["google2fa", "email_verify", "payment_password_verify"], rt: str = None):
|
|
297
|
+
if typ == "google2fa":
|
|
298
|
+
bybit_secret = self.agent.auth["2fa"]
|
|
299
|
+
totp = pyotp.TOTP(bybit_secret)
|
|
300
|
+
return totp.now()
|
|
301
|
+
elif typ == "email_verify":
|
|
302
|
+
res = await self._post("/user/public/risk/send/code", {"risk_token": rt, "component_id": "email_verify"})
|
|
303
|
+
if res["ret_msg"] != "success":
|
|
304
|
+
return self.gmail.bybit_code()
|
|
305
|
+
elif cool_down := int(res["result"]["cool_down"]):
|
|
306
|
+
await sleep(cool_down)
|
|
307
|
+
return self.gmail.bybit_code()
|
|
308
|
+
elif typ == "payment_password_verify":
|
|
309
|
+
return self.agent.auth["pass"]
|
|
310
|
+
raise Exception("2fa fail")
|
|
289
311
|
|
|
290
312
|
def _post_ad(self, risk_token: str):
|
|
291
313
|
self.create_ad_body.update({"securityRiskToken": risk_token})
|
|
@@ -339,27 +361,56 @@ class AgentClient(BaseAgentClient): # Bybit client
|
|
|
339
361
|
data = self.api.remove_ad(itemId=ad_id)
|
|
340
362
|
return data
|
|
341
363
|
|
|
342
|
-
async def
|
|
343
|
-
|
|
344
|
-
if
|
|
345
|
-
|
|
346
|
-
|
|
364
|
+
async def __preorder_request(self, ad_id: int) -> PreOrderResp:
|
|
365
|
+
res = await self._post("/fiat/otc/item/simple", data={"item_id": str(ad_id)})
|
|
366
|
+
if res["ret_code"] == 0:
|
|
367
|
+
res = res["result"]
|
|
368
|
+
return PreOrderResp.model_validate(res)
|
|
369
|
+
|
|
370
|
+
async def __order_request_build(self, por: PreOrderResp, br: BaseOrderReq) -> OrderRequest:
|
|
347
371
|
req = OrderRequest(
|
|
348
|
-
itemId=
|
|
372
|
+
itemId=por.id,
|
|
349
373
|
tokenId=br.coin_exid,
|
|
350
374
|
currencyId=br.cur_exid,
|
|
351
|
-
side=
|
|
352
|
-
amount=str(br.fiat_amount
|
|
353
|
-
curPrice=
|
|
354
|
-
quantity=str(
|
|
355
|
-
flag="amount"
|
|
375
|
+
side="1" if br.is_sell else "0",
|
|
376
|
+
amount=str(br.fiat_amount),
|
|
377
|
+
curPrice=por.curPrice,
|
|
378
|
+
quantity=str(round(br.fiat_amount / float(por.price), br.coin_scale)),
|
|
379
|
+
flag="amount",
|
|
380
|
+
# paymentType="51",
|
|
381
|
+
# paymentId="20399134",
|
|
382
|
+
# online="0"
|
|
356
383
|
)
|
|
384
|
+
if br.is_sell:
|
|
385
|
+
credex = await models.CredEx.get(
|
|
386
|
+
cred__person_id=self.actor.person_id,
|
|
387
|
+
pmcur__pm__pmexs__exid=por.payments[0],
|
|
388
|
+
pmcur__pm__pmexs__ex_id=self.ex_client.ex.id,
|
|
389
|
+
pmcur__cur_id=br.cur_exid,
|
|
390
|
+
)
|
|
391
|
+
req = OrderSellRequest(**req.model_dump(), paymentType=por.payments[0], paymentId=str(credex.exid))
|
|
392
|
+
return req
|
|
393
|
+
|
|
394
|
+
async def _order_request(self, bor: BaseOrderReq) -> OrderResp:
|
|
395
|
+
por: PreOrderResp = await self.__preorder_request(bor.ad_id)
|
|
396
|
+
req: OrderRequest | OrderSellRequest = await self.__order_request_build(por, bor)
|
|
357
397
|
# вот непосредственно сам запрос на ордер
|
|
358
|
-
|
|
398
|
+
return await self.__order_create(req, bor)
|
|
399
|
+
|
|
400
|
+
async def __order_create(self, req: OrderRequest | OrderSellRequest, bor: BaseOrderReq) -> OrderResp:
|
|
401
|
+
res: dict = await self._post("/fiat/otc/order/create", data=req.model_dump())
|
|
359
402
|
if res["ret_code"] == 0:
|
|
360
|
-
|
|
403
|
+
resp = OrderResp.model_validate(res["result"])
|
|
361
404
|
elif res["ret_code"] == 912120030 or res["ret_msg"] == "The price has changed, please try again later.":
|
|
362
|
-
|
|
405
|
+
resp = await self._order_request(bor)
|
|
406
|
+
if not resp.orderId and resp.needSecurityRisk:
|
|
407
|
+
if rc := await self._check_2fa(resp.securityRiskToken):
|
|
408
|
+
await self.bbot.send(self.actor.person.user.username_id, f"Bybit 2fa: {rc}")
|
|
409
|
+
raise Exception(f"Bybit 2fa: {rc}")
|
|
410
|
+
# еще раз уже с токеном
|
|
411
|
+
req.securityRiskToken = resp.securityRiskToken
|
|
412
|
+
resp = await self.__order_create(req, bor)
|
|
413
|
+
return resp
|
|
363
414
|
|
|
364
415
|
async def cancel_order(self, order_id: str) -> bool:
|
|
365
416
|
cr = CancelOrderReq(orderId=order_id)
|
|
@@ -545,7 +596,7 @@ class AgentClient(BaseAgentClient): # Bybit client
|
|
|
545
596
|
to = ((odb.payed_at or odb.created_at) + timedelta(minutes=180 + 30)).isoformat(sep=" ").split("+")[0]
|
|
546
597
|
tsa = [
|
|
547
598
|
t
|
|
548
|
-
for tid, t in self.hist.items()
|
|
599
|
+
for tid, t in (self.hist.items() if self.hist else [])
|
|
549
600
|
if (ecredex.accountNo == t["to"] and t["from"] != "@merchant" and frm < t["date"] < to)
|
|
550
601
|
]
|
|
551
602
|
buyer_person = (
|
|
@@ -1184,6 +1235,20 @@ class AgentClient(BaseAgentClient): # Bybit client
|
|
|
1184
1235
|
|
|
1185
1236
|
self.tree = tree
|
|
1186
1237
|
|
|
1238
|
+
async def take_ad(self, req: TakeAdReq):
|
|
1239
|
+
ad: Ad = Ad.model_validate(self.api.get_ad_details(itemId=req.ad_id))
|
|
1240
|
+
bor = BaseOrderReq(
|
|
1241
|
+
ad_id=str(ad.id),
|
|
1242
|
+
fiat_amount=req.amount,
|
|
1243
|
+
is_sell=bool(ad.side),
|
|
1244
|
+
cur_exid=ad.currencyId,
|
|
1245
|
+
coin_exid=ad.tokenId,
|
|
1246
|
+
coin_scale=ad.token.scale,
|
|
1247
|
+
pm_id=req.pm_id,
|
|
1248
|
+
)
|
|
1249
|
+
resp: OrderResp = await self._order_request(bor)
|
|
1250
|
+
return resp
|
|
1251
|
+
|
|
1187
1252
|
|
|
1188
1253
|
def ms2utc(msk_ts_str: str):
|
|
1189
1254
|
return datetime.fromtimestamp(int(msk_ts_str) / 1000, timezone(timedelta(hours=3), name="MSK"))
|
|
@@ -1248,7 +1313,7 @@ class ExcCode(IntEnum):
|
|
|
1248
1313
|
|
|
1249
1314
|
async def main():
|
|
1250
1315
|
logging.basicConfig(level=logging.INFO)
|
|
1251
|
-
|
|
1316
|
+
cn = await init_db(TORM)
|
|
1252
1317
|
|
|
1253
1318
|
@post_save(models.Race)
|
|
1254
1319
|
async def race_upserted(
|
|
@@ -1261,18 +1326,20 @@ async def main():
|
|
|
1261
1326
|
else: # параметры гонки изменены
|
|
1262
1327
|
...
|
|
1263
1328
|
|
|
1264
|
-
|
|
1265
|
-
await models.
|
|
1329
|
+
agent = (
|
|
1330
|
+
await models.Agent.filter(actor__ex_id=4, auth__isnull=False, active=True)
|
|
1331
|
+
.prefetch_related("actor__ex", "actor__person__user__gmail")
|
|
1332
|
+
.first()
|
|
1266
1333
|
)
|
|
1267
1334
|
filebot = FileClient(NET_TOKEN)
|
|
1268
1335
|
await filebot.start()
|
|
1269
1336
|
# b.add_handler(MessageHandler(cond_start_handler, command("cond")))
|
|
1270
|
-
cl: AgentClient =
|
|
1337
|
+
cl: AgentClient = agent.client(filebot, XyncBot(PAY_TOKEN, cn))
|
|
1271
1338
|
|
|
1272
1339
|
# await cl.ex_client.set_pairs()
|
|
1273
1340
|
# await cl.ex_client.set_pms()
|
|
1274
|
-
|
|
1275
|
-
|
|
1341
|
+
await cl.set_creds()
|
|
1342
|
+
await cl.export_my_ads()
|
|
1276
1343
|
|
|
1277
1344
|
# создание гонок по мои активным объявам:
|
|
1278
1345
|
# for ma in cl.my_ads():
|
|
@@ -1320,7 +1387,7 @@ async def main():
|
|
|
1320
1387
|
# )
|
|
1321
1388
|
# await cl.get_api_orders() # 43, 1741294800000, 1749157199999)
|
|
1322
1389
|
|
|
1323
|
-
races = await models.Race.filter(started=True).prefetch_related(
|
|
1390
|
+
races = await models.Race.filter(started=True, road__ad__maker_id=agent.actor_id).prefetch_related(
|
|
1324
1391
|
"road__ad__pair_side__pair__cur",
|
|
1325
1392
|
"road__ad__pms",
|
|
1326
1393
|
)
|
|
@@ -1332,20 +1399,10 @@ async def main():
|
|
|
1332
1399
|
# cl.get_api_orders(), # 10, 1738357200000, 1742504399999
|
|
1333
1400
|
)
|
|
1334
1401
|
except Exception as e:
|
|
1335
|
-
await filebot.send("🤬Bybit agent CRASHED!!!🤬", actor.person.user.username_id)
|
|
1336
|
-
await filebot.send(e.__repr__(), actor.person.user.username_id)
|
|
1402
|
+
await filebot.send("🤬Bybit agent CRASHED!!!🤬", agent.actor.person.user.username_id)
|
|
1403
|
+
await filebot.send(e.__repr__(), agent.actor.person.user.username_id)
|
|
1337
1404
|
raise e
|
|
1338
|
-
|
|
1339
|
-
# ad_id="1861440060199632896",
|
|
1340
|
-
# # asset_amount=40,
|
|
1341
|
-
# fiat_amount=3000,
|
|
1342
|
-
# amount_is_fiat=True,
|
|
1343
|
-
# is_sell=False,
|
|
1344
|
-
# cur_exid=rub.exid,
|
|
1345
|
-
# coin_exid=usdt.exid,
|
|
1346
|
-
# coin_scale=usdt.coin.scale,
|
|
1347
|
-
# )
|
|
1348
|
-
# res: OrderResp = await cl.order_request(bor)
|
|
1405
|
+
|
|
1349
1406
|
# await cl.cancel_order(res.orderId)
|
|
1350
1407
|
await filebot.stop()
|
|
1351
1408
|
await cl.close()
|
xync_client/Bybit/etype/order.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from datetime import datetime
|
|
2
1
|
from enum import IntEnum
|
|
3
2
|
from typing import Literal
|
|
4
3
|
|
|
@@ -45,6 +44,12 @@ class StatusApi(IntEnum):
|
|
|
45
44
|
waiting_for_objection = 110
|
|
46
45
|
|
|
47
46
|
|
|
47
|
+
class TakeAdReq(BaseModel):
|
|
48
|
+
ad_id: int | str
|
|
49
|
+
amount: float
|
|
50
|
+
pm_id: int = None
|
|
51
|
+
|
|
52
|
+
|
|
48
53
|
class OrderRequest(BaseModel):
|
|
49
54
|
class Side(IntEnum):
|
|
50
55
|
BUY = 0
|
|
@@ -60,33 +65,46 @@ class OrderRequest(BaseModel):
|
|
|
60
65
|
flag: Literal["amount", "quantity"]
|
|
61
66
|
version: str = "1.0"
|
|
62
67
|
securityRiskToken: str = ""
|
|
68
|
+
isFromAi: bool = False
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class OrderSellRequest(OrderRequest):
|
|
72
|
+
paymentId: str
|
|
73
|
+
paymentType: str
|
|
63
74
|
|
|
64
75
|
|
|
65
76
|
class PreOrderResp(BaseModel):
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
77
|
+
id: str # bigint
|
|
78
|
+
price: str # float .cur.scale
|
|
79
|
+
lastQuantity: str # float .coin.scale
|
|
80
|
+
curPrice: str # hex 32
|
|
81
|
+
lastPrice: str # float .cur.scale # future
|
|
82
|
+
isOnline: bool
|
|
83
|
+
lastLogoutTime: str # timestamp(0)+0
|
|
73
84
|
payments: list[str] # list[int]
|
|
74
85
|
status: Literal[10, 20]
|
|
75
|
-
paymentTerms: list
|
|
76
|
-
paymentPeriod: Literal[15]
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
86
|
+
paymentTerms: list # empty
|
|
87
|
+
paymentPeriod: Literal[15, 30, 60]
|
|
88
|
+
totalAmount: str # float .cur.scale
|
|
89
|
+
minAmount: str # float .cur.scale
|
|
90
|
+
maxAmount: str # float .cur.scale
|
|
91
|
+
minQuantity: str # float .coin.scale
|
|
92
|
+
maxQuantity: str # float .coin.scale
|
|
93
|
+
itemPriceAvailableTime: str # timestamp(0)+0
|
|
94
|
+
itemPriceValidTime: Literal["45000"]
|
|
83
95
|
itemType: Literal["ORIGIN"]
|
|
96
|
+
shareItem: bool # False
|
|
84
97
|
|
|
85
98
|
|
|
86
99
|
class OrderResp(BaseModel):
|
|
87
100
|
orderId: str
|
|
88
101
|
isNeedConfirm: bool
|
|
102
|
+
confirmId: str = ""
|
|
89
103
|
success: bool
|
|
104
|
+
securityRiskToken: str = ""
|
|
105
|
+
riskTokenType: Literal["challenge"] = None
|
|
106
|
+
riskVersion: Literal["1", "2"] = None
|
|
107
|
+
needSecurityRisk: bool
|
|
90
108
|
isBulkOrder: bool
|
|
91
109
|
confirmed: str = None
|
|
92
110
|
delayTime: str
|
xync_client/Gmail/__init__.py
CHANGED
|
@@ -1,113 +1,134 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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.
|
|
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)
|
|
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)
|
|
7
37
|
|
|
38
|
+
# Сохраняем credentials для следующего запуска
|
|
39
|
+
user.gmail.token = pickle.dumps(creds)
|
|
8
40
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
user: User
|
|
12
|
-
page: Page
|
|
13
|
-
bot: UserClient = None
|
|
14
|
-
HOME = "https://mail.google.com/mail/u/0/"
|
|
41
|
+
self.service = build("gmail", "v1", credentials=creds)
|
|
42
|
+
self.uid = user.username_id
|
|
15
43
|
|
|
16
|
-
def
|
|
17
|
-
|
|
44
|
+
def _get_last_email(self, sender_email, subject_keyword=None):
|
|
45
|
+
"""
|
|
46
|
+
Получить последнее письмо от определенного отправителя
|
|
18
47
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
await
|
|
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 volet_confirm(self, amount: float, dt: datetime):
|
|
96
|
+
if email := self._get_last_email("noreply@volet.com", "Please Confirm Withdrawal"): # "Volet.com"
|
|
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
|
|
109
|
+
|
|
110
|
+
async def bybit_code(self, dt: datetime) -> str | None:
|
|
111
|
+
if email := self._get_last_email("Bybit", "[Bybit]Security Code for Your Bybit Account"):
|
|
112
|
+
date = datetime.strptime(email["date"].split(",")[1].split(" +")[0], "%d %b %Y %H:%M:%S")
|
|
113
|
+
if match := re.search(r'<span style="font-size:28pt;color:#ff9c2e">(\d{6})</span>', email["body"]):
|
|
114
|
+
code = match.group(1)
|
|
115
|
+
|
|
116
|
+
if email and date > dt and code:
|
|
117
|
+
get(f"https://account.volet.com/verify/{code}")
|
|
118
|
+
return code
|
|
119
|
+
|
|
120
|
+
await self.receive("А нет запросов от волета")
|
|
121
|
+
return None
|
|
96
122
|
|
|
97
123
|
|
|
98
124
|
async def _test():
|
|
99
125
|
from x_model import init_db
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
gmc = GmClient(
|
|
105
|
-
|
|
106
|
-
await gmc.start(True)
|
|
107
|
-
except TimeoutError as te:
|
|
108
|
-
raise te
|
|
109
|
-
finally:
|
|
110
|
-
await gmc.stop()
|
|
126
|
+
|
|
127
|
+
_ = await init_db(TORM)
|
|
128
|
+
|
|
129
|
+
gm = await Gmail.get(id=1).prefetch_related("user__username")
|
|
130
|
+
gmc = GmClient(gm)
|
|
131
|
+
await gmc.volet_confirm(amount=90, dt=datetime.now())
|
|
111
132
|
|
|
112
133
|
|
|
113
134
|
if __name__ == "__main__":
|