mirror of
https://github.com/teacat/chaturbate-dvr.git
synced 2025-10-29 16:59:59 +00:00
131 lines
3.4 KiB
Go
131 lines
3.4 KiB
Go
package internal
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/teacat/chaturbate-dvr/server"
|
|
)
|
|
|
|
// Req represents an HTTP client with customized settings.
|
|
type Req struct {
|
|
client *http.Client
|
|
}
|
|
|
|
// NewReq creates a new HTTP client with specific transport configurations.
|
|
func NewReq() *Req {
|
|
return &Req{
|
|
client: &http.Client{
|
|
Transport: CreateTransport(),
|
|
},
|
|
}
|
|
}
|
|
|
|
// CreateTransport initializes a custom HTTP transport.
|
|
func CreateTransport() *http.Transport {
|
|
// The DefaultTransport allows user changes the proxy settings via environment variables
|
|
// such as HTTP_PROXY, HTTPS_PROXY.
|
|
defaultTransport := http.DefaultTransport.(*http.Transport)
|
|
|
|
newTransport := defaultTransport.Clone()
|
|
newTransport.TLSClientConfig = &tls.Config{
|
|
InsecureSkipVerify: true,
|
|
}
|
|
return newTransport
|
|
}
|
|
|
|
// Get sends an HTTP GET request and returns the response as a string.
|
|
func (h *Req) Get(ctx context.Context, url string) (string, error) {
|
|
resp, err := h.GetBytes(ctx, url)
|
|
if err != nil {
|
|
return "", fmt.Errorf("get bytes: %w", err)
|
|
}
|
|
return string(resp), nil
|
|
}
|
|
|
|
// GetBytes sends an HTTP GET request and returns the response as a byte slice.
|
|
func (h *Req) GetBytes(ctx context.Context, url string) ([]byte, error) {
|
|
req, cancel, err := CreateRequest(ctx, url)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("new request: %w", err)
|
|
}
|
|
defer cancel()
|
|
|
|
resp, err := h.client.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("client do: %w", err)
|
|
}
|
|
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), "<title>Just a moment...</title>") {
|
|
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 b, err
|
|
}
|
|
|
|
// CreateRequest constructs an HTTP GET request with necessary headers.
|
|
func CreateRequest(ctx context.Context, url string) (*http.Request, context.CancelFunc, error) {
|
|
ctx, cancel := context.WithTimeout(ctx, 10*time.Second) // timed out after 10 seconds
|
|
|
|
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
|
if err != nil {
|
|
return nil, cancel, err
|
|
}
|
|
SetRequestHeaders(req)
|
|
return req, cancel, nil
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
if server.Config.Cookies != "" {
|
|
cookies := ParseCookies(server.Config.Cookies)
|
|
for name, value := range cookies {
|
|
req.AddCookie(&http.Cookie{Name: name, Value: value})
|
|
}
|
|
}
|
|
}
|
|
|
|
// ParseCookies converts a cookie string into a map.
|
|
func ParseCookies(cookieStr string) map[string]string {
|
|
cookies := make(map[string]string)
|
|
pairs := strings.Split(cookieStr, ";")
|
|
|
|
// Iterate over each cookie pair and extract key-value pairs
|
|
for _, pair := range pairs {
|
|
parts := strings.SplitN(strings.TrimSpace(pair), "=", 2)
|
|
if len(parts) == 2 {
|
|
// Trim spaces around key and value
|
|
key := strings.TrimSpace(parts[0])
|
|
value := strings.TrimSpace(parts[1])
|
|
// Store cookie name and value in the map
|
|
cookies[key] = value
|
|
}
|
|
}
|
|
return cookies
|
|
}
|