254 lines
5.7 KiB
Go
254 lines
5.7 KiB
Go
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(§ion)
|
|
if r == nil {
|
|
// 尝试获取全部内容
|
|
section = imap.BodySectionName{}
|
|
r = msg.GetBody(§ion)
|
|
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))
|
|
}
|
|
}
|