Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

新增群聊收发消息API(已支持图文) #48

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
60 changes: 56 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,68 @@ func main() {
}

// 监听哪类事件就需要实现哪类的 handler,定义:websocket/event_handler.go
var atMessage websocket.ATMessageEventHandler = func(event *dto.WSPayload, data *dto.WSATMessageData) error {
fmt.Println(event, data)
return nil
}
var atMessage event.ATMessageEventHandler = func(event *dto.WSPayload, data *dto.WSATMessageData) error {
fmt.Println(event, data)
return nil
}

intent := websocket.RegisterHandlers(atMessage)
// 启动 session manager 进行 ws 连接的管理,如果接口返回需要启动多个 shard 的连接,这里也会自动启动多个
botgo.NewSessionManager().Start(ws, token, &intent)
}
```

### 3.群聊示例

```golang
func main() {
token := token.BotToken(conf.AppID, conf.Token)
api := botgo.NewOpenAPI(token).WithTimeout(3 * time.Second)
ctx := context.Background()
ws, err := api.WS(ctx, nil, "")
if err != nil {
log.Printf("%+v, err:%v", ws, err)
}

// 监听哪类事件就需要实现哪类的 handler,定义:websocket/event_handler.go
var atMessage event.ATMessageEventHandler = func(event *dto.WSPayload, data *dto.WSATMessageData) error {
fmt.Println(event, data)
return nil
}

var groupMessage event.GroupAtMessageEventHandler = func(event *dto.WSPayload, data *dto.WSGroupATMessageData) error {
groupId := data.GroupId
//userId := data.Author.UserId
content := strings.TrimSpace(data.Content)
msgId := data.MsgId

resp, err := api.PostGroupRichMediaMessage(ctx, groupId, &dto.GroupRichMediaMessageToCreate{FileType: 1, Url: "图片Url", SrvSendMsg: false})
if err != nil {
newMsg := &dto.GroupMessageToCreate{
Content: "图片上传失败",
MsgID: msgId,
MsgType: 0,
}
api.PostGroupMessage(ctx, groupId, newMsg)
return nil
}

newMsg := &dto.GroupMessageToCreate{
Content: content,
Media: &dto.FileInfo{FileInfo: resp.FileInfo},
MsgID: msgId,
MsgType: 7,
}
api.PostGroupMessage(ctx, groupId, newMsg)
return nil
}

intent := websocket.RegisterHandlers(atMessage, groupMessage)
// 启动 session manager 进行 ws 连接的管理,如果接口返回需要启动多个 shard 的连接,这里也会自动启动多个
botgo.NewSessionManager().Start(ws, token, &intent)
}
```

## 二、什么是 SessionManager

SessionManager,用于管理 websocket 连接的启动,重连等。接口定义在:`session_manager.go`。开发者也可以自己实现自己的 SessionManager。
Expand Down
15 changes: 15 additions & 0 deletions dto/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,21 @@ type Message struct {
SrcGuildID string `json:"src_guild_id"`
}

type GroupMessage struct {
GroupId string `json:"group_id"`
GroupOpenId string `json:"group_openid"`
Content string `json:"content"`
MsgId string `json:"id"`
Author Author `json:"author"`
Timestamp Timestamp `json:"timestamp"`
Attachments []*MessageAttachment `json:"attachments"`
}

type Author struct {
UserId string `json:"id"`
UserOpenId string `json:"member_openid"`
}

// Embed 结构
type Embed struct {
Title string `json:"title,omitempty"`
Expand Down
49 changes: 45 additions & 4 deletions dto/message_create.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package dto

import "github.com/tencent-connect/botgo/dto/keyboard"
import (
"github.com/tencent-connect/botgo/dto/keyboard"
)

// MessageToCreate 发送消息结构体定义
type MessageToCreate struct {
Expand All @@ -16,6 +18,33 @@ type MessageToCreate struct {
EventID string `json:"event_id,omitempty"` // 要回复的事件id, 逻辑同MsgID
}

// 消息类型: 0 是文本,1 图文混排,2 markdown, 3 ark,4 embed 7 富媒体
type GroupMessageToCreate struct {
Content string `json:"content,omitempty"`
MsgType int `json:"msg_type"`
Markdown *Markdown `json:"markdown,omitempty"`
Keyboard *keyboard.MessageKeyboard `json:"keyboard,omitempty"` // 消息按钮组件
Media *FileInfo `json:"media,omitempty"`
Ark *Ark `json:"ark,omitempty"`
Image string `json:"image,omitempty"`
MessageReference *MessageReference `json:"message_reference,omitempty"`
EventID string `json:"event_id,omitempty"` // 要回复的事件id, 逻辑同MsgID
MsgID string `json:"msg_id,omitempty"`
MsgReq uint `json:"msg_req,omitempty"`
}

type FileInfo struct {
FileInfo string `json:"file_info,omitempty"`
}

// 媒体类型:1 图片,2 视频,3 语音,4 文件(暂不开放) 资源格式要求: 图片:png/jpg,视频:mp4,语音:silk,
type GroupRichMediaMessageToCreate struct {
FileType int `json:"file_type"`
Url string `json:"url"`
SrvSendMsg bool `json:"srv_send_msg"`
FileData []byte `json:"file_data"`
}

// MessageReference 引用消息
type MessageReference struct {
MessageID string `json:"message_id"` // 消息 id
Expand All @@ -24,9 +53,10 @@ type MessageReference struct {

// Markdown markdown 消息
type Markdown struct {
TemplateID int `json:"template_id"` // 模版 id
Params []*MarkdownParams `json:"params"` // 模版参数
Content string `json:"content"` // 原生 markdown
TemplateID int `json:"template_id,omitempty"` // 模版 id
CustomTemplateId string `json:"custom_template_id,omitempty"`
Params []*MarkdownParams `json:"params"` // 模版参数
Content string `json:"content"` // 原生 markdown
}

// MarkdownParams markdown 模版参数 键值对
Expand All @@ -46,3 +76,14 @@ type SettingGuide struct {
// 频道ID, 当通过私信发送设置引导消息时,需要指定guild_id
GuildID string `json:"guild_id"`
}

type RichMediaMsgResp struct {
FileUuid string `json:"file_uuid,omitempty"`
FileInfo string `json:"file_info,omitempty"`
Ttl uint `json:"ttl,omitempty"`
}

type GroupMsgResp struct {
Id string `json:"id"`
Timestamp Timestamp `json:"timestamp"`
}
18 changes: 0 additions & 18 deletions dto/pager.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,6 @@ func (g *GuildMembersPager) QueryParams() map[string]string {
return query
}

// GuildRoleMembersPager 分页器
type GuildRoleMembersPager struct {
StartIndex string `json:"start_index"`
Limit string `json:"limit"`
}

// QueryParams 转换为 query 参数
func (g *GuildRoleMembersPager) QueryParams() map[string]string {
query := make(map[string]string)
if g.Limit != "" {
query["limit"] = g.Limit
}
if g.StartIndex != "" {
query["start_index"] = g.StartIndex
}
return query
}

// GuildPager 分页器
type GuildPager struct {
Before string `json:"before"` // 读此id之前的数据
Expand Down
12 changes: 12 additions & 0 deletions dto/websocket_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ const (
EventForumReplyDelete EventType = "FORUM_REPLY_DELETE"
EventForumAuditResult EventType = "FORUM_PUBLISH_AUDIT_RESULT"
EventInteractionCreate EventType = "INTERACTION_CREATE"
EventC2CMessageCreate EventType = "C2C_MESSAGE_CREATE"
EventGroupATMessageCreate EventType = "GROUP_AT_MESSAGE_CREATE"
EventGroupMessageCreate EventType = "GROUP_MESSAGE_CREATE"
EventGroupAddRobbot EventType = "GROUP_ADD_ROBBOT"
EventGroupDelRobbot EventType = "GROUP_DEL_ROBBOT"
EventGroupMsgReject EventType = "GROUP_MSG_REJECT"
EventGroupMsgReceive EventType = "GROUP_MSG_RECEIVE"
EventFriendAdd EventType = "FRIEND_ADD"
EventFriendDel EventType = "FRIEND_DEL"
EventC2CMsgReject EventType = "C2C_MSG_REJECT"
EventC2CMsgReceive EventType = "C2C_MSG_RECEIVE"
)

// intentEventMap 不同 intent 对应的事件定义
Expand All @@ -58,6 +69,7 @@ var intentEventMap = map[Intent][]EventType{
EventForumPostDelete, EventForumReplyCreate, EventForumReplyDelete, EventForumAuditResult,
},
IntentInteraction: {EventInteractionCreate},
IntentQQ: {EventC2CMessageCreate, EventC2CMsgReceive, EventC2CMsgReject, EventGroupATMessageCreate, EventGroupMessageCreate, EventGroupAddRobbot, EventGroupDelRobbot, EventGroupMsgReceive, EventGroupMsgReject, EventFriendAdd, EventFriendDel},
}

var eventIntentMap = transposeIntentEventMap(intentEventMap)
Expand Down
5 changes: 5 additions & 0 deletions dto/websocket_intent.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ const (
IntentDirectMessageReactions
IntentDirectMessageTyping

// IntentQQ 包含
// - C2C_MESSAGE_CREATE
// - GROUP_AT_MESSAGE_CREATE
IntentQQ Intent = 1 << 25

IntentInteraction Intent = 1 << 26 // 互动事件
IntentAudit Intent = 1 << 27 // 审核事件
// IntentForum 论坛事件
Expand Down
4 changes: 4 additions & 0 deletions dto/websocket_payload.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ type WSMessageData Message
// WSATMessageData only at 机器人的消息 payload
type WSATMessageData Message

type WSGroupATMessageData GroupMessage

type WSGroupMessageData GroupMessage

// WSDirectMessageData 私信消息 payload
type WSDirectMessageData Message

Expand Down
31 changes: 30 additions & 1 deletion event/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,19 @@ var eventParseFuncMap = map[dto.OPCode]map[dto.EventType]eventParseFunc{
dto.EventForumAuditResult: forumAuditHandler,

dto.EventInteractionCreate: interactionHandler,

dto.EventGroupATMessageCreate: groupAtMessageHandler,
dto.EventGroupMessageCreate: groupMessageHandler,
},
}

type eventParseFunc func(event *dto.WSPayload, message []byte) error

// ParseAndHandle 处理回调事件
func ParseAndHandle(payload *dto.WSPayload) error {
if (DefaultHandlers.Check != nil) && DefaultHandlers.Check(payload, payload.RawMessage) == false {
return nil
}
// 指定类型的 handler
if h, ok := eventParseFuncMap[payload.OPCode][payload.Type]; ok {
return h(payload, payload.RawMessage)
Expand All @@ -72,7 +78,8 @@ func ParseAndHandle(payload *dto.WSPayload) error {
// ParseData 解析数据
func ParseData(message []byte, target interface{}) error {
data := gjson.Get(string(message), "d")
return json.Unmarshal([]byte(data.String()), target)
err := json.Unmarshal([]byte(data.String()), target)
return err
}

func guildHandler(payload *dto.WSPayload, message []byte) error {
Expand Down Expand Up @@ -261,3 +268,25 @@ func interactionHandler(payload *dto.WSPayload, message []byte) error {
}
return nil
}

func groupAtMessageHandler(payload *dto.WSPayload, message []byte) error {
data := &dto.WSGroupATMessageData{}
if err := ParseData(message, data); err != nil {
return err
}
if DefaultHandlers.GroupAtMessage != nil {
return DefaultHandlers.GroupAtMessage(payload, data)
}
return nil
}

func groupMessageHandler(payload *dto.WSPayload, message []byte) error {
data := &dto.WSGroupMessageData{}
if err := ParseData(message, data); err != nil {
return err
}
if DefaultHandlers.GroupMessage != nil {
return DefaultHandlers.GroupMessage(payload, data)
}
return nil
}
19 changes: 19 additions & 0 deletions event/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ var DefaultHandlers struct {
Ready ReadyHandler
ErrorNotify ErrorNotifyHandler
Plain PlainEventHandler
Check CheckEventHandler

Guild GuildEventHandler
GuildMember GuildMemberEventHandler
Expand All @@ -31,6 +32,9 @@ var DefaultHandlers struct {
ForumAudit ForumAuditEventHandler

Interaction InteractionEventHandler

GroupAtMessage GroupAtMessageEventHandler
GroupMessage GroupMessageEventHandler
}

// ReadyHandler 可以处理 ws 的 ready 事件
Expand All @@ -43,6 +47,9 @@ type ErrorNotifyHandler func(err error)
// PlainEventHandler 透传handler
type PlainEventHandler func(event *dto.WSPayload, message []byte) error

// CheckEventHandler 消息前置检测
type CheckEventHandler func(event *dto.WSPayload, message []byte) bool

// GuildEventHandler 频道事件handler
type GuildEventHandler func(event *dto.WSPayload, data *dto.WSGuildData) error

Expand Down Expand Up @@ -94,6 +101,10 @@ type ForumAuditEventHandler func(event *dto.WSPayload, data *dto.WSForumAuditDat
// InteractionEventHandler 互动事件 handler
type InteractionEventHandler func(event *dto.WSPayload, data *dto.WSInteractionData) error

type GroupAtMessageEventHandler func(event *dto.WSPayload, data *dto.WSGroupATMessageData) error

type GroupMessageEventHandler func(event *dto.WSPayload, data *dto.WSGroupMessageData) error

// RegisterHandlers 注册事件回调,并返回 intent 用于 websocket 的鉴权
func RegisterHandlers(handlers ...interface{}) dto.Intent {
var i dto.Intent
Expand All @@ -103,6 +114,8 @@ func RegisterHandlers(handlers ...interface{}) dto.Intent {
DefaultHandlers.Ready = handle
case ErrorNotifyHandler:
DefaultHandlers.ErrorNotify = handle
case CheckEventHandler:
DefaultHandlers.Check = handle
case PlainEventHandler:
DefaultHandlers.Plain = handle
case AudioEventHandler:
Expand Down Expand Up @@ -194,6 +207,12 @@ func registerMessageHandlers(i dto.Intent, handlers ...interface{}) dto.Intent {
case MessageAuditEventHandler:
DefaultHandlers.MessageAudit = handle
i = i | dto.EventToIntent(dto.EventMessageAuditPass, dto.EventMessageAuditReject)
case GroupAtMessageEventHandler:
DefaultHandlers.GroupAtMessage = handle
i = i | dto.EventToIntent(dto.EventGroupATMessageCreate)
case GroupMessageEventHandler:
DefaultHandlers.GroupMessage = handle
i = i | dto.EventToIntent(dto.EventGroupMessageCreate)
default:
}
}
Expand Down
4 changes: 2 additions & 2 deletions examples/apitest/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ var (
testGuildID = "3326534247441079828" // replace your guild id
testChannelID = "1595028" // replace your channel id
testMessageID = `08e092eeb983afef9e0110f9bb5d1a1231343431313532313836373838333234303420801e
28003091c4bb02380c400c48d8a7928d06` // replace your channel id
28003091c4bb02380c400c48d8a7928d06` // replace your channel id
testRolesID = `10054557` // replace your roles id
testMemberID = `1201318637970874066` // replace your member id
testMarkdownTemplateID = 1231231231231231 // replace your markdown template id
Expand All @@ -46,7 +46,7 @@ func TestMain(m *testing.M) {
}
log.Println(conf)

botToken = token.BotToken(conf.AppID, conf.Token)
botToken = token.BotToken(conf.AppID, conf.Token, "Bot")
api = botgo.NewOpenAPI(botToken).WithTimeout(3 * time.Second)

os.Exit(m.Run())
Expand Down
Loading