diff --git a/coolq/api.go b/coolq/api.go index be8ca76..b59e538 100644 --- a/coolq/api.go +++ b/coolq/api.go @@ -4,6 +4,7 @@ import ( "crypto/md5" "encoding/hex" "io/ioutil" + "math" "os" "path" "path/filepath" @@ -667,8 +668,11 @@ func (bot *CQBot) CQGetStrangerInfo(userId int64) MSG { "sex": func() string { if info.Sex == 1 { return "female" + } else if info.Sex == 0 { + return "male" } - return "male" + // unknown = 0x2 + return "unknown" }(), "age": info.Age, "level": info.Level, @@ -848,7 +852,14 @@ func (bot *CQBot) CQGetGroupMessageHistory(groupId int64, seq int64) MSG { if g := bot.Client.FindGroup(groupId); g == nil { return Failed(100, "GROUP_NOT_FOUND", "群聊不存在") } - msg, err := bot.Client.GetGroupMessages(groupId, seq-19, seq) + if seq == 0 { + g, err := bot.Client.GetGroupInfo(groupId) + if err != nil { + return Failed(100, "GROUP_INFO_API_ERROR", err.Error()) + } + seq = g.LastMsgSeq + } + msg, err := bot.Client.GetGroupMessages(groupId, int64(math.Max(float64(seq-19), 1)), seq) if err != nil { log.Warnf("获取群历史消息失败: %v", err) return Failed(100, "MESSAGES_API_ERROR", err.Error()) @@ -1008,11 +1019,19 @@ func Failed(code int, msg ...string) MSG { func convertGroupMemberInfo(groupId int64, m *client.GroupMemberInfo) MSG { return MSG{ - "group_id": groupId, - "user_id": m.Uin, - "nickname": m.Nickname, - "card": m.CardName, - "sex": "unknown", + "group_id": groupId, + "user_id": m.Uin, + "nickname": m.Nickname, + "card": m.CardName, + "sex": func() string { + if m.Gender == 1 { + return "female" + } else if m.Gender == 0 { + return "male" + } + // unknown = 0xff + return "unknown" + }(), "age": 0, "area": "", "join_time": m.JoinTime, diff --git a/coolq/event.go b/coolq/event.go index 5555563..a2dfcd1 100644 --- a/coolq/event.go +++ b/coolq/event.go @@ -422,8 +422,8 @@ func (bot *CQBot) otherClientStatusChangedEvent(c *client.QQClient, e *client.Ot bot.dispatchEventMessage(MSG{ "post_type": "notice", "notice_type": "client_status", + "online": e.Online, "client": MSG{ - "online": e.Online, "app_id": e.Client.AppId, "device_name": e.Client.DeviceName, "device_kind": e.Client.DeviceKind, diff --git a/docs/cqhttp.md b/docs/cqhttp.md index f5686b6..f660e64 100644 --- a/docs/cqhttp.md +++ b/docs/cqhttp.md @@ -850,6 +850,30 @@ JSON数组: > 不提供起始序号将默认获取最新的消息 +### 获取当前账号在线客户端列表 + +终结点:`/get_online_clients` + +**参数** + +| 字段 | 类型 | 说明 | +| ---------- | ------ | ------------------------- | +| `no_cache` | bool | 是否无视缓存 | + +**响应数据** + +| 字段 | 类型 | 说明 | +| ---------- | ---------- | ------------ | +| `clients` | []Device | 在线客户端列表 | + +**Device** + +| 字段 | 类型 | 说明 | +| ---------- | ---------- | ------------ | +| `app_id` | int64 | 客户端ID | +| `device_name` | string | 设备名称 | +| `device_kind` | string | 设备类型 | + ### 获取用户VIP信息 终结点:`/_get_vip_info` @@ -1013,3 +1037,14 @@ JSON数组: | `name` | string | | 文件名 | | `size` | int64 | | 文件大小 | | `url` | string | | 下载链接 | + +### 其他客户端在线状态变更 + +**上报数据** + +| 字段 | 类型 | 可能的值 | 说明 | +| ------------- | ------ | -------------- | -------- | +| `post_type` | string | `notice` | 上报类型 | +| `notice_type` | string | `client_status` | 消息类型 | +| `client` | Device | | 客户端信息 | +| `online` | bool | | 当前是否在线 | \ No newline at end of file diff --git a/global/config.go b/global/config.go index 0396534..82f4cee 100644 --- a/global/config.go +++ b/global/config.go @@ -135,6 +135,9 @@ var DefaultConfigWithComments = ` } ` +//PasswordHash 存储QQ密码哈希供登录使用 +var PasswordHash [16]byte + //JSONConfig Config对应的结构体 type JSONConfig struct { Uin int64 `json:"uin"` diff --git a/go.mod b/go.mod index 760ad91..dcf8a63 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Mrs4s/go-cqhttp go 1.15 require ( - github.com/Mrs4s/MiraiGo v0.0.0-20210124063508-a401ed3ef104 + github.com/Mrs4s/MiraiGo v0.0.0-20210124065645-9549a32d954a github.com/dustin/go-humanize v1.0.0 github.com/gin-contrib/pprof v1.3.0 github.com/gin-gonic/gin v1.6.3 @@ -21,6 +21,7 @@ require ( github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816 github.com/tidwall/gjson v1.6.7 github.com/yinghau76/go-ascii-art v0.0.0-20190517192627-e7f465a30189 + golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 ) diff --git a/go.sum b/go.sum index 6f50907..4a055af 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Mrs4s/MiraiGo v0.0.0-20210124063508-a401ed3ef104 h1:dIKRhxzCrt/jdWRFdJupIU3BM94IYoicgqRzjN+2Bw4= -github.com/Mrs4s/MiraiGo v0.0.0-20210124063508-a401ed3ef104/go.mod h1:9V7DdSwpEfCKQNvLZhRnFJFkelTU0tPLfwR5l6UFF1Y= +github.com/Mrs4s/MiraiGo v0.0.0-20210124065645-9549a32d954a h1:ov5QCDpZpsr+geKLVON7E63UqrpNF0oTiKuZwbKwEmo= +github.com/Mrs4s/MiraiGo v0.0.0-20210124065645-9549a32d954a/go.mod h1:9V7DdSwpEfCKQNvLZhRnFJFkelTU0tPLfwR5l6UFF1Y= 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= @@ -110,6 +110,7 @@ github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/yinghau76/go-ascii-art v0.0.0-20190517192627-e7f465a30189 h1:4UJw9if55Fu3HOwbfcaQlJ27p3oeJU2JZqoeT3ITJQk= github.com/yinghau76/go-ascii-art v0.0.0-20190517192627-e7f465a30189/go.mod h1:rIrm5geMiBhPQkdfUm8gDFi/WiHneOp1i9KjmJqc+9I= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= diff --git a/main.go b/main.go index 63a756c..5f519f3 100644 --- a/main.go +++ b/main.go @@ -2,8 +2,11 @@ package main import ( "bufio" + "crypto/aes" "crypto/md5" + "crypto/sha1" "encoding/base64" + "encoding/hex" "fmt" "io" "io/ioutil" @@ -23,6 +26,7 @@ import ( "github.com/guonaihong/gout" "github.com/tidwall/gjson" "golang.org/x/term" + "golang.org/x/crypto/pbkdf2" "github.com/Mrs4s/MiraiGo/binary" "github.com/Mrs4s/MiraiGo/client" @@ -233,15 +237,11 @@ func main() { } if conf.EncryptPassword && conf.PasswordEncrypted == "" { log.Infof("密码加密已启用, 请输入Key对密码进行加密: (Enter 提交)") - byteKey, _ := term.ReadPassword(int(os.Stdin.Fd())) - key := md5.Sum(byteKey) - if encrypted := EncryptPwd(conf.Password, key[:]); encrypted != "" { - conf.Password = "" - conf.PasswordEncrypted = encrypted - _ = conf.Save("config.hjson") - } else { - log.Warnf("加密时出现问题.") - } + byteKey, _ = term.ReadPassword(int(os.Stdin.Fd())) + global.PasswordHash = md5.Sum([]byte(conf.Password)) + conf.Password = "" + conf.PasswordEncrypted = "AES:" + PasswordHashEncrypt(global.PasswordHash[:], byteKey) + _ = conf.Save("config.hjson") } if conf.PasswordEncrypted != "" { if len(byteKey) == 0 { @@ -262,8 +262,23 @@ func main() { } else { log.Infof("密码加密已启用, 使用运行时传递的参数进行解密,按 Ctrl+C 取消.") } - key := md5.Sum(byteKey) - conf.Password = DecryptPwd(conf.PasswordEncrypted, key[:]) + + //升级客户端密码加密方案,MD5+TEA 加密密码 -> PBKDF2+AES 加密 MD5 + //升级后的 PasswordEncrypted 字符串以"AES:"开始,其后为 Hex 编码的16字节加密 MD5 + if !strings.HasPrefix(conf.PasswordEncrypted, "AES:") { + password := OldPasswordDecrypt(conf.PasswordEncrypted, byteKey) + passwordHash := md5.Sum([]byte(password)) + newPasswordHash := PasswordHashEncrypt(passwordHash[:], byteKey) + conf.PasswordEncrypted = "AES:" + newPasswordHash + _ = conf.Save("config.hjson") + log.Debug("密码加密方案升级完成") + } + + ph, err := PasswordHashDecrypt(conf.PasswordEncrypted[4:], byteKey) + if err != nil { + log.Fatalf("加密存储的密码损坏,请尝试重新配置密码") + } + copy(global.PasswordHash[:], ph) } if !isFastStart { log.Info("Bot将在5秒后登录并开始信息处理, 按 Ctrl+C 取消.") @@ -283,7 +298,7 @@ func main() { } return "未知" }()) - cli := client.NewClient(conf.Uin, conf.Password) + cli := client.NewClientMd5(conf.Uin, global.PasswordHash) cli.OnLog(func(c *client.QQClient, e *client.LogEvent) { switch e.Type { case "INFO": @@ -340,27 +355,50 @@ func main() { } } -//EncryptPwd 通过给定key加密给定pwd -func EncryptPwd(pwd string, key []byte) string { - tea := binary.NewTeaCipher(key) - if tea == nil { - return "" +// PasswordHashEncrypt 使用key加密给定passwordHash +func PasswordHashEncrypt(passwordHash []byte, key []byte) string { + if len(passwordHash) != 16 { + panic("密码加密参数错误") } - return base64.StdEncoding.EncodeToString(tea.Encrypt([]byte(pwd))) + + key = pbkdf2.Key(key, key, 114514, 32, sha1.New) + + cipher, _ := aes.NewCipher(key) + result := make([]byte, 16) + cipher.Encrypt(result, passwordHash) + + return hex.EncodeToString(result) } -//DecryptPwd 通过给定key解密给定ePwd -func DecryptPwd(ePwd string, key []byte) string { +// PasswordHashDecrypt 使用key解密给定passwordHash +func PasswordHashDecrypt(encryptedPasswordHash string, key []byte) ([]byte, error) { + ciphertext, err := hex.DecodeString(encryptedPasswordHash) + if err != nil { + return nil, err + } + + key = pbkdf2.Key(key, key, 114514, 32, sha1.New) + + cipher, _ := aes.NewCipher(key) + result := make([]byte, 16) + cipher.Decrypt(result, ciphertext) + + return result, nil +} + +// OldPasswordDecrypt 使用key解密老password,仅供兼容使用 +func OldPasswordDecrypt(encryptedPassword string, key []byte) string { defer func() { if pan := recover(); pan != nil { log.Fatalf("密码解密失败: %v", pan) } }() - encrypted, err := base64.StdEncoding.DecodeString(ePwd) + encKey := md5.Sum(key) + encrypted, err := base64.StdEncoding.DecodeString(encryptedPassword) if err != nil { panic(err) } - tea := binary.NewTeaCipher(key) + tea := binary.NewTeaCipher(encKey[:]) if tea == nil { panic("密钥错误") }