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.
@@ -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, field_serializer
4
- from xync_schema import xtype
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 # 0 - покупка, 1 - продажа (для мейкера, т.е КАКАЯ объява)
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
- # class Ad(BaseAd):
132
- # accountId: str = None # for initial actualize
133
- # authStatus: int = None # for initial actualize
134
- # authTag: List[str] = None # for initial actualize
135
- # ban: bool = None # for initial actualize
136
- # baned: bool = None # for initial actualize
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):
@@ -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 PaymentConfigVo(BaseModel):
19
- paymentType: str
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: Literal[0, 1] # Non-balance coin purchase (0 Offline), balance coin purchase (1 Online)
26
- items: list[PaymentItem]
36
+ online: int
37
+ items: list[dict[str, str | bool]]
27
38
 
28
39
 
29
- class CredEpyd(CredExOut):
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
@@ -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
- price: str # float
67
- curPrice: str
68
- totalAmount: float
69
- minAmount: float
70
- maxAmount: float
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: datetime
81
- itemPriceAvailableTime: datetime
82
- itemPriceValidTime: int # 45000
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 PaymentConfigVo(BaseModel):
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
- headers = {"cookie": ";"} # rewrite token for public methods
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 = await Agent.get(actor__ex=self.ex).prefetch_related("actor")
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, ...
@@ -1,113 +1,134 @@
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)
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
- class GmClient:
10
- uid: int
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 __init__(self, uid: int):
17
- self.uid = uid
44
+ def _get_last_email(self, sender_email, subject_keyword=None):
45
+ """
46
+ Получить последнее письмо от определенного отправителя
18
47
 
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:")
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",
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 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
- 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()
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__":