Random Fixes:

- Fix uncached error
- Fix naming and race conditions
This commit is contained in:
Mukhtar Akere
2024-09-14 01:42:52 +01:00
parent 9511f3e99e
commit f622cbfe63
12 changed files with 108 additions and 75 deletions

View File

@@ -15,25 +15,29 @@ type DebridConfig struct {
RateLimit string `json:"rate_limit"` // 200/minute or 10/second RateLimit string `json:"rate_limit"` // 200/minute or 10/second
} }
type ProxyConfig struct {
Port string `json:"port"`
Enabled bool `json:"enabled"`
Debug bool `json:"debug"`
Username string `json:"username"`
Password string `json:"password"`
CachedOnly *bool `json:"cached_only"`
}
type QBitTorrentConfig struct {
Username string `json:"username"`
Password string `json:"password"`
Port string `json:"port"`
Debug bool `json:"debug"`
DownloadFolder string `json:"download_folder"`
Categories []string `json:"categories"`
}
type Config struct { type Config struct {
Debrid DebridConfig `json:"debrid"` Debrid DebridConfig `json:"debrid"`
Proxy struct { Proxy ProxyConfig `json:"proxy"`
Port string `json:"port"` MaxCacheSize int `json:"max_cache_size"`
Enabled bool `json:"enabled"` QBitTorrent QBitTorrentConfig `json:"qbittorrent"`
Debug bool `json:"debug"`
Username string `json:"username"`
Password string `json:"password"`
CachedOnly *bool `json:"cached_only"`
}
MaxCacheSize int `json:"max_cache_size"`
QBitTorrent struct {
Username string `json:"username"`
Password string `json:"password"`
Port string `json:"port"`
Debug bool `json:"debug"`
DownloadFolder string `json:"download_folder"`
Categories []string `json:"categories"`
} `json:"qbittorrent"`
} }
func LoadConfig(path string) (*Config, error) { func LoadConfig(path string) (*Config, error) {

View File

@@ -5,7 +5,7 @@ import (
) )
var ( 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|MKA|F4V|3GP|3G2|DIVX|X264|X265)$" VIDEOMATCH = "(?i)(\\.)(YUV|WMV|WEBM|VOB|VIV|SVI|ROQ|RMVB|RM|OGV|OGG|NSV|MXF|MPG|MPEG|M2V|MP2|MPE|MPV|MP4|M4P|M4V|MOV|QT|MNG|MKV|FLV|DRC|AVI|ASF|AMV|MKA|F4V|3GP|3G2|DIVX|X264|X265)$"
SUBMATCH = "(?i)(\\.)(SRT|SUB|SBV|ASS|VTT|TTML|DFXP|STL|SCC|CAP|SMI|TTXT|TDS|USF|JSS|SSA|PSB|RT|LRC|SSB)$" SUBMATCH = "(?i)(\\.)(SRT|SUB|SBV|ASS|VTT|TTML|DFXP|STL|SCC|CAP|SMI|TTXT|TDS|USF|JSS|SSA|PSB|RT|LRC|SSB)$"
SAMPLEMATCH = `(?i)(^|[\\/]|[._-])(sample|trailer|thumb)s?([._-]|$)` SAMPLEMATCH = `(?i)(^|[\\/]|[._-])(sample|trailer|thumb)s?([._-]|$)`
) )
@@ -16,8 +16,7 @@ func RegexMatch(regex string, value string) bool {
} }
func RemoveExtension(value string) string { func RemoveExtension(value string) string {
pattern := "(?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|MKA|F4V|3GP|3G2|DIVX|X264|X265)$" re := regexp.MustCompile(VIDEOMATCH)
re := regexp.MustCompile(pattern)
// Find the last index of the matched extension // Find the last index of the matched extension
loc := re.FindStringIndex(value) loc := re.FindStringIndex(value)

View File

@@ -2,6 +2,7 @@ package common
import ( import (
"bufio" "bufio"
"context"
"encoding/base32" "encoding/base32"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
@@ -15,6 +16,7 @@ import (
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings" "strings"
"time"
) )
type Magnet struct { type Magnet struct {
@@ -211,3 +213,44 @@ func NewLogger(prefix string, output *os.File) *log.Logger {
f := fmt.Sprintf("[%s] ", prefix) f := fmt.Sprintf("[%s] ", prefix)
return log.New(output, f, log.LstdFlags) return log.New(output, f, log.LstdFlags)
} }
func GetInfohashFromURL(url string) (string, error) {
// Download the torrent file
var magnetLink string
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
client := &http.Client{
Timeout: 30 * time.Second,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
if len(via) >= 3 {
return fmt.Errorf("stopped after 3 redirects")
}
if strings.HasPrefix(req.URL.String(), "magnet:") {
// Stop the redirect chain
magnetLink = req.URL.String()
return http.ErrUseLastResponse
}
return nil
},
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return "", err
}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
if magnetLink != "" {
return ExtractInfoHash(magnetLink), nil
}
mi, err := metainfo.Load(resp.Body)
if err != nil {
return "", err
}
hash := mi.HashInfoBytes()
infoHash := hash.HexString()
return infoHash, nil
}

4
go.mod
View File

@@ -6,8 +6,8 @@ require (
github.com/anacrolix/torrent v1.55.0 github.com/anacrolix/torrent v1.55.0
github.com/elazarl/goproxy v0.0.0-20240726154733-8b0c20506380 github.com/elazarl/goproxy v0.0.0-20240726154733-8b0c20506380
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2
github.com/fsnotify/fsnotify v1.7.0
github.com/go-chi/chi/v5 v5.1.0 github.com/go-chi/chi/v5 v5.1.0
github.com/google/uuid v1.6.0
github.com/valyala/fastjson v1.6.4 github.com/valyala/fastjson v1.6.4
golang.org/x/time v0.6.0 golang.org/x/time v0.6.0
) )
@@ -17,9 +17,7 @@ require (
github.com/anacrolix/missinggo/v2 v2.7.3 // indirect github.com/anacrolix/missinggo/v2 v2.7.3 // indirect
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect
github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-cmp v0.6.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/huandu/xstrings v1.3.2 // indirect github.com/huandu/xstrings v1.3.2 // indirect
golang.org/x/net v0.27.0 // indirect golang.org/x/net v0.27.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect golang.org/x/text v0.16.0 // indirect
) )

4
go.sum
View File

@@ -62,8 +62,6 @@ github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
github.com/glycerine/go-unsnap-stream v0.0.0-20190901134440-81cf024a9e0a/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/go-unsnap-stream v0.0.0-20190901134440-81cf024a9e0a/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
@@ -233,8 +231,6 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=

View File

@@ -12,7 +12,6 @@ type Service interface {
SubmitMagnet(torrent *Torrent) (*Torrent, error) SubmitMagnet(torrent *Torrent) (*Torrent, error)
CheckStatus(torrent *Torrent) (*Torrent, error) CheckStatus(torrent *Torrent) (*Torrent, error)
DownloadLink(torrent *Torrent) error DownloadLink(torrent *Torrent) error
Process(arr *Arr, magnet string) (*Torrent, error)
IsAvailable(infohashes []string) map[string]bool IsAvailable(infohashes []string) map[string]bool
GetMountPath() string GetMountPath() string
GetDownloadUncached() bool GetDownloadUncached() bool
@@ -132,17 +131,19 @@ func ProcessQBitTorrent(d Service, magnet *common.Magnet, category string) (*Tor
Size: magnet.Size, Size: magnet.Size,
} }
logger := d.GetLogger() logger := d.GetLogger()
logger.Printf("Torrent Name: %s", debridTorrent.Name) logger.Printf("Torrent Hash: %s", debridTorrent.InfoHash)
if !d.GetDownloadUncached() { if !d.GetDownloadUncached() {
hash, exists := d.IsAvailable([]string{debridTorrent.InfoHash})[debridTorrent.InfoHash] hash, exists := d.IsAvailable([]string{debridTorrent.InfoHash})[debridTorrent.InfoHash]
if !exists || !hash { if !exists || !hash {
return debridTorrent, fmt.Errorf("torrent is not cached") return debridTorrent, fmt.Errorf("torrent: %s is not cached", debridTorrent.Name)
} else {
logger.Printf("Torrent: %s is cached", debridTorrent.Name)
} }
logger.Printf("Torrent: %s is cached", debridTorrent.Name)
} }
debridTorrent, err := d.SubmitMagnet(debridTorrent) debridTorrent, err := d.SubmitMagnet(debridTorrent)
if err != nil || debridTorrent.Id == "" { if err != nil || debridTorrent.Id == "" {
logger.Printf("Error submitting magnet: %s", err)
return nil, err return nil, err
} }
return d.CheckStatus(debridTorrent) return d.CheckStatus(debridTorrent)

View File

@@ -55,28 +55,6 @@ func GetTorrentFiles(data structs.RealDebridTorrentInfo) []TorrentFile {
return files return files
} }
func (r *RealDebrid) Process(arr *Arr, magnet string) (*Torrent, error) {
torrent, err := GetTorrentInfo(magnet)
torrent.Arr = arr
if err != nil {
return torrent, err
}
log.Printf("Torrent Name: %s", torrent.Name)
if !r.DownloadUncached {
hash, exists := r.IsAvailable([]string{torrent.InfoHash})[torrent.InfoHash]
if !exists || !hash {
return torrent, fmt.Errorf("torrent is not cached")
}
log.Printf("Torrent: %s is cached", torrent.Name)
}
torrent, err = r.SubmitMagnet(torrent)
if err != nil || torrent.Id == "" {
return nil, err
}
return r.CheckStatus(torrent)
}
func (r *RealDebrid) IsAvailable(infohashes []string) map[string]bool { func (r *RealDebrid) IsAvailable(infohashes []string) map[string]bool {
// Check if the infohashes are available in the local cache // Check if the infohashes are available in the local cache
hashes, result := GetLocalCache(infohashes, r.cache) hashes, result := GetLocalCache(infohashes, r.cache)
@@ -183,7 +161,9 @@ func (r *RealDebrid) CheckStatus(torrent *Torrent) (*Torrent, error) {
var data structs.RealDebridTorrentInfo var data structs.RealDebridTorrentInfo
err = json.Unmarshal(resp, &data) err = json.Unmarshal(resp, &data)
status := data.Status status := data.Status
torrent.Folder = common.RemoveExtension(data.OriginalFilename) name := common.RemoveExtension(data.OriginalFilename)
torrent.Name = name // Important because some magnet changes the name
torrent.Folder = name
torrent.Bytes = data.Bytes torrent.Bytes = data.Bytes
torrent.Progress = data.Progress torrent.Progress = data.Progress
torrent.Speed = data.Speed torrent.Speed = data.Speed

View File

@@ -217,6 +217,13 @@ func (item Item) getHash() string {
//if err == nil && magnet != nil && magnet.InfoHash != "" { //if err == nil && magnet != nil && magnet.InfoHash != "" {
// log.Printf("Magnet: %s", magnet.InfoHash) // log.Printf("Magnet: %s", magnet.InfoHash)
//} //}
if strings.Contains(magnetLink, "http") {
h, _ := common.GetInfohashFromURL(magnetLink)
if h != "" {
infohash = h
}
}
} }
return infohash return infohash
@@ -249,7 +256,6 @@ func (p *Proxy) ProcessXMLResponse(resp *http.Response) *http.Response {
if len(rss.Channel.Items) > 0 { if len(rss.Channel.Items) > 0 {
indexer = rss.Channel.Items[0].ProwlarrIndexer.Text indexer = rss.Channel.Items[0].ProwlarrIndexer.Text
} else { } else {
p.logger.Println("No items found in RSS feed")
resp.Body = io.NopCloser(bytes.NewReader(body)) resp.Body = io.NopCloser(bytes.NewReader(body))
return resp return resp
} }
@@ -283,6 +289,8 @@ func (p *Proxy) ProcessXMLResponse(resp *http.Response) *http.Response {
p.logger.Printf("[%s Report]: %d/%d items are cached || Found %d infohash", indexer, len(newItems), len(rss.Channel.Items), len(hashes)) p.logger.Printf("[%s Report]: %d/%d items are cached || Found %d infohash", indexer, len(newItems), len(rss.Channel.Items), len(hashes))
} else { } else {
// This will prevent the indexer from being disabled by the arr // This will prevent the indexer from being disabled by the arr
//filename := common.RandomString(10) + ".xml"
//_ = os.WriteFile(filename, body, 0644)
p.logger.Printf("[%s Report]: No Items are cached; Return only first item with [UnCached]", indexer) p.logger.Printf("[%s Report]: No Items are cached; Return only first item with [UnCached]", indexer)
item := rss.Channel.Items[0] item := rss.Channel.Items[0]
item.Title = fmt.Sprintf("%s [UnCached]", item.Title) item.Title = fmt.Sprintf("%s [UnCached]", item.Title)

View File

@@ -37,15 +37,9 @@ func (q *QBit) handleTorrentsAdd(w http.ResponseWriter, r *http.Request) {
} }
} }
files := r.MultipartForm.File["torrents"]
urls := r.FormValue("urls") urls := r.FormValue("urls")
category := r.FormValue("category") category := r.FormValue("category")
if len(files) == 0 && urls == "" {
http.Error(w, "No torrent provided", http.StatusBadRequest)
return
}
var urlList []string var urlList []string
if urls != "" { if urls != "" {
urlList = strings.Split(urls, "\n") urlList = strings.Split(urls, "\n")
@@ -62,18 +56,22 @@ func (q *QBit) handleTorrentsAdd(w http.ResponseWriter, r *http.Request) {
} }
for _, fileHeader := range files { if contentType == "multipart/form-data" {
file, _ := fileHeader.Open() files := r.MultipartForm.File["torrents"]
defer file.Close() for _, fileHeader := range files {
var reader io.Reader = file file, _ := fileHeader.Open()
magnet, err := common.GetMagnetFromFile(reader, fileHeader.Filename) defer file.Close()
if err != nil { var reader io.Reader = file
http.Error(w, err.Error(), http.StatusBadRequest) magnet, err := common.GetMagnetFromFile(reader, fileHeader.Filename)
q.logger.Printf("Error reading file: %s", fileHeader.Filename) if err != nil {
return http.Error(w, err.Error(), http.StatusBadRequest)
q.logger.Printf("Error reading file: %s", fileHeader.Filename)
return
}
go q.Process(magnet, category)
} }
go q.Process(magnet, category)
} }
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
} }

View File

@@ -17,11 +17,14 @@ func (q *QBit) Process(magnet *common.Magnet, category string) (*Torrent, error)
debridTorrent, err := debrid.ProcessQBitTorrent(q.debrid, magnet, category) debridTorrent, err := debrid.ProcessQBitTorrent(q.debrid, magnet, category)
if err != nil || debridTorrent == nil { if err != nil || debridTorrent == nil {
// Mark as failed // Mark as failed
q.logger.Printf("Failed to process torrent: %s: %v", magnet.Name, err)
q.MarkAsFailed(torrent) q.MarkAsFailed(torrent)
return torrent, err return torrent, err
} }
torrent.ID = debridTorrent.Id torrent.ID = debridTorrent.Id
q.processFiles(torrent, debridTorrent) torrent.Name = debridTorrent.Name // Update the name before adding it to *arrs storage
torrent.DebridTorrent = debridTorrent
go q.processFiles(torrent, debridTorrent)
return torrent, nil return torrent, nil
} }

View File

@@ -1,5 +1,7 @@
package qbit package qbit
import "goBlack/pkg/debrid"
type BuildInfo struct { type BuildInfo struct {
Libtorrent string `json:"libtorrent"` Libtorrent string `json:"libtorrent"`
Bitness int `json:"bitness"` Bitness int `json:"bitness"`
@@ -167,7 +169,8 @@ type TorrentCategory struct {
} }
type Torrent struct { type Torrent struct {
ID string `json:"-"` ID string `json:"-"`
DebridTorrent *debrid.Torrent `json:"-"`
AddedOn int64 `json:"added_on,omitempty"` AddedOn int64 `json:"added_on,omitempty"`
AmountLeft int64 `json:"amount_left,omitempty"` AmountLeft int64 `json:"amount_left,omitempty"`

View File

@@ -49,7 +49,7 @@ func (q *QBit) UpdateTorrent(t *Torrent, debridTorrent *debrid.Torrent) *Torrent
t.Uploaded = sizeCompleted t.Uploaded = sizeCompleted
t.UploadedSession = sizeCompleted t.UploadedSession = sizeCompleted
t.AmountLeft = totalSize - sizeCompleted t.AmountLeft = totalSize - sizeCompleted
t.Progress = 100 t.Progress = float32(progress)
t.SavePath = savePath t.SavePath = savePath
t.ContentPath = torrentPath t.ContentPath = torrentPath
t.Eta = eta t.Eta = eta