diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml deleted file mode 100644 index e69de29..0000000 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..683e188 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,27 @@ +#### 0.1.0 +- Initial Release +- Added Real Debrid Support +- Added Arrs Support +- Added Proxy Support +- Added Basic Authentication for Proxy +- Added Rate Limiting for Debrid Providers + +#### 0.1.1 +- Added support for "No Blackhole" for Arrs +- Added support for "Cached Only" for Proxy +- Bug Fixes + +#### 0.1.2 +- Bug fixes +- Code cleanup +- Get available hashes at once + +#### 0.1.3 + +- Searching for infohashes in the xml description/summary/comments +- Added local cache support +- Added max cache size +- Rewrite blackhole.go +- Bug fixes + - Fixed indexer getting disabled + - Fixed blackhole not working \ No newline at end of file diff --git a/README.md b/README.md index 19a2129..03ce822 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,10 @@ This is a Golang implementation go Torrent Blackhole with a **Real Debrid Proxy The proxy is useful in filtering out un-cached Real Debrid torrents +### Changelog + +- View the [CHANGELOG.md](CHANGELOG.md) for the latest changes + #### Installation ##### Docker Compose @@ -75,7 +79,8 @@ Download the binary from the releases page and run it with the config file. "username": "username", "password": "password", "cached_only": true - } + }, + "max_cache_size": 1000 } ``` diff --git a/cmd/blackhole.go b/cmd/blackhole.go index fe47885..c863b3f 100644 --- a/cmd/blackhole.go +++ b/cmd/blackhole.go @@ -12,6 +12,21 @@ import ( "time" ) +type Blackhole struct { + config *common.Config + deb debrid.Service + cache *common.Cache +} + +func NewBlackhole(config *common.Config, deb debrid.Service, cache *common.Cache) *Blackhole { + return &Blackhole{ + config: config, + deb: deb, + cache: cache, + } + +} + func fileReady(path string) bool { _, err := os.Stat(path) return !os.IsNotExist(err) // Returns true if the file exists @@ -33,7 +48,7 @@ func checkFileLoop(wg *sync.WaitGroup, dir string, file debrid.TorrentFile, read } } -func ProcessFiles(arr *debrid.Arr, torrent *debrid.Torrent) { +func (b *Blackhole) processFiles(arr *debrid.Arr, torrent *debrid.Torrent) { var wg sync.WaitGroup files := torrent.Files ready := make(chan debrid.TorrentFile, len(files)) @@ -52,29 +67,29 @@ func ProcessFiles(arr *debrid.Arr, torrent *debrid.Torrent) { for r := range ready { log.Println("File is ready:", r.Name) - CreateSymLink(arr, torrent) + b.createSymLink(arr, torrent) } go torrent.Cleanup(true) fmt.Printf("%s downloaded", torrent.Name) } -func CreateSymLink(config *debrid.Arr, torrent *debrid.Torrent) { - path := filepath.Join(config.CompletedFolder, torrent.Folder) +func (b *Blackhole) createSymLink(arr *debrid.Arr, torrent *debrid.Torrent) { + path := filepath.Join(arr.CompletedFolder, torrent.Folder) err := os.MkdirAll(path, os.ModePerm) if err != nil { log.Printf("Failed to create directory: %s\n", path) } for _, file := range torrent.Files { // Combine the directory and filename to form a full path - fullPath := filepath.Join(config.CompletedFolder, file.Path) + fullPath := filepath.Join(arr.CompletedFolder, file.Path) // Create a symbolic link if file doesn't exist - _ = os.Symlink(filepath.Join(config.Debrid.Folder, file.Path), fullPath) + _ = os.Symlink(filepath.Join(arr.Debrid.Folder, file.Path), fullPath) } } -func watchFiles(watcher *fsnotify.Watcher, events map[string]time.Time) { +func watcher(watcher *fsnotify.Watcher, events map[string]time.Time) { for { select { case event, ok := <-watcher.Events: @@ -96,7 +111,7 @@ func watchFiles(watcher *fsnotify.Watcher, events map[string]time.Time) { } } -func processFilesDebounced(arr *debrid.Arr, db debrid.Service, events map[string]time.Time, debouncePeriod time.Duration) { +func (b *Blackhole) processFilesDebounced(arr *debrid.Arr, events map[string]time.Time, debouncePeriod time.Duration) { ticker := time.NewTicker(1 * time.Second) // Check every second defer ticker.Stop() @@ -105,7 +120,7 @@ func processFilesDebounced(arr *debrid.Arr, db debrid.Service, events map[string if time.Since(lastEventTime) >= debouncePeriod { log.Printf("Torrent file detected: %s", file) // Process the torrent file - torrent, err := db.Process(arr, file) + torrent, err := b.deb.Process(arr, file) if err != nil && torrent != nil { // remove torrent file torrent.Cleanup(true) @@ -113,7 +128,7 @@ func processFilesDebounced(arr *debrid.Arr, db debrid.Service, events map[string log.Printf("Error processing torrent file: %s", err) } if err == nil && torrent != nil && len(torrent.Files) > 0 { - go ProcessFiles(arr, torrent) + go b.processFiles(arr, torrent) } delete(events, file) // remove file from channel @@ -122,8 +137,8 @@ func processFilesDebounced(arr *debrid.Arr, db debrid.Service, events map[string } } -func StartArr(conf *debrid.Arr, db debrid.Service) { - log.Printf("Watching: %s", conf.WatchFolder) +func (b *Blackhole) startArr(arr *debrid.Arr) { + log.Printf("Watching: %s", arr.WatchFolder) w, err := fsnotify.NewWatcher() if err != nil { log.Println(err) @@ -136,19 +151,19 @@ func StartArr(conf *debrid.Arr, db debrid.Service) { }(w) events := make(map[string]time.Time) - go watchFiles(w, events) - if err = w.Add(conf.WatchFolder); err != nil { + go watcher(w, events) + if err = w.Add(arr.WatchFolder); err != nil { log.Println("Error Watching folder:", err) return } - processFilesDebounced(conf, db, events, 1*time.Second) + b.processFilesDebounced(arr, events, 1*time.Second) } -func StartBlackhole(config *common.Config, deb debrid.Service) { +func (b *Blackhole) Start() { log.Println("[*] Starting Blackhole") var wg sync.WaitGroup - for _, conf := range config.Arrs { + for _, conf := range b.config.Arrs { wg.Add(1) defer wg.Done() headers := map[string]string{ @@ -157,14 +172,14 @@ func StartBlackhole(config *common.Config, deb debrid.Service) { client := common.NewRLHTTPClient(nil, headers) arr := &debrid.Arr{ - Debrid: config.Debrid, + Debrid: b.config.Debrid, WatchFolder: conf.WatchFolder, CompletedFolder: conf.CompletedFolder, Token: conf.Token, URL: conf.URL, Client: client, } - go StartArr(arr, deb) + go b.startArr(arr) } wg.Wait() } diff --git a/cmd/main.go b/cmd/main.go index 9f08461..a02e955 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,19 +1,22 @@ package cmd import ( + "cmp" "goBlack/common" "goBlack/debrid" "sync" ) func Start(config *common.Config) { - - deb := debrid.NewDebrid(config.Debrid) + maxCacheSize := cmp.Or(config.MaxCacheSize, 1000) + cache := common.NewCache(maxCacheSize) + + deb := debrid.NewDebrid(config.Debrid, cache) var wg sync.WaitGroup if config.Proxy.Enabled { - proxy := NewProxy(*config, deb) + proxy := NewProxy(*config, deb, cache) wg.Add(1) go func() { defer wg.Done() @@ -22,10 +25,11 @@ func Start(config *common.Config) { } if len(config.Arrs) > 0 { + blackhole := NewBlackhole(config, deb, cache) wg.Add(1) go func() { defer wg.Done() - StartBlackhole(config, deb) + blackhole.Start() }() } diff --git a/cmd/proxy.go b/cmd/proxy.go index a4f2787..4b87254 100644 --- a/cmd/proxy.go +++ b/cmd/proxy.go @@ -16,6 +16,7 @@ import ( "os" "regexp" "strings" + "sync" ) type RSS struct { @@ -70,26 +71,28 @@ type TorznabAttr struct { } type Proxy 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"` - Debrid debrid.Service + port string + enabled bool + debug bool + username string + password string + cachedOnly bool + debrid debrid.Service + cache *common.Cache } -func NewProxy(config common.Config, deb debrid.Service) *Proxy { +func NewProxy(config common.Config, deb debrid.Service, cache *common.Cache) *Proxy { cfg := config.Proxy port := cmp.Or(os.Getenv("PORT"), cfg.Port, "8181") return &Proxy{ - Port: port, - Enabled: cfg.Enabled, - Debug: cfg.Debug, - Username: cfg.Username, - Password: cfg.Password, - CachedOnly: cfg.CachedOnly, - Debrid: deb, + port: port, + enabled: cfg.Enabled, + debug: cfg.Debug, + username: cfg.Username, + password: cfg.Password, + cachedOnly: cfg.CachedOnly, + debrid: deb, + cache: cache, } } @@ -147,45 +150,76 @@ func (p *Proxy) ProcessResponse(resp *http.Response) *http.Response { } func getItemsHash(items []Item) map[string]string { - IdHashMap := make(map[string]string) + + var wg sync.WaitGroup + idHashMap := sync.Map{} // Use sync.Map for concurrent access + for _, item := range items { - hash := getItemHash(item) - IdHashMap[item.GUID] = hash + wg.Add(1) + go func(item Item) { + defer wg.Done() + hash := strings.ToLower(getItemHash(item)) + if hash != "" { + idHashMap.Store(item.GUID, hash) // Store directly into sync.Map + } + }(item) } - return IdHashMap + wg.Wait() + + // Convert sync.Map to regular map + finalMap := make(map[string]string) + idHashMap.Range(func(key, value interface{}) bool { + finalMap[key.(string)] = value.(string) + return true + }) + + return finalMap } func getItemHash(item Item) string { - magnetLink := "" infohash := "" - // Extract magnet link from the link or comments - if strings.Contains(item.Link, "magnet:?") { - magnetLink = item.Link - } else if strings.Contains(item.GUID, "magnet:?") { - magnetLink = item.GUID - } - - // Extract infohash from elements for _, attr := range item.TorznabAttrs { if attr.Name == "infohash" { - infohash = attr.Value + return attr.Value } } - if magnetLink == "" && infohash == "" { + + if strings.Contains(item.GUID, "magnet:?") { + magnet, err := common.GetMagnetInfo(item.GUID) + if err == nil && magnet != nil && magnet.InfoHash != "" { + return magnet.InfoHash + } + } + + magnetLink := item.Link + + if magnetLink == "" { // We can't check the availability of the torrent without a magnet link or infohash return "" } - var magnet *common.Magnet - var err error - if infohash == "" { - magnet, err = common.GetMagnetInfo(magnetLink) - if err != nil || magnet == nil || magnet.InfoHash == "" { - log.Println("Error getting magnet info:", err) - return "" + if strings.Contains(magnetLink, "magnet:?") { + magnet, err := common.GetMagnetInfo(magnetLink) + if err == nil && magnet != nil && magnet.InfoHash != "" { + return magnet.InfoHash } - infohash = magnet.InfoHash + } + + //Check Description for infohash + hash := common.ExtractInfoHash(item.Description) + if hash == "" { + // Check Title for infohash + hash = common.ExtractInfoHash(item.Comments) + } + infohash = hash + if infohash == "" { + //Get torrent file from http link + //Takes too long, not worth it + //magnet, err := common.OpenMagnetHttpURL(magnetLink) + //if err == nil && magnet != nil && magnet.InfoHash != "" { + // log.Printf("Magnet: %s", magnet.InfoHash) + //} } return infohash @@ -198,9 +232,7 @@ func (p *Proxy) ProcessXMLResponse(resp *http.Response) *http.Response { body, err := io.ReadAll(resp.Body) if err != nil { - if p.Debug { - log.Println("Error reading response body:", err) - } + log.Println("Error reading response body:", err) return resp } err = resp.Body.Close() @@ -211,12 +243,9 @@ func (p *Proxy) ProcessXMLResponse(resp *http.Response) *http.Response { var rss RSS err = xml.Unmarshal(body, &rss) if err != nil { - if p.Debug { - log.Printf("Error unmarshalling XML: %v", err) - } + log.Printf("Error unmarshalling XML: %v", err) return resp } - newItems := make([]Item, 0) // Step 4: Extract infohash or magnet URI, manipulate data IdsHashMap := getItemsHash(rss.Channel.Items) @@ -226,45 +255,38 @@ func (p *Proxy) ProcessXMLResponse(resp *http.Response) *http.Response { hashes = append(hashes, hash) } } - if len(hashes) == 0 { - // No infohashes or magnet links found, should we return the original response? - return resp - } - availableHashes := p.Debrid.IsAvailable(hashes) - for _, item := range rss.Channel.Items { - hash := IdsHashMap[item.GUID] - if hash == "" { - // newItems = append(newItems, item) - continue - } - isCached, exists := availableHashes[hash] - if !exists { - // newItems = append(newItems, item) - continue - } - if !isCached { - continue - } - newItems = append(newItems, item) + log.Printf("Found %d infohashes/magnet links", len(hashes)) + availableHashesMap := p.debrid.IsAvailable(hashes) + newItems := make([]Item, 0, len(rss.Channel.Items)) + + if len(hashes) > 0 { + for _, item := range rss.Channel.Items { + hash := IdsHashMap[item.GUID] + if hash == "" { + continue + } + isCached, exists := availableHashesMap[hash] + if !exists || !isCached { + continue + } + newItems = append(newItems, item) + } } + log.Printf("Report: %d/%d items are cached", len(newItems), len(rss.Channel.Items)) rss.Channel.Items = newItems // rss.Channel.Items = newItems modifiedBody, err := xml.MarshalIndent(rss, "", " ") if err != nil { - if p.Debug { - log.Printf("Error marshalling XML: %v", err) - } + log.Printf("Error marshalling XML: %v", err) + resp.Body = io.NopCloser(bytes.NewReader(body)) return resp } modifiedBody = append([]byte(xml.Header), modifiedBody...) // Set the modified body back to the response resp.Body = io.NopCloser(bytes.NewReader(modifiedBody)) - resp.ContentLength = int64(len(modifiedBody)) - resp.Header.Set("Content-Length", string(rune(len(modifiedBody)))) - return resp } @@ -275,7 +297,7 @@ func UrlMatches(re *regexp.Regexp) goproxy.ReqConditionFunc { } func (p *Proxy) Start() { - username, password := p.Username, p.Password + username, password := p.username, p.password proxy := goproxy.NewProxyHttpServer() if username != "" || password != "" { // Set up basic auth for proxy @@ -285,13 +307,15 @@ func (p *Proxy) Start() { } proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("^.443$"))).HandleConnect(goproxy.AlwaysMitm) - proxy.OnResponse(UrlMatches(regexp.MustCompile("^.*/api\\?t=(search|tvsearch|movie)(&.*)?$"))).DoFunc( + proxy.OnResponse( + UrlMatches(regexp.MustCompile("^.*/api\\?t=(search|tvsearch|movie)(&.*)?$")), + goproxy.StatusCodeIs(http.StatusOK, http.StatusAccepted)).DoFunc( func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response { return p.ProcessResponse(resp) }) - proxy.Verbose = p.Debug - portFmt := fmt.Sprintf(":%s", p.Port) + proxy.Verbose = p.debug + portFmt := fmt.Sprintf(":%s", p.port) log.Printf("[*] Starting proxy server on %s\n", portFmt) log.Fatal(http.ListenAndServe(fmt.Sprintf("%s", portFmt), proxy)) } diff --git a/common/cache.go b/common/cache.go new file mode 100644 index 0000000..ba79f45 --- /dev/null +++ b/common/cache.go @@ -0,0 +1,88 @@ +package common + +import ( + "sync" +) + +type Cache struct { + data map[string]struct{} + order []string + maxItems int + mu sync.RWMutex +} + +func NewCache(maxItems int) *Cache { + if maxItems <= 0 { + maxItems = 1000 + } + return &Cache{ + data: make(map[string]struct{}, maxItems), + order: make([]string, 0, maxItems), + maxItems: maxItems, + } +} + +func (c *Cache) Add(value string) { + c.mu.Lock() + defer c.mu.Unlock() + + if _, exists := c.data[value]; !exists { + if len(c.order) >= c.maxItems { + delete(c.data, c.order[0]) + c.order = c.order[1:] + } + c.data[value] = struct{}{} + c.order = append(c.order, value) + } +} + +func (c *Cache) AddMultiple(values map[string]bool) { + c.mu.Lock() + defer c.mu.Unlock() + + for value := range values { + if _, exists := c.data[value]; !exists { + if len(c.order) >= c.maxItems { + delete(c.data, c.order[0]) + c.order = c.order[1:] + } + c.data[value] = struct{}{} + c.order = append(c.order, value) + } + } +} + +func (c *Cache) Get(index int) (string, bool) { + c.mu.RLock() + defer c.mu.RUnlock() + if index < 0 || index >= len(c.order) { + return "", false + } + return c.order[index], true +} + +func (c *Cache) GetMultiple(values []string) map[string]bool { + c.mu.RLock() + defer c.mu.RUnlock() + + result := make(map[string]bool, len(values)) + for _, value := range values { + if _, exists := c.data[value]; exists { + result[value] = true + } + } + return result +} + +func (c *Cache) Exists(value string) bool { + c.mu.RLock() + defer c.mu.RUnlock() + _, exists := c.data[value] + return exists +} + +func (c *Cache) Len() int { + c.mu.RLock() + defer c.mu.RUnlock() + return len(c.order) +} diff --git a/common/config.go b/common/config.go index 17d9742..92ed520 100644 --- a/common/config.go +++ b/common/config.go @@ -16,7 +16,6 @@ type DebridConfig struct { } type Config struct { - DbDSN string `json:"db_dsn"` Debrid DebridConfig `json:"debrid"` Arrs []struct { WatchFolder string `json:"watch_folder"` @@ -32,6 +31,7 @@ type Config struct { Password string `json:"password"` CachedOnly bool `json:"cached_only"` } + MaxCacheSize int `json:"max_cache_size"` } func LoadConfig(path string) (*Config, error) { diff --git a/common/utils.go b/common/utils.go index 4a6c89b..95fb173 100644 --- a/common/utils.go +++ b/common/utils.go @@ -2,11 +2,16 @@ package common import ( "bufio" + "encoding/base32" + "encoding/hex" "fmt" + "github.com/anacrolix/torrent/metainfo" "log" "math/rand" + "net/http" "net/url" "os" + "regexp" "strings" ) @@ -46,14 +51,51 @@ func OpenMagnetFile(filePath string) string { return "" } +func OpenMagnetHttpURL(magnetLink string) (*Magnet, error) { + resp, err := http.Get(magnetLink) + if err != nil { + return nil, fmt.Errorf("error making GET request: %v", err) + } + defer func(resp *http.Response) { + err := resp.Body.Close() + if err != nil { + return + } + }(resp) // Ensure the response is closed after the function ends + + // Create a scanner to read the file line by line + + mi, err := metainfo.Load(resp.Body) + if err != nil { + return nil, err + } + hash := mi.HashInfoBytes() + infoHash := hash.HexString() + info, err := mi.UnmarshalInfo() + if err != nil { + return nil, err + } + log.Println("InfoHash: ", infoHash) + magnet := &Magnet{ + InfoHash: infoHash, + Name: info.Name, + Size: info.Length, + Link: mi.Magnet(&hash, &info).String(), + } + return magnet, nil + +} + 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") @@ -81,3 +123,47 @@ func RandomString(length int) string { } return string(b) } + +func ExtractInfoHash(magnetDesc string) string { + const prefix = "xt=urn:btih:" + start := strings.Index(magnetDesc, prefix) + if start == -1 { + return "" + } + hash := "" + start += len(prefix) + end := strings.IndexAny(magnetDesc[start:], "&#") + if end == -1 { + hash = magnetDesc[start:] + } else { + hash = magnetDesc[start : start+end] + } + hash, _ = processInfoHash(hash) // Convert to hex if needed + return hash +} + +func processInfoHash(input string) (string, error) { + // Regular expression for a valid 40-character hex infohash + hexRegex := regexp.MustCompile("^[0-9a-fA-F]{40}$") + + // If it's already a valid hex infohash, return it as is + if hexRegex.MatchString(input) { + return strings.ToLower(input), nil + } + + // If it's 32 characters long, it might be Base32 encoded + if len(input) == 32 { + // Ensure the input is uppercase and remove any padding + input = strings.ToUpper(strings.TrimRight(input, "=")) + + // Try to decode from Base32 + decoded, err := base32.StdEncoding.DecodeString(input) + if err == nil && len(decoded) == 20 { + // If successful and the result is 20 bytes, encode to hex + return hex.EncodeToString(decoded), nil + } + } + + // If we get here, it's not a valid infohash and we couldn't convert it + return "", fmt.Errorf("invalid infohash: %s", input) +} diff --git a/debrid/debrid.go b/debrid/debrid.go index cc128ea..585f653 100644 --- a/debrid/debrid.go +++ b/debrid/debrid.go @@ -18,14 +18,16 @@ type Debrid struct { Host string `json:"host"` APIKey string DownloadUncached bool + client *common.RLHTTPClient + cache *common.Cache } -func NewDebrid(dc common.DebridConfig) Service { +func NewDebrid(dc common.DebridConfig, cache *common.Cache) Service { switch dc.Name { case "realdebrid": - return NewRealDebrid(dc) + return NewRealDebrid(dc, cache) default: - return NewRealDebrid(dc) + return NewRealDebrid(dc, cache) } } @@ -81,3 +83,31 @@ func getTorrentInfo(filePath string) (*Torrent, error) { } return torrent, nil } + +func GetLocalCache(infohashes []string, cache *common.Cache) (string, map[string]bool) { + result := make(map[string]bool) + + if len(infohashes) == 0 { + return "", result + } + if len(infohashes) == 1 { + if cache.Exists(infohashes[0]) { + return "", map[string]bool{infohashes[0]: true} + } + return infohashes[0], result + } + + cachedHashes := cache.GetMultiple(infohashes) + + hashes := "" + for _, h := range infohashes { + _, exists := cachedHashes[h] + if !exists { + hashes += h + "/" + } else { + result[h] = true + } + } + + return hashes, result +} diff --git a/debrid/realdebrid.go b/debrid/realdebrid.go index 342e27a..a4d11b6 100644 --- a/debrid/realdebrid.go +++ b/debrid/realdebrid.go @@ -18,6 +18,7 @@ type RealDebrid struct { APIKey string DownloadUncached bool client *common.RLHTTPClient + cache *common.Cache } func (r *RealDebrid) Process(arr *Arr, magnet string) (*Torrent, error) { @@ -28,7 +29,8 @@ func (r *RealDebrid) Process(arr *Arr, magnet string) (*Torrent, error) { } log.Printf("Torrent Name: %s", torrent.Name) if !r.DownloadUncached { - if !r.IsAvailable([]string{torrent.InfoHash})[torrent.InfoHash] { + 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) @@ -42,12 +44,18 @@ func (r *RealDebrid) Process(arr *Arr, magnet string) (*Torrent, error) { } func (r *RealDebrid) IsAvailable(infohashes []string) map[string]bool { - hashes := strings.Join(infohashes, "/") - result := make(map[string]bool) + // Check if the infohashes are available in the local cache + hashes, result := GetLocalCache(infohashes, r.cache) + + if hashes == "" { + // Either all the infohashes are locally cached or none are + r.cache.AddMultiple(result) + return result + } + url := fmt.Sprintf("%s/torrents/instantAvailability/%s", r.Host, hashes) resp, err := r.client.MakeRequest(http.MethodGet, url, nil) if err != nil { - log.Println(url) log.Println("Error checking availability:", err) return result } @@ -59,12 +67,11 @@ func (r *RealDebrid) IsAvailable(infohashes []string) map[string]bool { } for _, h := range infohashes { hosters, exists := data[strings.ToLower(h)] - if !exists || len(hosters.Rd) < 1 { - result[h] = false - } else { + if exists && len(hosters.Rd) > 0 { result[h] = true } } + r.cache.AddMultiple(result) // Add the results to the cache return result } @@ -149,7 +156,7 @@ func (r *RealDebrid) DownloadLink(torrent *Torrent) error { return nil } -func NewRealDebrid(dc common.DebridConfig) *RealDebrid { +func NewRealDebrid(dc common.DebridConfig, cache *common.Cache) *RealDebrid { rl := common.ParseRateLimit(dc.RateLimit) headers := map[string]string{ "Authorization": fmt.Sprintf("Bearer %s", dc.APIKey), @@ -160,5 +167,6 @@ func NewRealDebrid(dc common.DebridConfig) *RealDebrid { APIKey: dc.APIKey, DownloadUncached: dc.DownloadUncached, client: client, + cache: cache, } }