package app import ( "bufio" "fmt" "os" "path/filepath" "strings" "time" "douyu_go/internal/logger" "douyu_go/internal/types" "douyu_go/internal/utils" ) // accountLine 账号文件的一行解析结果。 type accountLine struct { Raw string // 原始行文本 Name string // 解析出的账号名(注释/空行为空) Comment bool // 是否为注释或空行 } // parseAccountFile 读取账号文件并按行解析账号名。 func parseAccountFile(path string) ([]accountLine, error) { data, err := os.ReadFile(path) if err != nil { return nil, err } var lines []accountLine scanner := bufio.NewScanner(strings.NewReader(string(data))) for scanner.Scan() { raw := scanner.Text() trimmed := strings.TrimSpace(raw) if trimmed == "" || strings.HasPrefix(trimmed, "#") { lines = append(lines, accountLine{Raw: raw, Comment: true}) continue } name := "" if parts := strings.SplitN(trimmed, "----", 3); len(parts) >= 1 { name = strings.TrimSpace(parts[0]) } lines = append(lines, accountLine{Raw: raw, Name: name}) } return lines, scanner.Err() } // ExportAccounts 导出指定账号到文件。 func (a *App) ExportAccounts(accountNames []string, filename string) types.ExportAccountsResult { if len(accountNames) == 0 { return types.ExportAccountsResult{Error: "没有要导出的账号"} } accountFile := a.scheduler.AccountFilePath() lines, err := parseAccountFile(accountFile) if err != nil { return types.ExportAccountsResult{Error: "读取账号文件失败: " + err.Error()} } nameSet := make(map[string]bool) for _, name := range accountNames { nameSet[name] = true } var matchedLines []string for _, line := range lines { if line.Comment { continue } if nameSet[line.Name] { matchedLines = append(matchedLines, line.Raw) } } if len(matchedLines) == 0 { return types.ExportAccountsResult{Error: "没有找到匹配的账号行"} } // 生成导出文件路径 exportPath := filepath.Join(filepath.Dir(accountFile), filename) content := strings.Join(matchedLines, "\n") + "\n" if err := os.WriteFile(exportPath, []byte(content), 0644); err != nil { return types.ExportAccountsResult{Error: "写入文件失败: " + err.Error()} } logger.LogEvent("导出", "success", "已导出 %d 个账号到: %s", len(matchedLines), exportPath) return types.ExportAccountsResult{ Success: true, Count: len(matchedLines), FilePath: exportPath, } } // ExportAccountsWithProduct 导出指定账号到文件,自动使用中文商品名和时间戳命名。 func (a *App) ExportAccountsWithProduct(accountNames []string, commodityID, commodityName string) types.ExportAccountsResult { // 生成文件名: user_已抢到_商品中文名_20260626_150405.txt now := time.Now() timestamp := now.Format("20060102_150405") // 清理商品名中的特殊字符(复用 utils.SafeFilename,避免与 utils 包重复实现) safeName := utils.SafeFilename(commodityName) filename := fmt.Sprintf("user_已抢到_%s_%s.txt", safeName, timestamp) return a.ExportAccounts(accountNames, filename) } // OrganizeAccounts 整理账号,把已抢到指定商品的账号分离出来。 func (a *App) OrganizeAccounts(commodityID, commodityName string, startTime, endTime int64) types.OrganizeAccountsResult { accounts := a.scheduler.AccountSnapshot() hitAccounts := make([]string, 0) missAccounts := make([]string, 0) lookups := a.lookupExchangeRecords("整理", accounts) for _, lookup := range lookups { if lookup.EmptyCookie || lookup.Err != nil { missAccounts = append(missAccounts, lookup.Account.Name) continue } if hasCommodityRecord(lookup.Records, commodityID, startTime, endTime) { hitAccounts = append(hitAccounts, lookup.Account.Name) } else { missAccounts = append(missAccounts, lookup.Account.Name) } } // 导出已抢到的账号 var exportResult types.ExportAccountsResult if len(hitAccounts) > 0 { exportResult = a.ExportAccountsWithProduct(hitAccounts, commodityID, commodityName) } // 更新原文件,只保留未抢到的 accountFile := a.scheduler.AccountFilePath() lines, err := parseAccountFile(accountFile) if err != nil { return types.OrganizeAccountsResult{Error: "读取账号文件失败: " + err.Error()} } missSet := make(map[string]bool) for _, name := range missAccounts { missSet[name] = true } var keepLines []string for _, line := range lines { if line.Comment { keepLines = append(keepLines, line.Raw) continue } if missSet[line.Name] { keepLines = append(keepLines, line.Raw) } } // 写回原文件 newContent := strings.Join(keepLines, "\n") + "\n" if err := os.WriteFile(accountFile, []byte(newContent), 0644); err != nil { return types.OrganizeAccountsResult{Error: "更新账号文件失败: " + err.Error()} } // 重新加载账号 a.scheduler.LoadAccounts() logger.LogEvent("整理", "success", "整理完成: 已抢到=%d, 未抢到=%d", len(hitAccounts), len(missAccounts)) return types.OrganizeAccountsResult{ Success: true, HitCount: len(hitAccounts), MissCount: len(missAccounts), HitAccounts: hitAccounts, MissAccounts: missAccounts, ExportResult: exportResult, } }