github.com/Mrs4s/go-cqhttp@v1.2.0/cmd/gocq/login.go (about) 1 package gocq 2 3 import ( 4 "bufio" 5 "bytes" 6 "fmt" 7 "image" 8 "image/png" 9 "os" 10 "strings" 11 "time" 12 13 "github.com/Mrs4s/MiraiGo/client" 14 "github.com/Mrs4s/MiraiGo/utils" 15 "github.com/mattn/go-colorable" 16 "github.com/pkg/errors" 17 log "github.com/sirupsen/logrus" 18 "gopkg.ilharper.com/x/isatty" 19 20 "github.com/Mrs4s/go-cqhttp/global" 21 "github.com/Mrs4s/go-cqhttp/internal/download" 22 ) 23 24 var console = bufio.NewReader(os.Stdin) 25 26 func readLine() (str string) { 27 str, _ = console.ReadString('\n') 28 str = strings.TrimSpace(str) 29 return 30 } 31 32 func readLineTimeout(t time.Duration) { 33 r := make(chan string) 34 go func() { 35 select { 36 case r <- readLine(): 37 case <-time.After(t): 38 } 39 }() 40 select { 41 case <-r: 42 case <-time.After(t): 43 } 44 } 45 46 func readIfTTY(de string) (str string) { 47 if isatty.Isatty(os.Stdin.Fd()) { 48 return readLine() 49 } 50 log.Warnf("未检测到输入终端,自动选择%s.", de) 51 return de 52 } 53 54 var cli *client.QQClient 55 var device *client.DeviceInfo 56 57 // ErrSMSRequestError SMS请求出错 58 var ErrSMSRequestError = errors.New("sms request error") 59 60 func commonLogin() error { 61 res, err := cli.Login() 62 if err != nil { 63 return err 64 } 65 return loginResponseProcessor(res) 66 } 67 68 func printQRCode(imgData []byte) { 69 const ( 70 black = "\033[48;5;0m \033[0m" 71 white = "\033[48;5;7m \033[0m" 72 ) 73 img, err := png.Decode(bytes.NewReader(imgData)) 74 if err != nil { 75 log.Panic(err) 76 } 77 data := img.(*image.Gray).Pix 78 bound := img.Bounds().Max.X 79 buf := make([]byte, 0, (bound*4+1)*(bound)) 80 i := 0 81 for y := 0; y < bound; y++ { 82 i = y * bound 83 for x := 0; x < bound; x++ { 84 if data[i] != 255 { 85 buf = append(buf, white...) 86 } else { 87 buf = append(buf, black...) 88 } 89 i++ 90 } 91 buf = append(buf, '\n') 92 } 93 _, _ = colorable.NewColorableStdout().Write(buf) 94 } 95 96 func qrcodeLogin() error { 97 rsp, err := cli.FetchQRCodeCustomSize(1, 2, 1) 98 if err != nil { 99 return err 100 } 101 _ = os.WriteFile("qrcode.png", rsp.ImageData, 0o644) 102 defer func() { _ = os.Remove("qrcode.png") }() 103 if cli.Uin != 0 { 104 log.Infof("请使用账号 %v 登录手机QQ扫描二维码 (qrcode.png) : ", cli.Uin) 105 } else { 106 log.Infof("请使用手机QQ扫描二维码 (qrcode.png) : ") 107 } 108 time.Sleep(time.Second) 109 printQRCode(rsp.ImageData) 110 s, err := cli.QueryQRCodeStatus(rsp.Sig) 111 if err != nil { 112 return err 113 } 114 prevState := s.State 115 for { 116 time.Sleep(time.Second) 117 s, _ = cli.QueryQRCodeStatus(rsp.Sig) 118 if s == nil { 119 continue 120 } 121 if prevState == s.State { 122 continue 123 } 124 prevState = s.State 125 switch s.State { 126 case client.QRCodeCanceled: 127 log.Fatalf("扫码被用户取消.") 128 case client.QRCodeTimeout: 129 log.Fatalf("二维码过期") 130 case client.QRCodeWaitingForConfirm: 131 log.Infof("扫码成功, 请在手机端确认登录.") 132 case client.QRCodeConfirmed: 133 res, err := cli.QRCodeLogin(s.LoginInfo) 134 if err != nil { 135 return err 136 } 137 return loginResponseProcessor(res) 138 case client.QRCodeImageFetch, client.QRCodeWaitingForScan: 139 // ignore 140 } 141 } 142 } 143 144 func loginResponseProcessor(res *client.LoginResponse) error { 145 var err error 146 for { 147 if err != nil { 148 return err 149 } 150 if res.Success { 151 return nil 152 } 153 var text string 154 switch res.Error { 155 case client.SliderNeededError: 156 log.Warnf("登录需要滑条验证码, 请验证后重试.") 157 ticket := getTicket(res.VerifyUrl) 158 if ticket == "" { 159 log.Infof("按 Enter 继续....") 160 readLine() 161 os.Exit(0) 162 } 163 res, err = cli.SubmitTicket(ticket) 164 continue 165 case client.NeedCaptcha: 166 log.Warnf("登录需要验证码.") 167 _ = os.WriteFile("captcha.jpg", res.CaptchaImage, 0o644) 168 log.Warnf("请输入验证码 (captcha.jpg): (Enter 提交)") 169 text = readLine() 170 global.DelFile("captcha.jpg") 171 res, err = cli.SubmitCaptcha(text, res.CaptchaSign) 172 continue 173 case client.SMSNeededError: 174 log.Warnf("账号已开启设备锁, 按 Enter 向手机 %v 发送短信验证码.", res.SMSPhone) 175 readLine() 176 if !cli.RequestSMS() { 177 log.Warnf("发送验证码失败,可能是请求过于频繁.") 178 return errors.WithStack(ErrSMSRequestError) 179 } 180 log.Warn("请输入短信验证码: (Enter 提交)") 181 text = readLine() 182 res, err = cli.SubmitSMS(text) 183 continue 184 case client.SMSOrVerifyNeededError: 185 log.Warnf("账号已开启设备锁,请选择验证方式:") 186 log.Warnf("1. 向手机 %v 发送短信验证码", res.SMSPhone) 187 log.Warnf("2. 使用手机QQ扫码验证.") 188 log.Warn("请输入(1 - 2):") 189 text = readIfTTY("2") 190 if strings.Contains(text, "1") { 191 if !cli.RequestSMS() { 192 log.Warnf("发送验证码失败,可能是请求过于频繁.") 193 return errors.WithStack(ErrSMSRequestError) 194 } 195 log.Warn("请输入短信验证码: (Enter 提交)") 196 text = readLine() 197 res, err = cli.SubmitSMS(text) 198 continue 199 } 200 fallthrough 201 case client.UnsafeDeviceError: 202 log.Warnf("账号已开启设备锁,请前往 -> %v <- 验证后重启Bot.", res.VerifyUrl) 203 log.Infof("按 Enter 或等待 5s 后继续....") 204 readLineTimeout(time.Second * 5) 205 os.Exit(0) 206 case client.OtherLoginError, client.UnknownLoginError, client.TooManySMSRequestError: 207 msg := res.ErrorMessage 208 log.Warnf("登录失败: %v Code: %v", msg, res.Code) 209 switch res.Code { 210 case 235: 211 log.Warnf("设备信息被封禁, 请删除 device.json 后重试.") 212 case 237: 213 log.Warnf("登录过于频繁, 请在手机QQ登录并根据提示完成认证后等一段时间重试") 214 case 45: 215 log.Warnf("你的账号被限制登录, 请配置 SignServer 后重试") 216 } 217 log.Infof("按 Enter 继续....") 218 readLine() 219 os.Exit(0) 220 } 221 } 222 } 223 224 func getTicket(u string) string { 225 log.Warnf("请选择提交滑块ticket方式:") 226 log.Warnf("1. 自动提交") 227 log.Warnf("2. 手动抓取提交") 228 log.Warn("请输入(1 - 2):") 229 text := readLine() 230 id := utils.RandomString(8) 231 auto := !strings.Contains(text, "2") 232 if auto { 233 u = strings.ReplaceAll(u, "https://ssl.captcha.qq.com/template/wireless_mqq_captcha.html?", fmt.Sprintf("https://captcha.go-cqhttp.org/captcha?id=%v&", id)) 234 } 235 log.Warnf("请前往该地址验证 -> %v ", u) 236 if !auto { 237 log.Warn("请输入ticket: (Enter 提交)") 238 return readLine() 239 } 240 241 for count := 120; count > 0; count-- { 242 str := fetchCaptcha(id) 243 if str != "" { 244 return str 245 } 246 time.Sleep(time.Second) 247 } 248 log.Warnf("验证超时") 249 return "" 250 } 251 252 func fetchCaptcha(id string) string { 253 g, err := download.Request{URL: "https://captcha.go-cqhttp.org/captcha/ticket?id=" + id}.JSON() 254 if err != nil { 255 log.Debugf("获取 Ticket 时出现错误: %v", err) 256 return "" 257 } 258 if g.Get("ticket").Exists() { 259 return g.Get("ticket").String() 260 } 261 return "" 262 }