Skip to content

Commit

Permalink
feat: 支持密码登录(x)
Browse files Browse the repository at this point in the history
  • Loading branch information
Redmomn committed Nov 12, 2024
1 parent faa0c02 commit 28a74bc
Show file tree
Hide file tree
Showing 6 changed files with 263 additions and 25 deletions.
145 changes: 143 additions & 2 deletions cmd/gocq/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ package gocq
import (
"bufio"
"bytes"
"fmt"
"image"
"image/color"
"image/png"
"os"
"strings"
"time"

"github.com/Mrs4s/go-cqhttp/internal/download"

"github.com/LagrangeDev/LagrangeGo/utils"

"github.com/LagrangeDev/LagrangeGo/client/packets/wtlogin/qrcodestate"
Expand Down Expand Up @@ -59,6 +62,14 @@ var device *auth.DeviceInfo
// ErrSMSRequestError SMS请求出错
var ErrSMSRequestError = errors.New("sms request error")

func commonLogin() error {
res, err := cli.PasswordLogin()
if err != nil {
return err
}
return loginResponseProcessor(res)
}

func printQRCode(imgData []byte) {
// (".", "^", " ", "@") : ("▄", "▀", " ", "█")
const (
Expand Down Expand Up @@ -104,6 +115,7 @@ func printQRCode(imgData []byte) {
_, _ = colorable.NewColorableStdout().Write(buf)
}

//nolint:unused
func printQRCodeCommon(imgData []byte) {
const (
black = "\033[48;5;0m \033[0m"
Expand Down Expand Up @@ -166,13 +178,142 @@ func qrcodeLogin() error {
case qrcodestate.WaitingForConfirm:
log.Infof("扫码成功, 请在手机端确认登录.")
case qrcodestate.Confirmed:
err := cli.QRCodeLogin(1)
res, err := cli.QRCodeLogin()
if err != nil {
return err
}
return cli.Register()
return loginResponseProcessor(res)
case qrcodestate.WaitingForScan:
// ignore
}
}
}

func loginResponseProcessor(res *client.LoginResponse) error {
var err error
for {
if err != nil {
return err
}
if res.Success {
return nil
}
//var text string
//nolint:exhaustive
switch res.Error {
case client.SliderNeededError:
log.Warnf("登录需要滑条验证码, 请验证后重试.")
ticket, randStr := getTicket(res.VerifyURL)
if ticket == "" {
log.Infof("按 Enter 继续....")
readLine()
os.Exit(0)
}
res, err = cli.SubmitCaptcha(strings.Split(strings.Split(res.VerifyURL, "sid=")[1], "&")[0], ticket, randStr)
continue
//case client.NeedCaptcha:
// log.Warnf("登录需要验证码.")
// _ = os.WriteFile("captcha.jpg", res.CaptchaImage, 0o644)
// log.Warnf("请输入验证码 (captcha.jpg): (Enter 提交)")
// text = readLine()
// global.DelFile("captcha.jpg")
// res, err = cli.SubmitCaptcha(text, res.CaptchaSign)
// continue
// TODO 短信验证码?
//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
// TODO 设备锁?
//case client.SMSOrVerifyNeededError:
// log.Warnf("账号已开启设备锁,请选择验证方式:")
// log.Warnf("1. 向手机 %v 发送短信验证码", res.SMSPhone)
// log.Warnf("2. 使用手机QQ扫码验证.")
// log.Warn("请输入(1 - 2):")
// text = readIfTTY("2")
// 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 或等待 5s 后继续....")
readLineTimeout(time.Second * 5)
os.Exit(0)
case client.OtherLoginError, client.UnknownLoginError, client.TooManySMSRequestError:
fallthrough
default:
msg := res.ErrorMessage
log.Warnf("登录失败: %v Code: %v", msg, res.Code)
switch res.Code {
case 235:
log.Warnf("设备信息被封禁, 请删除 device.json 后重试.")
case 237:
log.Warnf("登录过于频繁, 请在手机QQ登录并根据提示完成认证后等一段时间重试")
case 45:
log.Warnf("你的账号被限制登录, 请配置 SignServer 后重试")
}
log.Infof("按 Enter 继续....")
readLine()
os.Exit(0)
}
}
}

func getTicket(u string) (string, string) {
log.Warnf("请选择提交滑块ticket方式:")
log.Warnf("1. 自动提交")
log.Warnf("2. 手动抓取提交")
log.Warn("请输入(1 - 2):")
text := readLine()
id := utils.NewUUID()
auto := !strings.Contains(text, "2")
// TODO 自动获取验证码
if auto {
u = strings.ReplaceAll(u, "https://ti.qq.com/safe/tools/captcha/sms-verify-login?", fmt.Sprintf("https://captcha.go-cqhttp.org/captcha?id=%v&", id))
}
log.Warnf("请前往该地址验证 -> %v ", u)
if !auto {
log.Warn("请输入ticket: (Enter 提交)")
ticket := readLine()
log.Warn("请输入rand_str: (Enter 提交)")
randStr := readLine()
return ticket, randStr
}

for count := 120; count > 0; count-- {
ticket, randStr := fetchCaptcha(id)
if ticket != "" && randStr != "" {
return ticket, randStr
}
time.Sleep(time.Second)
}
log.Warnf("验证超时")
return "", ""
}

func fetchCaptcha(id string) (string, string) {
g, err := download.Request{URL: "https://captcha.go-cqhttp.org/captcha/ticket?id=" + id}.JSON()
if err != nil {
log.Debugf("获取 Ticket 时出现错误: %v", err)
return "", ""
}
if g.Get("ticket").Exists() && g.Get("randstr").Exists() {
return g.Get("ticket").String(), g.Get("randstr").String()
}
return "", ""
}
115 changes: 100 additions & 15 deletions cmd/gocq/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package gocq

import (
"crypto/aes"
"crypto/md5"
"crypto/sha1"
"encoding/hex"
"fmt"
Expand All @@ -17,9 +18,11 @@ import (
"github.com/LagrangeDev/LagrangeGo/client/auth"

"github.com/LagrangeDev/LagrangeGo/client"
para "github.com/fumiama/go-hide-param"
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
log "github.com/sirupsen/logrus"
"golang.org/x/crypto/pbkdf2"
"golang.org/x/term"

"github.com/Mrs4s/go-cqhttp/coolq"
"github.com/Mrs4s/go-cqhttp/db"
Expand Down Expand Up @@ -103,6 +106,7 @@ func PrepareData() {

// LoginInteract 登录交互, 可能需要键盘输入, 必须在 InitBase, PrepareData 之后执行
func LoginInteract() {
var byteKey []byte
arg := os.Args
if len(arg) > 1 {
for i := range arg {
Expand All @@ -113,12 +117,18 @@ func LoginInteract() {
} else {
selfupdate.SelfUpdate("")
}
case "key":
p := i + 1
if len(arg) > p {
byteKey = []byte(arg[p])
para.Hide(p)
}
}
}
}

if !global.FileExists("session.token") {
log.Info("不存在会话缓存,使用二维码登录.")
if (base.Account.Uin == 0 || (base.Account.Password == "" && !base.Account.Encrypt)) && !global.PathExists("session.token") {
log.Warn("账号密码未配置, 将使用二维码登录.")
if !base.FastStart {
log.Warn("将在 5秒 后继续.")
time.Sleep(time.Second * 5)
Expand All @@ -143,6 +153,56 @@ func LoginInteract() {
}
}

if base.Account.Encrypt {
if !global.PathExists("password.encrypt") {
if base.Account.Password == "" {
log.Error("无法进行加密,请在配置文件中的添加密码后重新启动.")
} else {
log.Infof("密码加密已启用, 请输入Key对密码进行加密: (Enter 提交)")
byteKey, _ = term.ReadPassword(int(os.Stdin.Fd()))
base.PasswordHash = md5.Sum([]byte(base.Account.Password))
_ = os.WriteFile("password.encrypt", []byte(PasswordHashEncrypt(base.PasswordHash[:], byteKey)), 0o644)
log.Info("密码已加密,为了您的账号安全,请删除配置文件中的密码后重新启动.")
}
readLine()
os.Exit(0)
}
if base.Account.Password != "" {
log.Error("密码已加密,为了您的账号安全,请删除配置文件中的密码后重新启动.")
readLine()
os.Exit(0)
}
if len(byteKey) == 0 {
log.Infof("密码加密已启用, 请输入Key对密码进行解密以继续: (Enter 提交)")
cancel := make(chan struct{}, 1)
state, _ := term.GetState(int(os.Stdin.Fd()))
go func() {
select {
case <-cancel:
return
case <-time.After(time.Second * 45):
log.Infof("解密key输入超时")
time.Sleep(3 * time.Second)
_ = term.Restore(int(os.Stdin.Fd()), state)
os.Exit(0)
}
}()
byteKey, _ = term.ReadPassword(int(os.Stdin.Fd()))
cancel <- struct{}{}
} else {
log.Infof("密码加密已启用, 使用运行时传递的参数进行解密,按 Ctrl+C 取消.")
}

encrypt, _ := os.ReadFile("password.encrypt")
ph, err := PasswordHashDecrypt(string(encrypt), byteKey)
if err != nil {
log.Fatalf("加密存储的密码损坏,请尝试重新配置密码")
}
copy(base.PasswordHash[:], ph)
} else if len(base.Account.Password) > 0 {
base.PasswordHash = md5.Sum([]byte(base.Account.Password))
}

if !base.FastStart {
log.Info("Bot将在5秒后登录并开始信息处理, 按 Ctrl+C 取消.")
time.Sleep(time.Second * 5)
Expand All @@ -151,8 +211,9 @@ func LoginInteract() {
app := auth.AppList["linux"]["3.2.10-25765"]
log.Infof("使用协议: %s %s", app.OS, app.CurrentVersion)
cli = newClient(app)
cli.UseVersion(app)
cli.UseDevice(device)
isQRCodeLogin := true
isQRCodeLogin := (base.Account.Uin == 0 || len(base.Account.Password) == 0) && !base.Account.Encrypt
isTokenLogin := false

saveToken := func() {
Expand All @@ -163,7 +224,20 @@ func LoginInteract() {
token, _ := os.ReadFile("session.token")
sig, err := auth.UnmarshalSigInfo(token, true)
if err == nil {
if err = cli.FastLogin(&sig); err != nil {
if base.Account.Uin != 0 && int64(sig.Uin) != base.Account.Uin {
log.Warnf("警告: 配置文件内的QQ号 (%v) 与缓存内的QQ号 (%v) 不相同", base.Account.Uin, int64(sig.Uin))
log.Warnf("1. 使用会话缓存继续.")
log.Warnf("2. 删除会话缓存并重启.")
log.Warnf("请选择:")
text := readIfTTY("1")
if text == "2" {
_ = os.Remove("session.token")
log.Infof("缓存已删除.")
os.Exit(0)
}
}
cli.UseSig(sig)
if err = cli.FastLogin(); err != nil {
_ = os.Remove("session.token")
log.Warnf("恢复会话失败: %v , 尝试使用正常流程登录.", err)
time.Sleep(time.Second)
Expand All @@ -176,9 +250,19 @@ func LoginInteract() {
}
}
}
if base.Account.Uin != 0 && base.PasswordHash != [16]byte{} {
cli.Uin = uint32(base.Account.Uin)
cli.PasswordMD5 = base.PasswordHash
}
if !isTokenLogin {
if err := qrcodeLogin(); err != nil {
log.Fatalf("登录时发生致命错误: %v", err)
if !isQRCodeLogin {
if err := commonLogin(); err != nil {
log.Fatalf("登录时发生致命错误: %v", err)
}
} else {
if err := qrcodeLogin(); err != nil {
log.Fatalf("登录时发生致命错误: %v", err)
}
}
}
var times uint = 1 // 重试次数
Expand Down Expand Up @@ -212,7 +296,7 @@ func LoginInteract() {
break
}
log.Warnf("尝试重连...")
err := cli.FastLogin(nil)
err := cli.FastLogin()
if err == nil {
saveToken()
return
Expand Down Expand Up @@ -242,7 +326,7 @@ func LoginInteract() {
global.Check(cli.RefreshAllGroupsInfo(), true)
GroupListLen := len(cli.GetCachedAllGroupsInfo())
log.Infof("共加载 %v 个群.", GroupListLen)
// TODO 设置在线状态 不支持
// TODO 设置在线状态 暂不支持
// if uint(base.Account.Status) >= uint(len(allowStatus)) {
// base.Account.Status = 0
//}
Expand Down Expand Up @@ -297,16 +381,18 @@ func PasswordHashDecrypt(encryptedPasswordHash string, key []byte) ([]byte, erro
return result, nil
}

func newClient(appInfo *auth.AppInfo) *client.QQClient {
signUrls := make([]string, len(base.SignServers))
for i, s := range base.SignServers {
func newClient(app *auth.AppInfo) *client.QQClient {
signUrls := make([]string, 0, len(base.SignServers))
for _, s := range base.SignServers {
u, err := url.Parse(s.URL)
if err != nil || u.Hostname() == "" {
continue
}
signUrls[i] = u.String()
signUrls = append(signUrls, u.String())
}
c := client.NewClient(0, appInfo, signUrls...)
c := client.NewClientEmpty()
c.UseVersion(app)
c.AddSignServer(signUrls...)
// TODO 服务器更新通知
// c.OnServerUpdated(func(bot *client.QQClient, e *client.ServerUpdatedEvent) bool {
// if !base.UseSSOAddress {
Expand All @@ -320,8 +406,7 @@ func newClient(appInfo *auth.AppInfo) *client.QQClient {
log.Infof("检测到 address.txt 文件. 将覆盖目标IP.")
addr := global.ReadAddrFile("address.txt")
if len(addr) > 0 {
// TODO 使用自定义服务器
// c.SetCustomServer(addr)
c.SetCustomServer(addr)
}
log.Infof("读取到 %v 个自定义地址.", len(addr))
}
Expand Down
Loading

0 comments on commit 28a74bc

Please sign in to comment.