1
0
mirror of https://github.com/Mrs4s/go-cqhttp.git synced 2025-05-04 19:17:37 +08:00

feat: qrcode login & remove admin api.

This commit is contained in:
Mrs4s 2021-03-21 19:37:14 +08:00
parent c51e603337
commit e0a551cea2
10 changed files with 321 additions and 725 deletions

View File

@ -1,14 +1,9 @@
package global package global
import ( import (
"errors"
"os" "os"
"os/exec"
"path" "path"
"path/filepath"
"runtime"
"strconv" "strconv"
"strings"
"time" "time"
"github.com/hjson/hjson-go" "github.com/hjson/hjson-go"
@ -324,13 +319,14 @@ func (c *JSONConfig) Save(path string) error {
// getCurrentPath 获取当前文件的路径直接返回string // getCurrentPath 获取当前文件的路径直接返回string
func getCurrentPath() string { func getCurrentPath() string {
cwd, e := GetCurrentPath() cwd, e := os.Getwd()
if e != nil { if e != nil {
panic(e) panic(e)
} }
return cwd return cwd
} }
/*
// GetCurrentPath 预留,获取当前目录地址 // GetCurrentPath 预留,获取当前目录地址
func GetCurrentPath() (string, error) { func GetCurrentPath() (string, error) {
file, err := exec.LookPath(os.Args[0]) file, err := exec.LookPath(os.Args[0])
@ -351,3 +347,4 @@ func GetCurrentPath() (string, error) {
} }
return fpath[0 : i+1], nil return fpath[0 : i+1], nil
} }
*/

9
go.mod
View File

@ -3,9 +3,9 @@ module github.com/Mrs4s/go-cqhttp
go 1.16 go 1.16
require ( 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/dustin/go-humanize v1.0.0
github.com/gin-contrib/pprof v1.3.0
github.com/gin-gonic/gin v1.6.3 github.com/gin-gonic/gin v1.6.3
github.com/gorilla/websocket v1.4.2 github.com/gorilla/websocket v1.4.2
github.com/guonaihong/gout v0.1.5 github.com/guonaihong/gout v0.1.5
@ -15,14 +15,17 @@ require (
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
github.com/lestrrat-go/strftime v1.0.4 // indirect 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/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.8.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/syndtr/goleveldb v1.0.0
github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816 github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816
github.com/tidwall/gjson v1.6.8 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/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/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/term v0.0.0-20210220032956-6a3ed077a48d
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba
) )

18
go.sum
View File

@ -1,7 +1,9 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 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/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-20210321041821-505aae133db3 h1:PEv43+PPI9JmCrXsK1N0oAwCgnjs4q3yrshugK6TpZo=
github.com/Mrs4s/MiraiGo v0.0.0-20210315005315-55aef1b0ffd0/go.mod h1:NjiWhlvGxwv1ftOWIoiFa/OzklnAYI4YqNexFOKSZKw= 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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 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= 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 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 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/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 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= 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/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 h1:T1Rb9EPkAhgxKqbcMIPguPq8glqXTA1koF8n9BHElA8=
github.com/lestrrat-go/strftime v1.0.4/go.mod h1:E1nN3pCbtMSu1yjSVeyuRFVm/U0xoR76fd03sz+Qz4g= 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 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 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= 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.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 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.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/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= 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/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU= github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU=
github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= 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 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= 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 h1:wSOdpTq0/eI46Ez/LkDwIsAKA71YP2SRKBODiRWM0as=
golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 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/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-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-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 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-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-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-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-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 h1:f1CIuDlJhwANEC2MM87MBEVMr3jl5bifgsfj90XAF9c=
golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

164
login.go Normal file
View File

@ -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)
}
}
}

131
main.go
View File

@ -13,13 +13,12 @@ import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"os" "os"
"os/exec"
"os/signal" "os/signal"
"path" "path"
"path/filepath"
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
"sync"
"syscall" "syscall"
"time" "time"
@ -168,11 +167,11 @@ func main() {
time.Sleep(time.Second * 10) time.Sleep(time.Second * 10)
} }
if conf.Uin == 0 || (conf.Password == "" && conf.PasswordEncrypted == "") { if conf.Uin == 0 || (conf.Password == "" && conf.PasswordEncrypted == "") {
log.Warnf("请修改 %s 以添加账号密码.", global.DefaultConfFile) log.Warn("账号密码未配置, 将使用二维码登录.")
if !isFastStart { if !isFastStart {
log.Warn("将在 5秒 后继续.")
time.Sleep(time.Second * 5) time.Sleep(time.Second * 5)
} }
return
} }
log.Info("当前版本:", coolq.Version) log.Info("当前版本:", coolq.Version)
@ -241,7 +240,7 @@ func main() {
log.Fatalf("加密存储的密码损坏,请尝试重新配置密码") log.Fatalf("加密存储的密码损坏,请尝试重新配置密码")
} }
copy(global.PasswordHash[:], ph) copy(global.PasswordHash[:], ph)
} else { } else if conf.Password != "" {
global.PasswordHash = md5.Sum([]byte(conf.Password)) global.PasswordHash = md5.Sum([]byte(conf.Password))
} }
if !isFastStart { if !isFastStart {
@ -262,7 +261,11 @@ func main() {
} }
return "未知" 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) { cli.OnLog(func(c *client.QQClient, e *client.LogEvent) {
switch e.Type { switch e.Type {
case "INFO": case "INFO":
@ -289,34 +292,102 @@ func main() {
log.Infof("收到服务器地址更新通知, 将在下一次重连时应用. ") log.Infof("收到服务器地址更新通知, 将在下一次重连时应用. ")
return true return true
}) })
if conf.WebUI == nil { /*
conf.WebUI = &global.GoCQWebUI{ if conf.WebUI == nil {
Enabled: true, conf.WebUI = &global.GoCQWebUI{
WebInput: false, Enabled: true,
Host: "0.0.0.0", WebInput: false,
WebUIPort: 9999, 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 { var times uint = 1 // 重试次数
conf.WebUI.WebUIPort = 9999 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 == "" { if conf.RateLimit.Enabled {
conf.WebUI.Host = "127.0.0.1" global.InitLimiter(conf.RateLimit.Frequency, conf.RateLimit.BucketSize)
} }
global.Proxy = conf.ProxyRewrite log.Info("正在加载事件过滤器.")
b := server.WebServer.Run(fmt.Sprintf("%s:%d", conf.WebUI.Host, conf.WebUI.WebUIPort), cli) global.BootFilter()
c := server.Console coolq.IgnoreInvalidCQCode = conf.IgnoreInvalidCQCode
r := server.Restart 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() go checkUpdate()
signal.Notify(c, os.Interrupt, syscall.SIGTERM) signal.Notify(c, os.Interrupt, syscall.SIGTERM)
select { <-c
case <-c:
b.Release()
case <-r:
log.Info("正在重启中...")
defer b.Release()
restart(arg)
}
} }
// PasswordHashEncrypt 使用key加密给定passwordHash // PasswordHashEncrypt 使用key加密给定passwordHash
@ -439,7 +510,7 @@ func selfUpdate(imageURL string) {
log.Error("更新失败: ", err) log.Error("更新失败: ", err)
return return
} }
defer resp.Body.Close() defer func() { _ = resp.Body.Close() }()
wc := global.WriteCounter{} wc := global.WriteCounter{}
err, _ = global.UpdateFromStream(io.TeeReader(resp.Body, &wc)) err, _ = global.UpdateFromStream(io.TeeReader(resp.Body, &wc))
fmt.Println() fmt.Println()
@ -463,6 +534,7 @@ func selfUpdate(imageURL string) {
os.Exit(0) os.Exit(0)
} }
/*
func restart(args []string) { func restart(args []string) {
var cmd *exec.Cmd var cmd *exec.Cmd
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
@ -493,6 +565,7 @@ func restart(args []string) {
} }
_ = cmd.Start() _ = cmd.Start()
} }
*/
func getConfig() *global.JSONConfig { func getConfig() *global.JSONConfig {
var conf *global.JSONConfig var conf *global.JSONConfig

View File

@ -2,7 +2,6 @@ package server
import ( import (
"strings" "strings"
"time"
"github.com/Mrs4s/go-cqhttp/coolq" "github.com/Mrs4s/go-cqhttp/coolq"
"github.com/Mrs4s/go-cqhttp/global" "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) return bot.CQGetGroupHonorInfo(p.Get("group_id").Int(), p.Get("type").Str)
} }
func setRestart(_ *coolq.CQBot, p resultGetter) coolq.MSG { func setRestart(_ *coolq.CQBot, _ resultGetter) coolq.MSG {
var delay int64 /*
delay = p.Get("delay").Int() var delay int64
if delay < 0 { delay = p.Get("delay").Int()
delay = 0 if delay < 0 {
} delay = 0
defer func(delay int64) { }
time.Sleep(time.Duration(delay) * time.Millisecond) defer func(delay int64) {
Restart <- struct{}{} time.Sleep(time.Duration(delay) * time.Millisecond)
}(delay) Restart <- struct{}{}
return coolq.MSG{"data": nil, "retcode": 0, "status": "async"} }(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 { func canSendImage(bot *coolq.CQBot, _ resultGetter) coolq.MSG {

View File

@ -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}))
}

View File

@ -1,6 +1,7 @@
// daemon 功能写在这,目前仅支持了-d 作为后台运行参数stopstartrestart这些功能目前看起来并不需要可以通过api控制后续需要的话再补全。
package server package server
// daemon 功能写在这,目前仅支持了-d 作为后台运行参数stopstartrestart这些功能目前看起来并不需要可以通过api控制后续需要的话再补全。
import ( import (
"fmt" "fmt"
"os" "os"

View File

@ -29,7 +29,8 @@ type httpServer struct {
api apiCaller api apiCaller
} }
type httpClient struct { // HTTPClient 反向HTTP上报客户端
type HTTPClient struct {
bot *coolq.CQBot bot *coolq.CQBot
secret string secret string
addr string addr string
@ -40,7 +41,8 @@ type httpContext struct {
ctx *gin.Context ctx *gin.Context
} }
var cqHTTPServer = &httpServer{} // CQHTTPApiServer CQHTTPApiServer实例
var CQHTTPApiServer = &httpServer{}
// Debug 是否启用Debug模式 // Debug 是否启用Debug模式
var Debug = false var Debug = false
@ -107,11 +109,13 @@ func (s *httpServer) Run(addr, authToken string, bot *coolq.CQBot) {
}() }()
} }
func newHTTPClient() *httpClient { // NewHTTPClient 返回反向HTTP客户端
return &httpClient{} 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.bot = bot
c.secret = secret c.secret = secret
c.addr = addr 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) log.Infof("HTTP POST上报器已启动: %v", addr)
} }
func (c *httpClient) onBotPushEvent(m *bytes.Buffer) { func (c *HTTPClient) onBotPushEvent(m *bytes.Buffer) {
var res string var res string
err := gout.POST(c.addr).SetJSON(m.Bytes()).BindBody(&res).SetHeader(func() gout.H { err := gout.POST(c.addr).SetJSON(m.Bytes()).BindBody(&res).SetHeader(func() gout.H {
h := gout.H{ h := gout.H{

View File

@ -177,7 +177,7 @@ func (c *WebSocketClient) connectUniversal() {
} }
func (c *WebSocketClient) listenAPI(conn *webSocketConn, u bool) { func (c *WebSocketClient) listenAPI(conn *webSocketConn, u bool) {
defer conn.Close() defer func() { _ = conn.Close() }()
for { for {
buffer := global.NewBuffer() buffer := global.NewBuffer()
t, reader, err := conn.NextReader() 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)) err = c.WriteMessage(websocket.TextMessage, []byte(s.handshake))
if err != nil { if err != nil {
log.Warnf("WebSocket 握手时出现错误: %v", err) log.Warnf("WebSocket 握手时出现错误: %v", err)
c.Close() _ = c.Close()
return return
} }
@ -309,7 +309,7 @@ func (s *webSocketServer) any(w http.ResponseWriter, r *http.Request) {
err = c.WriteMessage(websocket.TextMessage, []byte(s.handshake)) err = c.WriteMessage(websocket.TextMessage, []byte(s.handshake))
if err != nil { if err != nil {
log.Warnf("WebSocket 握手时出现错误: %v", err) log.Warnf("WebSocket 握手时出现错误: %v", err)
c.Close() _ = c.Close()
return return
} }
log.Infof("接受 WebSocket 连接: %v (/)", r.RemoteAddr) 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) { func (s *webSocketServer) listenAPI(c *webSocketConn) {
defer c.Close() defer func() { _ = c.Close() }()
for { for {
buffer := global.NewBuffer() buffer := global.NewBuffer()
t, reader, err := c.NextReader() t, reader, err := c.NextReader()
@ -348,7 +348,7 @@ func (c *webSocketConn) handleRequest(_ *coolq.CQBot, payload []byte) {
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
log.Printf("处置WS命令时发生无法恢复的异常%v\n%s", err, debug.Stack()) log.Printf("处置WS命令时发生无法恢复的异常%v\n%s", err, debug.Stack())
c.Close() _ = c.Close()
} }
}() }()
global.RateLimit(context.Background()) global.RateLimit(context.Background())