xync-client 0.0.141__py3-none-any.whl → 0.0.156.dev18__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.
Files changed (40) hide show
  1. xync_client/Abc/AdLoader.py +5 -0
  2. xync_client/Abc/Agent.py +354 -8
  3. xync_client/Abc/Ex.py +432 -25
  4. xync_client/Abc/HasAbotUid.py +10 -0
  5. xync_client/Abc/InAgent.py +0 -11
  6. xync_client/Abc/PmAgent.py +34 -26
  7. xync_client/Abc/xtype.py +57 -3
  8. xync_client/Bybit/InAgent.py +233 -409
  9. xync_client/Bybit/agent.py +844 -777
  10. xync_client/Bybit/etype/__init__.py +0 -0
  11. xync_client/Bybit/etype/ad.py +54 -86
  12. xync_client/Bybit/etype/cred.py +29 -9
  13. xync_client/Bybit/etype/order.py +75 -103
  14. xync_client/Bybit/ex.py +35 -48
  15. xync_client/Gmail/__init__.py +119 -98
  16. xync_client/Htx/agent.py +213 -40
  17. xync_client/Htx/etype/ad.py +40 -16
  18. xync_client/Htx/etype/order.py +194 -0
  19. xync_client/Htx/ex.py +17 -19
  20. xync_client/Mexc/agent.py +268 -0
  21. xync_client/Mexc/api.py +1255 -0
  22. xync_client/Mexc/etype/ad.py +52 -1
  23. xync_client/Mexc/etype/order.py +354 -0
  24. xync_client/Mexc/ex.py +34 -22
  25. xync_client/Okx/1.py +14 -0
  26. xync_client/Okx/agent.py +39 -0
  27. xync_client/Okx/ex.py +8 -8
  28. xync_client/Pms/Payeer/agent.py +396 -0
  29. xync_client/Pms/Payeer/login.py +1 -59
  30. xync_client/Pms/Payeer/trade.py +58 -0
  31. xync_client/Pms/Volet/__init__.py +82 -63
  32. xync_client/Pms/Volet/api.py +5 -4
  33. xync_client/loader.py +2 -0
  34. xync_client/pm_unifier.py +1 -1
  35. {xync_client-0.0.141.dist-info → xync_client-0.0.156.dev18.dist-info}/METADATA +5 -1
  36. {xync_client-0.0.141.dist-info → xync_client-0.0.156.dev18.dist-info}/RECORD +38 -29
  37. xync_client/Pms/Payeer/__init__.py +0 -253
  38. xync_client/Pms/Payeer/api.py +0 -25
  39. {xync_client-0.0.141.dist-info → xync_client-0.0.156.dev18.dist-info}/WHEEL +0 -0
  40. {xync_client-0.0.141.dist-info → xync_client-0.0.156.dev18.dist-info}/top_level.txt +0 -0
@@ -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,60 +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(options=options, headless=True)
32
- wait = WebDriverWait(driver, timeout=10)
33
- try:
34
- driver.get("https://payeer.com/en/auth")
35
- wait.until(EC.invisibility_of_element_located((By.TAG_NAME, "lottie-player")))
36
- login_link = wait.until(EC.element_to_be_clickable((By.CLASS_NAME, "button.button_empty")))
37
- login_link.click()
38
- email_field = wait.until(EC.presence_of_element_located((By.NAME, "email")))
39
- email_field.send_keys(agent.auth.get("email"))
40
- password_field = wait.until(EC.presence_of_element_located((By.NAME, "password")))
41
- password_field.send_keys(agent.auth.get("password"))
42
- login_button = wait.until(EC.element_to_be_clickable((By.CLASS_NAME, "login-form__login-btn.step1")))
43
- login_button.click()
44
- time.sleep(4)
45
- try:
46
- login_button.click()
47
- except Exception:
48
- pass
49
- time.sleep(1)
50
- if (v := driver.find_elements(By.CLASS_NAME, "form-input-top")) and v[0].text == "Введите проверочный код":
51
- code = input("Email code: ")
52
- actions = ActionChains(driver)
53
- for char in code:
54
- actions.send_keys(char).perform()
55
- step2_button = wait.until(EC.element_to_be_clickable((By.CLASS_NAME, "login-form__login-btn.step2")))
56
- step2_button.click()
57
- agent.state = {"cookies": driver.get_cookies()}
58
- await agent.save()
59
- finally:
60
- 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"]