From 3656a68a272e0fe0fd5ee4b85c8a9802107d37fd Mon Sep 17 00:00:00 2001 From: Daniel Mason Date: Fri, 11 Jul 2025 10:12:43 +1200 Subject: [PATCH] Add support for using freeleech tokens if available --- cmd/autobrrctl/main.go | 2 + internal/database/filter.go | 18 +++- internal/database/filter_test.go | 1 + internal/database/postgres_migrate.go | 3 + internal/database/release_test.go | 1 + internal/database/sqlite_migrate.go | 96 +++++++++++++++++++ internal/domain/filter.go | 2 + internal/domain/filter_test.go | 1 + internal/domain/macros.go | 1 + internal/domain/release_download_test.go | 1 + internal/filter/service.go | 6 +- internal/indexer/api.go | 8 +- internal/indexer/definitions/orpheus.yaml | 2 +- internal/mock/indexer_api.go | 4 +- pkg/btn/btn.go | 4 +- pkg/btn/client.go | 2 +- pkg/ggn/ggn.go | 7 +- pkg/ggn/ggn_test.go | 6 +- pkg/ops/ops.go | 7 +- pkg/ops/ops_test.go | 18 +++- pkg/ptp/ptp.go | 7 +- pkg/ptp/ptp_test.go | 6 +- pkg/red/red.go | 7 +- pkg/red/red_test.go | 8 +- web/src/screens/filters/Details.tsx | 1 + web/src/screens/filters/_const.ts | 1 + web/src/screens/filters/sections/Advanced.tsx | 17 +++- web/src/types/Filter.d.ts | 1 + 28 files changed, 199 insertions(+), 39 deletions(-) diff --git a/cmd/autobrrctl/main.go b/cmd/autobrrctl/main.go index c6aa987..71b902b 100644 --- a/cmd/autobrrctl/main.go +++ b/cmd/autobrrctl/main.go @@ -447,6 +447,7 @@ type FilterExport struct { // Media-specific fields Freeleech bool `json:"freeleech,omitempty"` FreeleechPercent string `json:"freeleech_percent,omitempty"` + FreeleechToken bool `json:"freeleech_token,omitempty"` Shows string `json:"shows,omitempty"` Seasons string `json:"seasons,omitempty"` Episodes string `json:"episodes,omitempty"` @@ -543,6 +544,7 @@ func prepareFilterForExport(filter domain.Filter, externalFilters []domain.Filte AnnounceTypes: filter.AnnounceTypes, Freeleech: filter.Freeleech, FreeleechPercent: filter.FreeleechPercent, + FreeleechToken: filter.FreeleechToken, Shows: filter.Shows, Seasons: filter.Seasons, Episodes: filter.Episodes, diff --git a/internal/database/filter.go b/internal/database/filter.go index 4fdda0d..d1e4d1c 100644 --- a/internal/database/filter.go +++ b/internal/database/filter.go @@ -210,6 +210,7 @@ func (r *FilterRepo) FindByID(ctx context.Context, filterID int) (*domain.Filter "f.scene", "f.freeleech", "f.freeleech_percent", + "f.freeleech_token", "f.smart_episode", "f.shows", "f.seasons", @@ -278,7 +279,7 @@ func (r *FilterRepo) FindByID(ctx context.Context, filterID int) (*domain.Filter // filter var minSize, maxSize, maxDownloadsUnit, matchReleases, exceptReleases, matchReleaseGroups, exceptReleaseGroups, matchReleaseTags, exceptReleaseTags, matchDescription, exceptDescription, freeleechPercent, shows, seasons, episodes, years, months, days, artists, albums, matchCategories, exceptCategories, matchUploaders, exceptUploaders, matchRecordLabels, exceptRecordLabels, tags, exceptTags, tagsMatchLogic, exceptTagsMatchLogic sql.NullString - var useRegex, scene, freeleech, hasLog, hasCue, perfectFlac sql.NullBool + var useRegex, scene, freeleech, freeleechToken, hasLog, hasCue, perfectFlac sql.NullBool var delay, maxDownloads, logScore sql.NullInt32 var releaseProfileDuplicateId sql.NullInt64 @@ -307,6 +308,7 @@ func (r *FilterRepo) FindByID(ctx context.Context, filterID int) (*domain.Filter &scene, &freeleech, &freeleechPercent, + &freeleechToken, &f.SmartEpisode, &shows, &seasons, @@ -375,7 +377,9 @@ func (r *FilterRepo) FindByID(ctx context.Context, filterID int) (*domain.Filter f.ExceptReleaseTags = exceptReleaseTags.String f.MatchDescription = matchDescription.String f.ExceptDescription = exceptDescription.String + f.Freeleech = freeleech.Bool f.FreeleechPercent = freeleechPercent.String + f.FreeleechToken = freeleechToken.Bool f.Shows = shows.String f.Seasons = seasons.String f.Episodes = episodes.String @@ -400,7 +404,6 @@ func (r *FilterRepo) FindByID(ctx context.Context, filterID int) (*domain.Filter f.ExceptTagsMatchLogic = exceptTagsMatchLogic.String f.UseRegex = useRegex.Bool f.Scene = scene.Bool - f.Freeleech = freeleech.Bool f.ReleaseProfileDuplicateID = releaseProfileDuplicateId.Int64 return &f, nil @@ -438,6 +441,7 @@ func (r *FilterRepo) findByIndexerIdentifier(ctx context.Context, indexer string "f.scene", "f.freeleech", "f.freeleech_percent", + "f.freeleech_token", "f.smart_episode", "f.shows", "f.seasons", @@ -535,7 +539,7 @@ func (r *FilterRepo) findByIndexerIdentifier(ctx context.Context, indexer string var f domain.Filter var minSize, maxSize, maxDownloadsUnit, matchReleases, exceptReleases, matchReleaseGroups, exceptReleaseGroups, matchReleaseTags, exceptReleaseTags, matchDescription, exceptDescription, freeleechPercent, shows, seasons, episodes, years, months, days, artists, albums, matchCategories, exceptCategories, matchUploaders, exceptUploaders, matchRecordLabels, exceptRecordLabels, tags, exceptTags, tagsMatchLogic, exceptTagsMatchLogic sql.NullString - var useRegex, scene, freeleech, hasLog, hasCue, perfectFlac sql.NullBool + var useRegex, scene, freeleech, freeleechToken, hasLog, hasCue, perfectFlac sql.NullBool var delay, maxDownloads, logScore sql.NullInt32 var releaseProfileDuplicateID, rdpId sql.NullInt64 @@ -567,6 +571,7 @@ func (r *FilterRepo) findByIndexerIdentifier(ctx context.Context, indexer string &scene, &freeleech, &freeleechPercent, + &freeleechToken, &f.SmartEpisode, &shows, &seasons, @@ -680,6 +685,7 @@ func (r *FilterRepo) findByIndexerIdentifier(ctx context.Context, indexer string f.UseRegex = useRegex.Bool f.Scene = scene.Bool f.Freeleech = freeleech.Bool + f.FreeleechToken = freeleechToken.Bool f.ReleaseProfileDuplicateID = releaseProfileDuplicateID.Int64 f.Rejections = []string{} @@ -832,6 +838,7 @@ func (r *FilterRepo) Store(ctx context.Context, filter *domain.Filter) error { "scene", "freeleech", "freeleech_percent", + "freeleech_token", "smart_episode", "shows", "seasons", @@ -901,6 +908,7 @@ func (r *FilterRepo) Store(ctx context.Context, filter *domain.Filter) error { filter.Scene, filter.Freeleech, filter.FreeleechPercent, + filter.FreeleechToken, filter.SmartEpisode, filter.Shows, filter.Seasons, @@ -988,6 +996,7 @@ func (r *FilterRepo) Update(ctx context.Context, filter *domain.Filter) error { Set("scene", filter.Scene). Set("freeleech", filter.Freeleech). Set("freeleech_percent", filter.FreeleechPercent). + Set("freeleech_token", filter.FreeleechToken). Set("smart_episode", filter.SmartEpisode). Set("shows", filter.Shows). Set("seasons", filter.Seasons). @@ -1128,6 +1137,9 @@ func (r *FilterRepo) UpdatePartial(ctx context.Context, filter domain.FilterUpda if filter.FreeleechPercent != nil { q = q.Set("freeleech_percent", filter.FreeleechPercent) } + if filter.FreeleechToken != nil { + q = q.Set("freeleech_token", filter.FreeleechToken) + } if filter.SmartEpisode != nil { q = q.Set("smart_episode", filter.SmartEpisode) } diff --git a/internal/database/filter_test.go b/internal/database/filter_test.go index 333b01b..e9d9774 100644 --- a/internal/database/filter_test.go +++ b/internal/database/filter_test.go @@ -39,6 +39,7 @@ func getMockFilter() *domain.Filter { Bonus: nil, Freeleech: false, FreeleechPercent: "100%", + FreeleechToken: false, SmartEpisode: false, Shows: "Is It Wrong to Try to Pick Up Girls in a Dungeon?", Seasons: "4", diff --git a/internal/database/postgres_migrate.go b/internal/database/postgres_migrate.go index f7cfa60..f72cdeb 100644 --- a/internal/database/postgres_migrate.go +++ b/internal/database/postgres_migrate.go @@ -1373,5 +1373,8 @@ CREATE INDEX release_hybrid_index FROM irc_network WHERE server = 'irc.rocket-hd.cc' ); +`, + `ALTER TABLE filter + ADD freeleech_token BOOLEAN DEFAULT FALSE AFTER freeleech_percent; `, } diff --git a/internal/database/release_test.go b/internal/database/release_test.go index a1c504d..09948b9 100644 --- a/internal/database/release_test.go +++ b/internal/database/release_test.go @@ -742,6 +742,7 @@ func getMockFilterDuplicates() *domain.Filter { Bonus: nil, Freeleech: false, FreeleechPercent: "100%", + FreeleechToken: false, SmartEpisode: false, Shows: "Is It Wrong to Try to Pick Up Girls in a Dungeon?", Seasons: "4", diff --git a/internal/database/sqlite_migrate.go b/internal/database/sqlite_migrate.go index 1ea827b..a1e1bef 100644 --- a/internal/database/sqlite_migrate.go +++ b/internal/database/sqlite_migrate.go @@ -2018,5 +2018,101 @@ CREATE INDEX release_hybrid_index FROM irc_network WHERE server = 'irc.rocket-hd.cc' ); +`, ``, + `ALTER TABLE filter RENAME TO filter_add_col; +DROP INDEX filter_enabled_index; +DROP INDEX filter_priority_index; + +CREATE TABLE filter +( + id INTEGER PRIMARY KEY AUTOINCREMENT, + enabled BOOLEAN, + name TEXT NOT NULL, + min_size TEXT, + max_size TEXT, + delay INTEGER, + priority INTEGER DEFAULT 0 NOT NULL, + max_downloads INTEGER DEFAULT 0, + max_downloads_unit TEXT, + announce_types TEXT [] DEFAULT '{}', + match_releases TEXT, + except_releases TEXT, + use_regex BOOLEAN, + match_release_groups TEXT, + except_release_groups TEXT, + match_release_tags TEXT, + except_release_tags TEXT, + use_regex_release_tags BOOLEAN DEFAULT FALSE, + match_description TEXT, + except_description TEXT, + use_regex_description BOOLEAN DEFAULT FALSE, + scene BOOLEAN, + freeleech BOOLEAN, + freeleech_percent TEXT, + freeleech_token BOOLEAN DEFAULT FALSE, + smart_episode BOOLEAN DEFAULT FALSE, + shows TEXT, + seasons TEXT, + episodes TEXT, + resolutions TEXT [] DEFAULT '{}' NOT NULL, + codecs TEXT [] DEFAULT '{}' NOT NULL, + sources TEXT [] DEFAULT '{}' NOT NULL, + containers TEXT [] DEFAULT '{}' NOT NULL, + match_hdr TEXT [] DEFAULT '{}', + except_hdr TEXT [] DEFAULT '{}', + match_other TEXT [] DEFAULT '{}', + except_other TEXT [] DEFAULT '{}', + years TEXT, + months TEXT, + days TEXT, + artists TEXT, + albums TEXT, + release_types_match TEXT [] DEFAULT '{}', + release_types_ignore TEXT [] DEFAULT '{}', + formats TEXT [] DEFAULT '{}', + quality TEXT [] DEFAULT '{}', + media TEXT [] DEFAULT '{}', + log_score INTEGER, + has_log BOOLEAN, + has_cue BOOLEAN, + perfect_flac BOOLEAN, + match_categories TEXT, + except_categories TEXT, + match_uploaders TEXT, + except_uploaders TEXT, + match_record_labels TEXT, + except_record_labels TEXT, + match_language TEXT [] DEFAULT '{}', + except_language TEXT [] DEFAULT '{}', + tags TEXT, + except_tags TEXT, + tags_match_logic TEXT, + except_tags_match_logic TEXT, + origins TEXT [] DEFAULT '{}', + except_origins TEXT [] DEFAULT '{}', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + min_seeders INTEGER DEFAULT 0, + max_seeders INTEGER DEFAULT 0, + min_leechers INTEGER DEFAULT 0, + max_leechers INTEGER DEFAULT 0, + release_profile_duplicate_id INTEGER, + FOREIGN KEY (release_profile_duplicate_id) REFERENCES release_profile_duplicate(id) ON DELETE SET NULL +); + +CREATE INDEX filter_enabled_index + ON filter (enabled); +CREATE INDEX filter_priority_index + ON filter (priority); + +INSERT INTO filter SELECT id, enabled, name, min_size, max_size, delay, priority, max_downloads, max_downloads_unit, announce_types, + match_releases, except_releases, use_regex, match_release_groups, except_release_groups, match_release_tags, except_release_tags, + use_regex_release_tags, match_description, except_description, use_regex_description, scene, freeleech, freeleech_percent, + false, + smart_episode, shows, seasons, episodes, resolutions, codecs, sources, containers, match_hdr, except_hdr, match_other, except_other, + years, months, days, artists, albums, release_types_match, release_types_ignore, formats, quality, media, log_score, has_log, has_cue, perfect_flac, match_categories, except_categories, match_uploaders, except_uploaders, match_record_labels, except_record_labels, match_language, except_language, tags, except_tags, tags_match_logic, except_tags_match_logic, origins, except_origins, created_at, updated_at, min_seeders, max_seeders, min_leechers, max_leechers, release_profile_duplicate_id + FROM filter_add_col; + +DROP TABLE filter_add_col; `, } diff --git a/internal/domain/filter.go b/internal/domain/filter.go index af4e3cb..0a49040 100644 --- a/internal/domain/filter.go +++ b/internal/domain/filter.go @@ -114,6 +114,7 @@ type Filter struct { Bonus []string `json:"bonus,omitempty"` Freeleech bool `json:"freeleech,omitempty"` FreeleechPercent string `json:"freeleech_percent,omitempty"` + FreeleechToken bool `json:"freeleech_token,omitempty"` SmartEpisode bool `json:"smart_episode"` Shows string `json:"shows,omitempty"` Seasons string `json:"seasons,omitempty"` @@ -248,6 +249,7 @@ type FilterUpdate struct { Bonus *[]string `json:"bonus,omitempty"` Freeleech *bool `json:"freeleech,omitempty"` FreeleechPercent *string `json:"freeleech_percent,omitempty"` + FreeleechToken *bool `json:"freeleech_token,omitempty"` SmartEpisode *bool `json:"smart_episode,omitempty"` Shows *string `json:"shows,omitempty"` Seasons *string `json:"seasons,omitempty"` diff --git a/internal/domain/filter_test.go b/internal/domain/filter_test.go index f4e18ef..8849972 100644 --- a/internal/domain/filter_test.go +++ b/internal/domain/filter_test.go @@ -2087,6 +2087,7 @@ func TestFilter_CheckFilter1(t *testing.T) { ExceptOrigins: tt.fields.ExceptOrigins, Freeleech: tt.fields.Freeleech, FreeleechPercent: tt.fields.FreeleechPercent, + FreeleechToken: tt.fields.FreeleechToken, Shows: tt.fields.Shows, Seasons: tt.fields.Seasons, Episodes: tt.fields.Episodes, diff --git a/internal/domain/macros.go b/internal/domain/macros.go index 6a459ec..d27ee6e 100644 --- a/internal/domain/macros.go +++ b/internal/domain/macros.go @@ -47,6 +47,7 @@ type Macro struct { FilterName string Freeleech bool FreeleechPercent int + FreeleechToken bool Group string GroupID string HDR string diff --git a/internal/domain/release_download_test.go b/internal/domain/release_download_test.go index 59d17f2..5539b3c 100644 --- a/internal/domain/release_download_test.go +++ b/internal/domain/release_download_test.go @@ -282,6 +282,7 @@ func TestRelease_DownloadTorrentFile(t *testing.T) { ReleaseTags: tt.fields.ReleaseTags, Freeleech: tt.fields.Freeleech, FreeleechPercent: tt.fields.FreeleechPercent, + FreeleechToken: tt.fields.FreeleechToken, Bonus: tt.fields.Bonus, Uploader: tt.fields.Uploader, PreTime: tt.fields.PreTime, diff --git a/internal/filter/service.go b/internal/filter/service.go index b7de765..8e0498d 100644 --- a/internal/filter/service.go +++ b/internal/filter/service.go @@ -558,7 +558,7 @@ func (s *service) AdditionalSizeCheck(ctx context.Context, f *domain.Filter, rel if (release.Size == 0 && release.AdditionalSizeCheckRequired) || (release.Uploader == "" && release.AdditionalUploaderCheckRequired) || (release.RecordLabel == "" && release.AdditionalRecordLabelCheckRequired) { l.Trace().Msgf("(%s) preparing to check size via api", f.Name) - torrentInfo, err := s.apiService.GetTorrentByID(ctx, release.Indexer.Identifier, release.TorrentID) + torrentInfo, err := s.apiService.GetTorrentByID(ctx, release.Indexer.Identifier, release.TorrentID, f.FreeleechToken) if err != nil || torrentInfo == nil { l.Error().Err(err).Msgf("(%s) could not get torrent info from api: '%s' from: %s", f.Name, release.TorrentID, release.Indexer.Identifier) return false, err @@ -643,7 +643,7 @@ func (s *service) AdditionalUploaderCheck(ctx context.Context, f *domain.Filter, case "redacted", "ops", "mock": l.Trace().Msgf("(%s) preparing to check via api", f.Name) - torrentInfo, err := s.apiService.GetTorrentByID(ctx, release.Indexer.Identifier, release.TorrentID) + torrentInfo, err := s.apiService.GetTorrentByID(ctx, release.Indexer.Identifier, release.TorrentID, f.FreeleechToken) if err != nil || torrentInfo == nil { l.Error().Err(err).Msgf("(%s) could not get torrent info from api: '%s' from: %s", f.Name, release.TorrentID, release.Indexer.Identifier) return false, err @@ -722,7 +722,7 @@ func (s *service) AdditionalRecordLabelCheck(ctx context.Context, f *domain.Filt case "redacted", "ops", "mock": l.Trace().Msgf("(%s) preparing to check via api", f.Name) - torrentInfo, err := s.apiService.GetTorrentByID(ctx, release.Indexer.Identifier, release.TorrentID) + torrentInfo, err := s.apiService.GetTorrentByID(ctx, release.Indexer.Identifier, release.TorrentID, f.FreeleechToken) if err != nil || torrentInfo == nil { l.Error().Err(err).Msgf("(%s) could not get torrent info from api: '%s' from: %s", f.Name, release.TorrentID, release.Indexer.Identifier) return false, err diff --git a/internal/indexer/api.go b/internal/indexer/api.go index 956d3c1..e3a07cd 100644 --- a/internal/indexer/api.go +++ b/internal/indexer/api.go @@ -20,13 +20,13 @@ import ( type APIService interface { TestConnection(ctx context.Context, req domain.IndexerTestApiRequest) (bool, error) - GetTorrentByID(ctx context.Context, indexer string, torrentID string) (*domain.TorrentBasic, error) + GetTorrentByID(ctx context.Context, indexer string, torrentID string, freeleechToken bool) (*domain.TorrentBasic, error) AddClient(indexer string, settings map[string]string) error RemoveClient(indexer string) error } type apiClient interface { - GetTorrentByID(ctx context.Context, torrentID string) (*domain.TorrentBasic, error) + GetTorrentByID(ctx context.Context, torrentID string, freeleechToken bool) (*domain.TorrentBasic, error) TestAPI(ctx context.Context) (bool, error) } @@ -42,7 +42,7 @@ func NewAPIService(log logger.Logger) APIService { } } -func (s *apiService) GetTorrentByID(ctx context.Context, indexer string, torrentID string) (*domain.TorrentBasic, error) { +func (s *apiService) GetTorrentByID(ctx context.Context, indexer string, torrentID string, freeleechToken bool) (*domain.TorrentBasic, error) { client, err := s.getApiClient(indexer) if err != nil { s.log.Error().Stack().Err(err).Msgf("could not get api client for: %s", indexer) @@ -51,7 +51,7 @@ func (s *apiService) GetTorrentByID(ctx context.Context, indexer string, torrent s.log.Trace().Str("method", "GetTorrentByID").Msgf("%s fetching torrent from api...", indexer) - torrent, err := client.GetTorrentByID(ctx, torrentID) + torrent, err := client.GetTorrentByID(ctx, torrentID, freeleechToken) if err != nil { s.log.Error().Stack().Err(err).Msgf("could not get torrent: %s from: %s", torrentID, indexer) return nil, err diff --git a/internal/indexer/definitions/orpheus.yaml b/internal/indexer/definitions/orpheus.yaml index a2bf243..f83fd2d 100644 --- a/internal/indexer/definitions/orpheus.yaml +++ b/internal/indexer/definitions/orpheus.yaml @@ -120,4 +120,4 @@ irc: match: infourl: "/torrents.php?torrentid={{ .torrentId }}" - torrenturl: "/torrents.php?action=download&id={{ .torrentId }}&torrent_pass={{ .torrent_pass }}" + torrenturl: "/torrents.php?action=download&id={{ .torrentId }}&torrent_pass={{ .torrent_pass }}{{ .freeleech_token }}" diff --git a/internal/mock/indexer_api.go b/internal/mock/indexer_api.go index 7f83d55..899cf83 100644 --- a/internal/mock/indexer_api.go +++ b/internal/mock/indexer_api.go @@ -11,7 +11,7 @@ import ( ) type IndexerApiClient interface { - GetTorrentByID(ctx context.Context, torrentID string) (*domain.TorrentBasic, error) + GetTorrentByID(ctx context.Context, torrentID string, freeleechToken bool) (*domain.TorrentBasic, error) TestAPI(ctx context.Context) (bool, error) } @@ -41,7 +41,7 @@ func NewMockClient(apiKey string, opts ...OptFunc) IndexerApiClient { return c } -func (c *IndexerClient) GetTorrentByID(ctx context.Context, torrentID string) (*domain.TorrentBasic, error) { +func (c *IndexerClient) GetTorrentByID(ctx context.Context, torrentID string, freeleechToken bool) (*domain.TorrentBasic, error) { if torrentID == "" { return nil, errors.New("mock client: must have torrentID") } diff --git a/pkg/btn/btn.go b/pkg/btn/btn.go index f6b5bcd..16d41e3 100644 --- a/pkg/btn/btn.go +++ b/pkg/btn/btn.go @@ -32,12 +32,12 @@ func (c *Client) TestAPI(ctx context.Context) (bool, error) { return false, nil } -func (c *Client) GetTorrentByID(ctx context.Context, torrentID string) (*domain.TorrentBasic, error) { +func (c *Client) GetTorrentByID(ctx context.Context, torrentID string, freeleechToken bool) (*domain.TorrentBasic, error) { if torrentID == "" { return nil, errors.New("btn client: must have torrentID") } - res, err := c.rpcClient.CallCtx(ctx, "getTorrentById", [2]string{c.APIKey, torrentID}) + res, err := c.rpcClient.CallCtx(ctx, "getTorrentById", [3]interface{}{c.APIKey, torrentID, freeleechToken}) if err != nil { return nil, errors.Wrap(err, "call getTorrentById failed") } diff --git a/pkg/btn/client.go b/pkg/btn/client.go index 289ee0e..92f92ac 100644 --- a/pkg/btn/client.go +++ b/pkg/btn/client.go @@ -20,7 +20,7 @@ import ( const DefaultURL = "https://api.broadcasthe.net/" type ApiClient interface { - GetTorrentByID(ctx context.Context, torrentID string) (*domain.TorrentBasic, error) + GetTorrentByID(ctx context.Context, torrentID string, freeleechToken bool) (*domain.TorrentBasic, error) TestAPI(ctx context.Context) (bool, error) } diff --git a/pkg/ggn/ggn.go b/pkg/ggn/ggn.go index f486105..5d8b8d5 100644 --- a/pkg/ggn/ggn.go +++ b/pkg/ggn/ggn.go @@ -27,7 +27,7 @@ var ErrForbidden = errors.New("forbidden") var ErrTooManyRequests = errors.New("too many requests: rate-limit reached") type ApiClient interface { - GetTorrentByID(ctx context.Context, torrentID string) (*domain.TorrentBasic, error) + GetTorrentByID(ctx context.Context, torrentID string, freelechToken bool) (*domain.TorrentBasic, error) TestAPI(ctx context.Context) (bool, error) } @@ -243,7 +243,7 @@ func (c *Client) getJSON(ctx context.Context, params url.Values, data any) error return nil } -func (c *Client) GetTorrentByID(ctx context.Context, torrentID string) (*domain.TorrentBasic, error) { +func (c *Client) GetTorrentByID(ctx context.Context, torrentID string, freeleechToken bool) (*domain.TorrentBasic, error) { if torrentID == "" { return nil, errors.New("ggn client: must have torrentID") } @@ -253,6 +253,9 @@ func (c *Client) GetTorrentByID(ctx context.Context, torrentID string) (*domain. params := url.Values{} params.Add("request", "torrent") params.Add("id", torrentID) + if freeleechToken { + params.Add("usetoken", "1") + } err := c.getJSON(ctx, params, &response) if err != nil { diff --git a/pkg/ggn/ggn_test.go b/pkg/ggn/ggn_test.go index a4bbddd..ec6a7b8 100644 --- a/pkg/ggn/ggn_test.go +++ b/pkg/ggn/ggn_test.go @@ -86,7 +86,7 @@ func Test_client_GetTorrentByID(t *testing.T) { Url: ts.URL, APIKey: key, }, - args: args{torrentID: "422368"}, + args: args{torrentID: "422368", freeleechToken: false}, want: &domain.TorrentBasic{ Id: "422368", InfoHash: "78DA2811E6732012B8224198D4DC2FD49A5E950F", @@ -100,7 +100,7 @@ func Test_client_GetTorrentByID(t *testing.T) { Url: ts.URL, APIKey: key, }, - args: args{torrentID: "100002"}, + args: args{torrentID: "100002", freeleechToken: true}, want: nil, wantErr: false, }, @@ -109,7 +109,7 @@ func Test_client_GetTorrentByID(t *testing.T) { t.Run(tt.name, func(t *testing.T) { c := NewClient(tt.fields.APIKey, WithUrl(ts.URL)) - got, err := c.GetTorrentByID(context.Background(), tt.args.torrentID) + got, err := c.GetTorrentByID(context.Background(), tt.args.torrentID, tt.args.freeleechToken) if tt.wantErr && assert.Error(t, err) { t.Logf("got err: %v", err) assert.Equal(t, tt.wantErr, err) diff --git a/pkg/ops/ops.go b/pkg/ops/ops.go index 4eb9306..9584ffa 100644 --- a/pkg/ops/ops.go +++ b/pkg/ops/ops.go @@ -23,7 +23,7 @@ import ( const DefaultURL = "https://orpheus.network/ajax.php" type ApiClient interface { - GetTorrentByID(ctx context.Context, torrentID string) (*domain.TorrentBasic, error) + GetTorrentByID(ctx context.Context, torrentID string, freeleechToken bool) (*domain.TorrentBasic, error) TestAPI(ctx context.Context) (bool, error) } @@ -226,7 +226,7 @@ func (c *Client) getJSON(ctx context.Context, params url.Values, data any) error return nil } -func (c *Client) GetTorrentByID(ctx context.Context, torrentID string) (*domain.TorrentBasic, error) { +func (c *Client) GetTorrentByID(ctx context.Context, torrentID string, freeleechToken bool) (*domain.TorrentBasic, error) { if torrentID == "" { return nil, errors.New("orpheus client: must have torrentID") } @@ -236,6 +236,9 @@ func (c *Client) GetTorrentByID(ctx context.Context, torrentID string) (*domain. params := url.Values{} params.Add("action", "torrent") params.Add("id", torrentID) + if freeleechToken { + params.Add("usetoken", "1") + } err := c.getJSON(ctx, params, &response) if err != nil { diff --git a/pkg/ops/ops_test.go b/pkg/ops/ops_test.go index 5901e5d..15570bd 100644 --- a/pkg/ops/ops_test.go +++ b/pkg/ops/ops_test.go @@ -70,7 +70,7 @@ func TestOrpheusClient_GetTorrentByID(t *testing.T) { Url: ts.URL, APIKey: key, }, - args: args{torrentID: "2156788"}, + args: args{torrentID: "2156788", freeleechToken: false}, want: &domain.TorrentBasic{ Id: "2156788", InfoHash: "", @@ -86,7 +86,7 @@ func TestOrpheusClient_GetTorrentByID(t *testing.T) { Url: ts.URL, APIKey: key, }, - args: args{torrentID: "100002"}, + args: args{torrentID: "100002", freeleechToken: false}, want: nil, wantErr: "could not get torrent by id: 100002: status code: 400 status: failure error: bad id parameter", }, @@ -96,16 +96,26 @@ func TestOrpheusClient_GetTorrentByID(t *testing.T) { Url: ts.URL, APIKey: "", }, - args: args{torrentID: "100002"}, + args: args{torrentID: "100002", freeleechToken: false}, want: nil, wantErr: "could not get torrent by id: 100002: orpheus client missing API key!", }, + { + name: "get_by_id_1_freeleech_token", + fields: fields{ + Url: ts.URL, + APIKey: "", + }, + args: args{torrentID: "100002", freeleechToken: true}, + want: nil, + wantErr: "could not get torrent by id: 1. freeleech token not supported or no tokens available", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := NewClient(tt.fields.APIKey, WithUrl(ts.URL)) - got, err := c.GetTorrentByID(context.Background(), tt.args.torrentID) + got, err := c.GetTorrentByID(context.Background(), tt.args.torrentID, tt.args.freeleechToken) if tt.wantErr != "" && assert.Error(t, err) { assert.EqualErrorf(t, err, tt.wantErr, "Error should be: %v, got: %v", tt.wantErr, err) } diff --git a/pkg/ptp/ptp.go b/pkg/ptp/ptp.go index 63d2bdd..1d5c123 100644 --- a/pkg/ptp/ptp.go +++ b/pkg/ptp/ptp.go @@ -26,7 +26,7 @@ var ErrForbidden = errors.New("forbidden") var ErrTooManyRequests = errors.New("too many requests: rate-limit reached") type ApiClient interface { - GetTorrentByID(ctx context.Context, torrentID string) (*domain.TorrentBasic, error) + GetTorrentByID(ctx context.Context, torrentID string, freeleechToken bool) (*domain.TorrentBasic, error) TestAPI(ctx context.Context) (bool, error) } @@ -180,7 +180,7 @@ func (c *Client) getJSON(ctx context.Context, params url.Values, data any) error return nil } -func (c *Client) GetTorrentByID(ctx context.Context, torrentID string) (*domain.TorrentBasic, error) { +func (c *Client) GetTorrentByID(ctx context.Context, torrentID string, freeleechToken bool) (*domain.TorrentBasic, error) { if torrentID == "" { return nil, errors.New("ptp client: must have torrentID") } @@ -189,6 +189,9 @@ func (c *Client) GetTorrentByID(ctx context.Context, torrentID string) (*domain. params := url.Values{} params.Add("torrentid", torrentID) + if freeleechToken { + params.Add("usetoken", "1") + } err := c.getJSON(ctx, params, &response) if err != nil { diff --git a/pkg/ptp/ptp_test.go b/pkg/ptp/ptp_test.go index 7c28f52..d48fa63 100644 --- a/pkg/ptp/ptp_test.go +++ b/pkg/ptp/ptp_test.go @@ -72,7 +72,7 @@ func TestPTPClient_GetTorrentByID(t *testing.T) { APIUser: user, APIKey: key, }, - args: args{torrentID: "1"}, + args: args{torrentID: "1", freeleechToken: false}, want: &domain.TorrentBasic{ Id: "1", InfoHash: "F57AA86DFB03F87FCC7636E310D35918442EAE5C", @@ -87,7 +87,7 @@ func TestPTPClient_GetTorrentByID(t *testing.T) { APIUser: user, APIKey: key, }, - args: args{torrentID: "100002"}, + args: args{torrentID: "100002", freeleechToken: false}, want: nil, wantErr: true, }, @@ -96,7 +96,7 @@ func TestPTPClient_GetTorrentByID(t *testing.T) { t.Run(tt.name, func(t *testing.T) { c := NewClient(tt.fields.APIUser, tt.fields.APIKey, WithUrl(ts.URL)) - got, err := c.GetTorrentByID(context.Background(), tt.args.torrentID) + got, err := c.GetTorrentByID(context.Background(), tt.args.torrentID, tt.args.freeleechToken) if tt.wantErr { assert.Error(t, err) } else { diff --git a/pkg/red/red.go b/pkg/red/red.go index acbc487..28f8e1b 100644 --- a/pkg/red/red.go +++ b/pkg/red/red.go @@ -23,7 +23,7 @@ import ( const DefaultURL = "https://redacted.sh/ajax.php" type ApiClient interface { - GetTorrentByID(ctx context.Context, torrentID string) (*domain.TorrentBasic, error) + GetTorrentByID(ctx context.Context, torrentID string, freeleechToken bool) (*domain.TorrentBasic, error) TestAPI(ctx context.Context) (bool, error) } @@ -214,7 +214,7 @@ func (c *Client) getJSON(ctx context.Context, params url.Values, data any) error return nil } -func (c *Client) GetTorrentByID(ctx context.Context, torrentID string) (*domain.TorrentBasic, error) { +func (c *Client) GetTorrentByID(ctx context.Context, torrentID string, freeleechToken bool) (*domain.TorrentBasic, error) { if torrentID == "" { return nil, errors.New("red client: must have torrentID") } @@ -224,6 +224,9 @@ func (c *Client) GetTorrentByID(ctx context.Context, torrentID string) (*domain. params := url.Values{} params.Add("action", "torrent") params.Add("id", torrentID) + if freeleechToken { + params.Add("usetoken", "1") + } err := c.getJSON(ctx, params, &response) if err != nil { diff --git a/pkg/red/red_test.go b/pkg/red/red_test.go index 836e9a4..0d1386c 100644 --- a/pkg/red/red_test.go +++ b/pkg/red/red_test.go @@ -70,7 +70,7 @@ func TestREDClient_GetTorrentByID(t *testing.T) { Url: ts.URL, APIKey: key, }, - args: args{torrentID: "29991962"}, + args: args{torrentID: "29991962", freeleechToken: false}, want: &domain.TorrentBasic{ Id: "29991962", InfoHash: "B2BABD3A361EAFC6C4E9142C422DF7DDF5D7E163", @@ -86,7 +86,7 @@ func TestREDClient_GetTorrentByID(t *testing.T) { Url: ts.URL, APIKey: key, }, - args: args{torrentID: "100002"}, + args: args{torrentID: "100002", freeleechToken: false}, want: nil, wantErr: "could not get torrent by id: 100002: status code: 400 status: failure error: bad id parameter", }, @@ -96,7 +96,7 @@ func TestREDClient_GetTorrentByID(t *testing.T) { Url: ts.URL, APIKey: "", }, - args: args{torrentID: "100002"}, + args: args{torrentID: "100002", freeleechToken: false}, want: nil, wantErr: "could not get torrent by id: 100002: RED client missing API key!", }, @@ -105,7 +105,7 @@ func TestREDClient_GetTorrentByID(t *testing.T) { t.Run(tt.name, func(t *testing.T) { c := NewClient(tt.fields.APIKey, WithUrl(ts.URL)) - got, err := c.GetTorrentByID(context.Background(), tt.args.torrentID) + got, err := c.GetTorrentByID(context.Background(), tt.args.torrentID, tt.args.freeleechToken) if tt.wantErr != "" && assert.Error(t, err) { assert.EqualErrorf(t, err, tt.wantErr, "Error should be: %v, got: %v", tt.wantErr, err) } diff --git a/web/src/screens/filters/Details.tsx b/web/src/screens/filters/Details.tsx index 2a41361..3b057f7 100644 --- a/web/src/screens/filters/Details.tsx +++ b/web/src/screens/filters/Details.tsx @@ -437,6 +437,7 @@ export const FilterDetails = () => { except_language: filter.except_language || [], freeleech: filter.freeleech, freeleech_percent: filter.freeleech_percent, + freeleech_token: filter.freeleech_token, formats: filter.formats || [], quality: filter.quality || [], media: filter.media || [], diff --git a/web/src/screens/filters/_const.ts b/web/src/screens/filters/_const.ts index 1b6d9a6..a0be3a0 100644 --- a/web/src/screens/filters/_const.ts +++ b/web/src/screens/filters/_const.ts @@ -14,6 +14,7 @@ export const FILTER_FIELDS: Record = { "scene": "boolean", "smart_episode": "boolean", "freeleech": "boolean", + "freeleech_token": "boolean", "perfect_flac": "boolean", "download_duplicates": "boolean", "cue": "boolean", diff --git a/web/src/screens/filters/sections/Advanced.tsx b/web/src/screens/filters/sections/Advanced.tsx index 8b79985..a23f69d 100644 --- a/web/src/screens/filters/sections/Advanced.tsx +++ b/web/src/screens/filters/sections/Advanced.tsx @@ -354,7 +354,7 @@ const Freeleech = () => { return ( @@ -401,6 +401,21 @@ const Freeleech = () => { } /> + + +

+ Use freelech tokens for downloads if supported bt the tracker. +

+ + } + /> +
); } diff --git a/web/src/types/Filter.d.ts b/web/src/types/Filter.d.ts index 4b3dbed..0ef5360 100644 --- a/web/src/types/Filter.d.ts +++ b/web/src/types/Filter.d.ts @@ -32,6 +32,7 @@ interface Filter { except_origins: string[]; freeleech: boolean; freeleech_percent: string; + freeleech_token: boolean; shows: string; seasons: string; episodes: string;