Compare commits

...
14 Commits
Author SHA1 Message Date
vladimir 904f9cd973 chore: создание каталога sandbox для хранения проверочных скриптов 2026-07-04 05:13:24 +03:00
vladimir 7ac981e055 test: добавление файла для проверки работоспособности VlessOutbound 2026-07-04 05:11:56 +03:00
vladimir d8c33a7e69 feat: добавление импорта из файлов outbound.py и target.py 2026-07-04 05:11:05 +03:00
vladimir cf00ddee07 feat: завершение работы над классом VlessOutbound 2026-07-04 05:10:11 +03:00
vladimir 8d6b23e5ac feat: добавление port override для vless 2026-07-04 04:28:59 +03:00
vladimir cce8431c40 fix: исправление опечатки 2026-07-04 04:06:11 +03:00
vladimir 44ede92073 feat: добавление примера секции "override" для переопределения параметров соединений пользователя 2026-07-04 04:05:13 +03:00
vladimir a344b1802b docs: изменение описания группы команд user outbound 2026-07-04 03:58:26 +03:00
vladimir 1fe07b0dd1 refactor: удаление методов targets, get_targets_list 2026-07-04 03:26:25 +03:00
vladimir 99e95d0624 refactor: удаление метода find_target_by_pretty_name из класса Outbound 2026-07-04 03:24:32 +03:00
vladimir d2e4537d15 test: небольшое изменение кода для проверки работоспособности Outbound с использованием Target 2026-07-04 03:21:12 +03:00
vladimir c637f944f9 refactor: изменение классов OutboundSpec, OutboundFactory и Outbound для использования класса Target 2026-07-04 03:19:37 +03:00
vladimir d0c3d5d50e refactor: замена полей target: str и target_pretty_name: str на target: Target 2026-07-04 03:17:14 +03:00
vladimir 669737e918 style: black formatter 2026-07-04 02:44:21 +03:00
10 changed files with 454 additions and 87 deletions
+3 -4
View File
@@ -7,10 +7,9 @@ user show (<username> | -a)
user modify <username> [-a <protocol>:<target> [<protocol>:<target> ...]] [-d <protocol>:<target> [<protocol>:<target> ...]] user modify <username> [-a <protocol>:<target> [<protocol>:<target> ...]] [-d <protocol>:<target> [<protocol>:<target> ...]]
user delete (<username> | -a) user delete (<username> | -a)
user outbound add <username> -p <protocol> -t <target> [--<param> <value> ...] user outbound set <username> -protocol [--target <target_id>] [--<param> <value> ...]
user outbound show <username> (-a | -p <protocol> | -t <target>) user outbound show <username> (-a | -p <protocol> [--target <target_id>])
user outbound modify <username> -p <protocol> -t <target> [--<param> <value> ...] user outbound delete <username> (-a | -p <protocol> | --target <target_id>)
user outbound delete <username> (-a | -p <protocol> | -t <target>)
user profile build (<username> | -a) user profile build (<username> | -a)
user profile show (<username> | -a) user profile show (<username> | -a)
+9
View File
@@ -1,5 +1,14 @@
from .connection import Connection, ConnectionFactory, ConnectionKey from .connection import Connection, ConnectionFactory, ConnectionKey
from .outbound import (
OUTBOUND_FIELDS,
Outbound,
OutboundFactory,
OutboundSpec,
ShadowsocksOutbound,
VlessOutbound,
)
from .profile import Profile, ProfileFactory, ProfileStorage from .profile import Profile, ProfileFactory, ProfileStorage
from .target import Target, TargetStorage
from .user import User, UserFactory from .user import User, UserFactory
from .xray_config import XrayConfig from .xray_config import XrayConfig
from .xray_manager_config import XrayManagerConfig from .xray_manager_config import XrayManagerConfig
+315 -50
View File
@@ -8,9 +8,11 @@ from dataclasses import dataclass, make_dataclass
from typing import TYPE_CHECKING, TypeVar from typing import TYPE_CHECKING, TypeVar
from urllib.parse import parse_qs, quote, unquote, urlencode, urlparse from urllib.parse import parse_qs, quote, unquote, urlencode, urlparse
from ..utils import private_to_public
from .outbound_fields import OUTBOUND_FIELDS from .outbound_fields import OUTBOUND_FIELDS
if TYPE_CHECKING: if TYPE_CHECKING:
from .target import Target, TargetStorage
from .xray_config import XrayConfig from .xray_config import XrayConfig
from .xray_manager_config import XrayManagerConfig from .xray_manager_config import XrayManagerConfig
@@ -18,7 +20,7 @@ if TYPE_CHECKING:
@dataclass(frozen=True) @dataclass(frozen=True)
class OutboundSpec: class OutboundSpec:
protocol: str protocol: str
target: str target_id: str
O = TypeVar("O", bound="Outbound") O = TypeVar("O", bound="Outbound")
@@ -50,22 +52,19 @@ class OutboundFactory:
return list(cls._registry) return list(cls._registry)
@classmethod @classmethod
def from_link( def from_link(cls, target_storage: TargetStorage, link: str) -> Outbound:
cls, xray_manager_config: XrayManagerConfig, link: str
) -> Outbound:
scheme = link.split("://", 1)[0] scheme = link.split("://", 1)[0]
if scheme not in cls._scheme_registry: if scheme not in cls._scheme_registry:
raise ValueError(f"Unsupported link scheme: {scheme}") raise ValueError(f"Unsupported link scheme: {scheme}")
outbound_cls = cls._scheme_registry[scheme] outbound_cls = cls._scheme_registry[scheme]
targets = xray_manager_config.targets return outbound_cls.from_link(target_storage, link)
return outbound_cls.from_link(targets, link)
@classmethod @classmethod
def from_inbound( def from_inbound(
cls, cls,
xray_manager_config: XrayManagerConfig, target_storage: TargetStorage,
host: str, host: str,
client: dict, client: dict,
inbound: dict, inbound: dict,
@@ -76,9 +75,8 @@ class OutboundFactory:
raise ValueError(f"Unsupported protocol: {protocol}") raise ValueError(f"Unsupported protocol: {protocol}")
outbound_cls = cls._registry[protocol] outbound_cls = cls._registry[protocol]
targets = xray_manager_config.targets
username, outbound = outbound_cls.from_inbound( username, outbound = outbound_cls.from_inbound(
targets, host, client, inbound target_storage, host, client, inbound
) )
return username, outbound return username, outbound
@@ -86,26 +84,18 @@ class OutboundFactory:
def from_spec( def from_spec(
cls, cls,
xray_config: XrayConfig, xray_config: XrayConfig,
xray_manager_config: XrayManagerConfig, target_storage: TargetStorage,
spec: OutboundSpec, spec: OutboundSpec,
) -> Outbound: ) -> Outbound:
if spec.protocol not in cls._registry: if spec.protocol not in cls._registry:
raise ValueError(f"Unsupported protocol: {spec.protocol}") raise ValueError(f"Unsupported protocol: {spec.protocol}")
if spec.target not in xray_manager_config.get_targets_list(): target = target_storage.load_by_id(spec.target_id)
raise ValueError(f"Target {spec.target} not found")
inbound = xray_config.find_managed_inbound_by_protocol(spec.protocol) inbound = xray_config.find_managed_inbound_by_protocol(spec.protocol)
outbound_cls = cls._registry[spec.protocol] outbound_cls = cls._registry[spec.protocol]
targets = xray_manager_config.targets return outbound_cls.from_scratch(target, xray_config.host, inbound)
target_pretty_name = targets.get(spec.target, {}).get(
"pretty_name", spec.target
)
return outbound_cls.from_scratch(
spec.target, target_pretty_name, xray_config.host, inbound
)
class Outbound(ABC): class Outbound(ABC):
@@ -115,14 +105,14 @@ class Outbound(ABC):
@classmethod @classmethod
@abstractmethod @abstractmethod
def from_link( def from_link(
cls, targets: dict[str, dict[str, str]], link: str cls, target_storage: TargetStorage, link: str
) -> Outbound: ... ) -> Outbound: ...
@classmethod @classmethod
@abstractmethod @abstractmethod
def from_inbound( def from_inbound(
cls, cls,
targets: dict[str, dict[str, str]], target_storage: TargetStorage,
host: str, host: str,
client: dict, client: dict,
inbound: dict, inbound: dict,
@@ -131,7 +121,7 @@ class Outbound(ABC):
@classmethod @classmethod
@abstractmethod @abstractmethod
def from_scratch( def from_scratch(
cls, target: str, target_pretty_name: str, host: str, inbound: dict cls, target: Target, host: str, inbound: dict
) -> Outbound: ... ) -> Outbound: ...
@abstractmethod @abstractmethod
@@ -155,16 +145,6 @@ class Outbound(ABC):
username, target = email.rsplit("-", 1) username, target = email.rsplit("-", 1)
return username, target return username, target
@staticmethod
def find_target_by_pretty_name(
targets: dict[str, dict[str, str]], pretty_name: str
) -> str | None:
search_name = pretty_name.strip()
for target, target_data in targets.items():
if target_data.get("pretty_name", "").strip() == search_name:
return target
return None
@abstractmethod @abstractmethod
def __hash__(self) -> int: ... def __hash__(self) -> int: ...
@@ -184,7 +164,7 @@ class ShadowsocksOutbound(_ShadowsocksFields, Outbound):
@classmethod @classmethod
def from_link( def from_link(
cls, targets: dict[str, dict[str, str]], link: str cls, target_storage: TargetStorage, link: str
) -> ShadowsocksOutbound: ) -> ShadowsocksOutbound:
import base64 import base64
@@ -198,15 +178,12 @@ class ShadowsocksOutbound(_ShadowsocksFields, Outbound):
method, server_password, client_password = prefix_str.split(":") method, server_password, client_password = prefix_str.split(":")
target_pretty_name = unquote(quoted_target_pretty_name) target_pretty_name = unquote(quoted_target_pretty_name)
target = cls.find_target_by_pretty_name(targets, target_pretty_name) target = target_storage.load_by_pretty(target_pretty_name)
if target is None:
target = "unknown"
return cls( return cls(
host=host, host=host,
port=port, port=port,
target=target, target=target,
target_pretty_name=target_pretty_name,
method=method, method=method,
server_password=server_password, server_password=server_password,
client_password=client_password, client_password=client_password,
@@ -215,7 +192,7 @@ class ShadowsocksOutbound(_ShadowsocksFields, Outbound):
@classmethod @classmethod
def from_inbound( def from_inbound(
cls, cls,
targets: dict[str, dict[str, str]], target_storage: TargetStorage,
host: str, host: str,
client: dict, client: dict,
inbound: dict, inbound: dict,
@@ -225,15 +202,15 @@ class ShadowsocksOutbound(_ShadowsocksFields, Outbound):
server_password = inbound["settings"]["password"] server_password = inbound["settings"]["password"]
client_password = client["password"] client_password = client["password"]
email = client["email"] email = client["email"]
username, target = cls.split_client_email(email)
target_pretty_name = targets.get(target, {}).get("pretty_name", target) username, target_id = cls.split_client_email(email)
target = target_storage.load_by_id(target_id)
outbound = cls( outbound = cls(
host=host, host=host,
port=port, port=port,
target=target, target=target,
target_pretty_name=target_pretty_name,
method=method, method=method,
server_password=server_password, server_password=server_password,
client_password=client_password, client_password=client_password,
@@ -242,7 +219,7 @@ class ShadowsocksOutbound(_ShadowsocksFields, Outbound):
@classmethod @classmethod
def from_scratch( def from_scratch(
cls, target: str, target_pretty_name: str, host: str, inbound: dict cls, target: Target, host: str, inbound: dict
) -> ShadowsocksOutbound: ) -> ShadowsocksOutbound:
port = inbound["port"] port = inbound["port"]
method = inbound["settings"]["method"] method = inbound["settings"]["method"]
@@ -253,7 +230,6 @@ class ShadowsocksOutbound(_ShadowsocksFields, Outbound):
host=host, host=host,
port=port, port=port,
target=target, target=target,
target_pretty_name=target_pretty_name,
method=method, method=method,
server_password=server_password, server_password=server_password,
client_password=client_password, client_password=client_password,
@@ -264,7 +240,7 @@ class ShadowsocksOutbound(_ShadowsocksFields, Outbound):
prefix = f"{self.method}:{self.server_password}:{self.client_password}" prefix = f"{self.method}:{self.server_password}:{self.client_password}"
prefix_b64 = base64.urlsafe_b64encode(prefix.encode()).decode() prefix_b64 = base64.urlsafe_b64encode(prefix.encode()).decode()
tag = quote(self.target_pretty_name) tag = quote(self.target.pretty)
link = f"ss://{prefix_b64}@{self.host}:{self.port}#{tag}" link = f"ss://{prefix_b64}@{self.host}:{self.port}#{tag}"
return link return link
@@ -307,7 +283,7 @@ class ShadowsocksOutbound(_ShadowsocksFields, Outbound):
def add_to_inbound(self, inbound: dict, username: str) -> None: def add_to_inbound(self, inbound: dict, username: str) -> None:
client = { client = {
"email": f"{username}-{self.target}", "email": f"{username}-{self.target.id}",
"password": self.client_password, "password": self.client_password,
} }
@@ -322,8 +298,8 @@ class ShadowsocksOutbound(_ShadowsocksFields, Outbound):
priority = {"default": 0, "managed": 1, "relay": 2} priority = {"default": 0, "managed": 1, "relay": 2}
def sort_key(c): def sort_key(c):
_, target = self.split_client_email(c["email"]) _, target_id = self.split_client_email(c["email"])
return (priority.get(target, 100), c["email"]) return (priority.get(target_id, 100), c["email"])
for user_clients in clients_by_user.values(): for user_clients in clients_by_user.values():
user_clients.sort(key=sort_key) user_clients.sort(key=sort_key)
@@ -335,7 +311,7 @@ class ShadowsocksOutbound(_ShadowsocksFields, Outbound):
inbound["settings"]["clients"] = sorted_clients inbound["settings"]["clients"] = sorted_clients
def delete_from_inbound(self, inbound: dict, username: str) -> None: def delete_from_inbound(self, inbound: dict, username: str) -> None:
target_email = f"{username}-{self.target}" target_email = f"{username}-{self.target.id}"
settings = inbound["settings"] settings = inbound["settings"]
clients = settings["clients"] clients = settings["clients"]
@@ -376,10 +352,299 @@ class ShadowsocksOutbound(_ShadowsocksFields, Outbound):
) )
_VlessFields = make_dataclass("_VlessFieldss", fields=OUTBOUND_FIELDS["vless"]) _VlessFields = make_dataclass("_VlessFields", fields=OUTBOUND_FIELDS["vless"])
@OutboundFactory.register @OutboundFactory.register
class VlessOutbound(_VlessFields, Outbound): class VlessOutbound(_VlessFields, Outbound):
PROTOCOL = "vless" PROTOCOL = "vless"
LINK_SCHEME = "vless" LINK_SCHEME = "vless"
@classmethod
def from_link(
cls, target_storage: TargetStorage, link: str
) -> VlessOutbound:
parsed = urlparse(link)
query = parse_qs(parsed.query)
def require(value: T | None, name: str) -> T:
if value is None:
raise ValueError(f"Missing required field: {name}")
return value
def require_str(value: str | None, name: str) -> str:
value = require(value, name)
if value == "":
raise ValueError(f"Empty value for required field: {name}")
return unquote(value)
def get_required(key: str) -> str:
values = query.get(key)
if not values:
raise ValueError(f"Missing required query param: {key}")
raw = values[0]
if raw is None or raw == "":
raise ValueError(f"Empty value for required query param: {key}")
return unquote(raw)
return cls(
host=require_str(parsed.hostname, "host"),
port=require(parsed.port, "port"),
target=target_storage.load_by_pretty(
require_str(parsed.fragment, "target_pretty_name")
),
id=require_str(parsed.username, "id"),
security=get_required("security"),
encryption=get_required("encryption"),
public_key=get_required("pbk"),
network_type=get_required("type"),
sni=get_required("sni"),
short_id=get_required("sid"),
flow=get_required("flow"),
header_type=get_required("headerType"),
finger_print=get_required("fp"),
)
@classmethod
def from_inbound(
cls,
target_storage: TargetStorage,
host: str,
client: dict,
inbound: dict,
) -> tuple[str, VlessOutbound]:
settings = inbound["settings"]
stream = inbound["streamSettings"]
reality = stream["realitySettings"]
port = inbound["port"]
id = client["id"]
flow = client["flow"]
encryption = settings["decryption"]
network_type = stream["network"]
security = stream["security"]
sni = reality["serverNames"][0]
public_key = private_to_public(reality["privateKey"])
clients = settings["clients"]
short_ids = reality["shortIds"]
try:
index = clients.index(client)
except ValueError:
raise RuntimeError(f"Client not found in inbound: {client}")
try:
short_id = short_ids[index]
except IndexError:
raise RuntimeError(f"No shortId for client at index {index}")
email = client["email"]
username, target_id = cls.split_client_email(email)
connection = cls(
host=host,
port=port,
target=target_storage.load_by_id(target_id),
id=id,
security=security,
encryption=encryption,
public_key=public_key,
network_type=network_type,
sni=sni,
short_id=short_id,
flow=flow,
)
return username, connection
@classmethod
def from_scratch(
cls, target: Target, host: str, inbound: dict
) -> VlessOutbound:
settings = inbound["settings"]
stream = inbound["streamSettings"]
reality = stream["realitySettings"]
port = inbound["port"]
encryption = settings["decryption"]
network_type = stream["network"]
security = stream["security"]
sni = reality["serverNames"][0]
public_key = private_to_public(reality["privateKey"])
id = cls.generate_id()
short_id = cls.generate_short_id()
return cls(
host=host,
port=port,
target=target,
id=id,
security=security,
encryption=encryption,
public_key=public_key,
network_type=network_type,
sni=sni,
short_id=short_id,
)
def to_link(self) -> str:
netloc = f"{self.id}@{self.host}:{self.port}"
params = {
"security": self.security,
"encryption": self.encryption,
"pbk": self.public_key,
"headerType": self.header_type,
"fp": self.finger_print,
"type": self.network_type,
"flow": self.flow,
"sni": self.sni,
"sid": self.short_id,
}
query = urlencode(params, quote_via=quote)
fragment = quote(self.target.pretty)
return f"vless://{netloc}?{query}#{fragment}"
@staticmethod
def generate_id() -> str:
return str(uuid.uuid4())
@staticmethod
def generate_short_id() -> str:
return secrets.token_hex(8)
def matches_inbound(self, inbound: dict) -> bool:
if inbound.get("protocol") != self.PROTOCOL:
return False
if inbound.get("port") != self.port:
return False
settings = inbound.get("settings", {})
if settings.get("decryption") != self.encryption:
return False
stream_settings = inbound.get("streamSettings", {})
if stream_settings.get("network") != self.network_type:
return False
if stream_settings.get("security") != self.security:
return False
reality_settings = stream_settings.get("realitySettings", {})
if self.sni not in reality_settings.get("serverNames", []):
return False
inbound_private_key = reality_settings.get("privateKey")
if not inbound_private_key:
raise ValueError("Reality Settings do not have private key")
inbound_public_key = private_to_public(inbound_private_key)
if inbound_public_key != self.public_key:
return False
return True
def add_to_inbound(self, inbound: dict, username: str) -> None:
client = {
"email": f"{username}-{self.target.id}",
"id": self.id,
"flow": self.flow,
}
clients = inbound["settings"].setdefault("clients", [])
reality = inbound["streamSettings"].setdefault("realitySettings", {})
short_ids = reality.setdefault("shortIds", [])
clients.append(client)
short_ids.append(self.short_id)
clients_and_sids_by_user = defaultdict(list)
for client, sid in zip(clients, short_ids):
username, _ = self.split_client_email(client["email"])
clients_and_sids_by_user[username].append((client, sid))
priority = {"default": 0, "managed": 1, "relay": 2}
def sort_key(pair):
client, _ = pair
_, target_id = self.split_client_email(client["email"])
return (priority.get(target_id, 100), client["email"])
for pair in clients_and_sids_by_user.values():
pair.sort(key=sort_key)
sorted_clients = []
sorted_short_ids = []
for username in sorted(clients_and_sids_by_user):
for client, sid in clients_and_sids_by_user[username]:
sorted_clients.append(client)
sorted_short_ids.append(sid)
inbound["settings"]["clients"] = sorted_clients
reality["shortIds"] = sorted_short_ids
def delete_from_inbound(self, inbound: dict, username: str) -> None:
target_email = f"{username}-{self.target.id}"
clients = inbound["settings"].get("clients", [])
reality = inbound["streamSettings"].get("realitySettings", {})
short_ids = reality.get("shortIds", [])
if not clients or not short_ids:
return
filtered_pairs = [
(client, sid)
for client, sid in zip(clients, short_ids)
if client["email"] != target_email
]
inbound["settings"]["clients"] = [
client for client, _ in filtered_pairs
]
inbound["streamSettings"]["realitySettings"]["shortIds"] = [
sid for _, sid in filtered_pairs
]
@property
def protocol(self) -> str:
return self.PROTOCOL
def __hash__(self) -> int:
return hash(
(
self.host,
self.port,
self.target,
self.id,
self.security,
self.encryption,
self.public_key,
self.network_type,
self.sni,
self.short_id,
self.flow,
self.header_type,
self.finger_print,
)
)
def __eq__(self, other: object) -> bool:
if not isinstance(other, VlessOutbound):
return NotImplemented
return (
self.host == other.host
and self.port == other.port
and self.target == other.target
and self.id == other.id
and self.security == other.security
and self.encryption == other.encryption
and self.public_key == other.public_key
and self.network_type == other.network_type
and self.sni == other.sni
and self.short_id == other.short_id
and self.flow == other.flow
and self.header_type == other.header_type
and self.finger_print == other.finger_print
)
+3 -6
View File
@@ -1,11 +1,8 @@
from dataclasses import field from dataclasses import field
_BASE_FIELDS = [ from .target import Target
("host", str),
("port", str), _BASE_FIELDS = [("host", str), ("port", str), ("target", Target)]
("target", str),
("target_pretty_name", str),
]
OUTBOUND_FIELDS = { OUTBOUND_FIELDS = {
+2 -1
View File
@@ -6,6 +6,7 @@ from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from .xray_manager_config import XrayManagerConfig from .xray_manager_config import XrayManagerConfig
@dataclass(frozen=True) @dataclass(frozen=True)
class Target: class Target:
id: str id: str
@@ -35,7 +36,7 @@ class TargetStorage:
return Target( return Target(
id=target_id, id=target_id,
pretty=target_data.get("pretty_name", target_id), pretty=target_data.get("pretty_name", target_id),
route=target_data["route"] route=target_data["route"],
) )
def load_by_pretty(self, target_pretty_name: str) -> Target: def load_by_pretty(self, target_pretty_name: str) -> Target:
+2 -9
View File
@@ -73,13 +73,6 @@ class XrayManagerConfig:
for username, folder_name in self.profile_folder_mapping.items() for username, folder_name in self.profile_folder_mapping.items()
} }
@property
def targets(self) -> dict[str, dict[str, str]]:
return self.config.get("target", {})
def get_targets_list(self) -> list[str]:
return list(self.targets.keys())
def set_profile_folder(self, username: str, folder_name: str): def set_profile_folder(self, username: str, folder_name: str):
self.config["profiles"]["folder_mapping"][username] = folder_name self.config["profiles"]["folder_mapping"][username] = folder_name
self._save_json() self._save_json()
@@ -89,11 +82,11 @@ class XrayManagerConfig:
self._save_json() self._save_json()
def set_target( def set_target(
self, target_id: str, target_pretty_name: str, target_route: str self, target_id: str, target_pretty_name: str, target_route: str
): ):
self.config["target"][target_id] = { self.config["target"][target_id] = {
"pretty_name": target_pretty_name, "pretty_name": target_pretty_name,
"route": target_route "route": target_route,
} }
self.config["target"][target_id]["pretty_name"] = target_pretty_name self.config["target"][target_id]["pretty_name"] = target_pretty_name
self.config["target"][target_id]["route"] = target_route self.config["target"][target_id]["route"] = target_route
@@ -27,5 +27,16 @@
"route": "out-us2", "route": "out-us2",
"pretty_name": "🇺🇸 USA #2" "pretty_name": "🇺🇸 USA #2"
} }
},
"override": {
"test": {
"vless": {
"port": 443,
"finger_print": "qq",
"by_target_id": {
"us2": "chrome"
}
}
}
} }
} }
@@ -3,7 +3,7 @@ from xray_manager.core.outbound import (
OutboundSpec, OutboundSpec,
ShadowsocksOutbound, ShadowsocksOutbound,
) )
from xray_manager.core.xray_config import XrayConfig from xray_manager.core.target import Target, TargetStorage
from xray_manager.core.xray_manager_config import XrayManagerConfig from xray_manager.core.xray_manager_config import XrayManagerConfig
inbound = { inbound = {
@@ -32,16 +32,13 @@ inbound = {
if __name__ == "__main__": if __name__ == "__main__":
DEV_CONFIG_PATH = "tests/mock_data/etc/xray-manager/config.json" DEV_CONFIG_PATH = "tests/mock_data/etc/xray-manager/config.json"
xray_manager_config = XrayManagerConfig(DEV_CONFIG_PATH) xray_manager_config = XrayManagerConfig(DEV_CONFIG_PATH)
target_storage = TargetStorage(xray_manager_config)
target = "us1" target = target_storage.load_by_id("default")
target_pretty_name = xray_manager_config.targets.get(target, {}).get(
"pretty_name", target
)
outbound = ShadowsocksOutbound.from_scratch( outbound = ShadowsocksOutbound.from_scratch(target, "127.0.0.1", inbound)
target, target_pretty_name, "127.0.0.1", inbound
)
link = outbound.to_link() link = outbound.to_link()
o = ShadowsocksOutbound.from_link(xray_manager_config.targets, link) print(link)
o = ShadowsocksOutbound.from_link(target_storage, link)
print(o) print(o)
+95
View File
@@ -0,0 +1,95 @@
from xray_manager.core import (
OutboundFactory,
ProfileStorage,
Target,
TargetStorage,
VlessOutbound,
XrayManagerConfig,
)
inbound = {
"tag": "in-vless",
"listen": "127.0.0.1",
"port": 443,
"protocol": "vless",
"settings": {
"clients": [
{
"email": "test-fr1",
"id": "0eb7a1a8-c24f-4eb2-9f89-8023a10ed070",
"flow": "xtls-rprx-vision",
},
{
"email": "test2-us2",
"id": "7b6da4ce-bb10-4127-a0c6-243618c62edd",
"flow": "xtls-rprx-vision",
},
],
"decryption": "none",
},
"streamSettings": {
"network": "tcp",
"security": "reality",
"realitySettings": {
"show": False,
"dest": "google.com:443",
"xver": 0,
"serverNames": ["google.com"],
"privateKey": "yHJQq57MNRi-0UQm7ymiy2DI17iO_Ovu2HF5EV1ViGg",
"minClientVer": "",
"maxClientVer": "",
"maxTimeDiff": 0,
"shortIds": ["91e7c34ea2c07723", "0419a64d06467571"],
},
},
}
def test_from_scratch(target_storage: TargetStorage) -> VlessOutbound:
target = target_storage.load_by_id("fr1")
outbound = VlessOutbound.from_scratch(target, "localhost", inbound)
print("Test from scratch")
print(outbound)
return outbound
def test_to_link(outbound: VlessOutbound) -> str:
link = outbound.to_link()
print("Test to link")
print(link)
return link
def test_from_link(target_storage: TargetStorage, link: str):
outbound = VlessOutbound.from_link(target_storage, link)
print("Test from link")
print(outbound)
def test_from_inbound(target_storage: TargetStorage):
client = {
"email": "test-fr1",
"id": "0eb7a1a8-c24f-4eb2-9f89-8023a10ed070",
"flow": "xtls-rprx-vision",
}
username, outbound = VlessOutbound.from_inbound(
target_storage, "localhost", client, inbound
)
print("Test from inbound")
print(username)
print(outbound)
if __name__ == "__main__":
DEV_CONFIG_PATH = "tests/mock_data/etc/xray-manager/config.json"
xray_manager_config = XrayManagerConfig(DEV_CONFIG_PATH)
profile_storage = ProfileStorage(xray_manager_config)
target_storage = TargetStorage(xray_manager_config)
outbound = test_from_scratch(target_storage)
link = test_to_link(outbound)
test_from_link(target_storage, link)
test_from_inbound(target_storage)