1
0
mirror of https://github.com/Mrs4s/go-cqhttp.git synced 2025-05-07 12:43:31 +08:00

feat: multi database support - leveldb.

This commit is contained in:
Mrs4s 2021-09-22 14:43:48 +08:00
parent efdd6bd16a
commit 66266f0d5e
No known key found for this signature in database
GPG Key ID: 3186E98FA19CE3A7
7 changed files with 577 additions and 187 deletions

View File

@ -5,6 +5,7 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"github.com/Mrs4s/go-cqhttp/db"
"math"
"os"
"path"
@ -474,20 +475,19 @@ func (bot *CQBot) CQSendGroupForwardMessage(groupID int64, m gjson.Result) globa
ts.Add(time.Second)
if e.Get("data.id").Exists() {
i := e.Get("data.id").Int()
m := bot.GetMessage(int32(i))
m, _ := bot.db.GetGroupMessageByGlobalID(int32(i))
if m != nil {
sender := m["sender"].(message.Sender)
return &message.ForwardNode{
SenderId: sender.Uin,
SenderName: (&sender).DisplayName(),
SenderId: m.Attribute.SenderUin,
SenderName: m.Attribute.SenderName,
Time: func() int32 {
msgTime := m["time"].(int32)
msgTime := m.Attribute.Timestamp
if hasCustom && msgTime == 0 {
return int32(ts.Unix())
}
return msgTime
return int32(msgTime)
}(),
Message: resolveElement(bot.ConvertStringMessage(m["message"].(string), true)),
Message: resolveElement(bot.ConvertContentMessage(m.Content, true)),
}
}
log.Warnf("警告: 引用消息 %v 错误或数据库未开启.", e.Get("data.id").Str)
@ -791,29 +791,28 @@ func (bot *CQBot) CQProcessGroupRequest(flag, subType, reason string, approve bo
//
// https:// git.io/Jtz1y
func (bot *CQBot) CQDeleteMessage(messageID int32) global.MSG {
msg := bot.GetMessage(messageID)
if msg == nil {
msg, err := bot.db.GetMessageByGlobalID(messageID)
if err != nil {
log.Warnf("撤回消息时出现错误: %v", err)
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 {
switch o := msg.(type) {
case *db.StoredGroupMessage:
if err = bot.Client.RecallGroupMessage(o.GroupCode, o.Attribute.MessageSeq, o.Attribute.InternalID); err != nil {
log.Warnf("撤回 %v 失败: %v", messageID, err)
return Failed(100, "RECALL_API_ERROR", err.Error())
}
} else {
if msg["sender"].(message.Sender).Uin != bot.Client.Uin {
case *db.StoredPrivateMessage:
if o.Attribute.SenderUin != bot.Client.Uin {
log.Warnf("撤回 %v 失败: 好友会话无法撤回对方消息.", messageID)
return Failed(100, "CANNOT_RECALL_FRIEND_MSG", "无法撤回对方消息")
}
if err := bot.Client.RecallPrivateMessage(msg["target"].(int64), int64(msg["time"].(int32)), msg["message-id"].(int32), msg["internal-id"].(int32)); err != nil {
if err = bot.Client.RecallPrivateMessage(o.TargetUin, o.Attribute.Timestamp, o.Attribute.MessageSeq, o.Attribute.InternalID); err != nil {
log.Warnf("撤回 %v 失败: %v", messageID, err)
return Failed(100, "RECALL_API_ERROR", err.Error())
}
default:
return Failed(100, "UNKNOWN_ERROR")
}
return OK(nil)
}
@ -1115,38 +1114,32 @@ func (bot *CQBot) CQGetForwardMessage(resID string) global.MSG {
//
// https://git.io/Jtz1b
func (bot *CQBot) CQGetMessage(messageID int32) global.MSG {
msg := bot.GetMessage(messageID)
if msg == nil {
msg, err := bot.db.GetMessageByGlobalID(messageID)
if err != nil {
log.Warnf("获取消息时出现错误: %v", err)
return Failed(100, "MSG_NOT_FOUND", "消息不存在")
}
sender := msg["sender"].(message.Sender)
gid, isGroup := msg["group"]
raw := msg["message"].(string)
return OK(global.MSG{
"message_id": messageID,
"real_id": msg["message-id"],
"message_seq": msg["message-id"],
"group": isGroup,
"group_id": gid,
"message_type": func() string {
if isGroup {
return "group"
}
return "private"
}(),
m := global.MSG{
"message_id": msg.GetGlobalID(),
"message_id_v2": msg.GetID(),
"message_type": msg.GetType(),
"real_id": msg.GetAttribute().MessageSeq,
"message_seq": msg.GetAttribute().MessageSeq,
"group": msg.GetType() == "group",
"sender": global.MSG{
"user_id": sender.Uin,
"nickname": sender.Nickname,
"user_id": msg.GetAttribute().SenderUin,
"nickname": msg.GetAttribute().SenderName,
},
"time": msg["time"],
"raw_message": raw,
"message": ToFormattedMessage(bot.ConvertStringMessage(raw, isGroup), func() int64 {
if isGroup {
return gid.(int64)
}
return 0
}(), false),
})
"time": msg.GetAttribute().Timestamp,
}
switch o := msg.(type) {
case *db.StoredGroupMessage:
m["group_id"] = o.GroupCode
m["message"] = ToFormattedMessage(bot.ConvertContentMessage(o.Content, true), o.GroupCode, false)
case *db.StoredPrivateMessage:
m["message"] = ToFormattedMessage(bot.ConvertContentMessage(o.Content, false), 0, false)
}
return OK(m)
}
// CQGetGroupSystemMessages 扩展API-获取群文件系统消息
@ -1308,18 +1301,13 @@ func (bot *CQBot) CQGetStatus() global.MSG {
//
// https://docs.go-cqhttp.org/api/#%E8%AE%BE%E7%BD%AE%E7%B2%BE%E5%8D%8E%E6%B6%88%E6%81%AF
func (bot *CQBot) CQSetEssenceMessage(messageID int32) global.MSG {
msg := bot.GetMessage(messageID)
if msg == nil {
msg, err := bot.db.GetGroupMessageByGlobalID(messageID)
if err != nil {
return Failed(100, "MESSAGE_NOT_FOUND", "消息不存在")
}
if _, ok := msg["group"]; ok {
if err := bot.Client.SetEssenceMessage(msg["group"].(int64), msg["message-id"].(int32), msg["internal-id"].(int32)); err != nil {
log.Warnf("设置精华消息 %v 失败: %v", messageID, err)
return Failed(100, "SET_ESSENCE_MSG_ERROR", err.Error())
}
} else {
log.Warnf("设置精华消息 %v 失败: 非群聊", messageID)
return Failed(100, "SET_ESSENCE_MSG_ERROR", "非群聊")
if err := bot.Client.SetEssenceMessage(msg.GroupCode, msg.Attribute.MessageSeq, msg.Attribute.InternalID); err != nil {
log.Warnf("设置精华消息 %v 失败: %v", messageID, err)
return Failed(100, "SET_ESSENCE_MSG_ERROR", err.Error())
}
return OK(nil)
}
@ -1328,18 +1316,13 @@ func (bot *CQBot) CQSetEssenceMessage(messageID int32) global.MSG {
//
// https://docs.go-cqhttp.org/api/#%E7%A7%BB%E5%87%BA%E7%B2%BE%E5%8D%8E%E6%B6%88%E6%81%AF
func (bot *CQBot) CQDeleteEssenceMessage(messageID int32) global.MSG {
msg := bot.GetMessage(messageID)
if msg == nil {
msg, err := bot.db.GetGroupMessageByGlobalID(messageID)
if err != nil {
return Failed(100, "MESSAGE_NOT_FOUND", "消息不存在")
}
if _, ok := msg["group"]; ok {
if err := bot.Client.DeleteEssenceMessage(msg["group"].(int64), msg["message-id"].(int32), msg["internal-id"].(int32)); err != nil {
log.Warnf("移出精华消息 %v 失败: %v", messageID, err)
return Failed(100, "DEL_ESSENCE_MSG_ERROR", err.Error())
}
} else {
log.Warnf("移出精华消息 %v 失败: 非群聊", messageID)
return Failed(100, "DEL_ESSENCE_MSG_ERROR", "非群聊")
if err := bot.Client.DeleteEssenceMessage(msg.GroupCode, msg.Attribute.MessageSeq, msg.Attribute.InternalID); err != nil {
log.Warnf("删除精华消息 %v 失败: %v", messageID, err)
return Failed(100, "SET_ESSENCE_MSG_ERROR", err.Error())
}
return OK(nil)
}
@ -1366,7 +1349,7 @@ func (bot *CQBot) CQGetEssenceMessageList(groupCode int64) global.MSG {
"sender_id": m.SenderUin,
"operator_id": m.AddDigestUin,
}
msg["message_id"] = toGlobalID(groupCode, int32(m.MessageID))
msg["message_id"] = db.ToGlobalID(groupCode, int32(m.MessageID))
list = append(list, msg)
}
return OK(list)
@ -1452,18 +1435,17 @@ func (bot *CQBot) CQSetModelShow(modelName string, modelShow string) global.MSG
// CQMarkMessageAsRead 标记消息已读
func (bot *CQBot) CQMarkMessageAsRead(msgID int32) global.MSG {
m := bot.GetMessage(msgID)
if m == nil {
m, err := bot.db.GetMessageByGlobalID(msgID)
if err != nil {
return Failed(100, "MSG_NOT_FOUND", "消息不存在")
}
if _, ok := m["group"]; ok {
bot.Client.MarkGroupMessageReaded(m["group"].(int64), int64(m["message-id"].(int32)))
switch o := m.(type) {
case *db.StoredGroupMessage:
bot.Client.MarkGroupMessageReaded(o.GroupCode, int64(o.Attribute.MessageSeq))
return OK(nil)
case *db.StoredPrivateMessage:
bot.Client.MarkPrivateMessageReaded(o.SessionUin, o.Attribute.Timestamp)
}
if _, ok := m["from-group"]; ok {
return Failed(100, "MSG_TYPE_ERROR", "不支持标记临时会话")
}
bot.Client.MarkPrivateMessageReaded(m["sender"].(message.Sender).Uin, int64(m["time"].(int32)))
return OK(nil)
}

View File

@ -2,11 +2,10 @@ package coolq
import (
"bytes"
"encoding/gob"
"encoding/hex"
"encoding/json"
"fmt"
"hash/crc32"
"github.com/Mrs4s/go-cqhttp/db"
"io"
"os"
"path"
@ -22,8 +21,6 @@ import (
"github.com/Mrs4s/MiraiGo/utils"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/opt"
"github.com/Mrs4s/go-cqhttp/global"
"github.com/Mrs4s/go-cqhttp/global/config"
@ -36,7 +33,7 @@ type CQBot struct {
lock sync.RWMutex
events []func(*Event)
db *leveldb.DB
db db.IDatabase
friendReqCache sync.Map
tempSessionCache sync.Map
}
@ -111,15 +108,11 @@ func NewQQBot(cli *client.QQClient, conf *config.Config) *CQBot {
enableLevelDB = lconf.Enable
}
if enableLevelDB {
p := path.Join("data", "leveldb")
db, err := leveldb.OpenFile(p, &opt.Options{
WriteBuffer: 128 * opt.KiB,
})
if err != nil {
log.Fatalf("打开数据库失败, 如果频繁遇到此问题请清理 data/leveldb 文件夹或关闭数据库功能。")
level := db.UseLevelDB()
if err := level.Open(); err != nil {
log.Fatalf("打开数据库失败: %v", err)
}
bot.db = db
gob.Register(message.Sender{})
bot.db = level
log.Info("信息数据库初始化完成.")
} else {
log.Warn("警告: 信息数据库已关闭,将无法使用 [回复/撤回] 等功能。")
@ -182,22 +175,6 @@ func (bot *CQBot) OnEventPush(f func(e *Event)) {
bot.lock.Unlock()
}
// GetMessage 获取给定消息id对应的消息
func (bot *CQBot) GetMessage(mid int32) global.MSG {
if bot.db != nil {
m := global.MSG{}
data, err := bot.db.Get(binary.ToBytes(mid), nil)
if err == nil {
err = gob.NewDecoder(bytes.NewReader(data)).Decode(&m)
if err == nil {
return m
}
}
log.Warnf("获取信息时出现错误: %v id: %v", err, mid)
}
return nil
}
// UploadLocalImageAsGroup 上传本地图片至群聊
func (bot *CQBot) UploadLocalImageAsGroup(groupCode int64, img *LocalImageElement) (i *message.GroupImageElement, err error) {
if img.File != "" {
@ -367,7 +344,7 @@ func (bot *CQBot) SendPrivateMessage(target int64, groupID int64, m *message.Sen
if session == nil && groupID != 0 {
msg := bot.Client.SendGroupTempMessage(groupID, target, m)
if msg != nil {
id = bot.InsertTempMessage(target, msg)
// id = bot.InsertTempMessage(target, msg)
}
break
}
@ -377,7 +354,7 @@ func (bot *CQBot) SendPrivateMessage(target int64, groupID int64, m *message.Sen
break
}
if msg != nil {
id = bot.InsertTempMessage(target, msg)
// id = bot.InsertTempMessage(target, msg)
}
}
case unidirectionalFriendExists(): // 单向好友
@ -397,57 +374,91 @@ func (bot *CQBot) SendPrivateMessage(target int64, groupID int64, m *message.Sen
// InsertGroupMessage 群聊消息入数据库
func (bot *CQBot) InsertGroupMessage(m *message.GroupMessage) int32 {
val := global.MSG{
"message-id": m.Id,
"internal-id": m.InternalId,
"group": m.GroupCode,
"group-name": m.GroupName,
"sender": m.Sender,
"time": m.Time,
"message": ToStringMessage(m.Elements, m.GroupCode, true),
t := &message.SendingMessage{Elements: m.Elements}
replyElem := t.FirstOrNil(func(e message.IMessageElement) bool {
_, ok := e.(*message.ReplyElement)
return ok
})
msg := &db.StoredGroupMessage{
ID: encodeMessageId(m.GroupCode, m.Id),
GlobalID: db.ToGlobalID(m.GroupCode, m.Id),
SubType: "normal",
Attribute: &db.StoredMessageAttribute{
MessageSeq: m.Id,
InternalID: m.InternalId,
SenderUin: m.Sender.Uin,
SenderName: m.Sender.DisplayName(),
Timestamp: int64(m.Time),
},
GroupCode: m.GroupCode,
AnonymousID: func() string {
if m.Sender.IsAnonymous() {
return m.Sender.AnonymousInfo.AnonymousId
}
return ""
}(),
Content: ToMessageContent(m.Elements),
}
id := toGlobalID(m.GroupCode, m.Id)
if bot.db != nil {
buf := global.NewBuffer()
defer global.PutBuffer(buf)
if err := gob.NewEncoder(buf).Encode(val); err != nil {
log.Warnf("记录聊天数据时出现错误: %v", err)
return -1
}
if err := bot.db.Put(binary.ToBytes(id), buf.Bytes(), nil); err != nil {
log.Warnf("记录聊天数据时出现错误: %v", err)
return -1
if replyElem != nil {
reply := replyElem.(*message.ReplyElement)
msg.SubType = "quote"
msg.QuotedInfo = &db.QuotedInfo{
PrevID: encodeMessageId(m.GroupCode, reply.ReplySeq),
PrevGlobalID: db.ToGlobalID(m.GroupCode, reply.ReplySeq),
QuotedContent: ToMessageContent(reply.Elements),
}
}
return id
if err := bot.db.InsertGroupMessage(msg); err != nil {
log.Warnf("记录聊天数据时出现错误: %v", err)
return -1
}
return msg.GlobalID
}
// InsertPrivateMessage 私聊消息入数据库
func (bot *CQBot) InsertPrivateMessage(m *message.PrivateMessage) int32 {
val := global.MSG{
"message-id": m.Id,
"internal-id": m.InternalId,
"target": m.Target,
"sender": m.Sender,
"time": m.Time,
"message": ToStringMessage(m.Elements, 0, true),
t := &message.SendingMessage{Elements: m.Elements}
replyElem := t.FirstOrNil(func(e message.IMessageElement) bool {
_, ok := e.(*message.ReplyElement)
return ok
})
msg := &db.StoredPrivateMessage{
ID: encodeMessageId(m.Sender.Uin, m.Id),
GlobalID: db.ToGlobalID(m.Sender.Uin, m.Id),
SubType: "normal",
Attribute: &db.StoredMessageAttribute{
MessageSeq: m.Id,
InternalID: m.InternalId,
SenderUin: m.Sender.Uin,
SenderName: m.Sender.DisplayName(),
Timestamp: int64(m.Time),
},
SessionUin: func() int64 {
if m.Sender.Uin == m.Self {
return m.Target
}
return m.Sender.Uin
}(),
TargetUin: m.Target,
Content: ToMessageContent(m.Elements),
}
id := toGlobalID(m.Sender.Uin, m.Id)
if bot.db != nil {
buf := global.NewBuffer()
defer global.PutBuffer(buf)
if err := gob.NewEncoder(buf).Encode(val); err != nil {
log.Warnf("记录聊天数据时出现错误: %v", err)
return -1
}
if err := bot.db.Put(binary.ToBytes(id), buf.Bytes(), nil); err != nil {
log.Warnf("记录聊天数据时出现错误: %v", err)
return -1
if replyElem != nil {
reply := replyElem.(*message.ReplyElement)
msg.SubType = "quote"
msg.QuotedInfo = &db.QuotedInfo{
PrevID: encodeMessageId(reply.Sender, reply.ReplySeq),
PrevGlobalID: db.ToGlobalID(reply.Sender, reply.ReplySeq),
QuotedContent: ToMessageContent(m.Elements),
}
}
return id
if err := bot.db.InsertPrivateMessage(msg); err != nil {
log.Warnf("记录聊天数据时出现错误: %v", err)
return -1
}
return msg.GlobalID
}
/*
// InsertTempMessage 临时消息入数据库
func (bot *CQBot) InsertTempMessage(target int64, m *message.TempMessage) int32 {
val := global.MSG{
@ -460,7 +471,7 @@ func (bot *CQBot) InsertTempMessage(target int64, m *message.TempMessage) int32
"time": int32(time.Now().Unix()),
"message": ToStringMessage(m.Elements, 0, true),
}
id := toGlobalID(m.Sender.Uin, m.Id)
id := db.ToGlobalID(m.Sender.Uin, m.Id)
if bot.db != nil {
buf := global.NewBuffer()
defer global.PutBuffer(buf)
@ -475,17 +486,11 @@ func (bot *CQBot) InsertTempMessage(target int64, m *message.TempMessage) int32
}
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))))
}
*/
// Release 释放Bot实例
func (bot *CQBot) Release() {
if bot.db != nil {
_ = bot.db.Close()
}
}
func (bot *CQBot) dispatchEventMessage(m global.MSG) {
@ -636,3 +641,11 @@ func IsLawfulImage(r io.ReadSeeker) (bool, string) {
}
return false, t.String()
}
// encodeMessageId 临时先这样, 暂时用不上
func encodeMessageId(target int64, seq int32) string {
return hex.EncodeToString(binary.NewWriterF(func(w *binary.Writer) {
w.WriteUInt64(uint64(target))
w.WriteUInt32(uint32(seq))
}))
}

View File

@ -7,6 +7,7 @@ import (
xml2 "encoding/xml"
"errors"
"fmt"
"github.com/Mrs4s/go-cqhttp/db"
"io"
"math/rand"
"net/url"
@ -141,7 +142,7 @@ func ToArrayMessage(e []message.IMessageElement, groupID int64) (r []global.MSG)
r = append(r, global.MSG{
"type": "reply",
"data": map[string]string{
"id": strconv.FormatInt(int64(toGlobalID(rid, replyElem.ReplySeq)), 10),
"id": strconv.FormatInt(int64(db.ToGlobalID(rid, replyElem.ReplySeq)), 10),
"seq": strconv.FormatInt(int64(replyElem.ReplySeq), 10),
"qq": strconv.FormatInt(replyElem.Sender, 10),
"time": strconv.FormatInt(int64(replyElem.Time), 10),
@ -151,7 +152,7 @@ func ToArrayMessage(e []message.IMessageElement, groupID int64) (r []global.MSG)
} else {
r = append(r, global.MSG{
"type": "reply",
"data": map[string]string{"id": strconv.FormatInt(int64(toGlobalID(rid, replyElem.ReplySeq)), 10)},
"data": map[string]string{"id": strconv.FormatInt(int64(db.ToGlobalID(rid, replyElem.ReplySeq)), 10)},
})
}
}
@ -281,11 +282,11 @@ func ToStringMessage(e []message.IMessageElement, groupID int64, isRaw ...bool)
}
if ExtraReplyData {
write("[CQ:reply,id=%d,seq=%d,qq=%d,time=%d,text=%s]",
toGlobalID(rid, replyElem.ReplySeq),
db.ToGlobalID(rid, replyElem.ReplySeq),
replyElem.ReplySeq, replyElem.Sender, replyElem.Time,
CQCodeEscapeValue(ToStringMessage(replyElem.Elements, groupID)))
} else {
write("[CQ:reply,id=%d]", toGlobalID(rid, replyElem.ReplySeq))
write("[CQ:reply,id=%d]", db.ToGlobalID(rid, replyElem.ReplySeq))
}
}
for i, elem := range e {
@ -361,6 +362,110 @@ func ToStringMessage(e []message.IMessageElement, groupID int64, isRaw ...bool)
return
}
// ToMessageContent 将消息转换成 Content. 忽略 Reply
// 不同于 onebot 的 Array Message, 此函数转换出来的 Content 的 data 段为实际类型
// 方便数据库查询
func ToMessageContent(e []message.IMessageElement) (r []global.MSG) {
for _, elem := range e {
var m global.MSG
switch o := elem.(type) {
case *message.TextElement:
m = global.MSG{
"type": "text",
"data": global.MSG{"text": o.Content},
}
case *message.LightAppElement:
m = global.MSG{
"type": "json",
"data": global.MSG{"data": o.Content},
}
case *message.AtElement:
if o.Target == 0 {
m = global.MSG{
"type": "at",
"data": global.MSG{
"subType": "all",
},
}
} else {
m = global.MSG{
"type": "at",
"data": global.MSG{
"subType": "user",
"target": o.Target,
"display": o.Display,
},
}
}
case *message.RedBagElement:
m = global.MSG{
"type": "redbag",
"data": global.MSG{"title": o.Title, "type": o.MsgType},
}
case *message.ForwardElement:
m = global.MSG{
"type": "forward",
"data": global.MSG{"id": o.ResId},
}
case *message.FaceElement:
m = global.MSG{
"type": "face",
"data": global.MSG{"id": o.Index},
}
case *message.VoiceElement:
m = global.MSG{
"type": "record",
"data": global.MSG{"file": o.Name, "url": o.Url},
}
case *message.ShortVideoElement:
m = global.MSG{
"type": "video",
"data": global.MSG{"file": o.Name, "url": o.Url},
}
case *message.GroupImageElement:
data := global.MSG{"file": hex.EncodeToString(o.Md5) + ".image", "url": o.Url, "subType": uint32(o.ImageBizType)}
switch {
case o.Flash:
data["type"] = "flash"
case o.EffectID != 0:
data["type"] = "show"
data["id"] = o.EffectID
}
m = global.MSG{
"type": "image",
"data": data,
}
case *message.FriendImageElement:
data := global.MSG{"file": hex.EncodeToString(o.Md5) + ".image", "url": o.Url}
if o.Flash {
data["type"] = "flash"
}
m = global.MSG{
"type": "image",
"data": data,
}
case *message.ServiceElement:
if isOk := strings.Contains(o.Content, "<?xml"); isOk {
m = global.MSG{
"type": "xml",
"data": global.MSG{"data": o.Content, "resid": o.Id},
}
} else {
m = global.MSG{
"type": "json",
"data": global.MSG{"data": o.Content, "resid": o.Id},
}
}
default:
continue
}
if m != nil {
r = append(r, m)
}
}
return
}
// ConvertStringMessage 将消息字符串转为消息元素数组
func (bot *CQBot) ConvertStringMessage(raw string, isGroup bool) (r []message.IMessageElement) {
var t, key string
@ -379,7 +484,7 @@ func (bot *CQBot) ConvertStringMessage(raw string, isGroup bool) (r []message.IM
switch {
case customText != "":
var elem *message.ReplyElement
var org global.MSG
var org db.IStoredMessage
sender, senderErr := strconv.ParseInt(d["qq"], 10, 64)
if senderErr != nil && err != nil {
log.Warnf("警告: 自定义 Reply 元素中必须包含 Uin 或 id")
@ -391,13 +496,13 @@ func (bot *CQBot) ConvertStringMessage(raw string, isGroup bool) (r []message.IM
}
messageSeq, seqErr := strconv.ParseInt(d["seq"], 10, 64)
if err == nil {
org = bot.GetMessage(int32(mid))
org, _ = bot.db.GetMessageByGlobalID(int32(mid))
}
if org != nil {
elem = &message.ReplyElement{
ReplySeq: org["message-id"].(int32),
Sender: org["sender"].(message.Sender).Uin,
Time: org["time"].(int32),
ReplySeq: org.GetAttribute().MessageSeq,
Sender: org.GetAttribute().SenderUin,
Time: int32(org.GetAttribute().Timestamp),
Elements: bot.ConvertStringMessage(customText, isGroup),
}
if senderErr != nil {
@ -419,14 +524,14 @@ func (bot *CQBot) ConvertStringMessage(raw string, isGroup bool) (r []message.IM
}
r = append([]message.IMessageElement{elem}, r...)
case err == nil:
org := bot.GetMessage(int32(mid))
if org != nil {
org, err := bot.db.GetMessageByGlobalID(int32(mid))
if err == nil {
r = append([]message.IMessageElement{
&message.ReplyElement{
ReplySeq: org["message-id"].(int32),
Sender: org["sender"].(message.Sender).Uin,
Time: org["time"].(int32),
Elements: bot.ConvertStringMessage(org["message"].(string), isGroup),
ReplySeq: org.GetAttribute().MessageSeq,
Sender: org.GetAttribute().SenderUin,
Time: int32(org.GetAttribute().Timestamp),
Elements: bot.ConvertContentMessage(org.GetContent(), isGroup),
},
}, r...)
}
@ -551,7 +656,7 @@ func (bot *CQBot) ConvertObjectMessage(m gjson.Result, isGroup bool) (r []messag
switch {
case customText != "":
var elem *message.ReplyElement
var org global.MSG
var org db.IStoredMessage
sender, senderErr := strconv.ParseInt(e.Get("data.[user_id,qq]").String(), 10, 64)
if senderErr != nil && err != nil {
log.Warnf("警告: 自定义 Reply 元素中必须包含 user_id 或 id")
@ -563,13 +668,13 @@ func (bot *CQBot) ConvertObjectMessage(m gjson.Result, isGroup bool) (r []messag
}
messageSeq, seqErr := strconv.ParseInt(e.Get("data.seq").String(), 10, 64)
if err == nil {
org = bot.GetMessage(int32(mid))
org, _ = bot.db.GetMessageByGlobalID(int32(mid))
}
if org != nil {
elem = &message.ReplyElement{
ReplySeq: org["message-id"].(int32),
Sender: org["sender"].(message.Sender).Uin,
Time: org["time"].(int32),
ReplySeq: org.GetAttribute().MessageSeq,
Sender: org.GetAttribute().SenderUin,
Time: int32(org.GetAttribute().Timestamp),
Elements: bot.ConvertStringMessage(customText, isGroup),
}
if senderErr != nil {
@ -591,14 +696,14 @@ func (bot *CQBot) ConvertObjectMessage(m gjson.Result, isGroup bool) (r []messag
}
r = append([]message.IMessageElement{elem}, r...)
case err == nil:
org := bot.GetMessage(int32(mid))
if org != nil {
org, err := bot.db.GetMessageByGlobalID(int32(mid))
if err == nil {
r = append([]message.IMessageElement{
&message.ReplyElement{
ReplySeq: org["message-id"].(int32),
Sender: org["sender"].(message.Sender).Uin,
Time: org["time"].(int32),
Elements: bot.ConvertStringMessage(org["message"].(string), isGroup),
ReplySeq: org.GetAttribute().MessageSeq,
Sender: org.GetAttribute().SenderUin,
Time: int32(org.GetAttribute().Timestamp),
Elements: bot.ConvertContentMessage(org.GetContent(), isGroup),
},
}, r...)
}
@ -654,6 +759,75 @@ func (bot *CQBot) ConvertObjectMessage(m gjson.Result, isGroup bool) (r []messag
return
}
// ConvertContentMessage 将数据库用的 content 转换为消息元素数组
func (bot *CQBot) ConvertContentMessage(content []global.MSG, group bool) (r []message.IMessageElement) {
for _, c := range content {
data := c["data"].(global.MSG)
switch c["type"] {
case "text":
r = append(r, message.NewText(data["text"].(string)))
case "image":
e, err := bot.makeImageOrVideoElem(map[string]string{"file": data["file"].(string)}, false, group)
if err != nil {
log.Warnf("make image elem error: %v", err)
continue
}
flash, id := false, int32(0)
if t, ok := data["type"]; ok {
if t.(string) == "flash" {
flash = true
}
if t.(string) == "show" {
id = data["id"].(int32)
if id < 40000 || id >= 40006 {
id = 40000
}
}
}
switch img := e.(type) {
case *LocalImageElement:
img.Flash = flash
img.EffectID = id
case *message.GroupImageElement:
img.Flash = flash
img.EffectID = id
img.ImageBizType = message.ImageBizType(data["subType"].(uint32))
case *message.FriendImageElement:
img.Flash = flash
}
r = append(r, e)
case "at":
switch data["subType"].(string) {
case "all":
r = append(r, message.NewAt(0))
case "user":
r = append(r, message.NewAt(data["target"].(int64), data["display"].(string)))
default:
continue
}
case "redbag":
r = append(r, &message.RedBagElement{
MsgType: message.RedBagMessageType(data["type"].(int)),
Title: data["title"].(string),
})
case "forward":
r = append(r, &message.ForwardElement{
ResId: data["id"].(string),
})
case "face":
r = append(r, message.NewFace(data["id"].(int32)))
case "video":
e, err := bot.makeImageOrVideoElem(map[string]string{"file": data["file"].(string)}, true, group)
if err != nil {
log.Warnf("make image elem error: %v", err)
continue
}
r = append(r, e)
}
}
return
}
// ToElement 将解码后的CQCode转换为Element.
//
// 返回 interface{} 存在三种类型

View File

@ -2,6 +2,7 @@ package coolq
import (
"encoding/hex"
"github.com/Mrs4s/go-cqhttp/db"
"os"
"path"
"strconv"
@ -112,7 +113,7 @@ func (bot *CQBot) tempMessageEvent(c *client.QQClient, e *client.TempMessageEven
bot.tempSessionCache.Store(m.Sender.Uin, e.Session)
id := m.Id
if bot.db != nil {
id = bot.InsertTempMessage(m.Sender.Uin, m)
// 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 := global.MSG{
@ -178,7 +179,7 @@ func (bot *CQBot) groupMutedEvent(c *client.QQClient, e *client.GroupMuteEvent)
func (bot *CQBot) groupRecallEvent(c *client.QQClient, e *client.GroupMessageRecalledEvent) {
g := c.FindGroup(e.GroupCode)
gid := toGlobalID(e.GroupCode, e.MessageId)
gid := db.ToGlobalID(e.GroupCode, e.MessageId)
log.Infof("群 %v 内 %v 撤回了 %v 的消息: %v.",
formatGroupName(g), formatMemberName(g.FindMember(e.OperatorUin)), formatMemberName(g.FindMember(e.AuthorUin)), gid)
bot.dispatchEventMessage(global.MSG{
@ -295,7 +296,7 @@ func (bot *CQBot) memberTitleUpdatedEvent(c *client.QQClient, e *client.MemberSp
func (bot *CQBot) friendRecallEvent(c *client.QQClient, e *client.FriendMessageRecalledEvent) {
f := c.FindFriend(e.FriendUin)
gid := toGlobalID(e.FriendUin, e.MessageId)
gid := db.ToGlobalID(e.FriendUin, e.MessageId)
if f != nil {
log.Infof("好友 %v(%v) 撤回了消息: %v", f.Nickname, f.Uin, gid)
} else {
@ -472,7 +473,7 @@ func (bot *CQBot) otherClientStatusChangedEvent(c *client.QQClient, e *client.Ot
func (bot *CQBot) groupEssenceMsg(c *client.QQClient, e *client.GroupDigestEvent) {
g := c.FindGroup(e.GroupCode)
gid := toGlobalID(e.GroupCode, e.MessageID)
gid := db.ToGlobalID(e.GroupCode, e.MessageID)
if e.OperationType == 1 {
log.Infof(
"群 %v 内 %v 将 %v 的消息(%v)设为了精华消息.",

104
db/database.go Normal file
View File

@ -0,0 +1,104 @@
package db
import (
"fmt"
"github.com/Mrs4s/go-cqhttp/global"
"hash/crc32"
)
type (
// IDatabase 数据库操作接口定义
IDatabase interface {
// Open 初始化数据库
Open() error
// GetMessageByGlobalID 通过 GlobalID 来获取消息
GetMessageByGlobalID(int32) (IStoredMessage, error)
// GetGroupMessageByGlobalID 通过 GlobalID 来获取群消息
GetGroupMessageByGlobalID(int32) (*StoredGroupMessage, error)
// GetPrivateMessageByGlobalID 通过 GlobalID 来获取私聊消息
GetPrivateMessageByGlobalID(int32) (*StoredPrivateMessage, error)
// InsertGroupMessage 向数据库写入新的群消息
InsertGroupMessage(*StoredGroupMessage) error
// InsertPrivateMessage 向数据库写入新的私聊消息
InsertPrivateMessage(*StoredPrivateMessage) error
}
IStoredMessage interface {
GetID() string
GetType() string
GetGlobalID() int32
GetAttribute() *StoredMessageAttribute
GetContent() []global.MSG
}
// StoredGroupMessage 持久化群消息
StoredGroupMessage struct {
ID string `bson:"_id"`
GlobalID int32 `bson:"globalId"`
Attribute *StoredMessageAttribute `bson:"attribute"`
SubType string `bson:"subType"`
QuotedInfo *QuotedInfo `bson:"quotedInfo"`
GroupCode int64 `bson:"groupCode"`
AnonymousID string `bson:"anonymousId"`
Content []global.MSG `bson:"content"`
}
// StoredPrivateMessage 持久化私聊消息
StoredPrivateMessage struct {
ID string `bson:"_id"`
GlobalID int32 `bson:"globalId"`
Attribute *StoredMessageAttribute `bson:"attribute"`
SubType string `bson:"subType"`
QuotedInfo *QuotedInfo `bson:"quotedInfo"`
SessionUin int64 `bson:"sessionUin"`
TargetUin int64 `bson:"targetUin"`
Content []global.MSG `bson:"content"`
}
// StoredMessageAttribute 持久化消息属性
StoredMessageAttribute struct {
MessageSeq int32 `bson:"messageSeq"`
InternalID int32 `bson:"internalId"`
SenderUin int64 `bson:"senderUin"`
SenderName string `bson:"senderName"`
Timestamp int64 `bson:"timestamp"`
}
// QuotedInfo 引用回复
QuotedInfo struct {
PrevID string `bson:"prevId"`
PrevGlobalID int32 `bson:"prevGlobalId"`
QuotedContent []global.MSG `bson:"quotedContent"`
}
// MultiDatabase todo
MultiDatabase struct {
}
)
// ToGlobalID 构建`code`-`msgID`的字符串并返回其CRC32 Checksum的值
func ToGlobalID(code int64, msgID int32) int32 {
return int32(crc32.ChecksumIEEE([]byte(fmt.Sprintf("%d-%d", code, msgID))))
}
func (m *StoredGroupMessage) GetID() string { return m.ID }
func (m *StoredGroupMessage) GetType() string { return "group" }
func (m *StoredGroupMessage) GetGlobalID() int32 { return m.GlobalID }
func (m *StoredGroupMessage) GetAttribute() *StoredMessageAttribute { return m.Attribute }
func (m *StoredGroupMessage) GetContent() []global.MSG { return m.Content }
func (m *StoredPrivateMessage) GetID() string { return m.ID }
func (m *StoredPrivateMessage) GetType() string { return "private" }
func (m *StoredPrivateMessage) GetGlobalID() int32 { return m.GlobalID }
func (m *StoredPrivateMessage) GetAttribute() *StoredMessageAttribute { return m.Attribute }
func (m *StoredPrivateMessage) GetContent() []global.MSG { return m.Content }

116
db/leveldb.go Normal file
View File

@ -0,0 +1,116 @@
package db
import (
"bytes"
"encoding/gob"
"github.com/Mrs4s/MiraiGo/binary"
"github.com/Mrs4s/go-cqhttp/global"
"github.com/pkg/errors"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/opt"
"path"
)
type LevelDBImpl struct {
db *leveldb.DB
}
const (
group byte = 0x0
private byte = 0x1
)
func UseLevelDB() IDatabase {
gob.Register(StoredMessageAttribute{})
gob.Register(QuotedInfo{})
gob.Register(global.MSG{})
gob.Register(StoredGroupMessage{})
gob.Register(StoredPrivateMessage{})
return &LevelDBImpl{}
}
func (db *LevelDBImpl) Open() error {
p := path.Join("data", "leveldb-v2")
d, err := leveldb.OpenFile(p, &opt.Options{
WriteBuffer: 128 * opt.KiB,
})
if err != nil {
return errors.Wrap(err, "open level db error")
}
db.db = d
return nil
}
func (db *LevelDBImpl) GetMessageByGlobalID(id int32) (IStoredMessage, error) {
v, err := db.db.Get(binary.ToBytes(id), nil)
if err != nil {
return nil, errors.Wrap(err, "get value error")
}
r := binary.NewReader(v)
switch r.ReadByte() {
case group:
g := &StoredGroupMessage{}
if err = gob.NewDecoder(bytes.NewReader(r.ReadAvailable())).Decode(g); err != nil {
return nil, errors.Wrap(err, "decode message error")
}
return g, nil
case private:
p := &StoredPrivateMessage{}
if err = gob.NewDecoder(bytes.NewReader(r.ReadAvailable())).Decode(p); err != nil {
return nil, errors.Wrap(err, "decode message error")
}
return p, nil
default:
return nil, errors.New("unknown message flag")
}
}
func (db *LevelDBImpl) GetGroupMessageByGlobalID(id int32) (*StoredGroupMessage, error) {
i, err := db.GetMessageByGlobalID(id)
if err != nil {
return nil, err
}
g, ok := i.(*StoredGroupMessage)
if !ok {
return nil, errors.New("message type error")
}
return g, nil
}
func (db *LevelDBImpl) GetPrivateMessageByGlobalID(id int32) (*StoredPrivateMessage, error) {
i, err := db.GetMessageByGlobalID(id)
if err != nil {
return nil, err
}
p, ok := i.(*StoredPrivateMessage)
if !ok {
return nil, errors.New("message type error")
}
return p, nil
}
func (db *LevelDBImpl) InsertGroupMessage(msg *StoredGroupMessage) error {
buf := global.NewBuffer()
defer global.PutBuffer(buf)
if err := gob.NewEncoder(buf).Encode(msg); err != nil {
return errors.Wrap(err, "encode message error")
}
err := db.db.Put(binary.ToBytes(msg.GlobalID), binary.NewWriterF(func(w *binary.Writer) {
w.WriteByte(group)
w.Write(buf.Bytes())
}), nil)
return errors.Wrap(err, "put data error")
}
func (db *LevelDBImpl) InsertPrivateMessage(msg *StoredPrivateMessage) error {
buf := global.NewBuffer()
defer global.PutBuffer(buf)
if err := gob.NewEncoder(buf).Encode(msg); err != nil {
return errors.Wrap(err, "encode message error")
}
err := db.db.Put(binary.ToBytes(msg.GlobalID), binary.NewWriterF(func(w *binary.Writer) {
w.WriteByte(private)
w.Write(buf.Bytes())
}), nil)
return errors.Wrap(err, "put data error")
}

View File

@ -75,7 +75,7 @@ func (h *httpCtx) Get(s string) gjson.Result {
func (s *httpServer) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
var ctx httpCtx
contentType := request.Header.Get("Content-Type")
contentType := request.Header.Get("Content-SubType")
switch request.Method {
case http.MethodPost:
if strings.Contains(contentType, "application/json") {
@ -119,7 +119,7 @@ func (s *httpServer) ServeHTTP(writer http.ResponseWriter, request *http.Request
log.Debugf("HTTPServer接收到API调用: %v", action)
ret := s.api.callAPI(action, &ctx)
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.Header().Set("Content-SubType", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_ = json.NewEncoder(writer).Encode(ret)
}