diff --git a/.dockerignore b/.dockerignore index 3067534..23237a9 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,4 +6,4 @@ docker-compose.yml **/.idea/ *.magnet **.torrent - +torrents.json diff --git a/.gitignore b/.gitignore index 632d6ba..c0d624c 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ docker-compose.yml *.log.* dist/ tmp/** +torrents.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 9843c7c..91c8713 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -105,4 +105,11 @@ - Add new /internal/cached endpoint to check if an hash is cached - implement per-debrid local cache - Fix file check for torbox -- Other minor bug fixes \ No newline at end of file +- Other minor bug fixes + + +#### 0.3.3 + +- Add AllDebrid Support +- Fix Torbox not downloading uncached torrents +- Fix Rar files being downloaded \ No newline at end of file diff --git a/README.md b/README.md index aaf6224..c61b7e1 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ The proxy is useful in filtering out un-cached Real Debrid torrents - Real Debrid - Torbox - Debrid Link +- All Debrid ### Changelog @@ -91,6 +92,15 @@ Download the binary from the releases page and run it with the config file. "rate_limit": "250/minute", "download_uncached": false, "check_cached": false + }, + { + "name": "alldebrid", + "host": "http://api.alldebrid.com/v4.1", + "api_key": "alldebrid_key", + "folder": "/media/remote/alldebrid/magnet/", + "rate_limit": "600/minute", + "download_uncached": false, + "check_cached": false } ], "proxy": { @@ -122,7 +132,7 @@ Download the binary from the releases page and run it with the config file. - The `name` key is the name of the debrid provider - The `host` key is the API endpoint of the debrid provider - The `api_key` key is the API key of the debrid provider -- The `folder` key is the folder where the torrents will be downloaded. e.g `data/realdebrid/torrents/` +- The `folder` key is the folder where your debrid folder is mounted(webdav, rclone, zurg etc). e.g `data/realdebrid/torrents/`, `/media/remote/alldebrid/magnets/` - The `rate_limit` key is the rate limit of the debrid provider(null by default) - The `download_uncached` bool key is used to download uncached torrents(disabled by default) - The `check_cached` bool key is used to check if the torrent is cached(disabled by default) @@ -184,9 +194,9 @@ The UI is a simple web interface that allows you to add torrents directly to the ### TODO - [ ] A proper name!!!! - [ ] Debrid - - [ ] Add more Debrid Providers + - [x] Add more Debrid Providers - [ ] Qbittorrent - [ ] Add more Qbittorrent features - - [ ] Persist torrents on restart/server crash + - [x] Persist torrents on restart/server crash - [ ] Add tests \ No newline at end of file diff --git a/pkg/debrid/alldebrid.go b/pkg/debrid/alldebrid.go new file mode 100644 index 0000000..c199288 --- /dev/null +++ b/pkg/debrid/alldebrid.go @@ -0,0 +1,239 @@ +package debrid + +import ( + "encoding/json" + "fmt" + "goBlack/common" + "goBlack/pkg/debrid/structs" + "log" + "net/http" + gourl "net/url" + "os" + "path/filepath" + "strconv" +) + +type AllDebrid struct { + BaseDebrid +} + +func (r *AllDebrid) GetMountPath() string { + return r.MountPath +} + +func (r *AllDebrid) GetName() string { + return r.Name +} + +func (r *AllDebrid) GetLogger() *log.Logger { + return r.logger +} + +func (r *AllDebrid) IsAvailable(infohashes []string) map[string]bool { + // Check if the infohashes are available in the local cache + hashes, result := GetLocalCache(infohashes, r.cache) + + if len(hashes) == 0 { + // Either all the infohashes are locally cached or none are + r.cache.AddMultiple(result) + return result + } + + // Divide hashes into groups of 100 + // AllDebrid does not support checking cached infohashes + return result +} + +func (r *AllDebrid) SubmitMagnet(torrent *Torrent) (*Torrent, error) { + url := fmt.Sprintf("%s/magnet/upload", r.Host) + query := gourl.Values{} + query.Add("magnets[]", torrent.Magnet.Link) + url += "?" + query.Encode() + req, _ := http.NewRequest(http.MethodGet, url, nil) + resp, err := r.client.MakeRequest(req) + if err != nil { + return nil, err + } + var data structs.AllDebridUploadMagnetResponse + err = json.Unmarshal(resp, &data) + if err != nil { + return nil, err + } + magnets := data.Data.Magnets + if len(magnets) == 0 { + return nil, fmt.Errorf("error adding torrent") + } + magnet := magnets[0] + torrentId := strconv.Itoa(magnet.ID) + r.logger.Printf("Torrent: %s added with id: %s\n", torrent.Name, torrentId) + torrent.Id = torrentId + + return torrent, nil +} + +func getAlldebridStatus(statusCode int) string { + switch { + case statusCode == 4: + return "downloaded" + case statusCode >= 0 && statusCode <= 3: + return "downloading" + default: + return "error" + } +} + +func (r *AllDebrid) GetTorrent(id string) (*Torrent, error) { + torrent := &Torrent{} + url := fmt.Sprintf("%s/magnet/status?id=%s", r.Host, id) + req, _ := http.NewRequest(http.MethodGet, url, nil) + resp, err := r.client.MakeRequest(req) + if err != nil { + return torrent, err + } + var res structs.AllDebridTorrentInfoResponse + err = json.Unmarshal(resp, &res) + if err != nil { + r.logger.Printf("Error unmarshalling torrent info: %s\n", err) + return torrent, err + } + data := res.Data.Magnets + name := data.Filename + torrent.Id = id + torrent.Name = name + torrent.Bytes = data.Size + torrent.Folder = name + torrent.Progress = float64((data.Downloaded / data.Size) * 100) + torrent.Status = getAlldebridStatus(data.StatusCode) + torrent.Speed = data.DownloadSpeed + torrent.Seeders = data.Seeders + torrent.Filename = name + torrent.OriginalFilename = name + files := make([]TorrentFile, 0) + for index, f := range data.Files { + fileName := filepath.Base(f.Name) + if common.RegexMatch(common.SAMPLEMATCH, fileName) { + // Skip sample files + continue + } + if !common.RegexMatch(common.VIDEOMATCH, fileName) && !common.RegexMatch(common.MUSICMATCH, fileName) { + continue + } + file := TorrentFile{ + Id: strconv.Itoa(index), + Name: fileName, + Size: f.Size, + Path: fileName, + } + files = append(files, file) + } + parentFolder := data.Filename + if data.NbLinks == 1 { + // All debrid doesn't return the parent folder for single file torrents + parentFolder = "" + } + torrent.OriginalFilename = parentFolder + torrent.Files = files + torrent.Debrid = r + return torrent, nil +} + +func (r *AllDebrid) CheckStatus(torrent *Torrent, isSymlink bool) (*Torrent, error) { + for { + tb, err := r.GetTorrent(torrent.Id) + + torrent = tb + + if err != nil || tb == nil { + return tb, err + } + status := torrent.Status + if status == "downloaded" { + r.logger.Printf("Torrent: %s downloaded\n", torrent.Name) + if !isSymlink { + err = r.GetDownloadLinks(torrent) + if err != nil { + return torrent, err + } + } + break + } else if status == "downloading" { + if !r.DownloadUncached { + go torrent.Delete() + return torrent, fmt.Errorf("torrent: %s not cached", torrent.Name) + } + // Break out of the loop if the torrent is downloading. + // This is necessary to prevent infinite loop since we moved to sync downloading and async processing + break + } else { + return torrent, fmt.Errorf("torrent: %s has error", torrent.Name) + } + + } + return torrent, nil +} + +func (r *AllDebrid) DeleteTorrent(torrent *Torrent) { + url := fmt.Sprintf("%s/magnet/delete?id=%s", r.Host, torrent.Id) + req, _ := http.NewRequest(http.MethodGet, url, nil) + _, err := r.client.MakeRequest(req) + if err == nil { + r.logger.Printf("Torrent: %s deleted\n", torrent.Name) + } else { + r.logger.Printf("Error deleting torrent: %s", err) + } +} + +func (r *AllDebrid) GetDownloadLinks(torrent *Torrent) error { + downloadLinks := make([]TorrentDownloadLinks, 0) + for _, file := range torrent.Files { + url := fmt.Sprintf("%s/link/unlock", r.Host) + query := gourl.Values{} + query.Add("link", file.Link) + url += "?" + query.Encode() + req, _ := http.NewRequest(http.MethodGet, url, nil) + resp, err := r.client.MakeRequest(req) + if err != nil { + return err + } + var data structs.AllDebridDownloadLink + if err = json.Unmarshal(resp, &data); err != nil { + return err + } + link := data.Data.Link + + dl := TorrentDownloadLinks{ + Link: file.Link, + Filename: data.Data.Filename, + DownloadLink: link, + } + downloadLinks = append(downloadLinks, dl) + } + torrent.DownloadLinks = downloadLinks + return nil +} + +func (r *AllDebrid) GetCheckCached() bool { + return r.CheckCached +} + +func NewAllDebrid(dc common.DebridConfig, cache *common.Cache) *AllDebrid { + rl := common.ParseRateLimit(dc.RateLimit) + headers := map[string]string{ + "Authorization": fmt.Sprintf("Bearer %s", dc.APIKey), + } + client := common.NewRLHTTPClient(rl, headers) + logger := common.NewLogger(dc.Name, os.Stdout) + return &AllDebrid{ + BaseDebrid: BaseDebrid{ + Name: "alldebrid", + Host: dc.Host, + APIKey: dc.APIKey, + DownloadUncached: dc.DownloadUncached, + client: client, + cache: cache, + MountPath: dc.Folder, + logger: logger, + CheckCached: dc.CheckCached, + }, + } +} diff --git a/pkg/debrid/debrid.go b/pkg/debrid/debrid.go index db10aad..1137d92 100644 --- a/pkg/debrid/debrid.go +++ b/pkg/debrid/debrid.go @@ -56,6 +56,8 @@ func createDebrid(dc common.DebridConfig, cache *common.Cache) Service { return NewTorbox(dc, cache) case "debridlink": return NewDebridLink(dc, cache) + case "alldebrid": + return NewAllDebrid(dc, cache) default: return NewRealDebrid(dc, cache) } diff --git a/pkg/debrid/structs/alldebrid.go b/pkg/debrid/structs/alldebrid.go new file mode 100644 index 0000000..ae1d46e --- /dev/null +++ b/pkg/debrid/structs/alldebrid.go @@ -0,0 +1,73 @@ +package structs + +type errorResponse struct { + Code string `json:"code"` + Message string `json:"message"` +} + +type magnetInfo struct { + Id int `json:"id"` + Filename string `json:"filename"` + Size int64 `json:"size"` + Hash string `json:"hash"` + Status string `json:"status"` + StatusCode int `json:"statusCode"` + UploadDate int `json:"uploadDate"` + Downloaded int64 `json:"downloaded"` + Uploaded int64 `json:"uploaded"` + DownloadSpeed int `json:"downloadSpeed"` + UploadSpeed int `json:"uploadSpeed"` + Seeders int `json:"seeders"` + CompletionDate int `json:"completionDate"` + Type string `json:"type"` + Notified bool `json:"notified"` + Version int `json:"version"` + NbLinks int `json:"nbLinks"` + Files []struct { + Name string `json:"n"` + Size int64 `json:"s"` + Link string `json:"l"` + } `json:"files"` +} + +type AllDebridTorrentInfoResponse struct { + Status string `json:"status"` + Data struct { + Magnets magnetInfo `json:"magnets"` + } `json:"data"` + Error *errorResponse `json:"error"` +} + +type AllDebridUploadMagnetResponse struct { + Status string `json:"status"` + Data struct { + Magnets []struct { + Magnet string `json:"magnet"` + Hash string `json:"hash"` + Name string `json:"name"` + FilenameOriginal string `json:"filename_original"` + Size int64 `json:"size"` + Ready bool `json:"ready"` + ID int `json:"id"` + } `json:"magnets"` + } + Error *errorResponse `json:"error"` +} + +type AllDebridDownloadLink struct { + Status string `json:"status"` + Data struct { + Link string `json:"link"` + Host string `json:"host"` + Filename string `json:"filename"` + Streaming []interface{} `json:"streaming"` + Paws bool `json:"paws"` + Filesize int `json:"filesize"` + Id string `json:"id"` + Path []struct { + Name string `json:"n"` + Size int `json:"s"` + } `json:"path"` + } `json:"data"` + Error *errorResponse `json:"error"` +} diff --git a/pkg/debrid/torbox.go b/pkg/debrid/torbox.go index 2ae5e51..c059713 100644 --- a/pkg/debrid/torbox.go +++ b/pkg/debrid/torbox.go @@ -123,7 +123,7 @@ func (r *Torbox) SubmitMagnet(torrent *Torrent) (*Torrent, error) { return torrent, nil } -func getStatus(status string, finished bool) string { +func getTorboxStatus(status string, finished bool) string { if finished { return "downloaded" } @@ -159,7 +159,7 @@ func (r *Torbox) GetTorrent(id string) (*Torrent, error) { torrent.Bytes = data.Size torrent.Folder = name torrent.Progress = data.Progress * 100 - torrent.Status = getStatus(data.DownloadState, data.DownloadFinished) + torrent.Status = getTorboxStatus(data.DownloadState, data.DownloadFinished) torrent.Speed = data.DownloadSpeed torrent.Seeders = data.Seeds torrent.Filename = name diff --git a/pkg/debrid/torrent.go b/pkg/debrid/torrent.go index bd28b6f..bc9c582 100644 --- a/pkg/debrid/torrent.go +++ b/pkg/debrid/torrent.go @@ -66,7 +66,7 @@ func (t *Torrent) GetMountFolder(rClonePath string) string { } for _, path := range possiblePaths { - if path != "" && common.FileReady(filepath.Join(rClonePath, path)) { + if common.FileReady(filepath.Join(rClonePath, path)) { return path } }