From 65397fb3a1de4e5f3b49e1ce5305a992da494c1c Mon Sep 17 00:00:00 2001 From: Yami Odymel Date: Tue, 6 May 2025 05:27:23 +0800 Subject: [PATCH] 2.0.2, Related #104, X-Request --- chaturbate/chaturbate.go | 5 ----- internal/internal_err.go | 1 + internal/internal_req.go | 27 +++++++++++++++++---------- main.go | 2 +- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/chaturbate/chaturbate.go b/chaturbate/chaturbate.go index 1c786e3..5598337 100644 --- a/chaturbate/chaturbate.go +++ b/chaturbate/chaturbate.go @@ -44,11 +44,6 @@ func FetchStream(ctx context.Context, client *internal.Req, username string) (*S return nil, fmt.Errorf("failed to get page body: %w", err) } - // Check for Cloudflare protection page - if strings.Contains(body, "Just a moment...") { - return nil, internal.ErrCloudflareBlocked - } - // Ensure that the playlist.m3u8 file is present in the response if !strings.Contains(body, "playlist.m3u8") { return nil, internal.ErrChannelOffline diff --git a/internal/internal_err.go b/internal/internal_err.go index 71fd66c..a10c9d3 100644 --- a/internal/internal_err.go +++ b/internal/internal_err.go @@ -6,6 +6,7 @@ var ( ErrChannelExists = errors.New("channel exists") ErrChannelNotFound = errors.New("channel not found") ErrCloudflareBlocked = errors.New("blocked by Cloudflare; try with `-cookies` and `-user-agent`") + ErrAgeVerification = errors.New("age verification required; try with `-cookies` and `-user-agent`") ErrChannelOffline = errors.New("channel offline") ErrPrivateStream = errors.New("channel went offline or private") ErrPaused = errors.New("channel paused") diff --git a/internal/internal_req.go b/internal/internal_req.go index 982d9c1..3e1a425 100644 --- a/internal/internal_req.go +++ b/internal/internal_req.go @@ -62,11 +62,25 @@ func (h *Req) GetBytes(ctx context.Context, url string) ([]byte, error) { } defer resp.Body.Close() + b, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("read body: %w", err) + } + + // Check for Cloudflare protection + if strings.Contains(string(b), "Just a moment...") { + return nil, ErrCloudflareBlocked + } + // Check for Age Verification + if strings.Contains(string(b), "Verify your age") { + return nil, ErrAgeVerification + } + if resp.StatusCode == http.StatusForbidden { return nil, fmt.Errorf("forbidden: %w", ErrPrivateStream) } - return ReadResponseBody(resp) + return b, err } // CreateRequest constructs an HTTP GET request with necessary headers. @@ -83,6 +97,8 @@ func CreateRequest(ctx context.Context, url string) (*http.Request, context.Canc // SetRequestHeaders applies necessary headers to the request. func SetRequestHeaders(req *http.Request) { + req.Header.Set("X-Requested-With", "XMLHttpRequest") // So Cloudflare would likely accept the request, and no Age Verification + if server.Config.UserAgent != "" { req.Header.Set("User-Agent", server.Config.UserAgent) } @@ -94,15 +110,6 @@ func SetRequestHeaders(req *http.Request) { } } -// ReadResponseBody reads and returns the response body. -func ReadResponseBody(resp *http.Response) ([]byte, error) { - b, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("read body: %w", err) - } - return b, nil -} - // ParseCookies converts a cookie string into a map. func ParseCookies(cookieStr string) map[string]string { cookies := make(map[string]string) diff --git a/main.go b/main.go index aee524c..678856f 100644 --- a/main.go +++ b/main.go @@ -30,7 +30,7 @@ const logo = ` func main() { app := &cli.App{ Name: "chaturbate-dvr", - Version: "2.0.1", + Version: "2.0.2", Usage: "Record your favorite Chaturbate streams automatically. 😎🫵", Flags: []cli.Flag{ &cli.StringFlag{