Skip to content

Commit

Permalink
refactor and remove qbittorrent wrapper
Browse files Browse the repository at this point in the history
  • Loading branch information
sonalys committed Feb 9, 2024
1 parent ca39ef7 commit 3e919e8
Show file tree
Hide file tree
Showing 13 changed files with 90 additions and 252 deletions.
2 changes: 1 addition & 1 deletion cmd/service/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import (
"github.com/sonalys/animeman/integrations/anilist"
"github.com/sonalys/animeman/integrations/myanimelist"
"github.com/sonalys/animeman/integrations/nyaa"
"github.com/sonalys/animeman/integrations/qbittorrent"
"github.com/sonalys/animeman/internal/configs"
"github.com/sonalys/animeman/internal/discovery"
"github.com/sonalys/animeman/internal/qbittorrent"
"github.com/sonalys/animeman/internal/utils"
)

Expand Down
9 changes: 8 additions & 1 deletion integrations/qbittorrent/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import (
"errors"
"fmt"
"net/http"
"net/http/cookiejar"
"syscall"
"time"

"github.com/rs/zerolog/log"
"github.com/sonalys/animeman/internal/utils"
)

type (
Expand All @@ -18,7 +21,11 @@ type (
}
)

func New(ctx context.Context, client *http.Client, host, username, password string) *API {
func New(ctx context.Context, host, username, password string) *API {
client := &http.Client{
Timeout: 3 * time.Second,
Jar: utils.Must(cookiejar.New(nil)),
}
api := &API{
host: fmt.Sprintf("%s/api/v2", host),
username: username,
Expand Down
75 changes: 21 additions & 54 deletions integrations/qbittorrent/torrent_add.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,68 +8,35 @@ import (
"mime/multipart"
"net/http"
"strings"
)

type ArgAddTorrent interface {
ApplyAddTorrent(*multipart.Writer)
}

func (t TorrentURL) ApplyAddTorrent(w *multipart.Writer) {
field, err := w.CreateFormField("urls")
if err != nil {
panic(err)
}
if _, err := io.WriteString(field, strings.Join(t, "\n")); err != nil {
panic(err)
}
}

func (t Tags) ApplyAddTorrent(w *multipart.Writer) {
field, err := w.CreateFormField("tags")
if err != nil {
panic(err)
}
if _, err := io.WriteString(field, strings.Join(t, ",")); err != nil {
panic(err)
}
}

func (c Category) ApplyAddTorrent(w *multipart.Writer) {
field, err := w.CreateFormField("category")
if err != nil {
panic(err)
}
io.WriteString(field, string(c))
}

func (p Paused) ApplyAddTorrent(w *multipart.Writer) {
field, err := w.CreateFormField("paused")
if err != nil {
panic(err)
}
io.WriteString(field, fmt.Sprint(p))
}
"github.com/sonalys/animeman/internal/utils"
"github.com/sonalys/animeman/pkg/v1/torrentclient"
)

func (s SavePath) ApplyAddTorrent(w *multipart.Writer) {
field, err := w.CreateFormField("savepath")
if err != nil {
panic(err)
}
io.WriteString(field, string(s))
func digestArg(arg *torrentclient.AddTorrentConfig) (io.Reader, string) {
var b bytes.Buffer
w := multipart.NewWriter(&b)
field := utils.Must(w.CreateFormField("urls"))
utils.Must(io.WriteString(field, strings.Join(arg.URLs, "\n")))
field = utils.Must(w.CreateFormField("tags"))
utils.Must(io.WriteString(field, strings.Join(arg.Tags, ",")))
field = utils.Must(w.CreateFormField("category"))
utils.Must(io.WriteString(field, fmt.Sprint(arg.Category)))
field = utils.Must(w.CreateFormField("paused"))
utils.Must(io.WriteString(field, fmt.Sprint(arg.Paused)))
field = utils.Must(w.CreateFormField("savepath"))
utils.Must(io.WriteString(field, fmt.Sprint(arg.SavePath)))
return &b, w.FormDataContentType()
}

func (api *API) AddTorrent(ctx context.Context, args ...ArgAddTorrent) error {
func (api *API) AddTorrent(ctx context.Context, arg *torrentclient.AddTorrentConfig) error {
var path = api.host + "/torrents/add"
var b bytes.Buffer
formdata := multipart.NewWriter(&b)
for _, f := range args {
f.ApplyAddTorrent(formdata)
}
req, err := http.NewRequest(http.MethodPost, path, &b)
r, contentType := digestArg(arg)
req, err := http.NewRequest(http.MethodPost, path, r)
if err != nil {
return fmt.Errorf("creating request failed: %w", err)
}
req.Header.Set("Content-Type", formdata.FormDataContentType())
req.Header.Set("Content-Type", contentType)
resp, err := api.Do(ctx, req)
if err != nil {
return fmt.Errorf("post torrents/add failed: %w", err)
Expand Down
14 changes: 2 additions & 12 deletions integrations/qbittorrent/torrent_add_tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,11 @@ import (
"strings"
)

type AddTorrentTagsArg interface {
ApplyAddTorrentTags(url.Values)
}

func (t Tags) ApplyAddTorrentTags(v url.Values) {
v.Set("tags", strings.Join(t, ","))
}

func (api *API) AddTorrentTags(ctx context.Context, hashes []string, args ...AddTorrentTagsArg) error {
func (api *API) AddTorrentTags(ctx context.Context, hashes []string, tags []string) error {
var path = api.host + "/torrents/addTags"
values := url.Values{
"hashes": []string{strings.Join(hashes, "|")},
}
for _, f := range args {
f.ApplyAddTorrentTags(values)
"tags": []string{strings.Join(tags, ",")},
}
req, err := http.NewRequest(http.MethodPost, path, strings.NewReader(values.Encode()))
if err != nil {
Expand Down
39 changes: 24 additions & 15 deletions integrations/qbittorrent/torrent_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,40 @@ import (
"net/url"

"github.com/sonalys/animeman/internal/utils"
"github.com/sonalys/animeman/pkg/v1/torrentclient"
)

type ArgListTorrent interface {
ApplyListTorrent(url.Values)
}

func (t Tag) ApplyListTorrent(v url.Values) {
v.Add("tag", string(t))
func convertTorrent(in []Torrent) []torrentclient.Torrent {
out := make([]torrentclient.Torrent, 0, len(in))
for i := range in {
out = append(out, torrentclient.Torrent{
Name: in[i].Name,
Category: in[i].Category,
Hash: in[i].Hash,
Tags: in[i].GetTags(),
})
}
return out
}

func (c Category) ApplyListTorrent(v url.Values) {
v.Add("category", string(c))
func digestListTorrentArg(arg *torrentclient.ListTorrentConfig) url.Values {
v := url.Values{}
if arg.Category != "" {
v.Set("category", arg.Category)
}
if arg.Tag != "" {
v.Set("tag", arg.Tag)
}
return v
}

func (api *API) List(ctx context.Context, args ...ArgListTorrent) ([]Torrent, error) {
func (api *API) List(ctx context.Context, arg *torrentclient.ListTorrentConfig) ([]torrentclient.Torrent, error) {
var path = api.host + "/torrents/info"
req, err := http.NewRequest(http.MethodGet, path, nil)
if err != nil {
return nil, fmt.Errorf("list request failed: %w", err)
}
values := url.Values{}
for _, f := range args {
f.ApplyListTorrent(values)
}
req.URL.RawQuery = values.Encode()
req.URL.RawQuery = digestListTorrentArg(arg).Encode()
resp, err := api.Do(ctx, req)
if err != nil {
return nil, fmt.Errorf("could not list torrents: %w", err)
Expand All @@ -44,5 +53,5 @@ func (api *API) List(ctx context.Context, args ...ArgListTorrent) ([]Torrent, er
if err := json.Unmarshal(rawBody, &respBody); err != nil {
return nil, fmt.Errorf("could not read response: %s: %w", string(rawBody), err)
}
return respBody, nil
return convertTorrent(respBody), nil
}
6 changes: 2 additions & 4 deletions integrations/qbittorrent/torrent_remove_tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,11 @@ import (
"strings"
)

func (api *API) RemoveTorrentTags(ctx context.Context, hashes []string, args ...AddTorrentTagsArg) error {
func (api *API) RemoveTorrentTags(ctx context.Context, hashes []string, tags []string) error {
var path = api.host + "/torrents/removeTags"
values := url.Values{
"hashes": []string{strings.Join(hashes, "|")},
}
for _, f := range args {
f.ApplyAddTorrentTags(values)
"tags": []string{strings.Join(tags, ",")},
}
req, err := http.NewRequest(http.MethodPost, path, strings.NewReader(values.Encode()))
if err != nil {
Expand Down
9 changes: 0 additions & 9 deletions integrations/qbittorrent/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,13 @@ import (
"strings"
)

var ErrUnauthorized = fmt.Errorf("unauthorized")

type (
Torrent struct {
Name string `json:"name"`
Category string `json:"category"`
Hash string `json:"hash"`
Tags string `json:"tags"`
}

TorrentURL []string
Tag string
Tags []string
SavePath string
Category string
Paused bool
)

func NewErrConnection(err error) error {
Expand Down
6 changes: 3 additions & 3 deletions internal/discovery/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ type (
}

TorrentClient interface {
List(ctx context.Context, args ...torrentclient.ArgListTorrent) ([]torrentclient.Torrent, error)
AddTorrent(ctx context.Context, args ...torrentclient.ArgAddTorrent) error
AddTorrentTags(ctx context.Context, hashes []string, args ...torrentclient.AddTorrentTagsArg) error
List(ctx context.Context, arg *torrentclient.ListTorrentConfig) ([]torrentclient.Torrent, error)
AddTorrent(ctx context.Context, arg *torrentclient.AddTorrentConfig) error
AddTorrentTags(ctx context.Context, hashes []string, tags []string) error
}

Dependencies struct {
Expand Down
4 changes: 3 additions & 1 deletion internal/discovery/tag_update.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import (
// This function exists for when you already have a collection of Anime categorized torrents.
// This function will tag all entries from the configured category for smart episode detection and filtering.
func (c *Controller) UpdateExistingTorrentsTags(ctx context.Context) error {
torrents, err := c.dep.TorrentClient.List(ctx, torrentclient.Category(c.dep.Config.Category))
torrents, err := c.dep.TorrentClient.List(ctx, &torrentclient.ListTorrentConfig{
Category: c.dep.Config.Category,
})
if err != nil {
return fmt.Errorf("listing: %w", err)
}
Expand Down
23 changes: 14 additions & 9 deletions internal/discovery/torrent.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,31 +89,36 @@ func (c *Controller) TagGetLatest(ctx context.Context, entry animelist.Entry) (s
title := parser.TitleParse(entry.Titles[i])
// we should consider both title and titleEng, because your anime list has different titles available,
// some torrent sources will use one, some will use the other, so to avoid duplication we check for both.
torrents1, err := c.dep.TorrentClient.List(ctx, torrentclient.Tag(title.TagBuildSeries()))
resp, err := c.dep.TorrentClient.List(ctx, &torrentclient.ListTorrentConfig{
Tag: title.TagBuildSeries(),
})
if err != nil {
return "", fmt.Errorf("listing torrents: %w", err)
}
torrents = append(torrents, torrents1...)
torrents = append(torrents, resp...)
}
return tagGetLatest(torrents...), nil
}

// torrentGetPath returns a torrent path, creating a show folder if configured.
func (c *Controller) torrentGetPath(title string) (path torrentclient.SavePath) {
func (c *Controller) torrentGetPath(title string) (path string) {
if c.dep.Config.CreateShowFolder {
return torrentclient.SavePath(fmt.Sprintf("%s/%s", c.dep.Config.DownloadPath, title))
return fmt.Sprintf("%s/%s", c.dep.Config.DownloadPath, title)
}
return torrentclient.SavePath(c.dep.Config.DownloadPath)
return c.dep.Config.DownloadPath
}

// DigestNyaaTorrent receives an anime list entry and a downloadable torrent.
// It will configure all necessary metadata and send it to your torrent client.
func (c *Controller) DigestNyaaTorrent(ctx context.Context, entry animelist.Entry, nyaaEntry ParsedNyaa) error {
tags := nyaaEntry.meta.TagsBuildTorrent()
savePath := c.torrentGetPath(entry.Titles[0])
torrentURL := torrentclient.TorrentURL{nyaaEntry.entry.Link}
category := torrentclient.Category(c.dep.Config.Category)
if err := c.dep.TorrentClient.AddTorrent(ctx, tags, savePath, torrentURL, category); err != nil {
tags := nyaaEntry.meta.TagsBuildTorrent()
if err := c.dep.TorrentClient.AddTorrent(ctx, &torrentclient.AddTorrentConfig{
Tags: tags,
URLs: []string{nyaaEntry.entry.Link},
Category: c.dep.Config.Category,
SavePath: savePath,
}); err != nil {
return fmt.Errorf("adding torrents: %w", err)
}
log.Info().Str("savePath", string(savePath)).Strs("tag", tags).Msgf("torrent added")
Expand Down
6 changes: 2 additions & 4 deletions internal/parser/title.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import (
"fmt"
"regexp"
"strings"

"github.com/sonalys/animeman/integrations/qbittorrent"
)

// Metadata is a digested metadata struct parsed from titles.
Expand Down Expand Up @@ -82,8 +80,8 @@ func (t Metadata) TagBuildSeries() string {
}

// TagsBuildTorrent builds all tags Animeman needs from your torrent client.
func (t Metadata) TagsBuildTorrent() qbittorrent.Tags {
tags := qbittorrent.Tags{t.TagBuildSeries(), t.TagBuildSeasonEpisode()}
func (t Metadata) TagsBuildTorrent() []string {
tags := []string{t.TagBuildSeries(), t.TagBuildSeasonEpisode()}
if t.IsMultiEpisode {
tags = append(tags, t.TagBuildBatch())
}
Expand Down
Loading

0 comments on commit 3e919e8

Please sign in to comment.