Merge pull request #90 from HeapOfChaos/master

Added ability to specify cookie for CloudFlare anti-bot check and to specify User-Agent string
This commit is contained in:
Yami Odymel 2025-03-23 19:16:57 +08:00 committed by GitHub
commit 251f0f1fbe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 115 additions and 15 deletions

View File

@ -106,6 +106,8 @@ GLOBAL OPTIONS:
--split-filesize value, --sf value size in MB to split each video into segments ('0' to disable) (default: 0)
--log-level value log level, availables: 'DEBUG', 'INFO', 'WARN', 'ERROR' (default: "INFO")
--port value port to expose the web interface and API (default: "8080")
--cf-cookie value Cloudflare cookie to bypass anti-bot page
--user-agent value Custom user agent for when using cf-cookie
--help, -h show help
--version, -v print the version
```
@ -209,6 +211,12 @@ A: Your network is unstable or being blocked by Chaturbate, the program can't he
 
**Q: `I'm receiving a message about CloudFlare anti-bot, what do I need to do?`**
A: You need to successfully pass the CloudFlare anti-bot check and retrieve the cf_clearance Cookie that is set in the browser after successfully passing the check. This MUST be done from the same IP address and the same User-Agent string MUST be provided to chaturbate-dvr. Provide the cookie value and User-Agent string with the --cf-cookie and --user-agent command line options. The Cookie does expire, but it looks like it's Age is at ~1 year.
 
## 💬 Verbose Log
Change `-log-level` to `DEBUG` to see more details in terminal, like Duration and Size.

View File

@ -27,6 +27,8 @@ type Channel struct {
filenamePattern string
LastStreamedAt string
Interval int
CFCookie string
UserAgent string
Framerate int
Resolution int
ResolutionFallback string
@ -60,6 +62,7 @@ type Channel struct {
// Run
func (w *Channel) Run() {
if w.Username == "" {
w.log(LogTypeError, "username is empty, use `-u USERNAME` to specify")
return
@ -98,8 +101,11 @@ func (w *Channel) Run() {
w.log(LogTypeError, "release file: %v", err)
}
}
w.log(LogTypeInfo, "channel is offline, check again %d min(s) later", w.Interval)
if strings.Contains(body, "<title>Just a moment...</title>") {
w.log(logTypeError, "Cloudflare anti-bot page detected, Try providing cf-cookie and user-agent (Check GitHub for instructions)... Exiting")
os.Exit(1)
}
w.log(logTypeInfo, "channel is offline, check again %d min(s) later", w.Interval)
<-time.After(time.Duration(w.Interval) * time.Minute) // minutes cooldown to check online status
}
}

View File

@ -18,14 +18,30 @@ import (
// requestChannelBody requests the channel page and returns the body.
func (w *Channel) requestChannelBody() (string, error) {
transport := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: transport}
resp, err := client.Get(w.ChannelURL)
req, err := http.NewRequest("GET", w.ChannelURL, nil)
if err != nil {
return "", fmt.Errorf("client get: %w", err)
return "", fmt.Errorf("new request: %w", err)
}
if w.CFCookie != "" {
cookie := &http.Cookie{
Name: "cf_clearance",
Value: w.CFCookie,
}
req.AddCookie(cookie)
}
if w.UserAgent != "" {
req.Header.Set("User-Agent", w.UserAgent)
}
resp, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("client do: %w", err)
}
defer resp.Body.Close()
@ -100,9 +116,25 @@ func (w *Channel) resolveSource(body string) (string, string, error) {
}
client := &http.Client{Transport: transport}
resp, err := client.Get(roomData.HLSSource)
req, err := http.NewRequest("GET", roomData.HLSSource, nil)
if err != nil {
return "", "", fmt.Errorf("client get: %w", err)
return "", "", fmt.Errorf("new request: %w", err)
}
if w.CFCookie != "" {
cookie := &http.Cookie{
Name: "cf_clearance",
Value: w.CFCookie,
}
req.AddCookie(cookie)
}
if w.UserAgent != "" {
req.Header.Set("User-Agent", w.UserAgent)
}
resp, err := client.Do(req)
if err != nil {
return "", "", fmt.Errorf("client do: %w", err)
}
if resp.StatusCode != http.StatusOK {
switch resp.StatusCode {
@ -196,7 +228,8 @@ func (w *Channel) resolveSource(body string) (string, string, error) {
return rootURL, sourceURL, nil
}
// mergeSegments runs in the background and merges segments from the buffer to the file.
// mergeSegments is a async function that runs in background for the channel,
// and it merges the segments from buffer to the file.
func (w *Channel) mergeSegments() {
var segmentRetries int
startTime := time.Now() // Track the start time of the current segment.
@ -339,9 +372,25 @@ func (w *Channel) requestChunks() ([]*m3u8.MediaSegment, float64, error) {
return nil, 0, fmt.Errorf("channel seems to be paused?")
}
resp, err := client.Get(w.sourceURL)
req, err := http.NewRequest("GET", w.sourceURL, nil)
if err != nil {
return nil, 3, fmt.Errorf("client get: %w", err)
return nil, 0, fmt.Errorf("new request: %w", err)
}
if w.CFCookie != "" {
cookie := &http.Cookie{
Name: "cf_clearance",
Value: w.CFCookie,
}
req.AddCookie(cookie)
}
if w.UserAgent != "" {
req.Header.Set("User-Agent", w.UserAgent)
}
resp, err := client.Do(req)
if err != nil {
return nil, 3, fmt.Errorf("client do: %w", err)
}
if resp.StatusCode != http.StatusOK {
switch resp.StatusCode {
@ -372,6 +421,7 @@ func (w *Channel) requestChunks() ([]*m3u8.MediaSegment, float64, error) {
return chunks, playlist.TargetDuration, nil
}
// requestSegment requests the specific single segment and put it into the buffer.
// the mergeSegments function will merge the segment from buffer to the file in the backgrond.
func (w *Channel) requestSegment(url string, index int) error {
@ -384,9 +434,25 @@ func (w *Channel) requestSegment(url string, index int) error {
return fmt.Errorf("channel seems to be paused?")
}
resp, err := client.Get(w.rootURL + url)
req, err := http.NewRequest("GET", w.rootURL+url, nil)
if err != nil {
return fmt.Errorf("client get: %w", err)
return fmt.Errorf("new request: %w", err)
}
if w.CFCookie != "" {
cookie := &http.Cookie{
Name: "cf_clearance",
Value: w.CFCookie,
}
req.AddCookie(cookie)
}
if w.UserAgent != "" {
req.Header.Set("User-Agent", w.UserAgent)
}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("client do: %w", err)
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("received status code %d", resp.StatusCode)
@ -407,3 +473,4 @@ func (w *Channel) requestSegment(url string, index int) error {
return nil
}

View File

@ -4,7 +4,6 @@ import (
"encoding/json"
"errors"
"os"
"github.com/google/uuid"
"github.com/urfave/cli/v2"
)
@ -32,6 +31,8 @@ type Config struct {
SplitDuration int
SplitFilesize int
Interval int
CFCookie string
UserAgent string
}
// Manager
@ -39,10 +40,12 @@ type Manager struct {
cli *cli.Context
Channels map[string]*Channel
Updates map[string]chan *Update
}
// NewManager
func NewManager(c *cli.Context) *Manager {
return &Manager{
cli: c,
Channels: map[string]*Channel{},
@ -101,6 +104,8 @@ func (m *Manager) CreateChannel(conf *Config) error {
Resolution: conf.Resolution,
ResolutionFallback: conf.ResolutionFallback,
Interval: conf.Interval,
CFCookie: m.cli.String("cf-cookie"),
UserAgent: m.cli.String("user-agent"),
LastStreamedAt: "-",
SegmentDuration: 0,
SplitDuration: conf.SplitDuration,

2
go.mod
View File

@ -1,6 +1,6 @@
module github.com/teacat/chaturbate-dvr
go 1.22.0
go 1.22
require (
github.com/gin-gonic/gin v1.9.1

16
main.go
View File

@ -104,6 +104,16 @@ func main() {
Usage: "minutes to check if the channel is online",
Value: 1,
},
&cli.StringFlag{
Name: "cf-cookie",
Usage: "Cloudflare cookie to bypass anti-bot page",
Value: "",
},
&cli.StringFlag{
Name: "user-agent",
Usage: "Custom user agent for when using cf-cookie",
Value: "",
},
//&cli.StringFlag{
// Name: "gui",
// Usage: "enabling GUI, availables: 'no', 'web'",
@ -119,6 +129,9 @@ func main() {
func start(c *cli.Context) error {
fmt.Println(logo)
if c.String("cf-cookie") != "" && c.String("user-agent") == ""{
return fmt.Errorf("When using the cf-cookie option a user-agent MUST be supplied")
}
//if c.String("gui") == "web" {
if c.String("username") == "" {
@ -135,6 +148,8 @@ func start(c *cli.Context) error {
SplitDuration: c.Int("split-duration"),
SplitFilesize: c.Int("split-filesize"),
Interval: c.Int("interval"),
CFCookie: c.String("cf-cookie"),
UserAgent: c.String("user-agent"),
}); err != nil {
return err
}
@ -159,7 +174,6 @@ func startWeb(c *cli.Context) error {
if err != nil {
log.Fatalln(err)
}
guiUsername := c.String("gui-username")
guiPassword := c.String("gui-password")