- 配置文件分离:用户配置与项目配置分离,项目级配置(客户端信息、需要 ID 命令的 provider)放在代码中 - 新增 check_id 字段:用户可选择禁用单个账户的 ID 命令发送 - 简化 provider:只保留 163 和 QQ,移除未使用的 Gmail/Outlook/188 等 - 修复 163 邮箱收件箱问题:通过发送 IMAP ID 命令解决 Unsafe Login 错误 BREAKING CHANGE: 配置文件格式变化,旧配置不兼容
196 lines
3.9 KiB
Go
196 lines
3.9 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
imap "github.com/BrianLeishman/go-imap"
|
|
)
|
|
|
|
type ReceivedEmail struct {
|
|
UID uint32
|
|
From string
|
|
FromName string
|
|
Subject string
|
|
Date time.Time
|
|
Preview string
|
|
Account string
|
|
AccountID string
|
|
}
|
|
|
|
func getEmailProviderName(email string) string {
|
|
if len(email) == 0 {
|
|
return "Email"
|
|
}
|
|
atIdx := -1
|
|
for i := len(email) - 1; i >= 0; i-- {
|
|
if email[i] == '@' {
|
|
atIdx = i
|
|
break
|
|
}
|
|
}
|
|
if atIdx == -1 {
|
|
return "Email"
|
|
}
|
|
domain := email[atIdx+1:]
|
|
for i := len(domain) - 1; i >= 0; i-- {
|
|
if domain[i] == '.' {
|
|
return domain[:i]
|
|
}
|
|
}
|
|
return domain
|
|
}
|
|
|
|
func FetchUnreadEmails(account Account, days int) ([]ReceivedEmail, error) {
|
|
if account.IMAP.Host == "" {
|
|
return nil, fmt.Errorf("IMAP host not configured for account: %s", account.Email)
|
|
}
|
|
|
|
port := account.IMAP.Port
|
|
if port == 0 {
|
|
port = 993
|
|
}
|
|
|
|
username := account.IMAP.Username
|
|
if username == "" {
|
|
username = account.Email
|
|
}
|
|
|
|
m, err := imap.New(username, account.IMAP.Password, account.IMAP.Host, port)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to connect to IMAP server: %w", err)
|
|
}
|
|
defer m.Close()
|
|
|
|
if account.CheckID != nil && *account.CheckID {
|
|
info := ProjectConfig.Info
|
|
idCmd := fmt.Sprintf("ID (\"name\" \"%s\" \"version\" \"%s\" \"vendor\" \"%s\")",
|
|
info.Name, info.Version, info.Vendor)
|
|
_, err := m.Exec(idCmd, false, 0, func(line []byte) error {
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
fmt.Printf("WARNING: failed to send IMAP ID: %v\n", err)
|
|
}
|
|
|
|
m.Exec("NOOP", false, 0, nil)
|
|
}
|
|
|
|
err = m.SelectFolder("INBOX")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to select inbox: %w", err)
|
|
}
|
|
|
|
sinceDate := time.Now().AddDate(0, 0, -days)
|
|
|
|
uids, err := m.GetUIDs(imap.Search().Since(sinceDate).Unseen().Build())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to search emails: %w", err)
|
|
}
|
|
|
|
if len(uids) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
overviews, err := m.GetOverviews(uids...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to fetch emails: %w", err)
|
|
}
|
|
|
|
var emails []ReceivedEmail
|
|
accountName := getEmailProviderName(account.Email)
|
|
accountID := account.Email
|
|
if account.Name != "" {
|
|
accountName = account.Name
|
|
accountID = account.Name
|
|
}
|
|
|
|
for uid, email := range overviews {
|
|
from := ""
|
|
if len(email.From) > 0 {
|
|
for _, addr := range email.From {
|
|
from = addr
|
|
break
|
|
}
|
|
}
|
|
|
|
emails = append(emails, ReceivedEmail{
|
|
UID: uint32(uid),
|
|
From: from,
|
|
FromName: "",
|
|
Subject: email.Subject,
|
|
Date: email.Sent,
|
|
Preview: fmt.Sprintf("(%.1f KB)", float64(email.Size)/1024),
|
|
Account: accountName,
|
|
AccountID: accountID,
|
|
})
|
|
}
|
|
|
|
return emails, nil
|
|
}
|
|
|
|
func GetAccountByEmail(email string) (Account, error) {
|
|
accounts, err := getAccounts()
|
|
if err != nil {
|
|
return Account{}, err
|
|
}
|
|
|
|
for _, acc := range accounts {
|
|
if acc.Email == email || acc.Name == email {
|
|
return acc, nil
|
|
}
|
|
}
|
|
|
|
return Account{}, fmt.Errorf("account not found: %s", email)
|
|
}
|
|
|
|
func GetAccountByID(id string) (Account, error) {
|
|
accounts, err := getAccounts()
|
|
if err != nil {
|
|
return Account{}, err
|
|
}
|
|
|
|
for _, acc := range accounts {
|
|
if acc.Email == id || acc.Name == id {
|
|
return acc, nil
|
|
}
|
|
}
|
|
|
|
return Account{}, fmt.Errorf("account not found: %s", id)
|
|
}
|
|
|
|
func FetchAllUnreadEmails(days int) ([]ReceivedEmail, error) {
|
|
accounts, err := getAccounts()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(accounts) == 0 {
|
|
return nil, fmt.Errorf("no accounts configured")
|
|
}
|
|
|
|
var allEmails []ReceivedEmail
|
|
|
|
for _, account := range accounts {
|
|
emails, err := FetchUnreadEmails(account, days)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
allEmails = append(allEmails, emails...)
|
|
}
|
|
|
|
sortEmailsByDate(allEmails)
|
|
|
|
return allEmails, nil
|
|
}
|
|
|
|
func sortEmailsByDate(emails []ReceivedEmail) {
|
|
for i := 0; i < len(emails)-1; i++ {
|
|
for j := i + 1; j < len(emails); j++ {
|
|
if emails[j].Date.After(emails[i].Date) {
|
|
emails[i], emails[j] = emails[j], emails[i]
|
|
}
|
|
}
|
|
}
|
|
}
|