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{