diff --git a/global/config.go b/global/config.go index 236b894..90b2dd2 100644 --- a/global/config.go +++ b/global/config.go @@ -1,14 +1,9 @@ package global import ( - "errors" "os" - "os/exec" "path" - "path/filepath" - "runtime" "strconv" - "strings" "time" "github.com/hjson/hjson-go" @@ -324,13 +319,14 @@ func (c *JSONConfig) Save(path string) error { // getCurrentPath 获取当前文件的路径,直接返回string func getCurrentPath() string { - cwd, e := GetCurrentPath() + cwd, e := os.Getwd() if e != nil { panic(e) } return cwd } +/* // GetCurrentPath 预留,获取当前目录地址 func GetCurrentPath() (string, error) { file, err := exec.LookPath(os.Args[0]) @@ -351,3 +347,4 @@ func GetCurrentPath() (string, error) { } return fpath[0 : i+1], nil } +*/ diff --git a/go.mod b/go.mod index 08a7190..c856c09 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,9 @@ module github.com/Mrs4s/go-cqhttp go 1.16 require ( - github.com/Mrs4s/MiraiGo v0.0.0-20210315005315-55aef1b0ffd0 + github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f // indirect + github.com/Mrs4s/MiraiGo v0.0.0-20210321041821-505aae133db3 github.com/dustin/go-humanize v1.0.0 - github.com/gin-contrib/pprof v1.3.0 github.com/gin-gonic/gin v1.6.3 github.com/gorilla/websocket v1.4.2 github.com/guonaihong/gout v0.1.5 @@ -15,14 +15,17 @@ require ( github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible github.com/lestrrat-go/strftime v1.0.4 // indirect + github.com/mattn/go-colorable v0.1.8 // indirect github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.8.1 + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect github.com/syndtr/goleveldb v1.0.0 github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816 github.com/tidwall/gjson v1.6.8 + github.com/tuotoo/qrcode v0.0.0-20190222102259-ac9c44189bf2 // indirect github.com/wdvxdr1123/go-silk v0.0.0-20210316130616-d47b553def60 - github.com/yinghau76/go-ascii-art v0.0.0-20190517192627-e7f465a30189 golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b + golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb // indirect golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba ) diff --git a/go.sum b/go.sum index 1812b82..3d55c73 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,9 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f h1:2dk3eOnYllh+wUOuDhOoC2vUVoJF/5z478ryJ+wzEII= +github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f/go.mod h1:4a58ifQTEe2uwwsaqbh3i2un5/CBPg+At/qHpt18Tmk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Mrs4s/MiraiGo v0.0.0-20210315005315-55aef1b0ffd0 h1:wCQr8EabWHuAuS6tgGOB4+RhvAA/s4AdMox7TeX2tQQ= -github.com/Mrs4s/MiraiGo v0.0.0-20210315005315-55aef1b0ffd0/go.mod h1:NjiWhlvGxwv1ftOWIoiFa/OzklnAYI4YqNexFOKSZKw= +github.com/Mrs4s/MiraiGo v0.0.0-20210321041821-505aae133db3 h1:PEv43+PPI9JmCrXsK1N0oAwCgnjs4q3yrshugK6TpZo= +github.com/Mrs4s/MiraiGo v0.0.0-20210321041821-505aae133db3/go.mod h1:NjiWhlvGxwv1ftOWIoiFa/OzklnAYI4YqNexFOKSZKw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -50,6 +52,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.1.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= @@ -76,6 +79,10 @@ github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible h1:Y6sqxHMyB1D2YSzWkL github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible/go.mod h1:ZQnN8lSECaebrkQytbHj4xNgtg8CR7RYXnPok8e0EHA= github.com/lestrrat-go/strftime v1.0.4 h1:T1Rb9EPkAhgxKqbcMIPguPq8glqXTA1koF8n9BHElA8= github.com/lestrrat-go/strftime v1.0.4/go.mod h1:E1nN3pCbtMSu1yjSVeyuRFVm/U0xoR76fd03sz+Qz4g= +github.com/maruel/rs v0.0.0-20150922171536-2c81c4312fe4 h1:u9jwvcKbQpghIXgNl/EOL8hzhAFXh4ePrEP493W3tNA= +github.com/maruel/rs v0.0.0-20150922171536-2c81c4312fe4/go.mod h1:kcRFpEzolcEklV6rD7W95mG49/sbdX/PlFmd7ni3RvA= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= @@ -101,6 +108,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qq github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -118,6 +127,8 @@ github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE= github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU= github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tuotoo/qrcode v0.0.0-20190222102259-ac9c44189bf2 h1:BWVtt2VBY+lmVDu9MGKqLGKl04B+iRHcrW1Ptyi/8tg= +github.com/tuotoo/qrcode v0.0.0-20190222102259-ac9c44189bf2/go.mod h1:lPnW9HVS0vJdeYyQtOvIvlXgZPNhUAhwz+z5r8AJk0Y= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= @@ -130,6 +141,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b h1:wSOdpTq0/eI46Ez/LkDwIsAKA71YP2SRKBODiRWM0as= golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb h1:fqpd0EBDzlHRCjiphRR5Zo/RSWWQlWv34418dnEixWk= +golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -151,6 +164,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201126233918-771906719818 h1:f1CIuDlJhwANEC2MM87MBEVMr3jl5bifgsfj90XAF9c= golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/login.go b/login.go new file mode 100644 index 0000000..6e0683e --- /dev/null +++ b/login.go @@ -0,0 +1,164 @@ +package main + +import ( + "bufio" + "bytes" + "io/ioutil" + "os" + "strings" + "time" + + qrcodeTerminal "github.com/Baozisoftware/qrcode-terminal-go" + "github.com/Mrs4s/MiraiGo/client" + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" + "github.com/tuotoo/qrcode" + + "github.com/Mrs4s/go-cqhttp/global" +) + +var console = bufio.NewReader(os.Stdin) + +var readLine = func() (str string) { + str, _ = console.ReadString('\n') + str = strings.TrimSpace(str) + return +} + +var cli *client.QQClient + +// ErrSMSRequestError SMS请求出错 +var ErrSMSRequestError = errors.New("sms request error") + +func commonLogin() error { + res, err := cli.Login() + if err != nil { + return err + } + return loginResponseProcessor(res) +} + +func qrcodeLogin() error { + rsp, err := cli.FetchQRCode() + if err != nil { + return err + } + fi, err := qrcode.Decode(bytes.NewReader(rsp.ImageData)) + if err != nil { + return err + } + _ = ioutil.WriteFile("qrcode.png", rsp.ImageData, 0644) + log.Infof("请使用手机QQ扫描二维码 (qrcode.png) : ") + time.Sleep(time.Second) + qrcodeTerminal.New().Get(fi.Content).Print() + s, err := cli.QueryQRCodeStatus(rsp.Sig) + if err != nil { + return err + } + prevState := s.State + for { + time.Sleep(time.Second) + s, _ = cli.QueryQRCodeStatus(rsp.Sig) + if s == nil { + continue + } + if prevState == s.State { + continue + } + prevState = s.State + if s.State == client.QRCodeCanceled { + log.Fatalf("扫码被用户取消.") + } + if s.State == client.QRCodeTimeout { + log.Fatalf("二维码过期") + } + if s.State == client.QRCodeWaitingForConfirm { + log.Infof("扫码成功, 请在手机端确认登录.") + } + if s.State == client.QRCodeConfirmed { + res, err := cli.QRCodeLogin(s.LoginInfo) + if err != nil { + return nil + } + return loginResponseProcessor(res) + } + } +} + +func loginResponseProcessor(res *client.LoginResponse) error { + var err error + for { + if err != nil { + return err + } + if res.Success { + return nil + } + var text string + switch res.Error { + case client.SliderNeededError: + log.Warnf("登录需要滑条验证码. ") + log.Warnf("请参考文档 -> https://github.com/Mrs4s/go-cqhttp/blob/master/docs/slider.md <- 抓包获取 Ticket") + println() + log.Warnf("请用浏览器打开 -> %v <- 并获取Ticket.", res.VerifyUrl) + println() + log.Warn("请输入Ticket: (Enter 提交)") + text = readLine() + res, err = cli.SubmitTicket(text) + continue + case client.NeedCaptcha: + log.Warnf("登录需要滑条验证码.") + _ = ioutil.WriteFile("captcha.jpg", res.CaptchaImage, 0644) + log.Warnf("请输入验证码 (captcha.jpg): (Enter 提交)") + text = readLine() + global.DelFile("captcha.jpg") + res, err = cli.SubmitCaptcha(text, res.CaptchaSign) + continue + case client.SMSNeededError: + log.Warnf("账号已开启设备锁, 按 Enter 向手机 %v 发送短信验证码.", res.SMSPhone) + readLine() + if !cli.RequestSMS() { + log.Warnf("发送验证码失败,可能是请求过于频繁.") + return errors.WithStack(ErrSMSRequestError) + } + log.Warn("请输入短信验证码: (Enter 提交)") + text = readLine() + res, err = cli.SubmitSMS(text) + continue + case client.SMSOrVerifyNeededError: + log.Warnf("账号已开启设备锁,请选择验证方式:") + log.Warnf("1. 向手机 %v 发送短信验证码", res.SMSPhone) + log.Warnf("2. 使用手机QQ扫码验证.") + log.Warn("请输入(1 - 2):") + text = readLine() + if strings.Contains(text, "1") { + if !cli.RequestSMS() { + log.Warnf("发送验证码失败,可能是请求过于频繁.") + return errors.WithStack(ErrSMSRequestError) + } + log.Warn("请输入短信验证码: (Enter 提交)") + text = readLine() + res, err = cli.SubmitSMS(text) + continue + } + fallthrough + case client.UnsafeDeviceError: + log.Warnf("账号已开启设备锁,请前往 -> %v <- 验证后重启Bot.", res.VerifyUrl) + log.Infof("按 Enter 继续....") + readLine() + os.Exit(0) + case client.OtherLoginError, client.UnknownLoginError, client.TooManySMSRequestError: + msg := res.ErrorMessage + if strings.Contains(msg, "版本") { + msg = "密码错误或账号被冻结" + } + if strings.Contains(msg, "冻结") { + log.Fatalf("账号被冻结") + } + log.Warnf("登录失败: %v", msg) + log.Infof("按 Enter 继续....") + readLine() + os.Exit(0) + } + } +} diff --git a/main.go b/main.go index 42937f4..175925d 100644 --- a/main.go +++ b/main.go @@ -13,13 +13,12 @@ import ( "io/ioutil" "net/http" "os" - "os/exec" "os/signal" "path" - "path/filepath" "runtime" "strconv" "strings" + "sync" "syscall" "time" @@ -168,11 +167,11 @@ func main() { time.Sleep(time.Second * 10) } if conf.Uin == 0 || (conf.Password == "" && conf.PasswordEncrypted == "") { - log.Warnf("请修改 %s 以添加账号密码.", global.DefaultConfFile) + log.Warn("账号密码未配置, 将使用二维码登录.") if !isFastStart { + log.Warn("将在 5秒 后继续.") time.Sleep(time.Second * 5) } - return } log.Info("当前版本:", coolq.Version) @@ -241,7 +240,7 @@ func main() { log.Fatalf("加密存储的密码损坏,请尝试重新配置密码") } copy(global.PasswordHash[:], ph) - } else { + } else if conf.Password != "" { global.PasswordHash = md5.Sum([]byte(conf.Password)) } if !isFastStart { @@ -262,7 +261,11 @@ func main() { } return "未知" }()) - cli := client.NewClientMd5(conf.Uin, global.PasswordHash) + cli = client.NewClientEmpty() + if conf.Uin != 0 && global.PasswordHash != [16]byte{} { + cli.Uin = conf.Uin + cli.PasswordMd5 = global.PasswordHash + } cli.OnLog(func(c *client.QQClient, e *client.LogEvent) { switch e.Type { case "INFO": @@ -289,34 +292,102 @@ func main() { log.Infof("收到服务器地址更新通知, 将在下一次重连时应用. ") return true }) - if conf.WebUI == nil { - conf.WebUI = &global.GoCQWebUI{ - Enabled: true, - WebInput: false, - Host: "0.0.0.0", - WebUIPort: 9999, + /* + if conf.WebUI == nil { + 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 = "127.0.0.1" + } + */ + global.Proxy = conf.ProxyRewrite + // b := server.WebServer.Run(fmt.Sprintf("%s:%d", conf.WebUI.Host, conf.WebUI.WebUIPort), cli) + // c := server.Console + isQRCodeLogin := conf.Uin == 0 || len(conf.Password) == 0 + if !isQRCodeLogin { + if err := commonLogin(); err != nil { + log.Fatalf("登录时发生致命错误: %v", err) + } + } else { + if err := qrcodeLogin(); err != nil { + log.Fatalf("登录时发生致命错误: %v", err) } } - if conf.WebUI.WebUIPort <= 0 { - conf.WebUI.WebUIPort = 9999 + var times uint = 1 // 重试次数 + var reLoginLock sync.Mutex + cli.OnDisconnected(func(q *client.QQClient, e *client.ClientDisconnectedEvent) { + reLoginLock.Lock() + defer reLoginLock.Unlock() + log.Warnf("Bot已离线: %v", e.Message) + if !conf.ReLogin.Enabled { + os.Exit(1) + } + if isQRCodeLogin { + log.Fatalf("二维码登录暂不支持重连.") + } + if times > conf.ReLogin.MaxReloginTimes && conf.ReLogin.MaxReloginTimes != 0 { + log.Fatalf("Bot重连次数超过限制, 停止") + } + if conf.ReLogin.ReLoginDelay > 0 { + log.Warnf("将在 %v 秒后尝试重连. 重连次数:%v/%v", conf.ReLogin.ReLoginDelay, times, conf.ReLogin.MaxReloginTimes) + } + log.Warnf("尝试重连...") + if cli.Online { + return + } + if err := commonLogin(); err != nil { + log.Fatalf("登录时发生致命错误: %v", err) + } + }) + cli.AllowSlider = true + log.Infof("登录成功 欢迎使用: %v", cli.Nickname) + log.Info("开始加载好友列表...") + global.Check(cli.ReloadFriendList()) + log.Infof("共加载 %v 个好友.", len(cli.FriendList)) + log.Infof("开始加载群列表...") + global.Check(cli.ReloadGroupList()) + log.Infof("共加载 %v 个群.", len(cli.GroupList)) + bot := 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.WebUI.Host == "" { - conf.WebUI.Host = "127.0.0.1" + if conf.RateLimit.Enabled { + global.InitLimiter(conf.RateLimit.Frequency, conf.RateLimit.BucketSize) } - global.Proxy = conf.ProxyRewrite - b := server.WebServer.Run(fmt.Sprintf("%s:%d", conf.WebUI.Host, conf.WebUI.WebUIPort), cli) - c := server.Console - r := server.Restart + log.Info("正在加载事件过滤器.") + global.BootFilter() + coolq.IgnoreInvalidCQCode = conf.IgnoreInvalidCQCode + coolq.SplitURL = conf.FixURL + coolq.ForceFragmented = conf.ForceFragmented + if conf.HTTPConfig != nil && conf.HTTPConfig.Enabled { + go server.CQHTTPApiServer.Run(fmt.Sprintf("%s:%d", conf.HTTPConfig.Host, conf.HTTPConfig.Port), conf.AccessToken, bot) + for k, v := range conf.HTTPConfig.PostUrls { + server.NewHTTPClient().Run(k, v, conf.HTTPConfig.Timeout, bot) + } + } + if conf.WSConfig != nil && conf.WSConfig.Enabled { + go server.WebSocketServer.Run(fmt.Sprintf("%s:%d", conf.WSConfig.Host, conf.WSConfig.Port), conf.AccessToken, bot) + } + for _, rc := range conf.ReverseServers { + go server.NewWebSocketClient(rc, conf.AccessToken, bot).Run() + } + log.Info("资源初始化完成, 开始处理信息.") + log.Info("アトリは、高性能ですから!") + c := make(chan os.Signal, 1) go checkUpdate() signal.Notify(c, os.Interrupt, syscall.SIGTERM) - select { - case <-c: - b.Release() - case <-r: - log.Info("正在重启中...") - defer b.Release() - restart(arg) - } + <-c } // PasswordHashEncrypt 使用key加密给定passwordHash @@ -439,7 +510,7 @@ func selfUpdate(imageURL string) { log.Error("更新失败: ", err) return } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() wc := global.WriteCounter{} err, _ = global.UpdateFromStream(io.TeeReader(resp.Body, &wc)) fmt.Println() @@ -463,6 +534,7 @@ func selfUpdate(imageURL string) { os.Exit(0) } +/* func restart(args []string) { var cmd *exec.Cmd if runtime.GOOS == "windows" { @@ -493,6 +565,7 @@ func restart(args []string) { } _ = cmd.Start() } +*/ func getConfig() *global.JSONConfig { var conf *global.JSONConfig diff --git a/server/api.go b/server/api.go index 368029d..782ebe2 100644 --- a/server/api.go +++ b/server/api.go @@ -2,7 +2,6 @@ package server import ( "strings" - "time" "github.com/Mrs4s/go-cqhttp/coolq" "github.com/Mrs4s/go-cqhttp/global" @@ -192,17 +191,19 @@ func getGroupHonorInfo(bot *coolq.CQBot, p resultGetter) coolq.MSG { return bot.CQGetGroupHonorInfo(p.Get("group_id").Int(), p.Get("type").Str) } -func setRestart(_ *coolq.CQBot, p resultGetter) coolq.MSG { - var delay int64 - delay = p.Get("delay").Int() - if delay < 0 { - delay = 0 - } - defer func(delay int64) { - time.Sleep(time.Duration(delay) * time.Millisecond) - Restart <- struct{}{} - }(delay) - return coolq.MSG{"data": nil, "retcode": 0, "status": "async"} +func setRestart(_ *coolq.CQBot, _ resultGetter) coolq.MSG { + /* + var delay int64 + delay = p.Get("delay").Int() + if delay < 0 { + delay = 0 + } + defer func(delay int64) { + time.Sleep(time.Duration(delay) * time.Millisecond) + Restart <- struct{}{} + }(delay) + */ + return coolq.MSG{"data": nil, "retcode": 99, "msg": "restart un-supported now", "wording": "restart函数暂不兼容", "status": "failed"} } func canSendImage(bot *coolq.CQBot, _ resultGetter) coolq.MSG { diff --git a/server/apiAdmin.go b/server/apiAdmin.go deleted file mode 100644 index 3e04e04..0000000 --- a/server/apiAdmin.go +++ /dev/null @@ -1,661 +0,0 @@ -package server - -import ( - "bufio" - "bytes" - "encoding/base64" - "fmt" - "image" - "io/ioutil" - "net/http" - "os" - "os/signal" - "strconv" - "strings" - "syscall" - "time" - - "github.com/Mrs4s/go-cqhttp/coolq" - "github.com/Mrs4s/go-cqhttp/global" - - "github.com/Mrs4s/MiraiGo/client" - "github.com/Mrs4s/MiraiGo/utils" - "github.com/gin-contrib/pprof" - "github.com/gin-gonic/gin" - jsoniter "github.com/json-iterator/go" - log "github.com/sirupsen/logrus" - "github.com/tidwall/gjson" - asciiart "github.com/yinghau76/go-ascii-art" -) - -var json = jsoniter.ConfigCompatibleWithStandardLibrary - -// WebInput 网页输入channel -var WebInput = make(chan string, 1) // 长度1,用于阻塞 - -// Console 控制台channel -var Console = make(chan os.Signal, 1) - -// Restart 重启信号监听channel -var Restart = make(chan struct{}, 1) - -// JSONConfig go-cqhttp配置 -var JSONConfig *global.JSONConfig - -type webServer struct { - engine *gin.Engine - bot *coolq.CQBot - Cli *client.QQClient - Conf *global.JSONConfig // old config - Console *bufio.Reader -} - -// WebServer Admin子站的Server -var WebServer = &webServer{} - -// APIAdminRoutingTable Admin子站的路由映射 -var APIAdminRoutingTable = map[string]func(s *webServer, c *gin.Context){ - "do_restart": AdminDoRestart, // 热重启 - "do_process_restart": AdminProcessRestart, // 进程重启 - "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": AdminDoConfigReverseWS, // 修改config.json 中的反向ws部分 - "do_config_json": AdminDoConfigJSON, // 直接修改 config.json配置 - "get_config_json": AdminGetConfigJSON, // 拉取 当前的config.json配置 -} - -// Failed 构建失败返回MSG -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() { - // 开启端口监听 - if s.Conf.WebUI != nil && s.Conf.WebUI.Enabled { - if Debug { - pprof.Register(s.engine) - log.Debugf("pprof 性能分析服务已启动在 http://%v/debug/pprof, 如果有任何性能问题请下载报告并提交给开发者", addr) - time.Sleep(time.Second * 3) - } - log.Infof("Admin API 服务器已启动: %v", addr) - err := s.engine.Run(addr) - if err != nil { - log.Error(err) - log.Infof("请检查端口是否被占用.") - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) - <-c - os.Exit(1) - } - } else { - // 关闭端口监听 - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) - <-c - os.Exit(1) - } - }() - s.Dologin() - s.UpServer() - b := s.bot // 外部引入 bot对象,用于操作bot - return b -} - -// logincore 登录核心实现 -func (s *webServer) logincore(relogin bool) { - s.Console = bufio.NewReader(os.Stdin) - readLine := func() (str string) { - str, _ = s.Console.ReadString('\n') - str = strings.TrimSpace(str) - return - } - - if s.Cli.Online { - log.Warn("Bot已登录") - return - } - - var times uint = 1 // 重试次数 - for res, err := s.Cli.Login(); ; res, err = s.Cli.Login() { - var text string - count := 0 - - if res == nil { - goto Relogin - } - - Again: // 不执行 s.Cli.Login() 的循环,适用输入验证码等更新 res 的操作 - if err == nil && res.Success { // 登录成功 - break - } else if err == client.ErrAlreadyOnline { - break - } - - switch res.Error { - case client.SliderNeededError: - log.Warnf("登录需要滑条验证码, 请选择解决方案: ") - log.Warnf("1. 自行抓包. (推荐)") - log.Warnf("2. 使用Cef自动处理.") - log.Warnf("3. 不提交滑块并继续.(可能会导致上网环境异常错误)") - log.Warnf("详细信息请参考文档 -> https://github.com/Mrs4s/go-cqhttp/blob/master/docs/slider.md <-") - if s.Conf.WebUI != nil && s.Conf.WebUI.WebInput { - log.Warnf("请输入(1 - 3): (http://%s:%d/admin/do_web_write 输入)", s.Conf.WebUI.Host, s.Conf.WebUI.WebUIPort) - text = <-WebInput - } else { - log.Warn("请输入(1 - 3):") - text = readLine() - } - if strings.Contains(text, "1") { - log.Warnf("请用浏览器打开 -> %v <- 并获取Ticket.", res.VerifyUrl) - if s.Conf.WebUI != nil && s.Conf.WebUI.WebInput { - log.Warnf("请输入Ticket: (http://%s:%d/admin/do_web_write 输入)", s.Conf.WebUI.Host, s.Conf.WebUI.WebUIPort) - text = <-WebInput - } else { - log.Warn("请输入Ticket: (Enter 提交)") - text = readLine() - } - res, err = s.Cli.SubmitTicket(strings.TrimSpace(text)) - goto Again - } - if strings.Contains(text, "3") { - s.Cli.AllowSlider = false - s.Cli.Disconnect() - continue - } - id := utils.RandomStringRange(6, "0123456789") - log.Warnf("滑块ID为 %v 请在30S内处理.", id) - ticket, err := global.GetSliderTicket(res.VerifyUrl, id) - if err != nil { - log.Warnf("错误: " + err.Error()) - os.Exit(0) - } - res, err = s.Cli.SubmitTicket(ticket) - if err != nil { - log.Warnf("错误: " + err.Error()) - continue // 尝试重新登录 - } - goto Again - case client.NeedCaptcha: - _ = ioutil.WriteFile("captcha.jpg", res.CaptchaImage, 0644) - img, _, _ := image.Decode(bytes.NewReader(res.CaptchaImage)) - fmt.Println(asciiart.New("image", img).Art) - if s.Conf.WebUI != nil && s.Conf.WebUI.WebInput { - log.Warnf("请输入验证码 (captcha.jpg): (http://%s:%d/admin/do_web_write 输入)", s.Conf.WebUI.Host, s.Conf.WebUI.WebUIPort) - text = <-WebInput - } else { - log.Warn("请输入验证码 (captcha.jpg): (Enter 提交)") - text = readLine() - } - global.DelFile("captcha.jpg") - res, err = s.Cli.SubmitCaptcha(strings.ReplaceAll(text, "\n", ""), res.CaptchaSign) - goto Again - case client.SMSNeededError: - if s.Conf.WebUI != nil && s.Conf.WebUI.WebInput { - log.Warnf("账号已开启设备锁, 已向手机 %v 发送短信验证码.", res.SMSPhone) - } else { - log.Warnf("账号已开启设备锁, 按下 Enter 向手机 %v 发送短信验证码.", res.SMSPhone) - readLine() - } - if !s.Cli.RequestSMS() { - log.Warnf("发送验证码失败,可能是请求过于频繁.") - time.Sleep(time.Second * 5) - continue - } - if s.Conf.WebUI != nil && s.Conf.WebUI.WebInput { - log.Warnf("请输入短信验证码: (http://%s:%d/admin/do_web_write 输入)", s.Conf.WebUI.Host, s.Conf.WebUI.WebUIPort) - text = <-WebInput - } else { - log.Warn("请输入短信验证码: (Enter 提交)") - text = readLine() - } - res, err = s.Cli.SubmitSMS(strings.ReplaceAll(strings.ReplaceAll(text, "\n", ""), "\r", "")) - goto Again - case client.SMSOrVerifyNeededError: - log.Warnf("账号已开启设备锁,请选择验证方式:") - log.Warnf("1. 向手机 %v 发送短信验证码", res.SMSPhone) - log.Warnf("2. 使用手机QQ扫码验证.") - if s.Conf.WebUI != nil && s.Conf.WebUI.WebInput { - log.Warnf("请输入(1 - 2): (http://%s:%d/admin/do_web_write 输入)", s.Conf.WebUI.Host, s.Conf.WebUI.WebUIPort) - text = <-WebInput - } else { - log.Warn("请输入(1 - 2):") - text = readLine() - } - if strings.Contains(text, "1") { - if !s.Cli.RequestSMS() { - log.Warnf("发送验证码失败,可能是请求过于频繁.") - time.Sleep(time.Second * 5) - os.Exit(0) - } - if s.Conf.WebUI != nil && s.Conf.WebUI.WebInput { - log.Warnf("请输入短信验证码: (http://%s:%d/admin/do_web_write 输入)....", s.Conf.WebUI.Host, s.Conf.WebUI.WebUIPort) - text = <-WebInput - } else { - log.Warn("请输入短信验证码: (Enter 提交)") - text = readLine() - } - res, err = s.Cli.SubmitSMS(strings.ReplaceAll(strings.ReplaceAll(text, "\n", ""), "\r", "")) - goto Again - } - log.Warnf("请前往 -> %v <- 验证.", res.VerifyUrl) - log.Infof("按 Enter 继续....") - readLine() - continue - case client.UnsafeDeviceError: - log.Warnf("账号已开启设备锁,请前往 -> %v <- 验证.", res.VerifyUrl) - if s.Conf.WebUI != nil && s.Conf.WebUI.WebInput { - log.Infof(" (http://%s:%d/admin/do_web_write 确认后继续)....", s.Conf.WebUI.Host, s.Conf.WebUI.WebUIPort) - text = <-WebInput - } else { - log.Infof("按 Enter 继续....") - readLine() - } - log.Info(text) - continue - case client.OtherLoginError, client.UnknownLoginError, client.TooManySMSRequestError: - msg := res.ErrorMessage - if strings.Contains(msg, "版本") { - msg = "密码错误或账号被冻结" - } - if strings.Contains(msg, "上网环境") && count < 5 { - s.Cli.Disconnect() - log.Warnf("错误: 当前上网环境异常. 将更换服务器并重试.") - count++ - time.Sleep(time.Second) - continue - } - if strings.Contains(msg, "冻结") { - log.Fatalf("账号被冻结, 放弃重连") - } - log.Warnf("登录失败: %v", msg) - log.Infof("按 Enter 继续....") - readLine() - os.Exit(0) - } - - Relogin: - if relogin { - if times > s.Conf.ReLogin.MaxReloginTimes && s.Conf.ReLogin.MaxReloginTimes != 0 { - log.Fatal("重连失败: 重连次数达到设置的上限值") - s.bot.Release() - return - } - log.Warnf("将在 %v 秒后尝试重连. 重连次数:%v", s.Conf.ReLogin.ReLoginDelay, times) - times++ - time.Sleep(time.Second * time.Duration(s.Conf.ReLogin.ReLoginDelay)) - s.Cli.Disconnect() - continue - } - } - if relogin { - log.Info("重连成功") - } -} - -// Dologin 主程序登录 -func (s *webServer) Dologin() { - s.Cli.AllowSlider = true - s.logincore(false) - log.Infof("登录成功 欢迎使用: %v", s.Cli.Nickname) - log.Info("开始加载好友列表...") - global.Check(s.Cli.ReloadFriendList()) - log.Infof("共加载 %v 个好友.", len(s.Cli.FriendList)) - log.Infof("开始加载群列表...") - global.Check(s.Cli.ReloadGroupList()) - log.Infof("共加载 %v 个群.", len(s.Cli.GroupList)) - s.bot = coolq.NewQQBot(s.Cli, s.Conf) - if s.Conf.PostMessageFormat != "string" && s.Conf.PostMessageFormat != "array" { - log.Warnf("post_message_format 配置错误, 将自动使用 string") - coolq.SetMessageFormat("string") - } else { - coolq.SetMessageFormat(s.Conf.PostMessageFormat) - } - if s.Conf.RateLimit.Enabled { - global.InitLimiter(s.Conf.RateLimit.Frequency, s.Conf.RateLimit.BucketSize) - } - log.Info("正在加载事件过滤器.") - global.BootFilter() - coolq.IgnoreInvalidCQCode = s.Conf.IgnoreInvalidCQCode - coolq.SplitURL = s.Conf.FixURL - coolq.ForceFragmented = s.Conf.ForceFragmented - log.Info("资源初始化完成, 开始处理信息.") - log.Info("アトリは、高性能ですから!") - - s.Cli.OnDisconnected(func(q *client.QQClient, e *client.ClientDisconnectedEvent) { - if !s.Conf.ReLogin.Enabled { - return - } - log.Warnf("Bot已离线 (%v),尝试重连", e.Message) - s.logincore(true) - }) -} - -func (s *webServer) admin(c *gin.Context) { - action := c.Param("action") - log.Debugf("WebServer接收到cgi调用: %v", action) - if f, ok := APIAdminRoutingTable[action]; ok { - f(s, c) - } else { - c.JSON(200, coolq.Failed(404)) - } -} - -// GetConf 获取当前配置文件信息 -func GetConf() *global.JSONConfig { - if JSONConfig != nil { - return JSONConfig - } - conf := global.LoadConfig(global.DefaultConfFile) - return conf -} - -// AuthMiddleWare 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 strings.Contains(c.Request.URL.Path, "debug") { - c.Next() - return - } - // 处理请求 - 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 - auth := c.Request.Header.Get("Authorization") - switch { - case auth != "": - if strings.SplitN(auth, " ", 2)[1] != authToken { - c.AbortWithStatus(401) - return - } - c.Next() - case c.Query("access_token") != authToken: - c.AbortWithStatus(401) - return - default: - c.Next() - } - } -} - -func (s *webServer) DoReLogin() { // TODO: 协议层的 ReLogin - 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.IPad: - return "iPad" - case client.AndroidPhone: - return "Android Phone" - case client.AndroidWatch: - return "Android Watch" - case client.MacOS: - return "MacOS" - } - 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) bool { - if !conf.UseSSOAddress { - log.Infof("收到服务器地址更新通知, 根据配置文件已忽略.") - return false - } - log.Infof("收到服务器地址更新通知, 将在下一次重连时应用. ") - return true - }) - s.Cli = cli - s.Dologin() - // 关闭之前的 server - if OldConf.HTTPConfig != nil && OldConf.HTTPConfig.Enabled { - cqHTTPServer.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 cqHTTPServer.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 cqHTTPServer.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() - } -} - -// AdminDoRestart 热重启 -func AdminDoRestart(s *webServer, c *gin.Context) { - s.bot.Release() - s.bot = nil - s.Cli = nil - s.DoReLogin() - c.JSON(200, coolq.OK(coolq.MSG{})) -} - -// AdminProcessRestart 进程重启 -func AdminProcessRestart(s *webServer, c *gin.Context) { - Restart <- struct{}{} - c.JSON(200, coolq.OK(coolq.MSG{})) -} - -// AdminDoRestartDocker 冷重启 -func AdminDoRestartDocker(s *webServer, c *gin.Context) { - Console <- os.Kill - c.JSON(200, coolq.OK(coolq.MSG{})) -} - -// AdminWebWrite 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上显示图片 - })) -} - -// AdminDoWebWrite web输入处理 -func AdminDoWebWrite(s *webServer, c *gin.Context) { - input := c.PostForm("input") - WebInput <- input - c.JSON(200, coolq.OK(coolq.MSG{})) -} - -// AdminDoConfigBase 普通配置修改 -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(global.DefaultConfFile); err != nil { - log.Fatalf("保存 %s 时出现错误: %v", global.DefaultConfFile, err) - c.JSON(200, Failed(502, "保存 "+global.DefaultConfFile+" 时出现错误:"+fmt.Sprintf("%v", err))) - } else { - JSONConfig = nil - c.JSON(200, coolq.OK(coolq.MSG{})) - } -} - -// AdminDoConfigHTTP 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(global.DefaultConfFile); err != nil { - log.Fatalf("保存 %s 时出现错误: %v", global.DefaultConfFile, err) - c.JSON(200, Failed(502, "保存 "+global.DefaultConfFile+" 时出现错误:"+fmt.Sprintf("%v", err))) - } else { - JSONConfig = nil - c.JSON(200, coolq.OK(coolq.MSG{})) - } -} - -// AdminDoConfigWS 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(global.DefaultConfFile); err != nil { - log.Fatalf("保存 %s 时出现错误: %v", global.DefaultConfFile, err) - c.JSON(200, Failed(502, "保存 "+global.DefaultConfFile+" 时出现错误:"+fmt.Sprintf("%v", err))) - } else { - JSONConfig = nil - c.JSON(200, coolq.OK(coolq.MSG{})) - } -} - -// AdminDoConfigReverseWS 反向ws配置修改 -func AdminDoConfigReverseWS(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(global.DefaultConfFile); err != nil { - log.Fatalf("保存 %s 时出现错误: %v", global.DefaultConfFile, err) - c.JSON(200, Failed(502, "保存 "+global.DefaultConfFile+" 时出现错误:"+fmt.Sprintf("%v", err))) - } else { - JSONConfig = nil - c.JSON(200, coolq.OK(coolq.MSG{})) - } -} - -// AdminDoConfigJSON config.hjson配置修改 -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", global.DefaultConfFile, err) - c.JSON(200, Failed(502, "保存 "+global.DefaultConfFile+" 时出现错误:"+fmt.Sprintf("%v", err))) - return - } - if err := conf.Save(global.DefaultConfFile); err != nil { - log.Fatalf("保存 %s 时出现错误: %v", global.DefaultConfFile, err) - c.JSON(200, Failed(502, "保存 "+global.DefaultConfFile+" 时出现错误:"+fmt.Sprintf("%v", err))) - } else { - JSONConfig = nil - c.JSON(200, coolq.OK(coolq.MSG{})) - } -} - -// AdminGetConfigJSON 拉取config.hjson配置 -func AdminGetConfigJSON(s *webServer, c *gin.Context) { - conf := GetConf() - c.JSON(200, coolq.OK(coolq.MSG{"config": conf})) -} diff --git a/server/daemon.go b/server/daemon.go index e9e2e58..644abe5 100644 --- a/server/daemon.go +++ b/server/daemon.go @@ -1,6 +1,7 @@ -// daemon 功能写在这,目前仅支持了-d 作为后台运行参数,stop,start,restart这些功能目前看起来并不需要,可以通过api控制,后续需要的话再补全。 package server +// daemon 功能写在这,目前仅支持了-d 作为后台运行参数,stop,start,restart这些功能目前看起来并不需要,可以通过api控制,后续需要的话再补全。 + import ( "fmt" "os" diff --git a/server/http.go b/server/http.go index 8dedd10..15b4060 100644 --- a/server/http.go +++ b/server/http.go @@ -29,7 +29,8 @@ type httpServer struct { api apiCaller } -type httpClient struct { +// HTTPClient 反向HTTP上报客户端 +type HTTPClient struct { bot *coolq.CQBot secret string addr string @@ -40,7 +41,8 @@ type httpContext struct { ctx *gin.Context } -var cqHTTPServer = &httpServer{} +// CQHTTPApiServer CQHTTPApiServer实例 +var CQHTTPApiServer = &httpServer{} // Debug 是否启用Debug模式 var Debug = false @@ -107,11 +109,13 @@ func (s *httpServer) Run(addr, authToken string, bot *coolq.CQBot) { }() } -func newHTTPClient() *httpClient { - return &httpClient{} +// NewHTTPClient 返回反向HTTP客户端 +func NewHTTPClient() *HTTPClient { + return &HTTPClient{} } -func (c *httpClient) Run(addr, secret string, timeout int32, bot *coolq.CQBot) { +// Run 运行反向HTTP服务 +func (c *HTTPClient) Run(addr, secret string, timeout int32, bot *coolq.CQBot) { c.bot = bot c.secret = secret c.addr = addr @@ -123,7 +127,7 @@ func (c *httpClient) Run(addr, secret string, timeout int32, bot *coolq.CQBot) { log.Infof("HTTP POST上报器已启动: %v", addr) } -func (c *httpClient) onBotPushEvent(m *bytes.Buffer) { +func (c *HTTPClient) onBotPushEvent(m *bytes.Buffer) { var res string err := gout.POST(c.addr).SetJSON(m.Bytes()).BindBody(&res).SetHeader(func() gout.H { h := gout.H{ diff --git a/server/websocket.go b/server/websocket.go index 4badfc0..f956151 100644 --- a/server/websocket.go +++ b/server/websocket.go @@ -177,7 +177,7 @@ func (c *WebSocketClient) connectUniversal() { } func (c *WebSocketClient) listenAPI(conn *webSocketConn, u bool) { - defer conn.Close() + defer func() { _ = conn.Close() }() for { buffer := global.NewBuffer() t, reader, err := conn.NextReader() @@ -258,7 +258,7 @@ func (s *webSocketServer) event(w http.ResponseWriter, r *http.Request) { err = c.WriteMessage(websocket.TextMessage, []byte(s.handshake)) if err != nil { log.Warnf("WebSocket 握手时出现错误: %v", err) - c.Close() + _ = c.Close() return } @@ -309,7 +309,7 @@ func (s *webSocketServer) any(w http.ResponseWriter, r *http.Request) { err = c.WriteMessage(websocket.TextMessage, []byte(s.handshake)) if err != nil { log.Warnf("WebSocket 握手时出现错误: %v", err) - c.Close() + _ = c.Close() return } log.Infof("接受 WebSocket 连接: %v (/)", r.RemoteAddr) @@ -321,7 +321,7 @@ func (s *webSocketServer) any(w http.ResponseWriter, r *http.Request) { } func (s *webSocketServer) listenAPI(c *webSocketConn) { - defer c.Close() + defer func() { _ = c.Close() }() for { buffer := global.NewBuffer() t, reader, err := c.NextReader() @@ -348,7 +348,7 @@ func (c *webSocketConn) handleRequest(_ *coolq.CQBot, payload []byte) { defer func() { if err := recover(); err != nil { log.Printf("处置WS命令时发生无法恢复的异常:%v\n%s", err, debug.Stack()) - c.Close() + _ = c.Close() } }() global.RateLimit(context.Background())