mirror of
https://github.com/idanoo/autobrr
synced 2025-07-22 16:29:12 +00:00
feat(clients): Transmission support seedtime and ratiolimit (#1211)
* feat(clients): Transmission seedtime ratiolimit * feat(clients): update client pkg * feat(clients): update client pkg test * feat(actions): update transmission
This commit is contained in:
parent
568e41de24
commit
d5d1cecc1b
6 changed files with 189 additions and 23 deletions
2
go.mod
2
go.mod
|
@ -23,7 +23,7 @@ require (
|
|||
github.com/gorilla/sessions v1.2.1
|
||||
github.com/gosimple/slug v1.13.1
|
||||
github.com/hashicorp/go-version v1.6.0
|
||||
github.com/hekmon/transmissionrpc/v2 v2.0.1
|
||||
github.com/hekmon/transmissionrpc/v3 v3.0.0
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/mattn/go-shellwords v1.0.12
|
||||
github.com/mmcdole/gofeed v1.2.1
|
||||
|
|
9
go.sum
9
go.sum
|
@ -147,8 +147,6 @@ github.com/ergochat/irc-go v0.4.0 h1:0YibCKfAAtwxQdNjLQd9xpIEPisLcJ45f8FNsMHAuZc
|
|||
github.com/ergochat/irc-go v0.4.0/go.mod h1:2vi7KNpIPWnReB5hmLpl92eMywQvuIeIIGdt/FQCph0=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/gdm85/go-rencode v0.1.8 h1:7+qxwoQWU1b1nMGcESOyoUR5dzPtRA6yLQpKn7uXmnI=
|
||||
|
@ -261,8 +259,8 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
|||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hekmon/cunits/v2 v2.1.0 h1:k6wIjc4PlacNOHwKEMBgWV2/c8jyD4eRMs5mR1BBhI0=
|
||||
github.com/hekmon/cunits/v2 v2.1.0/go.mod h1:9r1TycXYXaTmEWlAIfFV8JT+Xo59U96yUJAYHxzii2M=
|
||||
github.com/hekmon/transmissionrpc/v2 v2.0.1 h1:WkILCEdbNy3n/N/w7mi449waMPdH2AA1THyw7TfnN/w=
|
||||
github.com/hekmon/transmissionrpc/v2 v2.0.1/go.mod h1:+s96Pkg7dIP3h2PT3fzhXPvNb3OdLryh5J8PIvQg3aA=
|
||||
github.com/hekmon/transmissionrpc/v3 v3.0.0 h1:0Fb11qE0IBh4V4GlOwHNYpqpjcYDp5GouolwrpmcUDQ=
|
||||
github.com/hekmon/transmissionrpc/v3 v3.0.0/go.mod h1:38SlNhFzinVUuY87wGj3acOmRxeYZAZfrj6Re7UgCDg=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo=
|
||||
github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
|
||||
|
@ -331,8 +329,6 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
|
|||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/moistari/rls v0.5.9 h1:peRGW+1/HJDUZ76s0v2ukcBLCBUs4/Qf3TKOzRjOOco=
|
||||
github.com/moistari/rls v0.5.9/go.mod h1:/3P63JjNkaf1MNBoS2tSXqGeqee6l4je+Krakp4ob7c=
|
||||
github.com/moistari/rls v0.5.10 h1:e2XDxF0/+Riz9TEiTSewuZ7nuNZKOpbY1V2KSJWA2e4=
|
||||
github.com/moistari/rls v0.5.10/go.mod h1:/3P63JjNkaf1MNBoS2tSXqGeqee6l4je+Krakp4ob7c=
|
||||
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
|
||||
|
@ -614,7 +610,6 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
|
|
@ -6,13 +6,15 @@ package action
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/autobrr/autobrr/internal/domain"
|
||||
"github.com/autobrr/autobrr/pkg/errors"
|
||||
"github.com/autobrr/autobrr/pkg/transmission"
|
||||
|
||||
"github.com/hekmon/transmissionrpc/v2"
|
||||
"github.com/hekmon/transmissionrpc/v3"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -21,6 +23,7 @@ const (
|
|||
)
|
||||
|
||||
var ErrReannounceTookTooLong = errors.New("ErrReannounceTookTooLong")
|
||||
var TrTrue = true
|
||||
|
||||
func (s *service) transmission(ctx context.Context, action *domain.Action, release domain.Release) ([]string, error) {
|
||||
s.log.Debug().Msgf("action Transmission: %s", action.Name)
|
||||
|
@ -38,10 +41,21 @@ func (s *service) transmission(ctx context.Context, action *domain.Action, relea
|
|||
return nil, errors.New("could not find client by id: %d", action.ClientID)
|
||||
}
|
||||
|
||||
tbt, err := transmissionrpc.New(client.Host, client.Username, client.Password, &transmissionrpc.AdvancedConfig{
|
||||
HTTPS: client.TLS,
|
||||
Port: uint16(client.Port),
|
||||
scheme := "http"
|
||||
if client.TLS {
|
||||
scheme = "https"
|
||||
}
|
||||
|
||||
u, err := url.Parse(fmt.Sprintf("%s://%s:%d/transmission/rpc", scheme, client.Host, client.Port))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tbt, err := transmission.New(u, &transmission.Config{
|
||||
UserAgent: "autobrr",
|
||||
Username: client.Username,
|
||||
Password: client.Password,
|
||||
TLSSkipVerify: client.TLSSkipVerify,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error logging into client: %s", client.Host)
|
||||
|
@ -74,10 +88,36 @@ func (s *service) transmission(ctx context.Context, action *domain.Action, relea
|
|||
return nil, errors.Wrap(err, "could not add torrent from magnet %s to client: %s", release.MagnetURI, client.Host)
|
||||
}
|
||||
|
||||
if action.Label != "" {
|
||||
if action.Label != "" || action.LimitUploadSpeed > 0 || action.LimitDownloadSpeed > 0 || action.LimitRatio > 0 || action.LimitSeedTime > 0 {
|
||||
p := transmissionrpc.TorrentSetPayload{
|
||||
IDs: []int64{*torrent.ID},
|
||||
Labels: []string{action.Label},
|
||||
}
|
||||
|
||||
if action.Label != "" {
|
||||
p.Labels = []string{action.Label}
|
||||
}
|
||||
|
||||
if action.LimitUploadSpeed > 0 {
|
||||
p.UploadLimit = &action.LimitUploadSpeed
|
||||
p.UploadLimited = &TrTrue
|
||||
}
|
||||
if action.LimitDownloadSpeed > 0 {
|
||||
p.DownloadLimit = &action.LimitDownloadSpeed
|
||||
p.DownloadLimited = &TrTrue
|
||||
}
|
||||
if action.LimitRatio > 0 {
|
||||
p.SeedRatioLimit = &action.LimitRatio
|
||||
ratioMode := transmissionrpc.SeedRatioModeCustom
|
||||
p.SeedRatioMode = &ratioMode
|
||||
}
|
||||
if action.LimitSeedTime > 0 {
|
||||
t := time.Duration(action.LimitSeedTime) * time.Minute
|
||||
//p.SeedIdleLimit = &action.LimitSeedTime
|
||||
p.SeedIdleLimit = &t
|
||||
|
||||
// seed idle mode 1
|
||||
seedIdleMode := int64(1)
|
||||
p.SeedIdleMode = &seedIdleMode
|
||||
}
|
||||
|
||||
if err := tbt.TorrentSet(ctx, p); err != nil {
|
||||
|
@ -112,12 +152,39 @@ func (s *service) transmission(ctx context.Context, action *domain.Action, relea
|
|||
return nil, errors.Wrap(err, "could not add torrent %s to client: %s", release.TorrentTmpFile, client.Host)
|
||||
}
|
||||
|
||||
if action.Label != "" {
|
||||
if action.Label != "" || action.LimitUploadSpeed > 0 || action.LimitDownloadSpeed > 0 || action.LimitRatio > 0 || action.LimitSeedTime > 0 {
|
||||
p := transmissionrpc.TorrentSetPayload{
|
||||
IDs: []int64{*torrent.ID},
|
||||
Labels: []string{action.Label},
|
||||
}
|
||||
|
||||
if action.Label != "" {
|
||||
p.Labels = []string{action.Label}
|
||||
}
|
||||
|
||||
if action.LimitUploadSpeed > 0 {
|
||||
p.UploadLimit = &action.LimitUploadSpeed
|
||||
p.UploadLimited = &TrTrue
|
||||
}
|
||||
if action.LimitDownloadSpeed > 0 {
|
||||
p.DownloadLimit = &action.LimitDownloadSpeed
|
||||
p.DownloadLimited = &TrTrue
|
||||
}
|
||||
if action.LimitRatio > 0 {
|
||||
p.SeedRatioLimit = &action.LimitRatio
|
||||
ratioMode := transmissionrpc.SeedRatioModeCustom
|
||||
p.SeedRatioMode = &ratioMode
|
||||
}
|
||||
if action.LimitSeedTime > 0 {
|
||||
t := time.Duration(action.LimitSeedTime) * time.Minute
|
||||
p.SeedIdleLimit = &t
|
||||
|
||||
// seed idle mode 1
|
||||
seedIdleMode := int64(1)
|
||||
p.SeedIdleMode = &seedIdleMode
|
||||
}
|
||||
|
||||
s.log.Trace().Msgf("transmission torrent set payload: %+v for torrent hash %s client: %s", p, *torrent.HashString, client.Name)
|
||||
|
||||
if err := tbt.TorrentSet(ctx, p); err != nil {
|
||||
return nil, errors.Wrap(err, "could not set label for hash %s to client: %s", *torrent.HashString, client.Host)
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ package download_client
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/autobrr/autobrr/internal/domain"
|
||||
|
@ -15,13 +17,13 @@ import (
|
|||
"github.com/autobrr/autobrr/pkg/readarr"
|
||||
"github.com/autobrr/autobrr/pkg/sabnzbd"
|
||||
"github.com/autobrr/autobrr/pkg/sonarr"
|
||||
"github.com/autobrr/autobrr/pkg/transmission"
|
||||
"github.com/autobrr/autobrr/pkg/whisparr"
|
||||
|
||||
"github.com/autobrr/go-deluge"
|
||||
"github.com/autobrr/go-qbittorrent"
|
||||
"github.com/autobrr/go-rtorrent"
|
||||
"github.com/dcarbone/zadapters/zstdlog"
|
||||
"github.com/hekmon/transmissionrpc/v2"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
|
@ -175,9 +177,21 @@ func (s *service) testRTorrentConnection(ctx context.Context, client domain.Down
|
|||
}
|
||||
|
||||
func (s *service) testTransmissionConnection(ctx context.Context, client domain.DownloadClient) error {
|
||||
tbt, err := transmissionrpc.New(client.Host, client.Username, client.Password, &transmissionrpc.AdvancedConfig{
|
||||
HTTPS: client.TLS,
|
||||
Port: uint16(client.Port),
|
||||
scheme := "http"
|
||||
if client.TLS {
|
||||
scheme = "https"
|
||||
}
|
||||
|
||||
u, err := url.Parse(fmt.Sprintf("%s://%s:%d/transmission/rpc", scheme, client.Host, client.Port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tbt, err := transmission.New(u, &transmission.Config{
|
||||
UserAgent: "autobrr",
|
||||
Username: client.Username,
|
||||
Password: client.Password,
|
||||
TLSSkipVerify: client.TLSSkipVerify,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error logging into client: %v", client.Host)
|
||||
|
|
58
pkg/transmission/transmission.go
Normal file
58
pkg/transmission/transmission.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
package transmission
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/hekmon/transmissionrpc/v3"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
UserAgent string
|
||||
CustomClient *http.Client
|
||||
Username string
|
||||
Password string
|
||||
TLSSkipVerify bool
|
||||
Timeout int
|
||||
}
|
||||
|
||||
func New(endpoint *url.URL, cfg *Config) (*transmissionrpc.Client, error) {
|
||||
ct := &customTransport{
|
||||
Username: cfg.Username,
|
||||
Password: cfg.Password,
|
||||
TLSSkipVerify: cfg.TLSSkipVerify,
|
||||
}
|
||||
|
||||
extra := &transmissionrpc.Config{
|
||||
CustomClient: &http.Client{
|
||||
Transport: ct,
|
||||
Timeout: time.Second * 60,
|
||||
},
|
||||
UserAgent: cfg.UserAgent,
|
||||
}
|
||||
|
||||
return transmissionrpc.New(endpoint, extra)
|
||||
}
|
||||
|
||||
type customTransport struct {
|
||||
Username string
|
||||
Password string
|
||||
TLSSkipVerify bool
|
||||
}
|
||||
|
||||
func (t *customTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
dt := http.DefaultTransport.(*http.Transport).Clone()
|
||||
if t.TLSSkipVerify {
|
||||
dt.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
}
|
||||
|
||||
r := req.Clone(req.Context())
|
||||
|
||||
if t.Username != "" && t.Password != "" {
|
||||
r.SetBasicAuth(t.Username, t.Password)
|
||||
}
|
||||
|
||||
return dt.RoundTrip(r)
|
||||
}
|
|
@ -493,6 +493,38 @@ const TypeForm = ({ action, idx, clients }: TypeFormProps) => {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<CollapsableSection title="Rules" subtitle="client options">
|
||||
<div className="col-span-12">
|
||||
<div className="mt-6 grid grid-cols-12 gap-6">
|
||||
<NumberField
|
||||
name={`actions.${idx}.limit_download_speed`}
|
||||
label="Limit download speed (KiB/s)"
|
||||
placeholder="Takes any number (0 is no limit)"
|
||||
/>
|
||||
<NumberField
|
||||
name={`actions.${idx}.limit_upload_speed`}
|
||||
label="Limit upload speed (KiB/s)"
|
||||
placeholder="Takes any number (0 is no limit)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 grid grid-cols-12 gap-6">
|
||||
<NumberField
|
||||
name={`actions.${idx}.limit_ratio`}
|
||||
label="Ratio limit"
|
||||
placeholder="Takes any number (0 is no limit)"
|
||||
step={0.25}
|
||||
isDecimal
|
||||
/>
|
||||
<NumberField
|
||||
name={`actions.${idx}.limit_seed_time`}
|
||||
label="Seed time limit (minutes)"
|
||||
placeholder="Takes any number (0 is no limit)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CollapsableSection>
|
||||
|
||||
<CollapsableSection title="Re-announce" subtitle="Re-announce options">
|
||||
<div className="col-span-12">
|
||||
<div className="mt-6 grid grid-cols-12 gap-6">
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue