Files
codexautopool/backend/internal/mail/service.go

465 lines
11 KiB
Go

package mail
import (
"bytes"
"encoding/json"
"fmt"
"math/rand"
"net/http"
"regexp"
"strings"
"sync"
"time"
"codex-pool/internal/config"
)
// 默认邮箱配置
var defaultMailServices = []config.MailServiceConfig{
{
Name: "esyteam",
APIBase: "https://mail.esyteam.edu.kg",
APIToken: "005d6f3e-5312-4c37-8125-e1f71243e1ba",
Domain: "esyteam.edu.kg",
EmailPath: "/api/public/emailList",
AddUserAPI: "/api/public/addUser",
},
}
// 全局变量
var (
currentMailServices []config.MailServiceConfig
mailServicesMutex sync.RWMutex
currentServiceIndex int
)
func init() {
currentMailServices = defaultMailServices
}
// Init 初始化邮箱服务配置
func Init(services []config.MailServiceConfig) {
mailServicesMutex.Lock()
defer mailServicesMutex.Unlock()
if len(services) > 0 {
for i := range services {
if services[i].EmailPath == "" {
services[i].EmailPath = "/api/public/emailList"
}
if services[i].AddUserAPI == "" {
services[i].AddUserAPI = "/api/public/addUser"
}
if services[i].Name == "" {
services[i].Name = fmt.Sprintf("mail-service-%d", i+1)
}
}
currentMailServices = services
fmt.Printf("[邮箱] 已加载 %d 个邮箱服务配置:\n", len(services))
for _, s := range services {
fmt.Printf(" - %s (%s) @ %s\n", s.Name, s.Domain, s.APIBase)
}
} else {
currentMailServices = defaultMailServices
fmt.Println("[邮箱] 使用默认邮箱服务配置")
}
currentServiceIndex = 0
}
// GetServices 获取当前邮箱服务配置
func GetServices() []config.MailServiceConfig {
mailServicesMutex.RLock()
defer mailServicesMutex.RUnlock()
return currentMailServices
}
// GetNextService 轮询获取下一个邮箱服务
func GetNextService() config.MailServiceConfig {
mailServicesMutex.Lock()
defer mailServicesMutex.Unlock()
if len(currentMailServices) == 0 {
return defaultMailServices[0]
}
service := currentMailServices[currentServiceIndex]
currentServiceIndex = (currentServiceIndex + 1) % len(currentMailServices)
return service
}
// GetRandomService 随机获取一个邮箱服务
func GetRandomService() config.MailServiceConfig {
mailServicesMutex.RLock()
defer mailServicesMutex.RUnlock()
if len(currentMailServices) == 0 {
return defaultMailServices[0]
}
return currentMailServices[rand.Intn(len(currentMailServices))]
}
// GetServiceByDomain 根据域名获取对应的邮箱服务
func GetServiceByDomain(domain string) *config.MailServiceConfig {
mailServicesMutex.RLock()
defer mailServicesMutex.RUnlock()
for _, s := range currentMailServices {
if s.Domain == domain || strings.HasSuffix(domain, "."+s.Domain) {
return &s
}
}
return nil
}
// ==================== 邮件结构 ====================
// EmailListRequest 邮件列表请求
type EmailListRequest struct {
ToEmail string `json:"toEmail"`
TimeSort string `json:"timeSort"`
Size int `json:"size"`
}
// EmailListResponse 邮件列表响应
type EmailListResponse struct {
Code int `json:"code"`
Data []EmailItem `json:"data"`
}
// EmailItem 邮件项
type EmailItem struct {
Content string `json:"content"`
Text string `json:"text"`
Subject string `json:"subject"`
}
// AddUserRequest 创建用户请求
type AddUserRequest struct {
List []AddUserItem `json:"list"`
}
// AddUserItem 用户项
type AddUserItem struct {
Email string `json:"email"`
Password string `json:"password"`
}
// AddUserResponse 创建用户响应
type AddUserResponse struct {
Code int `json:"code"`
Message string `json:"message"`
}
// ==================== 邮箱生成 ====================
// GenerateEmail 生成随机邮箱并在邮件系统中创建
func GenerateEmail() string {
return GenerateEmailWithService(GetNextService())
}
// GenerateEmailWithService 使用指定服务生成随机邮箱
func GenerateEmailWithService(service config.MailServiceConfig) string {
const charset = "abcdefghijklmnopqrstuvwxyz0123456789"
b := make([]byte, 10)
for i := range b {
b[i] = charset[rand.Intn(len(charset))]
}
email := string(b) + "@" + service.Domain
if err := CreateMailboxWithService(email, service); err != nil {
fmt.Printf(" [!] 创建邮箱失败 (%s): %v (继续尝试)\n", service.Name, err)
}
return email
}
// CreateMailbox 在邮件系统中创建邮箱
func CreateMailbox(email string) error {
parts := strings.Split(email, "@")
if len(parts) != 2 {
return fmt.Errorf("无效的邮箱地址: %s", email)
}
domain := parts[1]
service := GetServiceByDomain(domain)
if service == nil {
services := GetServices()
if len(services) > 0 {
service = &services[0]
} else {
return fmt.Errorf("没有可用的邮箱服务")
}
}
return CreateMailboxWithService(email, *service)
}
// CreateMailboxWithService 使用指定服务在邮件系统中创建邮箱
func CreateMailboxWithService(email string, service config.MailServiceConfig) error {
client := &http.Client{Timeout: 10 * time.Second}
parts := strings.Split(email, "@")
if len(parts) == 2 {
domain := parts[1]
if strings.HasSuffix(domain, "."+service.Domain) {
email = parts[0] + "@" + service.Domain
}
}
payload := AddUserRequest{
List: []AddUserItem{
{Email: email, Password: GeneratePassword()},
},
}
jsonData, _ := json.Marshal(payload)
req, err := http.NewRequest("POST", service.APIBase+service.AddUserAPI, bytes.NewBuffer(jsonData))
if err != nil {
return err
}
req.Header.Set("Authorization", service.APIToken)
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
var result AddUserResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return err
}
if result.Code != 200 {
if strings.Contains(result.Message, "exist") {
return nil
}
return fmt.Errorf("API 错误: %s", result.Message)
}
return nil
}
// GeneratePassword 生成随机密码
func GeneratePassword() string {
const (
upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
lower = "abcdefghijklmnopqrstuvwxyz"
digits = "0123456789"
special = "#$%@!"
)
password := make([]byte, 12)
password[0] = upper[rand.Intn(len(upper))]
password[1] = lower[rand.Intn(len(lower))]
password[10] = digits[rand.Intn(len(digits))]
password[11] = special[rand.Intn(len(special))]
charset := upper + lower
for i := 2; i < 10; i++ {
password[i] = charset[rand.Intn(len(charset))]
}
return string(password)
}
// ==================== 邮件客户端 ====================
// Client 邮件客户端
type Client struct {
client *http.Client
service *config.MailServiceConfig
}
// NewClient 创建邮件客户端
func NewClient() *Client {
services := GetServices()
var service *config.MailServiceConfig
if len(services) > 0 {
service = &services[0]
} else {
service = &defaultMailServices[0]
}
return &Client{
client: &http.Client{Timeout: 10 * time.Second},
service: service,
}
}
// NewClientWithService 创建指定服务的邮件客户端
func NewClientWithService(service config.MailServiceConfig) *Client {
return &Client{
client: &http.Client{Timeout: 10 * time.Second},
service: &service,
}
}
// NewClientForEmail 根据邮箱地址创建对应的邮件客户端
func NewClientForEmail(email string) *Client {
parts := strings.Split(email, "@")
if len(parts) == 2 {
if service := GetServiceByDomain(parts[1]); service != nil {
return NewClientWithService(*service)
}
}
return NewClient()
}
// GetEmails 获取邮件列表
func (m *Client) GetEmails(email string, size int) ([]EmailItem, error) {
service := m.service
parts := strings.Split(email, "@")
if len(parts) == 2 {
if s := GetServiceByDomain(parts[1]); s != nil {
service = s
}
}
url := service.APIBase + service.EmailPath
payload := EmailListRequest{
ToEmail: email,
TimeSort: "desc",
Size: size,
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", service.APIToken)
req.Header.Set("Content-Type", "application/json")
resp, err := m.client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("HTTP %d", resp.StatusCode)
}
var result EmailListResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
if result.Code != 200 {
return nil, fmt.Errorf("API 错误: %d", result.Code)
}
return result.Data, nil
}
// WaitForCode 等待验证码邮件
func (m *Client) WaitForCode(email string, timeout time.Duration) (string, error) {
start := time.Now()
codeRegex := regexp.MustCompile(`\b(\d{6})\b`)
for time.Since(start) < timeout {
emails, err := m.GetEmails(email, 10)
if err == nil {
for _, mail := range emails {
subject := strings.ToLower(mail.Subject)
// 匹配多种可能的验证码邮件主题
// 包括 "Your ChatGPT code is 016547" 格式
isCodeEmail := strings.Contains(subject, "code") ||
strings.Contains(subject, "verify") ||
strings.Contains(subject, "verification") ||
strings.Contains(subject, "openai") ||
strings.Contains(subject, "confirm") ||
strings.Contains(subject, "chatgpt")
if !isCodeEmail {
continue
}
// 优先从标题中提取验证码 (如 "Your ChatGPT code is 016547")
matches := codeRegex.FindStringSubmatch(mail.Subject)
if len(matches) >= 2 {
return matches[1], nil
}
// 如果标题中没有,从内容中提取
content := mail.Content
if content == "" {
content = mail.Text
}
matches = codeRegex.FindStringSubmatch(content)
if len(matches) >= 2 {
return matches[1], nil
}
}
}
time.Sleep(1 * time.Second)
}
return "", fmt.Errorf("验证码获取超时")
}
// WaitForInviteLink 等待邀请邮件并提取链接
func (m *Client) WaitForInviteLink(email string, timeout time.Duration) (string, error) {
start := time.Now()
for time.Since(start) < timeout {
emails, err := m.GetEmails(email, 10)
if err == nil {
for _, mail := range emails {
content := mail.Content
if content == "" {
content = mail.Text
}
if strings.Contains(mail.Subject, "invite") ||
strings.Contains(mail.Subject, "Team") ||
strings.Contains(mail.Subject, "ChatGPT") ||
strings.Contains(content, "invite") {
link := extractInviteLink(content)
if link != "" {
return link, nil
}
}
}
}
time.Sleep(1 * time.Second)
}
return "", fmt.Errorf("等待邀请邮件超时")
}
// extractInviteLink 从邮件内容提取邀请链接
func extractInviteLink(content string) string {
patterns := []string{
`https://chatgpt\.com/invite/[^\s"'<>]+`,
`https://chat\.openai\.com/invite/[^\s"'<>]+`,
`https://chatgpt\.com/[^\s"'<>]*accept[^\s"'<>]*`,
}
for _, pattern := range patterns {
re := regexp.MustCompile(pattern)
match := re.FindString(content)
if match != "" {
match = strings.ReplaceAll(match, "&amp;", "&")
return match
}
}
return ""
}
// ==================== 便捷函数 ====================
// WaitForInviteEmail 等待邀请邮件
func WaitForInviteEmail(email string, timeout time.Duration) (string, error) {
client := NewClientForEmail(email)
return client.WaitForInviteLink(email, timeout)
}
// GetVerificationCode 获取验证码
func GetVerificationCode(email string, timeout time.Duration) (string, error) {
client := NewClientForEmail(email)
return client.WaitForCode(email, timeout)
}