Python-3xui 0.0.2__tar.gz → 0.0.4__tar.gz

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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Python-3xui
3
- Version: 0.0.2
3
+ Version: 0.0.4
4
4
  Summary: 3x-ui wrapper for python
5
5
  Project-URL: Homepage, https://github.com/Artem-Potapov/3x-py
6
6
  Project-URL: Issues, https://github.com/Artem-Potapov/3x-py/issues
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "Python-3xui"
3
- version = "0.0.2"
3
+ version = "0.0.4"
4
4
  authors = [
5
5
  { name="JustMe_001", email="justme001.causation755@passinbox.com" },
6
6
  ]
@@ -1,7 +1,9 @@
1
+ import time
1
2
  from collections.abc import Sequence, Mapping
2
3
  from typing import Self, Optional, Dict, Iterable, AsyncIterable, Type, Union, Any, List, Tuple, Literal
3
4
  from datetime import datetime, UTC
4
5
 
6
+ import pyotp
5
7
  from httpx import Response, AsyncClient
6
8
  from async_lru import alru_cache
7
9
  import asyncio
@@ -73,6 +75,7 @@ class XUIClient:
73
75
  session_duration: Maximum session duration in seconds. Defaults to 3600.
74
76
  """
75
77
  from . import endpoints # look, I know it's bad, but we need to evade cyclical imports
78
+ self.connected: bool = False
76
79
  self.PROD_STRING = "tester-777"
77
80
  self.session: AsyncClient | None = None
78
81
  self.base_host: str = base_website
@@ -83,13 +86,23 @@ class XUIClient:
83
86
  self.session_duration: int = session_duration
84
87
  self.xui_username: str | None = username
85
88
  self.xui_password: str | None = password
86
- self.two_fac_code: str | None = two_fac_code
89
+ self.two_fac_secret: str | None = two_fac_code
90
+ self.totp: pyotp.TOTP | None = None
87
91
  self.max_retries: int = 5
88
92
  self.retry_delay: int = 1
89
93
  # endpoints
90
94
  self.server_end = endpoints.Server(self)
91
95
  self.clients_end = endpoints.Clients(self)
92
96
  self.inbounds_end = endpoints.Inbounds(self)
97
+ #init self.totp
98
+ if self.two_fac_secret:
99
+ if self.two_fac_secret.isdigit() and len(self.two_fac_secret) <= 8:
100
+ #print("WARNING: You seem to have entered a 2FA **code**, not a 2FA secret."
101
+ #"Although entering the secret is dangerous, there is no other way to provide a consistent way"
102
+ #"for continuous login. This code will only work for this specific login.")
103
+ self.totp = None
104
+ else:
105
+ self.totp = pyotp.TOTP(self.two_fac_secret)
93
106
 
94
107
  #========================singleton pattern========================
95
108
  def __new__(cls, *args, **kwargs):
@@ -249,6 +262,13 @@ class XUIClient:
249
262
  "username": self.xui_username,
250
263
  "password": self.xui_password,
251
264
  }
265
+ if self.totp:
266
+ if self.totp.interval - datetime.now().timestamp() % self.totp.interval < 1:
267
+ await asyncio.sleep(1.1) # just to not submit an invalid code
268
+ payload["twoFactorCode"] = self.totp.now()
269
+ else:
270
+ if self.two_fac_secret:
271
+ payload["twoFactorCode"] = self.two_fac_secret
252
272
 
253
273
  #print(self.session.base_url)
254
274
  #print("WE'RE LOGGING IN")
@@ -272,6 +292,7 @@ class XUIClient:
272
292
  Self: The XUIClient instance.
273
293
  """
274
294
  self.session = AsyncClient(base_url=self.base_url)
295
+ self.connected = True
275
296
  return self
276
297
 
277
298
  async def disconnect(self) -> None:
@@ -279,6 +300,7 @@ class XUIClient:
279
300
 
280
301
  This method closes the async HTTP client session.
281
302
  """
303
+ self.connected = False
282
304
  await self.session.aclose()
283
305
 
284
306
  async def __aenter__(self) -> Self:
@@ -313,7 +335,7 @@ class XUIClient:
313
335
 
314
336
  #========================inbound management========================
315
337
  @alru_cache
316
- async def get_production_inbounds(self) -> List[Inbound]:
338
+ async def get_production_inbounds(self) -> Tuple[Inbound, ...]:
317
339
  """Retrieve production inbounds.
318
340
 
319
341
  This method fetches all inbounds and filters them based on the
@@ -333,7 +355,7 @@ class XUIClient:
333
355
  if len(usable_inbounds) == 0:
334
356
  raise RuntimeError("No production inbounds found! Change prod_string!")
335
357
 
336
- return usable_inbounds
358
+ return tuple(usable_inbounds)
337
359
 
338
360
  async def clear_prod_inbound_cache(self):
339
361
  """Clear the production inbound cache.
@@ -346,15 +368,12 @@ class XUIClient:
346
368
  This method currently runs every 10 seconds. Please change the
347
369
  timer from 5 to 60*60*24 in the code.
348
370
  """
349
- #This is useless for now, but later get_production_inbounds() will be cached,
350
- # and this will clear the cache every 24 hours or so, to make sure the client
351
- # always has the most up-to-date inbounds list
352
- while True:
371
+ while self.connected:
353
372
  #print("You're seeing this message because I forgot to remove it in api.update_inbounds() !")
354
373
  #print("Please change the timer from 5 to 60*60*24!")
355
374
  self.get_production_inbounds.cache_clear()
356
375
  await self.get_production_inbounds() #fill the cache
357
- await asyncio.sleep(10)
376
+ await asyncio.sleep(60*10)
358
377
  ##print(stat)
359
378
 
360
379
  #========================clients management========================
@@ -491,6 +510,7 @@ class XUIClient:
491
510
  email = util.generate_email_from_tgid_inbid(telegram_id, inbound.id)
492
511
  resp = await self.clients_end.delete_client_by_email(email, inbound.id)
493
512
  responses.append(resp)
513
+ #print("Inbound deleted")
494
514
 
495
515
  return responses
496
516
 
File without changes
File without changes
File without changes