diff --git a/pkg/debrid/alldebrid/alldebrid.go b/pkg/debrid/alldebrid/alldebrid.go index 2db6d06..2e67fce 100644 --- a/pkg/debrid/alldebrid/alldebrid.go +++ b/pkg/debrid/alldebrid/alldebrid.go @@ -176,6 +176,48 @@ func flattenFiles(torrentId string, files []MagnetFile, parentPath string, index return result } +func (ad *AllDebrid) GetTorrent(torrentId string) (*types.Torrent, error) { + url := fmt.Sprintf("%s/magnet/status?id=%s", ad.Host, torrentId) + req, _ := http.NewRequest(http.MethodGet, url, nil) + resp, err := ad.client.MakeRequest(req) + if err != nil { + return nil, err + } + var res TorrentInfoResponse + err = json.Unmarshal(resp, &res) + if err != nil { + ad.logger.Info().Msgf("Error unmarshalling torrent info: %s", err) + return nil, err + } + data := res.Data.Magnets + status := getAlldebridStatus(data.StatusCode) + name := data.Filename + t := &types.Torrent{ + Id: strconv.Itoa(data.Id), + Name: name, + Status: status, + Filename: name, + OriginalFilename: name, + Files: make(map[string]types.File), + InfoHash: data.Hash, + Debrid: ad.Name, + MountPath: ad.MountPath, + Added: time.Unix(data.CompletionDate, 0).Format(time.RFC3339), + } + t.Bytes = data.Size + t.Seeders = data.Seeders + if status == "downloaded" { + t.Progress = 100 + index := -1 + files := flattenFiles(t.Id, data.Files, "", &index) + t.Files = files + } else { + t.Progress = float64(data.Downloaded) / float64(data.Size) * 100 + t.Speed = data.DownloadSpeed + } + return t, nil +} + func (ad *AllDebrid) UpdateTorrent(t *types.Torrent) error { url := fmt.Sprintf("%s/magnet/status?id=%s", ad.Host, t.Id) req, _ := http.NewRequest(http.MethodGet, url, nil) @@ -201,6 +243,7 @@ func (ad *AllDebrid) UpdateTorrent(t *types.Torrent) error { t.Debrid = ad.Name t.Bytes = data.Size t.Seeders = data.Seeders + t.Added = time.Unix(data.CompletionDate, 0).Format(time.RFC3339) if status == "downloaded" { t.Progress = 100 index := -1 @@ -361,6 +404,7 @@ func (ad *AllDebrid) GetTorrents() ([]*types.Torrent, error) { InfoHash: magnet.Hash, Debrid: ad.Name, MountPath: ad.MountPath, + Added: time.Unix(magnet.CompletionDate, 0).Format(time.RFC3339), }) } diff --git a/pkg/debrid/alldebrid/types.go b/pkg/debrid/alldebrid/types.go index 4d88af5..bcc1130 100644 --- a/pkg/debrid/alldebrid/types.go +++ b/pkg/debrid/alldebrid/types.go @@ -18,13 +18,13 @@ type magnetInfo struct { Hash string `json:"hash"` Status string `json:"status"` StatusCode int `json:"statusCode"` - UploadDate int `json:"uploadDate"` + UploadDate int64 `json:"uploadDate"` Downloaded int64 `json:"downloaded"` Uploaded int64 `json:"uploaded"` DownloadSpeed int64 `json:"downloadSpeed"` UploadSpeed int64 `json:"uploadSpeed"` Seeders int `json:"seeders"` - CompletionDate int `json:"completionDate"` + CompletionDate int64 `json:"completionDate"` Type string `json:"type"` Notified bool `json:"notified"` Version int `json:"version"` diff --git a/pkg/debrid/debrid/cache.go b/pkg/debrid/debrid/cache.go index ec2d581..8b9d205 100644 --- a/pkg/debrid/debrid/cache.go +++ b/pkg/debrid/debrid/cache.go @@ -7,6 +7,7 @@ import ( "fmt" "os" "path/filepath" + "slices" "strconv" "strings" "sync" @@ -47,6 +48,15 @@ type CachedTorrent struct { DuplicateIds []string `json:"duplicate_ids"` } +func (ct *CachedTorrent) addDuplicateId(id string) { + if ct.DuplicateIds == nil { + ct.DuplicateIds = make([]string, 0) + } + if !slices.Contains(ct.DuplicateIds, id) { + ct.DuplicateIds = append(ct.DuplicateIds, id) + } +} + type downloadLinkCache struct { Id string Link string @@ -412,12 +422,17 @@ func (c *Cache) GetTorrentFolder(torrent *types.Torrent) string { func (c *Cache) setTorrent(t *CachedTorrent) { torrentKey := c.GetTorrentFolder(t.Torrent) - if o, ok := c.torrentsNames.Load(torrentKey); ok && t.Id != o.Id { + if o, ok := c.torrentsNames.Load(torrentKey); ok && o.Id != t.Id { // If another torrent with the same name exists, merge the files, if the same file exists, // keep the one with the most recent added date - mergedFiles := mergeFiles(t, o) + // Save the most recent torrent + if o.AddedOn.After(t.AddedOn) { + t = o + } + mergedFiles := mergeFiles(t, o) // Useful for merging files across multiple torrents, while keeping the most recent t.Files = mergedFiles + } c.torrents.Store(t.Id, torrentKey) c.torrentsNames.Store(torrentKey, t) @@ -427,15 +442,17 @@ func (c *Cache) setTorrent(t *CachedTorrent) { func (c *Cache) setTorrents(torrents map[string]*CachedTorrent) { for _, t := range torrents { torrentKey := c.GetTorrentFolder(t.Torrent) - if o, ok := c.torrentsNames.Load(torrentKey); ok && t.Id != o.Id { + if o, ok := c.torrentsNames.Load(torrentKey); ok && o.Id != t.Id { // Save the most recent torrent + if o.AddedOn.After(t.AddedOn) { + t = o + } mergedFiles := mergeFiles(t, o) t.Files = mergedFiles } c.torrents.Store(t.Id, torrentKey) c.torrentsNames.Store(torrentKey, t) } - c.RefreshListings(false) c.SaveTorrents() } @@ -635,7 +652,7 @@ func (c *Cache) DeleteTorrent(id string) error { func (c *Cache) deleteTorrent(id string, removeFromDebrid bool) bool { if torrentName, ok := c.torrents.Load(id); ok { - c.torrents.Delete(id) // Delete from id cache + c.torrents.Delete(id) // Delete id from cache defer func() { c.removeFromDB(id) if removeFromDebrid { @@ -643,7 +660,8 @@ func (c *Cache) deleteTorrent(id string, removeFromDebrid bool) bool { } }() // defer delete from debrid - if t, ok := c.torrentsNames.Load(torrentName); ok && t.Id == id { + if t, ok := c.torrentsNames.Load(torrentName); ok { + newFiles := map[string]types.File{} newId := t.Id for _, file := range t.Files { diff --git a/pkg/debrid/debrid/download_link.go b/pkg/debrid/debrid/download_link.go index ab2e77a..68df5d5 100644 --- a/pkg/debrid/debrid/download_link.go +++ b/pkg/debrid/debrid/download_link.go @@ -65,7 +65,7 @@ func (c *Cache) fetchDownloadLink(torrentName, filename, fileLink string) (strin if file.Link == "" { // file link is empty, refresh the torrent to get restricted links - ct = c.refreshTorrent(ct) // Refresh the torrent from the debrid + ct = c.refreshTorrent(file.TorrentId) // Refresh the torrent from the debrid if ct == nil { return "", fmt.Errorf("failed to refresh torrent: %s", torrentName) } else { diff --git a/pkg/debrid/debrid/refresh.go b/pkg/debrid/debrid/refresh.go index e70f401..f1bbd71 100644 --- a/pkg/debrid/debrid/refresh.go +++ b/pkg/debrid/debrid/refresh.go @@ -1,10 +1,8 @@ package debrid import ( - "errors" "fmt" "github.com/sirrobot01/decypharr/internal/config" - "github.com/sirrobot01/decypharr/internal/request" "github.com/sirrobot01/decypharr/internal/utils" "github.com/sirrobot01/decypharr/pkg/debrid/types" "io" @@ -205,33 +203,20 @@ func (c *Cache) RefreshRclone() error { return nil } -func (c *Cache) refreshTorrent(t *CachedTorrent) *CachedTorrent { - _torrent := t.Torrent - err := c.client.UpdateTorrent(_torrent) +func (c *Cache) refreshTorrent(torrentId string) *CachedTorrent { + torrent, err := c.client.GetTorrent(torrentId) if err != nil { - if errors.Is(err, request.TorrentNotFoundError) { - c.logger.Trace().Msgf("Torrent %s not found. Removing from cache", _torrent.Id) - err := c.DeleteTorrent(_torrent.Id) - if err != nil { - c.logger.Error().Err(err).Msgf("Failed to delete torrent %s from cache", _torrent.Id) - return nil - } - return nil - } - c.logger.Debug().Err(err).Msgf("Failed to get torrent files for %s", t.Id) + c.logger.Error().Err(err).Msgf("Failed to get torrent %s", torrentId) return nil } - if len(t.Files) == 0 { - return nil - } - addedOn, err := time.Parse(time.RFC3339, _torrent.Added) + addedOn, err := time.Parse(time.RFC3339, torrent.Added) if err != nil { addedOn = time.Now() } ct := &CachedTorrent{ - Torrent: _torrent, + Torrent: torrent, AddedOn: addedOn, - IsComplete: len(t.Files) > 0, + IsComplete: len(torrent.Files) > 0, } c.setTorrent(ct) diff --git a/pkg/debrid/debrid/repair.go b/pkg/debrid/debrid/repair.go index 229e1c2..036e4eb 100644 --- a/pkg/debrid/debrid/repair.go +++ b/pkg/debrid/debrid/repair.go @@ -55,7 +55,7 @@ func (c *Cache) IsTorrentBroken(t *CachedTorrent, filenames []string) bool { // Check if file is missing if f.Link == "" { // refresh torrent and then break - if newT := c.refreshTorrent(t); newT != nil { + if newT := c.refreshTorrent(f.TorrentId); newT != nil { t = newT } else { c.logger.Error().Str("torrentId", t.Torrent.Id).Msg("Failed to refresh torrent") diff --git a/pkg/debrid/debrid_link/debrid_link.go b/pkg/debrid/debrid_link/debrid_link.go index ada2d01..8bc1036 100644 --- a/pkg/debrid/debrid_link/debrid_link.go +++ b/pkg/debrid/debrid_link/debrid_link.go @@ -92,6 +92,65 @@ func (dl *DebridLink) IsAvailable(hashes []string) map[string]bool { return result } +func (dl *DebridLink) GetTorrent(torrentId string) (*types.Torrent, error) { + url := fmt.Sprintf("%s/seedbox/%s", dl.Host, torrentId) + req, _ := http.NewRequest(http.MethodGet, url, nil) + resp, err := dl.client.MakeRequest(req) + if err != nil { + return nil, err + } + var res torrentInfo + err = json.Unmarshal(resp, &res) + if err != nil { + return nil, err + } + if !res.Success || res.Value == nil { + return nil, fmt.Errorf("error getting torrent") + } + data := *res.Value + + if len(data) == 0 { + return nil, fmt.Errorf("torrent not found") + } + t := data[0] + name := utils.RemoveInvalidChars(t.Name) + torrent := &types.Torrent{ + Id: t.ID, + Name: name, + Bytes: t.TotalSize, + Status: "downloaded", + Filename: name, + OriginalFilename: name, + MountPath: dl.MountPath, + Debrid: dl.Name, + Added: time.Unix(t.Created, 0).Format(time.RFC3339), + } + cfg := config.Get() + for _, f := range t.Files { + if !cfg.IsSizeAllowed(f.Size) { + continue + } + file := types.File{ + TorrentId: t.ID, + Id: f.ID, + Name: f.Name, + Size: f.Size, + Path: f.Name, + DownloadLink: &types.DownloadLink{ + Filename: f.Name, + Link: f.DownloadURL, + DownloadLink: f.DownloadURL, + Generated: time.Now(), + AccountId: "0", + }, + Link: f.DownloadURL, + } + torrent.Files[file.Name] = file + } + + return torrent, nil +} + func (dl *DebridLink) UpdateTorrent(t *types.Torrent) error { url := fmt.Sprintf("%s/seedbox/list?ids=%s", dl.Host, t.Id) req, _ := http.NewRequest(http.MethodGet, url, nil) @@ -131,6 +190,7 @@ func (dl *DebridLink) UpdateTorrent(t *types.Torrent) error { t.Seeders = data.PeersConnected t.Filename = name t.OriginalFilename = name + t.Added = time.Unix(data.Created, 0).Format(time.RFC3339) cfg := config.Get() for _, f := range data.Files { if !cfg.IsSizeAllowed(f.Size) { @@ -188,6 +248,7 @@ func (dl *DebridLink) SubmitMagnet(t *types.Torrent) (*types.Torrent, error) { t.OriginalFilename = name t.MountPath = dl.MountPath t.Debrid = dl.Name + t.Added = time.Unix(data.Created, 0).Format(time.RFC3339) for _, f := range data.Files { file := types.File{ TorrentId: t.Id, @@ -365,6 +426,7 @@ func (dl *DebridLink) getTorrents(page, perPage int) ([]*types.Torrent, error) { Files: make(map[string]types.File), Debrid: dl.Name, MountPath: dl.MountPath, + Added: time.Unix(t.Created, 0).Format(time.RFC3339), } cfg := config.Get() for _, f := range t.Files { diff --git a/pkg/debrid/debrid_link/types.go b/pkg/debrid/debrid_link/types.go index ca44b2f..934c542 100644 --- a/pkg/debrid/debrid_link/types.go +++ b/pkg/debrid/debrid_link/types.go @@ -14,7 +14,7 @@ type AvailableResponse APIResponse[map[string]map[string]struct { } `json:"files"` }] -type debridLinkTorrentInfo struct { +type _torrentInfo struct { ID string `json:"id"` Name string `json:"name"` HashString string `json:"hashString"` @@ -40,6 +40,6 @@ type debridLinkTorrentInfo struct { UploadSpeed int64 `json:"uploadSpeed"` } -type torrentInfo APIResponse[[]debridLinkTorrentInfo] +type torrentInfo APIResponse[[]_torrentInfo] -type SubmitTorrentInfo APIResponse[debridLinkTorrentInfo] +type SubmitTorrentInfo APIResponse[_torrentInfo] diff --git a/pkg/debrid/realdebrid/realdebrid.go b/pkg/debrid/realdebrid/realdebrid.go index c828f1b..43eaf27 100644 --- a/pkg/debrid/realdebrid/realdebrid.go +++ b/pkg/debrid/realdebrid/realdebrid.go @@ -258,6 +258,49 @@ func (r *RealDebrid) addMagnet(t *types.Torrent) (*types.Torrent, error) { return t, nil } +func (r *RealDebrid) GetTorrent(torrentId string) (*types.Torrent, error) { + url := fmt.Sprintf("%s/torrents/info/%s", r.Host, torrentId) + req, _ := http.NewRequest(http.MethodGet, url, nil) + resp, err := r.client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("reading response body: %w", err) + } + if resp.StatusCode != http.StatusOK { + if resp.StatusCode == http.StatusNotFound { + return nil, request.TorrentNotFoundError + } + return nil, fmt.Errorf("realdebrid API error: Status: %d || Body: %s", resp.StatusCode, string(bodyBytes)) + } + var data torrentInfo + err = json.Unmarshal(bodyBytes, &data) + if err != nil { + return nil, err + } + t := &types.Torrent{ + Id: data.ID, + Name: data.Filename, + Bytes: data.Bytes, + Folder: data.OriginalFilename, + Progress: data.Progress, + Speed: data.Speed, + Seeders: data.Seeders, + Added: data.Added, + Status: data.Status, + Filename: data.Filename, + OriginalFilename: data.OriginalFilename, + Links: data.Links, + Debrid: r.Name, + MountPath: r.MountPath, + } + t.Files = getTorrentFiles(t, data) // Get selected files + return t, nil +} + func (r *RealDebrid) UpdateTorrent(t *types.Torrent) error { url := fmt.Sprintf("%s/torrents/info/%s", r.Host, t.Id) req, _ := http.NewRequest(http.MethodGet, url, nil) diff --git a/pkg/debrid/torbox/torbox.go b/pkg/debrid/torbox/torbox.go index 2284c2a..a39a154 100644 --- a/pkg/debrid/torbox/torbox.go +++ b/pkg/debrid/torbox/torbox.go @@ -183,6 +183,74 @@ func getTorboxStatus(status string, finished bool) string { } } +func (tb *Torbox) GetTorrent(torrentId string) (*types.Torrent, error) { + url := fmt.Sprintf("%s/api/torrents/mylist/?id=%s", tb.Host, torrentId) + req, _ := http.NewRequest(http.MethodGet, url, nil) + resp, err := tb.client.MakeRequest(req) + if err != nil { + return nil, err + } + var res InfoResponse + err = json.Unmarshal(resp, &res) + if err != nil { + return nil, err + } + data := res.Data + if data == nil { + return nil, fmt.Errorf("error getting torrent") + } + t := &types.Torrent{ + Id: strconv.Itoa(data.Id), + Name: data.Name, + Bytes: data.Size, + Folder: data.Name, + Progress: data.Progress * 100, + Status: getTorboxStatus(data.DownloadState, data.DownloadFinished), + Speed: data.DownloadSpeed, + Seeders: data.Seeds, + Filename: data.Name, + OriginalFilename: data.Name, + MountPath: tb.MountPath, + Debrid: tb.Name, + Files: make(map[string]types.File), + Added: data.CreatedAt.Format(time.RFC3339), + } + cfg := config.Get() + for _, f := range data.Files { + fileName := filepath.Base(f.Name) + if utils.IsSampleFile(f.AbsolutePath) { + // Skip sample files + continue + } + if !cfg.IsAllowedFile(fileName) { + continue + } + + if !cfg.IsSizeAllowed(f.Size) { + continue + } + file := types.File{ + TorrentId: t.Id, + Id: strconv.Itoa(f.Id), + Name: fileName, + Size: f.Size, + Path: fileName, + } + t.Files[fileName] = file + } + var cleanPath string + if len(t.Files) > 0 { + cleanPath = path.Clean(data.Files[0].Name) + } else { + cleanPath = path.Clean(data.Name) + } + + t.OriginalFilename = strings.Split(cleanPath, "/")[0] + t.Debrid = tb.Name + + return t, nil +} + func (tb *Torbox) UpdateTorrent(t *types.Torrent) error { url := fmt.Sprintf("%s/api/torrents/mylist/?id=%s", tb.Host, t.Id) req, _ := http.NewRequest(http.MethodGet, url, nil) diff --git a/pkg/debrid/types/client.go b/pkg/debrid/types/client.go index 5049b03..857e1d0 100644 --- a/pkg/debrid/types/client.go +++ b/pkg/debrid/types/client.go @@ -14,6 +14,7 @@ type Client interface { GetCheckCached() bool GetDownloadUncached() bool UpdateTorrent(torrent *Torrent) error + GetTorrent(torrentId string) (*Torrent, error) GetTorrents() ([]*Torrent, error) GetName() string GetLogger() zerolog.Logger