xync-client 0.0.155__py3-none-any.whl → 0.0.162__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/Mexc/ex.py CHANGED
@@ -6,7 +6,7 @@ import requests
6
6
  from xync_client.loader import TORM, NET_TOKEN
7
7
 
8
8
  from xync_client.Abc.Ex import BaseExClient
9
- from xync_client.Abc.xtype import PmEx, MapOfIdsList
9
+ from xync_client.Abc.xtype import PmEx, MapOfIdsList, GetAds
10
10
  from xync_client.Mexc.etype import pm, ad
11
11
 
12
12
  from xync_schema import xtype
@@ -66,26 +66,36 @@ class ExClient(BaseExClient):
66
66
  p = {cur: {c for c in coins} for cur in curs}
67
67
  return p, p
68
68
 
69
+ async def x2e_pm(self, xid: int, cur_id: int = None) -> str: # pmex.exid
70
+ if not self.pm_x2e.get(xid):
71
+ fltr = dict(pm_id=xid, ex=self.ex)
72
+ if cur_id:
73
+ cur = await models.Cur[cur_id]
74
+ pmexs = await models.PmEx.filter(**fltr)
75
+ self.pm_x2e[xid] = [pmex.exid for pmex in pmexs if pmex.name.endswith(f"({cur.ticker})")][0]
76
+ else:
77
+ self.pm_x2e[xid] = (await models.PmEx.get(**fltr)).exid
78
+ return self.pm_x2e[xid]
79
+
80
+ async def x2e_req_ads(self, xreq: GetAds) -> ad.AdsReq:
81
+ coin_id, _ = await self.x2e_coin(xreq.coin_id)
82
+ cur_id, _, __ = await self.x2e_cur(xreq.cur_id)
83
+ ereq = ad.AdsReq(
84
+ coinId=coin_id,
85
+ currency=cur_id,
86
+ tradeType="SELL" if xreq.is_sell else "BUY",
87
+ payMethod=",".join([await self.x2e_pm(pm_id, cur_id=xreq.cur_id) for pm_id in xreq.pm_ids]),
88
+ )
89
+ if xreq.amount:
90
+ ereq.amount = str(xreq.amount)
91
+ # todo: all kwargs
92
+ return ereq
93
+
69
94
  # 24: Список объяв по (buy/sell, cur, coin, pm)
70
- async def ads(
71
- self, coin_exid: str, cur_exid: str, is_sell: bool, pm_exids: list[str | int] = None, amount: int = None
95
+ async def _ads(
96
+ self, req: ad.AdsReq, lim: int = None, vm_filter: bool = False, **kwargs
72
97
  ) -> list[ad.Ad]: # {ad.id: ad}
73
- params = {
74
- "adsType": 1,
75
- "allowTrade": "false",
76
- "amount": amount or "",
77
- "blockTrade": "false",
78
- "coinId": coin_exid,
79
- # "countryCode": "",
80
- "currency": cur_exid,
81
- "follow": "false",
82
- "haveTrade": "false",
83
- "page": 1,
84
- "payMethod": ",".join(pm_exids) or "",
85
- "tradeType": "SELL" if is_sell else "BUY",
86
- }
87
-
88
- resp = requests.get("https://p2p.mexc.com/api/market", params=params)
98
+ resp = requests.get("https://p2p.mexc.com/api/market", params=req.model_dump())
89
99
  return [ad.Ad(**_ad) for _ad in resp.json()["data"]]
90
100
 
91
101
 
xync_client/Okx/1.py ADDED
@@ -0,0 +1,14 @@
1
+ import requests
2
+
3
+
4
+ headers = {
5
+ "authorization": "eyJraWQiOiIxMzYzODYiLCJhbGciOiJFUzI1NiJ9.eyJqdGkiOiJleDExMDE3NjUwMzE1NDY2MjE4NUU3RUJGMUYyRDFFRTE5MW1ka2IiLCJ1aWQiOiI2T3hmUi9aWGkrZkI0YlVUajRNLzh3PT0iLCJzdGEiOjAsIm1pZCI6IjZPeGZSL1pYaStmQjRiVVRqNE0vOHc9PSIsInBpZCI6IlBUeUE4VzA5ekZVSkJHSjZZUk5HWXc9PSIsIm5kZSI6MCwiaWF0IjoxNzY1MjA0NDE4LCJleHAiOjE3NjY0MTQwMTgsImJpZCI6MCwiZG9tIjoid3d3Lm9reC5jb20iLCJlaWQiOjE4LCJpc3MiOiJva2NvaW4iLCJkaWQiOiJ0OFlKbUFXMTNGeWZpMUZhUUhtQVFNSThNZElmYm44NnFPSnpNZFA3VVhhZFFDcFdQSkQ0NWs0ckMrMk1SWjJXIiwiZmlkIjoidDhZSm1BVzEzRnlmaTFGYVFIbUFRTUk4TWRJZmJuODZxT0p6TWRQN1VYYWRRQ3BXUEpENDVrNHJDKzJNUloyVyIsImxpZCI6IjZPeGZSL1pYaStmQjRiVVRqNE0vOHc9PSIsInVmYiI6IlBUeUE4VzA5ekZVSkJHSjZZUk5HWXc9PSIsInVwYiI6ImlCcmEyVmhOb2t5UmloeGlKLzN6RXc9PSIsImt5YyI6Miwia3lpIjoicHFHRVBYWHN6V1ZVNXhRR1NiNURYTGRUcENaS2c1LzZLbjFteGFpclpDbmovbGJBUGxqRlMwbU5qWGxlYXJ6Rjd5ZEYvWU5DR1FXLzV5aTRWQnpUM1E9PSIsImNwayI6ImhCdjNtSEZjb0lETG5TckZ6dEdTTlpMT29aU2s1bUE4SHBQcE9MOFE1TlVYamowc2w5OG9FK2dRWTRsSUdvbm5QUzJwZ0pQckw4ZGNyMlk4N3VEcjYyd0ExK1ZuRmUyUm10a2RCMFYzY3pVVEI2U09sMCtTQjdnQVJvbHdyeHMza1hvVEdua3BvWEVKamdsL2Q0bEN0aUxuUy9ydGRjY1pnZ0hpeEZhclBxWT0iLCJ2ZXIiOjEsImNsdCI6MiwidXVkIjoiV3VoZlhzRHRicG1GNXN3Q25pKzYrWVhUN2FodmJLWnRYZXZlUXNMVHI4az0iLCJzdWIiOiJBODI1QzI4OTI4QzZCRTNFMTlFREFFRUI1ODhEQTVEQiJ9.I1fFTnP7xYaK1s0jCU01NkjyBgk04dBMp7qFahIzmRUn3e34W6mSNDNSWmi-UIhxH2ORbqbhG2ErUSrrsvai3w",
6
+ }
7
+
8
+ response = requests.get("https://www.okx.com/v3/c2c/receiptAccounts", headers=headers)
9
+
10
+ itm = {
11
+ el["type"]: next((field["value"] for field in el.get("fields") if field.get("key") == "accountNo"))
12
+ for el in response.json()["data"]
13
+ }
14
+ print(itm)
@@ -0,0 +1,39 @@
1
+ from pyro_client.client.file import FileClient
2
+ from xync_bot import XyncBot
3
+
4
+ from xync_client.Abc.Agent import BaseAgentClient
5
+ from asyncio import run
6
+ from x_model import init_db
7
+ from xync_schema.models import Agent, Ex
8
+
9
+ from xync_client.Okx.ex import ExClient
10
+ from xync_client.loader import NET_TOKEN, PAY_TOKEN
11
+
12
+
13
+ class AgentClient(BaseAgentClient):
14
+ async def my_fiats(self):
15
+ response = await self._get("/v3/c2c/receiptAccounts")
16
+ fiats = response["data"]
17
+ return {
18
+ fiat["type"]: field["value"] for fiat in fiats for field in fiat["fields"] if field["key"] == "accountNo"
19
+ }
20
+
21
+
22
+ async def main():
23
+ from xync_client.loader import TORM
24
+
25
+ cn = await init_db(TORM)
26
+ ex = await Ex.get(name="Okx")
27
+ agent = await Agent.get(actor__ex=ex).prefetch_related("actor__ex", "actor__person__user__gmail")
28
+ fbot = FileClient(NET_TOKEN)
29
+ ecl: ExClient = ex.client(fbot)
30
+ abot = XyncBot(PAY_TOKEN, cn)
31
+ cl = agent.client(ecl, fbot, abot)
32
+
33
+ _fiats = await cl.my_fiats()
34
+
35
+ await cl.stop()
36
+
37
+
38
+ if __name__ == "__main__":
39
+ run(main())
xync_client/Okx/ex.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from asyncio import run
2
2
 
3
3
  from pyro_client.client.file import FileClient
4
- from pyro_client.loader import NET_TOKEN
4
+ from xync_client.loader import NET_TOKEN
5
5
  from x_model import init_db
6
6
 
7
7
  from xync_client.Abc.Exception import NoPairOnEx
@@ -20,16 +20,16 @@ class ExClient(BaseExClient):
20
20
  "quoteCurrency": cur,
21
21
  "needField": "false",
22
22
  }
23
- pms = await self._get("/v3/c2c/configs/receipt/templates", params=params)
23
+ pms = await self._get(url="/v3/c2c/configs/receipt/templates", params=params)
24
24
  return [pm.PmE(**_pm) for _pm in pms["data"] if _pm["paymentMethod"]]
25
25
 
26
26
  # 19: Список поддерживаемых валют тейкера
27
- async def curs(self) -> dict[int, xtype.CurEx]: # {cur.exid: cur.ticker}
28
- curs = await self._get("/v3/users/common/list/currencies")
29
- return {
30
- cur["displayName"]: xtype.CurEx(exid=cur["displayName"], ticker=cur["displayName"], scale=cur["precision"])
31
- for cur in curs["data"]
32
- }
27
+ # async def curs(self) -> dict[int, xtype.CurEx]: # {cur.exid: cur.ticker}
28
+ # curs = await self._get("/v3/users/common/list/currencies")
29
+ # return {
30
+ # cur["displayName"]: xtype.CurEx(exid=cur["displayName"], ticker=cur["displayName"], scale=cur["precision"])
31
+ # for cur in curs["data"]
32
+ # }
33
33
 
34
34
  # 20: Список платежных методов
35
35
  async def pms(self, cur: models.Cur = None) -> dict[int | str, PmEx]: # {pm.exid: pm}
@@ -0,0 +1,396 @@
1
+ import time
2
+ from collections import defaultdict
3
+ from copy import copy
4
+ from itertools import groupby
5
+
6
+ import undetected_chromedriver as uc
7
+ import websockets
8
+
9
+ from aiohttp import ClientResponse
10
+ from asyncio import run, sleep
11
+ from base64 import b64encode
12
+ from cryptography.hazmat.primitives import padding
13
+ from cryptography.hazmat.primitives.ciphers import Cipher
14
+ from cryptography.hazmat.primitives.ciphers.algorithms import AES
15
+ from cryptography.hazmat.primitives.ciphers.modes import CBC
16
+ from datetime import datetime, timedelta
17
+ from hashlib import sha256
18
+ from json import dumps, loads
19
+ from math import ceil
20
+ from os import urandom
21
+ from payeer_api import PayeerAPI
22
+ from re import search
23
+ from selenium.webdriver.common.by import By
24
+ from selenium.webdriver.support.ui import WebDriverWait
25
+ from selenium.webdriver.support import expected_conditions as ec
26
+ from selenium.webdriver.common.action_chains import ActionChains
27
+ from urllib.parse import urlencode
28
+ from x_client.aiohttp import Client
29
+ from xync_bot import XyncBot
30
+ from xync_schema import models
31
+
32
+ from xync_client.loader import PAY_TOKEN
33
+
34
+
35
+ def encrypt_data(data: dict, md5digest: bytes):
36
+ # Convert data to JSON string (equivalent to json_encode)
37
+ bdata = dumps(data).encode()
38
+
39
+ # Generate random IV (16 bytes for AES)
40
+ iv = urandom(16)
41
+
42
+ # Pad or truncate key to 32 bytes
43
+ if len(md5digest) < 32:
44
+ md5digest = md5digest.ljust(32, b"\0") # Pad with null bytes
45
+ elif len(md5digest) > 32:
46
+ md5digest = md5digest[:32] # Truncate to 32 bytes
47
+
48
+ # Apply PKCS7 padding
49
+ padder = padding.PKCS7(128).padder() # 128 bits = 16 bytes block size
50
+ padded_data = padder.update(bdata)
51
+ padded_data += padder.finalize()
52
+
53
+ # Create cipher
54
+ cipher = Cipher(AES(md5digest), CBC(iv))
55
+ encryptor = cipher.encryptor()
56
+
57
+ # Encrypt
58
+ ciphertext = encryptor.update(padded_data) + encryptor.finalize()
59
+
60
+ return iv + ciphertext
61
+
62
+
63
+ rep = {
64
+ "USDT": "UST",
65
+ "DOGE": "DOG",
66
+ "DASH": "DAA",
67
+ "MANA": "MA2",
68
+ "METIS": "ME2",
69
+ "AUDIO": "AU2",
70
+ "MASK": "MA3",
71
+ "SUPER": "SU2",
72
+ "USDP": "US2",
73
+ }
74
+
75
+
76
+ class PmAgentClient(Client):
77
+ host = "payeer.com/"
78
+ headers = {
79
+ "x-requested-with": "XMLHttpRequest",
80
+ "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
81
+ }
82
+ cookies = {"isAuthorized": "true"}
83
+ api: PayeerAPI
84
+ pages: dict[str, str] = {
85
+ "login": f"https://{host}en/auth/",
86
+ "home": f"https://{host}en/account/",
87
+ }
88
+ _ceils: tuple[float, float]
89
+
90
+ balances: dict[str, float] = {} # {cur: available}
91
+ orders: dict[int, dict] = defaultdict() # {(quote, cur): (price, amount)}
92
+ orders_dict: dict[str, dict[str, dict]] = defaultdict() # {(quote, cur): (price, amount)}
93
+
94
+ def __init__(self, agent: models.PmAgent, bot: XyncBot = None):
95
+ self.agent = agent
96
+ self.bot = bot
97
+ super().__init__(self.host, cookies=self.agent.state["cookies"])
98
+ # if api_id := agent.auth.get("api_id"):
99
+ # self.api = PayeerAPI(agent.auth["email"], api_id, agent.auth["api_sec"])
100
+ # if trade_id := agent.auth.get("trade_id"):
101
+ # self.tapi = PayeerTradeAPI(trade_id, agent.auth["trade_sec"])
102
+ # create_task(self.heartbeat())
103
+
104
+ async def login(self):
105
+ async def _save():
106
+ self.agent.state["cookies"] = {c["name"]: c["value"] for c in cd.get_cookies() if c["name"] == "PHPSESSID"}
107
+ self.agent.state["sessid"] = search(r"'bitrix_sessid':'([0-9a-f]{32})'", cd.page_source).group(1)
108
+ if not self.agent.auth.get("UserID"):
109
+ self.agent.auth["UserID"] = search(r"/?UserID=(\d{7,8})&", cd.page_source).group(1)
110
+ await self.agent.save()
111
+ super().__init__(self.host, cookies=self.agent.state["cookies"])
112
+
113
+ options = uc.ChromeOptions()
114
+ options.add_argument("--disable-blink-features=AutomationControlled")
115
+ options.add_argument("--no-sandbox")
116
+ options.add_argument("--disable-dev-shm-usage")
117
+ options.add_argument("--headless=new") # for Chrome >= 109
118
+ options.add_argument("--disable-renderer-backgrounding")
119
+ options.add_argument("--disable-background-timer-throttling")
120
+ options.add_argument("--disable-backgrounding-occluded-windows")
121
+ options.add_argument("--disable-client-side-phishing-detection")
122
+ options.add_argument("--disable-crash-reporter")
123
+ options.add_argument("--disable-oopr-debug-crash-dump")
124
+ options.add_argument("--no-crash-upload")
125
+ options.add_argument("--disable-gpu")
126
+ options.add_argument("--disable-extensions")
127
+ options.add_argument("--disable-low-res-tiling")
128
+ options.add_argument("--log-level=3")
129
+ options.add_argument("--silent")
130
+ options.add_argument("--window-size=1920,1080")
131
+ options.add_argument("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
132
+ cd = uc.Chrome(
133
+ options=options,
134
+ headless=False,
135
+ browser_executable_path="/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta",
136
+ )
137
+ wait = WebDriverWait(cd, timeout=15)
138
+ cd.get("https://payeer.com/en/")
139
+ wait.until(ec.invisibility_of_element_located((By.TAG_NAME, "lottie-player")))
140
+ login_link = wait.until(ec.element_to_be_clickable((By.CLASS_NAME, "button.button_empty")))
141
+ login_link.click()
142
+ email_field = wait.until(ec.presence_of_element_located((By.NAME, "email")))
143
+ email_field.send_keys(self.agent.auth.get("email"))
144
+ password_field = wait.until(ec.presence_of_element_located((By.NAME, "password")))
145
+ password_field.send_keys(self.agent.auth.get("password"))
146
+ login_button = wait.until(ec.element_to_be_clickable((By.CLASS_NAME, "login-form__login-btn.step1")))
147
+ await sleep(1)
148
+ login_button.click()
149
+ await sleep(4)
150
+ # check if logged in
151
+ if not cd.current_url.startswith(self.pages["login"]):
152
+ if cd.current_url.startswith(self.pages["home"]):
153
+ return await _save()
154
+ # we are NOT logged in
155
+ login_button.click()
156
+ await sleep(1)
157
+ if (v := cd.find_elements(By.CLASS_NAME, "form-input-top")) and v[0].text == "Введите проверочный код":
158
+ code = input("Email code: ")
159
+ actions = ActionChains(cd)
160
+ for char in code:
161
+ actions.send_keys(char).perform()
162
+ step2_button = wait.until(ec.element_to_be_clickable((By.CLASS_NAME, "login-form__login-btn.step2")))
163
+ step2_button.click()
164
+ # check if logged in
165
+ if not cd.current_url.startswith(self.pages["login"]):
166
+ if cd.current_url.startswith(self.pages["home"]):
167
+ return await _save()
168
+ # we are NOT logged in
169
+ await self.bot.send(193017646, "Payeer not logged in", photo=cd.get_screenshot_as_png())
170
+ cd.quit()
171
+ raise Exception("Payeer not logged in")
172
+
173
+ async def heartbeat(self):
174
+ """Фоновая задача для PING/PONG"""
175
+ url = "bitrix/components/payeer/account.info2/templates/top2/ajax.php"
176
+ params = {"action": "balance2", "sessid": self.agent.state["sessid"], "_": int(time.time() * 1000)}
177
+ resp = await self.session.get(url, params=params)
178
+ if not resp.content.total_bytes: # check
179
+ await self.login()
180
+ params["sessid"] = self.agent.state["sessid"]
181
+ while self.agent.active:
182
+ await sleep(30)
183
+ params["_"] = int(time.time() * 1000)
184
+ bb = await self._get("bitrix/components/payeer/account.info2/templates/top2/ajax.php", params)
185
+ avlb = {c: fb for c, b in bb["balance"].items() if (fb := float(".".join([b[0].replace(",", ""), b[1]])))}
186
+ print(avlb, end=" ")
187
+ await self.agent.refresh_from_db()
188
+ await self.stop()
189
+
190
+ async def send(self, recip: str, amount: float, cur_id: int) -> int | list:
191
+ cur = await models.Cur[cur_id]
192
+ data = {
193
+ "payout_method": 1136053,
194
+ "template_pay": "",
195
+ "param_ACCOUNT_NUMBER": recip,
196
+ "comment": "",
197
+ "protect_code": "",
198
+ "protect_day": 1,
199
+ "sum_receive": amount,
200
+ "curr_receive": cur.ticker,
201
+ "sum_pay": round(amount * 1.005, 2),
202
+ "curr_pay": cur.ticker,
203
+ "mfa_code": "",
204
+ "master_key": "",
205
+ "block": 0,
206
+ "ps": 1136053,
207
+ "sign": "",
208
+ "output_type": "list",
209
+ "fee_0": "N",
210
+ "sessid": self.agent.state["sessid"],
211
+ }
212
+ url = "bitrix/components/payeer/account.send.08_18/templates/as_add11/ajax.php?action=output"
213
+ res = await self._post(url, form_data=data)
214
+ if res["success"]:
215
+ return res["id"]
216
+ return res["error"]
217
+
218
+ async def check_in(self, amount: float, cur: str, dt: datetime = None, tid: int = None) -> dict[int, float]:
219
+ history = self.api.history(type="incoming", count=10)
220
+ if tid:
221
+ return (t := history.get(tid)) and {t["id"]: float(t["creditedAmount"])}
222
+ return {
223
+ h["id"]: float(h["creditedAmount"])
224
+ for h in history.values()
225
+ if (
226
+ amount <= float(h["creditedAmount"]) <= ceil(amount)
227
+ and h["creditedCurrency"] == cur
228
+ and datetime.fromisoformat(h["date"]) > dt - timedelta(minutes=3) # +180(tz)-5 # todo: wrong tz
229
+ )
230
+ }
231
+
232
+ async def ws(self, quote: str, fiat: str): # pair="UST_RUB"
233
+ pair = "_".join(rep.get(c, c[:2] + "1") if len(c) > 3 else c for c in [quote, fiat])
234
+ uid = self.agent.auth["UserID"]
235
+ psid = self.agent.state["cookies"]["PHPSESSID"]
236
+ ws_url = f"wss://payeer.com/wss/socket.io/?UserID={uid}&auth_hash={psid}&EIO=4&transport=websocket"
237
+ _sig = None
238
+ bids: dict[float, float] = {}
239
+ asks: dict[float, float] = {}
240
+ async with websockets.connect(ws_url) as websocket:
241
+ while resp := await websocket.recv():
242
+ if resp.startswith("0"):
243
+ await websocket.send("40")
244
+ elif resp.startswith("40"):
245
+ _sig = loads(resp.lstrip("40"))["sid"]
246
+ await websocket.send('42["pair change",{"pair":"' + pair + '"}]')
247
+ elif resp == "2": # ping?
248
+ await websocket.send("3") # pong?
249
+ elif (resp := resp.lstrip("42")) and resp.startswith("["):
250
+ data = loads(resp)
251
+ topic, data = data.pop(0), data.pop(0)
252
+ if topic == "toall":
253
+ if dif := data["data"].get("diff"): # это обновление стакана (мейкеры изменили ставки)
254
+ am_acc = 0
255
+ bid_list = [
256
+ (float(p), am, am_acc := am_acc + am)
257
+ for p, a, r, _ in dif[0]
258
+ if (am := float(a)) > 2 and am_acc < 500
259
+ ]
260
+ old_bids: dict[float, float] = copy(bids)
261
+ bids = {price: amount for price, amount, acc in bid_list}
262
+ bids_dif = {p: ad for p, a in bids.items() if round(ad := a - old_bids.get(p, 0), 2)}
263
+ am_acc = 0
264
+ ask_list = [
265
+ (float(p), am, am_acc := am_acc + am)
266
+ for p, a, r, _ in dif[1]
267
+ if (am := float(a)) > 2 and am_acc < 500
268
+ ]
269
+ old_asks: dict[float, float] = copy(asks)
270
+ asks = {price: amount for price, amount, acc in ask_list}
271
+ asks_dif = {p: ad for p, a in asks.items() if round(ad := a - old_asks.get(p, 0), 2)}
272
+
273
+ self._ceils = bid_list[0][0], ask_list[0][0]
274
+ base = round(sum(self._ceils) / 2, 3)
275
+ spread = self._ceils[0] - self._ceils[1]
276
+ pspread = spread / base * 100
277
+ print(round(base, 4), round(spread, 2), f"{pspread:.2f}%")
278
+ print(bids, bids_dif)
279
+ print(asks, asks_dif)
280
+ elif data["handler"] == "onTradeHistory":
281
+ side = int(data["data"]["history"][0][1])
282
+ hist = {
283
+ time: {float(h[2]): float(h[3]) for h in g}
284
+ for time, g in groupby(data["data"]["history"], key=lambda x: x[0])
285
+ }
286
+ print(side, hist)
287
+ # (asks if side else bids)
288
+ else:
289
+ raise ValueError(resp)
290
+
291
+ async def get_ceils(self) -> tuple[float, float]: # inside ceils + fee
292
+ return self._ceils[0] * (1 + self.agent.pm.fee), self._ceils[1] * (1 - self.agent.pm.fee)
293
+
294
+ @staticmethod
295
+ def form_redirect(topup: models.TopUp) -> tuple[str, dict | None]:
296
+ m_shop = str(topup.topupable.auth["id"])
297
+ m_orderid = str(topup.id)
298
+ m_amount = "{0:.2f}".format(topup.amount * 0.01)
299
+ m_curr = topup.cur.ticker
300
+ m_desc = b64encode(b"XyncPay top up").decode()
301
+ m_key = topup.topupable.auth["sec"]
302
+ data = [m_shop, m_orderid, m_amount, m_curr, m_desc]
303
+ # # additional
304
+ # m_params = {
305
+ # 'success_url': 'https://xync.net/topup?success=1',
306
+ # 'fail_url': 'https://xync.net/topup?success=0',
307
+ # 'status_url': 'https://xync.net/topup',
308
+ # 'reference': {'var1': '1'},
309
+ # }
310
+ #
311
+ # key = md5(m_orderid.to_bytes()).digest()
312
+ #
313
+ # base64url_encode(encrypt_data(params, key))
314
+ #
315
+ # data.append(m_params)
316
+ # # additional
317
+
318
+ data.append(m_key)
319
+
320
+ sign = sha256(":".join(data).encode()).hexdigest().upper()
321
+
322
+ params = {
323
+ "m_shop": m_shop,
324
+ "m_orderid": m_orderid,
325
+ "m_amount": m_amount,
326
+ "m_curr": m_curr,
327
+ "m_desc": m_desc,
328
+ "m_sign": sign,
329
+ # 'm_params': m_params,
330
+ # 'm_cipher_method': 'AES-256-CBC-IV',
331
+ "form[ps]": "2609",
332
+ "form[curr[2609]]": m_curr,
333
+ }
334
+ url = "https://payeer.com/merchant/?" + urlencode(params)
335
+ return url, None
336
+
337
+ def get_topup(self, tid: str) -> dict:
338
+ hi = self.api.get_history_info(tid)
339
+ ti = self.api.shop_order_info(hi["params"]["SHOP_ID"], hi["params"]["ORDER_ID"])["info"]
340
+ return ti["status"] == "execute" and {
341
+ "pmid": ti["id"],
342
+ "from_acc": hi["params"]["ACCOUNT_NUMBER"],
343
+ "oid": hi["params"]["ORDER_ID"],
344
+ "amount": int(float(ti["sumOut"]) * 100),
345
+ "ts": datetime.strptime(ti["dateCreate"], "%d.%m.%Y %H:%M:%S") - timedelta(hours=3),
346
+ }
347
+
348
+ async def _proc(self, resp: ClientResponse, bp=None) -> dict | str:
349
+ if resp.status == 200:
350
+ await resp.read()
351
+ # noinspection PyProtectedMember
352
+ return loads(resp._body.strip()) # payeer bug: returns json with content-type=html
353
+ raise Exception(resp)
354
+
355
+ async def balance_load(self):
356
+ self.balances = await self.tapi.acc()
357
+
358
+ async def orders_load(self):
359
+ self.orders = {
360
+ int(oid): {
361
+ "ts": o["date"],
362
+ "pair": o["pair"].split("_"),
363
+ "is_sell": o["action"] == "sell",
364
+ "amount": float(o["amount"]),
365
+ "price": float(o["price"]),
366
+ "value": float(o["value"]),
367
+ "amount_proc": float(o["amount_processed"]),
368
+ "value_proc": float(o["value_processed"]),
369
+ }
370
+ for oid, o in (await self.tapi.orders()).items()
371
+ }
372
+
373
+
374
+ async def main():
375
+ from x_model import init_db
376
+ from xync_client.loader import TORM
377
+
378
+ cn = await init_db(TORM, True)
379
+ agent = await models.PmAgent.get_or_none(pm__norm="payeer", user_id=1).prefetch_related("user", "pm")
380
+ # tapi = PayeerTradeAPI(agent.auth['trade_id'], agent.auth['trade_sec'])
381
+ # b = await tapi.acc()
382
+ # rub_am, usdt_am = b['RUB']['available'], b['USDT']['available']
383
+ # r = await tapi.trade('USDT_RUB', False, 10, 118.01)
384
+ bbot = XyncBot(PAY_TOKEN, cn)
385
+ cl: PmAgentClient = agent.client(bbot)
386
+ await cl.ws("USDT", "RUB")
387
+
388
+ while agent.active:
389
+ await sleep(75)
390
+ await cl.send("P1135398755", 10, 1)
391
+ await agent.refresh_from_db()
392
+ await cl.stop()
393
+
394
+
395
+ if __name__ == "__main__":
396
+ run(main())
@@ -1,64 +1,2 @@
1
- from selenium.webdriver.common.by import By
2
- import undetected_chromedriver as uc
3
- from xync_schema.models import PmAgent
4
- from selenium.webdriver.support.ui import WebDriverWait
5
- from selenium.webdriver.support import expected_conditions as EC
6
- from selenium.webdriver.common.action_chains import ActionChains
7
- import time
1
+ # login_url = "https://payeer.com/bitrix/components/auth/system.auth.authorize/templates/.default/ajax.php"
8
2
 
9
-
10
- async def login(agent: PmAgent):
11
- options = uc.ChromeOptions()
12
- options.add_argument("--disable-blink-features=AutomationControlled")
13
- options.add_argument("--no-sandbox")
14
- options.add_argument("--disable-dev-shm-usage")
15
- # options.add_argument("--headless=new") # for Chrome >= 109
16
- options.add_argument("--disable-renderer-backgrounding")
17
- options.add_argument("--disable-background-timer-throttling")
18
- options.add_argument("--disable-backgrounding-occluded-windows")
19
- options.add_argument("--disable-client-side-phishing-detection")
20
- options.add_argument("--disable-crash-reporter")
21
- options.add_argument("--disable-oopr-debug-crash-dump")
22
- options.add_argument("--no-crash-upload")
23
- options.add_argument("--disable-gpu")
24
- options.add_argument("--disable-extensions")
25
- options.add_argument("--disable-low-res-tiling")
26
- options.add_argument("--log-level=3")
27
- options.add_argument("--silent")
28
- options.add_argument("--window-size=1920,1080")
29
- options.add_argument("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
30
-
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
- )
36
- wait = WebDriverWait(driver, timeout=10)
37
- try:
38
- driver.get("https://payeer.com/en/auth")
39
- wait.until(EC.invisibility_of_element_located((By.TAG_NAME, "lottie-player")))
40
- login_link = wait.until(EC.element_to_be_clickable((By.CLASS_NAME, "button.button_empty")))
41
- login_link.click()
42
- email_field = wait.until(EC.presence_of_element_located((By.NAME, "email")))
43
- email_field.send_keys(agent.auth.get("email"))
44
- password_field = wait.until(EC.presence_of_element_located((By.NAME, "password")))
45
- password_field.send_keys(agent.auth.get("password"))
46
- login_button = wait.until(EC.element_to_be_clickable((By.CLASS_NAME, "login-form__login-btn.step1")))
47
- login_button.click()
48
- time.sleep(4)
49
- try:
50
- login_button.click()
51
- except Exception:
52
- pass
53
- time.sleep(1)
54
- if (v := driver.find_elements(By.CLASS_NAME, "form-input-top")) and v[0].text == "Введите проверочный код":
55
- code = input("Email code: ")
56
- actions = ActionChains(driver)
57
- for char in code:
58
- actions.send_keys(char).perform()
59
- step2_button = wait.until(EC.element_to_be_clickable((By.CLASS_NAME, "login-form__login-btn.step2")))
60
- step2_button.click()
61
- agent.state = {"cookies": driver.get_cookies()}
62
- await agent.save()
63
- finally:
64
- driver.quit()
@@ -0,0 +1,58 @@
1
+ import hashlib
2
+ import hmac
3
+
4
+ from json import dumps
5
+ from time import time
6
+
7
+ from x_client.aiohttp import Client
8
+
9
+ maker_fee = 0.01
10
+ taker_fee = 0.095
11
+
12
+
13
+ class PayeerTradeAPI(Client):
14
+ sec: bytes
15
+
16
+ def __init__(self, key: str, sec: str):
17
+ self.sec = sec.encode()
18
+ super().__init__("payeer.com/", headers={"API-ID": key})
19
+
20
+ async def _req(self, method: str, data: dict):
21
+ h = hmac.new(self.sec, digestmod=hashlib.sha256)
22
+ data["ts"] = int(time() * 1000)
23
+ sdata = method + dumps(data)
24
+ h.update(sdata.encode())
25
+ sign = h.hexdigest()
26
+ return await self._post("api/trade/" + method, data, hdrs={"API-SIGN": sign})
27
+
28
+ async def acc(self) -> dict[str, dict[str, float]]:
29
+ res = await self._req("account", {})
30
+ return res["success"] and {c: b for c, b in res["balances"].items() if b["total"]}
31
+
32
+ async def orders(self) -> dict[str, dict]:
33
+ res = await self._req("my_orders", {})
34
+ return res["success"] and res["items"]
35
+
36
+ async def pairs(self) -> dict:
37
+ res = await self._get("api/trade/info")
38
+ return res["success"] and {
39
+ p: {
40
+ k: v for k, v in d.items() if k in ("price_prec", "min_price", "max_price", "amount_prec", "value_prec")
41
+ }
42
+ for p, d in res["pairs"].items()
43
+ }
44
+
45
+ async def trade(self, pair: str, sell: bool, amount: float, price: float) -> tuple[int, int]:
46
+ data = {
47
+ "pair": pair,
48
+ "type": "limit",
49
+ "action": "sell" if sell else "buy",
50
+ "price": price,
51
+ "amount": amount,
52
+ }
53
+ res = await self._req("order_create", data)
54
+ return res["success"] and (res["order_id"], int(time()) + 60)
55
+
56
+ async def cancel(self, oid: int) -> bool:
57
+ res = await self._req("order_cancel", {"order_id": oid})
58
+ return res["success"]