mirror of
https://github.com/Gozargah/Marzban.git
synced 2026-05-17 00:25:53 +03:00
* chore: update pydantic to version 2.10.2 and refactor model validators * refactor: simplify field validators by removing unnecessary pre and always flags * remove typing_extensions==4.9.0 from requirements.txt * refactor: remove allow_reuse flag from status field validator * refactor: simplify field validators by removing pre and always flags * refactor: update user model imports and enhance account class with abstract method * refactor: update model_config to use dictionary format in Admin and SubscriptionUserResponse classes * fix typo in UserDataResetByNext * change pre=True to mode="before" * refactor: update validation methods and model configuration in User and Proxy classes * change pre=False with mode="after" * Migrated to Pydantic V2 * fix: custom subscriptions not workong * some small changes * add missing properties to example schema * replace from_orm with model_validate --------- Co-authored-by: MahdiButcher <madibutchercoding@gmail.com> Co-authored-by: Mahdi Butcher <MahdiButcherCoding@gmail.com>
207 lines
5.5 KiB
Python
207 lines
5.5 KiB
Python
import json
|
|
import re
|
|
from enum import Enum
|
|
from typing import Optional, Union
|
|
from uuid import UUID, uuid4
|
|
|
|
from pydantic import field_validator, ConfigDict, BaseModel, Field
|
|
|
|
from app.utils.system import random_password
|
|
from xray_api.types.account import (
|
|
ShadowsocksAccount,
|
|
ShadowsocksMethods,
|
|
TrojanAccount,
|
|
VLESSAccount,
|
|
VMessAccount,
|
|
XTLSFlows
|
|
)
|
|
|
|
FRAGMENT_PATTERN = re.compile(r'^((\d{1,4}-\d{1,4})|(\d{1,4})),((\d{1,3}-\d{1,3})|(\d{1,3})),(tlshello|\d|\d\-\d)$')
|
|
|
|
NOISE_PATTERN = re.compile(
|
|
r'^(rand:(\d{1,4}-\d{1,4}|\d{1,4})|str:.+|base64:.+)(,(\d{1,4}-\d{1,4}|\d{1,4}))?(&(rand:(\d{1,4}-\d{1,4}|\d{1,4})|str:.+|base64:.+)(,(\d{1,4}-\d{1,4}|\d{1,4}))?)*$')
|
|
|
|
|
|
class ProxyTypes(str, Enum):
|
|
# proxy_type = protocol
|
|
|
|
VMess = "vmess"
|
|
VLESS = "vless"
|
|
Trojan = "trojan"
|
|
Shadowsocks = "shadowsocks"
|
|
|
|
@property
|
|
def account_model(self):
|
|
if self == self.VMess:
|
|
return VMessAccount
|
|
if self == self.VLESS:
|
|
return VLESSAccount
|
|
if self == self.Trojan:
|
|
return TrojanAccount
|
|
if self == self.Shadowsocks:
|
|
return ShadowsocksAccount
|
|
|
|
@property
|
|
def settings_model(self):
|
|
if self == self.VMess:
|
|
return VMessSettings
|
|
if self == self.VLESS:
|
|
return VLESSSettings
|
|
if self == self.Trojan:
|
|
return TrojanSettings
|
|
if self == self.Shadowsocks:
|
|
return ShadowsocksSettings
|
|
|
|
|
|
class ProxySettings(BaseModel, use_enum_values=True):
|
|
@classmethod
|
|
def from_dict(cls, proxy_type: ProxyTypes, _dict: dict):
|
|
return ProxyTypes(proxy_type).settings_model.model_validate(_dict)
|
|
|
|
def dict(self, *, no_obj=False, **kwargs):
|
|
if no_obj:
|
|
return json.loads(self.json())
|
|
return super().dict(**kwargs)
|
|
|
|
|
|
class VMessSettings(ProxySettings):
|
|
id: UUID = Field(default_factory=uuid4)
|
|
|
|
def revoke(self):
|
|
self.id = uuid4()
|
|
|
|
|
|
class VLESSSettings(ProxySettings):
|
|
id: UUID = Field(default_factory=uuid4)
|
|
flow: XTLSFlows = XTLSFlows.NONE
|
|
|
|
def revoke(self):
|
|
self.id = uuid4()
|
|
|
|
|
|
class TrojanSettings(ProxySettings):
|
|
password: str = Field(default_factory=random_password)
|
|
flow: XTLSFlows = XTLSFlows.NONE
|
|
|
|
def revoke(self):
|
|
self.password = random_password()
|
|
|
|
|
|
class ShadowsocksSettings(ProxySettings):
|
|
password: str = Field(default_factory=random_password)
|
|
method: ShadowsocksMethods = ShadowsocksMethods.CHACHA20_POLY1305
|
|
|
|
def revoke(self):
|
|
self.password = random_password()
|
|
|
|
|
|
class ProxyHostSecurity(str, Enum):
|
|
inbound_default = "inbound_default"
|
|
none = "none"
|
|
tls = "tls"
|
|
|
|
|
|
ProxyHostALPN = Enum(
|
|
"ProxyHostALPN",
|
|
{
|
|
"none": "",
|
|
"h3": "h3",
|
|
"h2": "h2",
|
|
"http/1.1": "http/1.1",
|
|
"h3,h2,http/1.1": "h3,h2,http/1.1",
|
|
"h3,h2": "h3,h2",
|
|
"h2,http/1.1": "h2,http/1.1",
|
|
},
|
|
)
|
|
|
|
|
|
ProxyHostFingerprint = Enum(
|
|
"ProxyHostFingerprint",
|
|
{
|
|
"none": "",
|
|
"chrome": "chrome",
|
|
"firefox": "firefox",
|
|
"safari": "safari",
|
|
"ios": "ios",
|
|
"android": "android",
|
|
"edge": "edge",
|
|
"360": "360",
|
|
"qq": "qq",
|
|
"random": "random",
|
|
"randomized": "randomized",
|
|
},
|
|
)
|
|
|
|
|
|
class FormatVariables(dict):
|
|
def __missing__(self, key):
|
|
return key.join("{}")
|
|
|
|
|
|
class ProxyHost(BaseModel):
|
|
remark: str
|
|
address: str
|
|
port: Optional[int] = Field(None, nullable=True)
|
|
sni: Optional[str] = Field(None, nullable=True)
|
|
host: Optional[str] = Field(None, nullable=True)
|
|
path: Optional[str] = Field(None, nullable=True)
|
|
security: ProxyHostSecurity = ProxyHostSecurity.inbound_default
|
|
alpn: ProxyHostALPN = ProxyHostALPN.none
|
|
fingerprint: ProxyHostFingerprint = ProxyHostFingerprint.none
|
|
allowinsecure: Union[bool, None] = None
|
|
is_disabled: Union[bool, None] = None
|
|
mux_enable: Union[bool, None] = None
|
|
fragment_setting: Optional[str] = Field(None, nullable=True)
|
|
noise_setting: Optional[str] = Field(None, nullable=True)
|
|
random_user_agent: Union[bool, None] = None
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
@field_validator("remark", mode="after")
|
|
def validate_remark(cls, v):
|
|
try:
|
|
v.format_map(FormatVariables())
|
|
except ValueError as exc:
|
|
raise ValueError("Invalid formatting variables")
|
|
|
|
return v
|
|
|
|
@field_validator("address", mode="after")
|
|
def validate_address(cls, v):
|
|
try:
|
|
v.format_map(FormatVariables())
|
|
except ValueError as exc:
|
|
raise ValueError("Invalid formatting variables")
|
|
|
|
return v
|
|
|
|
@field_validator("fragment_setting", check_fields=False)
|
|
@classmethod
|
|
def validate_fragment(cls, v):
|
|
if v and not FRAGMENT_PATTERN.match(v):
|
|
raise ValueError(
|
|
"Fragment setting must be like this: length,interval,packet (10-100,100-200,tlshello)."
|
|
)
|
|
return v
|
|
|
|
@field_validator("noise_setting", check_fields=False)
|
|
@classmethod
|
|
def validate_noise(cls, v):
|
|
if v:
|
|
if not NOISE_PATTERN.match(v):
|
|
raise ValueError(
|
|
"Noise setting must be like this: packet,delay (rand:10-20,100-200)."
|
|
)
|
|
if len(v) > 2000:
|
|
raise ValueError(
|
|
"Noise can't be longer that 2000 character"
|
|
)
|
|
return v
|
|
|
|
|
|
class ProxyInbound(BaseModel):
|
|
tag: str
|
|
protocol: ProxyTypes
|
|
network: str
|
|
tls: str
|
|
port: Union[int, str]
|