Skip to content

Commit 4cae99d

Browse files
Add files via upload
1 parent 1cb3cd1 commit 4cae99d

34 files changed

Lines changed: 2197 additions & 0 deletions

.gitignore

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Python
2+
__pycache__/
3+
*.pyc
4+
.venv/
5+
build/
6+
dist/
7+
*.egg-info/
8+
9+
# Go
10+
/core_engine/core_engine.exe
11+
/core_engine/core_engine
12+
13+
# IDEs
14+
.vscode/
15+
.idea/

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Python V2Ray/Xray Core Wrapper
2+
3+
A powerful, high-level Python library to control and manage Xray-core instances, featuring a high-performance connection tester written in Go.
4+
5+
## Features
6+
7+
* Start and stop the Xray-core process programmatically.
8+
* Build and parse configurations using Python objects.
9+
* Communicate with the Xray API via gRPC for stats and management.
10+
* Ultra-fast concurrent connection testing engine powered by Go.
11+
12+
## Quick Start
13+
14+
1. Run the setup script to create a virtual environment:
15+
16+
`setup.bat` (on Windows)
17+
18+
`bash setup.sh` (on Linux/macOS)
19+
20+
2. Activate the environment and explore the examples.

config.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"log": {
3+
"loglevel": "warning"
4+
},
5+
"inbounds": [
6+
{
7+
"port": 10808,
8+
"protocol": "socks",
9+
"settings": {
10+
"auth": "noauth"
11+
}
12+
}
13+
],
14+
"outbounds": [
15+
{
16+
"protocol": "freedom",
17+
"settings": {}
18+
}
19+
]
20+
}

examples/01_simple_start_stop.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# examples/01_simple_start_stop.py
2+
3+
import time
4+
import os
5+
import sys
6+
7+
# * This is a clever way to make the script find our library
8+
# * without having to install it first.
9+
# * It adds the parent directory (the project root) to Python's path.
10+
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
11+
12+
from python_v2ray.core import XrayCore
13+
14+
def main():
15+
"""
16+
* A simple demonstration of starting and stopping the Xray core.
17+
"""
18+
# note: Define paths relative to the project root.
19+
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
20+
xray_path = os.path.join(project_root, "vendor", "xray.exe") # ? On Linux/Mac, this would be just "xray"
21+
config_path = os.path.join(project_root, "config.json")
22+
23+
print(f"note: Using Xray executable at: {xray_path}")
24+
print(f"note: Using config file at: {config_path}")
25+
26+
try:
27+
# Create an instance of our core controller
28+
xray = XrayCore(executable_path=xray_path, config_path=config_path)
29+
30+
# Start the core
31+
xray.start()
32+
33+
if xray.is_running():
34+
print("\n* Xray is running. You can check your Task Manager.")
35+
print("* Waiting for 10 seconds before stopping...")
36+
time.sleep(10)
37+
else:
38+
print("\n! Xray failed to start. Check the paths and permissions.")
39+
return
40+
41+
# Stop the core
42+
xray.stop()
43+
print("\n* Demo finished.")
44+
45+
except FileNotFoundError as e:
46+
print(f"\n! ERROR: A required file was not found.")
47+
print(f"! {e}")
48+
print("! Please make sure you have downloaded the Xray core into the 'vendor' folder.")
49+
except Exception as e:
50+
print(f"\n! An unexpected error occurred: {e}")
51+
52+
53+
if __name__ == "__main__":
54+
main()

examples/02_config_builder.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# examples/02_config_builder.py
2+
3+
import time
4+
import os
5+
import sys
6+
import json
7+
8+
# * This ensures the script can find our local library files
9+
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
10+
11+
from python_v2ray.core import XrayCore
12+
from python_v2ray.config_parser import parse_uri, XrayConfigBuilder
13+
14+
def run_test_with_uri(xray_path: str, uri: str):
15+
"""
16+
* A helper function to test a single URI from start to finish.
17+
"""
18+
print("\n" + "="*60)
19+
print(f"* Testing URI: {uri[:50]}...")
20+
print("="*60)
21+
22+
params = parse_uri(uri)
23+
if not params:
24+
print("! TEST FAILED: Could not parse the URI.")
25+
return
26+
27+
print(f"* PARSING SUCCESS: Protocol='{params.protocol}', Address='{params.address}:{params.port}'")
28+
# print(f"* Full Params: {params}") # note: Uncomment for deep debugging
29+
30+
print("\n* Building full Xray config...")
31+
builder = XrayConfigBuilder()
32+
33+
# * Add a local SOCKS inbound for our apps to connect to
34+
builder.add_inbound({
35+
"port": 10808, "listen": "127.0.0.1", "protocol": "socks",
36+
"settings": {"auth": "noauth", "udp": True}
37+
})
38+
39+
# * Build the outbound using our powerful engine
40+
outbound_dict = builder.build_outbound_from_params(params)
41+
builder.add_outbound(outbound_dict)
42+
43+
# * Add default direct and block outbounds (good practice)
44+
builder.add_outbound({"protocol": "freedom", "tag": "direct"})
45+
builder.add_outbound({"protocol": "blackhole", "tag": "block"})
46+
47+
print("\n* Final JSON config generated:")
48+
print(builder.to_json())
49+
50+
51+
print("\n* Attempting to start Xray core...")
52+
try:
53+
with XrayCore(executable_path=xray_path, config_builder=builder) as xray:
54+
if xray.is_running():
55+
print("\n* SUCCESS! Xray is running with this config.")
56+
print("* Local SOCKS proxy is available on 127.0.0.1:10808")
57+
print("* Running for 5 seconds...")
58+
time.sleep(5)
59+
else:
60+
print("\n! TEST FAILED: Xray did not start.")
61+
except Exception as e:
62+
print(f"\n! TEST FAILED: An error occurred during Xray execution: {e}")
63+
64+
print(f"* Test finished for URI: {uri[:50]}...")
65+
66+
67+
def main():
68+
"""
69+
* Runs a series of tests with different URI types.
70+
"""
71+
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
72+
xray_path = os.path.join(project_root, "vendor", "xray.exe") # ? On Linux/Mac, this would be "xray"
73+
74+
if not os.path.exists(xray_path):
75+
print(f"! FATAL ERROR: Xray executable not found at '{xray_path}'")
76+
print("! Please download it and place it in the 'vendor' folder.")
77+
return
78+
79+
# ! =======================================================================
80+
# ! === REPLACE THESE WITH YOUR OWN REAL TEST URIS ===
81+
# ! =======================================================================
82+
test_uris = [
83+
"vless://YOUR_UUID@your.domain.com:443?security=tls&sni=your.domain.com&fp=chrome&type=ws&path=%2F#VLESS-WS-TLS",
84+
"vmess://ewogICJ2IjogIjIiLAogICJwcyI6ICJWbWVzcy1URU5UIEtleSIsCiAgImFkZCI6ICJzb21lLmRvbWFpbi5jb20iLAogICJwb3J0IjogIjgwODAiLAogICJpZCI6ICJZV1JzTFRrM1pHRXRaV1UwTnkxa05EVm1MVGhsWm1NdFkyVTVNRFJsWWpkaE5XRmpZdyIsCiAgImFpZCI6ICIwIiwKICAic2N5IjogImF1dG8iLAogICJuZXQiOiAidGNwIiwKICAidHlwZSI6ICJub25lIiwKICAiaG9zdCI6ICIiLAogICJwYXRoIjogIi8iLAogICJ0bHMiOiAiIiwKICAic25pIjogIiIKfQ==",
85+
"trojan://YOUR_PASSWORD@your.domain.com:443?sni=your.domain.com#Trojan-Test",
86+
"ss://YWVzLTI1Ni1nY206eW91cl9wYXNzd29yZA==@your.domain.com:8443#ShadowSocks-Test"
87+
]
88+
89+
for uri in test_uris:
90+
if "YOUR_" in uri:
91+
print(f"\n! Skipping placeholder URI: {uri[:50]}...")
92+
print("! Please replace it with your own real config URI in the 'test_uris' list to test it.")
93+
continue
94+
run_test_with_uri(xray_path, uri)
95+
96+
97+
if __name__ == "__main__":
98+
main()

examples/03_stats_viewer.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# examples/03_stats_viewer.py
2+
3+
import time
4+
import os
5+
import sys
6+
7+
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
8+
9+
from python_v2ray.core import XrayCore
10+
from python_v2ray.config_parser import parse_uri, XrayConfigBuilder
11+
12+
def main():
13+
"""
14+
* Demonstrates how to run Xray with the API enabled and fetch live stats.
15+
"""
16+
# ! Replace with your own REAL and WORKING VLESS URI
17+
vless_uri = "vless://8b63cf90-830c-4fd8-a911-9e84fd7a5898@172.67.74.104:443?path=%2F%3FJoin---i10VPN---Join---i10VPN---Join---i10VPN---Join---i10VPN%3Fed%3D512&security=tls&encryption=none&alpn=http%2F1.1&host=s.s.google.com.b5r.ir.&fp=chrome&type=ws&sni=b5r.ir#%40i10VPN+%F0%9F%9A%80%7C+%D8%A8%D8%A7+%D9%85%D8%A7+%D9%85%D8%AA%D8%B5%D9%84+%D8%A8%D9%85%D9%88%D9%86%E2%9A%A1"
18+
19+
if "YOUR_UUID" in vless_uri:
20+
print("! Please replace the placeholder URI in the script with your own to run this demo.")
21+
return
22+
23+
params = parse_uri(vless_uri)
24+
if not params:
25+
return
26+
27+
# * The tag of our main outbound that we want to monitor
28+
params.tag = "proxy"
29+
outbound_tag_to_monitor = "proxy"
30+
31+
# * The port for the gRPC API
32+
api_port = 62789
33+
34+
print(f"* Building config to monitor outbound with tag: '{outbound_tag_to_monitor}'")
35+
builder = XrayConfigBuilder()
36+
builder.enable_api(port=api_port) # ! Enable the API service
37+
builder.add_inbound({
38+
"port": 10808,
39+
"listen": "127.0.0.1",
40+
"protocol": "socks",
41+
"settings": {
42+
"auth": "noauth",
43+
"udp": True,
44+
"userLevel": 0
45+
}
46+
})
47+
48+
outbound = builder.build_outbound_from_params(params)
49+
builder.add_outbound(outbound)
50+
builder.add_outbound({"protocol": "freedom", "tag": "direct"})
51+
print("\n" + "="*20 + " FINAL CONFIG TO BE USED " + "="*20)
52+
final_config_json = builder.to_json()
53+
print(final_config_json)
54+
print("="*61 + "\n")
55+
56+
57+
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
58+
xray_path = os.path.join(project_root, "vendor", "xray.exe")
59+
60+
print("* Starting Xray with API enabled...")
61+
# ! Pass the api_port to the XrayCore constructor
62+
with XrayCore(executable_path=xray_path, config_builder=builder, api_port=api_port) as xray:
63+
if not xray.is_running():
64+
print("! Xray failed to start.")
65+
return
66+
67+
print("\n* Xray is running. SOCKS on 10808. API on 62789.")
68+
print("* Now, generate some traffic through the SOCKS proxy (e.g., watch a video, run a speed test).")
69+
print("* Press Ctrl+C to stop.\n")
70+
71+
try:
72+
while True:
73+
time.sleep(2)
74+
stats = xray.get_stats(tag=outbound_tag_to_monitor)
75+
if stats:
76+
uplink_mb = stats['uplink'] / (1024 * 1024)
77+
downlink_mb = stats['downlink'] / (1024 * 1024)
78+
print(f"* Live Stats for '{outbound_tag_to_monitor}': Uplink: {uplink_mb:.2f} MB | Downlink: {downlink_mb:.2f} MB")
79+
else:
80+
print(f"\r* Waiting for traffic on tag '{outbound_tag_to_monitor}'...", end="")
81+
except KeyboardInterrupt:
82+
print("\n* User stopped the stats viewer.")
83+
84+
print("* Demo finished.")
85+
86+
if __name__ == "__main__":
87+
main()

examples/04_connection_tester.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# examples/04_connection_tester.py
2+
3+
import os
4+
import sys
5+
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
6+
from pathlib import Path
7+
from python_v2ray.config_parser import parse_uri, XrayConfigBuilder
8+
from python_v2ray.tester import ConnectionTester
9+
from python_v2ray.downloader import BinaryDownloader
10+
11+
def main():
12+
"""
13+
* Runs a series of tests with different URI types.
14+
"""
15+
project_root = Path(__file__).parent.parent
16+
17+
try:
18+
downloader = BinaryDownloader(project_root)
19+
downloader.ensure_all()
20+
except Exception as e:
21+
print(f"\n! FATAL: {e}")
22+
return
23+
24+
vendor_dir = project_root / "vendor"
25+
core_engine_dir = project_root / "core_engine"
26+
# Set to None to disable fragmentation
27+
fragment_settings = {
28+
"packets": "tlshello",
29+
"length": "10-30",
30+
"interval": "1-5"
31+
}
32+
33+
test_uris = [
34+
"vless://8b63cf90-830c-4fd8-a911-9e84fd7a5898@172.67.74.104:443?path=%2F%3FJoin---i10VPN---Join---i10VPN---Join---i10VPN---Join---i10VPN%3Fed%3D512&security=tls&encryption=none&alpn=http%2F1.1&host=s.s.google.com.b5r.ir.&fp=chrome&type=ws&sni=b5r.ir#vless",
35+
"hy2://dongtaiwang.com@208.87.243.187:22222/?insecure=1&sni=www.bing.com#hy2",
36+
"hy2://dongtaiwang.com@51.159.111.32:5355/?insecure=1&sni=www.bing.com#hy2",
37+
"hy2://7GEEGxAfgQaVPQX0PGk7lIuj3I@158.41.110.234:10820/?insecure=1&sni=bing.com#hy2",
38+
"vmess://eyJhZGQiOiJucG1qcy5jb20iLCJhaWQiOiIwIiwiYWxwbiI6IiIsImVjaENvbmZpZ0xpc3QiOiIiLCJlY2hGb3JjZVF1ZXJ5IjoiIiwiZWNoU2VydmVyS2V5cyI6IiIsImZha2Vob3N0X2RvbWFpbiI6IiIsImZwIjoiIiwiaG9zdCI6Im5hc25ldC0xMjgxNDAxMTIyMy5raGFzdGVobmFiYXNoaS5jb20iLCJpZCI6Im5hc25ldCIsImludGVydmFsIjoiIiwibGVuZ3RoIjoiIiwibXV4IjoiIiwibXV4Q29uY3VycmVuY3kiOiIiLCJuZXQiOiJ3cyIsInBhY2tldHMiOiIiLCJwYXRoIjoiL05BU05FVC9jZG4iLCJwb3J0IjoiODA4MCIsInBzIjoiXHUwMDNlXHUwMDNlQEZyZWFrQ29uZmlnOjpERSIsInNjeSI6ImF1dG8iLCJzbmkiOiIiLCJ0bHMiOiIiLCJ0eXBlIjoiIiwidiI6IjIifQ==",
39+
"trojan://2ee85121-31de-4581-a492-eb00f606e392@198.46.152.83:443?mux=&security=tls&headerType=none&type=tcp&muxConcurrency=-1&sni=sj3.freeguard.org#20%F0%9F%8E%A1%40oneclickvpnkeys",
40+
"ss://Y2hhY2hhMjAtaWV0Zi1wb2x5MTMwNTpmOGY3YUN6Y1BLYnNGOHAz@185.213.23.226:990#%3E%3E%40free4allVPN%3A%3ANO",
41+
# ... Add your other real URIs here ...
42+
]
43+
44+
print("* Parsing all URIs...")
45+
outbounds_to_test = []
46+
builder = XrayConfigBuilder()
47+
48+
for uri in test_uris:
49+
if "YOUR_" in uri or "..." in uri:
50+
print(f"! Skipping placeholder URI: {uri}")
51+
continue
52+
params = parse_uri(uri)
53+
if params:
54+
outbound_dict = builder.build_outbound_from_params(params)
55+
outbounds_to_test.append(outbound_dict)
56+
57+
if not outbounds_to_test:
58+
print("\n! No valid URIs found to test. Please edit the 'test_uris' list in the script.")
59+
return
60+
61+
print(f"\n* Preparing to test {len(outbounds_to_test)} configurations concurrently...")
62+
63+
tester = ConnectionTester(vendor_path=str(vendor_dir), core_engine_path=str(core_engine_dir))
64+
results = tester.test_outbounds(outbounds_to_test)
65+
66+
print("\n" + "="*20 + " TEST RESULTS " + "="*20)
67+
if results:
68+
sorted_results = sorted(results, key=lambda x: x.get('ping_ms', 9999))
69+
for result in sorted_results:
70+
tag = result.get('tag', 'N/A')
71+
ping = result.get('ping_ms', -1)
72+
status = result.get('status', 'error')
73+
if status == 'success':
74+
print(f"* Tag: {tag:<20} | Ping: {ping:>4} ms | Status: {status}")
75+
else:
76+
print(f"! Tag: {tag:<20} | Ping: {ping:>4} ms | Status: {status}")
77+
else:
78+
print("! No results received from the tester.")
79+
print("="*54)
80+
81+
if __name__ == "__main__":
82+
main()

0 commit comments

Comments
 (0)