From 90b0bdb9e440ec7e7b246fa21a37370898552492 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BA=90=E6=96=87=E9=9B=A8?= <41315874+fumiama@users.noreply.github.com> Date: Thu, 19 Oct 2023 00:41:07 +0900 Subject: [PATCH] feat: add message --- bot.go | 8 +++- context.go | 64 +++++++++++++++++++++++++-- example/echo/main.go | 2 +- message.go | 103 +++++++++++++++++++++++++++++++++++++++++++ openapi_message.go | 15 ++++--- 5 files changed, 180 insertions(+), 12 deletions(-) diff --git a/bot.go b/bot.go index e736117..4f0d5f5 100644 --- a/bot.go +++ b/bot.go @@ -357,7 +357,11 @@ func (bot *Bot) Listen() { clients.Store(k, bot) continue } - log.Debugln(getLogHeader(), "接收到第", payload.S, "个事件:", payload.Op, ", 类型:", payload.T, ", 数据:", BytesToString(payload.D)) + log.Debug(getLogHeader(), " 接收到第 ", payload.S, " 个事件: ", payload.Op, ", 类型: ", payload.T, ", 数据: ", BytesToString(payload.D)) + if payload.S <= bot.seq { + log.Warn(getLogHeader(), " 忽略重复编号: ", payload.S, ", 事件: ", payload.Op, ", 类型: ", payload.T) + continue + } bot.seq = payload.S switch payload.Op { case OpCodeDispatch: // Receive @@ -390,7 +394,7 @@ func (bot *Bot) Listen() { lastheartbeat = time.Now() case OpCodeHTTPCallbackACK: // Reply default: - log.Warnln(getLogHeader(), "忽略未知事件, 序号:", payload.S, ", Op:", payload.Op, ", 类型:", payload.T, ", 数据:", BytesToString(payload.D)) + log.Warn(getLogHeader(), " 忽略未知事件, 序号: ", payload.S, ", Op: ", payload.Op, ", 类型: ", payload.T, ", 数据: ", BytesToString(payload.D)) } } } diff --git a/context.go b/context.go index a40b35a..5bb6381 100644 --- a/context.go +++ b/context.go @@ -76,8 +76,53 @@ func (ctx *Ctx) CheckSession() Rule { } } -// Send 发送消息到对方 -func (ctx *Ctx) Send(replytosender bool, post *MessagePost) (reply *Message, err error) { +// Send 发送一批消息 +func (ctx *Ctx) Send(messages Messages) (m []*Message, err error) { + isnextreply := false + textlist := []any{} + var reply *Message + for _, msg := range messages { + switch msg.Type { + case MessageTypeText: + textlist = append(textlist, msg.Data) + case MessageTypeImage: + reply, err = ctx.SendImage(msg.Data, isnextreply, textlist...) + if isnextreply { + isnextreply = false + } + textlist = textlist[:0] + m = append(m, reply) + if err != nil { + return + } + case MessageTypeImageBytes: + reply, err = ctx.SendImageBytes(StringToBytes(msg.Data), isnextreply, textlist...) + if isnextreply { + isnextreply = false + } + textlist = textlist[:0] + m = append(m, reply) + if err != nil { + return + } + case MessageTypeReply: + isnextreply = true + } + } + if len(textlist) > 0 { + reply, err = ctx.SendPlainMessage(isnextreply, textlist...) + m = append(m, reply) + } + return +} + +// SendChain 链式发送 +func (ctx *Ctx) SendChain(message ...MessageSegment) (m []*Message, err error) { + return ctx.Send(message) +} + +// Post 发送消息到对方 +func (ctx *Ctx) Post(replytosender bool, post *MessagePost) (reply *Message, err error) { msg := ctx.Message if msg != nil { post.ReplyMessageID = msg.ID @@ -103,7 +148,7 @@ func (ctx *Ctx) Send(replytosender bool, post *MessagePost) (reply *Message, err // SendPlainMessage 发送纯文本消息到对方 func (ctx *Ctx) SendPlainMessage(replytosender bool, printable ...any) (*Message, error) { - return ctx.Send(replytosender, &MessagePost{ + return ctx.Post(replytosender, &MessagePost{ Content: HideURL(fmt.Sprint(printable...)), }) } @@ -120,7 +165,18 @@ func (ctx *Ctx) SendImage(file string, replytosender bool, caption ...any) (*Mes post.ImageFile = file } - return ctx.Send(replytosender, post) + return ctx.Post(replytosender, post) +} + +// SendImageBytes 发送带图片消息到对方 +func (ctx *Ctx) SendImageBytes(data []byte, replytosender bool, caption ...any) (*Message, error) { + post := &MessagePost{ + Content: HideURL(fmt.Sprint(caption...)), + } + + post.ImageBytes = data + + return ctx.Post(replytosender, post) } // Echo 向自身分发虚拟事件 diff --git a/example/echo/main.go b/example/echo/main.go index 066fabf..1d9794c 100644 --- a/example/echo/main.go +++ b/example/echo/main.go @@ -15,6 +15,6 @@ func init() { if args == "" { return } - _, _ = ctx.SendPlainMessage(false, args) + _, _ = ctx.SendChain(nano.ReplyTo(ctx.Message.Author.ID), nano.Text(args)) }) } diff --git a/message.go b/message.go index 500d481..bad179b 100644 --- a/message.go +++ b/message.go @@ -1,6 +1,8 @@ package nano import ( + "fmt" + "strconv" "sync" "time" @@ -24,3 +26,104 @@ func GetTriggeredMessages(id string) []string { defer triggeredMessagesMu.Unlock() return triggeredMessages.Get(id) } + +type MessageType int + +const ( + MessageTypeText MessageType = iota + MessageTypeImage + MessageTypeImageBytes + MessageTypeReply +) + +// Message impl the array form of message +type Messages []MessageSegment + +// MessageSegment impl the single message +// MessageSegment 消息数组 +type MessageSegment struct { + Type MessageType + Data string +} + +// String impls the interface fmt.Stringer +func (m MessageSegment) String() string { + return m.Data +} + +// Text 纯文本 +func Text(text ...interface{}) MessageSegment { + return MessageSegment{ + Type: MessageTypeText, + Data: MessageEscape(fmt.Sprint(text...)), + } +} + +// Face QQ表情 +// https://bot.q.qq.com/wiki/develop/api/openapi/message/message_format.html#%E6%94%AF%E6%8C%81%E7%9A%84%E6%A0%BC%E5%BC%8F +func Face(id int) MessageSegment { + return MessageSegment{ + Type: MessageTypeText, + Data: "", + } +} + +// Image 普通图片 +func Image(file string) MessageSegment { + return MessageSegment{ + Type: MessageTypeImage, + Data: file, + } +} + +// ImageBytes 普通图片 +func ImageBytes(data []byte) MessageSegment { + return MessageSegment{ + Type: MessageTypeImageBytes, + Data: BytesToString(data), + } +} + +// At @某人 +// https://bot.q.qq.com/wiki/develop/api/openapi/message/message_format.html#%E6%94%AF%E6%8C%81%E7%9A%84%E6%A0%BC%E5%BC%8F +func At(id string) MessageSegment { + if id == "all" { + return AtAll() + } + return MessageSegment{ + Type: MessageTypeText, + Data: "<@!" + id + ">", + } +} + +// AtAll @全体成员 +// https://bot.q.qq.com/wiki/develop/api/openapi/message/message_format.html#%E6%94%AF%E6%8C%81%E7%9A%84%E6%A0%BC%E5%BC%8F +func AtAll() MessageSegment { + return MessageSegment{ + Type: MessageTypeText, + Data: "@everyone", + } +} + +// AtChannel #频道 +// https://bot.q.qq.com/wiki/develop/api/openapi/message/message_format.html#%E6%94%AF%E6%8C%81%E7%9A%84%E6%A0%BC%E5%BC%8F +func AtChannel(id string) MessageSegment { + return MessageSegment{ + Type: MessageTypeText, + Data: "<#channel_id>", + } +} + +// Reply 回复 +// https://github.com/botuniverse/onebot-11/tree/master/message/segment.md#%E5%9B%9E%E5%A4%8D +func ReplyTo(id string) MessageSegment { + return MessageSegment{ + Type: MessageTypeReply, + Data: id, + } +} + +// ReplyWithMessage returns a reply message +func ReplyWithMessage(messageID string, m ...MessageSegment) Messages { + return append(Messages{ReplyTo(messageID)}, m...) +} diff --git a/openapi_message.go b/openapi_message.go index e5d5403..129df34 100644 --- a/openapi_message.go +++ b/openapi_message.go @@ -198,7 +198,8 @@ type MessagePost struct { Ark *MessageArk `json:"ark,omitempty"` // https://bot.q.qq.com/wiki/develop/api/openapi/message/message_template.html MessageReference *MessageReference `json:"message_reference,omitempty"` Image string `json:"image,omitempty"` - ImageFile string `json:"-"` // ImageFile 为图片路径 file:/// or base64:// or base16384:// , 与 Image 参数二选一, 优先 Image + ImageFile string `json:"-"` // ImageFile 为图片路径 file:/// or base64:// or base16384:// , 与 Image, ImageBytes 参数二选一, 优先 ImageBytes + ImageBytes []byte `json:"-"` // ImageBytes 图片数据 ReplyMessageID string `json:"msg_id,omitempty"` ReplyEventID string `json:"event_id,omitempty"` Markdown *MessageMarkdown `json:"markdown,omitempty"` @@ -237,17 +238,21 @@ func (mp *MessagePost) String() string { sb.WriteString(mp.MessageReference.MessageID) } if mp.Image != "" { - sb.WriteString(", 图片URL:") + sb.WriteString(", 图片URL: ") sb.WriteString(mp.Image) } if mp.ImageFile != "" { - sb.WriteString(", 图片内容:") + sb.WriteString(", 图片内容: ") x := mp.ImageFile if len(x) > 64 { x = x[:64] + "..." } sb.WriteString(x) } + if len(mp.ImageBytes) > 0 { + sb.WriteString(", 图片大小: ") + sb.WriteString(strconv.Itoa(len(mp.ImageBytes))) + } if mp.Markdown != nil { sb.WriteString(", MD模版: ") sb.WriteString(strconv.Itoa(mp.Markdown.TemplateID)) @@ -260,7 +265,7 @@ func (mp *MessagePost) String() string { } func (bot *Bot) postMessageTo(ep string, content *MessagePost) (*Message, error) { - if content.ImageFile == "" { + if len(content.ImageBytes) == 0 && content.ImageFile == "" { return bot.postOpenAPIofMessage(ep, "", WriteBodyFromJSON(content)) } x := reflect.ValueOf(content).Elem() @@ -284,7 +289,7 @@ func (bot *Bot) postMessageTo(ep string, content *MessagePost) (*Message, error) msg = append(msg, data) continue } - msg = append(msg, xi.String()) + msg = append(msg, xi.Interface()) // []byte or string } if len(msg) < 2 { return nil, ErrEmptyMessagePost