diff --git a/README.md b/README.md index 0f39931..f7aef0b 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ | [群消息](https://cqhttp.cc/docs/4.15/#/Post?id=群消息) | | [群消息撤回(拓展Event)](docs/cqhttp.md#群消息撤回) | | [好友消息撤回(拓展Event)](docs/cqhttp.md#好友消息撤回) | -| 群内提示事件(拓展Event)(docs/cqhttp.md#群内戳一戳) | +| [群内提示事件(拓展Event)(龙王等事件)](docs/cqhttp.md#群内戳一戳) | | [群管理员变动](https://cqhttp.cc/docs/4.15/#/Post?id=群管理员变动) | | [群成员减少](https://cqhttp.cc/docs/4.15/#/Post?id=群成员减少) | | [群成员增加](https://cqhttp.cc/docs/4.15/#/Post?id=群成员增加) | diff --git a/coolq/api.go b/coolq/api.go index 486c069..dedaadd 100644 --- a/coolq/api.go +++ b/coolq/api.go @@ -497,6 +497,27 @@ func (bot *CQBot) CQGetGroupHonorInfo(groupId int64, t string) MSG { return OK(msg) } +// https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_stranger_info-%E8%8E%B7%E5%8F%96%E9%99%8C%E7%94%9F%E4%BA%BA%E4%BF%A1%E6%81%AF +func (bot *CQBot) CQGetStrangerInfo(userId int64) MSG { + info, err := bot.Client.GetSummaryInfo(userId) + if err != nil { + return Failed(100) + } + return OK(MSG{ + "user_id": info.Uin, + "nickname": info.Nickname, + "sex": func() string { + if info.Sex == 1 { + return "female" + } + return "male" + }(), + "age": info.Age, + "level": info.Level, + "login_days": info.LoginDays, + }) +} + // https://cqhttp.cc/docs/4.15/#/API?id=-handle_quick_operation-%E5%AF%B9%E4%BA%8B%E4%BB%B6%E6%89%A7%E8%A1%8C%E5%BF%AB%E9%80%9F%E6%93%8D%E4%BD%9C // https://github.com/richardchien/coolq-http-api/blob/master/src/cqhttp/plugins/web/http.cpp#L376 func (bot *CQBot) CQHandleQuickOperation(context, operation gjson.Result) MSG { @@ -627,6 +648,20 @@ func (bot *CQBot) CQCanSendRecord() MSG { return OK(MSG{"yes": true}) } +func (bot *CQBot) CQOcrImage(imageId string) MSG { + img, err := bot.makeImageElem("image", map[string]string{"file": imageId}, true) + if err != nil { + log.Warnf("load image error: %v", err) + return Failed(100) + } + rsp, err := bot.Client.ImageOcr(img) + if err != nil { + log.Warnf("ocr image error: %v", err) + return Failed(100) + } + return OK(rsp) +} + func (bot *CQBot) CQReloadEventFilter() MSG { global.BootFilter() return OK(nil) @@ -655,6 +690,18 @@ func (bot *CQBot) CQGetVersionInfo() MSG { "runtime_version": runtime.Version(), "runtime_os": runtime.GOOS, "version": Version, + "protocol": func() int { + switch client.SystemDeviceInfo.Protocol { + case client.AndroidPad: + return 0 + case client.AndroidPhone: + return 1 + case client.AndroidWatch: + return 2 + default: + return -1 + } + }(), }) } diff --git a/coolq/bot.go b/coolq/bot.go index 3083d13..dda64e9 100644 --- a/coolq/bot.go +++ b/coolq/bot.go @@ -139,6 +139,14 @@ func (bot *CQBot) SendGroupMessage(groupId int64, m *message.SendingMessage) int newElem = append(newElem, gv) continue } + if i, ok := elem.(*PokeElement); ok { + if group := bot.Client.FindGroup(groupId); group != nil { + if mem := group.FindMember(i.Target); mem != nil { + mem.Poke() + return 0 + } + } + } newElem = append(newElem, elem) } m.Elements = newElem diff --git a/coolq/cqcode.go b/coolq/cqcode.go index 670a5df..c5a74f2 100644 --- a/coolq/cqcode.go +++ b/coolq/cqcode.go @@ -26,6 +26,14 @@ var paramReg = regexp.MustCompile(`,([\w\-.]+?)=([^,\]]+)`) var IgnoreInvalidCQCode = false +type PokeElement struct { + Target int64 +} + +func (e *PokeElement) Type() message.ElementType { + return message.At +} + func ToArrayMessage(e []message.IMessageElement, code int64, raw ...bool) (r []MSG) { ur := false if len(raw) != 0 { @@ -50,6 +58,8 @@ func ToArrayMessage(e []message.IMessageElement, code int64, raw ...bool) (r []M "type": "text", "data": map[string]string{"text": o.Content}, } + case *message.ReplyElement: + continue case *message.LightAppElement: //m = MSG{ // "type": "text", @@ -317,6 +327,12 @@ func (bot *CQBot) ToElement(t string, d map[string]string, group bool) (message. return message.NewText(d["text"]), nil case "image": return bot.makeImageElem(t, d, group) + case "poke": + if !group { + return nil, errors.New("todo") // TODO: private poke + } + t, _ := strconv.ParseInt(d["qq"], 10, 64) + return &PokeElement{Target: t}, nil case "record": if !group { return nil, errors.New("private voice unsupported now") diff --git a/coolq/event.go b/coolq/event.go index a7fa0fa..d03a8a4 100644 --- a/coolq/event.go +++ b/coolq/event.go @@ -216,11 +216,11 @@ func (bot *CQBot) groupNotifyEvent(c *client.QQClient, e client.IGroupNotifyEven "post_type": "notice", "group_id": group.Code, "notice_type": "notify", - "notify_type": "poke", + "sub_type": "poke", "self_id": c.Uin, "user_id": notify.Sender, "sender_id": notify.Sender, - "receiver_id": notify.Receiver, + "target_id": notify.Receiver, "time": time.Now().Unix(), }) case *client.GroupRedBagLuckyKingNotifyEvent: @@ -228,15 +228,38 @@ func (bot *CQBot) groupNotifyEvent(c *client.QQClient, e client.IGroupNotifyEven luckyKing := group.FindMember(notify.LuckyKing) log.Infof("群 %v 内 %v 的红包被抢完, %v 是运气王", formatGroupName(group), formatMemberName(sender), formatMemberName(luckyKing)) bot.dispatchEventMessage(MSG{ - "post_type": "notice", - "group_id": group.Code, - "notice_type": "notify", - "notify_type": "lucky_king", - "self_id": c.Uin, - "user_id": notify.Sender, - "sender_id": notify.Sender, - "lucky_king_id": notify.LuckyKing, - "time": time.Now().Unix(), + "post_type": "notice", + "group_id": group.Code, + "notice_type": "notify", + "sub_type": "lucky_king", + "self_id": c.Uin, + "user_id": notify.Sender, + "sender_id": notify.Sender, + "target_id": notify.LuckyKing, + "time": time.Now().Unix(), + }) + case *client.MemberHonorChangedNotifyEvent: + log.Info(notify.Content()) + bot.dispatchEventMessage(MSG{ + "post_type": "notice", + "group_id": group.Code, + "notice_type": "notify", + "sub_type": "honor", + "self_id": c.Uin, + "user_id": notify.Uin, + "time": time.Now().Unix(), + "honor_type": func() string { + switch notify.Honor { + case client.Talkative: + return "talkative" + case client.Performer: + return "performer" + case client.Emotion: + return "emotion" + default: + return "ERROR" + } + }(), }) } } diff --git a/docs/cqhttp.md b/docs/cqhttp.md index 3ef1681..5614553 100644 --- a/docs/cqhttp.md +++ b/docs/cqhttp.md @@ -32,6 +32,22 @@ Type: `redbag` 示例: `[CQ:redbag,title=恭喜发财]` +### 戳一戳 + +> 注意:发送戳一戳消息无法撤回,返回的 `message id` 恒定为 `0` + +Type: `poke` + +范围: **发送(仅群聊)** + +参数: + +| 参数名 | 类型 | 说明 | +| ------ | ------ | ----------- | +| qq | int64 | 需要戳的成员 | + +示例: `[CQ:poke,qq=123456]` + ### 合并转发 Type: `forward` @@ -148,9 +164,9 @@ Type: `xml` 示例: `[CQ:xml,data=xxxx]` -####一些xml样例 +#### 一些xml样例 -####ps:重要:xml中的value部分,记得html实体化处理后,再打加入到cq码中 +#### ps:重要:xml中的value部分,记得html实体化处理后,再打加入到cq码中 #### qq音乐 @@ -386,9 +402,10 @@ Type: `cardimage` | ------------- | ------ | -------------- | -------------- | | `post_type` | string | `notice` | 上报类型 | | `notice_type` | string | `notify` | 消息类型 | -| `notify_type` | string | `poke` | 提示类型 | +| `group_id` | int64 | | 群号 | +| `sub_type` | string | `poke` | 提示类型 | | `user_id` | int64 | | 发送者id | -| `receiver_id` | int64 | | 被戳者id | +| `target_id` | int64 | | 被戳者id | #### 群红包运气王提示 @@ -400,6 +417,22 @@ Type: `cardimage` | ------------- | ------ | -------------- | -------------- | | `post_type` | string | `notice` | 上报类型 | | `notice_type` | string | `notify` | 消息类型 | -| `notify_type` | string | `lucky_king` | 提示类型 | +| `group_id` | int64 | | 群号 | +| `sub_type` | string | `lucky_king` | 提示类型 | | `user_id` | int64 | | 红包发送者id | -| `lucky_king_id` | int64 | | 运气王id | +| `target_id` | int64 | | 运气王id | + +#### 群成员荣誉变更提示 + +> 注意:此事件无法在平板和手表协议上触发 + +**上报数据** + +| 字段 | 类型 | 可能的值 | 说明 | +| ------------- | ------ | -------------- | -------------- | +| `post_type` | string | `notice` | 上报类型 | +| `notice_type` | string | `notify` | 消息类型 | +| `group_id` | int64 | | 群号 | +| `sub_type` | string | `honor` | 提示类型 | +| `user_id` | int64 | | 成员id | +| `honor_type` | string | `talkative:龙王` `performer:群聊之火` `emotion:快乐源泉` | 荣誉类型 | diff --git a/go.mod b/go.mod index 56ddcdf..8a9524b 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Mrs4s/go-cqhttp go 1.14 require ( - github.com/Mrs4s/MiraiGo v0.0.0-20200912123655-d92d61c5998e + github.com/Mrs4s/MiraiGo v0.0.0-20200921142226-9a449519db5c github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 // indirect github.com/gin-gonic/gin v1.6.3 github.com/go-playground/validator/v10 v10.3.0 // indirect diff --git a/go.sum b/go.sum index cb98193..37daeee 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-20200912123655-d92d61c5998e h1:wiaXXMPnYCfA+rX1wy9SNrDyW3O43wfun5dIYKKK3BI= -github.com/Mrs4s/MiraiGo v0.0.0-20200912123655-d92d61c5998e/go.mod h1:cwYPI2uq6nxNbx0nA6YuAKF1V5szSs6FPlGVLQvRUlo= +github.com/Mrs4s/MiraiGo v0.0.0-20200921142226-9a449519db5c h1:TpGiSjI8Pe0YLEQPAVkOV37OFLeJnQ6jxvcDSb/LESY= +github.com/Mrs4s/MiraiGo v0.0.0-20200921142226-9a449519db5c/go.mod h1:cwYPI2uq6nxNbx0nA6YuAKF1V5szSs6FPlGVLQvRUlo= github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= diff --git a/main.go b/main.go index 71344e7..75207f6 100644 --- a/main.go +++ b/main.go @@ -237,9 +237,16 @@ func main() { } }) cli.OnServerUpdated(func(bot *client.QQClient, e *client.ServerUpdatedEvent) { - log.Infof("收到服务器地址更新通知, 将在下一次重连时应用. 服务器地址: %v:%v 服务器位置: %v", e.Servers[0].Server, e.Servers[0].Port, e.Servers[0].Location) + log.Infof("收到服务器地址更新通知, 将在下一次重连时应用. 地址信息已储存到 servers.bin 文件") _ = ioutil.WriteFile("servers.bin", binary.NewWriterF(func(w *binary.Writer) { - w.WriteUInt16(uint16(len(e.Servers))) + w.WriteUInt16(func() (c uint16) { + for _, s := range e.Servers { + if !strings.Contains(s.Server, "com") { + c++ + } + } + return + }()) for _, s := range e.Servers { if !strings.Contains(s.Server, "com") { w.WriteString(s.Server) @@ -250,12 +257,25 @@ func main() { }) if global.PathExists("servers.bin") { if data, err := ioutil.ReadFile("servers.bin"); err == nil { - r := binary.NewReader(data) - r.ReadUInt16() - cli.CustomServer = &net.TCPAddr{ - IP: net.ParseIP(r.ReadString()), - Port: int(r.ReadUInt16()), - } + func() { + defer func() { + if pan := recover(); pan != nil { + log.Error("读取服务器地址时出现错误: %v", pan) + } + }() + r := binary.NewReader(data) + var addr []*net.TCPAddr + l := r.ReadUInt16() + for i := 0; i < int(l); i++ { + addr = append(addr, &net.TCPAddr{ + IP: net.ParseIP(r.ReadString()), + Port: int(r.ReadUInt16()), + }) + } + if len(addr) > 0 { + cli.SetCustomServer(addr) + } + }() } } rsp, err := cli.Login() diff --git a/server/http.go b/server/http.go index 9b10904..743c040 100644 --- a/server/http.go +++ b/server/http.go @@ -333,6 +333,11 @@ func (s *httpServer) GetVipInfo(c *gin.Context) { c.JSON(200, s.bot.CQGetVipInfo(uid)) } +func (s *httpServer) GetStrangerInfo(c *gin.Context) { + uid, _ := strconv.ParseInt(getParam(c, "user_id"), 10, 64) + c.JSON(200, s.bot.CQGetStrangerInfo(uid)) +} + func (s *httpServer) HandleQuickOperation(c *gin.Context) { if c.Request.Method != "POST" { c.AbortWithStatus(404) @@ -344,6 +349,11 @@ func (s *httpServer) HandleQuickOperation(c *gin.Context) { } } +func (s *httpServer) OcrImage(c *gin.Context) { + img := getParam(c, "image") + c.JSON(200, s.bot.CQOcrImage(img)) +} + func getParamOrDefault(c *gin.Context, k, def string) string { r := getParam(c, k) if r != "" { @@ -486,10 +496,16 @@ var httpApi = map[string]func(s *httpServer, c *gin.Context){ "_get_vip_info": func(s *httpServer, c *gin.Context) { s.GetVipInfo(c) }, + "get_stranger_info": func(s *httpServer, c *gin.Context) { + s.GetStrangerInfo(c) + }, "reload_event_filter": func(s *httpServer, c *gin.Context) { s.ReloadEventFilter(c) }, ".handle_quick_operation": func(s *httpServer, c *gin.Context) { s.HandleQuickOperation(c) }, + ".ocr_image": func(s *httpServer, c *gin.Context) { + s.OcrImage(c) + }, } diff --git a/server/websocket.go b/server/websocket.go index ecad98a..3a28559 100644 --- a/server/websocket.go +++ b/server/websocket.go @@ -486,6 +486,9 @@ var wsApi = map[string]func(*coolq.CQBot, gjson.Result) coolq.MSG{ "can_send_record": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG { return bot.CQCanSendRecord() }, + "get_stranger_info": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG { + return bot.CQGetStrangerInfo(p.Get("user_id").Int()) + }, "get_status": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG { return bot.CQGetStatus() }, @@ -498,6 +501,9 @@ var wsApi = map[string]func(*coolq.CQBot, gjson.Result) coolq.MSG{ "reload_event_filter": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG { return bot.CQReloadEventFilter() }, + ".ocr_image": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG { + return bot.CQOcrImage(p.Get("image").Str) + }, ".handle_quick_operation": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG { return bot.CQHandleQuickOperation(p.Get("context"), p.Get("operation")) },