Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df2aa4e361 | ||
|
|
b51cb954f8 | ||
|
|
8bdb2e3547 | ||
|
|
2c9a076cd2 | ||
|
|
d2a77620bc | ||
|
|
4b8f1ccfb6 | ||
|
|
f118c5b794 |
18
CHANGELOG.md
18
CHANGELOG.md
@@ -67,4 +67,20 @@
|
||||
- Add file download support(Sequential Download)
|
||||
- Fix http handler error
|
||||
- Fix *arrs map failing concurrently
|
||||
- Fix cache not being updated
|
||||
- Fix cache not being updated
|
||||
|
||||
#### 0.2.5
|
||||
- Fix ContentPath not being set prior
|
||||
- Rewrote Readme
|
||||
- Cleaned up the code
|
||||
|
||||
#### 0.2.6
|
||||
- Delete torrent for empty matched files
|
||||
- Update Readme
|
||||
|
||||
#### 0.2.7
|
||||
|
||||
- Add support for multiple debrid providers
|
||||
- Add Torbox support
|
||||
- Add support for configurable debrid cache checks
|
||||
- Add support for configurable debrid download uncached torrents
|
||||
66
README.md
66
README.md
@@ -1,13 +1,22 @@
|
||||
### GoBlackHole(with Debrid Proxy Support)
|
||||
|
||||
This is a Golang implementation go Torrent QbitTorrent with a **Real Debrid Proxy Support**.
|
||||
This is a Golang implementation go Torrent QbitTorrent with a **Real Debrid & Torbox Support**.
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
#### Uses
|
||||
- Mock Qbittorent API that supports the Arrs(Sonarr, Radarr, etc)
|
||||
- Proxy support for the Arrs
|
||||
- Real Debrid Support
|
||||
- Torbox Support
|
||||
- Multi-Debrid Providers support
|
||||
|
||||
The proxy is useful in filtering out un-cached Real Debrid torrents
|
||||
|
||||
### Supported Debrid Providers
|
||||
- Real Debrid
|
||||
- Torbox
|
||||
|
||||
### Changelog
|
||||
|
||||
- View the [CHANGELOG.md](CHANGELOG.md) for the latest changes
|
||||
@@ -37,6 +46,8 @@ services:
|
||||
- QBIT_PORT=8282 # qBittorrent Port. This is optional. You can set this in the config file
|
||||
- PORT=8181 # Proxy Port. This is optional. You can set this in the config file
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- rclone # If you are using rclone with docker
|
||||
|
||||
```
|
||||
|
||||
@@ -50,16 +61,29 @@ Download the binary from the releases page and run it with the config file.
|
||||
#### Config
|
||||
```json
|
||||
{
|
||||
"debrid": {
|
||||
"name": "realdebrid",
|
||||
"host": "https://api.real-debrid.com/rest/1.0",
|
||||
"api_key": "realdebrid_api_key",
|
||||
"folder": "data/realdebrid/torrents/",
|
||||
"rate_limit": "250/minute"
|
||||
},
|
||||
"debrids": [
|
||||
{
|
||||
"name": "torbox",
|
||||
"host": "https://api.torbox.app/v1",
|
||||
"api_key": "torbox_api_key",
|
||||
"folder": "data/realdebrid/torrents/",
|
||||
"rate_limit": "250/minute",
|
||||
"download_uncached": false,
|
||||
"check_cached": true
|
||||
},
|
||||
{
|
||||
"name": "realdebrid",
|
||||
"host": "https://api.real-debrid.com/rest/1.0",
|
||||
"api_key": "realdebrid_key",
|
||||
"folder": "data/realdebrid/torrents/",
|
||||
"rate_limit": "250/minute",
|
||||
"download_uncached": false,
|
||||
"check_cached": false
|
||||
}
|
||||
],
|
||||
"proxy": {
|
||||
"enabled": true,
|
||||
"port": "8181",
|
||||
"port": "8100",
|
||||
"debug": false,
|
||||
"username": "username",
|
||||
"password": "password",
|
||||
@@ -68,18 +92,28 @@ Download the binary from the releases page and run it with the config file.
|
||||
"max_cache_size": 1000,
|
||||
"qbittorrent": {
|
||||
"port": "8282",
|
||||
"username": "admin", // deprecated
|
||||
"password": "admin", // deprecated
|
||||
"download_folder": "/media/symlinks/",
|
||||
"categories": ["sonarr", "radarr"],
|
||||
"refresh_interval": 5 // in seconds
|
||||
"refresh_interval": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Config Notes
|
||||
##### Max Cache Size
|
||||
- The `max_cache_size` key is used to set the maximum number of infohashes that can be stored in the availability cache. This is used to prevent round trip to the debrid provider when using the proxy/Qbittorrent
|
||||
- The default value is `1000`
|
||||
- The cache is stored in memory and is not persisted on restart
|
||||
|
||||
##### Debrid Config
|
||||
- This config key is important as it's used for both Blackhole and Proxy
|
||||
- The `debrids` key is an array of debrid providers
|
||||
- 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 `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)
|
||||
|
||||
##### Proxy Config
|
||||
- The `enabled` key is used to enable the proxy
|
||||
@@ -93,6 +127,7 @@ Download the binary from the releases page and run it with the config file.
|
||||
- The `port` key is the port the qBittorrent will listen on
|
||||
- The `download_folder` is the folder where the torrents will be downloaded. e.g `/media/symlinks/`
|
||||
- The `categories` key is used to filter out torrents based on the category. e.g `sonarr`, `radarr`
|
||||
- The `refresh_interval` key is used to set the interval in minutes to refresh the Arrs Monitored Downloads(it's in seconds). The default value is `5` seconds
|
||||
|
||||
### Proxy
|
||||
|
||||
@@ -133,9 +168,6 @@ Setting Up Qbittorrent in Arr
|
||||
- [ ] Debrid
|
||||
- [ ] Add more Debrid Providers
|
||||
|
||||
- [ ] Proxy
|
||||
- [ ] Add more Proxy features
|
||||
|
||||
- [ ] Qbittorrent
|
||||
- [ ] Add more Qbittorrent features
|
||||
- [ ] Persist torrents on restart/server crash
|
||||
|
||||
@@ -13,7 +13,7 @@ func Start(config *common.Config) {
|
||||
maxCacheSize := cmp.Or(config.MaxCacheSize, 1000)
|
||||
cache := common.NewCache(maxCacheSize)
|
||||
|
||||
deb := debrid.NewDebrid(config.Debrid, cache)
|
||||
deb := debrid.NewDebrid(config.Debrids, cache)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ type DebridConfig struct {
|
||||
APIKey string `json:"api_key"`
|
||||
Folder string `json:"folder"`
|
||||
DownloadUncached bool `json:"download_uncached"`
|
||||
CheckCached bool `json:"check_cached"`
|
||||
RateLimit string `json:"rate_limit"` // 200/minute or 10/second
|
||||
}
|
||||
|
||||
@@ -36,6 +37,7 @@ type QBitTorrentConfig struct {
|
||||
|
||||
type Config struct {
|
||||
Debrid DebridConfig `json:"debrid"`
|
||||
Debrids []DebridConfig `json:"debrids"`
|
||||
Proxy ProxyConfig `json:"proxy"`
|
||||
MaxCacheSize int `json:"max_cache_size"`
|
||||
QBitTorrent QBitTorrentConfig `json:"qbittorrent"`
|
||||
@@ -60,9 +62,9 @@ func LoadConfig(path string) (*Config, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if config.Proxy.CachedOnly == nil {
|
||||
config.Proxy.CachedOnly = new(bool)
|
||||
*config.Proxy.CachedOnly = true
|
||||
|
||||
if config.Debrid.Name != "" {
|
||||
config.Debrids = append(config.Debrids, config.Debrid)
|
||||
}
|
||||
|
||||
return config, nil
|
||||
|
||||
@@ -60,11 +60,7 @@ func (c *RLHTTPClient) Do(req *http.Request) (*http.Response, error) {
|
||||
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
|
||||
}
|
||||
func (c *RLHTTPClient) MakeRequest(req *http.Request) ([]byte, error) {
|
||||
if c.Headers != nil {
|
||||
for key, value := range c.Headers {
|
||||
req.Header.Set(key, value)
|
||||
@@ -75,6 +71,7 @@ func (c *RLHTTPClient) MakeRequest(method string, url string, body io.Reader) ([
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b, _ := io.ReadAll(res.Body)
|
||||
statusOk := strconv.Itoa(res.StatusCode)[0] == '2'
|
||||
if !statusOk {
|
||||
return nil, fmt.Errorf("unexpected status code: %d", res.StatusCode)
|
||||
@@ -85,7 +82,7 @@ func (c *RLHTTPClient) MakeRequest(method string, url string, body io.Reader) ([
|
||||
log.Println(err)
|
||||
}
|
||||
}(res.Body)
|
||||
return io.ReadAll(res.Body)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func NewRLHTTPClient(rl *rate.Limiter, headers map[string]string) *RLHTTPClient {
|
||||
|
||||
@@ -8,20 +8,8 @@ import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
SubmitMagnet(torrent *Torrent) (*Torrent, error)
|
||||
CheckStatus(torrent *Torrent, isSymlink bool) (*Torrent, error)
|
||||
GetDownloadLinks(torrent *Torrent) error
|
||||
DeleteTorrent(torrent *Torrent)
|
||||
IsAvailable(infohashes []string) map[string]bool
|
||||
GetMountPath() string
|
||||
GetDownloadUncached() bool
|
||||
GetTorrent(id string) (*Torrent, error)
|
||||
GetName() string
|
||||
GetLogger() *log.Logger
|
||||
}
|
||||
|
||||
type Debrid struct {
|
||||
type BaseDebrid struct {
|
||||
Name string
|
||||
Host string `json:"host"`
|
||||
APIKey string
|
||||
DownloadUncached bool
|
||||
@@ -29,12 +17,41 @@ type Debrid struct {
|
||||
cache *common.Cache
|
||||
MountPath string
|
||||
logger *log.Logger
|
||||
CheckCached bool
|
||||
}
|
||||
|
||||
func NewDebrid(dc common.DebridConfig, cache *common.Cache) Service {
|
||||
type Service interface {
|
||||
SubmitMagnet(torrent *Torrent) (*Torrent, error)
|
||||
CheckStatus(torrent *Torrent, isSymlink bool) (*Torrent, error)
|
||||
GetDownloadLinks(torrent *Torrent) error
|
||||
DeleteTorrent(torrent *Torrent)
|
||||
IsAvailable(infohashes []string) map[string]bool
|
||||
GetMountPath() string
|
||||
GetCheckCached() bool
|
||||
GetTorrent(id string) (*Torrent, error)
|
||||
GetName() string
|
||||
GetLogger() *log.Logger
|
||||
}
|
||||
|
||||
func NewDebrid(debs []common.DebridConfig, cache *common.Cache) *DebridService {
|
||||
debrids := make([]Service, 0)
|
||||
for _, dc := range debs {
|
||||
d := createDebrid(dc, cache)
|
||||
d.GetLogger().Println("Debrid Service started")
|
||||
debrids = append(debrids, d)
|
||||
}
|
||||
d := &DebridService{debrids: debrids, lastUsed: 0}
|
||||
return d
|
||||
}
|
||||
|
||||
func createDebrid(dc common.DebridConfig, cache *common.Cache) Service {
|
||||
switch dc.Name {
|
||||
case "realdebrid":
|
||||
return NewRealDebrid(dc, cache)
|
||||
case "torbox":
|
||||
return NewTorbox(dc, cache)
|
||||
case "debridlink":
|
||||
return NewDebridLink(dc, cache)
|
||||
default:
|
||||
return NewRealDebrid(dc, cache)
|
||||
}
|
||||
@@ -95,32 +112,31 @@ func getTorrentInfo(filePath string) (*Torrent, error) {
|
||||
|
||||
func GetLocalCache(infohashes []string, cache *common.Cache) ([]string, map[string]bool) {
|
||||
result := make(map[string]bool)
|
||||
hashes := make([]string, len(infohashes))
|
||||
|
||||
if len(infohashes) == 0 {
|
||||
return hashes, result
|
||||
}
|
||||
if len(infohashes) == 1 {
|
||||
if cache.Exists(infohashes[0]) {
|
||||
return hashes, map[string]bool{infohashes[0]: true}
|
||||
}
|
||||
return infohashes, result
|
||||
}
|
||||
//if len(infohashes) == 0 {
|
||||
// return hashes, result
|
||||
//}
|
||||
//if len(infohashes) == 1 {
|
||||
// if cache.Exists(infohashes[0]) {
|
||||
// return hashes, map[string]bool{infohashes[0]: true}
|
||||
// }
|
||||
// return infohashes, result
|
||||
//}
|
||||
//
|
||||
//cachedHashes := cache.GetMultiple(infohashes)
|
||||
//for _, h := range infohashes {
|
||||
// _, exists := cachedHashes[h]
|
||||
// if !exists {
|
||||
// hashes = append(hashes, h)
|
||||
// } else {
|
||||
// result[h] = true
|
||||
// }
|
||||
//}
|
||||
|
||||
cachedHashes := cache.GetMultiple(infohashes)
|
||||
for _, h := range infohashes {
|
||||
_, exists := cachedHashes[h]
|
||||
if !exists {
|
||||
hashes = append(hashes, h)
|
||||
} else {
|
||||
result[h] = true
|
||||
}
|
||||
}
|
||||
|
||||
return hashes, result
|
||||
return infohashes, result
|
||||
}
|
||||
|
||||
func ProcessQBitTorrent(d Service, magnet *common.Magnet, arr *Arr, isSymlink bool) (*Torrent, error) {
|
||||
func ProcessQBitTorrent(d *DebridService, magnet *common.Magnet, arr *Arr, isSymlink bool) (*Torrent, error) {
|
||||
debridTorrent := &Torrent{
|
||||
InfoHash: magnet.InfoHash,
|
||||
Magnet: magnet,
|
||||
@@ -128,21 +144,30 @@ func ProcessQBitTorrent(d Service, magnet *common.Magnet, arr *Arr, isSymlink bo
|
||||
Arr: arr,
|
||||
Size: magnet.Size,
|
||||
}
|
||||
logger := d.GetLogger()
|
||||
logger.Printf("Torrent Hash: %s", debridTorrent.InfoHash)
|
||||
if !d.GetDownloadUncached() {
|
||||
hash, exists := d.IsAvailable([]string{debridTorrent.InfoHash})[debridTorrent.InfoHash]
|
||||
if !exists || !hash {
|
||||
return debridTorrent, fmt.Errorf("torrent: %s is not cached", debridTorrent.Name)
|
||||
} else {
|
||||
logger.Printf("Torrent: %s is cached(or downloading)", debridTorrent.Name)
|
||||
}
|
||||
}
|
||||
|
||||
debridTorrent, err := d.SubmitMagnet(debridTorrent)
|
||||
if err != nil || debridTorrent.Id == "" {
|
||||
logger.Printf("Error submitting magnet: %s", err)
|
||||
return nil, err
|
||||
for index, db := range d.debrids {
|
||||
log.Println("Processing debrid: ", db.GetName())
|
||||
logger := db.GetLogger()
|
||||
logger.Printf("Torrent Hash: %s", debridTorrent.InfoHash)
|
||||
if db.GetCheckCached() {
|
||||
hash, exists := db.IsAvailable([]string{debridTorrent.InfoHash})[debridTorrent.InfoHash]
|
||||
if !exists || !hash {
|
||||
logger.Printf("Torrent: %s is not cached", debridTorrent.Name)
|
||||
continue
|
||||
} else {
|
||||
logger.Printf("Torrent: %s is cached(or downloading)", debridTorrent.Name)
|
||||
}
|
||||
}
|
||||
|
||||
debridTorrent, err := db.SubmitMagnet(debridTorrent)
|
||||
if err != nil || debridTorrent.Id == "" {
|
||||
logger.Printf("Error submitting magnet: %s", err)
|
||||
continue
|
||||
}
|
||||
logger.Printf("Torrent: %s submitted to %s", debridTorrent.Name, db.GetName())
|
||||
d.lastUsed = index
|
||||
debridTorrent.Debrid = db
|
||||
return db.CheckStatus(debridTorrent, isSymlink)
|
||||
}
|
||||
return d.CheckStatus(debridTorrent, isSymlink)
|
||||
return nil, fmt.Errorf("failed to process torrent")
|
||||
}
|
||||
|
||||
269
pkg/debrid/debrid_link.go
Normal file
269
pkg/debrid/debrid_link.go
Normal file
@@ -0,0 +1,269 @@
|
||||
package debrid
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"goBlack/common"
|
||||
"goBlack/pkg/debrid/structs"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type DebridLink struct {
|
||||
BaseDebrid
|
||||
}
|
||||
|
||||
func (r *DebridLink) GetMountPath() string {
|
||||
return r.MountPath
|
||||
}
|
||||
|
||||
func (r *DebridLink) GetName() string {
|
||||
return r.Name
|
||||
}
|
||||
|
||||
func (r *DebridLink) GetLogger() *log.Logger {
|
||||
return r.logger
|
||||
}
|
||||
|
||||
func (r *DebridLink) 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
|
||||
for i := 0; i < len(hashes); i += 200 {
|
||||
end := i + 200
|
||||
if end > len(hashes) {
|
||||
end = len(hashes)
|
||||
}
|
||||
|
||||
// Filter out empty strings
|
||||
validHashes := make([]string, 0, end-i)
|
||||
for _, hash := range hashes[i:end] {
|
||||
if hash != "" {
|
||||
validHashes = append(validHashes, hash)
|
||||
}
|
||||
}
|
||||
|
||||
// If no valid hashes in this batch, continue to the next batch
|
||||
if len(validHashes) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
hashStr := strings.Join(validHashes, ",")
|
||||
url := fmt.Sprintf("%s/seedbox/cached/%s", r.Host, hashStr)
|
||||
req, _ := http.NewRequest(http.MethodGet, url, nil)
|
||||
resp, err := r.client.MakeRequest(req)
|
||||
if err != nil {
|
||||
log.Println("Error checking availability:", err)
|
||||
return result
|
||||
}
|
||||
var data structs.DebridLinkAvailableResponse
|
||||
err = json.Unmarshal(resp, &data)
|
||||
if err != nil {
|
||||
log.Println("Error marshalling availability:", err)
|
||||
return result
|
||||
}
|
||||
if data.Value == nil {
|
||||
return result
|
||||
}
|
||||
value := *data.Value
|
||||
for _, h := range hashes[i:end] {
|
||||
_, exists := value[h]
|
||||
if exists {
|
||||
result[h] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
r.cache.AddMultiple(result) // Add the results to the cache
|
||||
return result
|
||||
}
|
||||
|
||||
func (r *DebridLink) GetTorrent(id string) (*Torrent, error) {
|
||||
torrent := &Torrent{}
|
||||
url := fmt.Sprintf("%s/seedbox/list/?ids=%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.DebridLinkTorrentInfo
|
||||
err = json.Unmarshal(resp, &res)
|
||||
if err != nil {
|
||||
return torrent, err
|
||||
}
|
||||
if res.Success == false {
|
||||
return torrent, fmt.Errorf("error getting torrent")
|
||||
}
|
||||
if res.Value == nil {
|
||||
return torrent, fmt.Errorf("torrent not found")
|
||||
}
|
||||
dt := *res.Value
|
||||
fmt.Printf("Length of dt: %d\n", len(dt))
|
||||
fmt.Printf("Raw response: %+v\n", res)
|
||||
|
||||
if len(dt) == 0 {
|
||||
return torrent, fmt.Errorf("torrent not found")
|
||||
}
|
||||
data := dt[0]
|
||||
status := "downloading"
|
||||
name := common.RemoveInvalidChars(data.Name)
|
||||
torrent.Id = data.ID
|
||||
torrent.Name = name
|
||||
torrent.Bytes = data.TotalSize
|
||||
torrent.Folder = name
|
||||
torrent.Progress = data.DownloadPercent
|
||||
torrent.Status = status
|
||||
torrent.Speed = data.DownloadSpeed
|
||||
torrent.Seeders = data.PeersConnected
|
||||
torrent.Filename = name
|
||||
torrent.OriginalFilename = name
|
||||
files := make([]TorrentFile, len(data.Files))
|
||||
for i, f := range data.Files {
|
||||
files[i] = TorrentFile{
|
||||
Id: f.ID,
|
||||
Name: f.Name,
|
||||
Size: f.Size,
|
||||
}
|
||||
}
|
||||
torrent.Files = files
|
||||
torrent.Debrid = r
|
||||
return torrent, nil
|
||||
}
|
||||
|
||||
func (r *DebridLink) SubmitMagnet(torrent *Torrent) (*Torrent, error) {
|
||||
url := fmt.Sprintf("%s/seedbox/add", r.Host)
|
||||
payload := map[string]string{"url": torrent.Magnet.Link}
|
||||
jsonPayload, _ := json.Marshal(payload)
|
||||
req, _ := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(jsonPayload))
|
||||
resp, err := r.client.MakeRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var res structs.DebridLinkSubmitTorrentInfo
|
||||
err = json.Unmarshal(resp, &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if res.Success == false || res.Value == nil {
|
||||
return nil, fmt.Errorf("error adding torrent")
|
||||
}
|
||||
data := *res.Value
|
||||
status := "downloading"
|
||||
log.Printf("Torrent: %s added with id: %s\n", torrent.Name, data.ID)
|
||||
name := common.RemoveInvalidChars(data.Name)
|
||||
torrent.Id = data.ID
|
||||
torrent.Name = name
|
||||
torrent.Bytes = data.TotalSize
|
||||
torrent.Folder = name
|
||||
torrent.Progress = data.DownloadPercent
|
||||
torrent.Status = status
|
||||
torrent.Speed = data.DownloadSpeed
|
||||
torrent.Seeders = data.PeersConnected
|
||||
torrent.Filename = name
|
||||
torrent.OriginalFilename = name
|
||||
files := make([]TorrentFile, len(data.Files))
|
||||
for i, f := range data.Files {
|
||||
files[i] = TorrentFile{
|
||||
Id: f.ID,
|
||||
Name: f.Name,
|
||||
Size: f.Size,
|
||||
Link: f.DownloadURL,
|
||||
}
|
||||
}
|
||||
torrent.Files = files
|
||||
torrent.Debrid = r
|
||||
|
||||
return torrent, nil
|
||||
}
|
||||
|
||||
func (r *DebridLink) CheckStatus(torrent *Torrent, isSymlink bool) (*Torrent, error) {
|
||||
for {
|
||||
torrent, err := r.GetTorrent(torrent.Id)
|
||||
|
||||
if err != nil || torrent == nil {
|
||||
return torrent, err
|
||||
}
|
||||
status := torrent.Status
|
||||
if status == "error" || status == "dead" || status == "magnet_error" {
|
||||
return torrent, fmt.Errorf("torrent: %s has error", torrent.Name)
|
||||
} else 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 r.DeleteTorrent(torrent)
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
return torrent, nil
|
||||
}
|
||||
|
||||
func (r *DebridLink) DeleteTorrent(torrent *Torrent) {
|
||||
url := fmt.Sprintf("%s/seedbox/%s/remove", r.Host, torrent.Id)
|
||||
req, _ := http.NewRequest(http.MethodDelete, 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 *DebridLink) GetDownloadLinks(torrent *Torrent) error {
|
||||
downloadLinks := make([]TorrentDownloadLinks, 0)
|
||||
for _, f := range torrent.Files {
|
||||
dl := TorrentDownloadLinks{
|
||||
Link: f.Link,
|
||||
Filename: f.Name,
|
||||
}
|
||||
downloadLinks = append(downloadLinks, dl)
|
||||
}
|
||||
torrent.DownloadLinks = downloadLinks
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *DebridLink) GetCheckCached() bool {
|
||||
return r.CheckCached
|
||||
}
|
||||
|
||||
func NewDebridLink(dc common.DebridConfig, cache *common.Cache) *DebridLink {
|
||||
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 &DebridLink{
|
||||
BaseDebrid: BaseDebrid{
|
||||
Name: "debridlink",
|
||||
Host: dc.Host,
|
||||
APIKey: dc.APIKey,
|
||||
DownloadUncached: dc.DownloadUncached,
|
||||
client: client,
|
||||
cache: cache,
|
||||
MountPath: dc.Folder,
|
||||
logger: logger,
|
||||
CheckCached: dc.CheckCached,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -15,13 +15,7 @@ import (
|
||||
)
|
||||
|
||||
type RealDebrid struct {
|
||||
Host string `json:"host"`
|
||||
APIKey string
|
||||
DownloadUncached bool
|
||||
client *common.RLHTTPClient
|
||||
cache *common.Cache
|
||||
MountPath string
|
||||
logger *log.Logger
|
||||
BaseDebrid
|
||||
}
|
||||
|
||||
func (r *RealDebrid) GetMountPath() string {
|
||||
@@ -29,7 +23,7 @@ func (r *RealDebrid) GetMountPath() string {
|
||||
}
|
||||
|
||||
func (r *RealDebrid) GetName() string {
|
||||
return "realdebrid"
|
||||
return r.Name
|
||||
}
|
||||
|
||||
func (r *RealDebrid) GetLogger() *log.Logger {
|
||||
@@ -89,7 +83,8 @@ func (r *RealDebrid) IsAvailable(infohashes []string) map[string]bool {
|
||||
|
||||
hashStr := strings.Join(validHashes, "/")
|
||||
url := fmt.Sprintf("%s/torrents/instantAvailability/%s", r.Host, hashStr)
|
||||
resp, err := r.client.MakeRequest(http.MethodGet, url, nil)
|
||||
req, _ := http.NewRequest(http.MethodGet, url, nil)
|
||||
resp, err := r.client.MakeRequest(req)
|
||||
if err != nil {
|
||||
log.Println("Error checking availability:", err)
|
||||
return result
|
||||
@@ -117,7 +112,8 @@ func (r *RealDebrid) SubmitMagnet(torrent *Torrent) (*Torrent, error) {
|
||||
"magnet": {torrent.Magnet.Link},
|
||||
}
|
||||
var data structs.RealDebridAddMagnetSchema
|
||||
resp, err := r.client.MakeRequest(http.MethodPost, url, strings.NewReader(payload.Encode()))
|
||||
req, _ := http.NewRequest(http.MethodPost, url, strings.NewReader(payload.Encode()))
|
||||
resp, err := r.client.MakeRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -131,7 +127,8 @@ func (r *RealDebrid) SubmitMagnet(torrent *Torrent) (*Torrent, error) {
|
||||
func (r *RealDebrid) GetTorrent(id string) (*Torrent, error) {
|
||||
torrent := &Torrent{}
|
||||
url := fmt.Sprintf("%s/torrents/info/%s", r.Host, id)
|
||||
resp, err := r.client.MakeRequest(http.MethodGet, url, nil)
|
||||
req, _ := http.NewRequest(http.MethodGet, url, nil)
|
||||
resp, err := r.client.MakeRequest(req)
|
||||
if err != nil {
|
||||
return torrent, err
|
||||
}
|
||||
@@ -152,6 +149,7 @@ func (r *RealDebrid) GetTorrent(id string) (*Torrent, error) {
|
||||
torrent.Filename = data.Filename
|
||||
torrent.OriginalFilename = data.OriginalFilename
|
||||
torrent.Links = data.Links
|
||||
torrent.Debrid = r
|
||||
files := GetTorrentFiles(data)
|
||||
torrent.Files = files
|
||||
return torrent, nil
|
||||
@@ -159,8 +157,9 @@ func (r *RealDebrid) GetTorrent(id string) (*Torrent, error) {
|
||||
|
||||
func (r *RealDebrid) CheckStatus(torrent *Torrent, isSymlink bool) (*Torrent, error) {
|
||||
url := fmt.Sprintf("%s/torrents/info/%s", r.Host, torrent.Id)
|
||||
req, _ := http.NewRequest(http.MethodGet, url, nil)
|
||||
for {
|
||||
resp, err := r.client.MakeRequest(http.MethodGet, url, nil)
|
||||
resp, err := r.client.MakeRequest(req)
|
||||
if err != nil {
|
||||
log.Println("ERROR Checking file: ", err)
|
||||
return torrent, err
|
||||
@@ -179,12 +178,14 @@ func (r *RealDebrid) CheckStatus(torrent *Torrent, isSymlink bool) (*Torrent, er
|
||||
torrent.Seeders = data.Seeders
|
||||
torrent.Links = data.Links
|
||||
torrent.Status = status
|
||||
torrent.Debrid = r
|
||||
if status == "error" || status == "dead" || status == "magnet_error" {
|
||||
return torrent, fmt.Errorf("torrent: %s has error", torrent.Name)
|
||||
} else if status == "waiting_files_selection" {
|
||||
files := GetTorrentFiles(data)
|
||||
torrent.Files = files
|
||||
if len(files) == 0 {
|
||||
r.DeleteTorrent(torrent)
|
||||
return torrent, fmt.Errorf("no video files found")
|
||||
}
|
||||
filesId := make([]string, 0)
|
||||
@@ -195,7 +196,8 @@ func (r *RealDebrid) CheckStatus(torrent *Torrent, isSymlink bool) (*Torrent, er
|
||||
"files": {strings.Join(filesId, ",")},
|
||||
}
|
||||
payload := strings.NewReader(p.Encode())
|
||||
_, err = r.client.MakeRequest(http.MethodPost, fmt.Sprintf("%s/torrents/selectFiles/%s", r.Host, torrent.Id), payload)
|
||||
req, _ := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/torrents/selectFiles/%s", r.Host, torrent.Id), payload)
|
||||
_, err = r.client.MakeRequest(req)
|
||||
if err != nil {
|
||||
return torrent, err
|
||||
}
|
||||
@@ -209,7 +211,6 @@ func (r *RealDebrid) CheckStatus(torrent *Torrent, isSymlink bool) (*Torrent, er
|
||||
return torrent, err
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
} else if status == "downloading" {
|
||||
if !r.DownloadUncached {
|
||||
@@ -227,7 +228,8 @@ func (r *RealDebrid) CheckStatus(torrent *Torrent, isSymlink bool) (*Torrent, er
|
||||
|
||||
func (r *RealDebrid) DeleteTorrent(torrent *Torrent) {
|
||||
url := fmt.Sprintf("%s/torrents/delete/%s", r.Host, torrent.Id)
|
||||
_, err := r.client.MakeRequest(http.MethodDelete, url, nil)
|
||||
req, _ := http.NewRequest(http.MethodDelete, url, nil)
|
||||
_, err := r.client.MakeRequest(req)
|
||||
if err == nil {
|
||||
r.logger.Printf("Torrent: %s deleted\n", torrent.Name)
|
||||
} else {
|
||||
@@ -245,7 +247,8 @@ func (r *RealDebrid) GetDownloadLinks(torrent *Torrent) error {
|
||||
payload := gourl.Values{
|
||||
"link": {link},
|
||||
}
|
||||
resp, err := r.client.MakeRequest(http.MethodPost, url, strings.NewReader(payload.Encode()))
|
||||
req, _ := http.NewRequest(http.MethodPost, url, strings.NewReader(payload.Encode()))
|
||||
resp, err := r.client.MakeRequest(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -264,8 +267,8 @@ func (r *RealDebrid) GetDownloadLinks(torrent *Torrent) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RealDebrid) GetDownloadUncached() bool {
|
||||
return r.DownloadUncached
|
||||
func (r *RealDebrid) GetCheckCached() bool {
|
||||
return r.CheckCached
|
||||
}
|
||||
|
||||
func NewRealDebrid(dc common.DebridConfig, cache *common.Cache) *RealDebrid {
|
||||
@@ -276,12 +279,16 @@ func NewRealDebrid(dc common.DebridConfig, cache *common.Cache) *RealDebrid {
|
||||
client := common.NewRLHTTPClient(rl, headers)
|
||||
logger := common.NewLogger(dc.Name, os.Stdout)
|
||||
return &RealDebrid{
|
||||
Host: dc.Host,
|
||||
APIKey: dc.APIKey,
|
||||
DownloadUncached: dc.DownloadUncached,
|
||||
client: client,
|
||||
cache: cache,
|
||||
MountPath: dc.Folder,
|
||||
logger: logger,
|
||||
BaseDebrid: BaseDebrid{
|
||||
Name: "realdebrid",
|
||||
Host: dc.Host,
|
||||
APIKey: dc.APIKey,
|
||||
DownloadUncached: dc.DownloadUncached,
|
||||
client: client,
|
||||
cache: cache,
|
||||
MountPath: dc.Folder,
|
||||
logger: logger,
|
||||
CheckCached: dc.CheckCached,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
13
pkg/debrid/service.go
Normal file
13
pkg/debrid/service.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package debrid
|
||||
|
||||
type DebridService struct {
|
||||
debrids []Service
|
||||
lastUsed int
|
||||
}
|
||||
|
||||
func (d *DebridService) Get() Service {
|
||||
if d.lastUsed == 0 {
|
||||
return d.debrids[0]
|
||||
}
|
||||
return d.debrids[d.lastUsed]
|
||||
}
|
||||
45
pkg/debrid/structs/debrid_link.go
Normal file
45
pkg/debrid/structs/debrid_link.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package structs
|
||||
|
||||
type DebridLinkAPIResponse[T any] struct {
|
||||
Success bool `json:"success"`
|
||||
Value *T `json:"value"` // Use pointer to allow nil
|
||||
}
|
||||
|
||||
type DebridLinkAvailableResponse DebridLinkAPIResponse[map[string]map[string]struct {
|
||||
Name string `json:"name"`
|
||||
HashString string `json:"hashString"`
|
||||
Files []struct {
|
||||
Name string `json:"name"`
|
||||
Size int `json:"size"`
|
||||
} `json:"files"`
|
||||
}]
|
||||
|
||||
type debridLinkTorrentInfo struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
HashString string `json:"hashString"`
|
||||
UploadRatio float64 `json:"uploadRatio"`
|
||||
ServerID string `json:"serverId"`
|
||||
Wait bool `json:"wait"`
|
||||
PeersConnected int `json:"peersConnected"`
|
||||
Status int `json:"status"`
|
||||
TotalSize int64 `json:"totalSize"`
|
||||
Files []struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
DownloadURL string `json:"downloadUrl"`
|
||||
Size int64 `json:"size"`
|
||||
DownloadPercent int `json:"downloadPercent"`
|
||||
} `json:"files"`
|
||||
Trackers []struct {
|
||||
Announce string `json:"announce"`
|
||||
} `json:"trackers"`
|
||||
Created int64 `json:"created"`
|
||||
DownloadPercent float64 `json:"downloadPercent"`
|
||||
DownloadSpeed int64 `json:"downloadSpeed"`
|
||||
UploadSpeed int64 `json:"uploadSpeed"`
|
||||
}
|
||||
|
||||
type DebridLinkTorrentInfo DebridLinkAPIResponse[[]debridLinkTorrentInfo]
|
||||
|
||||
type DebridLinkSubmitTorrentInfo DebridLinkAPIResponse[debridLinkTorrentInfo]
|
||||
75
pkg/debrid/structs/torbox.go
Normal file
75
pkg/debrid/structs/torbox.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package structs
|
||||
|
||||
import "time"
|
||||
|
||||
type TorboxAPIResponse[T any] struct {
|
||||
Success bool `json:"success"`
|
||||
Error any `json:"error"`
|
||||
Detail string `json:"detail"`
|
||||
Data *T `json:"data"` // Use pointer to allow nil
|
||||
}
|
||||
|
||||
type TorBoxAvailableResponse TorboxAPIResponse[map[string]struct {
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
Hash string `json:"hash"`
|
||||
}]
|
||||
|
||||
type TorBoxAddMagnetResponse TorboxAPIResponse[struct {
|
||||
Id int `json:"torrent_id"`
|
||||
Hash string `json:"hash"`
|
||||
}]
|
||||
|
||||
type torboxInfo struct {
|
||||
Id int `json:"id"`
|
||||
AuthId string `json:"auth_id"`
|
||||
Server int `json:"server"`
|
||||
Hash string `json:"hash"`
|
||||
Name string `json:"name"`
|
||||
Magnet interface{} `json:"magnet"`
|
||||
Size int64 `json:"size"`
|
||||
Active bool `json:"active"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DownloadState string `json:"download_state"`
|
||||
Seeds int `json:"seeds"`
|
||||
Peers int `json:"peers"`
|
||||
Ratio int `json:"ratio"`
|
||||
Progress float64 `json:"progress"`
|
||||
DownloadSpeed int64 `json:"download_speed"`
|
||||
UploadSpeed int `json:"upload_speed"`
|
||||
Eta int `json:"eta"`
|
||||
TorrentFile bool `json:"torrent_file"`
|
||||
ExpiresAt interface{} `json:"expires_at"`
|
||||
DownloadPresent bool `json:"download_present"`
|
||||
Files []struct {
|
||||
Id int `json:"id"`
|
||||
Md5 interface{} `json:"md5"`
|
||||
Hash string `json:"hash"`
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
Zipped bool `json:"zipped"`
|
||||
S3Path string `json:"s3_path"`
|
||||
Infected bool `json:"infected"`
|
||||
Mimetype string `json:"mimetype"`
|
||||
ShortName string `json:"short_name"`
|
||||
AbsolutePath string `json:"absolute_path"`
|
||||
} `json:"files"`
|
||||
DownloadPath string `json:"download_path"`
|
||||
InactiveCheck int `json:"inactive_check"`
|
||||
Availability int `json:"availability"`
|
||||
DownloadFinished bool `json:"download_finished"`
|
||||
Tracker interface{} `json:"tracker"`
|
||||
TotalUploaded int `json:"total_uploaded"`
|
||||
TotalDownloaded int `json:"total_downloaded"`
|
||||
Cached bool `json:"cached"`
|
||||
Owner string `json:"owner"`
|
||||
SeedTorrent bool `json:"seed_torrent"`
|
||||
AllowZipped bool `json:"allow_zipped"`
|
||||
LongTermSeeding bool `json:"long_term_seeding"`
|
||||
TrackerMessage interface{} `json:"tracker_message"`
|
||||
}
|
||||
|
||||
type TorboxInfoResponse TorboxAPIResponse[torboxInfo]
|
||||
|
||||
type TorBoxDownloadLinksResponse TorboxAPIResponse[string]
|
||||
290
pkg/debrid/torbox.go
Normal file
290
pkg/debrid/torbox.go
Normal file
@@ -0,0 +1,290 @@
|
||||
package debrid
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"goBlack/common"
|
||||
"goBlack/pkg/debrid/structs"
|
||||
"log"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
gourl "net/url"
|
||||
"os"
|
||||
"path"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Torbox struct {
|
||||
BaseDebrid
|
||||
}
|
||||
|
||||
func (r *Torbox) GetMountPath() string {
|
||||
return r.MountPath
|
||||
}
|
||||
|
||||
func (r *Torbox) GetName() string {
|
||||
return r.Name
|
||||
}
|
||||
|
||||
func (r *Torbox) GetLogger() *log.Logger {
|
||||
return r.logger
|
||||
}
|
||||
|
||||
func (r *Torbox) 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
|
||||
for i := 0; i < len(hashes); i += 200 {
|
||||
end := i + 200
|
||||
if end > len(hashes) {
|
||||
end = len(hashes)
|
||||
}
|
||||
|
||||
// Filter out empty strings
|
||||
validHashes := make([]string, 0, end-i)
|
||||
for _, hash := range hashes[i:end] {
|
||||
if hash != "" {
|
||||
validHashes = append(validHashes, hash)
|
||||
}
|
||||
}
|
||||
|
||||
// If no valid hashes in this batch, continue to the next batch
|
||||
if len(validHashes) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
hashStr := strings.Join(validHashes, ",")
|
||||
url := fmt.Sprintf("%s/api/torrents/checkcached?hash=%s", r.Host, hashStr)
|
||||
req, _ := http.NewRequest(http.MethodGet, url, nil)
|
||||
resp, err := r.client.MakeRequest(req)
|
||||
if err != nil {
|
||||
log.Println("Error checking availability:", err)
|
||||
return result
|
||||
}
|
||||
var res structs.TorBoxAvailableResponse
|
||||
err = json.Unmarshal(resp, &res)
|
||||
if err != nil {
|
||||
log.Println("Error marshalling availability:", err)
|
||||
return result
|
||||
}
|
||||
if res.Data == nil {
|
||||
return result
|
||||
}
|
||||
|
||||
for h, cache := range *res.Data {
|
||||
if cache.Size > 0 {
|
||||
result[strings.ToUpper(h)] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
r.cache.AddMultiple(result) // Add the results to the cache
|
||||
return result
|
||||
}
|
||||
|
||||
func (r *Torbox) SubmitMagnet(torrent *Torrent) (*Torrent, error) {
|
||||
url := fmt.Sprintf("%s/api/torrents/createtorrent", r.Host)
|
||||
payload := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(payload)
|
||||
_ = writer.WriteField("magnet", torrent.Magnet.Link)
|
||||
err := writer.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, _ := http.NewRequest(http.MethodPost, url, payload)
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
resp, err := r.client.MakeRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data structs.TorBoxAddMagnetResponse
|
||||
err = json.Unmarshal(resp, &data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if data.Data == nil {
|
||||
return nil, fmt.Errorf("error adding torrent")
|
||||
}
|
||||
dt := *data.Data
|
||||
torrentId := strconv.Itoa(dt.Id)
|
||||
log.Printf("Torrent: %s added with id: %s\n", torrent.Name, torrentId)
|
||||
torrent.Id = torrentId
|
||||
|
||||
return torrent, nil
|
||||
}
|
||||
|
||||
func getStatus(status string, finished bool) string {
|
||||
if finished {
|
||||
return "downloaded"
|
||||
}
|
||||
downloading := []string{"completed", "cached", "paused", "downloading", "uploading",
|
||||
"checkingResumeData", "metaDL", "pausedUP", "queuedUP", "checkingUP",
|
||||
"forcedUP", "allocating", "downloading", "metaDL", "pausedDL",
|
||||
"queuedDL", "checkingDL", "forcedDL", "checkingResumeData", "moving"}
|
||||
switch {
|
||||
case slices.Contains(downloading, status):
|
||||
return "downloading"
|
||||
default:
|
||||
return "error"
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Torbox) GetTorrent(id string) (*Torrent, error) {
|
||||
torrent := &Torrent{}
|
||||
url := fmt.Sprintf("%s/api/torrents/mylist/?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.TorboxInfoResponse
|
||||
err = json.Unmarshal(resp, &res)
|
||||
if err != nil {
|
||||
return torrent, err
|
||||
}
|
||||
data := res.Data
|
||||
name := data.Name
|
||||
torrent.Id = id
|
||||
torrent.Name = name
|
||||
torrent.Bytes = data.Size
|
||||
torrent.Folder = name
|
||||
torrent.Progress = data.Progress
|
||||
torrent.Status = getStatus(data.DownloadState, data.DownloadFinished)
|
||||
torrent.Speed = data.DownloadSpeed
|
||||
torrent.Seeders = data.Seeds
|
||||
torrent.Filename = name
|
||||
torrent.OriginalFilename = name
|
||||
files := make([]TorrentFile, len(data.Files))
|
||||
for i, f := range data.Files {
|
||||
files[i] = TorrentFile{
|
||||
Id: strconv.Itoa(f.Id),
|
||||
Name: f.Name,
|
||||
Size: f.Size,
|
||||
}
|
||||
}
|
||||
if len(files) > 0 && name == data.Hash {
|
||||
cleanPath := path.Clean(files[0].Name)
|
||||
torrent.OriginalFilename = strings.Split(strings.TrimPrefix(cleanPath, "/"), "/")[0]
|
||||
}
|
||||
torrent.Files = files
|
||||
torrent.Debrid = r
|
||||
return torrent, nil
|
||||
}
|
||||
|
||||
func (r *Torbox) 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 == "error" || status == "dead" || status == "magnet_error" {
|
||||
return torrent, fmt.Errorf("torrent: %s has error", torrent.Name)
|
||||
} else 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 r.DeleteTorrent(torrent)
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
return torrent, nil
|
||||
}
|
||||
|
||||
func (r *Torbox) DeleteTorrent(torrent *Torrent) {
|
||||
url := fmt.Sprintf("%s/api//torrents/controltorrent/%s", r.Host, torrent.Id)
|
||||
payload := map[string]string{"torrent_id": torrent.Id, "action": "Delete"}
|
||||
jsonPayload, _ := json.Marshal(payload)
|
||||
req, _ := http.NewRequest(http.MethodDelete, url, bytes.NewBuffer(jsonPayload))
|
||||
_, 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 *Torbox) GetDownloadLinks(torrent *Torrent) error {
|
||||
downloadLinks := make([]TorrentDownloadLinks, 0)
|
||||
for _, file := range torrent.Files {
|
||||
url := fmt.Sprintf("%s/api/torrents/requestdl/", r.Host)
|
||||
query := gourl.Values{}
|
||||
query.Add("torrent_id", torrent.Id)
|
||||
query.Add("token", r.APIKey)
|
||||
query.Add("file_id", file.Id)
|
||||
url += "?" + query.Encode()
|
||||
req, _ := http.NewRequest(http.MethodGet, url, nil)
|
||||
resp, err := r.client.MakeRequest(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var data structs.TorBoxDownloadLinksResponse
|
||||
if err = json.Unmarshal(resp, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
if data.Data == nil {
|
||||
return fmt.Errorf("error getting download links")
|
||||
}
|
||||
idx := 0
|
||||
link := *data.Data
|
||||
|
||||
dl := TorrentDownloadLinks{
|
||||
Link: link,
|
||||
Filename: torrent.Files[idx].Name,
|
||||
DownloadLink: link,
|
||||
}
|
||||
downloadLinks = append(downloadLinks, dl)
|
||||
}
|
||||
torrent.DownloadLinks = downloadLinks
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Torbox) GetCheckCached() bool {
|
||||
return r.CheckCached
|
||||
}
|
||||
|
||||
func NewTorbox(dc common.DebridConfig, cache *common.Cache) *Torbox {
|
||||
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 &Torbox{
|
||||
BaseDebrid: BaseDebrid{
|
||||
Name: "torbox",
|
||||
Host: dc.Host,
|
||||
APIKey: dc.APIKey,
|
||||
DownloadUncached: dc.DownloadUncached,
|
||||
client: client,
|
||||
cache: cache,
|
||||
MountPath: dc.Folder,
|
||||
logger: logger,
|
||||
CheckCached: dc.CheckCached,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -36,13 +36,14 @@ type Torrent struct {
|
||||
Magnet *common.Magnet `json:"magnet"`
|
||||
Files []TorrentFile `json:"files"`
|
||||
Status string `json:"status"`
|
||||
Added string `json:"added"`
|
||||
Progress float64 `json:"progress"`
|
||||
Speed int64 `json:"speed"`
|
||||
Seeders int `json:"seeders"`
|
||||
Links []string `json:"links"`
|
||||
DownloadLinks []TorrentDownloadLinks `json:"download_links"`
|
||||
|
||||
Debrid *Debrid
|
||||
Debrid Service
|
||||
Arr *Arr
|
||||
}
|
||||
|
||||
@@ -57,12 +58,11 @@ func (t *Torrent) GetSymlinkFolder(parent string) string {
|
||||
}
|
||||
|
||||
func (t *Torrent) GetMountFolder(rClonePath string) string {
|
||||
pathWithNoExt := common.RemoveExtension(t.OriginalFilename)
|
||||
if common.FileReady(filepath.Join(rClonePath, t.OriginalFilename)) {
|
||||
return t.OriginalFilename
|
||||
} else if common.FileReady(filepath.Join(rClonePath, t.Filename)) {
|
||||
return t.Filename
|
||||
} else if common.FileReady(filepath.Join(rClonePath, pathWithNoExt)) {
|
||||
} else if pathWithNoExt := common.RemoveExtension(t.OriginalFilename); common.FileReady(filepath.Join(rClonePath, pathWithNoExt)) {
|
||||
return pathWithNoExt
|
||||
} else {
|
||||
return ""
|
||||
@@ -74,6 +74,7 @@ type TorrentFile struct {
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
Path string `json:"path"`
|
||||
Link string `json:"link"`
|
||||
}
|
||||
|
||||
func getEventId(eventType string) int {
|
||||
|
||||
@@ -77,7 +77,7 @@ type Proxy struct {
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
func NewProxy(config common.Config, deb debrid.Service, cache *common.Cache) *Proxy {
|
||||
func NewProxy(config common.Config, deb *debrid.DebridService, cache *common.Cache) *Proxy {
|
||||
cfg := config.Proxy
|
||||
port := cmp.Or(os.Getenv("PORT"), cfg.Port, "8181")
|
||||
return &Proxy{
|
||||
@@ -87,7 +87,7 @@ func NewProxy(config common.Config, deb debrid.Service, cache *common.Cache) *Pr
|
||||
username: cfg.Username,
|
||||
password: cfg.Password,
|
||||
cachedOnly: *cfg.CachedOnly,
|
||||
debrid: deb,
|
||||
debrid: deb.Get(),
|
||||
cache: cache,
|
||||
logger: common.NewLogger("Proxy", os.Stdout),
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ func (q *QBit) processSymlink(torrent *Torrent, debridTorrent *debrid.Torrent, a
|
||||
ready := make(chan debrid.TorrentFile, len(files))
|
||||
|
||||
q.logger.Printf("Checking %d files...", len(files))
|
||||
rCloneBase := q.debrid.GetMountPath()
|
||||
rCloneBase := debridTorrent.Debrid.GetMountPath()
|
||||
torrentPath, err := q.getTorrentPath(rCloneBase, debridTorrent) // /MyTVShow/
|
||||
if err != nil {
|
||||
q.MarkAsFailed(torrent)
|
||||
@@ -125,7 +125,7 @@ func (q *QBit) createSymLink(path string, torrentMountPath string, file debrid.T
|
||||
torrentFilePath := filepath.Join(torrentMountPath, file.Name) // debridFolder/MyTVShow/MyTVShow.S01E01.720p.mkv
|
||||
err := os.Symlink(torrentFilePath, fullPath)
|
||||
if err != nil {
|
||||
q.logger.Printf("Failed to create symlink: %s\n", fullPath)
|
||||
q.logger.Printf("Failed to create symlink: %s: %v\n", fullPath, err)
|
||||
}
|
||||
// Check if the file exists
|
||||
if !common.FileReady(fullPath) {
|
||||
|
||||
@@ -24,6 +24,7 @@ func (q *QBit) AddRoutes(r chi.Router) http.Handler {
|
||||
r.Get("/resume", q.handleTorrentsResume)
|
||||
r.Get("/recheck", q.handleTorrentRecheck)
|
||||
r.Get("/properties", q.handleTorrentProperties)
|
||||
r.Get("/files", q.handleTorrentFiles)
|
||||
})
|
||||
|
||||
r.Route("/app", func(r chi.Router) {
|
||||
|
||||
@@ -5,6 +5,5 @@ import (
|
||||
)
|
||||
|
||||
func (q *QBit) handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("Ok."))
|
||||
_, _ = w.Write([]byte("Ok."))
|
||||
}
|
||||
|
||||
@@ -163,3 +163,13 @@ func (q *QBit) handleTorrentProperties(w http.ResponseWriter, r *http.Request) {
|
||||
properties := q.GetTorrentProperties(torrent)
|
||||
JSONResponse(w, properties, http.StatusOK)
|
||||
}
|
||||
|
||||
func (q *QBit) handleTorrentFiles(w http.ResponseWriter, r *http.Request) {
|
||||
hash := r.URL.Query().Get("hash")
|
||||
torrent := q.storage.Get(hash)
|
||||
if torrent == nil {
|
||||
return
|
||||
}
|
||||
files := q.GetTorrentFiles(torrent)
|
||||
JSONResponse(w, files, http.StatusOK)
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ type QBit struct {
|
||||
Port string `json:"port"`
|
||||
DownloadFolder string `json:"download_folder"`
|
||||
Categories []string `json:"categories"`
|
||||
debrid debrid.Service
|
||||
debrid *debrid.DebridService
|
||||
cache *common.Cache
|
||||
storage *TorrentStorage
|
||||
debug bool
|
||||
@@ -39,7 +39,7 @@ type QBit struct {
|
||||
RefreshInterval int
|
||||
}
|
||||
|
||||
func NewQBit(config *common.Config, deb debrid.Service, cache *common.Cache) *QBit {
|
||||
func NewQBit(config *common.Config, deb *debrid.DebridService, cache *common.Cache) *QBit {
|
||||
cfg := config.QBitTorrent
|
||||
storage := NewTorrentStorage("torrents.json")
|
||||
port := cmp.Or(cfg.Port, os.Getenv("QBIT_PORT"), "8182")
|
||||
|
||||
@@ -58,9 +58,7 @@ func (q *QBit) Process(ctx context.Context, magnet *common.Magnet, category stri
|
||||
}
|
||||
return err
|
||||
}
|
||||
torrent.ID = debridTorrent.Id
|
||||
torrent.DebridTorrent = debridTorrent
|
||||
torrent.Name = debridTorrent.Name
|
||||
torrent = q.UpdateTorrentMin(torrent, debridTorrent)
|
||||
q.storage.AddOrUpdate(torrent)
|
||||
go q.processFiles(torrent, debridTorrent, arr, isSymlink) // We can send async for file processing not to delay the response
|
||||
return nil
|
||||
@@ -74,23 +72,14 @@ func (q *QBit) CreateTorrentFromMagnet(magnet *common.Magnet, category string) *
|
||||
Size: magnet.Size,
|
||||
Category: category,
|
||||
State: "downloading",
|
||||
AddedOn: time.Now().Unix(),
|
||||
MagnetUri: magnet.Link,
|
||||
|
||||
Tracker: "udp://tracker.opentrackr.org:1337",
|
||||
UpLimit: -1,
|
||||
DlLimit: -1,
|
||||
FlPiecePrio: false,
|
||||
ForceStart: false,
|
||||
AutoTmm: false,
|
||||
Availability: 2,
|
||||
MaxRatio: -1,
|
||||
MaxSeedingTime: -1,
|
||||
NumComplete: 10,
|
||||
NumIncomplete: 0,
|
||||
NumLeechs: 1,
|
||||
Ratio: 1,
|
||||
RatioLimit: 1,
|
||||
Tracker: "udp://tracker.opentrackr.org:1337",
|
||||
UpLimit: -1,
|
||||
DlLimit: -1,
|
||||
AutoTmm: false,
|
||||
Ratio: 1,
|
||||
RatioLimit: 1,
|
||||
}
|
||||
return torrent
|
||||
}
|
||||
@@ -98,15 +87,17 @@ func (q *QBit) CreateTorrentFromMagnet(magnet *common.Magnet, category string) *
|
||||
func (q *QBit) processFiles(torrent *Torrent, debridTorrent *debrid.Torrent, arr *debrid.Arr, isSymlink bool) {
|
||||
for debridTorrent.Status != "downloaded" {
|
||||
progress := debridTorrent.Progress
|
||||
q.logger.Printf("Progress: %.2f%%", progress)
|
||||
q.logger.Printf("%s Download Progress: %.2f%%", debridTorrent.Debrid.GetName(), progress)
|
||||
time.Sleep(5 * time.Second)
|
||||
dbT, err := q.debrid.CheckStatus(debridTorrent, isSymlink)
|
||||
dbT, err := debridTorrent.Debrid.CheckStatus(debridTorrent, isSymlink)
|
||||
if err != nil {
|
||||
q.logger.Printf("Error checking status: %v", err)
|
||||
q.MarkAsFailed(torrent)
|
||||
q.RefreshArr(arr)
|
||||
return
|
||||
}
|
||||
debridTorrent = dbT
|
||||
torrent = q.UpdateTorrentMin(torrent, debridTorrent)
|
||||
}
|
||||
if isSymlink {
|
||||
q.processSymlink(torrent, debridTorrent, arr)
|
||||
|
||||
@@ -174,20 +174,20 @@ type Torrent struct {
|
||||
TorrentPath string `json:"-"`
|
||||
|
||||
AddedOn int64 `json:"added_on,omitempty"`
|
||||
AmountLeft int64 `json:"amount_left,omitempty"`
|
||||
AmountLeft int64 `json:"amount_left"`
|
||||
AutoTmm bool `json:"auto_tmm"`
|
||||
Availability float64 `json:"availability"`
|
||||
Availability float64 `json:"availability,omitempty"`
|
||||
Category string `json:"category,omitempty"`
|
||||
Completed int64 `json:"completed,omitempty"`
|
||||
Completed int64 `json:"completed"`
|
||||
CompletionOn int64 `json:"completion_on,omitempty"`
|
||||
ContentPath string `json:"content_path,omitempty"`
|
||||
DlLimit int64 `json:"dl_limit,omitempty"`
|
||||
Dlspeed int64 `json:"dlspeed,omitempty"`
|
||||
Downloaded int64 `json:"downloaded,omitempty"`
|
||||
DownloadedSession int64 `json:"downloaded_session,omitempty"`
|
||||
Eta int64 `json:"eta,omitempty"`
|
||||
FlPiecePrio bool `json:"f_l_piece_prio"`
|
||||
ForceStart bool `json:"force_start"`
|
||||
ContentPath string `json:"content_path"`
|
||||
DlLimit int64 `json:"dl_limit"`
|
||||
Dlspeed int64 `json:"dlspeed"`
|
||||
Downloaded int64 `json:"downloaded"`
|
||||
DownloadedSession int64 `json:"downloaded_session"`
|
||||
Eta int64 `json:"eta"`
|
||||
FlPiecePrio bool `json:"f_l_piece_prio,omitempty"`
|
||||
ForceStart bool `json:"force_start,omitempty"`
|
||||
Hash string `json:"hash"`
|
||||
LastActivity int64 `json:"last_activity,omitempty"`
|
||||
MagnetUri string `json:"magnet_uri,omitempty"`
|
||||
@@ -202,7 +202,7 @@ type Torrent struct {
|
||||
Progress float32 `json:"progress"`
|
||||
Ratio int64 `json:"ratio,omitempty"`
|
||||
RatioLimit int64 `json:"ratio_limit,omitempty"`
|
||||
SavePath string `json:"save_path,omitempty"`
|
||||
SavePath string `json:"save_path"`
|
||||
SeedingTimeLimit int64 `json:"seeding_time_limit,omitempty"`
|
||||
SeenComplete int64 `json:"seen_complete,omitempty"`
|
||||
SeqDl bool `json:"seq_dl"`
|
||||
@@ -259,6 +259,17 @@ type TorrentProperties struct {
|
||||
UpSpeedAvg int64 `json:"up_speed_avg,omitempty"`
|
||||
}
|
||||
|
||||
type TorrentFile struct {
|
||||
Index int `json:"index,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Size int64 `json:"size,omitempty"`
|
||||
Progress int64 `json:"progress,omitempty"`
|
||||
Priority int64 `json:"priority,omitempty"`
|
||||
IsSeed bool `json:"is_seed,omitempty"`
|
||||
PieceRange []int64 `json:"piece_range,omitempty"`
|
||||
Availability float64 `json:"availability,omitempty"`
|
||||
}
|
||||
|
||||
func NewAppPreferences() *AppPreferences {
|
||||
preferences := &AppPreferences{
|
||||
AddTrackers: "",
|
||||
|
||||
@@ -16,31 +16,19 @@ func (q *QBit) MarkAsFailed(t *Torrent) *Torrent {
|
||||
return t
|
||||
}
|
||||
|
||||
func (q *QBit) UpdateTorrent(t *Torrent, debridTorrent *debrid.Torrent) *Torrent {
|
||||
rcLoneMount := q.debrid.GetMountPath()
|
||||
if debridTorrent == nil && t.ID != "" {
|
||||
debridTorrent, _ = q.debrid.GetTorrent(t.ID)
|
||||
}
|
||||
func (q *QBit) UpdateTorrentMin(t *Torrent, debridTorrent *debrid.Torrent) *Torrent {
|
||||
if debridTorrent == nil {
|
||||
q.logger.Printf("Torrent with ID %s not found in %s", t.ID, q.debrid.GetName())
|
||||
return t
|
||||
}
|
||||
if debridTorrent.Status != "downloaded" {
|
||||
debridTorrent, _ = q.debrid.GetTorrent(t.ID)
|
||||
}
|
||||
|
||||
if t.TorrentPath == "" {
|
||||
t.TorrentPath = filepath.Base(debridTorrent.GetMountFolder(rcLoneMount))
|
||||
addedOn, err := time.Parse(time.RFC3339, debridTorrent.Added)
|
||||
if err != nil {
|
||||
addedOn = time.Now()
|
||||
}
|
||||
|
||||
totalSize := float64(cmp.Or(debridTorrent.Bytes, 1.0))
|
||||
totalSize := float64(debridTorrent.Bytes)
|
||||
progress := cmp.Or(debridTorrent.Progress, 100.0)
|
||||
progress = progress / 100.0
|
||||
var sizeCompleted int64
|
||||
|
||||
sizeCompleted = int64(totalSize * progress)
|
||||
savePath := filepath.Join(q.DownloadFolder, t.Category) + string(os.PathSeparator)
|
||||
torrentPath := filepath.Join(savePath, t.TorrentPath) + string(os.PathSeparator)
|
||||
sizeCompleted := int64(totalSize * progress)
|
||||
|
||||
var speed int64
|
||||
if debridTorrent.Speed != 0 {
|
||||
@@ -50,9 +38,11 @@ func (q *QBit) UpdateTorrent(t *Torrent, debridTorrent *debrid.Torrent) *Torrent
|
||||
if speed != 0 {
|
||||
eta = int64((totalSize - float64(sizeCompleted)) / float64(speed))
|
||||
}
|
||||
|
||||
t.Size = debridTorrent.Bytes
|
||||
t.ID = debridTorrent.Id
|
||||
t.Name = debridTorrent.Name
|
||||
t.AddedOn = addedOn.Unix()
|
||||
t.DebridTorrent = debridTorrent
|
||||
t.Size = int64(totalSize)
|
||||
t.Completed = sizeCompleted
|
||||
t.Downloaded = sizeCompleted
|
||||
t.DownloadedSession = sizeCompleted
|
||||
@@ -60,29 +50,58 @@ func (q *QBit) UpdateTorrent(t *Torrent, debridTorrent *debrid.Torrent) *Torrent
|
||||
t.UploadedSession = sizeCompleted
|
||||
t.AmountLeft = int64(totalSize) - sizeCompleted
|
||||
t.Progress = float32(progress)
|
||||
t.SavePath = savePath
|
||||
t.ContentPath = torrentPath
|
||||
t.Eta = eta
|
||||
t.Dlspeed = speed
|
||||
t.Upspeed = speed
|
||||
t.SavePath = filepath.Join(q.DownloadFolder, t.Category) + string(os.PathSeparator)
|
||||
t.ContentPath = filepath.Join(t.SavePath, t.Name) + string(os.PathSeparator)
|
||||
return t
|
||||
}
|
||||
|
||||
func (q *QBit) UpdateTorrent(t *Torrent, debridTorrent *debrid.Torrent) *Torrent {
|
||||
db := debridTorrent.Debrid
|
||||
rcLoneMount := db.GetMountPath()
|
||||
if debridTorrent == nil && t.ID != "" {
|
||||
debridTorrent, _ = db.GetTorrent(t.ID)
|
||||
}
|
||||
if debridTorrent == nil {
|
||||
q.logger.Printf("Torrent with ID %s not found in %s", t.ID, db.GetName())
|
||||
return t
|
||||
}
|
||||
if debridTorrent.Status != "downloaded" {
|
||||
debridTorrent, _ = db.GetTorrent(t.ID)
|
||||
}
|
||||
|
||||
if t.TorrentPath == "" {
|
||||
t.TorrentPath = filepath.Base(debridTorrent.GetMountFolder(rcLoneMount))
|
||||
}
|
||||
savePath := filepath.Join(q.DownloadFolder, t.Category) + string(os.PathSeparator)
|
||||
torrentPath := filepath.Join(savePath, t.TorrentPath) + string(os.PathSeparator)
|
||||
t = q.UpdateTorrentMin(t, debridTorrent)
|
||||
t.ContentPath = torrentPath
|
||||
|
||||
if t.IsReady() {
|
||||
t.State = "pausedUP"
|
||||
q.storage.AddOrUpdate(t)
|
||||
q.storage.Update(t)
|
||||
return t
|
||||
}
|
||||
ticker := time.NewTicker(3 * time.Second)
|
||||
|
||||
ticker := time.NewTicker(2 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if t.IsReady() {
|
||||
t.State = "pausedUP"
|
||||
q.storage.AddOrUpdate(t)
|
||||
ticker.Stop()
|
||||
q.storage.Update(t)
|
||||
return t
|
||||
} else {
|
||||
return q.UpdateTorrent(t, debridTorrent)
|
||||
}
|
||||
updatedT := q.UpdateTorrent(t, debridTorrent)
|
||||
t = updatedT
|
||||
|
||||
case <-time.After(10 * time.Minute): // Add a timeout
|
||||
return t
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,3 +142,18 @@ func (q *QBit) GetTorrentProperties(t *Torrent) *TorrentProperties {
|
||||
ShareRatio: 100,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *QBit) GetTorrentFiles(t *Torrent) []*TorrentFile {
|
||||
files := make([]*TorrentFile, 0)
|
||||
if t.DebridTorrent == nil {
|
||||
return files
|
||||
}
|
||||
for index, file := range t.DebridTorrent.Files {
|
||||
files = append(files, &TorrentFile{
|
||||
Index: index,
|
||||
Name: file.Path,
|
||||
Size: file.Size,
|
||||
})
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user