From c3bb809c71c12a0d84ac856784c55ac9b5487dd9 Mon Sep 17 00:00:00 2001 From: yuzuki999 Date: Sun, 20 Aug 2023 22:24:57 +0800 Subject: [PATCH] support notes for config --- api/panel/user.go | 2 + common/json5/json5.go | 112 ++++++++++++++++++++++++++++++++++++++++++ conf/conf.go | 3 +- conf/conf_test.go | 2 +- example/config.json | 86 +++++++++++++++++++------------- 5 files changed, 170 insertions(+), 35 deletions(-) create mode 100644 common/json5/json5.go diff --git a/api/panel/user.go b/api/panel/user.go index 8357a7d..c54f83f 100644 --- a/api/panel/user.go +++ b/api/panel/user.go @@ -2,6 +2,7 @@ package panel import ( "fmt" + log "github.com/sirupsen/logrus" "github.com/goccy/go-json" ) @@ -66,5 +67,6 @@ func (c *Client) ReportUserTraffic(userTraffic []UserTraffic) error { if err != nil { return err } + log.Println(r.String()) return nil } diff --git a/common/json5/json5.go b/common/json5/json5.go new file mode 100644 index 0000000..166ad78 --- /dev/null +++ b/common/json5/json5.go @@ -0,0 +1,112 @@ +package json5 + +import ( + "bytes" + "io" +) + +type TrimNodeReader struct { + r io.Reader + br *bytes.Reader +} + +func isNL(c byte) bool { + return c == '\n' || c == '\r' +} + +func isWS(c byte) bool { + return c == ' ' || c == '\t' || isNL(c) +} + +func consumeComment(s []byte, i int) int { + if i < len(s) && s[i] == '/' { + s[i-1] = ' ' + for ; i < len(s) && !isNL(s[i]); i += 1 { + s[i] = ' ' + } + } + if i < len(s) && s[i] == '*' { + s[i-1] = ' ' + s[i] = ' ' + for ; i < len(s); i += 1 { + if s[i] != '*' { + s[i] = ' ' + } else { + s[i] = ' ' + i++ + if i < len(s) { + if s[i] == '/' { + s[i] = ' ' + break + } + } + } + } + } + return i +} + +func prep(r io.Reader) (s []byte, err error) { + buf := &bytes.Buffer{} + _, err = io.Copy(buf, r) + s = buf.Bytes() + if err != nil { + return + } + + i := 0 + for i < len(s) { + switch s[i] { + case '"': + i += 1 + for i < len(s) { + if s[i] == '"' { + i += 1 + break + } else if s[i] == '\\' { + i += 1 + } + i += 1 + } + case '/': + i = consumeComment(s, i+1) + case ',': + j := i + for { + i += 1 + if i >= len(s) { + break + } else if s[i] == '}' || s[i] == ']' { + s[j] = ' ' + break + } else if s[i] == '/' { + i = consumeComment(s, i+1) + } else if !isWS(s[i]) { + break + } + } + default: + i += 1 + } + } + return +} + +// Read acts as a proxy for the underlying reader and cleans p +// of comments and trailing commas preceeding ] and } +// comments are delimitted by // up until the end the line +func (st *TrimNodeReader) Read(p []byte) (n int, err error) { + if st.br == nil { + var s []byte + if s, err = prep(st.r); err != nil { + return + } + st.br = bytes.NewReader(s) + } + return st.br.Read(p) +} + +// NewTrimNodeReader New returns an io.Reader acting as proxy to r +func NewTrimNodeReader(r io.Reader) io.Reader { + return &TrimNodeReader{r: r} +} diff --git a/conf/conf.go b/conf/conf.go index d00824b..7fdd933 100644 --- a/conf/conf.go +++ b/conf/conf.go @@ -2,6 +2,7 @@ package conf import ( "fmt" + "github.com/InazumaV/V2bX/common/json5" "os" "github.com/goccy/go-json" @@ -28,5 +29,5 @@ func (p *Conf) LoadFromPath(filePath string) error { return fmt.Errorf("open config file error: %s", err) } defer f.Close() - return json.NewDecoder(f).Decode(p) + return json.NewDecoder(json5.NewTrimNodeReader(f)).Decode(p) } diff --git a/conf/conf_test.go b/conf/conf_test.go index 4349b92..531395c 100644 --- a/conf/conf_test.go +++ b/conf/conf_test.go @@ -6,7 +6,7 @@ import ( func TestConf_LoadFromPath(t *testing.T) { c := New() - t.Log(c.LoadFromPath("./config.json"), c.NodeConfig) + t.Log(c.LoadFromPath("../example/config.json"), c.NodeConfig) } func TestConf_Watch(t *testing.T) { diff --git a/example/config.json b/example/config.json index c29d2e1..2f83b88 100644 --- a/example/config.json +++ b/example/config.json @@ -1,65 +1,85 @@ { "Log": { + // V2bX 的日志配置,独立于各 Core 的 log 配置 "Level": "error" }, "Cores": [ { "Type": "sing", + // Core类型 "Log": { + // 同 SingBox log 部分配置 "Level": "error", "Timestamp": true } - },{ + // More + }, + { "Type": "xray", "Log": { + // 同 Xray-core log 部分配置 "Level": "error" }, - "DnsConfigPath": "", - ... + "DnsConfigPath": "" + // More } ], "Nodes": [ + // Node配置有两种写法 { + // 写法1 "Core": "sing", + // Core类型 "ApiHost": "http://127.0.0.1", + // API接口地址 "ApiKey": "test", + // API密钥,即Token "NodeID": 33, + // 节点ID "NodeType": "shadowsocks", + // 节点类型 "Timeout": 30, + // 请求超时时间 "RuleListPath": "", + // 本地审计规则 "ListenIP": "0.0.0.0", + // 监听IP "SendIP": "0.0.0.0", + // 发送IP "EnableProxyProtocol": true, - "EnableTFO": true, - ... - },{ - "ApiConfig": { - "ApiHost": "http://127.0.0.1", - "ApiKey": "test", - "NodeID": 33, - "Timeout": 30, - "RuleListPath": "" - }, - "Options": { - "Core": "sing", - "EnableProxyProtocol": true, - "EnableTFO": true, - ... - } - }, - { - "Core": "xray", - "ApiHost": "http://127.0.0.1", - "ApiKey": "test", - "NodeID": 33, - "NodeType": "shadowsocks", - "Timeout": 30, - "RuleListPath": "", - "ListenIP": "0.0.0.0", - "SendIP": "0.0.0.0", - "EnableProxyProtocol": true, - "EnableTFO": true, - ... + // 开启 Proxy Protocol,参见 https://github.com/haproxy/haproxy/blob/master/doc/proxy-protocol.txt + "EnableTFO": true + // 开启 TCP Fast Open + // More } + /*, + { + // 写法2 + "ApiConfig": { + "ApiHost": "http://127.0.0.1", + "ApiKey": "test", + "NodeID": 33, + "Timeout": 30, + "RuleListPath": "" + }, + "Options": { + "Core": "sing", + "EnableProxyProtocol": true, + "EnableTFO": true + } + }, + { + "Core": "xray", + "ApiHost": "http://127.0.0.1", + "ApiKey": "test", + "NodeID": 33, + "NodeType": "shadowsocks", + "Timeout": 30, + "RuleListPath": "", + "ListenIP": "0.0.0.0", + "SendIP": "0.0.0.0", + "EnableProxyProtocol": true, + "EnableTFO": true + }*/ ] } \ No newline at end of file