Revert former beta chnages

This commit is contained in:
Mukhtar Akere
2025-08-30 04:10:18 +01:00
parent aff12c2e4b
commit 85cd37f29b
12 changed files with 115 additions and 137 deletions

View File

@@ -302,7 +302,7 @@ func (ad *AllDebrid) GetFileDownloadLinks(t *types.Torrent) error {
for _, file := range t.Files { for _, file := range t.Files {
go func(file types.File) { go func(file types.File) {
defer wg.Done() defer wg.Done()
link, err := ad.GetDownloadLink(t, &file) link, _, err := ad.GetDownloadLink(t, &file)
if err != nil { if err != nil {
errCh <- err errCh <- err
return return
@@ -337,7 +337,7 @@ func (ad *AllDebrid) GetFileDownloadLinks(t *types.Torrent) error {
links[link.Link] = link links[link.Link] = link
} }
// Update the files with download links // Update the files with download links
ad.accounts.SetDownloadLinks(links) ad.accounts.SetDownloadLinks(nil, links)
// Check for errors // Check for errors
for err := range errCh { for err := range errCh {
@@ -350,7 +350,7 @@ func (ad *AllDebrid) GetFileDownloadLinks(t *types.Torrent) error {
return nil return nil
} }
func (ad *AllDebrid) GetDownloadLink(t *types.Torrent, file *types.File) (*types.DownloadLink, error) { func (ad *AllDebrid) GetDownloadLink(t *types.Torrent, file *types.File) (*types.DownloadLink, *types.Account, error) {
url := fmt.Sprintf("%s/link/unlock", ad.Host) url := fmt.Sprintf("%s/link/unlock", ad.Host)
query := gourl.Values{} query := gourl.Values{}
query.Add("link", file.Link) query.Add("link", file.Link)
@@ -358,22 +358,21 @@ func (ad *AllDebrid) GetDownloadLink(t *types.Torrent, file *types.File) (*types
req, _ := http.NewRequest(http.MethodGet, url, nil) req, _ := http.NewRequest(http.MethodGet, url, nil)
resp, err := ad.client.MakeRequest(req) resp, err := ad.client.MakeRequest(req)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
var data DownloadLink var data DownloadLink
if err = json.Unmarshal(resp, &data); err != nil { if err = json.Unmarshal(resp, &data); err != nil {
return nil, err return nil, nil, err
} }
if data.Error != nil { if data.Error != nil {
return nil, fmt.Errorf("error getting download link: %s", data.Error.Message) return nil, nil, fmt.Errorf("error getting download link: %s", data.Error.Message)
} }
link := data.Data.Link link := data.Data.Link
if link == "" { if link == "" {
return nil, fmt.Errorf("download link is empty") return nil, nil, fmt.Errorf("download link is empty")
} }
now := time.Now() now := time.Now()
account := ad.accounts.Current()
return &types.DownloadLink{ return &types.DownloadLink{
Link: file.Link, Link: file.Link,
DownloadLink: link, DownloadLink: link,
@@ -382,9 +381,7 @@ func (ad *AllDebrid) GetDownloadLink(t *types.Torrent, file *types.File) (*types
Filename: file.Name, Filename: file.Name,
Generated: now, Generated: now,
ExpiresAt: now.Add(ad.autoExpiresLinksAfter), ExpiresAt: now.Add(ad.autoExpiresLinksAfter),
Token: account.Token, }, nil, nil
MaskedToken: account.MaskedToken,
}, nil
} }
func (ad *AllDebrid) GetTorrents() ([]*types.Torrent, error) { func (ad *AllDebrid) GetTorrents() ([]*types.Torrent, error) {

View File

@@ -247,7 +247,7 @@ func (dl *DebridLink) UpdateTorrent(t *types.Torrent) error {
t.Files[f.Name] = file t.Files[f.Name] = file
} }
dl.accounts.SetDownloadLinks(links) dl.accounts.SetDownloadLinks(nil, links)
return nil return nil
} }
@@ -287,7 +287,6 @@ func (dl *DebridLink) SubmitMagnet(t *types.Torrent) (*types.Torrent, error) {
links := make(map[string]*types.DownloadLink) links := make(map[string]*types.DownloadLink)
now := time.Now() now := time.Now()
account := dl.accounts.Current()
for _, f := range data.Files { for _, f := range data.Files {
file := types.File{ file := types.File{
TorrentId: t.Id, TorrentId: t.Id,
@@ -304,14 +303,12 @@ func (dl *DebridLink) SubmitMagnet(t *types.Torrent) (*types.Torrent, error) {
DownloadLink: f.DownloadURL, DownloadLink: f.DownloadURL,
Generated: now, Generated: now,
ExpiresAt: now.Add(dl.autoExpiresLinksAfter), ExpiresAt: now.Add(dl.autoExpiresLinksAfter),
Token: account.Token,
MaskedToken: account.MaskedToken,
} }
links[file.Link] = link links[file.Link] = link
file.DownloadLink = link file.DownloadLink = link
t.Files[f.Name] = file t.Files[f.Name] = file
} }
dl.accounts.SetDownloadLinks(links) dl.accounts.SetDownloadLinks(nil, links)
return t, nil return t, nil
} }
@@ -359,7 +356,7 @@ func (dl *DebridLink) RefreshDownloadLinks() error {
return nil return nil
} }
func (dl *DebridLink) GetDownloadLink(t *types.Torrent, file *types.File) (*types.DownloadLink, error) { func (dl *DebridLink) GetDownloadLink(t *types.Torrent, file *types.File) (*types.DownloadLink, *types.Account, error) {
return dl.accounts.GetDownloadLink(file.Link) return dl.accounts.GetDownloadLink(file.Link)
} }
@@ -410,7 +407,6 @@ func (dl *DebridLink) getTorrents(page, perPage int) ([]*types.Torrent, error) {
if len(data) == 0 { if len(data) == 0 {
return torrents, nil return torrents, nil
} }
account := dl.accounts.Current()
for _, t := range data { for _, t := range data {
if t.Status != 100 { if t.Status != 100 {
continue continue
@@ -448,8 +444,6 @@ func (dl *DebridLink) getTorrents(page, perPage int) ([]*types.Torrent, error) {
DownloadLink: f.DownloadURL, DownloadLink: f.DownloadURL,
Generated: now, Generated: now,
ExpiresAt: now.Add(dl.autoExpiresLinksAfter), ExpiresAt: now.Add(dl.autoExpiresLinksAfter),
Token: account.Token,
MaskedToken: account.MaskedToken,
} }
links[file.Link] = link links[file.Link] = link
file.DownloadLink = link file.DownloadLink = link
@@ -457,7 +451,7 @@ func (dl *DebridLink) getTorrents(page, perPage int) ([]*types.Torrent, error) {
} }
torrents = append(torrents, torrent) torrents = append(torrents, torrent)
} }
dl.accounts.SetDownloadLinks(links) dl.accounts.SetDownloadLinks(nil, links)
return torrents, nil return torrents, nil
} }

View File

@@ -195,7 +195,7 @@ func (r *RealDebrid) handleRarArchive(t *types.Torrent, data torrentInfo, select
r.logger.Info().Msgf("RAR file detected, unpacking: %s", t.Name) r.logger.Info().Msgf("RAR file detected, unpacking: %s", t.Name)
linkFile := &types.File{TorrentId: t.Id, Link: data.Links[0]} linkFile := &types.File{TorrentId: t.Id, Link: data.Links[0]}
downloadLinkObj, err := r.GetDownloadLink(t, linkFile) downloadLinkObj, account, err := r.GetDownloadLink(t, linkFile)
if err != nil { if err != nil {
r.logger.Debug().Err(err).Msgf("Error getting download link for RAR file: %s. Falling back to single file representation.", t.Name) r.logger.Debug().Err(err).Msgf("Error getting download link for RAR file: %s. Falling back to single file representation.", t.Name)
@@ -244,7 +244,7 @@ func (r *RealDebrid) handleRarArchive(t *types.Torrent, data torrentInfo, select
return r.handleRarFallback(t, data) return r.handleRarFallback(t, data)
} }
r.logger.Info().Msgf("Unpacked RAR archive for torrent: %s with %d files", t.Name, len(files)) r.logger.Info().Msgf("Unpacked RAR archive for torrent: %s with %d files", t.Name, len(files))
r.accounts.SetDownloadLink(downloadLinkObj) r.accounts.SetDownloadLink(account, downloadLinkObj)
return files, nil return files, nil
} }
@@ -591,7 +591,7 @@ func (r *RealDebrid) GetFileDownloadLinks(t *types.Torrent) error {
go func(file types.File) { go func(file types.File) {
defer wg.Done() defer wg.Done()
link, err := r.GetDownloadLink(t, &file) link, account, err := r.GetDownloadLink(t, &file)
if err != nil { if err != nil {
mu.Lock() mu.Lock()
if firstErr == nil { if firstErr == nil {
@@ -610,7 +610,7 @@ func (r *RealDebrid) GetFileDownloadLinks(t *types.Torrent) error {
} }
file.DownloadLink = link file.DownloadLink = link
r.accounts.SetDownloadLink(link) r.accounts.SetDownloadLink(account, link)
mu.Lock() mu.Lock()
files[file.Name] = file files[file.Name] = file
@@ -703,8 +703,6 @@ func (r *RealDebrid) getDownloadLink(account *types.Account, file *types.File) (
} }
now := time.Now() now := time.Now()
return &types.DownloadLink{ return &types.DownloadLink{
MaskedToken: account.MaskedToken,
Token: account.Token,
Filename: data.Filename, Filename: data.Filename,
Size: data.Filesize, Size: data.Filesize,
Link: data.Link, Link: data.Link,
@@ -715,13 +713,14 @@ func (r *RealDebrid) getDownloadLink(account *types.Account, file *types.File) (
} }
func (r *RealDebrid) GetDownloadLink(t *types.Torrent, file *types.File) (*types.DownloadLink, error) { func (r *RealDebrid) GetDownloadLink(t *types.Torrent, file *types.File) (*types.DownloadLink, *types.Account, error) {
accounts := r.accounts.Active() accounts := r.accounts.Active()
for _, account := range accounts { for _, account := range accounts {
downloadLink, err := r.getDownloadLink(account, file) downloadLink, err := r.getDownloadLink(account, file)
if err == nil { if err == nil {
return downloadLink, nil return downloadLink, account, nil
} }
retries := 0 retries := 0
@@ -730,16 +729,16 @@ func (r *RealDebrid) GetDownloadLink(t *types.Torrent, file *types.File) (*types
retries = 5 retries = 5
} else { } else {
// If the error is not traffic exceeded, return the error // If the error is not traffic exceeded, return the error
return nil, err return nil, account, err
} }
backOff := 1 * time.Second backOff := 1 * time.Second
for retries > 0 { for retries > 0 {
downloadLink, err = r.getDownloadLink(account, file) downloadLink, err = r.getDownloadLink(account, file)
if err == nil { if err == nil {
return downloadLink, nil return downloadLink, account, nil
} }
if !errors.Is(err, utils.TrafficExceededError) { if !errors.Is(err, utils.TrafficExceededError) {
return nil, err return nil, account, err
} }
// Add a delay before retrying // Add a delay before retrying
time.Sleep(backOff) time.Sleep(backOff)
@@ -747,7 +746,7 @@ func (r *RealDebrid) GetDownloadLink(t *types.Torrent, file *types.File) (*types
retries-- retries--
} }
} }
return nil, fmt.Errorf("realdebrid API error: download link not found") return nil, nil, fmt.Errorf("realdebrid API error: used all active accounts")
} }
func (r *RealDebrid) getTorrents(offset int, limit int) (int, []*types.Torrent, error) { func (r *RealDebrid) getTorrents(offset int, limit int) (int, []*types.Torrent, error) {
@@ -873,7 +872,7 @@ func (r *RealDebrid) RefreshDownloadLinks() error {
offset += len(dl) offset += len(dl)
} }
r.accounts.SetDownloadLinks(links) r.accounts.SetDownloadLinks(account, links)
} }
return nil return nil
} }
@@ -983,7 +982,7 @@ func (r *RealDebrid) SyncAccounts() error {
} }
for _, account := range r.accounts.All() { for _, account := range r.accounts.All() {
if err := r.syncAccount(account); err != nil { if err := r.syncAccount(account); err != nil {
r.logger.Error().Err(err).Str("account", account.MaskedToken).Msgf("Error syncing account %s", account.Username) r.logger.Error().Err(err).Msgf("Error syncing account %s", account.Username)
continue // Skip this account and continue with the next continue // Skip this account and continue with the next
} }
} }

View File

@@ -412,7 +412,7 @@ func (tb *Torbox) GetFileDownloadLinks(t *types.Torrent) error {
for _, file := range t.Files { for _, file := range t.Files {
go func() { go func() {
defer wg.Done() defer wg.Done()
link, err := tb.GetDownloadLink(t, &file) link, _, err := tb.GetDownloadLink(t, &file)
if err != nil { if err != nil {
errCh <- err errCh <- err
return return
@@ -440,7 +440,7 @@ func (tb *Torbox) GetFileDownloadLinks(t *types.Torrent) error {
// Collect download links // Collect download links
for link := range linkCh { for link := range linkCh {
if link != nil { if link != nil {
tb.accounts.SetDownloadLink(link) tb.accounts.SetDownloadLink(nil, link)
} }
} }
@@ -455,7 +455,7 @@ func (tb *Torbox) GetFileDownloadLinks(t *types.Torrent) error {
return nil return nil
} }
func (tb *Torbox) GetDownloadLink(t *types.Torrent, file *types.File) (*types.DownloadLink, error) { func (tb *Torbox) GetDownloadLink(t *types.Torrent, file *types.File) (*types.DownloadLink, *types.Account, error) {
url := fmt.Sprintf("%s/api/torrents/requestdl/", tb.Host) url := fmt.Sprintf("%s/api/torrents/requestdl/", tb.Host)
query := gourl.Values{} query := gourl.Values{}
query.Add("torrent_id", t.Id) query.Add("torrent_id", t.Id)
@@ -471,7 +471,7 @@ func (tb *Torbox) GetDownloadLink(t *types.Torrent, file *types.File) (*types.Do
Str("torrent_id", t.Id). Str("torrent_id", t.Id).
Str("file_id", file.Id). Str("file_id", file.Id).
Msg("Failed to make request to Torbox API") Msg("Failed to make request to Torbox API")
return nil, err return nil, nil, err
} }
var data DownloadLinksResponse var data DownloadLinksResponse
@@ -481,7 +481,7 @@ func (tb *Torbox) GetDownloadLink(t *types.Torrent, file *types.File) (*types.Do
Str("torrent_id", t.Id). Str("torrent_id", t.Id).
Str("file_id", file.Id). Str("file_id", file.Id).
Msg("Failed to unmarshal Torbox API response") Msg("Failed to unmarshal Torbox API response")
return nil, err return nil, nil, err
} }
if data.Data == nil { if data.Data == nil {
@@ -492,7 +492,7 @@ func (tb *Torbox) GetDownloadLink(t *types.Torrent, file *types.File) (*types.Do
Interface("error", data.Error). Interface("error", data.Error).
Str("detail", data.Detail). Str("detail", data.Detail).
Msg("Torbox API returned no data") Msg("Torbox API returned no data")
return nil, fmt.Errorf("error getting download links") return nil, nil, fmt.Errorf("error getting download links")
} }
link := *data.Data link := *data.Data
@@ -501,7 +501,7 @@ func (tb *Torbox) GetDownloadLink(t *types.Torrent, file *types.File) (*types.Do
Str("torrent_id", t.Id). Str("torrent_id", t.Id).
Str("file_id", file.Id). Str("file_id", file.Id).
Msg("Torbox API returned empty download link") Msg("Torbox API returned empty download link")
return nil, fmt.Errorf("error getting download links") return nil, nil, fmt.Errorf("error getting download links")
} }
now := time.Now() now := time.Now()
@@ -511,11 +511,9 @@ func (tb *Torbox) GetDownloadLink(t *types.Torrent, file *types.File) (*types.Do
Id: file.Id, Id: file.Id,
Generated: now, Generated: now,
ExpiresAt: now.Add(tb.autoExpiresLinksAfter), ExpiresAt: now.Add(tb.autoExpiresLinksAfter),
Token: tb.APIKey,
MaskedToken: utils.Mask(tb.APIKey),
} }
return downloadLink, nil return downloadLink, nil, nil
} }
func (tb *Torbox) GetDownloadingStatus() []string { func (tb *Torbox) GetDownloadingStatus() []string {

View File

@@ -30,21 +30,22 @@ func (r *downloadLinkRequest) Wait() (string, error) {
return r.result, r.err return r.result, r.err
} }
func (c *Cache) GetDownloadLink(torrentName, filename, fileLink string) (*types.DownloadLink, error) { func (c *Cache) GetDownloadLink(torrentName, filename, fileLink string) (string, error) {
// Check link cache // Check link cache
if dl, err := c.checkDownloadLink(fileLink); dl != nil && err == nil { if dl, err := c.checkDownloadLink(fileLink); dl != "" && err == nil {
return dl, nil return dl, nil
} }
dl, err := c.fetchDownloadLink(torrentName, filename, fileLink) dl, err := c.fetchDownloadLink(torrentName, filename, fileLink)
if err != nil { if err != nil {
return dl, err return "", err
} }
if dl == nil || dl.DownloadLink == "" { if dl == nil || dl.DownloadLink == "" {
return nil, fmt.Errorf("download link is empty for %s in torrent %s", filename, torrentName) err = fmt.Errorf("download link is empty for %s in torrent %s", filename, torrentName)
return "", err
} }
return dl, nil return dl.DownloadLink, err
} }
func (c *Cache) fetchDownloadLink(torrentName, filename, fileLink string) (*types.DownloadLink, error) { func (c *Cache) fetchDownloadLink(torrentName, filename, fileLink string) (*types.DownloadLink, error) {
@@ -85,10 +86,11 @@ func (c *Cache) fetchDownloadLink(torrentName, filename, fileLink string) (*type
} }
c.logger.Trace().Msgf("Getting download link for %s(%s)", filename, file.Link) c.logger.Trace().Msgf("Getting download link for %s(%s)", filename, file.Link)
downloadLink, err := c.client.GetDownloadLink(ct.Torrent, &file) downloadLink, account, err := c.client.GetDownloadLink(ct.Torrent, &file)
if err != nil { if err != nil {
if errors.Is(err, utils.HosterUnavailableError) { if errors.Is(err, utils.HosterUnavailableError) {
c.logger.Trace(). c.logger.Trace().
Str("account", account.Username).
Str("filename", filename). Str("filename", filename).
Str("torrent_id", ct.Id). Str("torrent_id", ct.Id).
Msg("Hoster unavailable, attempting to reinsert torrent") Msg("Hoster unavailable, attempting to reinsert torrent")
@@ -103,7 +105,7 @@ func (c *Cache) fetchDownloadLink(torrentName, filename, fileLink string) (*type
return nil, fmt.Errorf("file %s not found in reinserted torrent %s", filename, torrentName) return nil, fmt.Errorf("file %s not found in reinserted torrent %s", filename, torrentName)
} }
// Retry getting the download link // Retry getting the download link
downloadLink, err = c.client.GetDownloadLink(ct.Torrent, &file) downloadLink, account, err = c.client.GetDownloadLink(ct.Torrent, &file)
if err != nil { if err != nil {
return nil, fmt.Errorf("retry failed to get download link: %w", err) return nil, fmt.Errorf("retry failed to get download link: %w", err)
} }
@@ -123,7 +125,7 @@ func (c *Cache) fetchDownloadLink(torrentName, filename, fileLink string) (*type
} }
// Set link to cache // Set link to cache
go c.client.Accounts().SetDownloadLink(downloadLink) go c.client.Accounts().SetDownloadLink(account, downloadLink)
return downloadLink, nil return downloadLink, nil
} }
@@ -134,30 +136,25 @@ func (c *Cache) GetFileDownloadLinks(t CachedTorrent) {
} }
} }
func (c *Cache) checkDownloadLink(link string) (*types.DownloadLink, error) { func (c *Cache) checkDownloadLink(link string) (string, error) {
dl, err := c.client.Accounts().GetDownloadLink(link) dl, _, err := c.client.Accounts().GetDownloadLink(link)
if err != nil { if err != nil {
return nil, err return "", err
} }
if !c.downloadLinkIsInvalid(dl.DownloadLink) { if !c.downloadLinkIsInvalid(dl.DownloadLink) {
return dl, nil return dl.DownloadLink, nil
} }
return nil, fmt.Errorf("download link not found for %s", link) return "", fmt.Errorf("download link not found for %s", link)
} }
func (c *Cache) MarkDownloadLinkAsInvalid(link string, downloadLink *types.DownloadLink, reason string) { func (c *Cache) MarkDownloadLinkAsInvalid(link, downloadLink, reason string) {
c.invalidDownloadLinks.Store(downloadLink.DownloadLink, reason) c.invalidDownloadLinks.Store(downloadLink, reason)
// Remove the download api key from active // Remove the download api key from active
if reason == "bandwidth_exceeded" { if reason == "bandwidth_exceeded" {
// Disable the account // Disable the account
account, err := c.client.Accounts().GetAccountFromLink(link) account, err := c.client.Accounts().GetAccountFromLink(link)
if err != nil { if err != nil {
maskedToken := ""
if account != nil {
maskedToken = account.MaskedToken
}
c.logger.Warn().Err(err).Str("account", maskedToken).Str("link", link).Msg("Failed to mark download link as invalid")
return return
} }
c.client.Accounts().Disable(account) c.client.Accounts().Disable(account)

View File

@@ -1,9 +1,7 @@
package types package types
import ( import (
"fmt"
"github.com/sirrobot01/decypharr/internal/config" "github.com/sirrobot01/decypharr/internal/config"
"github.com/sirrobot01/decypharr/internal/utils"
"slices" "slices"
"sync" "sync"
"sync/atomic" "sync/atomic"
@@ -39,7 +37,6 @@ type Account struct {
Disabled bool Disabled bool
InUse bool InUse bool
Token string `json:"token"` Token string `json:"token"`
MaskedToken string `json:"masked_token"`
links map[string]*DownloadLink links map[string]*DownloadLink
mu sync.RWMutex mu sync.RWMutex
TrafficUsed int64 `json:"traffic_used"` // Traffic used in bytes TrafficUsed int64 `json:"traffic_used"` // Traffic used in bytes
@@ -98,12 +95,13 @@ func (a *Accounts) Current() *Account {
return current return current
} }
current = activeAccounts[0] current = activeAccounts[0]
return a.setCurrent(current) a.setCurrent(current)
return current
} }
func (a *Accounts) setCurrent(account *Account) *Account { func (a *Accounts) setCurrent(account *Account) {
if account == nil { if account == nil {
return nil return
} }
// Set every account InUse to false // Set every account InUse to false
a.accounts.Range(func(key, value interface{}) bool { a.accounts.Range(func(key, value interface{}) bool {
@@ -116,24 +114,25 @@ func (a *Accounts) setCurrent(account *Account) *Account {
}) })
account.InUse = true account.InUse = true
a.current.Store(account) a.current.Store(account)
return account
} }
func (a *Accounts) Disable(account *Account) { func (a *Accounts) Disable(account *Account) {
account.Disabled = true account.Disabled = true
account.InUse = false
a.accounts.Store(account.Token, account) a.accounts.Store(account.Token, account)
current := a.getCurrent() current := a.getCurrent()
if current.Equals(account) { if current.Equals(account) {
var newCurrent *Account var newCurrent *Account
activeAccounts := a.Active()
if len(activeAccounts) > 0 { a.accounts.Range(func(key, value interface{}) bool {
newCurrent = activeAccounts[0] acc, ok := value.(*Account)
} else { if ok && !acc.Disabled {
newCurrent = a.All()[0] newCurrent = acc
} return false // Break the loop
}
return true // Continue the loop
})
a.setCurrent(newCurrent) a.setCurrent(newCurrent)
} }
} }
@@ -156,20 +155,19 @@ func (a *Accounts) Reset() {
a.setCurrent(current) a.setCurrent(current)
} }
func (a *Accounts) GetDownloadLink(fileLink string) (*DownloadLink, error) { func (a *Accounts) GetDownloadLink(fileLink string) (*DownloadLink, *Account, error) {
current := a.Current() current := a.Current()
if current == nil { if current == nil {
return nil, NoActiveAccountsError return nil, nil, NoActiveAccountsError
} }
dl, ok := current.GetDownloadLink(fileLink) dl, ok := current.getLink(fileLink)
if !ok { if !ok {
return nil, NoDownloadLinkError return nil, current, NoDownloadLinkError
} }
if !dl.Valid() { if err := dl.Valid(); err != nil {
return nil, fmt.Errorf("invalid download link: %s", dl.Link) return nil, current, err
} }
dl.Token = current.Token return dl, current, nil
return dl, nil
} }
func (a *Accounts) GetAccountFromLink(fileLink string) (*Account, error) { func (a *Accounts) GetAccountFromLink(fileLink string) (*Account, error) {
@@ -177,7 +175,7 @@ func (a *Accounts) GetAccountFromLink(fileLink string) (*Account, error) {
if currentAccount == nil { if currentAccount == nil {
return nil, NoActiveAccountsError return nil, NoActiveAccountsError
} }
dl, ok := currentAccount.GetDownloadLink(fileLink) dl, ok := currentAccount.getLink(fileLink)
if !ok { if !ok {
return nil, NoDownloadLinkError return nil, NoDownloadLinkError
} }
@@ -188,22 +186,13 @@ func (a *Accounts) GetAccountFromLink(fileLink string) (*Account, error) {
} }
// SetDownloadLink sets the download link for the current account // SetDownloadLink sets the download link for the current account
func (a *Accounts) SetDownloadLink(dl *DownloadLink) { func (a *Accounts) SetDownloadLink(account *Account, dl *DownloadLink) {
if dl == nil { if dl == nil {
return return
} }
var account *Account
// Get account from link token
if dl.Token != "" {
if acc, ok := a.accounts.Load(dl.Token); ok {
account = acc.(*Account)
}
}
if account == nil { if account == nil {
account = a.getCurrent() account = a.getCurrent()
} }
account.setLink(dl.Link, dl) account.setLink(dl.Link, dl)
} }
@@ -221,11 +210,12 @@ func (a *Accounts) GetLinksCount() int {
return a.Current().LinksCount() return a.Current().LinksCount()
} }
func (a *Accounts) SetDownloadLinks(links map[string]*DownloadLink) { func (a *Accounts) SetDownloadLinks(account *Account, links map[string]*DownloadLink) {
if a.Current() == nil { if account == nil {
return account = a.Current()
} }
a.Current().setLinks(links) account.setLinks(links)
a.accounts.Store(account.Token, account)
} }
func (a *Accounts) Update(account *Account) { func (a *Accounts) Update(account *Account) {
@@ -237,11 +227,10 @@ func (a *Accounts) Update(account *Account) {
func newAccount(debridName, token string, index int) *Account { func newAccount(debridName, token string, index int) *Account {
return &Account{ return &Account{
Debrid: debridName, Debrid: debridName,
Token: token, Token: token,
MaskedToken: utils.Mask(token), Order: index,
Order: index, links: make(map[string]*DownloadLink),
links: make(map[string]*DownloadLink),
} }
} }
@@ -252,7 +241,7 @@ func (a *Account) Equals(other *Account) bool {
return a.Token == other.Token && a.Debrid == other.Debrid return a.Token == other.Token && a.Debrid == other.Debrid
} }
func (a *Account) GetDownloadLink(fileLink string) (*DownloadLink, bool) { func (a *Account) getLink(fileLink string) (*DownloadLink, bool) {
a.mu.RLock() a.mu.RLock()
defer a.mu.RUnlock() defer a.mu.RUnlock()
dl, ok := a.links[a.sliceFileLink(fileLink)] dl, ok := a.links[a.sliceFileLink(fileLink)]
@@ -284,7 +273,7 @@ func (a *Account) setLinks(links map[string]*DownloadLink) {
a.mu.Lock() a.mu.Lock()
defer a.mu.Unlock() defer a.mu.Unlock()
for _, dl := range links { for _, dl := range links {
if !dl.Valid() { if err := dl.Valid(); err != nil {
continue continue
} }
a.links[a.sliceFileLink(dl.Link)] = dl a.links[a.sliceFileLink(dl.Link)] = dl

View File

@@ -8,7 +8,7 @@ type Client interface {
SubmitMagnet(tr *Torrent) (*Torrent, error) SubmitMagnet(tr *Torrent) (*Torrent, error)
CheckStatus(tr *Torrent) (*Torrent, error) CheckStatus(tr *Torrent) (*Torrent, error)
GetFileDownloadLinks(tr *Torrent) error GetFileDownloadLinks(tr *Torrent) error
GetDownloadLink(tr *Torrent, file *File) (*DownloadLink, error) GetDownloadLink(tr *Torrent, file *File) (*DownloadLink, *Account, error)
DeleteTorrent(torrentId string) error DeleteTorrent(torrentId string) error
IsAvailable(infohashes []string) map[string]bool IsAvailable(infohashes []string) map[string]bool
GetDownloadUncached() bool GetDownloadUncached() bool

View File

@@ -176,19 +176,17 @@ type DownloadLink struct {
Generated time.Time `json:"generated"` Generated time.Time `json:"generated"`
Size int64 `json:"size"` Size int64 `json:"size"`
Id string `json:"id"` Id string `json:"id"`
ExpiresAt time.Time `json:"expires_at"` ExpiresAt time.Time
MaskedToken string `json:"masked_token"`
Token string `json:"token"` // The token or api key used to generate this link
} }
func (dl *DownloadLink) Valid() bool { func (dl *DownloadLink) Valid() error {
if dl.DownloadLink == "" { if dl.DownloadLink == "" {
return false return EmptyDownloadLinkError
} }
if dl.ExpiresAt.IsZero() || dl.ExpiresAt.Before(time.Now()) { if dl.ExpiresAt.IsZero() || dl.ExpiresAt.Before(time.Now()) {
return false return DownloadLinkExpiredError
} }
return true return nil
} }
func (dl *DownloadLink) String() string { func (dl *DownloadLink) String() string {

View File

@@ -123,11 +123,21 @@ func (s *Server) handleStats(w http.ResponseWriter, r *http.Request) {
accounts := client.Accounts().All() accounts := client.Accounts().All()
accountDetails := make([]map[string]any, 0) accountDetails := make([]map[string]any, 0)
for _, account := range accounts { for _, account := range accounts {
// Mask token - show first 8 characters and last 4 characters
maskedToken := ""
if len(account.Token) > 12 {
maskedToken = account.Token[:8] + "****" + account.Token[len(account.Token)-4:]
} else if len(account.Token) > 8 {
maskedToken = account.Token[:4] + "****" + account.Token[len(account.Token)-2:]
} else {
maskedToken = "****"
}
accountDetail := map[string]any{ accountDetail := map[string]any{
"order": account.Order, "order": account.Order,
"disabled": account.Disabled, "disabled": account.Disabled,
"in_use": account.InUse, "in_use": account.InUse,
"masked_token": account.MaskedToken, "token_masked": maskedToken,
"username": account.Username, "username": account.Username,
"traffic_used": account.TrafficUsed, "traffic_used": account.TrafficUsed,
"links_count": account.LinksCount(), "links_count": account.LinksCount(),

View File

@@ -384,13 +384,12 @@
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<h5 class="font-medium text-sm">Account #${account.order + 1}</h5> <h5 class="font-medium text-sm">Account #${account.order + 1}</h5>
${statusBadge} ${statusBadge}
${inUseBadge}
</div> </div>
<p class="text-xs text-base-content/70 mt-1">${account.username || 'No username'}</p> <p class="text-xs text-base-content/70 mt-1">${account.username || 'No username'}</p>
</div> </div>
<div class="text-right"> <div class="text-right">
<div class="text-xs font-mono text-base-content/80"> <div class="text-xs font-mono text-base-content/80">
Token: ${account.masked_token || '****'} Token: ${account.token_masked || '****'}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -2,7 +2,6 @@ package webdav
import ( import (
"fmt" "fmt"
"github.com/sirrobot01/decypharr/pkg/debrid/types"
"io" "io"
"net/http" "net/http"
"os" "os"
@@ -30,7 +29,7 @@ type File struct {
name string name string
torrentName string torrentName string
link string link string
downloadLink *types.DownloadLink downloadLink string
size int64 size int64
isDir bool isDir bool
fileId string fileId string
@@ -56,26 +55,26 @@ func (f *File) Close() error {
// This is just to satisfy the os.File interface // This is just to satisfy the os.File interface
f.content = nil f.content = nil
f.children = nil f.children = nil
f.downloadLink = nil f.downloadLink = ""
f.readOffset = 0 f.readOffset = 0
return nil return nil
} }
func (f *File) getDownloadLink() (*types.DownloadLink, error) { func (f *File) getDownloadLink() (string, error) {
// Check if we already have a final URL cached // Check if we already have a final URL cached
if f.downloadLink != nil && isValidURL(f.downloadLink.DownloadLink) { if f.downloadLink != "" && isValidURL(f.downloadLink) {
return f.downloadLink, nil return f.downloadLink, nil
} }
downloadLink, err := f.cache.GetDownloadLink(f.torrentName, f.name, f.link) downloadLink, err := f.cache.GetDownloadLink(f.torrentName, f.name, f.link)
if err != nil { if err != nil {
return nil, err return "", err
} }
if downloadLink != nil && isValidURL(downloadLink.DownloadLink) { if downloadLink != "" && isValidURL(downloadLink) {
f.downloadLink = downloadLink f.downloadLink = downloadLink
return downloadLink, nil return downloadLink, nil
} }
return nil, os.ErrNotExist return "", os.ErrNotExist
} }
func (f *File) getDownloadByteRange() (*[2]int64, error) { func (f *File) getDownloadByteRange() (*[2]int64, error) {
@@ -139,12 +138,12 @@ func (f *File) streamWithRetry(w http.ResponseWriter, r *http.Request, retryCoun
return &streamError{Err: err, StatusCode: http.StatusPreconditionFailed} return &streamError{Err: err, StatusCode: http.StatusPreconditionFailed}
} }
if downloadLink == nil { if downloadLink == "" {
return &streamError{Err: fmt.Errorf("empty download link"), StatusCode: http.StatusNotFound} return &streamError{Err: fmt.Errorf("empty download link"), StatusCode: http.StatusNotFound}
} }
// Create upstream request with streaming optimizations // Create upstream request with streaming optimizations
upstreamReq, err := http.NewRequest("GET", downloadLink.DownloadLink, nil) upstreamReq, err := http.NewRequest("GET", downloadLink, nil)
if err != nil { if err != nil {
return &streamError{Err: err, StatusCode: http.StatusInternalServerError} return &streamError{Err: err, StatusCode: http.StatusInternalServerError}
} }
@@ -169,7 +168,6 @@ func (f *File) streamWithRetry(w http.ResponseWriter, r *http.Request, retryCoun
// Retry with new download link // Retry with new download link
_log.Debug(). _log.Debug().
Int("retry_count", retryCount+1). Int("retry_count", retryCount+1).
Str("account", downloadLink.MaskedToken).
Str("file", f.name). Str("file", f.name).
Msg("Retrying stream request") Msg("Retrying stream request")
return f.streamWithRetry(w, r, retryCount+1) return f.streamWithRetry(w, r, retryCount+1)
@@ -281,7 +279,6 @@ func (f *File) handleUpstream(resp *http.Response, retryCount, maxRetries int) (
_log.Debug(). _log.Debug().
Str("file", f.name). Str("file", f.name).
Int("retry_count", retryCount). Int("retry_count", retryCount).
Str("account", f.downloadLink.MaskedToken).
Msg("Bandwidth exceeded. Marking link as invalid") Msg("Bandwidth exceeded. Marking link as invalid")
f.cache.MarkDownloadLinkAsInvalid(f.link, f.downloadLink, "bandwidth_exceeded") f.cache.MarkDownloadLinkAsInvalid(f.link, f.downloadLink, "bandwidth_exceeded")
@@ -292,7 +289,7 @@ func (f *File) handleUpstream(resp *http.Response, retryCount, maxRetries int) (
} }
return false, &streamError{ return false, &streamError{
Err: fmt.Errorf("bandwidth exceeded for %s after %d retries", f.downloadLink.MaskedToken, retryCount), Err: fmt.Errorf("bandwidth exceeded after %d retries", retryCount),
StatusCode: http.StatusServiceUnavailable, StatusCode: http.StatusServiceUnavailable,
} }
} }
@@ -315,7 +312,7 @@ func (f *File) handleUpstream(resp *http.Response, retryCount, maxRetries int) (
// Try to regenerate download link if we haven't exceeded retries // Try to regenerate download link if we haven't exceeded retries
if retryCount < maxRetries { if retryCount < maxRetries {
// Clear cached link to force regeneration // Clear cached link to force regeneration
f.downloadLink = nil f.downloadLink = ""
return true, nil return true, nil
} }

View File

@@ -451,15 +451,15 @@ func (h *Handler) handleGet(w http.ResponseWriter, r *http.Request) {
// Handle nginx proxy (X-Accel-Redirect) // Handle nginx proxy (X-Accel-Redirect)
if file.content == nil && !file.isRar && h.cache.StreamWithRclone() { if file.content == nil && !file.isRar && h.cache.StreamWithRclone() {
link, err := file.getDownloadLink() link, err := file.getDownloadLink()
if err != nil || link == nil { if err != nil || link == "" {
http.Error(w, "Could not fetch download link", http.StatusPreconditionFailed) http.Error(w, "Could not fetch download link", http.StatusPreconditionFailed)
return return
} }
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", fi.Name())) w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", fi.Name()))
w.Header().Set("X-Accel-Redirect", link.DownloadLink) w.Header().Set("X-Accel-Redirect", link)
w.Header().Set("X-Accel-Buffering", "no") w.Header().Set("X-Accel-Buffering", "no")
http.Redirect(w, r, link.DownloadLink, http.StatusFound) http.Redirect(w, r, link, http.StatusFound)
return return
} }