Skip to content

Commit d8ffc24

Browse files
committed
new: add singbox
1 parent 2b07b26 commit d8ffc24

16 files changed

Lines changed: 386 additions & 0 deletions

xtlsapi/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
from xtlsapi.client.XrayClient import XrayClient
2+
from xtlsapi.client.SingboxClient import SingboxClient
23
import xtlsapi.exceptions
34
from xtlsapi.ext import utils

xtlsapi/client/SingboxClient.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from grpc import insecure_channel as grpc_insecure_channel
2+
3+
from xtlsapi.api_services import APIService
4+
5+
6+
class SingboxClient(APIService):
7+
def __init__(self, host, port):
8+
self.host = host
9+
self.port = port
10+
self._channel = grpc_insecure_channel(f'{host}:{port}')
11+
super().__init__()

xtlsapi/singbox_api/build.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
sudo apt install -y protobuf-compiler
2+
protoc ./stats.proto --python_out=./
3+
python3 -m grpc_tools.protoc -I ./ --python_out=./ --grpc_python_out=./ ./stats.proto

xtlsapi/singbox_api/stats.proto

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
syntax = "proto3";
2+
3+
package experimental.v2rayapi;
4+
option go_package = "github.com/sagernet/sing-box/experimental/v2rayapi";
5+
6+
message GetStatsRequest {
7+
// Name of the stat counter.
8+
string name = 1;
9+
// Whether or not to reset the counter to fetching its value.
10+
bool reset = 2;
11+
}
12+
13+
message Stat {
14+
string name = 1;
15+
int64 value = 2;
16+
}
17+
18+
message GetStatsResponse {
19+
Stat stat = 1;
20+
}
21+
22+
message QueryStatsRequest {
23+
// Deprecated, use Patterns instead
24+
string pattern = 1;
25+
bool reset = 2;
26+
repeated string patterns = 3;
27+
bool regexp = 4;
28+
}
29+
30+
message QueryStatsResponse {
31+
repeated Stat stat = 1;
32+
}
33+
34+
message SysStatsRequest {}
35+
36+
message SysStatsResponse {
37+
uint32 NumGoroutine = 1;
38+
uint32 NumGC = 2;
39+
uint64 Alloc = 3;
40+
uint64 TotalAlloc = 4;
41+
uint64 Sys = 5;
42+
uint64 Mallocs = 6;
43+
uint64 Frees = 7;
44+
uint64 LiveObjects = 8;
45+
uint64 PauseTotalNs = 9;
46+
uint32 Uptime = 10;
47+
}
48+
49+
service StatsService {
50+
rpc GetStats(GetStatsRequest) returns (GetStatsResponse) {}
51+
rpc QueryStats(QueryStatsRequest) returns (QueryStatsResponse) {}
52+
rpc GetSysStats(SysStatsRequest) returns (SysStatsResponse) {}
53+
}

xtlsapi/singbox_api/stats_pb2.py

Lines changed: 40 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
2+
"""Client and server classes corresponding to protobuf-defined services."""
3+
import grpc
4+
5+
import stats_pb2 as stats__pb2
6+
7+
8+
class StatsServiceStub(object):
9+
"""Missing associated documentation comment in .proto file."""
10+
11+
def __init__(self, channel):
12+
"""Constructor.
13+
14+
Args:
15+
channel: A grpc.Channel.
16+
"""
17+
self.GetStats = channel.unary_unary(
18+
'/experimental.v2rayapi.StatsService/GetStats',
19+
request_serializer=stats__pb2.GetStatsRequest.SerializeToString,
20+
response_deserializer=stats__pb2.GetStatsResponse.FromString,
21+
)
22+
self.QueryStats = channel.unary_unary(
23+
'/experimental.v2rayapi.StatsService/QueryStats',
24+
request_serializer=stats__pb2.QueryStatsRequest.SerializeToString,
25+
response_deserializer=stats__pb2.QueryStatsResponse.FromString,
26+
)
27+
self.GetSysStats = channel.unary_unary(
28+
'/experimental.v2rayapi.StatsService/GetSysStats',
29+
request_serializer=stats__pb2.SysStatsRequest.SerializeToString,
30+
response_deserializer=stats__pb2.SysStatsResponse.FromString,
31+
)
32+
33+
34+
class StatsServiceServicer(object):
35+
"""Missing associated documentation comment in .proto file."""
36+
37+
def GetStats(self, request, context):
38+
"""Missing associated documentation comment in .proto file."""
39+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
40+
context.set_details('Method not implemented!')
41+
raise NotImplementedError('Method not implemented!')
42+
43+
def QueryStats(self, request, context):
44+
"""Missing associated documentation comment in .proto file."""
45+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
46+
context.set_details('Method not implemented!')
47+
raise NotImplementedError('Method not implemented!')
48+
49+
def GetSysStats(self, request, context):
50+
"""Missing associated documentation comment in .proto file."""
51+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
52+
context.set_details('Method not implemented!')
53+
raise NotImplementedError('Method not implemented!')
54+
55+
56+
def add_StatsServiceServicer_to_server(servicer, server):
57+
rpc_method_handlers = {
58+
'GetStats': grpc.unary_unary_rpc_method_handler(
59+
servicer.GetStats,
60+
request_deserializer=stats__pb2.GetStatsRequest.FromString,
61+
response_serializer=stats__pb2.GetStatsResponse.SerializeToString,
62+
),
63+
'QueryStats': grpc.unary_unary_rpc_method_handler(
64+
servicer.QueryStats,
65+
request_deserializer=stats__pb2.QueryStatsRequest.FromString,
66+
response_serializer=stats__pb2.QueryStatsResponse.SerializeToString,
67+
),
68+
'GetSysStats': grpc.unary_unary_rpc_method_handler(
69+
servicer.GetSysStats,
70+
request_deserializer=stats__pb2.SysStatsRequest.FromString,
71+
response_serializer=stats__pb2.SysStatsResponse.SerializeToString,
72+
),
73+
}
74+
generic_handler = grpc.method_handlers_generic_handler(
75+
'experimental.v2rayapi.StatsService', rpc_method_handlers)
76+
server.add_generic_rpc_handlers((generic_handler,))
77+
78+
79+
# This class is part of an EXPERIMENTAL API.
80+
class StatsService(object):
81+
"""Missing associated documentation comment in .proto file."""
82+
83+
@staticmethod
84+
def GetStats(request,
85+
target,
86+
options=(),
87+
channel_credentials=None,
88+
call_credentials=None,
89+
insecure=False,
90+
compression=None,
91+
wait_for_ready=None,
92+
timeout=None,
93+
metadata=None):
94+
return grpc.experimental.unary_unary(request, target, '/experimental.v2rayapi.StatsService/GetStats',
95+
stats__pb2.GetStatsRequest.SerializeToString,
96+
stats__pb2.GetStatsResponse.FromString,
97+
options, channel_credentials,
98+
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
99+
100+
@staticmethod
101+
def QueryStats(request,
102+
target,
103+
options=(),
104+
channel_credentials=None,
105+
call_credentials=None,
106+
insecure=False,
107+
compression=None,
108+
wait_for_ready=None,
109+
timeout=None,
110+
metadata=None):
111+
return grpc.experimental.unary_unary(request, target, '/experimental.v2rayapi.StatsService/QueryStats',
112+
stats__pb2.QueryStatsRequest.SerializeToString,
113+
stats__pb2.QueryStatsResponse.FromString,
114+
options, channel_credentials,
115+
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
116+
117+
@staticmethod
118+
def GetSysStats(request,
119+
target,
120+
options=(),
121+
channel_credentials=None,
122+
call_credentials=None,
123+
insecure=False,
124+
compression=None,
125+
wait_for_ready=None,
126+
timeout=None,
127+
metadata=None):
128+
return grpc.experimental.unary_unary(request, target, '/experimental.v2rayapi.StatsService/GetSysStats',
129+
stats__pb2.SysStatsRequest.SerializeToString,
130+
stats__pb2.SysStatsResponse.FromString,
131+
options, channel_credentials,
132+
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from .stats import StatsAPIService
2+
from .handler import HandlerAPIService
3+
from .logger import LoggerAPIService
4+
5+
6+
class APIService(StatsAPIService):
7+
pass
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from xtlsapi.singbox_api import stats_pb2_grpc
2+
3+
4+
5+
class BaseService:
6+
def __init__(self):
7+
self.stats_stub = stats_pb2_grpc.StatsServiceStub(self._channel)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from .get_client_upload_traffic import GetClientUploadTraffic
2+
from .get_client_download_traffic import GetClientDownloadTraffic
3+
from .get_inbound_upload_traffic import GetInboundUploadTraffic
4+
from .get_inbound_download_traffic import GetInboundDownloadTraffic
5+
from .get_total_upload_traffic import GetTotalUploadTraffic
6+
from .get_total_download_traffic import GetTotalDownloadTraffic
7+
from .get_statsquery import StatsQuery
8+
9+
class StatsAPIService(
10+
GetClientUploadTraffic,
11+
GetClientDownloadTraffic,
12+
GetInboundUploadTraffic,
13+
GetInboundDownloadTraffic,
14+
GetTotalUploadTraffic,
15+
GetTotalDownloadTraffic,
16+
StatsQuery
17+
):
18+
pass
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import grpc
2+
from xtlsapi.singbox_api import stats_pb2
3+
4+
from .._base import BaseService
5+
6+
7+
class GetClientDownloadTraffic(BaseService):
8+
def get_client_download_traffic(self, email, reset=False):
9+
try:
10+
return self.stats_stub.GetStats(
11+
stats_pb2.GetStatsRequest(
12+
name=f"user>>>{email}>>>traffic>>>downlink", reset=reset
13+
)
14+
).stat.value
15+
except grpc.RpcError:
16+
# raise
17+
return None

0 commit comments

Comments
 (0)