First Release

This commit is contained in:
Mukhtar Akere
2024-08-25 05:36:47 +01:00
commit d54706fed6
20 changed files with 1682 additions and 0 deletions
+57
View File
@@ -0,0 +1,57 @@
package common
import (
"encoding/json"
"log"
"os"
)
type DebridConfig struct {
Name string `json:"name"`
Host string `json:"host"`
APIKey string `json:"api_key"`
Folder string `json:"folder"`
DownloadUncached bool `json:"download_uncached"`
RateLimit string `json:"rate_limit"` // 200/minute or 10/second
}
type Config struct {
DbDSN string `json:"db_dsn"`
Debrid DebridConfig `json:"debrid"`
Arrs []struct {
WatchFolder string `json:"watch_folder"`
CompletedFolder string `json:"completed_folder"`
Token string `json:"token"`
URL string `json:"url"`
} `json:"arrs"`
Proxy struct {
Port string `json:"port"`
Enabled bool `json:"enabled"`
Debug bool `json:"debug"`
Username string `json:"username"`
Password string `json:"password"`
}
}
func LoadConfig(path string) (*Config, error) {
// Load the config file
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer func(file *os.File) {
err := file.Close()
if err != nil {
log.Fatal(err)
}
}(file)
decoder := json.NewDecoder(file)
config := &Config{}
err = decoder.Decode(config)
if err != nil {
return nil, err
}
return config, nil
}
+27
View File
@@ -0,0 +1,27 @@
package common
import (
"regexp"
)
var (
VIDEOMATCH = "(?i)(\\.)(YUV|WMV|WEBM|VOB|VIV|SVI|ROQ|RMVB|RM|OGV|OGG|NSV|MXF|MTS|M2TS|TS|MPG|MPEG|M2V|MP2|MPE|MPV|MP4|M4P|M4V|MOV|QT|MNG|MKV|FLV|DRC|AVI|ASF|AMV)$"
SUBMATCH = "(?i)(\\.)(SRT|SUB|SBV|ASS|VTT|TTML|DFXP|STL|SCC|CAP|SMI|TTXT|TDS|USF|JSS|SSA|PSB|RT|LRC|SSB)$"
)
func RegexMatch(regex string, value string) bool {
re := regexp.MustCompile(regex)
return re.MatchString(value)
}
func RemoveExtension(value string) string {
re := regexp.MustCompile(VIDEOMATCH)
// Find the last index of the matched extension
loc := re.FindStringIndex(value)
if loc != nil {
return value[:loc[0]]
} else {
return value
}
}
+130
View File
@@ -0,0 +1,130 @@
package common
import (
"crypto/tls"
"fmt"
"golang.org/x/time/rate"
"io"
"log"
"net/http"
"regexp"
"strconv"
"time"
)
type RLHTTPClient struct {
client *http.Client
Ratelimiter *rate.Limiter
Headers map[string]string
}
func (c *RLHTTPClient) Doer(req *http.Request) (*http.Response, error) {
if c.Ratelimiter != nil {
err := c.Ratelimiter.Wait(req.Context())
if err != nil {
return nil, err
}
}
resp, err := c.client.Do(req)
if err != nil {
return nil, err
}
return resp, nil
}
func (c *RLHTTPClient) Do(req *http.Request) (*http.Response, error) {
var resp *http.Response
var err error
backoff := time.Millisecond * 500
for i := 0; i < 3; i++ {
resp, err = c.Doer(req)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusTooManyRequests {
return resp, nil
}
// Close the response body to prevent resource leakage
resp.Body.Close()
// Wait for the backoff duration before retrying
time.Sleep(backoff)
// Exponential backoff
backoff *= 2
}
return resp, fmt.Errorf("max retries exceeded")
}
func (c *RLHTTPClient) MakeRequest(method string, url string, body io.Reader) ([]byte, error) {
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
if c.Headers != nil {
for key, value := range c.Headers {
req.Header.Set(key, value)
}
}
res, err := c.Do(req)
if err != nil {
return nil, err
}
statusOk := strconv.Itoa(res.StatusCode)[0] == '2'
if !statusOk {
return nil, fmt.Errorf("unexpected status code: %d", res.StatusCode)
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
log.Println(err)
}
}(res.Body)
return io.ReadAll(res.Body)
}
func NewRLHTTPClient(rl *rate.Limiter, headers map[string]string) *RLHTTPClient {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
c := &RLHTTPClient{
client: &http.Client{
Transport: tr,
},
Ratelimiter: rl,
Headers: headers,
}
return c
}
func ParseRateLimit(rateStr string) *rate.Limiter {
if rateStr == "" {
return nil
}
re := regexp.MustCompile(`(\d+)/(minute|second)`)
matches := re.FindStringSubmatch(rateStr)
if len(matches) != 3 {
return nil
}
count, err := strconv.Atoi(matches[1])
if err != nil {
return nil
}
unit := matches[2]
switch unit {
case "minute":
reqsPerSecond := float64(count) / 60.0
return rate.NewLimiter(rate.Limit(reqsPerSecond), 5)
case "second":
return rate.NewLimiter(rate.Limit(float64(count)), 5)
default:
return nil
}
}
+83
View File
@@ -0,0 +1,83 @@
package common
import (
"bufio"
"fmt"
"log"
"math/rand"
"net/url"
"os"
"strings"
)
type Magnet struct {
Name string
InfoHash string
Size int64
Link string
}
func OpenMagnetFile(filePath string) string {
file, err := os.Open(filePath)
if err != nil {
log.Println("Error opening file:", err)
return ""
}
defer func(file *os.File) {
err := file.Close()
if err != nil {
return
}
}(file) // Ensure the file is closed after the function ends
// Create a scanner to read the file line by line
scanner := bufio.NewScanner(file)
for scanner.Scan() {
magnetLink := scanner.Text()
if magnetLink != "" {
return magnetLink
}
}
// Check for any errors during scanning
if err := scanner.Err(); err != nil {
log.Println("Error reading file:", err)
}
return ""
}
func GetMagnetInfo(magnetLink string) (*Magnet, error) {
if magnetLink == "" {
return nil, fmt.Errorf("error getting magnet from file")
}
magnetURI, err := url.Parse(magnetLink)
if err != nil {
return nil, fmt.Errorf("error parsing magnet link")
}
query := magnetURI.Query()
xt := query.Get("xt")
dn := query.Get("dn")
// Extract BTIH
parts := strings.Split(xt, ":")
btih := ""
if len(parts) > 2 {
btih = parts[2]
}
magnet := &Magnet{
InfoHash: btih,
Name: dn,
Size: 0,
Link: magnetLink,
}
return magnet, nil
}
func RandomString(length int) string {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
b := make([]byte, length)
for i := range b {
b[i] = charset[rand.Intn(len(charset))]
}
return string(b)
}