This commit is contained in:
huangzhenpc
2025-05-23 23:08:02 +08:00
parent 9444bf4802
commit a8d25054fc
14 changed files with 2277 additions and 0 deletions

253
pkg/api/email_fetch.go Normal file
View File

@@ -0,0 +1,253 @@
package api
import (
"fmt"
"io"
"io/ioutil"
"strings"
"time"
"github.com/emersion/go-imap"
"github.com/emersion/go-imap/client"
"github.com/emersion/go-message/mail"
)
// EmailMessage 表示一封邮件
type EmailMessage struct {
ID string
Subject string
From string
To string
Date time.Time
Body string
MimeType string
}
// GetLatestEmails 获取指定邮箱的最新邮件
func GetLatestEmails(emailAddr, password, server string, port int, useSSL bool, numEmails int) ([]*EmailMessage, error) {
fmt.Printf("正在连接到 %s:%d...\n", server, port)
// 连接到服务器
var c *client.Client
var err error
if useSSL {
c, err = client.DialTLS(fmt.Sprintf("%s:%d", server, port), nil)
} else {
c, err = client.Dial(fmt.Sprintf("%s:%d", server, port))
}
if err != nil {
return nil, fmt.Errorf("连接到IMAP服务器失败: %w", err)
}
defer c.Logout()
// 登录
fmt.Printf("正在登录 %s...\n", emailAddr)
if err := c.Login(emailAddr, password); err != nil {
return nil, fmt.Errorf("登录失败: %w", err)
}
// 选择收件箱
mbox, err := c.Select("INBOX", false)
if err != nil {
return nil, fmt.Errorf("选择收件箱失败: %w", err)
}
if mbox.Messages == 0 {
fmt.Println("收件箱为空")
return nil, nil
}
// 计算要获取的消息范围
from := uint32(1)
if mbox.Messages > uint32(numEmails) {
from = mbox.Messages - uint32(numEmails) + 1
}
to := mbox.Messages
// 创建搜索条件
criteria := imap.NewSearchCriteria()
criteria.WithoutFlags = []string{imap.DeletedFlag}
if from > 1 {
criteria.Uid = new(imap.SeqSet)
criteria.Uid.AddRange(from, to)
}
// 搜索消息
ids, err := c.Search(criteria)
if err != nil {
return nil, fmt.Errorf("搜索邮件失败: %w", err)
}
if len(ids) == 0 {
fmt.Println("未找到符合条件的邮件")
return nil, nil
}
// 限制获取的邮件数量
if len(ids) > numEmails {
ids = ids[len(ids)-numEmails:]
}
// 创建序列集
seqSet := new(imap.SeqSet)
seqSet.AddNum(ids...)
// 设置获取项
items := []imap.FetchItem{imap.FetchEnvelope, imap.FetchBodyStructure, imap.FetchFlags, imap.FetchRFC822Text, imap.FetchUid}
// 获取消息
messages := make(chan *imap.Message, 10)
done := make(chan error, 1)
go func() {
done <- c.Fetch(seqSet, items, messages)
}()
// 处理消息
var emails []*EmailMessage
for msg := range messages {
email := &EmailMessage{
ID: fmt.Sprintf("%d", msg.Uid),
}
if msg.Envelope != nil {
email.Subject = msg.Envelope.Subject
if len(msg.Envelope.From) > 0 {
email.From = formatAddress(msg.Envelope.From[0])
}
if len(msg.Envelope.To) > 0 {
email.To = formatAddress(msg.Envelope.To[0])
}
email.Date = msg.Envelope.Date
}
// 获取正文
var section imap.BodySectionName
section.Specifier = imap.TextSpecifier
r := msg.GetBody(&section)
if r == nil {
// 尝试获取全部内容
section = imap.BodySectionName{}
r = msg.GetBody(&section)
if r == nil {
continue
}
}
// 处理正文
body, err := extractBody(r)
if err != nil {
fmt.Printf("提取邮件正文失败: %v\n", err)
continue
}
email.Body = body
emails = append(emails, email)
fmt.Printf("已获取邮件ID: %s\n", email.ID)
}
if err := <-done; err != nil {
return nil, fmt.Errorf("获取邮件失败: %w", err)
}
// 反转列表,让最新的邮件在前面
for i, j := 0, len(emails)-1; i < j; i, j = i+1, j-1 {
emails[i], emails[j] = emails[j], emails[i]
}
return emails, nil
}
// formatAddress 格式化地址
func formatAddress(addr *imap.Address) string {
if addr == nil {
return ""
}
if addr.PersonalName != "" {
return fmt.Sprintf("%s <%s@%s>", addr.PersonalName, addr.MailboxName, addr.HostName)
}
return fmt.Sprintf("%s@%s", addr.MailboxName, addr.HostName)
}
// extractBody 提取邮件正文
func extractBody(r io.Reader) (string, error) {
if r == nil {
return "", fmt.Errorf("无邮件内容")
}
// 尝试解析为邮件
mr, err := mail.CreateReader(r)
if err != nil {
// 如果无法解析为邮件,直接读取内容
data, err := ioutil.ReadAll(r)
if err != nil {
return "", fmt.Errorf("读取邮件内容失败: %w", err)
}
return string(data), nil
}
var textParts []string
for {
p, err := mr.NextPart()
if err == io.EOF {
break
}
if err != nil {
return "", fmt.Errorf("读取邮件部分失败: %w", err)
}
switch h := p.Header.(type) {
case *mail.InlineHeader:
contentType, _, _ := h.ContentType()
if strings.HasPrefix(contentType, "text/plain") {
data, err := ioutil.ReadAll(p.Body)
if err != nil {
return "", fmt.Errorf("读取文本部分失败: %w", err)
}
textParts = append(textParts, string(data))
}
}
}
if len(textParts) > 0 {
return strings.Join(textParts, "\n"), nil
}
// 如果没有找到文本部分,尝试读取原始内容
data, err := ioutil.ReadAll(r)
if err != nil {
return "", fmt.Errorf("读取原始内容失败: %w", err)
}
return string(data), nil
}
// PrintEmails 打印邮件信息
func PrintEmails(emails []*EmailMessage) {
if len(emails) == 0 {
fmt.Println("没有找到邮件")
return
}
for i, email := range emails {
fmt.Println("\n" + strings.Repeat("=", 60))
fmt.Printf("邮件 %d/%d\n", i+1, len(emails))
fmt.Println(strings.Repeat("-", 60))
fmt.Printf("主题: %s\n", email.Subject)
fmt.Printf("发件人: %s\n", email.From)
fmt.Printf("收件人: %s\n", email.To)
fmt.Printf("日期: %s\n", email.Date.Format(time.RFC1123Z))
fmt.Println(strings.Repeat("-", 60))
fmt.Println("邮件正文:")
body := email.Body
if len(body) > 1000 {
body = body[:1000] + "..."
}
fmt.Println(body)
fmt.Println(strings.Repeat("=", 60))
}
}