2.0.0 refactor

This commit is contained in:
Yami Odymel
2025-05-02 23:52:58 +08:00
parent cad4689a5c
commit f26602b49e
51 changed files with 2108 additions and 2989 deletions

56
internal/internal.go Normal file
View File

@@ -0,0 +1,56 @@
package internal
import (
"fmt"
"regexp"
"strconv"
)
// FormatDuration converts a float64 duration (in seconds) to h:m:s format.
func FormatDuration(duration float64) string {
if duration == 0 {
return ""
}
var (
hours = int(duration) / 3600
minutes = (int(duration) % 3600) / 60
seconds = int(duration) % 60
)
return fmt.Sprintf("%d:%02d:%02d", hours, minutes, seconds)
}
// FormatFilesize converts an int filesize in bytes to a human-readable string (KB, MB, GB).
func FormatFilesize(filesize int) string {
if filesize == 0 {
return ""
}
const (
KB = 1024
MB = KB * 1024
GB = MB * 1024
)
switch {
case filesize >= GB:
return fmt.Sprintf("%.2f GB", float64(filesize)/float64(GB))
case filesize >= MB:
return fmt.Sprintf("%.2f MB", float64(filesize)/float64(MB))
case filesize >= KB:
return fmt.Sprintf("%.2f KB", float64(filesize)/float64(KB))
default:
return fmt.Sprintf("%d bytes", filesize)
}
}
// SegmentSeq extracts the segment sequence number from a filename.
func SegmentSeq(filename string) int {
re := regexp.MustCompile(`_(\d+)\.ts$`)
match := re.FindStringSubmatch(filename)
if len(match) > 1 {
number, err := strconv.Atoi(match[1])
if err == nil {
return number
}
}
return -1
}

13
internal/internal_err.go Normal file
View File

@@ -0,0 +1,13 @@
package internal
import "errors"
var (
ErrChannelExists = errors.New("channel exists")
ErrChannelNotFound = errors.New("channel not found")
ErrCloudflareBlocked = errors.New("blocked by Cloudflare; try with `-cookies` and `-user-agent`")
ErrChannelOffline = errors.New("channel offline")
ErrPrivateStream = errors.New("channel went offline or private")
ErrPaused = errors.New("channel paused")
ErrStopped = errors.New("channel stopped")
)

117
internal/internal_req.go Normal file
View File

@@ -0,0 +1,117 @@
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 {
return &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
}
// 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()
if resp.StatusCode == http.StatusForbidden {
return nil, fmt.Errorf("forbidden: %w", ErrPrivateStream)
}
return ReadResponseBody(resp)
}
// 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) {
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})
}
}
}
// 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)
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
}