Closed SpiritFoxo closed 4 weeks ago
@SpiritFoxo, hello!
Thank you for reaching out. I would like to ask you if the following approach is working in your case:
Code reference for traffic limit:
async def some_other_function():
user_uuid = str(uuid.uuid4())
new_client = Client(id=user_uuid, email=str(telegram_id), enable=True)
await api.client.add(inbound.id, [new_client])
await set_traffic_limit(telegram_id, user_uuid)
async def set_traffic_limit(telegram_id: int, user_uuid: str) -> None:
client = await api.client.get_by_email(str(telegram_id))
client.id = user_uuid
client.total_gb = gigabytes_to_bytes(Settings().traffic_limit)
await api.client.update(user_uuid, client)
When working with 3x-ui API I found out that some of the fields may be ignored by the API on user create, even if they were passed (or maybe I'm doing something wrong). So I used this workaround to set the traffic limit, maybe it will help you too.
Sincerely
As an additional check, I also would like to ask for help to test if the simple POST request without SDK will work for this case, so we can check if the issue is in the SDK or on the API side.
Oh, yeah. Thats works. Thanks for fast response!
@SpiritFoxo
I found that when you initiate new Client() object and pass some params like expiry_time as initial args, pydantic set default value instead passed argument, and do not include them to POST request payload, because they are exluded while serializing client.model_dump(by_alias=True, exclude_defaults=True)
(You can check this part here)
from py3xui import Client
client = Client(
email=TEST_USER_EMAIL,
enable=True,
id='00000-0000-000000-0000', # uuid4
expiry_time=1728927613678,
flow='xtls-rprx-vision',
)
print(client.model_dump(by_alias=True, exclude_defaults=True))
>>> {'email': 'test-client-email', 'enable': True, 'id': '00000-0000-000000-0000', 'flow': 'xtls-rprx-vision'}
but if you force set parameters like this, it will be set and serialized correctly
client.expiry_time = 1728927613678
print(client.model_dump(by_alias=True, exclude_defaults=True))
>>> {'email': 'test-client-email', 'enable': True, 'id': '00000-0000-000000-0000', 'expiryTime': 1728927613678, 'flow': 'xtls-rprx-vision'}
So you do not need workarounds like "create" and "update" client to set expiry_time, just force set fields you need to be set while you preparing your Client pydantic object.
I believe this is pydantic strange behavior, not py3xui issue. @iwatkot FYI
@eugeny-m, hey!
Thanks for the info, I guess maybe it's related to the additional parameters, such as exclude_defaults=True
. I was wondering if this will work correctly if remove the argument.
So, I'll check it and apply changes if needed.
Sincerely
@iwatkot wow it's nice to see such a quick response! =)
I am afraid removing exclude_defaults=True
args will not help and need strong testing anyway, because will add many defaults values in payload, that could be different from x-ui core defaults.
Just pay attention to expiry_time
value stored in client object, it is 0 until you force it. May be we need to fix pydantic field description
client = Client(
email='TEST_USER_EMAIL',
enable=True,
id='00000-0000-000000-0000', # uuid4
expiry_time=1728927613678,
flow='xtls-rprx-vision',
)
print(f'{client.expiry_time=}')
>>> client.expiry_time=0
client.expiry_time = 1728927613678
print(f'{client.expiry_time=}')
>>> client.expiry_time=1728927613678
@eugeny-m, so I just checked it like this:
client = Client(email="test", id=str(uuid.uuid4()), enable=True, expiryTime=9999999)
settings = {
"clients": [
client.model_dump(by_alias=True, exclude_defaults=True) for client in clients
]
}
data = {"id": inbound_id, "settings": json.dumps(settings)}
print(data)
And received the correct payload:
{'id': 1, 'settings': '{"clients": [{"email": "test", "enable": true, "id": "5cfb7796-ae09-41b3-9033-91035ffaa6e5", "expiryTime": 9999999}]}'}
Which looks like in the API documentation:
curl --location 'http://localhost:2053/panel/api/inbounds/addClient' \
--header 'Accept: application/json' \
--data '{
"id": 1,
"settings": "{\"clients\":[{\"id\":\"95e4e7bb-7796-47e7-e8a7-f4055194f776\",\"alterId\":0,\"email\":\"New Client\",\"limitIp\":2,\"totalGB\":42949672960,\"expiryTime\":1682864675944,\"enable\":true,\"tgId\":\"\",\"subId\":\"\"}]}"
}'
So, I'm not able to reproduce the behavior where the payload does not contain the field that was set when creating an object. Could you please help with it?
Thank you
@eugeny-m, also, this provided the correct output too:
client = Client(email="test", id=str(uuid.uuid4()), enable=True, expiryTime=9999999)
print(client.expiry_time)
9999999
@iwatkot I found what my problem is=)
I use param name expiry_time
as it defined in model. But if pydantic field alias defined, you have to pass alias as initial param instead field name.
You already use alias expiryTime
in your experiment and get expected behavior
@iwatkot pydantic param populate_by_name can modify this behavior
class ModifiedClient(Client):
class Config:
populate_by_name = True
client = ModifiedClient(
email='TEST_USER_EMAIL',
enable=True,
id='00000-0000-000000-0000', # uuid4
expiry_time=1728927613678,
flow='xtls-rprx-vision',
)
print(f'{client.expiry_time=}')
>>> client.expiry_time=1728927613678
I am not sure should you to add this config to you models definitions or not) But I am sure not everybody knows about this feature
@eugeny-m, actually, I've added this feature to the Inbound object but forgot about the Client. 😅 I'll update the Client, too, but let's get back to the Client. Did you manage to set some additional parameters using the addClient endpoint? Or is this case still a mystery for us? :)
@eugeny-m, just in case: https://github.com/iwatkot/py3xui/pull/24 I'll merge it soon, so the populate by name feature would work for Client too (in the next version of the package).
I'll merge it soon, so the populate by name feature would work for Client too (in the next version of the package).
sounds great! Thanks for what you did!
Did you manage to set some additional parameters using the addClient endpoint? Or is this case still a mystery for us? :)
what params exactly are you talking about? @iwatkot
@eugeny-m: I'm talking about this: https://github.com/iwatkot/py3xui/discussions/11
When adding a Client, the API ignores some fields, such as expiry_time, total_gb, and others. I was too lazy to check raw requests without SDK, so I still think that there's a chance that the issue is in the py3xui. But the problem is when using the exact same payload on an update client endpoint, those fields work perfectly. So I decided the bug was on the 3x-ui API side.
When you commented here, I thought that you finally found the reason for this, but it looks like our FAQ will remain in the same state. :)
Sincerely
@eugeny-m, just confirm that you have no idea how to set those parameters when creating a Client, so I will close the issue. Or maybe you know how, then let's investigate it. :)
@iwatkot I checked expiry_time
, after I pass it correct as alias name, client creates with expiry_time setted. So it works. Let me check total_gb
@eugeny-m, huh, so maybe they've finally fixed that issue on the API side?
@iwatkot maybe) totalGB also works, but you have to send int value in bytes
, not Gb
@eugeny-m, thank you for the update. Yeah, some time ago the API was ignoring those fields (even if they were set correctly). I believe that the issue was fixed in 3x-ui.
Cool, thanks for the help, I'm closing it.
Have a nice day and you're welcome to contribute to the project next time.
Sincerely
@iwatkot I found another issue, api.client.get_by_email(email=TEST_USER_EMAIL)
returns client object with client.id = some integer value, like database PK, not client uuid value. But 3x-ui expects client_uuid in update url path
found it while trying to update client with your examples in docstrings
api = py3xui.AsyncApi.from_env()
await api.login()
client = await api.client.get_by_email("email")
client.email = "newemail"
await api.client.update(client.id, client)
it will cause a not found error
@eugeny-m, it's a known behavior of the 3x-ui API. Learn more if FAQ: https://github.com/iwatkot/py3xui/discussions/10
@SpiritFoxo, hello again!
I'm sorry, but the problem was different. So it was obviously me doing something wrong. :D
The problem was that the populate by name option was not enabled for the Client model, as a result, the attributes had to be specified as expiryTime. In the latest release, this error has been fixed and everything should work correctly.
Best Regards
when i try to add new client with limited duration it doesnt work. It just creates new client with expiry_time=0