diff --git a/main.go b/main.go index 4731027..746ec18 100644 --- a/main.go +++ b/main.go @@ -23,23 +23,67 @@ var roomDossierRegexp = regexp.MustCompile(`window\.initialRoomDossier = "(.*?)" // --- main --- func main() { if len(os.Args) < 2 { - fmt.Println("Verwendung: recorder.exe [-o ]") + fmt.Println("Verwendung: recorder.exe [--http-cookie \"\"] [best] [-o ] [-f]") os.Exit(1) } - arg := os.Args[1] - username := extractUsername(arg) + var ( + httpCookie string + outputPath string + urlArg string + ) - outputPath := fmt.Sprintf("%s_%s.ts", username, time.Now().Format("20060102_150405")) - for i := 2; i < len(os.Args); i++ { - if os.Args[i] == "-o" && i+1 < len(os.Args) { - outputPath = os.Args[i+1] - break + args := os.Args[1:] + + for i := 0; i < len(args); i++ { + a := args[i] + + switch a { + case "--http-cookie": + if i+1 >= len(args) { + fmt.Println("Fehlender Wert nach --http-cookie") + os.Exit(1) + } + httpCookie = args[i+1] + i++ + + case "-o": + if i+1 >= len(args) { + fmt.Println("Fehlender Wert nach -o") + os.Exit(1) + } + outputPath = args[i+1] + i++ + + case "-f": + // nur für Kompatibilität, wird ignoriert + case "best": + // wird ignoriert, wir wählen sowieso beste Qualität + + default: + // erste „normale“ Angabe ist die URL / der Benutzername + if urlArg == "" { + urlArg = a + } } } + if urlArg == "" { + fmt.Println("Keine URL / kein Benutzername angegeben.") + os.Exit(1) + } + + username := extractUsername(urlArg) + + // Default-Ausgabedatei, falls kein -o + if outputPath == "" { + outputPath = fmt.Sprintf("%s_%s.ts", username, time.Now().Format("20060102_150405")) + } + ctx := context.Background() - body, err := fetchPage("https://chaturbate.com/" + username) + + // Seite laden (inkl. Cookie, falls gesetzt) + body, err := fetchPage("https://chaturbate.com/"+username, httpCookie) if err != nil { fmt.Println("❌ Fehler beim Laden der Seite:", err) os.Exit(1) @@ -51,7 +95,7 @@ func main() { os.Exit(1) } - playlist, err := FetchPlaylist(ctx, hlsURL) + playlist, err := FetchPlaylist(ctx, hlsURL, httpCookie) if err != nil { fmt.Println("❌ Fehler beim Abrufen der Playlist:", err) os.Exit(1) @@ -66,7 +110,7 @@ func main() { fmt.Println("📡 Aufnahme gestartet:", outputPath) - err = playlist.WatchSegments(ctx, func(b []byte, _ float64) error { + err = playlist.WatchSegments(ctx, httpCookie, func(b []byte, _ float64) error { _, err := file.Write(b) if err != nil { fmt.Println("❌ Fehler beim Schreiben in Datei:", err) @@ -78,10 +122,9 @@ func main() { fmt.Println("❌ Aufnahmefehler:", err) } + // TS -> MP4 remuxen (wie gehabt) mp4Out := outputPath ext := filepath.Ext(outputPath) - - // Falls nicht bereits .mp4 als Endung existiert if ext != ".mp4" { mp4Out = strings.TrimSuffix(outputPath, ext) + ".mp4" } @@ -100,7 +143,6 @@ func main() { } else { fmt.Println("✅ Umwandlung abgeschlossen (web-optimiert):", mp4Out) } - } // --- helper --- @@ -114,13 +156,40 @@ func extractUsername(input string) string { return strings.TrimSpace(input) } -func fetchPage(url string) (string, error) { +func addCookiesFromString(req *http.Request, cookieStr string) { + if cookieStr == "" { + return + } + pairs := strings.Split(cookieStr, ";") + for _, pair := range pairs { + parts := strings.SplitN(strings.TrimSpace(pair), "=", 2) + if len(parts) != 2 { + continue + } + name := strings.TrimSpace(parts[0]) + value := strings.TrimSpace(parts[1]) + if name == "" { + continue + } + req.AddCookie(&http.Cookie{ + Name: name, + Value: value, + }) + } +} + + +func fetchPage(url, httpCookie string) (string, error) { client := http.Client{Timeout: 10 * time.Second} req, err := http.NewRequest("GET", url, nil) if err != nil { return "", err } req.Header.Set("User-Agent", "Mozilla/5.0") + if httpCookie != "" { + // kompletter Inhalt von --http-cookie, z.B. "session=XYZ; foo=bar" + addCookiesFromString(req, httpCookie) + } resp, err := client.Do(req) if err != nil { @@ -140,6 +209,7 @@ func fetchPage(url string) (string, error) { return string(data), nil } + func ParseStream(html string) (string, error) { matches := roomDossierRegexp.FindStringSubmatch(html) if len(matches) < 2 { @@ -176,12 +246,22 @@ type Resolution struct { Width int } -func FetchPlaylist(ctx context.Context, hlsSource string) (*Playlist, error) { +func FetchPlaylist(ctx context.Context, hlsSource, httpCookie string) (*Playlist, error) { if hlsSource == "" { return nil, errors.New("HLS-URL leer") } - resp, err := http.Get(hlsSource) + client := http.Client{Timeout: 10 * time.Second} + req, err := http.NewRequestWithContext(ctx, "GET", hlsSource, nil) + if err != nil { + return nil, fmt.Errorf("Fehler beim Erstellen der Playlist-Request: %w", err) + } + req.Header.Set("User-Agent", "Mozilla/5.0") + if httpCookie != "" { + req.Header.Set("Cookie", httpCookie) + } + + resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("Fehler beim Laden der Playlist: %w", err) } @@ -217,7 +297,6 @@ func FetchPlaylist(ctx context.Context, hlsSource string) (*Playlist, error) { fr = 60 } - // Besser, wenn größere Auflösung oder gleiche Auflösung mit höherem Framerate if width > bestWidth || (width == bestWidth && fr > bestFramerate) { bestWidth = width bestFramerate = fr @@ -238,12 +317,16 @@ func FetchPlaylist(ctx context.Context, hlsSource string) (*Playlist, error) { }, nil } -func (p *Playlist) WatchSegments(ctx context.Context, handler func([]byte, float64) error) error { +func (p *Playlist) WatchSegments( + ctx context.Context, + httpCookie string, + handler func([]byte, float64) error, +) error { var lastSeq int64 = -1 client := http.Client{Timeout: 10 * time.Second} emptyRounds := 0 - const maxEmptyRounds = 5 // z. B. nach 5 leeren Runden abbrechen + const maxEmptyRounds = 5 for { select { @@ -252,7 +335,17 @@ func (p *Playlist) WatchSegments(ctx context.Context, handler func([]byte, float default: } - resp, err := client.Get(p.PlaylistURL) + // Playlist holen + req, err := http.NewRequestWithContext(ctx, "GET", p.PlaylistURL, nil) + if err != nil { + return fmt.Errorf("Fehler beim Erstellen der Playlist-Request: %w", err) + } + req.Header.Set("User-Agent", "Mozilla/5.0") + if httpCookie != "" { + req.Header.Set("Cookie", httpCookie) + } + + resp, err := client.Do(req) if err != nil { emptyRounds++ if emptyRounds >= maxEmptyRounds { @@ -261,6 +354,7 @@ func (p *Playlist) WatchSegments(ctx context.Context, handler func([]byte, float time.Sleep(2 * time.Second) continue } + playlist, listType, err := m3u8.DecodeFrom(resp.Body, true) resp.Body.Close() @@ -288,7 +382,17 @@ func (p *Playlist) WatchSegments(ctx context.Context, handler func([]byte, float newSegment = true segmentURL := p.RootURL + segment.URI - segResp, err := client.Get(segmentURL) + + segReq, err := http.NewRequestWithContext(ctx, "GET", segmentURL, nil) + if err != nil { + continue + } + segReq.Header.Set("User-Agent", "Mozilla/5.0") + if httpCookie != "" { + segReq.Header.Set("Cookie", httpCookie) + } + + segResp, err := client.Do(segReq) if err != nil { continue } @@ -316,3 +420,4 @@ func (p *Playlist) WatchSegments(ctx context.Context, handler func([]byte, float time.Sleep(1 * time.Second) } } + diff --git a/recorder.exe b/recorder.exe index 8619536..9a2e432 100644 Binary files a/recorder.exe and b/recorder.exe differ