package app import ( "fmt" "strings" "douyu_go/internal/logger" "douyu_go/internal/types" ) // ImportAccounts 将剪贴板中的账号行追加到账号文件。每行格式必须为 // 账号名----密码----Cookie,账号名不能与已有账号重复。 func (a *App) ImportAccounts(content string) types.AccountMutationResult { entries, err := parseAccountImport(content) if err != nil { return types.AccountMutationResult{Error: err.Error()} } a.accountsMu.Lock() defer a.accountsMu.Unlock() accountFile := a.scheduler.AccountFilePath() lines, err := parseAccountFile(accountFile) if err != nil { return types.AccountMutationResult{Error: "读取账号文件失败: " + err.Error()} } existingNames := make(map[string]struct{}, len(lines)) for _, line := range lines { if line.Name != "" { existingNames[line.Name] = struct{}{} } } for _, entry := range entries { if _, exists := existingNames[entry.Name]; exists { return types.AccountMutationResult{Error: fmt.Sprintf("账号 %q 已存在", entry.Name)} } existingNames[entry.Name] = struct{}{} lines = append(lines, entry) } if err := a.saveAccountLines(accountFile, lines); err != nil { return types.AccountMutationResult{Error: err.Error()} } logger.LogEvent("账号", "success", "已从剪贴板导入 %d 个账号", len(entries)) return types.AccountMutationResult{Success: true, Count: len(entries)} } // UpdateAccount 更新账号名或 Cookie。newCookie 留空时保留文件中的原 Cookie。 func (a *App) UpdateAccount(originalName, newName, newCookie string) types.AccountMutationResult { originalName = strings.TrimSpace(originalName) newName = strings.TrimSpace(newName) newCookie = strings.TrimSpace(newCookie) if originalName == "" || newName == "" { return types.AccountMutationResult{Error: "账号名不能为空"} } if invalidAccountField(newName) || strings.ContainsAny(newCookie, "\r\n") { return types.AccountMutationResult{Error: "账号名或 Cookie 包含无效字符"} } a.accountsMu.Lock() defer a.accountsMu.Unlock() accountFile := a.scheduler.AccountFilePath() lines, err := parseAccountFile(accountFile) if err != nil { return types.AccountMutationResult{Error: "读取账号文件失败: " + err.Error()} } targetIndex := -1 nameCount := 0 for i, line := range lines { if line.Name == originalName { targetIndex = i nameCount++ } if line.Name == newName && newName != originalName { return types.AccountMutationResult{Error: fmt.Sprintf("账号 %q 已存在", newName)} } } if targetIndex < 0 { return types.AccountMutationResult{Error: "未找到要编辑的账号"} } if nameCount > 1 { return types.AccountMutationResult{Error: "账号名重复,请先删除重复账号后再编辑"} } parts := strings.SplitN(strings.TrimSpace(lines[targetIndex].Raw), "----", 3) if len(parts) != 3 { return types.AccountMutationResult{Error: "原账号格式不正确,无法保留密码段"} } oldCookie := strings.TrimSpace(parts[2]) if newCookie == "" { newCookie = oldCookie } if newCookie == "" { return types.AccountMutationResult{Error: "Cookie 不能为空"} } lines[targetIndex] = accountLine{ Raw: newName + "----" + strings.TrimSpace(parts[1]) + "----" + newCookie, Name: newName, } if err := a.saveAccountLines(accountFile, lines); err != nil { return types.AccountMutationResult{Error: err.Error()} } logger.LogEvent("账号", "success", "账号已更新: %s", newName) return types.AccountMutationResult{Success: true, Count: 1} } // DeleteAccounts 从账号文件中删除指定账号,可一次删除多个账号。 func (a *App) DeleteAccounts(accountNames []string) types.AccountMutationResult { nameSet := make(map[string]struct{}, len(accountNames)) for _, name := range accountNames { if trimmed := strings.TrimSpace(name); trimmed != "" { nameSet[trimmed] = struct{}{} } } if len(nameSet) == 0 { return types.AccountMutationResult{Error: "请选择要删除的账号"} } a.accountsMu.Lock() defer a.accountsMu.Unlock() accountFile := a.scheduler.AccountFilePath() lines, err := parseAccountFile(accountFile) if err != nil { return types.AccountMutationResult{Error: "读取账号文件失败: " + err.Error()} } kept := make([]accountLine, 0, len(lines)) deleted := 0 for _, line := range lines { if _, remove := nameSet[line.Name]; remove && !line.Comment { deleted++ continue } kept = append(kept, line) } if deleted == 0 { return types.AccountMutationResult{Error: "未找到要删除的账号"} } if err := a.saveAccountLines(accountFile, kept); err != nil { return types.AccountMutationResult{Error: err.Error()} } logger.LogEvent("账号", "success", "已删除 %d 个账号", deleted) return types.AccountMutationResult{Success: true, Count: deleted} } func parseAccountImport(content string) ([]accountLine, error) { content = strings.ReplaceAll(content, "\r\n", "\n") content = strings.ReplaceAll(content, "\r", "\n") entries := make([]accountLine, 0) names := make(map[string]struct{}) for index, raw := range strings.Split(content, "\n") { trimmed := strings.TrimSpace(raw) if trimmed == "" || strings.HasPrefix(trimmed, "#") { continue } parts := strings.SplitN(trimmed, "----", 3) if len(parts) != 3 { return nil, fmt.Errorf("第 %d 行格式不正确,应为 账号名----密码----Cookie", index+1) } name := strings.TrimSpace(parts[0]) password := strings.TrimSpace(parts[1]) cookie := strings.TrimSpace(parts[2]) if name == "" || cookie == "" || invalidAccountField(name) || strings.ContainsAny(cookie, "\r\n") { return nil, fmt.Errorf("第 %d 行账号名或 Cookie 无效", index+1) } if _, exists := names[name]; exists { return nil, fmt.Errorf("剪贴板中账号 %q 重复", name) } names[name] = struct{}{} entries = append(entries, accountLine{Raw: name + "----" + password + "----" + cookie, Name: name}) } if len(entries) == 0 { return nil, fmt.Errorf("未识别到可导入的账号") } return entries, nil } func invalidAccountField(value string) bool { return strings.ContainsAny(value, "\r\n") || strings.Contains(value, "----") } func (a *App) saveAccountLines(accountFile string, lines []accountLine) error { rawLines := make([]string, 0, len(lines)) for _, line := range lines { rawLines = append(rawLines, line.Raw) } content := strings.Join(rawLines, "\r\n") if content != "" { content += "\r\n" } if _, err := writeFileWithBackup(accountFile, []byte(content)); err != nil { return fmt.Errorf("更新账号文件失败: %w", err) } a.scheduler.LoadAccounts() return nil }