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) // 匹配多种可能的验证码邮件主题 isCodeEmail := strings.Contains(subject, "code") || strings.Contains(subject, "verify") || strings.Contains(subject, "verification") || strings.Contains(subject, "openai") || strings.Contains(subject, "confirm") if !isCodeEmail { continue } 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, "&", "&") 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) }