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/AdLoader.py +299 -0
- xync_client/Abc/Agent.py +94 -10
- xync_client/Abc/Ex.py +27 -22
- xync_client/Abc/HasAbotUid.py +10 -0
- xync_client/Abc/InAgent.py +0 -11
- xync_client/Abc/PmAgent.py +34 -26
- xync_client/Abc/xtype.py +24 -2
- xync_client/Bybit/InAgent.py +122 -87
- xync_client/Bybit/agent.py +477 -542
- xync_client/Bybit/etype/ad.py +11 -56
- xync_client/Bybit/etype/cred.py +29 -9
- xync_client/Bybit/etype/order.py +42 -55
- xync_client/Bybit/ex.py +15 -2
- xync_client/Gmail/__init__.py +119 -98
- xync_client/Htx/agent.py +162 -31
- xync_client/Htx/etype/ad.py +18 -11
- xync_client/Htx/ex.py +7 -9
- xync_client/Mexc/agent.py +85 -0
- xync_client/Mexc/api.py +636 -0
- xync_client/Mexc/etype/order.py +639 -0
- xync_client/Mexc/ex.py +10 -8
- xync_client/Pms/Payeer/__init__.py +38 -29
- xync_client/Pms/Payeer/login.py +6 -2
- xync_client/Pms/Volet/__init__.py +82 -63
- xync_client/Pms/Volet/api.py +5 -4
- xync_client/loader.py +1 -0
- xync_client/pm_unifier.py +1 -1
- {xync_client-0.0.141.dist-info → xync_client-0.0.155.dist-info}/METADATA +4 -1
- {xync_client-0.0.141.dist-info → xync_client-0.0.155.dist-info}/RECORD +31 -26
- {xync_client-0.0.141.dist-info → xync_client-0.0.155.dist-info}/WHEEL +0 -0
- {xync_client-0.0.141.dist-info → xync_client-0.0.155.dist-info}/top_level.txt +0 -0
xync_client/Bybit/etype/ad.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from enum import StrEnum
|
|
2
2
|
from typing import List, Optional, Any, Literal
|
|
3
|
-
from pydantic import BaseModel, Field
|
|
4
|
-
from
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
from xync_client.Bybit.etype.cred import MyPaymentTerm
|
|
5
5
|
from xync_schema.xtype import BaseAd
|
|
6
6
|
|
|
7
7
|
from xync_client.Abc.xtype import BaseAdUpdate
|
|
@@ -113,7 +113,7 @@ class Ad(BaseAd):
|
|
|
113
113
|
recommend: bool = None # for initial actualize
|
|
114
114
|
recommendTag: str = None # for initial actualize
|
|
115
115
|
remark: str = Field(serialization_alias="auto_msg")
|
|
116
|
-
side: Literal[0, 1] = None # for initial actualize
|
|
116
|
+
side: Literal[0, 1] = None # for initial actualize # 0 - покупка, 1 - продажа (для мейкера, т.е КАКАЯ объява)
|
|
117
117
|
status: Literal[10, 20, 30] # 10: online; 20: offline; 30: completed
|
|
118
118
|
symbolInfo: SymbolInfo = None # for initial actualize
|
|
119
119
|
tokenId: str = None # for initial actualize
|
|
@@ -126,59 +126,14 @@ class Ad(BaseAd):
|
|
|
126
126
|
verificationOrderLabels: List[Any] = None # for initial actualize
|
|
127
127
|
verificationOrderSwitch: bool = None # for initial actualize
|
|
128
128
|
version: int = None # for initial actualize
|
|
129
|
-
|
|
130
|
-
#
|
|
131
|
-
#
|
|
132
|
-
#
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
# blocked: str = None # for initial actualize
|
|
138
|
-
# createDate: str = None # for initial actualize
|
|
139
|
-
# currencyId: str = None # for initial actualize
|
|
140
|
-
# executedQuantity: str = None # for initial actualize
|
|
141
|
-
# fee: str = None # for initial actualize
|
|
142
|
-
# finishNum: int = None # for initial actualize
|
|
143
|
-
# frozenQuantity: str = None # for initial actualize
|
|
144
|
-
# exid: str = Field(serialization_alias="id")
|
|
145
|
-
# isOnline: bool = None # for initial actualize
|
|
146
|
-
# itemType: str = None # for initial actualize
|
|
147
|
-
# lastLogoutTime: str = None # for initial actualize
|
|
148
|
-
# quantity: str = Field(serialization_alias="lastQuantity")
|
|
149
|
-
# makerContact: bool = None # for initial actualize
|
|
150
|
-
# max_fiat: str = Field(serialization_alias="maxAmount")
|
|
151
|
-
# min_fiat: str = Field(serialization_alias="minAmount")
|
|
152
|
-
# nickName: str = None # for initial actualize
|
|
153
|
-
# orderNum: int = None # for initial actualize
|
|
154
|
-
# paymentPeriod: int = None # for initial actualize
|
|
155
|
-
# payments: List[str] = None # for initial actualize
|
|
156
|
-
# premium: str = None # for initial actualize
|
|
157
|
-
# price: str = None # for initial actualize
|
|
158
|
-
# priceType: Literal[0, 1] = None # for initial actualize # 0 - fix rate, 1 - floating
|
|
159
|
-
# allQuantity: str = Field(serialization_alias="quantity") # for initial actualize
|
|
160
|
-
# recentExecuteRate: int = None # for initial actualize
|
|
161
|
-
# recentOrderNum: int = None # for initial actualize
|
|
162
|
-
# recommend: bool = None # for initial actualize
|
|
163
|
-
# recommendTag: str = None # for initial actualize
|
|
164
|
-
# auto_msg: str = Field(serialization_alias="remark")
|
|
165
|
-
# is_sell: Literal[0, 1] = Field(serialization_alias="side") # for initial actualize # 0 - покупка, 1 - продажа (для мейкера, т.е КАКАЯ объява)
|
|
166
|
-
# status: Literal[10, 20, 30] # 10: online; 20: offline; 30: completed
|
|
167
|
-
# symbolInfo: SymbolInfo = None # for initial actualize
|
|
168
|
-
# tokenId: str = None # for initial actualize
|
|
169
|
-
# tokenName: str = None # for initial actualize
|
|
170
|
-
# tradingPreferenceSet: TradingPreferenceSet | None = None # for initial actualize
|
|
171
|
-
# userId: str = Field(serialization_alias="maker__exid")
|
|
172
|
-
# userMaskId: str = None # for initial actualize
|
|
173
|
-
# userType: str = None # for initial actualize
|
|
174
|
-
# verificationOrderAmount: str = None # for initial actualize
|
|
175
|
-
# verificationOrderLabels: List[Any] = None # for initial actualize
|
|
176
|
-
# verificationOrderSwitch: bool = None # for initial actualize
|
|
177
|
-
# version: int = None # for initial actualize
|
|
178
|
-
|
|
179
|
-
@field_serializer("status")
|
|
180
|
-
def status(self, status, _info) -> xtype.AdStatus:
|
|
181
|
-
return {10: xtype.AdStatus.active, 20: xtype.AdStatus.defActive, 30: xtype.AdStatus.soldOut}[status]
|
|
129
|
+
|
|
130
|
+
# @field_serializer("status")
|
|
131
|
+
# def status(self, status, _info) -> xtype.AdStatus:
|
|
132
|
+
# return {10: xtype.AdStatus.active, 20: xtype.AdStatus.defActive, 30: xtype.AdStatus.soldOut}[status]
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class MyAd(Ad):
|
|
136
|
+
paymentTerms: List[MyPaymentTerm]
|
|
182
137
|
|
|
183
138
|
|
|
184
139
|
class AdPostRequest(BaseModel):
|
xync_client/Bybit/etype/cred.py
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from typing import Literal
|
|
2
|
-
|
|
3
1
|
from pydantic import BaseModel
|
|
4
2
|
|
|
5
3
|
from xync_client.Abc.xtype import CredExOut
|
|
@@ -15,18 +13,31 @@ class PaymentItem(BaseModel):
|
|
|
15
13
|
required: bool
|
|
16
14
|
|
|
17
15
|
|
|
18
|
-
class
|
|
19
|
-
paymentType:
|
|
16
|
+
class BasePaymentConf(BaseModel):
|
|
17
|
+
paymentType: int
|
|
18
|
+
paymentName: str
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PaymentConfig(BasePaymentConf):
|
|
22
|
+
class PaymentTemplateItem(BaseModel):
|
|
23
|
+
labelDialect: str
|
|
24
|
+
placeholderDialect: str
|
|
25
|
+
fieldName: str
|
|
26
|
+
|
|
27
|
+
paymentDialect: str
|
|
28
|
+
paymentTemplateItem: list[PaymentTemplateItem]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class PaymentConfigVo(BasePaymentConf):
|
|
20
32
|
checkType: int
|
|
21
33
|
sort: int
|
|
22
|
-
paymentName: str
|
|
23
34
|
addTips: str
|
|
24
35
|
itemTips: str
|
|
25
|
-
online:
|
|
26
|
-
items: list[
|
|
36
|
+
online: int
|
|
37
|
+
items: list[dict[str, str | bool]]
|
|
27
38
|
|
|
28
39
|
|
|
29
|
-
class
|
|
40
|
+
class PaymentTerm(CredExOut):
|
|
30
41
|
id: str # int
|
|
31
42
|
realName: str
|
|
32
43
|
paymentType: int # int
|
|
@@ -44,7 +55,7 @@ class CredEpyd(CredExOut):
|
|
|
44
55
|
mobile: str
|
|
45
56
|
businessName: str
|
|
46
57
|
concept: str
|
|
47
|
-
online: str
|
|
58
|
+
online: str = None
|
|
48
59
|
paymentExt1: str
|
|
49
60
|
paymentExt2: str
|
|
50
61
|
paymentExt3: str
|
|
@@ -54,6 +65,15 @@ class CredEpyd(CredExOut):
|
|
|
54
65
|
paymentTemplateVersion: int
|
|
55
66
|
|
|
56
67
|
|
|
68
|
+
class MyPaymentTerm(PaymentTerm):
|
|
69
|
+
paymentConfig: PaymentConfig
|
|
70
|
+
realNameVerified: bool
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class CredEpyd(PaymentTerm):
|
|
74
|
+
securityRiskToken: str = ""
|
|
75
|
+
|
|
76
|
+
|
|
57
77
|
class MyCredEpyd(CredEpyd): # todo: заменить везде где надо CredEpyd -> MyCredEpyd
|
|
58
78
|
countNo: str
|
|
59
79
|
hasPaymentTemplateChanged: bool
|
xync_client/Bybit/etype/order.py
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
from datetime import datetime
|
|
2
1
|
from enum import IntEnum
|
|
3
2
|
from typing import Literal
|
|
4
3
|
|
|
5
4
|
from pydantic import BaseModel
|
|
6
5
|
|
|
7
|
-
from xync_client.Bybit.etype.cred import CredEpyd
|
|
6
|
+
from xync_client.Bybit.etype.cred import CredEpyd, PaymentTerm as CredPaymentTerm, PaymentConfigVo
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
class Topic(IntEnum):
|
|
@@ -45,6 +44,17 @@ 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
|
+
is_sell: bool
|
|
51
|
+
pm_id: int
|
|
52
|
+
coin_id: int
|
|
53
|
+
cur_id: int
|
|
54
|
+
quantity: float | None = None
|
|
55
|
+
price: float | None = None
|
|
56
|
+
|
|
57
|
+
|
|
48
58
|
class OrderRequest(BaseModel):
|
|
49
59
|
class Side(IntEnum):
|
|
50
60
|
BUY = 0
|
|
@@ -60,33 +70,46 @@ class OrderRequest(BaseModel):
|
|
|
60
70
|
flag: Literal["amount", "quantity"]
|
|
61
71
|
version: str = "1.0"
|
|
62
72
|
securityRiskToken: str = ""
|
|
73
|
+
isFromAi: bool = False
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class OrderSellRequest(OrderRequest):
|
|
77
|
+
paymentId: str
|
|
78
|
+
paymentType: str
|
|
63
79
|
|
|
64
80
|
|
|
65
81
|
class PreOrderResp(BaseModel):
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
minQuantity: float
|
|
72
|
-
maxQuantity: float
|
|
73
|
-
payments: list[str] # list[int]
|
|
74
|
-
status: Literal[10, 20]
|
|
75
|
-
paymentTerms: list
|
|
76
|
-
paymentPeriod: Literal[15]
|
|
77
|
-
lastQuantity: float
|
|
78
|
-
lastPrice: float
|
|
82
|
+
id: str # bigint
|
|
83
|
+
price: str # float .cur.scale
|
|
84
|
+
lastQuantity: str # float .coin.scale
|
|
85
|
+
curPrice: str # hex 32
|
|
86
|
+
lastPrice: str # float .cur.scale # future
|
|
79
87
|
isOnline: bool
|
|
80
|
-
lastLogoutTime:
|
|
81
|
-
|
|
82
|
-
|
|
88
|
+
lastLogoutTime: str # timestamp(0)+0
|
|
89
|
+
payments: list[str] # list[int]
|
|
90
|
+
status: Literal[10, 15, 20]
|
|
91
|
+
paymentTerms: list # empty
|
|
92
|
+
paymentPeriod: Literal[15, 30, 60]
|
|
93
|
+
totalAmount: str # float .cur.scale
|
|
94
|
+
minAmount: str # float .cur.scale
|
|
95
|
+
maxAmount: str # float .cur.scale
|
|
96
|
+
minQuantity: str # float .coin.scale
|
|
97
|
+
maxQuantity: str # float .coin.scale
|
|
98
|
+
itemPriceAvailableTime: str # timestamp(0)+0
|
|
99
|
+
itemPriceValidTime: Literal["45000"]
|
|
83
100
|
itemType: Literal["ORIGIN"]
|
|
101
|
+
shareItem: bool # False
|
|
84
102
|
|
|
85
103
|
|
|
86
104
|
class OrderResp(BaseModel):
|
|
87
105
|
orderId: str
|
|
88
106
|
isNeedConfirm: bool
|
|
107
|
+
confirmId: str = ""
|
|
89
108
|
success: bool
|
|
109
|
+
securityRiskToken: str = ""
|
|
110
|
+
riskTokenType: Literal["challenge", ""] = ""
|
|
111
|
+
riskVersion: Literal["1", "2", ""] = ""
|
|
112
|
+
needSecurityRisk: bool
|
|
90
113
|
isBulkOrder: bool
|
|
91
114
|
confirmed: str = None
|
|
92
115
|
delayTime: str
|
|
@@ -121,43 +144,7 @@ class AppraiseInfo(BaseModel):
|
|
|
121
144
|
updateDate: str
|
|
122
145
|
|
|
123
146
|
|
|
124
|
-
class
|
|
125
|
-
paymentType: str
|
|
126
|
-
checkType: int
|
|
127
|
-
sort: int
|
|
128
|
-
paymentName: str
|
|
129
|
-
addTips: str
|
|
130
|
-
itemTips: str
|
|
131
|
-
online: int
|
|
132
|
-
items: list[dict[str, str | bool]]
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
class PaymentTerm(BaseModel):
|
|
136
|
-
id: str
|
|
137
|
-
realName: str
|
|
138
|
-
paymentType: int
|
|
139
|
-
bankName: str
|
|
140
|
-
branchName: str
|
|
141
|
-
accountNo: str
|
|
142
|
-
qrcode: str
|
|
143
|
-
visible: int
|
|
144
|
-
payMessage: str
|
|
145
|
-
firstName: str
|
|
146
|
-
lastName: str
|
|
147
|
-
secondLastName: str
|
|
148
|
-
clabe: str
|
|
149
|
-
debitCardNumber: str
|
|
150
|
-
mobile: str
|
|
151
|
-
businessName: str
|
|
152
|
-
concept: str
|
|
153
|
-
online: str
|
|
154
|
-
paymentExt1: str
|
|
155
|
-
paymentExt2: str
|
|
156
|
-
paymentExt3: str
|
|
157
|
-
paymentExt4: str
|
|
158
|
-
paymentExt5: str
|
|
159
|
-
paymentExt6: str
|
|
160
|
-
paymentTemplateVersion: int
|
|
147
|
+
class PaymentTerm(CredPaymentTerm):
|
|
161
148
|
paymentConfigVo: PaymentConfigVo
|
|
162
149
|
ruPaymentPrompt: bool
|
|
163
150
|
|
xync_client/Bybit/ex.py
CHANGED
|
@@ -2,8 +2,10 @@ import json
|
|
|
2
2
|
from asyncio import run
|
|
3
3
|
|
|
4
4
|
from pyro_client.client.file import FileClient
|
|
5
|
+
from x_client import df_hdrs
|
|
5
6
|
from x_model import init_db
|
|
6
7
|
from xync_schema import models, xtype
|
|
8
|
+
from xync_schema.enums import AdStatus, AgentStatus
|
|
7
9
|
from xync_schema.models import Ex, Agent
|
|
8
10
|
|
|
9
11
|
from xync_client.Abc.Ex import BaseExClient
|
|
@@ -14,14 +16,25 @@ from xync_client.loader import TORM
|
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
class ExClient(BaseExClient): # Bybit client
|
|
17
|
-
|
|
19
|
+
host = "api2.bybit.com"
|
|
20
|
+
headers = df_hdrs # rewrite token for public methods
|
|
18
21
|
agent: Agent = None
|
|
19
22
|
|
|
20
23
|
async def _get_auth_cks(self) -> dict[str, str]:
|
|
21
24
|
if not self.agent:
|
|
22
|
-
self.agent =
|
|
25
|
+
self.agent = (
|
|
26
|
+
await Agent.filter(actor__ex=self.ex, status__gt=AgentStatus.off).prefetch_related("actor").first()
|
|
27
|
+
)
|
|
23
28
|
return self.agent.auth["cookies"]
|
|
24
29
|
|
|
30
|
+
@staticmethod
|
|
31
|
+
def ad_status(status: int) -> AdStatus:
|
|
32
|
+
return {
|
|
33
|
+
10: AdStatus.active,
|
|
34
|
+
20: AdStatus.defActive,
|
|
35
|
+
30: AdStatus.soldOut,
|
|
36
|
+
}[status]
|
|
37
|
+
|
|
25
38
|
async def _get_config(self):
|
|
26
39
|
resp = await self._get("/fiat/p2p/config/initial")
|
|
27
40
|
return resp["result"] # todo: tokens, pairs, ...
|
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",
|
|
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__":
|