mirror of
https://github.com/idanoo/autobrr
synced 2025-07-22 16:29:12 +00:00
feat(download-clients): rtorrent support Digest Auth (#1596)
* feat(download-clients): rtorrent support basic auth * feat(download-client): implement new auth logic * fix(download-client): tests store * chore(deps): update go-rtorrent to v1.11.0
This commit is contained in:
parent
861f30c144
commit
0d53f7e5fc
11 changed files with 231 additions and 71 deletions
3
go.mod
3
go.mod
|
@ -11,7 +11,7 @@ require (
|
||||||
github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef
|
github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef
|
||||||
github.com/autobrr/go-deluge v1.2.0
|
github.com/autobrr/go-deluge v1.2.0
|
||||||
github.com/autobrr/go-qbittorrent v1.9.0
|
github.com/autobrr/go-qbittorrent v1.9.0
|
||||||
github.com/autobrr/go-rtorrent v1.10.0
|
github.com/autobrr/go-rtorrent v1.11.0
|
||||||
github.com/avast/retry-go v3.0.0+incompatible
|
github.com/avast/retry-go v3.0.0+incompatible
|
||||||
github.com/avast/retry-go/v4 v4.6.0
|
github.com/avast/retry-go/v4 v4.6.0
|
||||||
github.com/containrrr/shoutrrr v0.8.0
|
github.com/containrrr/shoutrrr v0.8.0
|
||||||
|
@ -72,6 +72,7 @@ require (
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
github.com/hekmon/cunits/v2 v2.1.0 // indirect
|
github.com/hekmon/cunits/v2 v2.1.0 // indirect
|
||||||
github.com/huandu/xstrings v1.4.0 // indirect
|
github.com/huandu/xstrings v1.4.0 // indirect
|
||||||
|
github.com/icholy/digest v0.1.23 // indirect
|
||||||
github.com/imdario/mergo v0.3.16 // indirect
|
github.com/imdario/mergo v0.3.16 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||||
|
|
6
go.sum
6
go.sum
|
@ -64,6 +64,10 @@ github.com/autobrr/go-qbittorrent v1.9.0 h1:HaLueJ99D3G1cQ2r5ADVbtfwyEhekt2eQoEZ
|
||||||
github.com/autobrr/go-qbittorrent v1.9.0/go.mod h1:z88B3+O/1/3doQABErvIOOxE4hjpmIpulu6XzDG/q78=
|
github.com/autobrr/go-qbittorrent v1.9.0/go.mod h1:z88B3+O/1/3doQABErvIOOxE4hjpmIpulu6XzDG/q78=
|
||||||
github.com/autobrr/go-rtorrent v1.10.0 h1:SCs7Rdi1BZ3MxNoVIdWK0qTUHQyhSj9rEU8KUTRi4Ug=
|
github.com/autobrr/go-rtorrent v1.10.0 h1:SCs7Rdi1BZ3MxNoVIdWK0qTUHQyhSj9rEU8KUTRi4Ug=
|
||||||
github.com/autobrr/go-rtorrent v1.10.0/go.mod h1:1CyQ2tcLOGP+p9drOqFiVPb/+QvfExMPCHnEGQd0BmM=
|
github.com/autobrr/go-rtorrent v1.10.0/go.mod h1:1CyQ2tcLOGP+p9drOqFiVPb/+QvfExMPCHnEGQd0BmM=
|
||||||
|
github.com/autobrr/go-rtorrent v1.10.1-0.20240718115807-9f53e3443272 h1:qna9uyozEZbLS2HE7FP6qaXbnJzBOClQ+49mEUI51nM=
|
||||||
|
github.com/autobrr/go-rtorrent v1.10.1-0.20240718115807-9f53e3443272/go.mod h1:1CyQ2tcLOGP+p9drOqFiVPb/+QvfExMPCHnEGQd0BmM=
|
||||||
|
github.com/autobrr/go-rtorrent v1.11.0 h1:T1NRPgFLooFFMX0kfvewftLk4hFQUsMlDNB8WMynBSw=
|
||||||
|
github.com/autobrr/go-rtorrent v1.11.0/go.mod h1:1CyQ2tcLOGP+p9drOqFiVPb/+QvfExMPCHnEGQd0BmM=
|
||||||
github.com/autobrr/sse/v2 v2.0.0-20230520125637-530e06346d7d h1:9EGCYgeugAVWLBAtjHC7AFnXSwUdYfCB98WaOgdDREE=
|
github.com/autobrr/sse/v2 v2.0.0-20230520125637-530e06346d7d h1:9EGCYgeugAVWLBAtjHC7AFnXSwUdYfCB98WaOgdDREE=
|
||||||
github.com/autobrr/sse/v2 v2.0.0-20230520125637-530e06346d7d/go.mod h1:zCozZ9lp4DE340T2+wfMPL/eoQwLVIGDOCKCDEFwTQU=
|
github.com/autobrr/sse/v2 v2.0.0-20230520125637-530e06346d7d/go.mod h1:zCozZ9lp4DE340T2+wfMPL/eoQwLVIGDOCKCDEFwTQU=
|
||||||
github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0=
|
github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0=
|
||||||
|
@ -200,6 +204,8 @@ github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq
|
||||||
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||||
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
|
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
|
||||||
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||||
|
github.com/icholy/digest v0.1.23 h1:4hX2pIloP0aDx7RJW0JewhPPy3R8kU+vWKdxPsCCGtY=
|
||||||
|
github.com/icholy/digest v0.1.23/go.mod h1:QNrsSGQ5v7v9cReDI0+eyjsXGUoRSUZQHeQ5C4XLa0Y=
|
||||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||||
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
|
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
|
||||||
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
|
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
|
||||||
|
|
|
@ -142,6 +142,7 @@ func (r *DownloadClientRepo) Store(ctx context.Context, client *domain.DownloadC
|
||||||
Rules: client.Settings.Rules,
|
Rules: client.Settings.Rules,
|
||||||
ExternalDownloadClientId: client.Settings.ExternalDownloadClientId,
|
ExternalDownloadClientId: client.Settings.ExternalDownloadClientId,
|
||||||
ExternalDownloadClient: client.Settings.ExternalDownloadClient,
|
ExternalDownloadClient: client.Settings.ExternalDownloadClient,
|
||||||
|
Auth: client.Settings.Auth,
|
||||||
}
|
}
|
||||||
|
|
||||||
settingsJson, err := json.Marshal(&settings)
|
settingsJson, err := json.Marshal(&settings)
|
||||||
|
@ -177,6 +178,7 @@ func (r *DownloadClientRepo) Update(ctx context.Context, client *domain.Download
|
||||||
Rules: client.Settings.Rules,
|
Rules: client.Settings.Rules,
|
||||||
ExternalDownloadClientId: client.Settings.ExternalDownloadClientId,
|
ExternalDownloadClientId: client.Settings.ExternalDownloadClientId,
|
||||||
ExternalDownloadClient: client.Settings.ExternalDownloadClient,
|
ExternalDownloadClient: client.Settings.ExternalDownloadClient,
|
||||||
|
Auth: client.Settings.Auth,
|
||||||
}
|
}
|
||||||
|
|
||||||
settingsJson, err := json.Marshal(&settings)
|
settingsJson, err := json.Marshal(&settings)
|
||||||
|
|
|
@ -44,6 +44,12 @@ func getMockDownloadClient() domain.DownloadClient {
|
||||||
},
|
},
|
||||||
ExternalDownloadClientId: 0,
|
ExternalDownloadClientId: 0,
|
||||||
ExternalDownloadClient: "",
|
ExternalDownloadClient: "",
|
||||||
|
Auth: domain.DownloadClientAuth{
|
||||||
|
Enabled: true,
|
||||||
|
Type: domain.DownloadClientAuthTypeBasic,
|
||||||
|
Username: "username",
|
||||||
|
Password: "password",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ package domain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
|
@ -38,10 +39,72 @@ type DownloadClient struct {
|
||||||
|
|
||||||
type DownloadClientSettings struct {
|
type DownloadClientSettings struct {
|
||||||
APIKey string `json:"apikey,omitempty"`
|
APIKey string `json:"apikey,omitempty"`
|
||||||
Basic BasicAuth `json:"basic,omitempty"`
|
Basic BasicAuth `json:"basic,omitempty"` // Deprecated: Use Auth instead
|
||||||
Rules DownloadClientRules `json:"rules,omitempty"`
|
Rules DownloadClientRules `json:"rules,omitempty"`
|
||||||
ExternalDownloadClientId int `json:"external_download_client_id,omitempty"`
|
ExternalDownloadClientId int `json:"external_download_client_id,omitempty"`
|
||||||
ExternalDownloadClient string `json:"external_download_client,omitempty"`
|
ExternalDownloadClient string `json:"external_download_client,omitempty"`
|
||||||
|
Auth DownloadClientAuth `json:"auth,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON Custom method to translate Basic into Auth without including Basic in JSON output
|
||||||
|
func (dcs *DownloadClientSettings) MarshalJSON() ([]byte, error) {
|
||||||
|
// Ensuring Auth is updated with Basic info before marshaling if Basic is set
|
||||||
|
if dcs.Basic.Username != "" || dcs.Basic.Password != "" {
|
||||||
|
dcs.Auth = DownloadClientAuth{
|
||||||
|
Enabled: dcs.Basic.Auth,
|
||||||
|
Type: DownloadClientAuthTypeBasic,
|
||||||
|
Username: dcs.Basic.Username,
|
||||||
|
Password: dcs.Basic.Password,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Alias DownloadClientSettings
|
||||||
|
return json.Marshal(&struct {
|
||||||
|
*Alias
|
||||||
|
}{
|
||||||
|
Alias: (*Alias)(dcs),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON Custom method to translate Basic into Auth
|
||||||
|
func (dcs *DownloadClientSettings) UnmarshalJSON(data []byte) error {
|
||||||
|
type Alias DownloadClientSettings
|
||||||
|
aux := &struct {
|
||||||
|
*Alias
|
||||||
|
}{
|
||||||
|
Alias: (*Alias)(dcs),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(data, &aux); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If Basic fields are not empty, populate Auth fields accordingly
|
||||||
|
if aux.Basic.Username != "" || aux.Basic.Password != "" {
|
||||||
|
dcs.Auth = DownloadClientAuth{
|
||||||
|
Enabled: aux.Basic.Auth,
|
||||||
|
Type: DownloadClientAuthTypeBasic,
|
||||||
|
Username: aux.Basic.Username,
|
||||||
|
Password: aux.Basic.Password,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DownloadClientAuthType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
DownloadClientAuthTypeNone = "NONE"
|
||||||
|
DownloadClientAuthTypeBasic = "BASIC_AUTH"
|
||||||
|
DownloadClientAuthTypeDigest = "DIGEST_AUTH"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DownloadClientAuth struct {
|
||||||
|
Enabled bool `json:"enabled,omitempty"`
|
||||||
|
Type DownloadClientAuthType `json:"type,omitempty"`
|
||||||
|
Username string `json:"username,omitempty"`
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DownloadClientRules struct {
|
type DownloadClientRules struct {
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
// Copyright (c) 2021 - 2024, Ludvig Lundgren and the autobrr contributors.
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
package download_client
|
package download_client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -5,7 +5,9 @@ package download_client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -24,6 +26,7 @@ import (
|
||||||
"github.com/autobrr/go-qbittorrent"
|
"github.com/autobrr/go-qbittorrent"
|
||||||
"github.com/autobrr/go-rtorrent"
|
"github.com/autobrr/go-rtorrent"
|
||||||
"github.com/dcarbone/zadapters/zstdlog"
|
"github.com/dcarbone/zadapters/zstdlog"
|
||||||
|
"github.com/icholy/digest"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -70,16 +73,16 @@ func (s *service) testConnection(ctx context.Context, client domain.DownloadClie
|
||||||
func (s *service) testQbittorrentConnection(ctx context.Context, client domain.DownloadClient) error {
|
func (s *service) testQbittorrentConnection(ctx context.Context, client domain.DownloadClient) error {
|
||||||
qbtSettings := qbittorrent.Config{
|
qbtSettings := qbittorrent.Config{
|
||||||
Host: client.BuildLegacyHost(),
|
Host: client.BuildLegacyHost(),
|
||||||
|
TLSSkipVerify: client.TLSSkipVerify,
|
||||||
Username: client.Username,
|
Username: client.Username,
|
||||||
Password: client.Password,
|
Password: client.Password,
|
||||||
TLSSkipVerify: client.TLSSkipVerify,
|
|
||||||
Log: s.subLogger,
|
Log: s.subLogger,
|
||||||
}
|
}
|
||||||
|
|
||||||
// only set basic auth if enabled
|
// only set basic auth if enabled
|
||||||
if client.Settings.Basic.Auth {
|
if client.Settings.Auth.Enabled {
|
||||||
qbtSettings.BasicUser = client.Settings.Basic.Username
|
qbtSettings.BasicUser = client.Settings.Auth.Username
|
||||||
qbtSettings.BasicPass = client.Settings.Basic.Password
|
qbtSettings.BasicPass = client.Settings.Auth.Password
|
||||||
}
|
}
|
||||||
|
|
||||||
qbt := qbittorrent.NewClient(qbtSettings)
|
qbt := qbittorrent.NewClient(qbtSettings)
|
||||||
|
@ -155,14 +158,31 @@ func (s *service) testDelugeConnection(ctx context.Context, client domain.Downlo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) testRTorrentConnection(ctx context.Context, client domain.DownloadClient) error {
|
func (s *service) testRTorrentConnection(ctx context.Context, client domain.DownloadClient) error {
|
||||||
// create client
|
cfg := rtorrent.Config{
|
||||||
rt := rtorrent.NewClient(rtorrent.Config{
|
|
||||||
Addr: client.Host,
|
Addr: client.Host,
|
||||||
TLSSkipVerify: client.TLSSkipVerify,
|
TLSSkipVerify: client.TLSSkipVerify,
|
||||||
BasicUser: client.Settings.Basic.Username,
|
BasicUser: client.Settings.Auth.Username,
|
||||||
BasicPass: client.Settings.Basic.Password,
|
BasicPass: client.Settings.Auth.Password,
|
||||||
Log: nil,
|
Log: s.subLogger,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
// create client
|
||||||
|
rt := rtorrent.NewClient(cfg)
|
||||||
|
|
||||||
|
if client.Settings.Auth.Type == domain.DownloadClientAuthTypeDigest {
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Transport: &digest.Transport{
|
||||||
|
Username: client.Settings.Auth.Username,
|
||||||
|
Password: client.Settings.Auth.Password,
|
||||||
|
Transport: &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: client.TLSSkipVerify},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// override client
|
||||||
|
rt = rtorrent.NewClientWithOpts(cfg, rtorrent.WithCustomClient(httpClient))
|
||||||
|
}
|
||||||
|
|
||||||
name, err := rt.Name(ctx)
|
name, err := rt.Name(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -206,7 +226,7 @@ func (s *service) testTransmissionConnection(ctx context.Context, client domain.
|
||||||
return errors.Wrap(err, "error getting rpc info: %v", client.Host)
|
return errors.Wrap(err, "error getting rpc info: %v", client.Host)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.log.Debug().Msgf("test client connection for Transmission: got version: %v", version)
|
s.log.Trace().Msgf("test client connection for Transmission: got version: %v", version)
|
||||||
|
|
||||||
s.log.Debug().Msgf("test client connection for Transmission: success")
|
s.log.Debug().Msgf("test client connection for Transmission: success")
|
||||||
|
|
||||||
|
@ -217,9 +237,9 @@ func (s *service) testRadarrConnection(ctx context.Context, client domain.Downlo
|
||||||
r := radarr.New(radarr.Config{
|
r := radarr.New(radarr.Config{
|
||||||
Hostname: client.Host,
|
Hostname: client.Host,
|
||||||
APIKey: client.Settings.APIKey,
|
APIKey: client.Settings.APIKey,
|
||||||
BasicAuth: client.Settings.Basic.Auth,
|
BasicAuth: client.Settings.Auth.Enabled,
|
||||||
Username: client.Settings.Basic.Username,
|
Username: client.Settings.Auth.Username,
|
||||||
Password: client.Settings.Basic.Password,
|
Password: client.Settings.Auth.Password,
|
||||||
Log: s.subLogger,
|
Log: s.subLogger,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -236,9 +256,9 @@ func (s *service) testSonarrConnection(ctx context.Context, client domain.Downlo
|
||||||
r := sonarr.New(sonarr.Config{
|
r := sonarr.New(sonarr.Config{
|
||||||
Hostname: client.Host,
|
Hostname: client.Host,
|
||||||
APIKey: client.Settings.APIKey,
|
APIKey: client.Settings.APIKey,
|
||||||
BasicAuth: client.Settings.Basic.Auth,
|
BasicAuth: client.Settings.Auth.Enabled,
|
||||||
Username: client.Settings.Basic.Username,
|
Username: client.Settings.Auth.Username,
|
||||||
Password: client.Settings.Basic.Password,
|
Password: client.Settings.Auth.Password,
|
||||||
Log: s.subLogger,
|
Log: s.subLogger,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -255,9 +275,9 @@ func (s *service) testLidarrConnection(ctx context.Context, client domain.Downlo
|
||||||
r := lidarr.New(lidarr.Config{
|
r := lidarr.New(lidarr.Config{
|
||||||
Hostname: client.Host,
|
Hostname: client.Host,
|
||||||
APIKey: client.Settings.APIKey,
|
APIKey: client.Settings.APIKey,
|
||||||
BasicAuth: client.Settings.Basic.Auth,
|
BasicAuth: client.Settings.Auth.Enabled,
|
||||||
Username: client.Settings.Basic.Username,
|
Username: client.Settings.Auth.Username,
|
||||||
Password: client.Settings.Basic.Password,
|
Password: client.Settings.Auth.Password,
|
||||||
Log: s.subLogger,
|
Log: s.subLogger,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -274,9 +294,9 @@ func (s *service) testWhisparrConnection(ctx context.Context, client domain.Down
|
||||||
r := whisparr.New(whisparr.Config{
|
r := whisparr.New(whisparr.Config{
|
||||||
Hostname: client.Host,
|
Hostname: client.Host,
|
||||||
APIKey: client.Settings.APIKey,
|
APIKey: client.Settings.APIKey,
|
||||||
BasicAuth: client.Settings.Basic.Auth,
|
BasicAuth: client.Settings.Auth.Enabled,
|
||||||
Username: client.Settings.Basic.Username,
|
Username: client.Settings.Auth.Username,
|
||||||
Password: client.Settings.Basic.Password,
|
Password: client.Settings.Auth.Password,
|
||||||
Log: s.subLogger,
|
Log: s.subLogger,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -293,9 +313,9 @@ func (s *service) testReadarrConnection(ctx context.Context, client domain.Downl
|
||||||
r := readarr.New(readarr.Config{
|
r := readarr.New(readarr.Config{
|
||||||
Hostname: client.Host,
|
Hostname: client.Host,
|
||||||
APIKey: client.Settings.APIKey,
|
APIKey: client.Settings.APIKey,
|
||||||
BasicAuth: client.Settings.Basic.Auth,
|
BasicAuth: client.Settings.Auth.Enabled,
|
||||||
Username: client.Settings.Basic.Username,
|
Username: client.Settings.Auth.Username,
|
||||||
Password: client.Settings.Basic.Password,
|
Password: client.Settings.Auth.Password,
|
||||||
Log: s.subLogger,
|
Log: s.subLogger,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -310,8 +330,12 @@ func (s *service) testReadarrConnection(ctx context.Context, client domain.Downl
|
||||||
|
|
||||||
func (s *service) testPorlaConnection(client domain.DownloadClient) error {
|
func (s *service) testPorlaConnection(client domain.DownloadClient) error {
|
||||||
p := porla.NewClient(porla.Config{
|
p := porla.NewClient(porla.Config{
|
||||||
Hostname: client.Host,
|
Hostname: client.Host,
|
||||||
AuthToken: client.Settings.APIKey,
|
TLSSkipVerify: client.TLSSkipVerify,
|
||||||
|
AuthToken: client.Settings.APIKey,
|
||||||
|
BasicUser: client.Settings.Auth.Username,
|
||||||
|
BasicPass: client.Settings.Auth.Password,
|
||||||
|
Log: s.subLogger,
|
||||||
})
|
})
|
||||||
|
|
||||||
version, err := p.Version()
|
version, err := p.Version()
|
||||||
|
@ -320,13 +344,13 @@ func (s *service) testPorlaConnection(client domain.DownloadClient) error {
|
||||||
return errors.Wrap(err, "porla: failed to get version: %v", client.Host)
|
return errors.Wrap(err, "porla: failed to get version: %v", client.Host)
|
||||||
}
|
}
|
||||||
|
|
||||||
commitish := version.Commitish
|
commitHash := version.Commitish
|
||||||
|
|
||||||
if len(commitish) > 8 {
|
if len(commitHash) > 8 {
|
||||||
commitish = commitish[:8]
|
commitHash = commitHash[:8]
|
||||||
}
|
}
|
||||||
|
|
||||||
s.log.Debug().Msgf("test client connection for porla: found version %s (commit %s)", version.Version, commitish)
|
s.log.Debug().Msgf("test client connection for porla: found version %s (commit %s)", version.Version, commitHash)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -335,9 +359,9 @@ func (s *service) testSabnzbdConnection(ctx context.Context, client domain.Downl
|
||||||
opts := sabnzbd.Options{
|
opts := sabnzbd.Options{
|
||||||
Addr: client.Host,
|
Addr: client.Host,
|
||||||
ApiKey: client.Settings.APIKey,
|
ApiKey: client.Settings.APIKey,
|
||||||
BasicUser: client.Settings.Basic.Username,
|
BasicUser: client.Settings.Auth.Username,
|
||||||
BasicPass: client.Settings.Basic.Password,
|
BasicPass: client.Settings.Auth.Password,
|
||||||
Log: nil,
|
Log: s.subLogger,
|
||||||
}
|
}
|
||||||
|
|
||||||
sab := sabnzbd.New(opts)
|
sab := sabnzbd.New(opts)
|
||||||
|
|
|
@ -5,8 +5,10 @@ package download_client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -27,6 +29,7 @@ import (
|
||||||
"github.com/autobrr/go-qbittorrent"
|
"github.com/autobrr/go-qbittorrent"
|
||||||
"github.com/autobrr/go-rtorrent"
|
"github.com/autobrr/go-rtorrent"
|
||||||
"github.com/dcarbone/zadapters/zstdlog"
|
"github.com/dcarbone/zadapters/zstdlog"
|
||||||
|
"github.com/icholy/digest"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -184,8 +187,8 @@ func (s *service) GetClient(ctx context.Context, clientId int32) (*domain.Downlo
|
||||||
Password: client.Password,
|
Password: client.Password,
|
||||||
TLSSkipVerify: client.TLSSkipVerify,
|
TLSSkipVerify: client.TLSSkipVerify,
|
||||||
Log: zstdlog.NewStdLoggerWithLevel(s.log.With().Str("type", "qBittorrent").Str("client", client.Name).Logger(), zerolog.TraceLevel),
|
Log: zstdlog.NewStdLoggerWithLevel(s.log.With().Str("type", "qBittorrent").Str("client", client.Name).Logger(), zerolog.TraceLevel),
|
||||||
BasicUser: client.Settings.Basic.Username,
|
BasicUser: client.Settings.Auth.Username,
|
||||||
BasicPass: client.Settings.Basic.Password,
|
BasicPass: client.Settings.Auth.Password,
|
||||||
})
|
})
|
||||||
|
|
||||||
case domain.DownloadClientTypePorla:
|
case domain.DownloadClientTypePorla:
|
||||||
|
@ -193,8 +196,8 @@ func (s *service) GetClient(ctx context.Context, clientId int32) (*domain.Downlo
|
||||||
Hostname: client.Host,
|
Hostname: client.Host,
|
||||||
AuthToken: client.Settings.APIKey,
|
AuthToken: client.Settings.APIKey,
|
||||||
TLSSkipVerify: client.TLSSkipVerify,
|
TLSSkipVerify: client.TLSSkipVerify,
|
||||||
BasicUser: client.Settings.Basic.Username,
|
BasicUser: client.Settings.Auth.Username,
|
||||||
BasicPass: client.Settings.Basic.Password,
|
BasicPass: client.Settings.Auth.Password,
|
||||||
Log: zstdlog.NewStdLoggerWithLevel(s.log.With().Str("type", "Porla").Str("client", client.Name).Logger(), zerolog.TraceLevel),
|
Log: zstdlog.NewStdLoggerWithLevel(s.log.With().Str("type", "Porla").Str("client", client.Name).Logger(), zerolog.TraceLevel),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -241,22 +244,46 @@ func (s *service) GetClient(ctx context.Context, clientId int32) (*domain.Downlo
|
||||||
client.Client = tbt
|
client.Client = tbt
|
||||||
|
|
||||||
case domain.DownloadClientTypeRTorrent:
|
case domain.DownloadClientTypeRTorrent:
|
||||||
client.Client = rtorrent.NewClient(rtorrent.Config{
|
if client.Settings.Auth.Type == domain.DownloadClientAuthTypeDigest {
|
||||||
Addr: client.Host,
|
cfg := rtorrent.Config{
|
||||||
TLSSkipVerify: client.TLSSkipVerify,
|
Addr: client.Host,
|
||||||
BasicUser: client.Settings.Basic.Username,
|
TLSSkipVerify: client.TLSSkipVerify,
|
||||||
BasicPass: client.Settings.Basic.Password,
|
BasicUser: client.Settings.Auth.Username,
|
||||||
Log: zstdlog.NewStdLoggerWithLevel(s.log.With().Str("type", "rTorrent").Str("client", client.Name).Logger(), zerolog.TraceLevel),
|
BasicPass: client.Settings.Auth.Password,
|
||||||
})
|
Log: zstdlog.NewStdLoggerWithLevel(s.log.With().Str("type", "rTorrent").Str("client", client.Name).Logger(), zerolog.TraceLevel),
|
||||||
|
}
|
||||||
|
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Transport: &digest.Transport{
|
||||||
|
Username: client.Settings.Auth.Username,
|
||||||
|
Password: client.Settings.Auth.Password,
|
||||||
|
Transport: &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: client.TLSSkipVerify},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// override client
|
||||||
|
client.Client = rtorrent.NewClientWithOpts(cfg, rtorrent.WithCustomClient(httpClient))
|
||||||
|
|
||||||
|
} else {
|
||||||
|
client.Client = rtorrent.NewClient(rtorrent.Config{
|
||||||
|
Addr: client.Host,
|
||||||
|
TLSSkipVerify: client.TLSSkipVerify,
|
||||||
|
BasicUser: client.Settings.Auth.Username,
|
||||||
|
BasicPass: client.Settings.Auth.Password,
|
||||||
|
Log: zstdlog.NewStdLoggerWithLevel(s.log.With().Str("type", "rTorrent").Str("client", client.Name).Logger(), zerolog.TraceLevel),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
case domain.DownloadClientTypeLidarr:
|
case domain.DownloadClientTypeLidarr:
|
||||||
client.Client = lidarr.New(lidarr.Config{
|
client.Client = lidarr.New(lidarr.Config{
|
||||||
Hostname: client.Host,
|
Hostname: client.Host,
|
||||||
APIKey: client.Settings.APIKey,
|
APIKey: client.Settings.APIKey,
|
||||||
Log: zstdlog.NewStdLoggerWithLevel(s.log.With().Str("type", "Lidarr").Str("client", client.Name).Logger(), zerolog.TraceLevel),
|
Log: zstdlog.NewStdLoggerWithLevel(s.log.With().Str("type", "Lidarr").Str("client", client.Name).Logger(), zerolog.TraceLevel),
|
||||||
BasicAuth: client.Settings.Basic.Auth,
|
BasicAuth: client.Settings.Auth.Enabled,
|
||||||
Username: client.Settings.Basic.Username,
|
Username: client.Settings.Auth.Username,
|
||||||
Password: client.Settings.Basic.Password,
|
Password: client.Settings.Auth.Password,
|
||||||
})
|
})
|
||||||
|
|
||||||
case domain.DownloadClientTypeRadarr:
|
case domain.DownloadClientTypeRadarr:
|
||||||
|
@ -264,9 +291,9 @@ func (s *service) GetClient(ctx context.Context, clientId int32) (*domain.Downlo
|
||||||
Hostname: client.Host,
|
Hostname: client.Host,
|
||||||
APIKey: client.Settings.APIKey,
|
APIKey: client.Settings.APIKey,
|
||||||
Log: zstdlog.NewStdLoggerWithLevel(s.log.With().Str("type", "Radarr").Str("client", client.Name).Logger(), zerolog.TraceLevel),
|
Log: zstdlog.NewStdLoggerWithLevel(s.log.With().Str("type", "Radarr").Str("client", client.Name).Logger(), zerolog.TraceLevel),
|
||||||
BasicAuth: client.Settings.Basic.Auth,
|
BasicAuth: client.Settings.Auth.Enabled,
|
||||||
Username: client.Settings.Basic.Username,
|
Username: client.Settings.Auth.Username,
|
||||||
Password: client.Settings.Basic.Password,
|
Password: client.Settings.Auth.Password,
|
||||||
})
|
})
|
||||||
|
|
||||||
case domain.DownloadClientTypeReadarr:
|
case domain.DownloadClientTypeReadarr:
|
||||||
|
@ -274,9 +301,9 @@ func (s *service) GetClient(ctx context.Context, clientId int32) (*domain.Downlo
|
||||||
Hostname: client.Host,
|
Hostname: client.Host,
|
||||||
APIKey: client.Settings.APIKey,
|
APIKey: client.Settings.APIKey,
|
||||||
Log: zstdlog.NewStdLoggerWithLevel(s.log.With().Str("type", "Readarr").Str("client", client.Name).Logger(), zerolog.TraceLevel),
|
Log: zstdlog.NewStdLoggerWithLevel(s.log.With().Str("type", "Readarr").Str("client", client.Name).Logger(), zerolog.TraceLevel),
|
||||||
BasicAuth: client.Settings.Basic.Auth,
|
BasicAuth: client.Settings.Auth.Enabled,
|
||||||
Username: client.Settings.Basic.Username,
|
Username: client.Settings.Auth.Username,
|
||||||
Password: client.Settings.Basic.Password,
|
Password: client.Settings.Auth.Password,
|
||||||
})
|
})
|
||||||
|
|
||||||
case domain.DownloadClientTypeSonarr:
|
case domain.DownloadClientTypeSonarr:
|
||||||
|
@ -284,9 +311,9 @@ func (s *service) GetClient(ctx context.Context, clientId int32) (*domain.Downlo
|
||||||
Hostname: client.Host,
|
Hostname: client.Host,
|
||||||
APIKey: client.Settings.APIKey,
|
APIKey: client.Settings.APIKey,
|
||||||
Log: zstdlog.NewStdLoggerWithLevel(s.log.With().Str("type", "Sonarr").Str("client", client.Name).Logger(), zerolog.TraceLevel),
|
Log: zstdlog.NewStdLoggerWithLevel(s.log.With().Str("type", "Sonarr").Str("client", client.Name).Logger(), zerolog.TraceLevel),
|
||||||
BasicAuth: client.Settings.Basic.Auth,
|
BasicAuth: client.Settings.Auth.Enabled,
|
||||||
Username: client.Settings.Basic.Username,
|
Username: client.Settings.Auth.Username,
|
||||||
Password: client.Settings.Basic.Password,
|
Password: client.Settings.Auth.Password,
|
||||||
})
|
})
|
||||||
|
|
||||||
case domain.DownloadClientTypeWhisparr:
|
case domain.DownloadClientTypeWhisparr:
|
||||||
|
@ -294,9 +321,9 @@ func (s *service) GetClient(ctx context.Context, clientId int32) (*domain.Downlo
|
||||||
Hostname: client.Host,
|
Hostname: client.Host,
|
||||||
APIKey: client.Settings.APIKey,
|
APIKey: client.Settings.APIKey,
|
||||||
Log: zstdlog.NewStdLoggerWithLevel(s.log.With().Str("type", "Whisparr").Str("client", client.Name).Logger(), zerolog.TraceLevel),
|
Log: zstdlog.NewStdLoggerWithLevel(s.log.With().Str("type", "Whisparr").Str("client", client.Name).Logger(), zerolog.TraceLevel),
|
||||||
BasicAuth: client.Settings.Basic.Auth,
|
BasicAuth: client.Settings.Auth.Enabled,
|
||||||
Username: client.Settings.Basic.Username,
|
Username: client.Settings.Auth.Username,
|
||||||
Password: client.Settings.Basic.Password,
|
Password: client.Settings.Auth.Password,
|
||||||
})
|
})
|
||||||
|
|
||||||
case domain.DownloadClientTypeSabnzbd:
|
case domain.DownloadClientTypeSabnzbd:
|
||||||
|
@ -304,8 +331,8 @@ func (s *service) GetClient(ctx context.Context, clientId int32) (*domain.Downlo
|
||||||
Addr: client.Host,
|
Addr: client.Host,
|
||||||
ApiKey: client.Settings.APIKey,
|
ApiKey: client.Settings.APIKey,
|
||||||
Log: nil,
|
Log: nil,
|
||||||
BasicUser: client.Settings.Basic.Username,
|
BasicUser: client.Settings.Auth.Username,
|
||||||
BasicPass: client.Settings.Basic.Password,
|
BasicPass: client.Settings.Auth.Password,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -129,7 +129,7 @@ export const Tooltip = ({
|
||||||
{...getTooltipProps({
|
{...getTooltipProps({
|
||||||
className: classNames(
|
className: classNames(
|
||||||
maxWidth,
|
maxWidth,
|
||||||
"rounded-md border border-gray-300 text-black text-xs normal-case tracking-normal font-normal shadow-lg dark:text-white dark:border-gray-700 dark:shadow-2xl"
|
"z-10 rounded-md border border-gray-300 text-black text-xs normal-case tracking-normal font-normal shadow-lg dark:text-white dark:border-gray-700 dark:shadow-2xl"
|
||||||
),
|
),
|
||||||
onClick: (e: React.MouseEvent) => e.stopPropagation()
|
onClick: (e: React.MouseEvent) => e.stopPropagation()
|
||||||
})}
|
})}
|
||||||
|
|
|
@ -474,6 +474,21 @@ export const DownloadRuleConditionOptions: OptionBasic[] = [
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const DownloadClientAuthType: OptionBasic[] = [
|
||||||
|
{
|
||||||
|
label: "None",
|
||||||
|
value: "NONE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Basic Auth",
|
||||||
|
value: "BASIC_AUTH"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Digest Auth",
|
||||||
|
value: "DIGEST_AUTH"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
const logLevel = ["DEBUG", "INFO", "WARN", "ERROR", "TRACE"] as const;
|
const logLevel = ["DEBUG", "INFO", "WARN", "ERROR", "TRACE"] as const;
|
||||||
|
|
||||||
export const LogLevelOptions = logLevel.map(v => ({ value: v, label: v, key: v }));
|
export const LogLevelOptions = logLevel.map(v => ({ value: v, label: v, key: v }));
|
||||||
|
|
|
@ -14,7 +14,7 @@ import { classNames, sleep } from "@utils";
|
||||||
import { DEBUG } from "@components/debug";
|
import { DEBUG } from "@components/debug";
|
||||||
import { APIClient } from "@api/APIClient";
|
import { APIClient } from "@api/APIClient";
|
||||||
import { DownloadClientKeys } from "@api/query_keys";
|
import { DownloadClientKeys } from "@api/query_keys";
|
||||||
import { DownloadClientTypeOptions, DownloadRuleConditionOptions } from "@domain/constants";
|
import { DownloadClientAuthType, DownloadClientTypeOptions, DownloadRuleConditionOptions } from "@domain/constants";
|
||||||
import Toast from "@components/notifications/Toast";
|
import Toast from "@components/notifications/Toast";
|
||||||
import { useToggle } from "@hooks/hooks";
|
import { useToggle } from "@hooks/hooks";
|
||||||
import { DeleteModal } from "@components/modals";
|
import { DeleteModal } from "@components/modals";
|
||||||
|
@ -34,6 +34,12 @@ interface InitialValuesSettings {
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
};
|
};
|
||||||
|
auth?: {
|
||||||
|
enabled: boolean;
|
||||||
|
type: string;
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
rules?: {
|
rules?: {
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
ignore_slow_torrents?: boolean;
|
ignore_slow_torrents?: boolean;
|
||||||
|
@ -267,12 +273,19 @@ function FormFieldsRTorrent() {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<SwitchGroupWide name="settings.basic.auth" label="Basic auth" />
|
<SwitchGroupWide name="settings.auth.enabled" label="Auth" />
|
||||||
|
|
||||||
{settings.basic?.auth === true && (
|
{settings.auth?.enabled && (
|
||||||
<>
|
<>
|
||||||
<TextFieldWide name="settings.basic.username" label="Username" />
|
<SelectFieldBasic
|
||||||
<PasswordFieldWide name="settings.basic.password" label="Password" />
|
name="settings.auth.type"
|
||||||
|
label="Auth type"
|
||||||
|
placeholder="Select auth type"
|
||||||
|
options={DownloadClientAuthType}
|
||||||
|
tooltip={<p>This should in most cases be Basic Auth, but some providers use Digest Auth.</p>}
|
||||||
|
/>
|
||||||
|
<TextFieldWide name="settings.auth.username" label="Username" />
|
||||||
|
<PasswordFieldWide name="settings.auth.password" label="Password" />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue