diff --git a/coolq/api.go b/coolq/api.go index c7fdb2a..407dc23 100644 --- a/coolq/api.go +++ b/coolq/api.go @@ -423,9 +423,13 @@ func (bot *CQBot) CQSendGroupForwardMessage(groupID int64, m gjson.Result) MSG { sendNodes = convert(m) } if len(sendNodes) > 0 { - gm := bot.Client.SendGroupForwardMessage(groupID, &message.ForwardMessage{Nodes: sendNodes}) + ret := bot.Client.SendGroupForwardMessage(groupID, &message.ForwardMessage{Nodes: sendNodes}) + if ret == nil || ret.Id == -1 { + log.Warnf("合并转发(群)消息发送失败: 账号可能被风控.") + return Failed(100, "SEND_MSG_API_ERROR", "请参考输出") + } return OK(MSG{ - "message_id": bot.InsertGroupMessage(gm), + "message_id": bot.InsertGroupMessage(ret), }) } return Failed(100) @@ -547,7 +551,7 @@ func (bot *CQBot) CQSetGroupBan(groupID, userID int64, duration uint32) MSG { if m := g.FindMember(userID); m != nil { err := m.Mute(duration) if err != nil { - if duration >= 2592000 { + if duration > 2592000 { return Failed(100, "DURATION_IS_NOT_IN_RANGE", "非法的禁言时长") } return Failed(100, "NOT_MANAGEABLE", "机器人权限不足") @@ -663,6 +667,11 @@ func (bot *CQBot) CQDeleteMessage(messageID int32) MSG { return Failed(100, "MESSAGE_NOT_FOUND", "消息不存在") } if _, ok := msg["group"]; ok { + if msg["internal-id"] == nil { + // TODO 撤回临时对话消息 + log.Warnf("撤回 %v 失败: 无法撤回临时对话消息", messageID) + return Failed(100, "CANNOT_RECALL_TEMP_MSG", "无法撤回临时对话消息") + } if err := bot.Client.RecallGroupMessage(msg["group"].(int64), msg["message-id"].(int32), msg["internal-id"].(int32)); err != nil { log.Warnf("撤回 %v 失败: %v", messageID, err) return Failed(100, "RECALL_API_ERROR", err.Error()) diff --git a/coolq/bot.go b/coolq/bot.go index f0f6f2e..fef5bca 100644 --- a/coolq/bot.go +++ b/coolq/bot.go @@ -295,7 +295,7 @@ func (bot *CQBot) SendPrivateMessage(target int64, m *message.SendingMessage) in } else if code, ok := bot.tempMsgCache.Load(target); ok { // 临时会话 msg := bot.Client.SendTempMessage(code.(int64), target, m) if msg != nil { - id = msg.Id + id = bot.InsertTempMessage(target, msg) } } else if _, ok := bot.oneWayMsgCache.Load(target); ok { // 单向好友 msg := bot.Client.SendPrivateMessage(target, m) @@ -360,6 +360,33 @@ func (bot *CQBot) InsertPrivateMessage(m *message.PrivateMessage) int32 { return id } +// InsertTempMessage 临时消息入数据库 +func (bot *CQBot) InsertTempMessage(target int64, m *message.TempMessage) int32 { + val := MSG{ + "message-id": m.Id, + // FIXME(InsertTempMessage) InternalId missing + "group": m.GroupCode, + "group-name": m.GroupName, + "target": target, + "sender": m.Sender, + "time": time.Now().Unix(), + "message": ToStringMessage(m.Elements, m.Sender.Uin, true), + } + id := toGlobalID(m.Sender.Uin, m.Id) + if bot.db != nil { + buf := new(bytes.Buffer) + if err := gob.NewEncoder(buf).Encode(val); err != nil { + log.Warnf("记录聊天数据时出现错误: %v", err) + return -1 + } + if err := bot.db.Put(binary.ToBytes(id), binary.GZipCompress(buf.Bytes()), nil); err != nil { + log.Warnf("记录聊天数据时出现错误: %v", err) + return -1 + } + } + return id +} + // toGlobalID 构建`code`-`msgID`的字符串并返回其CRC32 Checksum的值 func toGlobalID(code int64, msgID int32) int32 { return int32(crc32.ChecksumIEEE([]byte(fmt.Sprintf("%d-%d", code, msgID)))) diff --git a/coolq/cqcode.go b/coolq/cqcode.go index bad9b2f..24d517e 100644 --- a/coolq/cqcode.go +++ b/coolq/cqcode.go @@ -390,16 +390,20 @@ func (bot *CQBot) ConvertStringMessage(s string, isGroup bool) (r []message.IMes } else if customText != "" { sender, err := strconv.ParseInt(d["qq"], 10, 64) if err != nil { - log.Warnf("警告:自定义 Reply 元素中必须包含Uin") + log.Warnf("警告:自定义 Reply 元素中必须包含 Uin") return } msgTime, err := strconv.ParseInt(d["time"], 10, 64) if err != nil { msgTime = time.Now().Unix() } + messageSeq, err := strconv.ParseInt(d["seq"], 10, 64) + if err != nil { + messageSeq = 0 + } r = append([]message.IMessageElement{ &message.ReplyElement{ - ReplySeq: int32(0), + ReplySeq: int32(messageSeq), Sender: sender, Time: int32(msgTime), Elements: bot.ConvertStringMessage(customText, isGroup), @@ -530,16 +534,20 @@ func (bot *CQBot) ConvertObjectMessage(m gjson.Result, isGroup bool) (r []messag } else if customText != "" { sender, err := strconv.ParseInt(e.Get("data").Get("qq").String(), 10, 64) if err != nil { - log.Warnf("警告:自定义 Reply 元素中必须包含Uin") + log.Warnf("警告:自定义 Reply 元素中必须包含 Uin") return } msgTime, err := strconv.ParseInt(e.Get("data").Get("time").String(), 10, 64) if err != nil { msgTime = time.Now().Unix() } + messageSeq, err := strconv.ParseInt(e.Get("data").Get("seq").String(), 10, 64) + if err != nil { + messageSeq = 0 + } r = append([]message.IMessageElement{ &message.ReplyElement{ - ReplySeq: int32(0), + ReplySeq: int32(messageSeq), Sender: sender, Time: int32(msgTime), Elements: bot.ConvertStringMessage(customText, isGroup), diff --git a/coolq/event.go b/coolq/event.go index 40f43f2..7c4d396 100644 --- a/coolq/event.go +++ b/coolq/event.go @@ -103,16 +103,20 @@ func (bot *CQBot) groupMessageEvent(c *client.QQClient, m *message.GroupMessage) func (bot *CQBot) tempMessageEvent(c *client.QQClient, m *message.TempMessage) { bot.checkMedia(m.Elements) - cqm := ToStringMessage(m.Elements, 0, true) + cqm := ToStringMessage(m.Elements, m.Sender.Uin, true) bot.tempMsgCache.Store(m.Sender.Uin, m.GroupCode) + id := m.Id + if bot.db != nil { + id = bot.InsertTempMessage(m.Sender.Uin, m) + } log.Infof("收到来自群 %v(%v) 内 %v(%v) 的临时会话消息: %v", m.GroupName, m.GroupCode, m.Sender.DisplayName(), m.Sender.Uin, cqm) tm := MSG{ "post_type": "message", "message_type": "private", "sub_type": "group", - "message_id": m.Id, + "message_id": id, "user_id": m.Sender.Uin, - "message": ToFormattedMessage(m.Elements, 0, false), + "message": ToFormattedMessage(m.Elements, m.Sender.Uin, false), "raw_message": cqm, "font": 0, "self_id": c.Uin, diff --git a/docs/cqhttp.md b/docs/cqhttp.md index 5e08239..c67d0a0 100644 --- a/docs/cqhttp.md +++ b/docs/cqhttp.md @@ -111,12 +111,13 @@ Type : `reply` | `text` | string | 自定义回复的信息 | | `qq` | int64 | 自定义回复时的自定义QQ, 如果使用自定义信息必须指定. | | `time` | int64 | 可选. 自定义回复时的时间, 格式为Unix时间 | +| `seq` | int64 | 起始消息序号, 可通过 `get_msg` 获得 | 示例: `[CQ:reply,id=123456]` \ -自定义回复示例: `[CQ:reply,text=Hello World,qq=10086,time=3376656000]` +自定义回复示例: `[CQ:reply,text=Hello World,qq=10086,time=3376656000,seq=5123]` ### 音乐分享 @@ -263,8 +264,9 @@ Type: `node` | `name` | string | 发送者显示名字 | 用于自定义消息 (自定义消息并合并转发,实际查看顺序为自定义消息段顺序) | | `uin` | int64 | 发送者QQ号 | 用于自定义消息 | | `content` | message | 具体消息 | 用于自定义消息 | +| `seq` | message | 具体消息 | 用于自定义消息 | -特殊说明: **需要使用单独的API `/send_group_forward_msg` 发送,并且由于消息段较为复杂,仅支持Array形式入参。 如果引用消息和自定义消息同时出现,实际查看顺序将取消息段顺序. 另外按 [CQHTTP](https://cqhttp.cc/docs/4.15/#/Message?id=格式) 文档说明, `data` 应全为字符串, 但由于需要接收`message` 类型的消息, 所以 *仅限此Type的content字段* 支持Array套娃** +特殊说明: **需要使用单独的API `/send_group_forward_msg` 发送,并且由于消息段较为复杂,仅支持Array形式入参。 如果引用消息和自定义消息同时出现,实际查看顺序将取消息段顺序. 另外按 [CQHTTP](https://git.io/JtxtN) 文档说明, `data` 应全为字符串, 但由于需要接收`message` 类型的消息, 所以 *仅限此Type的content字段* 支持Array套娃** 示例: @@ -325,6 +327,7 @@ Type: `node` "name": "自定义发送者", "uin": "10086", "content": "我是自定义消息", + "seq": "5123", "time": "3376656000" } }, @@ -603,10 +606,10 @@ Type: `tts` | 字段 | 类型 | 说明 | | ---------- | -------------- | ---------------------------- | | `group_id` | int64 | 群号 | -| `messages` | forward node[] | 自定义转发消息, 具体看CQCode | +| `messages` | forward node[] | 自定义转发消息, 具体看 [CQCode](https://github.com/Mrs4s/go-cqhttp/blob/master/docs/cqhttp.md#%E5%90%88%E5%B9%B6%E8%BD%AC%E5%8F%91%E6%B6%88%E6%81%AF%E8%8A%82%E7%82%B9) | 响应数据 - + | 字段 | 类型 | 说明 | | ------------ | ------ | ------ | | `message_id` | string | 消息id |