From 45102614b8c899855f1553b1efd3af98e209654e Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 1 Mar 2021 21:48:50 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=9A=E9=A1=B9=E6=94=B9=E5=8A=A8=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D:=20-=20SendGroupForwardMessage=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E5=8F=91=E9=80=81=E5=A4=B1=E8=B4=A5Handler=20-=20?= =?UTF-8?q?=E7=A6=81=E8=A8=80=E9=80=BB=E8=BE=91=E4=BF=AE=E5=A4=8D,=20?= =?UTF-8?q?=E5=8F=AF=E4=BB=A5=E8=BF=9B=E8=A1=8C30(=E6=95=B4)=E5=A4=A9?= =?UTF-8?q?=E7=9A=84=E7=A6=81=E8=A8=80=20-=20=E6=B7=BB=E5=8A=A0=E6=97=A0?= =?UTF-8?q?=E6=B3=95=E6=92=A4=E5=9B=9E=E4=B8=B4=E6=97=B6=E4=BF=A1=E6=81=AF?= =?UTF-8?q?Handling=20-=20=E4=B8=B4=E6=97=B6=E4=BC=9A=E8=AF=9D=E5=8F=91?= =?UTF-8?q?=E9=80=81=E4=B8=8E=E6=8E=A5=E6=94=B6=E4=BF=A1=E6=81=AF=E5=85=A5?= =?UTF-8?q?=E5=BA=93=20(=E6=9A=82=E6=97=B6=E4=B8=8D=E8=83=BD=E6=92=A4?= =?UTF-8?q?=E5=9B=9E=E4=BF=A1=E6=81=AF,=20Fixes=20#495)=20=E6=9B=B4?= =?UTF-8?q?=E6=94=B9:=20-=20=E4=B8=B4=E6=97=B6=E4=BC=9A=E8=AF=9D=E4=B8=AD?= =?UTF-8?q?=E7=9A=84=E5=8F=98=E9=87=8F=E6=94=B9=E5=90=8D=20(code=20->=20gr?= =?UTF-8?q?oupId)=20-=20=E8=AD=A6=E5=91=8A=E4=BF=A1=E6=81=AF=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E7=A9=BA=E6=A0=BC=20(=E8=A7=81cqcode.go)=20-=20?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=96=87=E6=A1=A3:=20=E6=B7=BB=E5=8A=A0seq?= =?UTF-8?q?=E8=AF=B4=E6=98=8E,=20=E7=BC=A9=E7=9F=AD=E6=9F=90=E4=B8=AA?= =?UTF-8?q?=E7=BD=91=E5=9D=80,=20=E6=B7=BB=E5=8A=A0=E6=9F=90=E4=B8=AACQcod?= =?UTF-8?q?e=E5=AE=9A=E4=B9=89=E7=9A=84=E5=AF=BC=E5=90=91=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0:=20-=20=E4=B8=BA=E8=87=AA=E5=AE=9A=E4=B9=89reply?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0seq=E9=80=89=E9=A1=B9=E8=AE=A9=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=E6=9B=B4=E5=85=B7=E7=9C=9F=E5=AE=9E=E6=80=A7=20-=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=88=A0=E9=99=A4=E5=A5=BD=E5=8F=8B=E5=A4=84?= =?UTF-8?q?=E7=90=86=20(https://github.com/Mrs4s/MiraiGo/pull/129,=20Fixes?= =?UTF-8?q?=20#374)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- coolq/api.go | 15 ++++++++++++--- coolq/bot.go | 29 ++++++++++++++++++++++++++++- coolq/cqcode.go | 16 ++++++++++++---- coolq/event.go | 10 +++++++--- docs/cqhttp.md | 11 +++++++---- 5 files changed, 66 insertions(+), 15 deletions(-) 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 |