Python-3xui 0.0.11__tar.gz → 0.0.12__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.
- {python_3xui-0.0.11 → python_3xui-0.0.12}/PKG-INFO +4 -8
- python_3xui-0.0.12/README.md +6 -0
- {python_3xui-0.0.11 → python_3xui-0.0.12}/pyproject.toml +2 -2
- {python_3xui-0.0.11 → python_3xui-0.0.12}/python_3xui/__init__.py +1 -1
- {python_3xui-0.0.11 → python_3xui-0.0.12}/python_3xui/api.py +10 -5
- {python_3xui-0.0.11 → python_3xui-0.0.12}/python_3xui/api_core/client_service.py +34 -7
- python_3xui-0.0.11/README.md +0 -10
- {python_3xui-0.0.11 → python_3xui-0.0.12}/.gitignore +0 -0
- {python_3xui-0.0.11 → python_3xui-0.0.12}/LICENSE +0 -0
- {python_3xui-0.0.11 → python_3xui-0.0.12}/python_3xui/api_core/__init__.py +0 -0
- {python_3xui-0.0.11 → python_3xui-0.0.12}/python_3xui/api_core/identity.py +0 -0
- {python_3xui-0.0.11 → python_3xui-0.0.12}/python_3xui/api_core/prod_cache.py +0 -0
- {python_3xui-0.0.11 → python_3xui-0.0.12}/python_3xui/api_core/session_core.py +0 -0
- {python_3xui-0.0.11 → python_3xui-0.0.12}/python_3xui/base_model.py +0 -0
- {python_3xui-0.0.11 → python_3xui-0.0.12}/python_3xui/custom_exceptions.py +0 -0
- {python_3xui-0.0.11 → python_3xui-0.0.12}/python_3xui/endpoints.py +0 -0
- {python_3xui-0.0.11 → python_3xui-0.0.12}/python_3xui/models.py +0 -0
- {python_3xui-0.0.11 → python_3xui-0.0.12}/python_3xui/util.py +0 -0
- {python_3xui-0.0.11 → python_3xui-0.0.12}/tests/conftest.py +0 -0
- {python_3xui-0.0.11 → python_3xui-0.0.12}/tests/gather_response_stubs.py +0 -0
- {python_3xui-0.0.11 → python_3xui-0.0.12}/tests/pytest.ini +0 -0
- {python_3xui-0.0.11 → python_3xui-0.0.12}/tests/test_endpoints_clients.py +0 -0
- {python_3xui-0.0.11 → python_3xui-0.0.12}/tests/test_endpoints_inbounds.py +0 -0
- {python_3xui-0.0.11 → python_3xui-0.0.12}/tests/test_non_idempotent_endpoints_clients.py +0 -0
- {python_3xui-0.0.11 → python_3xui-0.0.12}/tests/test_non_idempotent_endpoints_inbounds.py +0 -0
- {python_3xui-0.0.11 → python_3xui-0.0.12}/tests/test_xuiclient_helpers.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Python-3xui
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.12
|
|
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
|
|
@@ -11,7 +11,7 @@ Classifier: Development Status :: 3 - Alpha
|
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
12
12
|
Classifier: Operating System :: OS Independent
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
14
|
-
Requires-Python: >=3.
|
|
14
|
+
Requires-Python: >=3.12
|
|
15
15
|
Requires-Dist: async-lru~=2.3.0
|
|
16
16
|
Requires-Dist: httpx~=0.28.1
|
|
17
17
|
Requires-Dist: pydantic<3,~=2.12.5
|
|
@@ -28,9 +28,5 @@ Description-Content-Type: text/markdown
|
|
|
28
28
|
<p>I'm not expecting much to be honest, so please feel free to fork it if I abandon the project and you need it!</p>
|
|
29
29
|
<p>Also, if you REALLY want it I can give you the ownership if I step down, you can find my email in the pyproject.toml (I don't check it that much but trust me I do)</p>
|
|
30
30
|
|
|
31
|
-
<h2>0.0.
|
|
32
|
-
|
|
33
|
-
<li>HOTFIX: make models.SingleInboundClient default flow "", because turns out panel can not return it because of zombification...</li>
|
|
34
|
-
<li>Add a custom uuid generator for XUIClient that <i>defaults</i> to method in util but you can make your own!</li>
|
|
35
|
-
<li>Uncomplicate self.sub_gen into self._resolve_sub</li>
|
|
36
|
-
</ul>
|
|
31
|
+
<h2>0.0.12 Release Notes</h2>
|
|
32
|
+
Mainly just bugfixes. Fix email fallback, fix adding clients, add inbound management.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<h1>Hi! This is my example python 3x-ui wrapper!</h1>
|
|
2
|
+
<p>I'm not expecting much to be honest, so please feel free to fork it if I abandon the project and you need it!</p>
|
|
3
|
+
<p>Also, if you REALLY want it I can give you the ownership if I step down, you can find my email in the pyproject.toml (I don't check it that much but trust me I do)</p>
|
|
4
|
+
|
|
5
|
+
<h2>0.0.12 Release Notes</h2>
|
|
6
|
+
Mainly just bugfixes. Fix email fallback, fix adding clients, add inbound management.
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "Python-3xui"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.12"
|
|
4
4
|
authors = [
|
|
5
5
|
{ name="JustMe_001", email="justme001.causation755@passinbox.com" },
|
|
6
6
|
]
|
|
7
7
|
description = "3x-ui wrapper for python"
|
|
8
8
|
readme = "README.md"
|
|
9
9
|
|
|
10
|
-
requires-python = ">=3.
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
11
|
classifiers = [
|
|
12
12
|
"Programming Language :: Python :: 3",
|
|
13
13
|
"Operating System :: OS Independent",
|
|
@@ -470,7 +470,8 @@ class XUIClient:
|
|
|
470
470
|
sub_id: str | None = None,
|
|
471
471
|
comment: str | None = None,
|
|
472
472
|
email: str | None = None,
|
|
473
|
-
verbose: bool = True
|
|
473
|
+
verbose: bool = True,
|
|
474
|
+
force_resolve_by_email: bool = False) -> Response:
|
|
474
475
|
"""
|
|
475
476
|
Update a client in a specific inbound by Telegram ID. NOT optimized for multiple inbounds.
|
|
476
477
|
|
|
@@ -486,8 +487,9 @@ class XUIClient:
|
|
|
486
487
|
enable: Whether the client is enabled (optional)
|
|
487
488
|
sub_id: Subscription ID (optional)
|
|
488
489
|
comment: Client comment/note (optional)
|
|
489
|
-
email: New client email (optional). USE WITH CAUTION BECAUSE THE
|
|
490
|
-
|
|
490
|
+
email: New client email (optional). USE WITH CAUTION BECAUSE THE XUIClient WILL NOT TRACK THE NEW EMAIL.
|
|
491
|
+
verbose: Enables guardrails.
|
|
492
|
+
force_resolve_by_email: Whether to enable fetch-thru-email fallback when a client is not found, uses ~3 extra fetches but provides an extra layer of protection.
|
|
491
493
|
Returns:
|
|
492
494
|
Response from the API
|
|
493
495
|
"""
|
|
@@ -505,19 +507,22 @@ class XUIClient:
|
|
|
505
507
|
comment=comment,
|
|
506
508
|
email=email,
|
|
507
509
|
verbose=verbose,
|
|
510
|
+
force_resolve_by_email=force_resolve_by_email,
|
|
508
511
|
)
|
|
509
512
|
|
|
510
|
-
async def delete_client_by_tgid(self, telegram_id: int, inbound_id: int) -> Response:
|
|
513
|
+
async def delete_client_by_tgid(self, telegram_id: int, inbound_id: int, *, suffix: str = "") -> Response:
|
|
511
514
|
"""Delete a client from a specific inbound by Telegram ID.
|
|
512
515
|
|
|
513
516
|
Args:
|
|
514
517
|
telegram_id: The Telegram ID of the client
|
|
515
518
|
inbound_id: The ID of the inbound
|
|
519
|
+
suffix: Appended to the generated email before deletion (use when the
|
|
520
|
+
target client was created with a custom email suffix).
|
|
516
521
|
|
|
517
522
|
Returns:
|
|
518
523
|
Response from the API
|
|
519
524
|
"""
|
|
520
|
-
return await self._tg_client_service.delete_client_by_tgid(telegram_id, inbound_id)
|
|
525
|
+
return await self._tg_client_service.delete_client_by_tgid(telegram_id, inbound_id, suffix=suffix)
|
|
521
526
|
|
|
522
527
|
async def revoke_client_by_tgid_all_inbounds(self, telegram_id: int) -> List[Response]:
|
|
523
528
|
"""Delete a client from all production inbounds by Telegram ID.
|
|
@@ -107,13 +107,29 @@ class TgIDClientService:
|
|
|
107
107
|
|
|
108
108
|
if _update_exec:
|
|
109
109
|
update_results: list[Response] = await asyncio.gather(*_update_exec)
|
|
110
|
+
_search_update_exec: list[Task] = []
|
|
111
|
+
_search_update_resp: dict[int, Task] = {}
|
|
110
112
|
for i, inb_id in enumerate(update_inbound_ids):
|
|
111
|
-
|
|
113
|
+
_resp = update_results[i]
|
|
114
|
+
_msg: str = _resp.json()["msg"]
|
|
115
|
+
if "empty client id" in _msg.lower():
|
|
116
|
+
t = asyncio.create_task(
|
|
117
|
+
self._resolve_update_client(telegram_id, inb_id, clients_by_inbound[inb_id])
|
|
118
|
+
)
|
|
119
|
+
_search_update_exec.append(t)
|
|
120
|
+
_search_update_resp[inb_id] = t
|
|
121
|
+
else:
|
|
122
|
+
responses[inb_id] = _resp
|
|
123
|
+
|
|
124
|
+
if _search_update_exec:
|
|
125
|
+
await asyncio.gather(*_search_update_exec)
|
|
126
|
+
for inb_id, task in _search_update_resp.items():
|
|
127
|
+
responses[inb_id] = task.result()
|
|
112
128
|
|
|
113
129
|
# --- Phase 3: raise on remaining duplicates if not exist_ok ---
|
|
114
130
|
if not exist_ok:
|
|
115
131
|
for inb_id, resp in responses.items():
|
|
116
|
-
json_resp = resp.json()
|
|
132
|
+
json_resp: dict = resp.json()
|
|
117
133
|
msg = json_resp.get("msg", "")
|
|
118
134
|
if "duplicate email" in msg.lower():
|
|
119
135
|
logging.error(
|
|
@@ -124,13 +140,22 @@ class TgIDClientService:
|
|
|
124
140
|
|
|
125
141
|
return responses
|
|
126
142
|
|
|
143
|
+
async def _resolve_update_client(self, telegram_id: int, inb_id: int,
|
|
144
|
+
inbound_client: SingleInboundClient) -> Response:
|
|
145
|
+
_found = await self._clients.get_client_with_email(
|
|
146
|
+
util.generate_email_from_tgid_inbid(telegram_id, inb_id)
|
|
147
|
+
)
|
|
148
|
+
return await self._clients.request_update_client(
|
|
149
|
+
inbound_client, inb_id, original_uuid=_found.uuid,
|
|
150
|
+
)
|
|
151
|
+
|
|
127
152
|
async def _find_client_in_inbound(self,
|
|
128
153
|
client_uuid: str,
|
|
129
154
|
inbound_id: int,
|
|
130
155
|
*,
|
|
131
|
-
|
|
156
|
+
use_prod_cache: bool = False,
|
|
132
157
|
) -> SingleInboundClient | None:
|
|
133
|
-
if
|
|
158
|
+
if use_prod_cache:
|
|
134
159
|
prod_inbs = await self._prod_cache.get()
|
|
135
160
|
prod_inb_index = None
|
|
136
161
|
for i, prod_inb in enumerate(prod_inbs):
|
|
@@ -256,7 +281,7 @@ class TgIDClientService:
|
|
|
256
281
|
)
|
|
257
282
|
|
|
258
283
|
client_uuid = await self._identity.resolve_uuid(telegram_id)
|
|
259
|
-
found = await self._find_client_in_inbound(client_uuid, inbound_id)
|
|
284
|
+
found = await self._find_client_in_inbound(client_uuid, inbound_id, use_prod_cache=True)
|
|
260
285
|
if not found:
|
|
261
286
|
if force_resolve_by_email:
|
|
262
287
|
_email_to_search = util.generate_email_from_tgid_inbid(telegram_id, inbound_id)
|
|
@@ -264,6 +289,8 @@ class TgIDClientService:
|
|
|
264
289
|
if resp is None:
|
|
265
290
|
raise ClientDoesNotExistError(f"The target inbound was force-checked by email but client {_email_to_search} was not found.")
|
|
266
291
|
client_uuid = resp.uuid
|
|
292
|
+
found_inbound_id = resp.inboundId
|
|
293
|
+
found = await self._find_client_in_inbound(client_uuid, found_inbound_id, use_prod_cache=True)
|
|
267
294
|
else:
|
|
268
295
|
raise ClientDoesNotExistError(
|
|
269
296
|
f"The target inbound was checked but client {client_uuid} was not found."
|
|
@@ -283,8 +310,8 @@ class TgIDClientService:
|
|
|
283
310
|
comment=comment,
|
|
284
311
|
)
|
|
285
312
|
|
|
286
|
-
async def delete_client_by_tgid(self, telegram_id: int, inbound_id: int) -> Response:
|
|
287
|
-
email = util.generate_email_from_tgid_inbid(telegram_id, inbound_id)
|
|
313
|
+
async def delete_client_by_tgid(self, telegram_id: int, inbound_id: int, *, suffix: str = "") -> Response:
|
|
314
|
+
email = util.generate_email_from_tgid_inbid(telegram_id, inbound_id) + suffix
|
|
288
315
|
return await self._clients.delete_client_by_email(email, inbound_id)
|
|
289
316
|
|
|
290
317
|
async def revoke_client_by_tgid_all_inbounds(self, telegram_id: int) -> List[Response]:
|
python_3xui-0.0.11/README.md
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
<h1>Hi! This is my example python 3x-ui wrapper!</h1>
|
|
2
|
-
<p>I'm not expecting much to be honest, so please feel free to fork it if I abandon the project and you need it!</p>
|
|
3
|
-
<p>Also, if you REALLY want it I can give you the ownership if I step down, you can find my email in the pyproject.toml (I don't check it that much but trust me I do)</p>
|
|
4
|
-
|
|
5
|
-
<h2>0.0.10 Release Notes</h2>
|
|
6
|
-
<ul>
|
|
7
|
-
<li>HOTFIX: make models.SingleInboundClient default flow "", because turns out panel can not return it because of zombification...</li>
|
|
8
|
-
<li>Add a custom uuid generator for XUIClient that <i>defaults</i> to method in util but you can make your own!</li>
|
|
9
|
-
<li>Uncomplicate self.sub_gen into self._resolve_sub</li>
|
|
10
|
-
</ul>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|