Add AllDebrid support
This commit is contained in:
@@ -6,4 +6,4 @@ docker-compose.yml
|
|||||||
**/.idea/
|
**/.idea/
|
||||||
*.magnet
|
*.magnet
|
||||||
**.torrent
|
**.torrent
|
||||||
|
torrents.json
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -10,3 +10,4 @@ docker-compose.yml
|
|||||||
*.log.*
|
*.log.*
|
||||||
dist/
|
dist/
|
||||||
tmp/**
|
tmp/**
|
||||||
|
torrents.json
|
||||||
|
|||||||
@@ -106,3 +106,10 @@
|
|||||||
- implement per-debrid local cache
|
- implement per-debrid local cache
|
||||||
- Fix file check for torbox
|
- Fix file check for torbox
|
||||||
- Other minor bug fixes
|
- Other minor bug fixes
|
||||||
|
|
||||||
|
|
||||||
|
#### 0.3.3
|
||||||
|
|
||||||
|
- Add AllDebrid Support
|
||||||
|
- Fix Torbox not downloading uncached torrents
|
||||||
|
- Fix Rar files being downloaded
|
||||||
16
README.md
16
README.md
@@ -19,6 +19,7 @@ The proxy is useful in filtering out un-cached Real Debrid torrents
|
|||||||
- Real Debrid
|
- Real Debrid
|
||||||
- Torbox
|
- Torbox
|
||||||
- Debrid Link
|
- Debrid Link
|
||||||
|
- All Debrid
|
||||||
|
|
||||||
### Changelog
|
### Changelog
|
||||||
|
|
||||||
@@ -91,6 +92,15 @@ Download the binary from the releases page and run it with the config file.
|
|||||||
"rate_limit": "250/minute",
|
"rate_limit": "250/minute",
|
||||||
"download_uncached": false,
|
"download_uncached": false,
|
||||||
"check_cached": 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": {
|
"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 `name` key is the name of the debrid provider
|
||||||
- The `host` key is the API endpoint 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 `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 `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 `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)
|
- 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
|
### TODO
|
||||||
- [ ] A proper name!!!!
|
- [ ] A proper name!!!!
|
||||||
- [ ] Debrid
|
- [ ] Debrid
|
||||||
- [ ] Add more Debrid Providers
|
- [x] Add more Debrid Providers
|
||||||
|
|
||||||
- [ ] Qbittorrent
|
- [ ] Qbittorrent
|
||||||
- [ ] Add more Qbittorrent features
|
- [ ] Add more Qbittorrent features
|
||||||
- [ ] Persist torrents on restart/server crash
|
- [x] Persist torrents on restart/server crash
|
||||||
- [ ] Add tests
|
- [ ] Add tests
|
||||||
239
pkg/debrid/alldebrid.go
Normal file
239
pkg/debrid/alldebrid.go
Normal file
@@ -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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -56,6 +56,8 @@ func createDebrid(dc common.DebridConfig, cache *common.Cache) Service {
|
|||||||
return NewTorbox(dc, cache)
|
return NewTorbox(dc, cache)
|
||||||
case "debridlink":
|
case "debridlink":
|
||||||
return NewDebridLink(dc, cache)
|
return NewDebridLink(dc, cache)
|
||||||
|
case "alldebrid":
|
||||||
|
return NewAllDebrid(dc, cache)
|
||||||
default:
|
default:
|
||||||
return NewRealDebrid(dc, cache)
|
return NewRealDebrid(dc, cache)
|
||||||
}
|
}
|
||||||
|
|||||||
73
pkg/debrid/structs/alldebrid.go
Normal file
73
pkg/debrid/structs/alldebrid.go
Normal file
@@ -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"`
|
||||||
|
}
|
||||||
@@ -123,7 +123,7 @@ func (r *Torbox) SubmitMagnet(torrent *Torrent) (*Torrent, error) {
|
|||||||
return torrent, nil
|
return torrent, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStatus(status string, finished bool) string {
|
func getTorboxStatus(status string, finished bool) string {
|
||||||
if finished {
|
if finished {
|
||||||
return "downloaded"
|
return "downloaded"
|
||||||
}
|
}
|
||||||
@@ -159,7 +159,7 @@ func (r *Torbox) GetTorrent(id string) (*Torrent, error) {
|
|||||||
torrent.Bytes = data.Size
|
torrent.Bytes = data.Size
|
||||||
torrent.Folder = name
|
torrent.Folder = name
|
||||||
torrent.Progress = data.Progress * 100
|
torrent.Progress = data.Progress * 100
|
||||||
torrent.Status = getStatus(data.DownloadState, data.DownloadFinished)
|
torrent.Status = getTorboxStatus(data.DownloadState, data.DownloadFinished)
|
||||||
torrent.Speed = data.DownloadSpeed
|
torrent.Speed = data.DownloadSpeed
|
||||||
torrent.Seeders = data.Seeds
|
torrent.Seeders = data.Seeds
|
||||||
torrent.Filename = name
|
torrent.Filename = name
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ func (t *Torrent) GetMountFolder(rClonePath string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, path := range possiblePaths {
|
for _, path := range possiblePaths {
|
||||||
if path != "" && common.FileReady(filepath.Join(rClonePath, path)) {
|
if common.FileReady(filepath.Join(rClonePath, path)) {
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user