Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

qbittorrent and transmission as clients of bitmagnet #301

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ tasks:

gen-classifier-schema:
cmds:
- go run . classifier schema --format json > ./bitmagnet.io/schemas/classifier-0.1.json
- go run . classifier schema --format json > ./bitmagnet.io/schemas/classifier-0.1

gen-webui-graphql:
dir: ./webui
Expand All @@ -55,6 +55,25 @@ tasks:
cmds:
- npm run i18n:extract

i18n-translate:
cmds:
- for:
[
"ar",
"de",
"es",
"fr",
"hi",
"ja",
"nl",
"pt",
"ru",
"tr",
"uk",
"zh",
]
cmd: npx i18n-auto-translation -k $API_KEY -d webui/src/app/i18n/translations -t {{ .ITEM }}

lint:
cmds:
# Removing golang-ci lint as the Nix package is currently broken
Expand Down
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/anacrolix/dht/v2 v2.22.0
github.com/anacrolix/missinggo/v2 v2.8.0
github.com/anacrolix/torrent v1.57.1
github.com/autobrr/go-qbittorrent v1.9.0
github.com/bits-and-blooms/bloom/v3 v3.7.0
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb
github.com/frankban/quicktest v1.14.6
Expand All @@ -21,6 +22,7 @@ require (
github.com/grafana/pyroscope-go/godeltaprof v0.1.8
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/hedhyw/rex v1.0.0
github.com/hekmon/transmissionrpc/v3 v3.0.0
github.com/iancoleman/strcase v0.3.0
github.com/jackc/pgx/v5 v5.7.1
github.com/jedib0t/go-pretty/v6 v6.6.0
Expand Down Expand Up @@ -69,6 +71,7 @@ require (
github.com/anacrolix/stm v0.5.0 // indirect
github.com/anacrolix/sync v0.5.3 // indirect
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
github.com/avast/retry-go v3.0.0+incompatible // indirect
github.com/benbjohnson/immutable v0.4.3 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.14.3 // indirect
Expand All @@ -94,7 +97,9 @@ require (
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hekmon/cunits/v2 v2.1.0 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
Expand Down
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmO
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
github.com/autobrr/go-qbittorrent v1.9.0 h1:HaLueJ99D3G1cQ2r5ADVbtfwyEhekt2eQoEZ7yhAwYs=
github.com/autobrr/go-qbittorrent v1.9.0/go.mod h1:z88B3+O/1/3doQABErvIOOxE4hjpmIpulu6XzDG/q78=
github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0=
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
github.com/benbjohnson/immutable v0.2.0/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI=
github.com/benbjohnson/immutable v0.4.3 h1:GYHcksoJ9K6HyAUpGxwZURrbTkXA0Dh4otXGqbhdrjA=
github.com/benbjohnson/immutable v0.4.3/go.mod h1:qJIKKSmdqz1tVzNtst1DZzvaqOU1onk1rc03IeM3Owk=
Expand Down Expand Up @@ -244,13 +248,19 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grafana/pyroscope-go/godeltaprof v0.1.8 h1:iwOtYXeeVSAeYefJNaxDytgjKtUuKQbJqgAIjlnicKg=
github.com/grafana/pyroscope-go/godeltaprof v0.1.8/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
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/hedhyw/rex v1.0.0 h1:f7QNmVMBsmuEop/wg85byq6YbOHQr0q3Y78Q3IwKp+I=
github.com/hedhyw/rex v1.0.0/go.mod h1:n9CYz3ztkAp56mrMXw65Q3LeXCO2AZSUvO7VMHsVMF8=
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/v3 v3.0.0 h1:0Fb11qE0IBh4V4GlOwHNYpqpjcYDp5GouolwrpmcUDQ=
github.com/hekmon/transmissionrpc/v3 v3.0.0/go.mod h1:38SlNhFzinVUuY87wGj3acOmRxeYZAZfrj6Re7UgCDg=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
Expand Down
5 changes: 5 additions & 0 deletions graphql/mutations/Download.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mutation Download($infoHashes: [Hash20!]!) {
downloadclient {
download(infoHashes: $infoHashes)
}
}
5 changes: 5 additions & 0 deletions graphql/queries/DownloadClientEnabled.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
query DownloadClientEnabled {
downloadClient {
enabled
}
}
5 changes: 5 additions & 0 deletions graphql/schema/enums.graphqls
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,8 @@ enum QueueJobsOrderByField {
ran_at
priority
}

enum ClientID {
Transmission
QBittorrent
}
5 changes: 5 additions & 0 deletions graphql/schema/mutation.graphqls
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
type Mutation {
torrent: TorrentMutation!
queue: QueueMutation!
downloadclient: DownloadClientMutation!
}

type TorrentMutation {
Expand All @@ -9,3 +10,7 @@ type TorrentMutation {
setTags(infoHashes: [Hash20!]!, tagNames: [String!]!): Void
deleteTags(infoHashes: [Hash20!], tagNames: [String!]): Void
}

type DownloadClientMutation {
download(infoHashes: [Hash20!]): Void
}
5 changes: 5 additions & 0 deletions graphql/schema/query.graphqls
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ type Query {
queue: QueueQuery!
torrent: TorrentQuery!
torrentContent: TorrentContentQuery!
downloadClient: DownloadClientConfigQuery!
}

type TorrentQuery {
Expand Down Expand Up @@ -67,3 +68,7 @@ type HealthQuery {
status: HealthStatus!
checks: [HealthCheck!]!
}

type DownloadClientConfigQuery {
enabled: Boolean!
}
2 changes: 2 additions & 0 deletions internal/app/appfx/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/bitmagnet-io/bitmagnet/internal/boilerplate/app/boilerplateappfx"
"github.com/bitmagnet-io/bitmagnet/internal/boilerplate/httpserver/httpserverfx"
"github.com/bitmagnet-io/bitmagnet/internal/classifier/classifierfx"
"github.com/bitmagnet-io/bitmagnet/internal/client/clientfx"
"github.com/bitmagnet-io/bitmagnet/internal/database/databasefx"
"github.com/bitmagnet-io/bitmagnet/internal/database/migrations"
"github.com/bitmagnet-io/bitmagnet/internal/dhtcrawler/dhtcrawlerfx"
Expand Down Expand Up @@ -48,6 +49,7 @@ func New() fx.Option {
torznabfx.New(),
versionfx.New(),
classifierfx.New(),
clientfx.New(),
// cli commands:
fx.Provide(
classifiercmd.New,
Expand Down
76 changes: 76 additions & 0 deletions internal/client/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package client

import (
"context"
"fmt"

q "github.com/bitmagnet-io/bitmagnet/internal/database/query"
"github.com/bitmagnet-io/bitmagnet/internal/database/search"
"github.com/bitmagnet-io/bitmagnet/internal/gql/gqlmodel/gen"
"github.com/bitmagnet-io/bitmagnet/internal/model"
"github.com/bitmagnet-io/bitmagnet/internal/protocol"
)

type AddInfoHashesRequest struct {
ClientID gen.ClientID
InfoHashes []protocol.ID
}

type content = []search.TorrentContentResultItem

type clientWorker interface {
AddInfoHashes(ctx context.Context, req AddInfoHashesRequest) error
download(ctx context.Context, content *content) error
}

type commonClient struct {
config *Config
search search.Search
client clientWorker
}

func New(cfg *Config, search search.Search) commonClient {
cc := commonClient{
config: cfg,
search: search,
}

return cc

}

func (c commonClient) downloadCategory(contentType model.ContentType) string {
category := c.config.Categories[contentType]
if category == "" {
category = c.config.DefaultCategory
}
return category
}

func (c commonClient) AddInfoHashes(ctx context.Context, req AddInfoHashesRequest) error {

switch c.config.DownloadClient {
case gen.ClientIDTransmission:
c.client = transmissionClient{commonClient: c}
case gen.ClientIDQBittorrent:
c.client = qBitClient{commonClient: c}
default:
return fmt.Errorf("not implemented %s", c.config.DownloadClient)
}

options := []q.Option{
q.Where(
search.TorrentContentInfoHashCriteria(req.InfoHashes...),
),
search.TorrentContentCoreJoins(),
search.HydrateTorrentContentContent(),
search.HydrateTorrentContentTorrent(),
q.Limit(uint(len(req.InfoHashes))),
}
sr, err := c.search.TorrentContent(ctx, options...)
if err != nil {
return err
}

return c.client.download(ctx, &sr.Items)
}
14 changes: 14 additions & 0 deletions internal/client/clientfx/module.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package clientfx

import (
"github.com/bitmagnet-io/bitmagnet/internal/boilerplate/config/configfx"
"github.com/bitmagnet-io/bitmagnet/internal/client"
"go.uber.org/fx"
)

func New() fx.Option {
return fx.Module(
"client",
configfx.NewConfigModule[client.Config]("client", client.NewDefaultConfig()),
)
}
46 changes: 46 additions & 0 deletions internal/client/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package client

import (
"github.com/bitmagnet-io/bitmagnet/internal/gql/gqlmodel/gen"
"github.com/bitmagnet-io/bitmagnet/internal/model"
)

type DownloadClient struct {
Host string
Port string
Username string
Password string
}

type Config struct {
Enabled bool
Transmission DownloadClient
Qbittorrent DownloadClient
DownloadClient gen.ClientID
DefaultCategory string
Categories map[model.ContentType]string
}

func NewDefaultConfig() Config {
cfg := Config{
Enabled: false,
Transmission: DownloadClient{
Host: "localhost",
Port: "9091",
},
Qbittorrent: DownloadClient{
Host: "localhost",
Port: "8080",
Username: "required",
Password: "required",
},
DownloadClient: gen.ClientIDQBittorrent,
DefaultCategory: "prowlarr",
}
cat := make(map[model.ContentType]string)
cat[model.ContentTypeTvShow] = "sonarr"
cat[model.ContentTypeMovie] = "radarr"
cfg.Categories = cat

return cfg
}
51 changes: 51 additions & 0 deletions internal/client/qbittorrent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package client

import (
"context"
"fmt"

"github.com/autobrr/go-qbittorrent"
)

type qBitClient struct {
commonClient
}

func (c qBitClient) download(ctx context.Context, content *content) error {

qb := qbittorrent.NewClient(qbittorrent.Config{
Host: fmt.Sprintf("http://%v:%v/", c.config.Qbittorrent.Host, c.config.Qbittorrent.Port),
Username: c.config.Qbittorrent.Username,
Password: c.config.Qbittorrent.Password,
Timeout: 1,
})

err := qb.LoginCtx(ctx)
if err != nil {
return err
}

pref, err := qb.GetAppPreferencesCtx(ctx)
if err != nil {
return err
}

for _, item := range *content {
category := c.downloadCategory(item.Content.Type)

err = qb.AddTorrentFromUrlCtx(
ctx,
item.Torrent.MagnetUri(),
map[string]string{
"savepath": fmt.Sprintf("%v/%v", pref.SavePath, category),
"category": category,
},
)
if err != nil {
return err
}
}

return nil

}
Loading