- Deprecate proxy

- Add Proxy for each debrid
- Add support for multiple-API keys
- Use internal http.Client for streaming
- Bug fixes etc
This commit is contained in:
Mukhtar Akere
2025-04-08 17:30:24 +01:00
parent 4659cd4273
commit 4b5e18df94
23 changed files with 788 additions and 599 deletions
+20 -26
View File
@@ -7,7 +7,6 @@ import (
"github.com/goccy/go-json"
"os"
"path/filepath"
"strings"
"sync"
)
@@ -18,14 +17,15 @@ var (
)
type Debrid struct {
Name string `json:"name"`
Host string `json:"host"`
APIKey string `json:"api_key"`
DownloadAPIKeys string `json:"download_api_keys"`
Folder string `json:"folder"`
DownloadUncached bool `json:"download_uncached"`
CheckCached bool `json:"check_cached"`
RateLimit string `json:"rate_limit"` // 200/minute or 10/second
Name string `json:"name"`
Host string `json:"host"`
APIKey string `json:"api_key"`
DownloadAPIKeys []string `json:"download_api_keys"`
Folder string `json:"folder"`
DownloadUncached bool `json:"download_uncached"`
CheckCached bool `json:"check_cached"`
RateLimit string `json:"rate_limit"` // 200/minute or 10/second
Proxy string `json:"proxy"`
UseWebDav bool `json:"use_webdav"`
WebDav
@@ -192,15 +192,15 @@ func validateDebrids(debrids []Debrid) error {
return nil
}
func validateQbitTorrent(config *QBitTorrent) error {
if config.DownloadFolder == "" {
return errors.New("qbittorent download folder is required")
}
if _, err := os.Stat(config.DownloadFolder); os.IsNotExist(err) {
return fmt.Errorf("qbittorent download folder(%s) does not exist", config.DownloadFolder)
}
return nil
}
//func validateQbitTorrent(config *QBitTorrent) error {
// if config.DownloadFolder == "" {
// return errors.New("qbittorent download folder is required")
// }
// if _, err := os.Stat(config.DownloadFolder); os.IsNotExist(err) {
// return fmt.Errorf("qbittorent download folder(%s) does not exist", config.DownloadFolder)
// }
// return nil
//}
func validateConfig(config *Config) error {
// Run validations concurrently
@@ -299,15 +299,9 @@ func (c *Config) NeedsSetup() bool {
func (c *Config) updateDebrid(d Debrid) Debrid {
downloadAPIKeys := strings.Split(d.DownloadAPIKeys, ",")
newApiKeys := make([]string, 0, len(downloadAPIKeys))
for _, key := range downloadAPIKeys {
key = strings.TrimSpace(key)
if key != "" {
newApiKeys = append(newApiKeys, key)
}
if len(d.DownloadAPIKeys) == 0 {
d.DownloadAPIKeys = append(d.DownloadAPIKeys, d.APIKey)
}
d.DownloadAPIKeys = strings.Join(newApiKeys, ",")
if !d.UseWebDav {
return d
+71 -12
View File
@@ -3,15 +3,18 @@ package request
import (
"bytes"
"compress/gzip"
"context"
"crypto/tls"
"fmt"
"github.com/goccy/go-json"
"github.com/rs/zerolog"
"github.com/sirrobot01/debrid-blackhole/internal/logger"
"golang.org/x/net/proxy"
"golang.org/x/time/rate"
"io"
"math"
"math/rand"
"net"
"net/http"
"net/url"
"regexp"
@@ -52,11 +55,13 @@ type Client struct {
client *http.Client
rateLimiter *rate.Limiter
headers map[string]string
headersMu sync.RWMutex
maxRetries int
timeout time.Duration
skipTLSVerify bool
retryableStatus map[int]bool
logger zerolog.Logger
proxy string
}
// WithMaxRetries sets the maximum number of retry attempts
@@ -89,12 +94,16 @@ func WithRateLimiter(rl *rate.Limiter) ClientOption {
// WithHeaders sets default headers
func WithHeaders(headers map[string]string) ClientOption {
return func(c *Client) {
c.headersMu.Lock()
c.headers = headers
c.headersMu.Unlock()
}
}
func (c *Client) SetHeader(key, value string) {
c.headersMu.Lock()
c.headers[key] = value
c.headersMu.Unlock()
}
func WithLogger(logger zerolog.Logger) ClientOption {
@@ -118,6 +127,12 @@ func WithRetryableStatus(statusCodes ...int) ClientOption {
}
}
func WithProxy(proxyURL string) ClientOption {
return func(c *Client) {
c.proxy = proxyURL
}
}
// doRequest performs a single HTTP request with rate limiting
func (c *Client) doRequest(req *http.Request) (*http.Response, error) {
if c.rateLimiter != nil {
@@ -154,11 +169,13 @@ func (c *Client) Do(req *http.Request) (*http.Response, error) {
}
// Apply headers
c.headersMu.RLock()
if c.headers != nil {
for key, value := range c.headers {
req.Header.Set(key, value)
}
}
c.headersMu.RUnlock()
resp, err = c.doRequest(req)
if err != nil {
@@ -256,25 +273,67 @@ func New(options ...ClientOption) *Client {
},
logger: logger.New("request"),
timeout: 60 * time.Second,
proxy: "",
headers: make(map[string]string), // Initialize headers map
}
// Apply options
// default http client
client.client = &http.Client{
Timeout: client.timeout,
}
// Apply options before configuring transport
for _, option := range options {
option(client)
}
// Create transport
transport := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: client.skipTLSVerify,
},
Proxy: http.ProxyFromEnvironment,
}
// Check if transport was set by WithTransport option
if client.client.Transport == nil {
// No custom transport provided, create the default one
transport := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: client.skipTLSVerify,
},
}
// Create HTTP client
client.client = &http.Client{
Transport: transport,
Timeout: client.timeout,
// Configure proxy if needed
if client.proxy != "" {
if strings.HasPrefix(client.proxy, "socks5://") {
// Handle SOCKS5 proxy
socksURL, err := url.Parse(client.proxy)
if err != nil {
client.logger.Error().Msgf("Failed to parse SOCKS5 proxy URL: %v", err)
} else {
auth := &proxy.Auth{}
if socksURL.User != nil {
auth.User = socksURL.User.Username()
password, _ := socksURL.User.Password()
auth.Password = password
}
dialer, err := proxy.SOCKS5("tcp", socksURL.Host, auth, proxy.Direct)
if err != nil {
client.logger.Error().Msgf("Failed to create SOCKS5 dialer: %v", err)
} else {
transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
return dialer.Dial(network, addr)
}
}
}
} else {
proxyURL, err := url.Parse(client.proxy)
if err != nil {
client.logger.Error().Msgf("Failed to parse proxy URL: %v", err)
} else {
transport.Proxy = http.ProxyURL(proxyURL)
}
}
} else {
transport.Proxy = http.ProxyFromEnvironment
}
// Set the transport to the client
client.client.Transport = transport
}
return client
+1 -1
View File
@@ -11,7 +11,7 @@ var (
MUSICMATCH = "(?i)(\\.)(mp2|mp3|m4a|m4b|m4p|ogg|oga|opus|wma|wav|wv|flac|ape|aif|aiff|aifc)$"
)
var SAMPLEMATCH = `(?i)(^|[\\/]|\s|[(])(?:sample|trailer|thumb|special|extras?)s?([\s._\-/)]|$)`
var SAMPLEMATCH = `(?i)(^|[\\/])(sample|trailer|thumb|special|extras?)s?([\s._-]|$|/)|(\(sample\))|(-\s*sample)`
func RegexMatch(regex string, value string) bool {
re := regexp.MustCompile(regex)