package hook import ( "bytes" "context" "fmt" "html" "path/filepath" "strings" "time" "git.pcux.cn/xuans/Smail/dto/parsemail" "github.com/go-telegram/bot" "github.com/go-telegram/bot/models" "github.com/ydzydzydz/pmail_telegram_push/model" ) // escapeTelegramText 在完整字符和实体边界裁剪,再返回可安全嵌入 Telegram HTML 的文本。 func escapeTelegramText(text string, limit int) string { if limit < 3 { return "" } var result strings.Builder for _, char := range text { escaped := html.EscapeString(string(char)) if result.Len()+len(escaped) > limit-3 { return result.String() + "..." } result.WriteString(escaped) } return result.String() } func telegramAddresses(users []*parsemail.User) string { var result strings.Builder for _, user := range users { if user == nil || user.EmailAddress == "" { continue } remaining := 480 - result.Len() if remaining < 12 { break } result.WriteString("<" + escapeTelegramText(user.EmailAddress, remaining-9) + "> ") } return result.String() } const ( TELEGRAM_TEXT_MAX_SIZE = 4096 // TELEGRAM_TEXT_MAX_SIZE Telegram 文本最大长度 TELEGRAM_ATTACHMENT_MAX_COUNT = 10 // TELEGRAM_ATTACHMENT_MAX_COUNT Telegram 附件最大数量 TELEGRAM_ATTACHMENT_MAX_SIZE = 20 * 1024 * 1024 // TELEGRAM_ATTACHMENT_MAX_SIZE Telegram 附件最大大小 ) // getSubjectText 获取主题文本 func (h *PmailTelegramPushHook) getSubjectText(email *parsemail.Email) string { if len(email.Subject) <= 0 { return "" } return fmt.Sprintf("🔖 主题:%s\n", escapeTelegramText(email.Subject, 600)) } // getFromText 获取发件人文本 func (h *PmailTelegramPushHook) getFromText(email *parsemail.Email) string { if email.From == nil || len(email.From.EmailAddress) <= 0 { return "" } return fmt.Sprintf("📤 发件:<%s>\n", escapeTelegramText(email.From.EmailAddress, 256)) } // getToText 获取收件人文本 func (h *PmailTelegramPushHook) getToText(email *parsemail.Email) string { if len(email.To) <= 0 { return "" } return "📥 收件:" + telegramAddresses(email.To) + "\n" } // getCcText 获取抄送人文本 func (h *PmailTelegramPushHook) getCcText(email *parsemail.Email) string { if len(email.Cc) <= 0 { return "" } return "📋 抄送:" + telegramAddresses(email.Cc) + "\n" } // getBccText 获取密送人文本 func (h *PmailTelegramPushHook) getBccText(email *parsemail.Email) string { if len(email.Bcc) <= 0 { return "" } return "🕵️ 密送:" + telegramAddresses(email.Bcc) + "\n" } // getAttachmentsText 获取附件文本 func (h *PmailTelegramPushHook) getAttachmentsText(email *parsemail.Email) string { if len(email.Attachments) <= 0 { return "" } return fmt.Sprintf("📎 附件:%d 个\n", len(email.Attachments)) } // getContentText 获取邮件内容文本 func (h *PmailTelegramPushHook) getContentText(email *parsemail.Email, setting *model.TelegramPushSetting) string { if !setting.ShowContent { return "" } if len(email.Text) > 0 { return string(email.Text) } if len(email.HTML) > 0 { return removeHTMLTag(string(email.HTML)) } return "" } // getSpoilerText 获取spoiler文本 func (h *PmailTelegramPushHook) getSpoilerText(text string, setting *model.TelegramPushSetting) string { if !setting.SpoilerContent { return text } return fmt.Sprintf("%s", text) } // buildSendText 构建发送文本 func (h *PmailTelegramPushHook) buildSendText(email *parsemail.Email, setting *model.TelegramPushSetting) string { text := "📧 有新邮件\n" text += h.getSubjectText(email) text += h.getFromText(email) text += h.getToText(email) text += h.getCcText(email) text += h.getBccText(email) text += h.getAttachmentsText(email) // 按更保守的 UTF-8 字节预算分配正文,标签与实体不参与截断。 remaining := TELEGRAM_TEXT_MAX_SIZE - len(text) - 20 if setting.SpoilerContent { remaining -= len("") } content := escapeTelegramText(h.getContentText(email, setting), remaining) return removeExtraSpace(text + h.getSpoilerText(content, setting)) } // buildPamilLinkButton 创建邮件页面链接按钮 func (h *PmailTelegramPushHook) buildPamilLinkButton() *models.InlineKeyboardMarkup { domain := strings.TrimSpace(h.pluginConfig.WebDomain) if domain == "" { return nil } scheme := "https" if !h.pluginConfig.HTTPSEnabled { scheme = "http" } return &models.InlineKeyboardMarkup{ InlineKeyboard: [][]models.InlineKeyboardButton{{{ Text: "查收邮件", URL: scheme + "://" + domain, }}}, } } // sendText 发送文本消息 // sendText 发送文本消息 func (h *PmailTelegramPushHook) sendText(email *parsemail.Email, setting *model.TelegramPushSetting) (msg *models.Message, err error) { ctx, cancel := context.WithTimeout(context.Background(), time.Duration(h.pluginConfig.Timeout)*time.Second) defer cancel() parmas := &bot.SendMessageParams{ ChatID: setting.ChatID, Text: h.buildSendText(email, setting), ParseMode: models.ParseModeHTML, ReplyMarkup: h.buildPamilLinkButton(), LinkPreviewOptions: &models.LinkPreviewOptions{ IsDisabled: &setting.DisableLinkPreview, }, } return h.bot.SendMessage(ctx, parmas) } // sendNotification 发送通知消息 // 先发送文本消息,再发送附件消息 func (h *PmailTelegramPushHook) sendNotification(email *parsemail.Email, setting *model.TelegramPushSetting) (err error) { msg, err := h.sendText(email, setting) if err != nil { return err } return h.sendAttachmentsBatch(msg.ID, email, setting) } // sendAttachmentsBatch 批量发送附件消息 func (h *PmailTelegramPushHook) sendAttachmentsBatch(id int, email *parsemail.Email, setting *model.TelegramPushSetting) (err error) { if !setting.SendAttachments { return nil } ctx, cancel := context.WithTimeout(context.Background(), time.Duration(h.pluginConfig.Timeout)*time.Second) defer cancel() // 引用消息中包含附件关键字 params := &bot.SendMediaGroupParams{ ChatID: setting.ChatID, ReplyParameters: &models.ReplyParameters{ MessageID: id, Quote: fmt.Sprintf("📎 附件:%d 个", len(email.Attachments)), }, } // 批量发送附件, 每个批次最多 TELEGRAM_ATTACHMENT_MAX_COUNT 个附件 for i := 0; i < len(email.Attachments); i += TELEGRAM_ATTACHMENT_MAX_COUNT { end := min(len(email.Attachments), i+TELEGRAM_ATTACHMENT_MAX_COUNT) params.Media = nil batch := email.Attachments[i:end] for j, attachment := range batch { // 判断文件大小, 超过最大大小则跳过 if len(attachment.Content) > TELEGRAM_ATTACHMENT_MAX_SIZE { continue } // 构建 InputMediaDocument params.Media = append(params.Media, &models.InputMediaDocument{ Media: fmt.Sprintf("attach://%s", filepath.Base(attachment.Filename)), Caption: fmt.Sprintf("📎 附件 %d", i+j+1), MediaAttachment: bytes.NewReader(attachment.Content), }) } if len(params.Media) == 0 { continue } if _, err = h.bot.SendMediaGroup(ctx, params); err != nil { return err } // 每个批次发送后休息 1 秒, 避免触发速率限制 time.Sleep(time.Second) } return nil } func (h *PmailTelegramPushHook) sendTestMessage(setting *model.TelegramPushSetting) (msg *models.Message, err error) { ctx, cancel := context.WithTimeout(context.Background(), time.Duration(h.pluginConfig.Timeout)*time.Second) defer cancel() parmas := &bot.SendMessageParams{ ChatID: setting.ChatID, Text: "这是一条测试消息", ParseMode: models.ParseModeHTML, } return h.bot.SendMessage(ctx, parmas) }