diff --git a/client/group_msg.go b/client/group_msg.go index 3a35f250..95e2e0d7 100644 --- a/client/group_msg.go +++ b/client/group_msg.go @@ -49,7 +49,7 @@ func (c *QQClient) SendGroupMessage(groupCode int64, m *message.SendingMessage, } } msgLen := message.EstimateLength(m.Elements) - if msgLen > 5000 || imgCount > 50 { + if msgLen > message.MaxMessageSize || imgCount > 50 { return nil } if !useFram && (msgLen > 100 || imgCount > 2) { diff --git a/client/private_msg.go b/client/private_msg.go index 22c7e3b1..ac118d66 100644 --- a/client/private_msg.go +++ b/client/private_msg.go @@ -26,7 +26,7 @@ func (c *QQClient) SendPrivateMessage(target int64, m *message.SendingMessage) * } } msgLen := message.EstimateLength(m.Elements) - if msgLen > 5000 || imgCount > 50 { + if msgLen > message.MaxMessageSize || imgCount > 50 { return nil } if frag && (msgLen > 300 || imgCount > 2) { diff --git a/message/message.go b/message/message.go index b0f06d58..c585c009 100644 --- a/message/message.go +++ b/message/message.go @@ -203,6 +203,9 @@ func (msg *SendingMessage) ToFragmented() [][]IMessageElement { return fragmented } +// 单条消息发送的大小限制(预估) +const MaxMessageSize = 5000 + func EstimateLength(elems []IMessageElement) int { sum := 0 for _, elem := range elems { @@ -624,3 +627,161 @@ func FaceNameById(id int) string { } return "未知表情" } + +// SplitLongMessage 将过长的消息分割为若干个适合发送的消息 +func SplitLongMessage(sendingMessage *SendingMessage) []*SendingMessage { + // 合并连续文本消息 + sendingMessage = mergeContinuousTextMessages(sendingMessage) + + // 分割过长元素 + sendingMessage = splitElements(sendingMessage) + + // 将元素分为多组,确保各组不超过单条消息的上限 + splitMessages := splitMessages(sendingMessage) + + return splitMessages +} + +// mergeContinuousTextMessages 预先将所有连续的文本消息合并为到一起,方便后续统一切割 +func mergeContinuousTextMessages(sendingMessage *SendingMessage) *SendingMessage { + // 检查下是否有连续的文本消息,若没有,则可以直接返回 + lastIsText := false + hasContinuousText := false + for _, message := range sendingMessage.Elements { + if message.Type() == Text { + if lastIsText { + // 有连续的文本消息,需要进行处理 + hasContinuousText = true + break + } + + // 遇到文本元素先存放起来,方便将连续的文本元素合并 + lastIsText = true + continue + } else { + lastIsText = false + } + } + if !hasContinuousText { + return sendingMessage + } + + // 存在连续的文本消息,需要进行合并处理 + textBuffer := strings.Builder{} + lastIsText = false + totalMessageCount := 0 + for _, message := range sendingMessage.Elements { + if msgVal, ok := message.(*TextElement); ok { + // 遇到文本元素先存放起来,方便将连续的文本元素合并 + textBuffer.WriteString(msgVal.Content) + lastIsText = true + continue + } + + // 如果之前的是文本元素(可能是多个合并起来的),则在这里将其实际放入消息中 + if lastIsText { + sendingMessage.Elements[totalMessageCount] = NewText(textBuffer.String()) + totalMessageCount += 1 + textBuffer.Reset() + } + lastIsText = false + + // 非文本元素则直接处理 + sendingMessage.Elements[totalMessageCount] = message + totalMessageCount += 1 + } + // 处理最后几个元素是文本的情况 + if textBuffer.Len() != 0 { + sendingMessage.Elements[totalMessageCount] = NewText(textBuffer.String()) + totalMessageCount += 1 + textBuffer.Reset() + } + sendingMessage.Elements = sendingMessage.Elements[:totalMessageCount] + + return sendingMessage +} + +// splitElements 将原有消息的各个元素先尝试处理,如过长的文本消息按需分割为多个元素 +func splitElements(sendingMessage *SendingMessage) *SendingMessage { + // 检查下是否存在需要文本消息,若不存在,则直接返回 + needSplit := false + for _, message := range sendingMessage.Elements { + if msgVal, ok := message.(*TextElement); ok { + if textNeedSplit(msgVal.Content) { + needSplit = true + break + } + } + } + if !needSplit { + return sendingMessage + } + + // 开始尝试切割 + messageParts := NewSendingMessage() + + for _, message := range sendingMessage.Elements { + switch msgVal := message.(type) { + case *TextElement: + messageParts.Elements = append(messageParts.Elements, splitPlainMessage(msgVal.Content)...) + default: + messageParts.Append(message) + } + } + + return messageParts +} + +// splitMessages 根据大小分为多个消息进行发送 +func splitMessages(sendingMessage *SendingMessage) []*SendingMessage { + var splitMessages []*SendingMessage + + messagePart := NewSendingMessage() + msgSize := 0 + for _, part := range sendingMessage.Elements { + estimateSize := EstimateLength([]IMessageElement{part}) + // 若当前分消息加上新的元素后大小会超限,且已经有元素(确保不会无限循环),则开始切分为新的一个元素 + if msgSize+estimateSize > MaxMessageSize && len(messagePart.Elements) > 0 { + splitMessages = append(splitMessages, messagePart) + + messagePart = NewSendingMessage() + msgSize = 0 + } + + // 加上新的元素 + messagePart.Append(part) + msgSize += estimateSize + } + // 将最后一个分片加上 + if len(messagePart.Elements) != 0 { + splitMessages = append(splitMessages, messagePart) + } + + return splitMessages +} + +func splitPlainMessage(content string) []IMessageElement { + if !textNeedSplit(content) { + return []IMessageElement{NewText(content)} + } + + splittedMessage := make([]IMessageElement, 0, (len(content)+MaxMessageSize-1)/MaxMessageSize) + + last := 0 + for runeIndex, runeValue := range content { + // 如果加上新的这个字符后,会超出大小,则从这个字符前分一次片 + if runeIndex+len(string(runeValue))-last > MaxMessageSize { + splittedMessage = append(splittedMessage, NewText(content[last:runeIndex])) + last = runeIndex + } + } + if last != len(content) { + splittedMessage = append(splittedMessage, NewText(content[last:len(content)])) + } + + return splittedMessage +} + +func textNeedSplit(content string) bool { + return len(content) > MaxMessageSize +} diff --git a/message/message_test.go b/message/message_test.go new file mode 100644 index 00000000..89093b5a --- /dev/null +++ b/message/message_test.go @@ -0,0 +1,36 @@ +package message + +import ( + "strings" + "testing" +) + +func Test_mergeContinuousTextMessages(t *testing.T) { + msg := NewSendingMessage() + msg.Append(NewText("短片段一")) + msg.Append(NewText(strings.Repeat("长一", 800))) // 6*800 + msg.Append(NewText("短片段二")) + msg.Append(NewText(strings.Repeat("长二", 1200))) // 6*1200 + msg.Append(NewText("短片段三")) + + // 总长度为 12036 + totalSize := EstimateLength(msg.Elements) + expectedPart := (totalSize + MaxMessageSize - 1) / MaxMessageSize + + messages := SplitLongMessage(msg) + // 应分为 3段 + if len(messages) != expectedPart { + t.Errorf("should split into %v part", expectedPart) + } + partsSize := 0 + for idx, message := range messages { + partSize := EstimateLength(message.Elements) + if partSize > MaxMessageSize { + t.Errorf("part %v size=%v is more than %v", idx, partSize, MaxMessageSize) + } + partsSize += partSize + } + if partsSize != totalSize { + t.Errorf("parts size sum=%v is not equal to total size=%v", partsSize, totalSize) + } +}