From a6a1de0a00b62101430864f4ecbeaea179d94fe3 Mon Sep 17 00:00:00 2001 From: scjtqs Date: Wed, 30 Sep 2020 20:10:59 +0800 Subject: [PATCH 1/7] =?UTF-8?q?admin=20api=20=E5=88=9D=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- global/config.go | 12 ++ global/fs.go | 13 ++ global/net.go | 1 - main.go | 118 ++--------- server/apiAdmin.go | 485 +++++++++++++++++++++++++++++++++++++++++++++ server/http.go | 29 ++- 6 files changed, 552 insertions(+), 106 deletions(-) create mode 100644 server/apiAdmin.go diff --git a/global/config.go b/global/config.go index 31ed776..4277433 100644 --- a/global/config.go +++ b/global/config.go @@ -35,6 +35,7 @@ type JsonConfig struct { PostMessageFormat string `json:"post_message_format"` Debug bool `json:"debug"` LogLevel string `json:"log_level"` + WebUi *GoCqWebUi `json:"web_ui"` } type CQHttpApiConfig struct { @@ -78,6 +79,12 @@ type GoCQReverseWebsocketConfig struct { ReverseReconnectInterval uint16 `json:"reverse_reconnect_interval"` } +type GoCqWebUi struct { + Enabled bool `json:"enabled"` + WebUiPort uint64 `json:"web_ui_port"` + WebInput bool `json:"webinput"` +} + func DefaultConfig() *JsonConfig { return &JsonConfig{ EnableDB: true, @@ -121,6 +128,11 @@ func DefaultConfig() *JsonConfig { ReverseReconnectInterval: 3000, }, }, + WebUi: &GoCqWebUi{ + Enabled: true, + WebInput: false, + WebUiPort: 9999, + }, } } diff --git a/global/fs.go b/global/fs.go index 9aa164d..5af0e07 100644 --- a/global/fs.go +++ b/global/fs.go @@ -93,3 +93,16 @@ func FindFile(f, cache, PATH string) (data []byte, err error) { } return } + +func DelFile(path string) bool { + err := os.Remove(path) + if err != nil { + // 删除失败 + log.Error(err) + return false + } else { + // 删除成功 + log.Info(path + "删除成功") + return true + } +} diff --git a/global/net.go b/global/net.go index 55f27de..7691e89 100644 --- a/global/net.go +++ b/global/net.go @@ -50,4 +50,3 @@ func NeteaseMusicSongInfo(id string) (gjson.Result, error) { } return gjson.ParseBytes(d).Get("songs.0"), nil } - diff --git a/main.go b/main.go index 4e8d99b..15f10ab 100644 --- a/main.go +++ b/main.go @@ -2,32 +2,27 @@ package main import ( "bufio" - "bytes" "crypto/md5" "encoding/base64" "encoding/json" "fmt" - "image" + "github.com/Mrs4s/go-cqhttp/server" "io" "io/ioutil" "os" "os/signal" "path" "strconv" - "strings" "time" "github.com/Mrs4s/MiraiGo/binary" "github.com/Mrs4s/MiraiGo/client" "github.com/Mrs4s/go-cqhttp/coolq" "github.com/Mrs4s/go-cqhttp/global" - "github.com/Mrs4s/go-cqhttp/server" - - rotatelogs "github.com/lestrrat-go/file-rotatelogs" + "github.com/lestrrat-go/file-rotatelogs" "github.com/rifflock/lfshook" log "github.com/sirupsen/logrus" - easy "github.com/t-tomalak/logrus-easy-formatter" - asciiart "github.com/yinghau76/go-ascii-art" + "github.com/t-tomalak/logrus-easy-formatter" ) func init() { @@ -238,105 +233,22 @@ func main() { cli.OnServerUpdated(func(bot *client.QQClient, e *client.ServerUpdatedEvent) { log.Infof("收到服务器地址更新通知, 将在下一次重连时应用. ") }) - rsp, err := cli.Login() - for { - global.Check(err) - if !rsp.Success { - switch rsp.Error { - case client.NeedCaptcha: - _ = ioutil.WriteFile("captcha.jpg", rsp.CaptchaImage, 0644) - img, _, _ := image.Decode(bytes.NewReader(rsp.CaptchaImage)) - fmt.Println(asciiart.New("image", img).Art) - log.Warn("请输入验证码 (captcha.jpg): (Enter 提交)") - text, _ := console.ReadString('\n') - rsp, err = cli.SubmitCaptcha(strings.ReplaceAll(text, "\n", ""), rsp.CaptchaSign) - continue - case client.UnsafeDeviceError: - log.Warnf("账号已开启设备锁,请前往 -> %v <- 验证并重启Bot.", rsp.VerifyUrl) - log.Infof(" 按 Enter 继续....") - _, _ = console.ReadString('\n') - return - case client.OtherLoginError, client.UnknownLoginError: - log.Fatalf("登录失败: %v", rsp.ErrorMessage) - } - } - break - } - log.Infof("登录成功 欢迎使用: %v", cli.Nickname) - time.Sleep(time.Second) - log.Info("开始加载好友列表...") - global.Check(cli.ReloadFriendList()) - log.Infof("共加载 %v 个好友.", len(cli.FriendList)) - log.Infof("开始加载群列表...") - global.Check(cli.ReloadGroupList()) - log.Infof("共加载 %v 个群.", len(cli.GroupList)) - b := coolq.NewQQBot(cli, conf) - if conf.PostMessageFormat != "string" && conf.PostMessageFormat != "array" { - log.Warnf("post_message_format 配置错误, 将自动使用 string") - coolq.SetMessageFormat("string") - } else { - coolq.SetMessageFormat(conf.PostMessageFormat) - } - if conf.RateLimit.Enabled { - global.InitLimiter(conf.RateLimit.Frequency, conf.RateLimit.BucketSize) - } - log.Info("正在加载事件过滤器.") - global.BootFilter() - coolq.IgnoreInvalidCQCode = conf.IgnoreInvalidCQCode - coolq.ForceFragmented = conf.ForceFragmented - if conf.HttpConfig != nil && conf.HttpConfig.Enabled { - server.HttpServer.Run(fmt.Sprintf("%s:%d", conf.HttpConfig.Host, conf.HttpConfig.Port), conf.AccessToken, b) - for k, v := range conf.HttpConfig.PostUrls { - server.NewHttpClient().Run(k, v, conf.HttpConfig.Timeout, b) + if conf.WebUi == nil { + conf.WebUi = &global.GoCqWebUi{ + Enabled: true, + WebInput: false, + WebUiPort: 9999, } } - if conf.WSConfig != nil && conf.WSConfig.Enabled { - server.WebsocketServer.Run(fmt.Sprintf("%s:%d", conf.WSConfig.Host, conf.WSConfig.Port), conf.AccessToken, b) + if conf.WebUi.WebUiPort <= 0 { + conf.WebUi.WebUiPort = 9999 } - for _, rc := range conf.ReverseServers { - server.NewWebsocketClient(rc, conf.AccessToken, b).Run() + confErr := conf.Save("config.json") + if confErr != nil { + log.Error("保存配置文件失败") } - log.Info("资源初始化完成, 开始处理信息.") - log.Info("アトリは、高性能ですから!") - cli.OnDisconnected(func(bot *client.QQClient, e *client.ClientDisconnectedEvent) { - if conf.ReLogin.Enabled { - var times uint = 1 - for { - - if conf.ReLogin.MaxReloginTimes == 0 { - } else if times > conf.ReLogin.MaxReloginTimes { - break - } - log.Warnf("Bot已离线 (%v),将在 %v 秒后尝试重连. 重连次数:%v", - e.Message, conf.ReLogin.ReLoginDelay, times) - times++ - time.Sleep(time.Second * time.Duration(conf.ReLogin.ReLoginDelay)) - rsp, err := cli.Login() - if err != nil { - log.Errorf("重连失败: %v", err) - continue - } - if !rsp.Success { - switch rsp.Error { - case client.NeedCaptcha: - log.Fatalf("重连失败: 需要验证码. (验证码处理正在开发中)") - case client.UnsafeDeviceError: - log.Fatalf("重连失败: 设备锁") - default: - log.Errorf("重连失败: %v", rsp.ErrorMessage) - continue - } - } - log.Info("重连成功") - return - - } - log.Fatal("重连失败: 重连次数达到设置的上限值") - } - b.Release() - log.Fatalf("Bot已离线:%v", e.Message) - }) - c := make(chan os.Signal, 1) + b := server.WebServer.Run(fmt.Sprintf("%s:%d", "0.0.0.0", conf.WebUi.WebUiPort), cli) + c := server.Console signal.Notify(c, os.Interrupt, os.Kill) <-c b.Release() diff --git a/server/apiAdmin.go b/server/apiAdmin.go new file mode 100644 index 0000000..221517c --- /dev/null +++ b/server/apiAdmin.go @@ -0,0 +1,485 @@ +package server + +import ( + "bufio" + "bytes" + "encoding/base64" + "encoding/json" + "fmt" + "github.com/Mrs4s/MiraiGo/client" + "github.com/Mrs4s/go-cqhttp/coolq" + "github.com/Mrs4s/go-cqhttp/global" + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" + "github.com/tidwall/gjson" + "github.com/yinghau76/go-ascii-art" + "image" + "io/ioutil" + "net/http" + "os" + "strconv" + "strings" + "time" +) + +var WebInput = make(chan string, 1) //长度1,用于阻塞 + +var Console = make(chan os.Signal, 1) + +var Jsonconfig *global.JsonConfig + +type webServer struct { + engine *gin.Engine + bot *coolq.CQBot + Cli *client.QQClient + Conf *global.JsonConfig //old config + Console *bufio.Reader +} + +var WebServer = &webServer{} + +// admin 子站的 路由映射 +var HttpuriAdmin = map[string]func(s *webServer, c *gin.Context){ + "do_restart": AdminDoRestart, //热重启 + "get_web_write": AdminWebWrite, //获取是否验证码输入 + "do_web_write": AdminDoWebWrite, //web上进行输入操作 + "do_restart_docker": AdminDoRestartDocker, //直接停止(依赖supervisord/docker)重新拉起 + "do_config_base": AdminDoConfigBase, //修改config.json中的基础部分 + "do_config_http": AdminDoConfigHttp, //修改config.json的http部分 + "do_config_ws": AdminDoConfigWs, //修改config.json的正向ws部分 + "do_config_reverse": AdminDoConfigReverse, //修改config.json 中的反向ws部分 + "do_config_json": AdminDoConfigJson, //直接修改 config.json配置 + "get_config_json": AdminDoConfigJson, //拉取 当前的config.json配置 +} + +func Failed(code int, msg string) coolq.MSG { + return coolq.MSG{"data": nil, "retcode": code, "status": "failed", "msg": msg} +} + +func (s *webServer) Run(addr string, cli *client.QQClient) *coolq.CQBot { + s.Cli = cli + s.Conf = GetConf() + Jsonconfig = s.Conf + gin.SetMode(gin.ReleaseMode) + s.engine = gin.New() + + s.engine.Use(AuthMiddleWare()) + + //通用路由 + s.engine.Any("/admin/:action", s.admin) + + go func() { + log.Infof("miraigo adminapi 服务器已启动: %v", addr) + err := s.engine.Run(addr) + if err != nil { + log.Error(err) + log.Infof("请检查端口是否被占用.") + time.Sleep(time.Second * 5) + os.Exit(1) + } + }() + s.Dologin() + s.UpServer() + b := s.bot //外部引入 bot对象,用于操作bot + return b +} + +func (s *webServer) Dologin() { + s.Console = bufio.NewReader(os.Stdin) + conf := GetConf() + cli := s.Cli + rsp, err := cli.Login() + for { + global.Check(err) + var text string + if !rsp.Success { + switch rsp.Error { + case client.NeedCaptcha: + _ = ioutil.WriteFile("captcha.jpg", rsp.CaptchaImage, 0644) + img, _, _ := image.Decode(bytes.NewReader(rsp.CaptchaImage)) + fmt.Println(asciiart.New("image", img).Art) + if conf.WebUi.WebInput { + log.Warn("请输入验证码 (captcha.jpg): (http://127.0.0.1/admin/web_write 输入)") + text = <-WebInput + } else { + log.Warn("请输入验证码 (captcha.jpg): (Enter 提交)") + text, _ = s.Console.ReadString('\n') + } + rsp, err = cli.SubmitCaptcha(strings.ReplaceAll(text, "\n", ""), rsp.CaptchaSign) + global.DelFile("captcha.jpg") + continue + case client.UnsafeDeviceError: + log.Warnf("账号已开启设备锁,请前往 -> %v <- 验证并重启Bot.", rsp.VerifyUrl) + if conf.WebUi.WebInput { + log.Infof(" (http://127.0.0.1/admin/web_write 确认后继续)....") + text = <-WebInput + } else { + log.Infof(" 按 Enter 继续....") + _, _ = s.Console.ReadString('\n') + } + log.Info(text) + return + case client.OtherLoginError, client.UnknownLoginError: + log.Fatalf("登录失败: %v", rsp.ErrorMessage) + return + } + } + break + } + log.Infof("登录成功 欢迎使用: %v", cli.Nickname) + time.Sleep(time.Second) + log.Info("开始加载好友列表...") + global.Check(cli.ReloadFriendList()) + log.Infof("共加载 %v 个好友.", len(cli.FriendList)) + log.Infof("开始加载群列表...") + global.Check(cli.ReloadGroupList()) + log.Infof("共加载 %v 个群.", len(cli.GroupList)) + s.bot = coolq.NewQQBot(cli, conf) + if conf.PostMessageFormat != "string" && conf.PostMessageFormat != "array" { + log.Warnf("post_message_format 配置错误, 将自动使用 string") + coolq.SetMessageFormat("string") + return + } else { + coolq.SetMessageFormat(conf.PostMessageFormat) + } + if conf.RateLimit.Enabled { + global.InitLimiter(conf.RateLimit.Frequency, conf.RateLimit.BucketSize) + } + log.Info("正在加载事件过滤器.") + global.BootFilter() + coolq.IgnoreInvalidCQCode = conf.IgnoreInvalidCQCode + coolq.ForceFragmented = conf.ForceFragmented + log.Info("资源初始化完成, 开始处理信息.") + log.Info("アトリは、高性能ですから!") + cli.OnDisconnected(func(bot *client.QQClient, e *client.ClientDisconnectedEvent) { + if conf.ReLogin.Enabled { + var times uint = 1 + for { + if conf.ReLogin.MaxReloginTimes == 0 { + } else if times > conf.ReLogin.MaxReloginTimes { + break + } + log.Warnf("Bot已离线 (%v),将在 %v 秒后尝试重连. 重连次数:%v", + e.Message, conf.ReLogin.ReLoginDelay, times) + times++ + time.Sleep(time.Second * time.Duration(conf.ReLogin.ReLoginDelay)) + rsp, err := cli.Login() + if err != nil { + log.Errorf("重连失败: %v", err) + continue + } + if !rsp.Success { + switch rsp.Error { + case client.NeedCaptcha: + log.Fatalf("重连失败: 需要验证码. (验证码处理正在开发中)") + case client.UnsafeDeviceError: + log.Fatalf("重连失败: 设备锁") + default: + log.Errorf("重连失败: %v", rsp.ErrorMessage) + continue + } + } + log.Info("重连成功") + return + } + log.Fatal("重连失败: 重连次数达到设置的上限值") + } + s.bot.Release() + log.Fatalf("Bot已离线:%v", e.Message) + }) +} + +func (s *webServer) admin(c *gin.Context) { + action := c.Param("action") + log.Debugf("WebServer接收到cgi调用: %v", action) + if f, ok := HttpuriAdmin[action]; ok { + f(s, c) + } else { + c.JSON(200, coolq.Failed(404)) + } +} + +// 获取当前配置文件信息 +func GetConf() *global.JsonConfig { + if Jsonconfig != nil { + return Jsonconfig + } + conf := global.Load("config.json") + return conf +} + +// admin 控制器 登录验证 +func AuthMiddleWare() gin.HandlerFunc { + return func(c *gin.Context) { + conf := GetConf() + //处理跨域问题 + c.Header("Access-Control-Allow-Origin", "*") + c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token") + c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, PATCH, DELETE") + c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type") + c.Header("Access-Control-Allow-Credentials", "true") + // 放行所有OPTIONS方法,因为有的模板是要请求两次的 + if c.Request.Method == "OPTIONS" { + c.AbortWithStatus(http.StatusNoContent) + } + // 处理请求 + if c.Request.Method != "GET" && c.Request.Method != "POST" { + log.Warnf("已拒绝客户端 %v 的请求: 方法错误", c.Request.RemoteAddr) + c.Status(404) + c.Abort() + } + if c.Request.Method == "POST" && strings.Contains(c.Request.Header.Get("Content-Type"), "application/json") { + d, err := c.GetRawData() + if err != nil { + log.Warnf("获取请求 %v 的Body时出现错误: %v", c.Request.RequestURI, err) + c.Status(400) + c.Abort() + } + if !gjson.ValidBytes(d) { + log.Warnf("已拒绝客户端 %v 的请求: 非法Json", c.Request.RemoteAddr) + c.Status(400) + c.Abort() + } + c.Set("json_body", gjson.ParseBytes(d)) + } + authToken := conf.AccessToken + if auth := c.Request.Header.Get("Authorization"); auth != "" { + if strings.SplitN(auth, " ", 2)[1] != authToken { + c.AbortWithStatus(401) + return + } + } else if c.Query("access_token") != authToken { + c.AbortWithStatus(401) + return + } else { + c.Next() + } + } +} + +func (s *webServer) DoRelogin() { + Jsonconfig = nil + conf := GetConf() + OldConf := s.Conf + cli := client.NewClient(conf.Uin, conf.Password) + log.Info("开始尝试登录并同步消息...") + log.Infof("使用协议: %v", func() string { + switch client.SystemDeviceInfo.Protocol { + case client.AndroidPad: + return "Android Pad" + case client.AndroidPhone: + return "Android Phone" + case client.AndroidWatch: + return "Android Watch" + } + return "未知" + }()) + cli.OnLog(func(c *client.QQClient, e *client.LogEvent) { + switch e.Type { + case "INFO": + log.Info("Protocol -> " + e.Message) + case "ERROR": + log.Error("Protocol -> " + e.Message) + case "DEBUG": + log.Debug("Protocol -> " + e.Message) + } + }) + cli.OnServerUpdated(func(bot *client.QQClient, e *client.ServerUpdatedEvent) { + log.Infof("收到服务器地址更新通知, 将在下一次重连时应用. ") + }) + s.Cli = cli + s.Dologin() + //关闭之前的 server + if OldConf.HttpConfig != nil && OldConf.HttpConfig.Enabled { + HttpServer.ShutDown() + } + //if OldConf.WSConfig != nil && OldConf.WSConfig.Enabled { + // server.WsShutdown() + //} + //s.UpServer() + s.ReloadServer() + s.Conf = conf +} + +func (s *webServer) UpServer() { + conf := GetConf() + if conf.HttpConfig != nil && conf.HttpConfig.Enabled { + go HttpServer.Run(fmt.Sprintf("%s:%d", conf.HttpConfig.Host, conf.HttpConfig.Port), conf.AccessToken, s.bot) + for k, v := range conf.HttpConfig.PostUrls { + NewHttpClient().Run(k, v, conf.HttpConfig.Timeout, s.bot) + } + } + if conf.WSConfig != nil && conf.WSConfig.Enabled { + go WebsocketServer.Run(fmt.Sprintf("%s:%d", conf.WSConfig.Host, conf.WSConfig.Port), conf.AccessToken, s.bot) + } + for _, rc := range conf.ReverseServers { + go NewWebsocketClient(rc, conf.AccessToken, s.bot).Run() + } +} + +// 暂不支持ws服务的重启 +func (s *webServer) ReloadServer() { + conf := GetConf() + if conf.HttpConfig != nil && conf.HttpConfig.Enabled { + go HttpServer.Run(fmt.Sprintf("%s:%d", conf.HttpConfig.Host, conf.HttpConfig.Port), conf.AccessToken, s.bot) + for k, v := range conf.HttpConfig.PostUrls { + NewHttpClient().Run(k, v, conf.HttpConfig.Timeout, s.bot) + } + } + for _, rc := range conf.ReverseServers { + go NewWebsocketClient(rc, conf.AccessToken, s.bot).Run() + } +} + +// 热重启 +func AdminDoRestart(s *webServer, c *gin.Context) { + s.bot = nil + s.Cli = nil + s.DoRelogin() + c.JSON(200, coolq.OK(coolq.MSG{})) + return +} + +// 冷重启 +func AdminDoRestartDocker(s *webServer, c *gin.Context) { + Console <- os.Kill + c.JSON(200, coolq.OK(coolq.MSG{})) + return +} + +// web输入 html 页面 +func AdminWebWrite(s *webServer, c *gin.Context) { + pic := global.ReadAllText("captcha.jpg") + var picbase64 string + var ispic = false + if pic != "" { + input := []byte(pic) + // base64编码 + picbase64 = base64.StdEncoding.EncodeToString(input) + ispic = true + } + c.JSON(200, coolq.OK(coolq.MSG{ + "ispic": ispic, //为空则为 设备锁 或者没有需要输入 + "picbase64": picbase64, //web上显示图片 + })) +} + +// web输入 处理 +func AdminDoWebWrite(s *webServer, c *gin.Context) { + input := c.PostForm("input") + WebInput <- input + c.JSON(200, coolq.OK(coolq.MSG{})) +} + +// 普通配置修改 +func AdminDoConfigBase(s *webServer, c *gin.Context) { + conf := GetConf() + conf.Uin, _ = strconv.ParseInt(c.PostForm("uin"), 10, 64) + conf.Password = c.PostForm("password") + if c.PostForm("enable_db") == "true" { + conf.EnableDB = true + } else { + conf.EnableDB = false + } + conf.AccessToken = c.PostForm("access_token") + if err := conf.Save("config.json"); err != nil { + log.Fatalf("保存 config.json 时出现错误: %v", err) + c.JSON(200, Failed(502, "保存 config.json 时出现错误:"+fmt.Sprintf("%v", err))) + } else { + Jsonconfig = nil + c.JSON(200, coolq.OK(coolq.MSG{})) + } +} + +// http配置修改 +func AdminDoConfigHttp(s *webServer, c *gin.Context) { + conf := GetConf() + p, _ := strconv.ParseUint(c.PostForm("port"), 10, 16) + conf.HttpConfig.Port = uint16(p) + conf.HttpConfig.Host = c.PostForm("host") + if c.PostForm("enable") == "true" { + conf.HttpConfig.Enabled = true + } else { + conf.HttpConfig.Enabled = false + } + t, _ := strconv.ParseInt(c.PostForm("timeout"), 10, 32) + conf.HttpConfig.Timeout = int32(t) + if c.PostForm("post_url") != "" { + conf.HttpConfig.PostUrls[c.PostForm("post_url")] = c.PostForm("post_secret") + } + if err := conf.Save("config.json"); err != nil { + log.Fatalf("保存 config.json 时出现错误: %v", err) + c.JSON(200, Failed(502, "保存 config.json 时出现错误:"+fmt.Sprintf("%v", err))) + } else { + Jsonconfig = nil + c.JSON(200, coolq.OK(coolq.MSG{})) + } +} + +// ws配置修改 +func AdminDoConfigWs(s *webServer, c *gin.Context) { + conf := GetConf() + p, _ := strconv.ParseUint(c.PostForm("port"), 10, 16) + conf.WSConfig.Port = uint16(p) + conf.WSConfig.Host = c.PostForm("host") + if c.PostForm("enable") == "true" { + conf.WSConfig.Enabled = true + } else { + conf.WSConfig.Enabled = false + } + if err := conf.Save("config.json"); err != nil { + log.Fatalf("保存 config.json 时出现错误: %v", err) + c.JSON(200, Failed(502, "保存 config.json 时出现错误:"+fmt.Sprintf("%v", err))) + } else { + Jsonconfig = nil + c.JSON(200, coolq.OK(coolq.MSG{})) + } +} + +// 反向ws配置修改 +func AdminDoConfigReverse(s *webServer, c *gin.Context) { + conf := GetConf() + conf.ReverseServers[0].ReverseApiUrl = c.PostForm("reverse_api_url") + conf.ReverseServers[0].ReverseUrl = c.PostForm("reverse_url") + conf.ReverseServers[0].ReverseEventUrl = c.PostForm("reverse_event_url") + t, _ := strconv.ParseUint(c.PostForm("reverse_reconnect_interval"), 10, 16) + conf.ReverseServers[0].ReverseReconnectInterval = uint16(t) + if c.PostForm("enable") == "true" { + conf.ReverseServers[0].Enabled = true + } else { + conf.ReverseServers[0].Enabled = false + } + if err := conf.Save("config.json"); err != nil { + log.Fatalf("保存 config.json 时出现错误: %v", err) + c.JSON(200, Failed(502, "保存 config.json 时出现错误:"+fmt.Sprintf("%v", err))) + } else { + Jsonconfig = nil + c.JSON(200, coolq.OK(coolq.MSG{})) + } +} + +// config.json配置修改 +func AdminDoConfigJson(s *webServer, c *gin.Context) { + conf := GetConf() + Json := c.PostForm("json") + err := json.Unmarshal([]byte(Json), &conf) + if err != nil { + log.Warnf("尝试加载配置文件 %v 时出现错误: %v", "config.json", err) + c.JSON(200, Failed(502, "保存 config.json 时出现错误:"+fmt.Sprintf("%v", err))) + return + } + if err := conf.Save("config.json"); err != nil { + log.Fatalf("保存 config.json 时出现错误: %v", err) + c.JSON(200, Failed(502, "保存 config.json 时出现错误:"+fmt.Sprintf("%v", err))) + } else { + Jsonconfig = nil + c.JSON(200, coolq.OK(coolq.MSG{})) + } +} + +// 拉取config.json配置 +func AdminGetConfigJson(s *webServer, c *gin.Context) { + conf := GetConf() + c.JSON(200, coolq.OK(coolq.MSG{"config": conf})) + +} diff --git a/server/http.go b/server/http.go index 5d61c76..d3aed47 100644 --- a/server/http.go +++ b/server/http.go @@ -5,6 +5,7 @@ import ( "crypto/hmac" "crypto/sha1" "encoding/hex" + "net/http" "os" "strconv" "strings" @@ -21,6 +22,7 @@ import ( type httpServer struct { engine *gin.Engine bot *coolq.CQBot + Http *http.Server } type httpClient struct { @@ -79,13 +81,23 @@ func (s *httpServer) Run(addr, authToken string, bot *coolq.CQBot) { go func() { log.Infof("CQ HTTP 服务器已启动: %v", addr) - err := s.engine.Run(addr) - if err != nil { + s.Http=&http.Server{ + Addr:addr, + Handler:s.engine, + } + if err := s.Http.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Error(err) log.Infof("请检查端口是否被占用.") time.Sleep(time.Second * 5) os.Exit(1) } + //err := s.engine.Run(addr) + //if err != nil { + // log.Error(err) + // log.Infof("请检查端口是否被占用.") + // time.Sleep(time.Second * 5) + // os.Exit(1) + //} }() } @@ -519,3 +531,16 @@ var httpApi = map[string]func(s *httpServer, c *gin.Context){ s.OcrImage(c) }, } + +func (s *httpServer) ShutDown() { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := s.Http.Shutdown(ctx); err != nil { + log.Fatal("http Server Shutdown:", err) + } + select { + case <-ctx.Done(): + log.Println("timeout of 5 seconds.") + } + log.Println("http Server exiting") +} \ No newline at end of file From a5958e087784933887618931a081b47224a397df Mon Sep 17 00:00:00 2001 From: Mrs4s <1844812067@qq.com> Date: Sat, 3 Oct 2020 13:05:17 +0800 Subject: [PATCH 2/7] fix typo. --- global/config.go | 2 +- server/apiAdmin.go | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/global/config.go b/global/config.go index 302bfa9..71ecaaf 100644 --- a/global/config.go +++ b/global/config.go @@ -82,7 +82,7 @@ type GoCQReverseWebsocketConfig struct { type GoCqWebUi struct { Enabled bool `json:"enabled"` WebUiPort uint64 `json:"web_ui_port"` - WebInput bool `json:"webinput"` + WebInput bool `json:"web_input"` } func DefaultConfig() *JsonConfig { diff --git a/server/apiAdmin.go b/server/apiAdmin.go index 376f79f..9fa7c68 100644 --- a/server/apiAdmin.go +++ b/server/apiAdmin.go @@ -26,7 +26,7 @@ var WebInput = make(chan string, 1) //长度1,用于阻塞 var Console = make(chan os.Signal, 1) -var Jsonconfig *global.JsonConfig +var JsonConfig *global.JsonConfig type webServer struct { engine *gin.Engine @@ -59,7 +59,7 @@ func Failed(code int, msg string) coolq.MSG { func (s *webServer) Run(addr string, cli *client.QQClient) *coolq.CQBot { s.Cli = cli s.Conf = GetConf() - Jsonconfig = s.Conf + JsonConfig = s.Conf gin.SetMode(gin.ReleaseMode) s.engine = gin.New() @@ -205,8 +205,8 @@ func (s *webServer) admin(c *gin.Context) { // 获取当前配置文件信息 func GetConf() *global.JsonConfig { - if Jsonconfig != nil { - return Jsonconfig + if JsonConfig != nil { + return JsonConfig } conf := global.Load("config.json") return conf @@ -261,8 +261,8 @@ func AuthMiddleWare() gin.HandlerFunc { } } -func (s *webServer) DoRelogin() { - Jsonconfig = nil +func (s *webServer) DoReLogin() { // TODO: 协议层的 ReLogin + JsonConfig = nil conf := GetConf() OldConf := s.Conf cli := client.NewClient(conf.Uin, conf.Password) @@ -339,7 +339,7 @@ func (s *webServer) ReloadServer() { func AdminDoRestart(s *webServer, c *gin.Context) { s.bot = nil s.Cli = nil - s.DoRelogin() + s.DoReLogin() c.JSON(200, coolq.OK(coolq.MSG{})) return } @@ -390,7 +390,7 @@ func AdminDoConfigBase(s *webServer, c *gin.Context) { log.Fatalf("保存 config.json 时出现错误: %v", err) c.JSON(200, Failed(502, "保存 config.json 时出现错误:"+fmt.Sprintf("%v", err))) } else { - Jsonconfig = nil + JsonConfig = nil c.JSON(200, coolq.OK(coolq.MSG{})) } } @@ -415,7 +415,7 @@ func AdminDoConfigHttp(s *webServer, c *gin.Context) { log.Fatalf("保存 config.json 时出现错误: %v", err) c.JSON(200, Failed(502, "保存 config.json 时出现错误:"+fmt.Sprintf("%v", err))) } else { - Jsonconfig = nil + JsonConfig = nil c.JSON(200, coolq.OK(coolq.MSG{})) } } @@ -435,7 +435,7 @@ func AdminDoConfigWs(s *webServer, c *gin.Context) { log.Fatalf("保存 config.json 时出现错误: %v", err) c.JSON(200, Failed(502, "保存 config.json 时出现错误:"+fmt.Sprintf("%v", err))) } else { - Jsonconfig = nil + JsonConfig = nil c.JSON(200, coolq.OK(coolq.MSG{})) } } @@ -457,7 +457,7 @@ func AdminDoConfigReverse(s *webServer, c *gin.Context) { log.Fatalf("保存 config.json 时出现错误: %v", err) c.JSON(200, Failed(502, "保存 config.json 时出现错误:"+fmt.Sprintf("%v", err))) } else { - Jsonconfig = nil + JsonConfig = nil c.JSON(200, coolq.OK(coolq.MSG{})) } } @@ -476,7 +476,7 @@ func AdminDoConfigJson(s *webServer, c *gin.Context) { log.Fatalf("保存 config.json 时出现错误: %v", err) c.JSON(200, Failed(502, "保存 config.json 时出现错误:"+fmt.Sprintf("%v", err))) } else { - Jsonconfig = nil + JsonConfig = nil c.JSON(200, coolq.OK(coolq.MSG{})) } } From b4d29e270ae41c4886bf683187ca4a2f759dc6ca Mon Sep 17 00:00:00 2001 From: Mrs4s <1844812067@qq.com> Date: Sat, 3 Oct 2020 13:13:13 +0800 Subject: [PATCH 3/7] fix typo. --- server/apiAdmin.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/apiAdmin.go b/server/apiAdmin.go index 9fa7c68..ad83d97 100644 --- a/server/apiAdmin.go +++ b/server/apiAdmin.go @@ -69,7 +69,7 @@ func (s *webServer) Run(addr string, cli *client.QQClient) *coolq.CQBot { s.engine.Any("/admin/:action", s.admin) go func() { - log.Infof("miraigo adminapi 服务器已启动: %v", addr) + log.Infof("Admin API 服务器已启动: %v", addr) err := s.engine.Run(addr) if err != nil { log.Error(err) @@ -118,6 +118,7 @@ func (s *webServer) Dologin() { _, _ = s.Console.ReadString('\n') } log.Info(text) + os.Exit(0) return case client.OtherLoginError, client.UnknownLoginError: log.Fatalf("登录失败: %v", rsp.ErrorMessage) From b0d5589dcdf61f09e7b241347a5bffd5e6357248 Mon Sep 17 00:00:00 2001 From: Mrs4s <1844812067@qq.com> Date: Sat, 3 Oct 2020 13:23:50 +0800 Subject: [PATCH 4/7] update MiraiGo. --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 4c72aa5..9db7483 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Mrs4s/go-cqhttp go 1.14 require ( - github.com/Mrs4s/MiraiGo v0.0.0-20201002092940-afe4132a38d5 + github.com/Mrs4s/MiraiGo v0.0.0-20201003051902-8a968656c116 github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 // indirect github.com/gin-gonic/gin v1.6.3 github.com/go-playground/validator/v10 v10.4.0 // indirect diff --git a/go.sum b/go.sum index a183748..fe885d2 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/Mrs4s/MiraiGo v0.0.0-20201002083653-383954dc11e2 h1:Pjs3pQKXDxvaWf3zT github.com/Mrs4s/MiraiGo v0.0.0-20201002083653-383954dc11e2/go.mod h1:cwYPI2uq6nxNbx0nA6YuAKF1V5szSs6FPlGVLQvRUlo= github.com/Mrs4s/MiraiGo v0.0.0-20201002092940-afe4132a38d5 h1:NVXpGL7tssdjYBAsPRvCIL8f8V9e8n9YO8sSsOwdy44= github.com/Mrs4s/MiraiGo v0.0.0-20201002092940-afe4132a38d5/go.mod h1:cwYPI2uq6nxNbx0nA6YuAKF1V5szSs6FPlGVLQvRUlo= +github.com/Mrs4s/MiraiGo v0.0.0-20201003051902-8a968656c116 h1:GQHsohWh9LxaWmHIVS2tKzZZSysJBrnS+Sh5jXZ+5zk= +github.com/Mrs4s/MiraiGo v0.0.0-20201003051902-8a968656c116/go.mod h1:cwYPI2uq6nxNbx0nA6YuAKF1V5szSs6FPlGVLQvRUlo= github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= From e3f0dbc4acd0f0fc00675042dd51d687e9a8a3c6 Mon Sep 17 00:00:00 2001 From: scjtqs Date: Sat, 3 Oct 2020 17:28:21 +0800 Subject: [PATCH 5/7] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20=E6=97=A0?= =?UTF-8?q?=E6=B3=95=E5=85=B3=E9=97=AD=20web=20admin=E7=9B=91=E5=90=AC?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/apiAdmin.go | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/server/apiAdmin.go b/server/apiAdmin.go index ad83d97..7ede328 100644 --- a/server/apiAdmin.go +++ b/server/apiAdmin.go @@ -17,6 +17,7 @@ import ( "io/ioutil" "net/http" "os" + "os/signal" "strconv" "strings" "time" @@ -69,12 +70,21 @@ func (s *webServer) Run(addr string, cli *client.QQClient) *coolq.CQBot { s.engine.Any("/admin/:action", s.admin) go func() { - log.Infof("Admin API 服务器已启动: %v", addr) - err := s.engine.Run(addr) - if err != nil { - log.Error(err) - log.Infof("请检查端口是否被占用.") - time.Sleep(time.Second * 5) + //开启端口监听 + if s.Conf.WebUi.Enabled{ + log.Infof("Admin API 服务器已启动: %v", addr) + err := s.engine.Run(addr) + if err != nil { + log.Error(err) + log.Infof("请检查端口是否被占用.") + time.Sleep(time.Second * 5) + os.Exit(1) + } + }else{ + //关闭端口监听 + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt, os.Kill) + <-c os.Exit(1) } }() From 95c399a00338e07a8be666d2209fa1a9ef5963b5 Mon Sep 17 00:00:00 2001 From: scjtqs Date: Sat, 3 Oct 2020 17:43:03 +0800 Subject: [PATCH 6/7] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=20admin=20api?= =?UTF-8?q?=20=E7=9A=84=E7=9B=91=E5=90=AC=20host=E6=94=AF=E6=8C=81?= =?UTF-8?q?=EF=BC=8C=E9=AA=8C=E8=AF=81=E7=A0=81=E6=8F=90=E7=A4=BA=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=20host+port?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- global/config.go | 2 ++ main.go | 6 +++++- server/apiAdmin.go | 8 ++++---- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/global/config.go b/global/config.go index 71ecaaf..e380e99 100644 --- a/global/config.go +++ b/global/config.go @@ -81,6 +81,7 @@ type GoCQReverseWebsocketConfig struct { type GoCqWebUi struct { Enabled bool `json:"enabled"` + Host string `json:"host"` WebUiPort uint64 `json:"web_ui_port"` WebInput bool `json:"web_input"` } @@ -130,6 +131,7 @@ func DefaultConfig() *JsonConfig { }, WebUi: &GoCqWebUi{ Enabled: true, + Host: "0.0.0.0", WebInput: false, WebUiPort: 9999, }, diff --git a/main.go b/main.go index 15f10ab..46ce32d 100644 --- a/main.go +++ b/main.go @@ -237,17 +237,21 @@ func main() { conf.WebUi = &global.GoCqWebUi{ Enabled: true, WebInput: false, + Host: "0.0.0.0", WebUiPort: 9999, } } if conf.WebUi.WebUiPort <= 0 { conf.WebUi.WebUiPort = 9999 } + if conf.WebUi.Host == "" { + conf.WebUi.Host = "0.0.0.0" + } confErr := conf.Save("config.json") if confErr != nil { log.Error("保存配置文件失败") } - b := server.WebServer.Run(fmt.Sprintf("%s:%d", "0.0.0.0", conf.WebUi.WebUiPort), cli) + b := server.WebServer.Run(fmt.Sprintf("%s:%d", conf.WebUi.Host, conf.WebUi.WebUiPort), cli) c := server.Console signal.Notify(c, os.Interrupt, os.Kill) <-c diff --git a/server/apiAdmin.go b/server/apiAdmin.go index 7ede328..c136391 100644 --- a/server/apiAdmin.go +++ b/server/apiAdmin.go @@ -71,7 +71,7 @@ func (s *webServer) Run(addr string, cli *client.QQClient) *coolq.CQBot { go func() { //开启端口监听 - if s.Conf.WebUi.Enabled{ + if s.Conf.WebUi.Enabled { log.Infof("Admin API 服务器已启动: %v", addr) err := s.engine.Run(addr) if err != nil { @@ -80,7 +80,7 @@ func (s *webServer) Run(addr string, cli *client.QQClient) *coolq.CQBot { time.Sleep(time.Second * 5) os.Exit(1) } - }else{ + } else { //关闭端口监听 c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, os.Kill) @@ -109,7 +109,7 @@ func (s *webServer) Dologin() { img, _, _ := image.Decode(bytes.NewReader(rsp.CaptchaImage)) fmt.Println(asciiart.New("image", img).Art) if conf.WebUi.WebInput { - log.Warn("请输入验证码 (captcha.jpg): (http://127.0.0.1/admin/web_write 输入)") + log.Warnf("请输入验证码 (captcha.jpg): (http://%s:%d/admin/web_write 输入)", conf.WebUi.Host, conf.WebUi.WebUiPort) text = <-WebInput } else { log.Warn("请输入验证码 (captcha.jpg): (Enter 提交)") @@ -121,7 +121,7 @@ func (s *webServer) Dologin() { case client.UnsafeDeviceError: log.Warnf("账号已开启设备锁,请前往 -> %v <- 验证并重启Bot.", rsp.VerifyUrl) if conf.WebUi.WebInput { - log.Infof(" (http://127.0.0.1/admin/web_write 确认后继续)....") + log.Infof(" (http://%s:%d/admin/web_write 确认后继续)....", conf.WebUi.Host, conf.WebUi.WebUiPort) text = <-WebInput } else { log.Infof(" 按 Enter 继续....") From b19b114d3a6d3dfb211c07e1fc568dd2030c7d31 Mon Sep 17 00:00:00 2001 From: scjtqs Date: Sat, 3 Oct 2020 17:54:15 +0800 Subject: [PATCH 7/7] =?UTF-8?q?fix=20web=E8=BE=93=E5=85=A5api=E6=98=BE?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/apiAdmin.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/apiAdmin.go b/server/apiAdmin.go index c136391..6d44638 100644 --- a/server/apiAdmin.go +++ b/server/apiAdmin.go @@ -109,7 +109,7 @@ func (s *webServer) Dologin() { img, _, _ := image.Decode(bytes.NewReader(rsp.CaptchaImage)) fmt.Println(asciiart.New("image", img).Art) if conf.WebUi.WebInput { - log.Warnf("请输入验证码 (captcha.jpg): (http://%s:%d/admin/web_write 输入)", conf.WebUi.Host, conf.WebUi.WebUiPort) + log.Warnf("请输入验证码 (captcha.jpg): (http://%s:%d/admin/do_web_write 输入)", conf.WebUi.Host, conf.WebUi.WebUiPort) text = <-WebInput } else { log.Warn("请输入验证码 (captcha.jpg): (Enter 提交)") @@ -121,7 +121,7 @@ func (s *webServer) Dologin() { case client.UnsafeDeviceError: log.Warnf("账号已开启设备锁,请前往 -> %v <- 验证并重启Bot.", rsp.VerifyUrl) if conf.WebUi.WebInput { - log.Infof(" (http://%s:%d/admin/web_write 确认后继续)....", conf.WebUi.Host, conf.WebUi.WebUiPort) + log.Infof(" (http://%s:%d/admin/do_web_write 确认后继续)....", conf.WebUi.Host, conf.WebUi.WebUiPort) text = <-WebInput } else { log.Infof(" 按 Enter 继续....")