diff --git a/modules/config/config.go b/modules/config/config.go index c49a160..33930cc 100644 --- a/modules/config/config.go +++ b/modules/config/config.go @@ -8,11 +8,12 @@ import ( "os" "strconv" "strings" - - "github.com/Mrs4s/go-cqhttp/internal/param" + "sync" log "github.com/sirupsen/logrus" "gopkg.in/yaml.v3" + + "github.com/Mrs4s/go-cqhttp/internal/param" ) // defaultConfig 默认配置文件 @@ -69,68 +70,11 @@ type Config struct { Database map[string]yaml.Node `yaml:"database"` } -// MiddleWares 通信中间件 -type MiddleWares struct { - AccessToken string `yaml:"access-token"` - Filter string `yaml:"filter"` - RateLimit struct { - Enabled bool `yaml:"enabled"` - Frequency float64 `yaml:"frequency"` - Bucket int `yaml:"bucket"` - } `yaml:"rate-limit"` -} - -// HTTPServer HTTP通信相关配置 -type HTTPServer struct { - Disabled bool `yaml:"disabled"` - Host string `yaml:"host"` - Port int `yaml:"port"` - Timeout int32 `yaml:"timeout"` - LongPolling struct { - Enabled bool `yaml:"enabled"` - MaxQueueSize int `yaml:"max-queue-size"` - } `yaml:"long-polling"` - Post []struct { - URL string `yaml:"url"` - Secret string `yaml:"secret"` - } - - MiddleWares `yaml:"middlewares"` -} - -// PprofServer pprof性能分析服务器相关配置 -type PprofServer struct { - Disabled bool `yaml:"disabled"` - Host string `yaml:"host"` - Port int `yaml:"port"` -} - -// WebsocketServer 正向WS相关配置 -type WebsocketServer struct { - Disabled bool `yaml:"disabled"` - Host string `yaml:"host"` - Port int `yaml:"port"` - - MiddleWares `yaml:"middlewares"` -} - -// WebsocketReverse 反向WS相关配置 -type WebsocketReverse struct { - Disabled bool `yaml:"disabled"` - Universal string `yaml:"universal"` - API string `yaml:"api"` - Event string `yaml:"event"` - ReconnectInterval int `yaml:"reconnect-interval"` - - MiddleWares `yaml:"middlewares"` -} - -// LambdaServer 云函数配置 -type LambdaServer struct { - Disabled bool `yaml:"disabled"` - Type string `yaml:"type"` - - MiddleWares `yaml:"middlewares"` +// Server 的简介和初始配置 +type Server struct { + Brief string + Default string + ParseEnv func() (string, *yaml.Node) } // LevelDBConfig leveldb 相关配置 @@ -183,161 +127,58 @@ func Parse(path string) *Config { _ = n.Encode(dbConf) return *n }() - accessTokenEnv := os.Getenv("GCQ_ACCESS_TOKEN") - if os.Getenv("GCQ_HTTP_PORT") != "" { - node := &yaml.Node{} - httpConf := &HTTPServer{ - Host: "0.0.0.0", - Port: 5700, - MiddleWares: MiddleWares{ - AccessToken: accessTokenEnv, - }, + + for _, s := range serverconfs { + if s.ParseEnv != nil { + name, node := s.ParseEnv() + if node != nil { + config.Servers = append(config.Servers, map[string]yaml.Node{name: *node}) + } } - param.SetExcludeDefault(&httpConf.Disabled, param.EnsureBool(os.Getenv("GCQ_HTTP_DISABLE"), false), false) - param.SetExcludeDefault(&httpConf.Host, os.Getenv("GCQ_HTTP_HOST"), "") - param.SetExcludeDefault(&httpConf.Port, int(toInt64(os.Getenv("GCQ_HTTP_PORT"))), 0) - if os.Getenv("GCQ_HTTP_POST_URL") != "" { - httpConf.Post = append(httpConf.Post, struct { - URL string `yaml:"url"` - Secret string `yaml:"secret"` - }{os.Getenv("GCQ_HTTP_POST_URL"), os.Getenv("GCQ_HTTP_POST_SECRET")}) - } - _ = node.Encode(httpConf) - config.Servers = append(config.Servers, map[string]yaml.Node{"http": *node}) - } - if os.Getenv("GCQ_WS_PORT") != "" { - node := &yaml.Node{} - wsServerConf := &WebsocketServer{ - Host: "0.0.0.0", - Port: 6700, - MiddleWares: MiddleWares{ - AccessToken: accessTokenEnv, - }, - } - param.SetExcludeDefault(&wsServerConf.Disabled, param.EnsureBool(os.Getenv("GCQ_WS_DISABLE"), false), false) - param.SetExcludeDefault(&wsServerConf.Host, os.Getenv("GCQ_WS_HOST"), "") - param.SetExcludeDefault(&wsServerConf.Port, int(toInt64(os.Getenv("GCQ_WS_PORT"))), 0) - _ = node.Encode(wsServerConf) - config.Servers = append(config.Servers, map[string]yaml.Node{"ws": *node}) - } - if os.Getenv("GCQ_RWS_API") != "" || os.Getenv("GCQ_RWS_EVENT") != "" || os.Getenv("GCQ_RWS_UNIVERSAL") != "" { - node := &yaml.Node{} - rwsConf := &WebsocketReverse{ - MiddleWares: MiddleWares{ - AccessToken: accessTokenEnv, - }, - } - param.SetExcludeDefault(&rwsConf.Disabled, param.EnsureBool(os.Getenv("GCQ_RWS_DISABLE"), false), false) - param.SetExcludeDefault(&rwsConf.API, os.Getenv("GCQ_RWS_API"), "") - param.SetExcludeDefault(&rwsConf.Event, os.Getenv("GCQ_RWS_EVENT"), "") - param.SetExcludeDefault(&rwsConf.Universal, os.Getenv("GCQ_RWS_UNIVERSAL"), "") - _ = node.Encode(rwsConf) - config.Servers = append(config.Servers, map[string]yaml.Node{"ws-reverse": *node}) } } return config } +var serverconfs []*Server +var mu sync.Mutex + +// AddServer 添加该服务的简介和默认配置 +func AddServer(s *Server) { + mu.Lock() + serverconfs = append(serverconfs, s) + mu.Unlock() +} + // generateConfig 生成配置文件 func generateConfig() { fmt.Println("未找到配置文件,正在为您生成配置文件中!") sb := strings.Builder{} sb.WriteString(defaultConfig) - fmt.Print(`请选择你需要的通信方式: -> 1: HTTP通信 -> 2: 正向 Websocket 通信 -> 3: 反向 Websocket 通信 -> 4: pprof 性能分析服务器 -> 5: 云函数服务 -请输入你需要的编号,可输入多个,同一编号也可输入多个(如: 233) -您的选择是:`) + hint := "请选择你需要的通信方式:" + for i, s := range serverconfs { + hint += fmt.Sprintf("\n> %d: %s", i, s.Brief) + } + hint += ` +请输入你需要的编号(0-9),可输入多个,同一编号也可输入多个(如: 233) +您的选择是:` + fmt.Print(hint) input := bufio.NewReader(os.Stdin) readString, err := input.ReadString('\n') if err != nil { log.Fatal("输入不合法: ", err) } + rmax := len(serverconfs) + if rmax > 10 { + rmax = 10 + } for _, r := range readString { - switch r { - case '1': - sb.WriteString(httpDefault) - case '2': - sb.WriteString(wsDefault) - case '3': - sb.WriteString(wsReverseDefault) - case '4': - sb.WriteString(pprofDefault) - case '5': - sb.WriteString(lambdaDefault) + r -= '0' + if r >= 0 && r < rune(rmax) { + sb.WriteString(serverconfs[r].Default) } } _ = os.WriteFile("config.yml", []byte(sb.String()), 0o644) fmt.Println("默认配置文件已生成,请修改 config.yml 后重新启动!") _, _ = input.ReadString('\n') } - -const httpDefault = ` # HTTP 通信设置 - - http: - # 服务端监听地址 - host: 127.0.0.1 - # 服务端监听端口 - port: 5700 - # 反向HTTP超时时间, 单位秒 - # 最小值为5,小于5将会忽略本项设置 - timeout: 5 - # 长轮询拓展 - long-polling: - # 是否开启 - enabled: false - # 消息队列大小,0 表示不限制队列大小,谨慎使用 - max-queue-size: 2000 - middlewares: - <<: *default # 引用默认中间件 - # 反向HTTP POST地址列表 - post: - #- url: '' # 地址 - # secret: '' # 密钥 - #- url: http://127.0.0.1:5701/ # 地址 - # secret: '' # 密钥 -` - -const lambdaDefault = ` # LambdaServer 配置 - - lambda: - type: scf # scf: 腾讯云函数 aws: aws Lambda - middlewares: - <<: *default # 引用默认中间件 -` - -const wsDefault = ` # 正向WS设置 - - ws: - # 正向WS服务器监听地址 - host: 127.0.0.1 - # 正向WS服务器监听端口 - port: 6700 - middlewares: - <<: *default # 引用默认中间件 -` - -const wsReverseDefault = ` # 反向WS设置 - - ws-reverse: - # 反向WS Universal 地址 - # 注意 设置了此项地址后下面两项将会被忽略 - universal: ws://your_websocket_universal.server - # 反向WS API 地址 - api: ws://your_websocket_api.server - # 反向WS Event 地址 - event: ws://your_websocket_event.server - # 重连间隔 单位毫秒 - reconnect-interval: 3000 - middlewares: - <<: *default # 引用默认中间件 -` - -const pprofDefault = ` # pprof 性能分析服务器, 一般情况下不需要启用. - # 如果遇到性能问题请上传报告给开发者处理 - # 注意: pprof服务不支持中间件、不支持鉴权. 请不要开放到公网 - - pprof: - # pprof服务器监听地址 - host: 127.0.0.1 - # pprof服务器监听端口 - port: 7700 -` diff --git a/modules/pprof/pprof.go b/modules/pprof/pprof.go index f12dbed..56e1494 100644 --- a/modules/pprof/pprof.go +++ b/modules/pprof/pprof.go @@ -16,9 +16,33 @@ import ( "github.com/Mrs4s/go-cqhttp/modules/servers" ) +const pprofDefault = ` # pprof 性能分析服务器, 一般情况下不需要启用. + # 如果遇到性能问题请上传报告给开发者处理 + # 注意: pprof服务不支持中间件、不支持鉴权. 请不要开放到公网 + - pprof: + # pprof服务器监听地址 + host: 127.0.0.1 + # pprof服务器监听端口 + port: 7700 +` + +// pprofServer pprof性能分析服务器相关配置 +type pprofServer struct { + Disabled bool `yaml:"disabled"` + Host string `yaml:"host"` + Port int `yaml:"port"` +} + +func init() { + config.AddServer(&config.Server{ + Brief: "pprof 性能分析服务器", + Default: pprofDefault, + }) +} + // runPprof 启动 pprof 性能分析服务器 func runPprof(_ *coolq.CQBot, node yaml.Node) { - var conf config.PprofServer + var conf pprofServer switch err := node.Decode(&conf); { case err != nil: log.Warn("读取pprof配置失败 :", err) diff --git a/modules/servers/servers.go b/modules/servers/servers.go index 579f21b..7a8ae28 100644 --- a/modules/servers/servers.go +++ b/modules/servers/servers.go @@ -8,7 +8,10 @@ import ( "github.com/Mrs4s/go-cqhttp/internal/base" ) -var svr = make(map[string]func(*coolq.CQBot, yaml.Node)) +var ( + svr = make(map[string]func(*coolq.CQBot, yaml.Node)) + nocfgsvr = make(map[string]func(*coolq.CQBot)) +) // Register 注册 Server func Register(name string, proc func(*coolq.CQBot, yaml.Node)) { @@ -19,6 +22,15 @@ func Register(name string, proc func(*coolq.CQBot, yaml.Node)) { svr[name] = proc } +// RegisterCustom 注册无需 config 的自定义 Server +func RegisterCustom(name string, proc func(*coolq.CQBot)) { + _, ok := nocfgsvr[name] + if ok { + panic(name + " server has existed") + } + nocfgsvr[name] = proc +} + // Run 运行所有svr func Run(bot *coolq.CQBot) { for _, l := range base.Servers { @@ -28,4 +40,7 @@ func Run(bot *coolq.CQBot) { } } } + for _, fn := range nocfgsvr { + go fn(bot) + } } diff --git a/server/http.go b/server/http.go index 2c4c811..fc233c2 100644 --- a/server/http.go +++ b/server/http.go @@ -24,11 +24,30 @@ import ( "github.com/Mrs4s/go-cqhttp/coolq" "github.com/Mrs4s/go-cqhttp/global" + "github.com/Mrs4s/go-cqhttp/internal/param" "github.com/Mrs4s/go-cqhttp/modules/api" "github.com/Mrs4s/go-cqhttp/modules/config" "github.com/Mrs4s/go-cqhttp/modules/filter" ) +// HTTPServer HTTP通信相关配置 +type HTTPServer struct { + Disabled bool `yaml:"disabled"` + Host string `yaml:"host"` + Port int `yaml:"port"` + Timeout int32 `yaml:"timeout"` + LongPolling struct { + Enabled bool `yaml:"enabled"` + MaxQueueSize int `yaml:"max-queue-size"` + } `yaml:"long-polling"` + Post []struct { + URL string `yaml:"url"` + Secret string `yaml:"secret"` + } + + MiddleWares `yaml:"middlewares"` +} + type httpServer struct { HTTP *http.Server api *api.Caller @@ -51,6 +70,68 @@ type httpCtx struct { postForm url.Values } +const httpDefault = ` # HTTP 通信设置 + - http: + # 服务端监听地址 + host: 127.0.0.1 + # 服务端监听端口 + port: 5700 + # 反向HTTP超时时间, 单位秒 + # 最小值为5,小于5将会忽略本项设置 + timeout: 5 + # 长轮询拓展 + long-polling: + # 是否开启 + enabled: false + # 消息队列大小,0 表示不限制队列大小,谨慎使用 + max-queue-size: 2000 + middlewares: + <<: *default # 引用默认中间件 + # 反向HTTP POST地址列表 + post: + #- url: '' # 地址 + # secret: '' # 密钥 + #- url: http://127.0.0.1:5701/ # 地址 + # secret: '' # 密钥 +` + +func init() { + config.AddServer(&config.Server{ + Brief: "HTTP通信", + Default: httpDefault, + ParseEnv: func() (string, *yaml.Node) { + if os.Getenv("GCQ_HTTP_PORT") != "" { + // type convert tools + toInt64 := func(str string) int64 { + i, _ := strconv.ParseInt(str, 10, 64) + return i + } + accessTokenEnv := os.Getenv("GCQ_ACCESS_TOKEN") + node := &yaml.Node{} + httpConf := &HTTPServer{ + Host: "0.0.0.0", + Port: 5700, + MiddleWares: MiddleWares{ + AccessToken: accessTokenEnv, + }, + } + param.SetExcludeDefault(&httpConf.Disabled, param.EnsureBool(os.Getenv("GCQ_HTTP_DISABLE"), false), false) + param.SetExcludeDefault(&httpConf.Host, os.Getenv("GCQ_HTTP_HOST"), "") + param.SetExcludeDefault(&httpConf.Port, int(toInt64(os.Getenv("GCQ_HTTP_PORT"))), 0) + if os.Getenv("GCQ_HTTP_POST_URL") != "" { + httpConf.Post = append(httpConf.Post, struct { + URL string `yaml:"url"` + Secret string `yaml:"secret"` + }{os.Getenv("GCQ_HTTP_POST_URL"), os.Getenv("GCQ_HTTP_POST_SECRET")}) + } + _ = node.Encode(httpConf) + return "http", node + } + return "", nil + }, + }) +} + func (h *httpCtx) Get(s string) gjson.Result { j := h.json.Get(s) if j.Exists() { @@ -163,7 +244,7 @@ func checkAuth(req *http.Request, token string) int { // runHTTP 启动HTTP服务器与HTTP上报客户端 func runHTTP(bot *coolq.CQBot, node yaml.Node) { - var conf config.HTTPServer + var conf HTTPServer switch err := node.Decode(&conf); { case err != nil: log.Warn("读取http配置失败 :", err) diff --git a/server/middlewares.go b/server/middlewares.go index aff1a8d..d7835a4 100644 --- a/server/middlewares.go +++ b/server/middlewares.go @@ -13,6 +13,17 @@ import ( "golang.org/x/time/rate" ) +// MiddleWares 通信中间件 +type MiddleWares struct { + AccessToken string `yaml:"access-token"` + Filter string `yaml:"filter"` + RateLimit struct { + Enabled bool `yaml:"enabled"` + Frequency float64 `yaml:"frequency"` + Bucket int `yaml:"bucket"` + } `yaml:"rate-limit"` +} + func rateLimit(frequency float64, bucketSize int) api.Handler { limiter := rate.NewLimiter(rate.Limit(frequency), bucketSize) return func(_ string, _ api.Getter) global.MSG { diff --git a/server/scf.go b/server/scf.go index d863419..29546ae 100644 --- a/server/scf.go +++ b/server/scf.go @@ -81,7 +81,7 @@ var cli *lambdaClient // runLambda type: [scf,aws] func runLambda(bot *coolq.CQBot, node yaml.Node) { - var conf config.LambdaServer + var conf LambdaServer switch err := node.Decode(&conf); { case err != nil: log.Warn("读取lambda配置失败 :", err) @@ -155,6 +155,28 @@ type lambdaInvoke struct { } `json:"requestContext"` } +const lambdaDefault = ` # LambdaServer 配置 + - lambda: + type: scf # scf: 腾讯云函数 aws: aws Lambda + middlewares: + <<: *default # 引用默认中间件 +` + +// LambdaServer 云函数配置 +type LambdaServer struct { + Disabled bool `yaml:"disabled"` + Type string `yaml:"type"` + + MiddleWares `yaml:"middlewares"` +} + +func init() { + config.AddServer(&config.Server{ + Brief: "云函数服务", + Default: lambdaDefault, + }) +} + func (c *lambdaClient) next() *http.Request { r, err := http.NewRequest(http.MethodGet, c.nextURL, nil) if err != nil { diff --git a/server/websocket.go b/server/websocket.go index 38fb96a..6cdf658 100644 --- a/server/websocket.go +++ b/server/websocket.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "net/http" + "os" "runtime/debug" "strconv" "strings" @@ -19,6 +20,7 @@ import ( "github.com/Mrs4s/go-cqhttp/coolq" "github.com/Mrs4s/go-cqhttp/global" + "github.com/Mrs4s/go-cqhttp/internal/param" "github.com/Mrs4s/go-cqhttp/modules/api" "github.com/Mrs4s/go-cqhttp/modules/config" "github.com/Mrs4s/go-cqhttp/modules/filter" @@ -26,7 +28,7 @@ import ( type webSocketServer struct { bot *coolq.CQBot - conf *config.WebsocketServer + conf *WebsocketServer mu sync.Mutex eventConn []*wsConn @@ -71,9 +73,107 @@ var upgrader = websocket.Upgrader{ }, } +const wsDefault = ` # 正向WS设置 + - ws: + # 正向WS服务器监听地址 + host: 127.0.0.1 + # 正向WS服务器监听端口 + port: 6700 + middlewares: + <<: *default # 引用默认中间件 +` + +const wsReverseDefault = ` # 反向WS设置 + - ws-reverse: + # 反向WS Universal 地址 + # 注意 设置了此项地址后下面两项将会被忽略 + universal: ws://your_websocket_universal.server + # 反向WS API 地址 + api: ws://your_websocket_api.server + # 反向WS Event 地址 + event: ws://your_websocket_event.server + # 重连间隔 单位毫秒 + reconnect-interval: 3000 + middlewares: + <<: *default # 引用默认中间件 +` + +// WebsocketServer 正向WS相关配置 +type WebsocketServer struct { + Disabled bool `yaml:"disabled"` + Host string `yaml:"host"` + Port int `yaml:"port"` + + MiddleWares `yaml:"middlewares"` +} + +// WebsocketReverse 反向WS相关配置 +type WebsocketReverse struct { + Disabled bool `yaml:"disabled"` + Universal string `yaml:"universal"` + API string `yaml:"api"` + Event string `yaml:"event"` + ReconnectInterval int `yaml:"reconnect-interval"` + + MiddleWares `yaml:"middlewares"` +} + +func init() { + config.AddServer(&config.Server{ + Brief: "正向 Websocket 通信", + Default: wsDefault, + ParseEnv: func() (string, *yaml.Node) { + if os.Getenv("GCQ_WS_PORT") != "" { + // type convert tools + toInt64 := func(str string) int64 { + i, _ := strconv.ParseInt(str, 10, 64) + return i + } + accessTokenEnv := os.Getenv("GCQ_ACCESS_TOKEN") + node := &yaml.Node{} + wsServerConf := &WebsocketServer{ + Host: "0.0.0.0", + Port: 6700, + MiddleWares: MiddleWares{ + AccessToken: accessTokenEnv, + }, + } + param.SetExcludeDefault(&wsServerConf.Disabled, param.EnsureBool(os.Getenv("GCQ_WS_DISABLE"), false), false) + param.SetExcludeDefault(&wsServerConf.Host, os.Getenv("GCQ_WS_HOST"), "") + param.SetExcludeDefault(&wsServerConf.Port, int(toInt64(os.Getenv("GCQ_WS_PORT"))), 0) + _ = node.Encode(wsServerConf) + return "ws", node + } + return "", nil + }, + }) + config.AddServer(&config.Server{ + Brief: "反向 Websocket 通信", + Default: wsReverseDefault, + ParseEnv: func() (string, *yaml.Node) { + if os.Getenv("GCQ_RWS_API") != "" || os.Getenv("GCQ_RWS_EVENT") != "" || os.Getenv("GCQ_RWS_UNIVERSAL") != "" { + accessTokenEnv := os.Getenv("GCQ_ACCESS_TOKEN") + node := &yaml.Node{} + rwsConf := &WebsocketReverse{ + MiddleWares: MiddleWares{ + AccessToken: accessTokenEnv, + }, + } + param.SetExcludeDefault(&rwsConf.Disabled, param.EnsureBool(os.Getenv("GCQ_RWS_DISABLE"), false), false) + param.SetExcludeDefault(&rwsConf.API, os.Getenv("GCQ_RWS_API"), "") + param.SetExcludeDefault(&rwsConf.Event, os.Getenv("GCQ_RWS_EVENT"), "") + param.SetExcludeDefault(&rwsConf.Universal, os.Getenv("GCQ_RWS_UNIVERSAL"), "") + _ = node.Encode(rwsConf) + return "ws-reverse", node + } + return "", nil + }, + }) +} + // runWSServer 运行一个正向WS server func runWSServer(b *coolq.CQBot, node yaml.Node) { - var conf config.WebsocketServer + var conf WebsocketServer switch err := node.Decode(&conf); { case err != nil: log.Warn("读取正向Websocket配置失败 :", err) @@ -103,7 +203,7 @@ func runWSServer(b *coolq.CQBot, node yaml.Node) { // runWSClient 运行一个反向向WS client func runWSClient(b *coolq.CQBot, node yaml.Node) { - var conf config.WebsocketReverse + var conf WebsocketReverse switch err := node.Decode(&conf); { case err != nil: log.Warn("读取反向Websocket配置失败 :", err)