diff --git a/README.md b/README.md index fa642f6..56e41c3 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ - [x] 修改群名 - [x] 消息撤回事件 - [x] 解析/发送 回复消息 -- [x] 解析合并转发 +- [x] 解析/发送 合并转发 - [ ] 使用代理请求网络图片 #### 实现 diff --git a/coolq/api.go b/coolq/api.go index de43349..effc713 100644 --- a/coolq/api.go +++ b/coolq/api.go @@ -12,6 +12,7 @@ import ( "path" "runtime" "strconv" + "time" ) // https://cqhttp.cc/docs/4.15/#/API?id=get_login_info-%E8%8E%B7%E5%8F%96%E7%99%BB%E5%BD%95%E5%8F%B7%E4%BF%A1%E6%81%AF @@ -119,6 +120,76 @@ func (bot *CQBot) CQSendGroupMessage(groupId int64, m gjson.Result) MSG { return Failed(100) } +func (bot *CQBot) CQSendGroupForwardMessage(groupId int64, m gjson.Result) MSG { + if m.Type != gjson.JSON { + return Failed(100) + } + var nodes []*message.ForwardNode + ts := time.Now().Add(-time.Minute * 5) + hasCustom := func() bool { + for _, item := range m.Array() { + if item.Get("data.uin").Exists() { + return true + } + } + return false + }() + convert := func(e gjson.Result) { + if e.Get("type").Str != "node" { + return + } + ts.Add(time.Second) + if e.Get("data.id").Exists() { + i, _ := strconv.Atoi(e.Get("data.id").Str) + m := bot.GetGroupMessage(int32(i)) + if m != nil { + sender := m["sender"].(message.Sender) + nodes = append(nodes, &message.ForwardNode{ + SenderId: sender.Uin, + SenderName: (&sender).DisplayName(), + Time: func() int32 { + if hasCustom { + return int32(ts.Unix()) + } + return m["time"].(int32) + }(), + Message: bot.ConvertStringMessage(m["message"].(string), true), + }) + return + } + log.Warnf("警告: 引用消息 %v 错误或数据库未开启.", e.Get("data.id").Str) + return + } + uin, _ := strconv.ParseInt(e.Get("data.uin").Str, 10, 64) + name := e.Get("data.name").Str + content := bot.ConvertObjectMessage(e.Get("data.content"), true) + if uin != 0 && name != "" && len(content) > 0 { + nodes = append(nodes, &message.ForwardNode{ + SenderId: uin, + SenderName: name, + Time: int32(ts.Unix()), + Message: content, + }) + return + } + log.Warnf("警告: 非法 Forward node 将跳过") + } + if m.IsArray() { + for _, item := range m.Array() { + convert(item) + } + } else { + convert(m) + } + if len(nodes) > 0 { + gm := bot.Client.SendGroupForwardMessage(groupId, &message.ForwardMessage{Nodes: nodes}) + return OK(MSG{ + "message_id": ToGlobalId(groupId, gm.Id), + }) + } + return Failed(100) +} + // https://cqhttp.cc/docs/4.15/#/API?id=send_private_msg-%E5%8F%91%E9%80%81%E7%A7%81%E8%81%8A%E6%B6%88%E6%81%AF func (bot *CQBot) CQSendPrivateMessage(userId int64, m gjson.Result) MSG { if m.Type == gjson.String { diff --git a/coolq/cqcode.go b/coolq/cqcode.go index 19174e9..a3f6195 100644 --- a/coolq/cqcode.go +++ b/coolq/cqcode.go @@ -142,6 +142,9 @@ func (bot *CQBot) ConvertObjectMessage(m gjson.Result, group bool) (r []message. } r = append(r, elem) } + if m.Type == gjson.String { + return bot.ConvertStringMessage(m.Str, group) + } if m.IsArray() { for _, e := range m.Array() { convertElem(e) diff --git a/docs/cqhttp.md b/docs/cqhttp.md index 87bbb2e..a9f90ac 100644 --- a/docs/cqhttp.md +++ b/docs/cqhttp.md @@ -4,10 +4,122 @@ ## CQCode -| Code | 示例 | 说明 | -| ------- | -------------------- | ------------------------------------------------------------ | -| reply | [CQ:reply,id=123456] | 回复ID为 `123456`的信息. 发送时一条 `message` 仅能使用一次 | -| forward | [CQ:forward,id=abcd] | ID为abcd的转发消息, 暂时仅能接收. 可通过 `/get_forward_msg` API获取具体信息 | +### 回复 + +Type : `reply` + +范围: **发送/接收** + +参数: + +| 参数名 | 类型 | 说明 | +| ------ | ---- | ------------------------------------- | +| id | int | 回复时所引用的消息id, 必须为本群消息. | + +示例: `[CQ:reply,id=123456]` + + ### 合并转发 + +Type: `forward` + +范围: **接收** + +参数: + +| 参数名 | 类型 | 说明 | +| ------ | ------ | ------------------------------------------------------------ | +| id | string | 合并转发ID, 需要通过 `/get_forward_msg` API获取转发的具体内容 | + +示例: `[CQ:forward,id=xxxx]` + +### 合并转发消息节点 + +Type: `node` + +范围: **发送** + +参数: + +| 参数名 | 类型 | 说明 | 特殊说明 | +| ------- | ------- | -------------- | ------------------------------------------------------------ | +| id | int32 | 转发消息id | 直接引用他人的消息合并转发, 实际查看顺序为原消息发送顺序 **与下面的自定义消息二选一** | +| name | string | 发送者显示名字 | 用于自定义消息 (自定义消息并合并转发,实际查看顺序为自定义消息段顺序) | +| uin | int64 | 发送者QQ号 | 用于自定义消息 | +| content | message | 具体消息 | 用于自定义消息 **不支持转发套娃,不支持引用回复** | + +特殊说明: **需要使用单独的API `/send_group_forward_msg` 发送,并且由于消息段较为复杂,仅支持Array形式入参。 如果引用消息和自定义消息同时出现,实际查看顺序将取消息段顺序. 另外按 [CQHTTP](https://cqhttp.cc/docs/4.15/#/Message?id=格式) 文档说明, `data` 应全为字符串, 但由于需要接收`message` 类型的消息, 所以 *仅限此Type的content字段* 支持Array套娃** + +示例: + +直接引用消息合并转发: + +````json +[ + { + "type": "node", + "data": { + "id": "123" + } + }, + { + "type": "node", + "data": { + "id": "456" + } + } +] +```` + +自定义消息合并转发: + +````json +[ + { + "type": "node", + "data": { + "name": "消息发送者A", + "uin": "10086", + "content": [ + { + "type": "text", + "data": {"text": "测试消息1"} + } + ] + } + }, + { + "type": "node", + "data": { + "name": "消息发送者B", + "uin": "10087", + "content": "[CQ:image,file=xxxxx]测试消息2" + } + } +] +```` + +引用自定义混合合并转发: + +````json +[ + { + "type": "node", + "data": { + "name": "自定义发送者", + "uin": "10086", + "content": "我是自定义消息" + } + }, + { + "type": "node", + "data": { + "id": "123" + } + } +] +```` + + ## API diff --git a/server/http.go b/server/http.go index 68dee66..dc162cc 100644 --- a/server/http.go +++ b/server/http.go @@ -97,6 +97,9 @@ func (s *httpServer) Run(addr, authToken string, bot *coolq.CQBot) { s.engine.Any("/send_group_msg", s.SendGroupMessage) s.engine.Any("/send_group_msg_async", s.SendGroupMessage) + s.engine.Any("/send_group_forward_msg", s.SendGroupForwardMessage) + s.engine.Any("/send_group_forward_msg_async", s.SendGroupForwardMessage) + s.engine.Any("/delete_msg", s.DeleteMessage) s.engine.Any("/delete_msg_async", s.DeleteMessage) @@ -246,6 +249,12 @@ func (s *httpServer) SendGroupMessage(c *gin.Context) { c.JSON(200, s.bot.CQSendGroupMessage(gid, gjson.Result{Type: gjson.String, Str: msg})) } +func (s *httpServer) SendGroupForwardMessage(c *gin.Context) { + gid, _ := strconv.ParseInt(getParam(c, "group_id"), 10, 64) + msg := getParam(c, "messages") + c.JSON(200, s.bot.CQSendGroupForwardMessage(gid, gjson.Parse(msg))) +} + func (s *httpServer) GetImage(c *gin.Context) { file := getParam(c, "file") c.JSON(200, s.bot.CQGetImage(file)) diff --git a/server/websocket.go b/server/websocket.go index 899723e..13874ea 100644 --- a/server/websocket.go +++ b/server/websocket.go @@ -331,6 +331,9 @@ var wsApi = map[string]func(*coolq.CQBot, gjson.Result) coolq.MSG{ "send_group_msg": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG { return bot.CQSendGroupMessage(p.Get("group_id").Int(), p.Get("message")) }, + "send_group_forward_msg": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG { + return bot.CQSendGroupForwardMessage(p.Get("group_id").Int(), p.Get("messages")) + }, "send_private_msg": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG { return bot.CQSendPrivateMessage(p.Get("user_id").Int(), p.Get("message")) },