feat: send recode adjust
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
package unified
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"message-nest/service/send_message_service/unified/channels"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// 重导出channels包的类型,方便外部使用
|
||||
type (
|
||||
Channel = channels.Channel
|
||||
UnifiedMessageContent = channels.UnifiedMessageContent
|
||||
)
|
||||
|
||||
// 重导出常量
|
||||
const (
|
||||
FormatTypeText = channels.FormatTypeText
|
||||
FormatTypeHTML = channels.FormatTypeHTML
|
||||
FormatTypeMarkdown = channels.FormatTypeMarkdown
|
||||
|
||||
MessageTypeEmail = channels.MessageTypeEmail
|
||||
MessageTypeDtalk = channels.MessageTypeDtalk
|
||||
MessageTypeQyWeiXin = channels.MessageTypeQyWeiXin
|
||||
MessageTypeCustom = channels.MessageTypeCustom
|
||||
MessageTypeWeChatOFAccount = channels.MessageTypeWeChatOFAccount
|
||||
MessageTypeMessageNest = channels.MessageTypeMessageNest
|
||||
)
|
||||
|
||||
// ChannelRegistry 渠道注册表
|
||||
type ChannelRegistry struct {
|
||||
channels map[string]Channel
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewChannelRegistry 创建渠道注册表
|
||||
func NewChannelRegistry() *ChannelRegistry {
|
||||
return &ChannelRegistry{
|
||||
channels: make(map[string]Channel),
|
||||
}
|
||||
}
|
||||
|
||||
// Register 注册渠道
|
||||
func (r *ChannelRegistry) Register(channel Channel) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
r.channels[channel.GetType()] = channel
|
||||
}
|
||||
|
||||
// GetChannel 获取渠道
|
||||
func (r *ChannelRegistry) GetChannel(channelType string) (Channel, bool) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
channel, ok := r.channels[channelType]
|
||||
return channel, ok
|
||||
}
|
||||
|
||||
// GetAllChannels 获取所有渠道
|
||||
func (r *ChannelRegistry) GetAllChannels() map[string]Channel {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
result := make(map[string]Channel, len(r.channels))
|
||||
for k, v := range r.channels {
|
||||
result[k] = v
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ListChannels 列出所有渠道
|
||||
func (r *ChannelRegistry) ListChannels() []string {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
types := make([]string, 0, len(r.channels))
|
||||
for t := range r.channels {
|
||||
types = append(types, t)
|
||||
}
|
||||
return types
|
||||
}
|
||||
|
||||
var (
|
||||
globalChannelRegistry *ChannelRegistry
|
||||
channelRegistryOnce sync.Once
|
||||
)
|
||||
|
||||
// GetGlobalChannelRegistry 获取全局渠道注册表(单例)
|
||||
func GetGlobalChannelRegistry() *ChannelRegistry {
|
||||
channelRegistryOnce.Do(func() {
|
||||
globalChannelRegistry = NewChannelRegistry()
|
||||
|
||||
// 注册所有渠道
|
||||
globalChannelRegistry.Register(channels.NewEmailChannel())
|
||||
globalChannelRegistry.Register(channels.NewDtalkChannel())
|
||||
globalChannelRegistry.Register(channels.NewQyWeiXinChannel())
|
||||
globalChannelRegistry.Register(channels.NewCustomChannel())
|
||||
globalChannelRegistry.Register(channels.NewWeChatOFAccountChannel())
|
||||
globalChannelRegistry.Register(channels.NewMessageNestChannel())
|
||||
})
|
||||
return globalChannelRegistry
|
||||
}
|
||||
|
||||
// GetChannelInfo 获取渠道信息(用于调试和文档)
|
||||
func GetChannelInfo(channelType string) (string, error) {
|
||||
registry := GetGlobalChannelRegistry()
|
||||
channel, ok := registry.GetChannel(channelType)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("未知的渠道类型: %s", channelType)
|
||||
}
|
||||
|
||||
info := fmt.Sprintf("渠道: %s\n支持格式: %v\n",
|
||||
channel.GetType(),
|
||||
channel.GetSupportedFormats())
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// ListAllChannels 列出所有渠道信息
|
||||
func ListAllChannels() string {
|
||||
registry := GetGlobalChannelRegistry()
|
||||
allChannels := registry.GetAllChannels()
|
||||
|
||||
result := "已注册的渠道:\n"
|
||||
for channelType, channel := range allChannels {
|
||||
result += fmt.Sprintf(" - %s: %v\n", channelType, channel.GetSupportedFormats())
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package channels
|
||||
|
||||
import "message-nest/models"
|
||||
|
||||
type Channel interface {
|
||||
GetType() string
|
||||
GetSupportedFormats() []string
|
||||
FormatContent(content *UnifiedMessageContent) (formatType string, formattedContent string, err error)
|
||||
SendUnified(msgObj interface{}, ins models.SendTasksIns, content *UnifiedMessageContent) (string, string)
|
||||
}
|
||||
|
||||
type BaseChannel struct {
|
||||
channelType string
|
||||
supportedFormats []string
|
||||
}
|
||||
|
||||
func NewBaseChannel(channelType string, supportedFormats []string) *BaseChannel {
|
||||
return &BaseChannel{channelType: channelType, supportedFormats: supportedFormats}
|
||||
}
|
||||
|
||||
func (c *BaseChannel) GetType() string { return c.channelType }
|
||||
func (c *BaseChannel) GetSupportedFormats() []string { return c.supportedFormats }
|
||||
|
||||
func (c *BaseChannel) FormatContent(content *UnifiedMessageContent) (string, string, error) {
|
||||
for _, formatType := range c.supportedFormats {
|
||||
switch formatType {
|
||||
case FormatTypeMarkdown:
|
||||
if content.HasMarkdown() {
|
||||
return FormatTypeMarkdown, content.Markdown, nil
|
||||
}
|
||||
case FormatTypeHTML:
|
||||
if content.HasHTML() {
|
||||
return FormatTypeHTML, content.HTML, nil
|
||||
}
|
||||
case FormatTypeText:
|
||||
if content.HasText() {
|
||||
return FormatTypeText, content.Text, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if content.HasText() {
|
||||
return FormatTypeText, content.Text, nil
|
||||
}
|
||||
return FormatTypeText, "", nil
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package channels
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"message-nest/models"
|
||||
"message-nest/pkg/message"
|
||||
"message-nest/service/send_way_service"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type CustomChannel struct{ *BaseChannel }
|
||||
|
||||
func NewCustomChannel() *CustomChannel {
|
||||
return &CustomChannel{BaseChannel: NewBaseChannel(MessageTypeCustom, []string{FormatTypeText})}
|
||||
}
|
||||
|
||||
func (c *CustomChannel) FormatContent(content *UnifiedMessageContent) (string, string, error) {
|
||||
if content.HasText() {
|
||||
return FormatTypeText, content.Text, nil
|
||||
}
|
||||
if content.HasMarkdown() {
|
||||
return FormatTypeText, markdownToText(content.Markdown), nil
|
||||
}
|
||||
if content.HasHTML() {
|
||||
return FormatTypeText, htmlToText(content.HTML), nil
|
||||
}
|
||||
return FormatTypeText, "", nil
|
||||
}
|
||||
|
||||
func (c *CustomChannel) SendUnified(msgObj interface{}, ins models.SendTasksIns, content *UnifiedMessageContent) (string, string) {
|
||||
auth, ok := msgObj.(send_way_service.WayDetailCustom)
|
||||
if !ok {
|
||||
return "", "类型转换失败"
|
||||
}
|
||||
_, formattedContent, err := c.FormatContent(content)
|
||||
if err != nil {
|
||||
return "", err.Error()
|
||||
}
|
||||
cli := message.CustomWebhook{}
|
||||
data, _ := json.Marshal(formattedContent)
|
||||
dataStr := strings.Trim(string(data), "\"")
|
||||
bodyStr := strings.Replace(auth.Body, "TEXT", dataStr, -1)
|
||||
res, err := cli.Request(auth.Webhook, bodyStr)
|
||||
var errMsg string
|
||||
if err != nil {
|
||||
errMsg = fmt.Sprintf("发送失败:%s", err.Error())
|
||||
}
|
||||
return string(res), errMsg
|
||||
}
|
||||
|
||||
func markdownToText(md string) string {
|
||||
t := md
|
||||
t = regexp.MustCompile(`#{1,6}\s+`).ReplaceAllString(t, "")
|
||||
t = regexp.MustCompile(`\*\*([^*]+)\*\*`).ReplaceAllString(t, "$1")
|
||||
t = regexp.MustCompile(`\*([^*]+)\*`).ReplaceAllString(t, "$1")
|
||||
t = regexp.MustCompile(`__([^_]+)__`).ReplaceAllString(t, "$1")
|
||||
t = regexp.MustCompile(`_([^_]+)_`).ReplaceAllString(t, "$1")
|
||||
t = regexp.MustCompile(`\[([^\]]+)\]\([^)]+\)`).ReplaceAllString(t, "$1")
|
||||
t = regexp.MustCompile("```[^`]*```").ReplaceAllString(t, "")
|
||||
t = regexp.MustCompile("`([^`]+)`").ReplaceAllString(t, "$1")
|
||||
t = regexp.MustCompile(`(?m)^>\s+`).ReplaceAllString(t, "")
|
||||
t = regexp.MustCompile(`(?m)^[\*\-\+]\s+`).ReplaceAllString(t, "")
|
||||
t = regexp.MustCompile(`(?m)^\d+\.\s+`).ReplaceAllString(t, "")
|
||||
return strings.TrimSpace(t)
|
||||
}
|
||||
|
||||
func htmlToText(html string) string {
|
||||
t := html
|
||||
t = regexp.MustCompile(`(?i)<script[^>]*>.*?</script>`).ReplaceAllString(t, "")
|
||||
t = regexp.MustCompile(`(?i)<style[^>]*>.*?</style>`).ReplaceAllString(t, "")
|
||||
t = regexp.MustCompile(`(?i)<br\s*/?>`).ReplaceAllString(t, "\n")
|
||||
t = regexp.MustCompile(`(?i)</p>`).ReplaceAllString(t, "\n")
|
||||
t = regexp.MustCompile(`<[^>]+>`).ReplaceAllString(t, "")
|
||||
t = strings.ReplaceAll(t, " ", " ")
|
||||
t = strings.ReplaceAll(t, "<", "<")
|
||||
t = strings.ReplaceAll(t, ">", ">")
|
||||
t = strings.ReplaceAll(t, "&", "&")
|
||||
t = strings.ReplaceAll(t, """, "\"")
|
||||
t = regexp.MustCompile(`\n{3,}`).ReplaceAllString(t, "\n\n")
|
||||
return strings.TrimSpace(t)
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package channels
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"message-nest/models"
|
||||
"message-nest/pkg/message"
|
||||
"message-nest/service/send_ins_service"
|
||||
"message-nest/service/send_way_service"
|
||||
)
|
||||
|
||||
type DtalkChannel struct{ *BaseChannel }
|
||||
|
||||
func NewDtalkChannel() *DtalkChannel {
|
||||
return &DtalkChannel{BaseChannel: NewBaseChannel(MessageTypeDtalk, []string{FormatTypeMarkdown, FormatTypeText})}
|
||||
}
|
||||
|
||||
func (c *DtalkChannel) SendUnified(msgObj interface{}, ins models.SendTasksIns, content *UnifiedMessageContent) (string, string) {
|
||||
auth, ok := msgObj.(send_way_service.WayDetailDTalk)
|
||||
if !ok {
|
||||
return "", "类型转换失败"
|
||||
}
|
||||
insService := send_ins_service.SendTaskInsService{}
|
||||
errStr, configInterface := insService.ValidateDiffIns(ins)
|
||||
if errStr != "" {
|
||||
return errStr, ""
|
||||
}
|
||||
_, ok = configInterface.(models.InsDtalkConfig)
|
||||
if !ok {
|
||||
return "钉钉config校验失败", ""
|
||||
}
|
||||
contentType, formattedContent, err := c.FormatContent(content)
|
||||
if err != nil {
|
||||
return "", err.Error()
|
||||
}
|
||||
atMobiles := content.GetAtMobiles()
|
||||
if content.IsAtAll() {
|
||||
atMobiles = append(atMobiles, "all")
|
||||
}
|
||||
cli := message.Dtalk{AccessToken: auth.AccessToken, Secret: auth.Secret}
|
||||
var res []byte
|
||||
var errMsg string
|
||||
if contentType == FormatTypeText {
|
||||
res, err = cli.SendMessageText(formattedContent, atMobiles...)
|
||||
if err != nil {
|
||||
errMsg = fmt.Sprintf("发送失败:%s", err.Error())
|
||||
}
|
||||
} else if contentType == FormatTypeMarkdown {
|
||||
res, err = cli.SendMessageMarkdown(content.Title, formattedContent, atMobiles...)
|
||||
if err != nil {
|
||||
errMsg = fmt.Sprintf("发送失败:%s", err.Error())
|
||||
}
|
||||
} else {
|
||||
errMsg = fmt.Sprintf("未知的钉钉发送内容类型:%s", contentType)
|
||||
}
|
||||
return string(res), errMsg
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package channels
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"message-nest/models"
|
||||
"message-nest/pkg/message"
|
||||
"message-nest/service/send_ins_service"
|
||||
"message-nest/service/send_way_service"
|
||||
)
|
||||
|
||||
type EmailChannel struct{ *BaseChannel }
|
||||
|
||||
func NewEmailChannel() *EmailChannel {
|
||||
return &EmailChannel{BaseChannel: NewBaseChannel(MessageTypeEmail, []string{FormatTypeHTML, FormatTypeText})}
|
||||
}
|
||||
|
||||
func (c *EmailChannel) SendUnified(msgObj interface{}, ins models.SendTasksIns, content *UnifiedMessageContent) (string, string) {
|
||||
auth, ok := msgObj.(send_way_service.WayDetailEmail)
|
||||
if !ok {
|
||||
return "", "类型转换失败"
|
||||
}
|
||||
insService := send_ins_service.SendTaskInsService{}
|
||||
errStr, configInterface := insService.ValidateDiffIns(ins)
|
||||
if errStr != "" {
|
||||
return "", errStr
|
||||
}
|
||||
config, ok := configInterface.(models.InsEmailConfig)
|
||||
if !ok {
|
||||
return "", "邮箱config校验失败"
|
||||
}
|
||||
contentType, formattedContent, err := c.FormatContent(content)
|
||||
if err != nil {
|
||||
return "", err.Error()
|
||||
}
|
||||
var emailer message.EmailMessage
|
||||
emailer.Init(auth.Server, auth.Port, auth.Account, auth.Passwd)
|
||||
var errMsg string
|
||||
if contentType == FormatTypeText {
|
||||
errMsg = emailer.SendTextMessage(config.ToAccount, content.Title, formattedContent)
|
||||
} else if contentType == FormatTypeHTML {
|
||||
errMsg = emailer.SendHtmlMessage(config.ToAccount, content.Title, formattedContent)
|
||||
} else {
|
||||
errMsg = fmt.Sprintf("未知的邮件发送内容类型:%s", contentType)
|
||||
}
|
||||
return "", errMsg
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package channels
|
||||
|
||||
import (
|
||||
"message-nest/models"
|
||||
"message-nest/service/hosted_message_service"
|
||||
"message-nest/service/send_way_service"
|
||||
)
|
||||
|
||||
type MessageNestChannel struct{ *BaseChannel }
|
||||
|
||||
func NewMessageNestChannel() *MessageNestChannel {
|
||||
return &MessageNestChannel{BaseChannel: NewBaseChannel(MessageTypeMessageNest, []string{FormatTypeMarkdown, FormatTypeHTML, FormatTypeText})}
|
||||
}
|
||||
|
||||
func (c *MessageNestChannel) SendUnified(msgObj interface{}, ins models.SendTasksIns, content *UnifiedMessageContent) (string, string) {
|
||||
_, ok := msgObj.(send_way_service.MessageNest)
|
||||
if !ok {
|
||||
return "", "类型转换失败"
|
||||
}
|
||||
contentType, formattedContent, err := c.FormatContent(content)
|
||||
if err != nil {
|
||||
return "", err.Error()
|
||||
}
|
||||
messageService := hosted_message_service.HostMessageService{
|
||||
Title: content.Title,
|
||||
Content: formattedContent,
|
||||
Type: contentType,
|
||||
}
|
||||
err = messageService.Add()
|
||||
var res, errMsg string
|
||||
if err != nil {
|
||||
errMsg = err.Error()
|
||||
res = "托管消息创建失败!"
|
||||
} else {
|
||||
res = "托管消息创建成功!"
|
||||
}
|
||||
return res, errMsg
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package channels
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"message-nest/models"
|
||||
"message-nest/pkg/message"
|
||||
"message-nest/service/send_ins_service"
|
||||
"message-nest/service/send_way_service"
|
||||
)
|
||||
|
||||
type QyWeiXinChannel struct{ *BaseChannel }
|
||||
|
||||
func NewQyWeiXinChannel() *QyWeiXinChannel {
|
||||
return &QyWeiXinChannel{BaseChannel: NewBaseChannel(MessageTypeQyWeiXin, []string{FormatTypeMarkdown, FormatTypeText})}
|
||||
}
|
||||
|
||||
func (c *QyWeiXinChannel) SendUnified(msgObj interface{}, ins models.SendTasksIns, content *UnifiedMessageContent) (string, string) {
|
||||
auth, ok := msgObj.(send_way_service.WayDetailQyWeiXin)
|
||||
if !ok {
|
||||
return "", "类型转换失败"
|
||||
}
|
||||
insService := send_ins_service.SendTaskInsService{}
|
||||
errStr, configInterface := insService.ValidateDiffIns(ins)
|
||||
if errStr != "" {
|
||||
return errStr, ""
|
||||
}
|
||||
_, ok = configInterface.(models.InsQyWeiXinConfig)
|
||||
if !ok {
|
||||
return "企业微信config校验失败", ""
|
||||
}
|
||||
contentType, formattedContent, err := c.FormatContent(content)
|
||||
if err != nil {
|
||||
return "", err.Error()
|
||||
}
|
||||
atList := []string{}
|
||||
atList = append(atList, content.GetAtUserIds()...)
|
||||
atList = append(atList, content.GetAtMobiles()...)
|
||||
if content.IsAtAll() {
|
||||
atList = append(atList, "@all")
|
||||
}
|
||||
cli := message.QyWeiXin{AccessToken: auth.AccessToken}
|
||||
var res []byte
|
||||
var errMsg string
|
||||
if contentType == FormatTypeText {
|
||||
res, err = cli.SendMessageText(formattedContent, atList...)
|
||||
if err != nil {
|
||||
errMsg = fmt.Sprintf("发送失败:%s", err.Error())
|
||||
}
|
||||
} else if contentType == FormatTypeMarkdown {
|
||||
res, err = cli.SendMessageMarkdown(content.Title, formattedContent, atList...)
|
||||
if err != nil {
|
||||
errMsg = fmt.Sprintf("发送失败:%s", err.Error())
|
||||
}
|
||||
} else {
|
||||
errMsg = fmt.Sprintf("未知的企业微信发送内容类型:%s", contentType)
|
||||
}
|
||||
return string(res), errMsg
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package channels
|
||||
|
||||
// 消息格式类型常量
|
||||
const (
|
||||
FormatTypeText = "text"
|
||||
FormatTypeHTML = "html"
|
||||
FormatTypeMarkdown = "markdown"
|
||||
)
|
||||
|
||||
// 消息类型常量
|
||||
const (
|
||||
MessageTypeEmail = "Email"
|
||||
MessageTypeDtalk = "Dtalk"
|
||||
MessageTypeQyWeiXin = "QyWeiXin"
|
||||
MessageTypeCustom = "Custom"
|
||||
MessageTypeWeChatOFAccount = "WeChatOFAccount"
|
||||
MessageTypeMessageNest = "MessageNest"
|
||||
)
|
||||
|
||||
// UnifiedMessageContent 统一的消息内容结构
|
||||
type UnifiedMessageContent struct {
|
||||
Title string
|
||||
URL string
|
||||
Text string
|
||||
HTML string
|
||||
Markdown string
|
||||
AtMobiles []string
|
||||
AtUserIds []string
|
||||
AtAll bool
|
||||
Summary string
|
||||
ImageURL string
|
||||
Extra map[string]interface{}
|
||||
}
|
||||
|
||||
func (m *UnifiedMessageContent) HasText() bool { return m.Text != "" }
|
||||
func (m *UnifiedMessageContent) HasHTML() bool { return m.HTML != "" }
|
||||
func (m *UnifiedMessageContent) HasMarkdown() bool { return m.Markdown != "" }
|
||||
|
||||
func (m *UnifiedMessageContent) GetAtMobiles() []string {
|
||||
if m.AtMobiles == nil {
|
||||
return []string{}
|
||||
}
|
||||
return m.AtMobiles
|
||||
}
|
||||
|
||||
func (m *UnifiedMessageContent) GetAtUserIds() []string {
|
||||
if m.AtUserIds == nil {
|
||||
return []string{}
|
||||
}
|
||||
return m.AtUserIds
|
||||
}
|
||||
|
||||
func (m *UnifiedMessageContent) IsAtAll() bool { return m.AtAll }
|
||||
@@ -0,0 +1,48 @@
|
||||
package channels
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"message-nest/models"
|
||||
"message-nest/pkg/message"
|
||||
"message-nest/service/send_ins_service"
|
||||
"message-nest/service/send_way_service"
|
||||
)
|
||||
|
||||
type WeChatOFAccountChannel struct{ *BaseChannel }
|
||||
|
||||
func NewWeChatOFAccountChannel() *WeChatOFAccountChannel {
|
||||
return &WeChatOFAccountChannel{BaseChannel: NewBaseChannel(MessageTypeWeChatOFAccount, []string{FormatTypeText})}
|
||||
}
|
||||
|
||||
func (c *WeChatOFAccountChannel) SendUnified(msgObj interface{}, ins models.SendTasksIns, content *UnifiedMessageContent) (string, string) {
|
||||
auth, ok := msgObj.(send_way_service.WeChatOFAccount)
|
||||
if !ok {
|
||||
return "", "类型转换失败"
|
||||
}
|
||||
insService := send_ins_service.SendTaskInsService{}
|
||||
errStr, configInterface := insService.ValidateDiffIns(ins)
|
||||
if errStr != "" {
|
||||
return errStr, ""
|
||||
}
|
||||
config, ok := configInterface.(models.InsWeChatAccountConfig)
|
||||
if !ok {
|
||||
return "微信公众号模板消息config校验失败", ""
|
||||
}
|
||||
_, formattedContent, err := c.FormatContent(content)
|
||||
if err != nil {
|
||||
return "", err.Error()
|
||||
}
|
||||
cli := message.WeChatOFAccount{
|
||||
AppID: auth.AppID,
|
||||
AppSecret: auth.APPSecret,
|
||||
TemplateID: auth.TempID,
|
||||
ToUser: config.ToAccount,
|
||||
URL: content.URL,
|
||||
}
|
||||
res, err := cli.Send(content.Title, formattedContent)
|
||||
var errMsg string
|
||||
if err != nil {
|
||||
errMsg = fmt.Sprintf("发送失败:%s", err.Error())
|
||||
}
|
||||
return res, errMsg
|
||||
}
|
||||
Reference in New Issue
Block a user