github.com/fumiama/NanoBot@v0.0.0-20231122134259-c22d8183efca/openapi_message.go (about)

     1  package nano
     2  
     3  import (
     4  	"encoding/json"
     5  	"reflect"
     6  	"strconv"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/pkg/errors"
    11  	"github.com/sirupsen/logrus"
    12  )
    13  
    14  var (
    15  	ErrEmptyMessagePost = errors.New("empty message post")
    16  )
    17  
    18  // Message 消息对象
    19  //
    20  // https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#%E6%B6%88%E6%81%AF%E5%AF%B9%E8%B1%A1-message
    21  type Message struct {
    22  	ID               string              `json:"id"`
    23  	ChannelID        string              `json:"channel_id"`
    24  	GuildID          string              `json:"guild_id"`
    25  	GroupOpenID      string              `json:"group_openid"`
    26  	FileUUID         string              `json:"file_uuid"`
    27  	FileInfo         string              `json:"file_info"`
    28  	Content          string              `json:"content"`
    29  	Timestamp        *time.Time          `json:"timestamp"`
    30  	EditedTimestamp  *time.Time          `json:"edited_timestamp"`
    31  	FileInfoTTL      int                 `json:"ttl"`
    32  	MentionEveryone  bool                `json:"mention_everyone"`
    33  	Author           *User               `json:"author"`
    34  	Attachments      []MessageAttachment `json:"attachments"`
    35  	Embeds           []MessageEmbed      `json:"embeds"`
    36  	Member           *Member             `json:"member"`
    37  	Ark              *MessageArk         `json:"ark"`
    38  	SeqInChannel     string              `json:"seq_in_channel"`
    39  	MessageReference *MessageReference   `json:"message_reference"`
    40  	SrcGuildID       string              `json:"src_guild_id"`
    41  	Data             *struct {
    42  		MessageAudit *MessageAudited `json:"message_audit,omitempty"`
    43  	} `json:"data,omitempty"`
    44  }
    45  
    46  // "=> ギルド:", ctx.Message.GuildID+", 频道:", ctx.Message.ChannelID+", 用户:", ctx.Message.Author.Username+"("+ctx.Message.Author.ID+"), 内容:", ctx.Message.Content
    47  func (m *Message) String() string {
    48  	sb := strings.Builder{}
    49  	if m.Timestamp != nil {
    50  		sb.WriteString(m.Timestamp.Format(time.DateTime))
    51  		sb.WriteByte(' ')
    52  	}
    53  	if m.FileUUID != "" {
    54  		sb.WriteString("富媒体: ")
    55  		sb.WriteString(m.FileUUID)
    56  		sb.WriteString(", 有效期: ")
    57  		if m.FileInfoTTL == 0 {
    58  			sb.WriteString("长期")
    59  		} else {
    60  			sb.WriteString(strconv.Itoa(m.FileInfoTTL))
    61  			sb.WriteByte('s')
    62  		}
    63  		u, err := mediaURL(m.FileInfo)
    64  		if err == nil {
    65  			sb.WriteString(", URL: ")
    66  			sb.WriteString(u)
    67  		}
    68  		return sb.String()
    69  	}
    70  	if m.SeqInChannel != "" {
    71  		sb.WriteByte('[')
    72  		sb.WriteString(m.SeqInChannel)
    73  		sb.WriteByte(']')
    74  	}
    75  	sb.WriteString(m.ID)
    76  	sb.WriteString(" ギルド: ")
    77  	sb.WriteString(m.GuildID)
    78  	if m.SrcGuildID != "" {
    79  		sb.WriteString(", 元ギルド: ")
    80  		sb.WriteString(m.SrcGuildID)
    81  	}
    82  	sb.WriteString(", 频道: ")
    83  	sb.WriteString(m.ChannelID)
    84  	if m.Author != nil {
    85  		sb.WriteString(", 用户: ")
    86  		sb.WriteString(m.Author.Username)
    87  		sb.WriteByte('(')
    88  		sb.WriteString(m.Author.ID)
    89  		sb.WriteByte(')')
    90  		if m.Author.Bot {
    91  			sb.WriteString("(机器人)")
    92  		}
    93  	} else {
    94  		sb.WriteString(", 用户: 未知")
    95  	}
    96  	if m.Content == "" {
    97  		sb.WriteString(", 无文本")
    98  	} else {
    99  		sb.WriteString(", 文本: ")
   100  		if m.MentionEveryone {
   101  			sb.WriteString("[@全体]")
   102  		}
   103  		sb.WriteString(m.Content)
   104  	}
   105  	if len(m.Attachments) > 0 {
   106  		sb.WriteString(", 附加: ")
   107  		for _, a := range m.Attachments {
   108  			sb.WriteString("<ID:")
   109  			sb.WriteString(a.ID)
   110  			sb.WriteString(",URL:")
   111  			sb.WriteString(a.URL)
   112  			sb.WriteByte('>')
   113  		}
   114  	}
   115  	if len(m.Embeds) > 0 {
   116  		for _, e := range m.Embeds {
   117  			sb.WriteString(", 嵌入: <标题:")
   118  			sb.WriteString(e.Title)
   119  			sb.WriteString(",提示:")
   120  			sb.WriteString(e.Prompt)
   121  			sb.WriteByte('>')
   122  		}
   123  	}
   124  	if m.Ark != nil {
   125  		sb.WriteString(", 模版: ")
   126  		sb.WriteString(strconv.Itoa(m.Ark.TemplateID))
   127  	}
   128  	if m.MessageReference != nil {
   129  		sb.WriteString(", 回复: ")
   130  		sb.WriteString(m.MessageReference.MessageID)
   131  	}
   132  	return sb.String()
   133  }
   134  
   135  // MessageEmbed https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#messageembed
   136  type MessageEmbed struct {
   137  	Title     string                 `json:"title"`
   138  	Prompt    string                 `json:"prompt"`
   139  	Thumbnail *MessageEmbedThumbnail `json:"thumbnail"`
   140  	Fields    []MessageEmbedField    `json:"fields"`
   141  }
   142  
   143  // MessageEmbedThumbnail https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#messageembedthumbnail
   144  type MessageEmbedThumbnail struct {
   145  	URL string `json:"url"`
   146  }
   147  
   148  // MessageEmbedField https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#messageembedfield
   149  type MessageEmbedField struct {
   150  	Name string `json:"name"`
   151  }
   152  
   153  // MessageAttachment https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#messageattachment
   154  type MessageAttachment struct {
   155  	ContentType string `json:"content_type,omitempty"`
   156  	Filename    string `json:"filename,omitempty"`
   157  	Height      int    `json:"height,omitempty"`
   158  	ID          string `json:"id,omitempty"`
   159  	Size        int    `json:"size,omitempty"`
   160  	URL         string `json:"url,omitempty"`
   161  	Width       int    `json:"width,omitempty"`
   162  }
   163  
   164  // MessageArk https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#messageark
   165  type MessageArk struct {
   166  	TemplateID int            `json:"template_id"`
   167  	KV         []MessageArkKV `json:"kv"`
   168  }
   169  
   170  // MessageArkKV https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#messagearkkv
   171  type MessageArkKV struct {
   172  	Key   string          `json:"key"`
   173  	Value string          `json:"value"`
   174  	Obj   []MessageArkObj `json:"obj"`
   175  }
   176  
   177  // MessageArkObj https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#messagearkobj
   178  type MessageArkObj struct {
   179  	ObjKV []MessageArkObjKV `json:"obj_kv"`
   180  }
   181  
   182  // MessageArkObjKV https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#messagearkobjkv
   183  type MessageArkObjKV struct {
   184  	Key   string `json:"key"`
   185  	Value string `json:"value"`
   186  }
   187  
   188  // MessageReference https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#messagereference
   189  type MessageReference struct {
   190  	MessageID             string `json:"message_id"`
   191  	IgnoreGetMessageError bool   `json:"ignore_get_message_error"`
   192  }
   193  
   194  // MessageDelete https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#messagedelete
   195  type MessageDelete struct {
   196  	Message *Message `json:"message"`
   197  	OpUser  *User    `json:"op_user"`
   198  }
   199  
   200  func (mdl *MessageDelete) String() string {
   201  	sb := strings.Builder{}
   202  	sb.WriteString("用户ID ")
   203  	sb.WriteString(mdl.OpUser.ID)
   204  	sb.WriteString(" 删除了消息: ")
   205  	sb.WriteString(mdl.Message.ID)
   206  	sb.WriteString(" ギルド: ")
   207  	sb.WriteString(mdl.Message.GuildID)
   208  	if mdl.Message.SrcGuildID != "" {
   209  		sb.WriteString(", 元ギルド: ")
   210  		sb.WriteString(mdl.Message.SrcGuildID)
   211  	}
   212  	sb.WriteString(", 频道: ")
   213  	sb.WriteString(mdl.Message.ChannelID)
   214  	if mdl.Message.Member.User != nil {
   215  		sb.WriteString(", 用户: ")
   216  		sb.WriteString(mdl.Message.Member.User.Username)
   217  		sb.WriteByte('(')
   218  		sb.WriteString(mdl.Message.Member.User.ID)
   219  		sb.WriteByte(')')
   220  		if mdl.Message.Member.User.Bot {
   221  			sb.WriteString("(机器人)")
   222  		}
   223  	} else {
   224  		sb.WriteString(", 用户: 未知")
   225  	}
   226  	return sb.String()
   227  }
   228  
   229  // MessageAudited 消息审核对象
   230  //
   231  // https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#%E6%B6%88%E6%81%AF%E5%AE%A1%E6%A0%B8%E5%AF%B9%E8%B1%A1-messageaudited
   232  type MessageAudited struct {
   233  	AuditID    string    `json:"audit_id"`
   234  	AuditTime  time.Time `json:"audit_time"`
   235  	ChannelID  string    `json:"channel_id"`
   236  	CreateTime time.Time `json:"create_time"`
   237  	GuildID    string    `json:"guild_id"`
   238  	MessageID  string    `json:"message_id"`
   239  }
   240  
   241  // GetMessageFromChannel 获取子频道 channel_id 下的消息 message_id 的详情
   242  //
   243  // https://bot.q.qq.com/wiki/develop/api/openapi/message/get_message_of_id.html
   244  func (bot *Bot) GetMessageFromChannel(messageid, channelid string) (*Message, error) {
   245  	return bot.getOpenAPIofMessage("/channels/" + channelid + "/messages/" + messageid)
   246  }
   247  
   248  // MessagePost 发送消息所需参数
   249  //
   250  // https://bot.q.qq.com/wiki/develop/api/openapi/message/post_messages.html#%E9%80%9A%E7%94%A8%E5%8F%82%E6%95%B0
   251  type MessagePost struct {
   252  	// https://bot.q.qq.com/wiki/develop/api-231017/server-inter/message/send-receive/send.html
   253  	Type             MessageType       `json:"msg_type"`
   254  	Seq              int               `json:"msg_seq,omitempty"` // 回复消息的序号,与 msg_id 联合使用,避免相同消息id回复重复发送,不填默认是1。相同的 msg_id + msg_seq 重复发送会失败。
   255  	Content          string            `json:"content,omitempty"`
   256  	Embed            *MessageEmbed     `json:"embed,omitempty"` // https://bot.q.qq.com/wiki/develop/api/openapi/message/template/embed_message.html
   257  	Ark              *MessageArk       `json:"ark,omitempty"`   // https://bot.q.qq.com/wiki/develop/api/openapi/message/message_template.html
   258  	MessageReference *MessageReference `json:"message_reference,omitempty"`
   259  	Image            string            `json:"image,omitempty"`
   260  	ImageFile        string            `json:"-"` // ImageFile 为图片路径 file:/// or base64:// or base16384:// , 与 Image, ImageBytes 参数二选一, 优先 ImageBytes
   261  	ImageBytes       []byte            `json:"-"` // ImageBytes 图片数据
   262  	ReplyMessageID   string            `json:"msg_id,omitempty"`
   263  	ReplyEventID     string            `json:"event_id,omitempty"`
   264  	Markdown         *MessageMarkdown  `json:"markdown,omitempty"`
   265  	KeyBoard         *MessageKeyboard  `json:"keyboard,omitempty"`
   266  	Media            *MessageMedia     `json:"media,omitempty"`
   267  }
   268  
   269  func (mp *MessagePost) String() string {
   270  	sb := strings.Builder{}
   271  	if mp.Seq > 0 {
   272  		sb.WriteString("[v2:")
   273  		sb.WriteString(mp.Type.String())
   274  		sb.WriteString("#")
   275  		sb.WriteString(strconv.Itoa(mp.Seq))
   276  		sb.WriteString("]")
   277  	}
   278  	if mp.Content == "" {
   279  		sb.WriteString("无文本")
   280  	} else {
   281  		sb.WriteString("文本: ")
   282  		sb.WriteString(mp.Content)
   283  	}
   284  	if mp.ReplyMessageID != "" {
   285  		sb.WriteString(", 回应消息: ")
   286  		sb.WriteString(mp.ReplyMessageID)
   287  	}
   288  	if mp.ReplyEventID != "" {
   289  		sb.WriteString(", 回应事件: ")
   290  		sb.WriteString(mp.ReplyEventID)
   291  	}
   292  	if mp.Embed != nil {
   293  		sb.WriteString(", 嵌入: <标题:")
   294  		sb.WriteString(mp.Embed.Title)
   295  		sb.WriteString(",提示:")
   296  		sb.WriteString(mp.Embed.Prompt)
   297  		sb.WriteByte('>')
   298  	}
   299  	if mp.Ark != nil {
   300  		sb.WriteString(", 模版: ")
   301  		sb.WriteString(strconv.Itoa(mp.Ark.TemplateID))
   302  	}
   303  	if mp.MessageReference != nil {
   304  		sb.WriteString(", 回复: ")
   305  		sb.WriteString(mp.MessageReference.MessageID)
   306  	}
   307  	if mp.Image != "" {
   308  		sb.WriteString(", 图片URL: ")
   309  		sb.WriteString(mp.Image)
   310  	}
   311  	if mp.ImageFile != "" {
   312  		sb.WriteString(", 图片内容: ")
   313  		x := mp.ImageFile
   314  		if len(x) > 64 {
   315  			x = x[:64] + "..."
   316  		}
   317  		sb.WriteString(x)
   318  	}
   319  	if len(mp.ImageBytes) > 0 {
   320  		sb.WriteString(", 图片大小: ")
   321  		sb.WriteString(strconv.Itoa(len(mp.ImageBytes)))
   322  	}
   323  	if mp.Markdown != nil {
   324  		sb.WriteString(", MD模版: ")
   325  		sb.WriteString(strconv.Itoa(mp.Markdown.TemplateID))
   326  	}
   327  	if mp.KeyBoard != nil {
   328  		sb.WriteString(", KB模版: ")
   329  		sb.WriteString(mp.KeyBoard.ID)
   330  	}
   331  	if mp.Media != nil {
   332  		sb.WriteString(", 富媒体: ")
   333  		u, err := mediaURL(mp.Media.FileInfo)
   334  		if err == nil {
   335  			sb.WriteString(u)
   336  		} else {
   337  			sb.WriteString(mp.Media.FileInfo)
   338  		}
   339  	}
   340  	return sb.String()
   341  }
   342  
   343  func (bot *Bot) postMessageTo(ep string, content *MessagePost) (*Message, error) {
   344  	if len(content.ImageBytes) == 0 && content.ImageFile == "" {
   345  		return bot.postOpenAPIofMessage(ep, "", WriteBodyFromJSON(content))
   346  	}
   347  	x := reflect.ValueOf(content).Elem()
   348  	t := x.Type()
   349  	msg := []any{}
   350  	for i := 0; i < x.NumField(); i++ {
   351  		xi := x.Field(i)
   352  		if xi.IsZero() {
   353  			continue
   354  		}
   355  		tag, _, _ := strings.Cut(t.Field(i).Tag.Get("json"), ",")
   356  		if tag == "-" {
   357  			tag = "file_image"
   358  		}
   359  		msg = append(msg, tag)
   360  		if xi.Kind() == reflect.Pointer && xi.Elem().Kind() == reflect.Struct {
   361  			data, err := json.Marshal(xi.Interface())
   362  			if err != nil {
   363  				return nil, err
   364  			}
   365  			msg = append(msg, data)
   366  			continue
   367  		}
   368  		msg = append(msg, xi.Interface()) // []byte or string
   369  	}
   370  	if len(msg) < 2 {
   371  		return nil, ErrEmptyMessagePost
   372  	}
   373  	body, contenttype, err := WriteBodyByMultipartFormData(msg...)
   374  	if err != nil {
   375  		return nil, errors.Wrap(err, getThisFuncName())
   376  	}
   377  	m, err := bot.postOpenAPIofMessage(ep, contenttype, body)
   378  	if err != nil {
   379  		return nil, errors.Wrap(err, getThisFuncName())
   380  	}
   381  	logrus.Infoln(getLogHeader(), "=> 消息结果:", m)
   382  	return m, nil
   383  }
   384  
   385  // PostMessageToChannel 向 channel_id 指定的子频道发送消息
   386  //
   387  // https://bot.q.qq.com/wiki/develop/api/openapi/message/post_messages.html
   388  func (bot *Bot) PostMessageToChannel(id string, content *MessagePost) (*Message, error) {
   389  	logrus.Infoln(getLogHeader(), "<= [公]频道:", id+",", content)
   390  	return bot.postMessageTo("/channels/"+id+"/messages", content)
   391  }
   392  
   393  // DeleteMessageInChannel 回子频道 channel_id 下的消息 message_id
   394  //
   395  // https://bot.q.qq.com/wiki/develop/api/openapi/message/delete_message.html
   396  func (bot *Bot) DeleteMessageInChannel(channelid, messageid string, hidetip bool) error {
   397  	logrus.Infoln(getLogHeader(), "<x 频道:", channelid+", 消息:", messageid)
   398  	return bot.DeleteOpenAPI(WriteHTTPQueryIfNotNil("/channels/"+channelid+"/messages/"+messageid,
   399  		"hidetip", hidetip,
   400  	), "", nil)
   401  }
   402  
   403  // MessageSetting 频道消息频率设置对象
   404  //
   405  // https://bot.q.qq.com/wiki/develop/api/openapi/setting/model.html
   406  type MessageSetting struct {
   407  	DisableCreateDm   bool     `json:"disable_create_dm"`
   408  	DisablePushMsg    bool     `json:"disable_push_msg"`
   409  	ChannelIDs        []string `json:"channel_ids"`
   410  	ChannelPushMaxNum uint32   `json:"channel_push_max_num"`
   411  }
   412  
   413  // GetGuildMessageSetting 获取机器人在频道 guild_id 内的消息频率设置
   414  //
   415  // https://bot.q.qq.com/wiki/develop/api/openapi/setting/message_setting.html
   416  func (bot *Bot) GetGuildMessageSetting(id string) (*MessageSetting, error) {
   417  	return bot.getOpenAPIofMessageSetting("/guilds/" + id + "/message/setting")
   418  }