mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 00:39:13 +00:00
feat(filters): add external script and webhook checks
This commit is contained in:
parent
16dd8c5419
commit
d56693cd33
17 changed files with 635 additions and 200 deletions
|
@ -135,7 +135,7 @@ func (s *service) delugeV1(client *domain.DownloadClient, action domain.Action,
|
||||||
}
|
}
|
||||||
|
|
||||||
// macros handle args and replace vars
|
// macros handle args and replace vars
|
||||||
m := NewMacro(release)
|
m := domain.NewMacro(release)
|
||||||
|
|
||||||
options, err := s.prepareDelugeOptions(action, m)
|
options, err := s.prepareDelugeOptions(action, m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -224,7 +224,7 @@ func (s *service) delugeV2(client *domain.DownloadClient, action domain.Action,
|
||||||
}
|
}
|
||||||
|
|
||||||
// macros handle args and replace vars
|
// macros handle args and replace vars
|
||||||
m := NewMacro(release)
|
m := domain.NewMacro(release)
|
||||||
|
|
||||||
// set options
|
// set options
|
||||||
options, err := s.prepareDelugeOptions(action, m)
|
options, err := s.prepareDelugeOptions(action, m)
|
||||||
|
@ -265,7 +265,7 @@ func (s *service) delugeV2(client *domain.DownloadClient, action domain.Action,
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) prepareDelugeOptions(action domain.Action, m Macro) (delugeClient.Options, error) {
|
func (s *service) prepareDelugeOptions(action domain.Action, m domain.Macro) (delugeClient.Options, error) {
|
||||||
|
|
||||||
// set options
|
// set options
|
||||||
options := delugeClient.Options{}
|
options := delugeClient.Options{}
|
||||||
|
|
|
@ -56,7 +56,7 @@ func (s *service) execCmd(action domain.Action, release domain.Release) error {
|
||||||
|
|
||||||
func (s *service) parseExecArgs(release domain.Release, execArgs string) ([]string, error) {
|
func (s *service) parseExecArgs(release domain.Release, execArgs string) ([]string, error) {
|
||||||
// handle args and replace vars
|
// handle args and replace vars
|
||||||
m := NewMacro(release)
|
m := domain.NewMacro(release)
|
||||||
|
|
||||||
// parse and replace values in argument string before continuing
|
// parse and replace values in argument string before continuing
|
||||||
parsedArgs, err := m.Parse(execArgs)
|
parsedArgs, err := m.Parse(execArgs)
|
||||||
|
|
|
@ -76,7 +76,7 @@ func (s *service) qbittorrent(action domain.Action, release domain.Release) ([]s
|
||||||
}
|
}
|
||||||
|
|
||||||
// macros handle args and replace vars
|
// macros handle args and replace vars
|
||||||
m := NewMacro(release)
|
m := domain.NewMacro(release)
|
||||||
|
|
||||||
options, err := s.prepareQbitOptions(action, m)
|
options, err := s.prepareQbitOptions(action, m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -100,7 +100,7 @@ func (s *service) qbittorrent(action domain.Action, release domain.Release) ([]s
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) prepareQbitOptions(action domain.Action, m Macro) (map[string]string, error) {
|
func (s *service) prepareQbitOptions(action domain.Action, m domain.Macro) (map[string]string, error) {
|
||||||
|
|
||||||
options := map[string]string{}
|
options := map[string]string{}
|
||||||
|
|
||||||
|
|
|
@ -136,7 +136,7 @@ func (s *service) watchFolder(action domain.Action, release domain.Release) erro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m := NewMacro(release)
|
m := domain.NewMacro(release)
|
||||||
|
|
||||||
// parse and replace values in argument string before continuing
|
// parse and replace values in argument string before continuing
|
||||||
watchFolderArgs, err := m.Parse(action.WatchFolder)
|
watchFolderArgs, err := m.Parse(action.WatchFolder)
|
||||||
|
@ -187,7 +187,7 @@ func (s *service) webhook(action domain.Action, release domain.Release) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m := NewMacro(release)
|
m := domain.NewMacro(release)
|
||||||
|
|
||||||
// parse and replace values in argument string before continuing
|
// parse and replace values in argument string before continuing
|
||||||
dataArgs, err := m.Parse(action.WebhookData)
|
dataArgs, err := m.Parse(action.WebhookData)
|
||||||
|
|
|
@ -123,6 +123,14 @@ func (r *FilterRepo) FindByID(ctx context.Context, filterID int) (*domain.Filter
|
||||||
"tags",
|
"tags",
|
||||||
"except_tags",
|
"except_tags",
|
||||||
"origins",
|
"origins",
|
||||||
|
"external_script_enabled",
|
||||||
|
"external_script_cmd",
|
||||||
|
"external_script_args",
|
||||||
|
"external_script_expect_status",
|
||||||
|
"external_webhook_enabled",
|
||||||
|
"external_webhook_host",
|
||||||
|
"external_webhook_data",
|
||||||
|
"external_webhook_expect_status",
|
||||||
"created_at",
|
"created_at",
|
||||||
"updated_at",
|
"updated_at",
|
||||||
).
|
).
|
||||||
|
@ -140,11 +148,11 @@ func (r *FilterRepo) FindByID(ctx context.Context, filterID int) (*domain.Filter
|
||||||
}
|
}
|
||||||
|
|
||||||
var f domain.Filter
|
var f domain.Filter
|
||||||
var minSize, maxSize, maxDownloadsUnit, matchReleases, exceptReleases, matchReleaseGroups, exceptReleaseGroups, freeleechPercent, shows, seasons, episodes, years, artists, albums, matchCategories, exceptCategories, matchUploaders, exceptUploaders, tags, exceptTags sql.NullString
|
var minSize, maxSize, maxDownloadsUnit, matchReleases, exceptReleases, matchReleaseGroups, exceptReleaseGroups, freeleechPercent, shows, seasons, episodes, years, artists, albums, matchCategories, exceptCategories, matchUploaders, exceptUploaders, tags, exceptTags, extScriptCmd, extScriptArgs, extWebhookHost, extWebhookData sql.NullString
|
||||||
var useRegex, scene, freeleech, hasLog, hasCue, perfectFlac sql.NullBool
|
var useRegex, scene, freeleech, hasLog, hasCue, perfectFlac, extScriptEnabled, extWebhookEnabled sql.NullBool
|
||||||
var delay, maxDownloads, logScore sql.NullInt32
|
var delay, maxDownloads, logScore, extWebhookStatus, extScriptStatus sql.NullInt32
|
||||||
|
|
||||||
if err := row.Scan(&f.ID, &f.Enabled, &f.Name, &minSize, &maxSize, &delay, &f.Priority, &maxDownloads, &maxDownloadsUnit, &matchReleases, &exceptReleases, &useRegex, &matchReleaseGroups, &exceptReleaseGroups, &scene, &freeleech, &freeleechPercent, &shows, &seasons, &episodes, pq.Array(&f.Resolutions), pq.Array(&f.Codecs), pq.Array(&f.Sources), pq.Array(&f.Containers), pq.Array(&f.MatchHDR), pq.Array(&f.ExceptHDR), pq.Array(&f.MatchOther), pq.Array(&f.ExceptOther), &years, &artists, &albums, pq.Array(&f.MatchReleaseTypes), pq.Array(&f.Formats), pq.Array(&f.Quality), pq.Array(&f.Media), &logScore, &hasLog, &hasCue, &perfectFlac, &matchCategories, &exceptCategories, &matchUploaders, &exceptUploaders, &tags, &exceptTags, pq.Array(&f.Origins), &f.CreatedAt, &f.UpdatedAt); err != nil {
|
if err := row.Scan(&f.ID, &f.Enabled, &f.Name, &minSize, &maxSize, &delay, &f.Priority, &maxDownloads, &maxDownloadsUnit, &matchReleases, &exceptReleases, &useRegex, &matchReleaseGroups, &exceptReleaseGroups, &scene, &freeleech, &freeleechPercent, &shows, &seasons, &episodes, pq.Array(&f.Resolutions), pq.Array(&f.Codecs), pq.Array(&f.Sources), pq.Array(&f.Containers), pq.Array(&f.MatchHDR), pq.Array(&f.ExceptHDR), pq.Array(&f.MatchOther), pq.Array(&f.ExceptOther), &years, &artists, &albums, pq.Array(&f.MatchReleaseTypes), pq.Array(&f.Formats), pq.Array(&f.Quality), pq.Array(&f.Media), &logScore, &hasLog, &hasCue, &perfectFlac, &matchCategories, &exceptCategories, &matchUploaders, &exceptUploaders, &tags, &exceptTags, pq.Array(&f.Origins), &extScriptEnabled, &extScriptCmd, &extScriptArgs, &extScriptStatus, &extWebhookEnabled, &extWebhookHost, &extWebhookData, &extWebhookStatus, &f.CreatedAt, &f.UpdatedAt); err != nil {
|
||||||
return nil, errors.Wrap(err, "error scanning row")
|
return nil, errors.Wrap(err, "error scanning row")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,6 +186,16 @@ func (r *FilterRepo) FindByID(ctx context.Context, filterID int) (*domain.Filter
|
||||||
f.Scene = scene.Bool
|
f.Scene = scene.Bool
|
||||||
f.Freeleech = freeleech.Bool
|
f.Freeleech = freeleech.Bool
|
||||||
|
|
||||||
|
f.ExternalScriptEnabled = extScriptEnabled.Bool
|
||||||
|
f.ExternalScriptCmd = extScriptCmd.String
|
||||||
|
f.ExternalScriptArgs = extScriptArgs.String
|
||||||
|
f.ExternalScriptExpectStatus = int(extScriptStatus.Int32)
|
||||||
|
|
||||||
|
f.ExternalWebhookEnabled = extWebhookEnabled.Bool
|
||||||
|
f.ExternalWebhookHost = extWebhookHost.String
|
||||||
|
f.ExternalWebhookData = extWebhookData.String
|
||||||
|
f.ExternalWebhookExpectStatus = int(extWebhookStatus.Int32)
|
||||||
|
|
||||||
return &f, nil
|
return &f, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,6 +273,14 @@ func (r *FilterRepo) findByIndexerIdentifier(ctx context.Context, tx *Tx, indexe
|
||||||
"f.tags",
|
"f.tags",
|
||||||
"f.except_tags",
|
"f.except_tags",
|
||||||
"f.origins",
|
"f.origins",
|
||||||
|
"f.external_script_enabled",
|
||||||
|
"f.external_script_cmd",
|
||||||
|
"f.external_script_args",
|
||||||
|
"f.external_script_expect_status",
|
||||||
|
"f.external_webhook_enabled",
|
||||||
|
"f.external_webhook_host",
|
||||||
|
"f.external_webhook_data",
|
||||||
|
"f.external_webhook_expect_status",
|
||||||
"f.created_at",
|
"f.created_at",
|
||||||
"f.updated_at",
|
"f.updated_at",
|
||||||
).
|
).
|
||||||
|
@ -282,11 +308,11 @@ func (r *FilterRepo) findByIndexerIdentifier(ctx context.Context, tx *Tx, indexe
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var f domain.Filter
|
var f domain.Filter
|
||||||
|
|
||||||
var minSize, maxSize, maxDownloadsUnit, matchReleases, exceptReleases, matchReleaseGroups, exceptReleaseGroups, freeleechPercent, shows, seasons, episodes, years, artists, albums, matchCategories, exceptCategories, matchUploaders, exceptUploaders, tags, exceptTags sql.NullString
|
var minSize, maxSize, maxDownloadsUnit, matchReleases, exceptReleases, matchReleaseGroups, exceptReleaseGroups, freeleechPercent, shows, seasons, episodes, years, artists, albums, matchCategories, exceptCategories, matchUploaders, exceptUploaders, tags, exceptTags, extScriptCmd, extScriptArgs, extWebhookHost, extWebhookData sql.NullString
|
||||||
var useRegex, scene, freeleech, hasLog, hasCue, perfectFlac sql.NullBool
|
var useRegex, scene, freeleech, hasLog, hasCue, perfectFlac, extScriptEnabled, extWebhookEnabled sql.NullBool
|
||||||
var delay, maxDownloads, logScore sql.NullInt32
|
var delay, maxDownloads, logScore, extWebhookStatus, extScriptStatus sql.NullInt32
|
||||||
|
|
||||||
if err := rows.Scan(&f.ID, &f.Enabled, &f.Name, &minSize, &maxSize, &delay, &f.Priority, &maxDownloads, &maxDownloadsUnit, &matchReleases, &exceptReleases, &useRegex, &matchReleaseGroups, &exceptReleaseGroups, &scene, &freeleech, &freeleechPercent, &shows, &seasons, &episodes, pq.Array(&f.Resolutions), pq.Array(&f.Codecs), pq.Array(&f.Sources), pq.Array(&f.Containers), pq.Array(&f.MatchHDR), pq.Array(&f.ExceptHDR), pq.Array(&f.MatchOther), pq.Array(&f.ExceptOther), &years, &artists, &albums, pq.Array(&f.MatchReleaseTypes), pq.Array(&f.Formats), pq.Array(&f.Quality), pq.Array(&f.Media), &logScore, &hasLog, &hasCue, &perfectFlac, &matchCategories, &exceptCategories, &matchUploaders, &exceptUploaders, &tags, &exceptTags, pq.Array(&f.Origins), &f.CreatedAt, &f.UpdatedAt); err != nil {
|
if err := rows.Scan(&f.ID, &f.Enabled, &f.Name, &minSize, &maxSize, &delay, &f.Priority, &maxDownloads, &maxDownloadsUnit, &matchReleases, &exceptReleases, &useRegex, &matchReleaseGroups, &exceptReleaseGroups, &scene, &freeleech, &freeleechPercent, &shows, &seasons, &episodes, pq.Array(&f.Resolutions), pq.Array(&f.Codecs), pq.Array(&f.Sources), pq.Array(&f.Containers), pq.Array(&f.MatchHDR), pq.Array(&f.ExceptHDR), pq.Array(&f.MatchOther), pq.Array(&f.ExceptOther), &years, &artists, &albums, pq.Array(&f.MatchReleaseTypes), pq.Array(&f.Formats), pq.Array(&f.Quality), pq.Array(&f.Media), &logScore, &hasLog, &hasCue, &perfectFlac, &matchCategories, &exceptCategories, &matchUploaders, &exceptUploaders, &tags, &exceptTags, pq.Array(&f.Origins), &extScriptEnabled, &extScriptCmd, &extScriptArgs, &extScriptStatus, &extWebhookEnabled, &extWebhookHost, &extWebhookData, &extWebhookStatus, &f.CreatedAt, &f.UpdatedAt); err != nil {
|
||||||
return nil, errors.Wrap(err, "error scanning row")
|
return nil, errors.Wrap(err, "error scanning row")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -320,6 +346,16 @@ func (r *FilterRepo) findByIndexerIdentifier(ctx context.Context, tx *Tx, indexe
|
||||||
f.Scene = scene.Bool
|
f.Scene = scene.Bool
|
||||||
f.Freeleech = freeleech.Bool
|
f.Freeleech = freeleech.Bool
|
||||||
|
|
||||||
|
f.ExternalScriptEnabled = extScriptEnabled.Bool
|
||||||
|
f.ExternalScriptCmd = extScriptCmd.String
|
||||||
|
f.ExternalScriptArgs = extScriptArgs.String
|
||||||
|
f.ExternalScriptExpectStatus = int(extScriptStatus.Int32)
|
||||||
|
|
||||||
|
f.ExternalWebhookEnabled = extWebhookEnabled.Bool
|
||||||
|
f.ExternalWebhookHost = extWebhookHost.String
|
||||||
|
f.ExternalWebhookData = extWebhookData.String
|
||||||
|
f.ExternalWebhookExpectStatus = int(extWebhookStatus.Int32)
|
||||||
|
|
||||||
filters = append(filters, f)
|
filters = append(filters, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -375,6 +411,14 @@ func (r *FilterRepo) Store(ctx context.Context, filter domain.Filter) (*domain.F
|
||||||
"has_cue",
|
"has_cue",
|
||||||
"perfect_flac",
|
"perfect_flac",
|
||||||
"origins",
|
"origins",
|
||||||
|
"external_script_enabled",
|
||||||
|
"external_script_cmd",
|
||||||
|
"external_script_args",
|
||||||
|
"external_script_expect_status",
|
||||||
|
"external_webhook_enabled",
|
||||||
|
"external_webhook_host",
|
||||||
|
"external_webhook_data",
|
||||||
|
"external_webhook_expect_status",
|
||||||
).
|
).
|
||||||
Values(
|
Values(
|
||||||
filter.Name,
|
filter.Name,
|
||||||
|
@ -422,6 +466,14 @@ func (r *FilterRepo) Store(ctx context.Context, filter domain.Filter) (*domain.F
|
||||||
filter.Cue,
|
filter.Cue,
|
||||||
filter.PerfectFlac,
|
filter.PerfectFlac,
|
||||||
pq.Array(filter.Origins),
|
pq.Array(filter.Origins),
|
||||||
|
filter.ExternalScriptEnabled,
|
||||||
|
filter.ExternalScriptCmd,
|
||||||
|
filter.ExternalScriptArgs,
|
||||||
|
filter.ExternalScriptExpectStatus,
|
||||||
|
filter.ExternalWebhookEnabled,
|
||||||
|
filter.ExternalWebhookHost,
|
||||||
|
filter.ExternalWebhookData,
|
||||||
|
filter.ExternalWebhookExpectStatus,
|
||||||
).
|
).
|
||||||
Suffix("RETURNING id").RunWith(r.db.handler)
|
Suffix("RETURNING id").RunWith(r.db.handler)
|
||||||
|
|
||||||
|
@ -488,6 +540,14 @@ func (r *FilterRepo) Update(ctx context.Context, filter domain.Filter) (*domain.
|
||||||
Set("has_cue", filter.Cue).
|
Set("has_cue", filter.Cue).
|
||||||
Set("perfect_flac", filter.PerfectFlac).
|
Set("perfect_flac", filter.PerfectFlac).
|
||||||
Set("origins", pq.Array(filter.Origins)).
|
Set("origins", pq.Array(filter.Origins)).
|
||||||
|
Set("external_script_enabled", filter.ExternalScriptEnabled).
|
||||||
|
Set("external_script_cmd", filter.ExternalScriptCmd).
|
||||||
|
Set("external_script_args", filter.ExternalScriptArgs).
|
||||||
|
Set("external_script_expect_status", filter.ExternalScriptExpectStatus).
|
||||||
|
Set("external_webhook_enabled", filter.ExternalWebhookEnabled).
|
||||||
|
Set("external_webhook_host", filter.ExternalWebhookHost).
|
||||||
|
Set("external_webhook_data", filter.ExternalWebhookData).
|
||||||
|
Set("external_webhook_expect_status", filter.ExternalWebhookExpectStatus).
|
||||||
Set("updated_at", time.Now().Format(time.RFC3339)).
|
Set("updated_at", time.Now().Format(time.RFC3339)).
|
||||||
Where("id = ?", filter.ID)
|
Where("id = ?", filter.ID)
|
||||||
|
|
||||||
|
|
|
@ -60,55 +60,63 @@ CREATE TABLE irc_channel
|
||||||
|
|
||||||
CREATE TABLE filter
|
CREATE TABLE filter
|
||||||
(
|
(
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
enabled BOOLEAN,
|
enabled BOOLEAN,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
min_size TEXT,
|
min_size TEXT,
|
||||||
max_size TEXT,
|
max_size TEXT,
|
||||||
delay INTEGER,
|
delay INTEGER,
|
||||||
priority INTEGER DEFAULT 0 NOT NULL,
|
priority INTEGER DEFAULT 0 NOT NULL,
|
||||||
max_downloads INTEGER DEFAULT 0,
|
max_downloads INTEGER DEFAULT 0,
|
||||||
max_downloads_unit TEXT,
|
max_downloads_unit TEXT,
|
||||||
match_releases TEXT,
|
match_releases TEXT,
|
||||||
except_releases TEXT,
|
except_releases TEXT,
|
||||||
use_regex BOOLEAN,
|
use_regex BOOLEAN,
|
||||||
match_release_groups TEXT,
|
match_release_groups TEXT,
|
||||||
except_release_groups TEXT,
|
except_release_groups TEXT,
|
||||||
scene BOOLEAN,
|
scene BOOLEAN,
|
||||||
freeleech BOOLEAN,
|
freeleech BOOLEAN,
|
||||||
freeleech_percent TEXT,
|
freeleech_percent TEXT,
|
||||||
shows TEXT,
|
shows TEXT,
|
||||||
seasons TEXT,
|
seasons TEXT,
|
||||||
episodes TEXT,
|
episodes TEXT,
|
||||||
resolutions TEXT [] DEFAULT '{}' NOT NULL,
|
resolutions TEXT [] DEFAULT '{}' NOT NULL,
|
||||||
codecs TEXT [] DEFAULT '{}' NOT NULL,
|
codecs TEXT [] DEFAULT '{}' NOT NULL,
|
||||||
sources TEXT [] DEFAULT '{}' NOT NULL,
|
sources TEXT [] DEFAULT '{}' NOT NULL,
|
||||||
containers TEXT [] DEFAULT '{}' NOT NULL,
|
containers TEXT [] DEFAULT '{}' NOT NULL,
|
||||||
match_hdr TEXT [] DEFAULT '{}',
|
match_hdr TEXT [] DEFAULT '{}',
|
||||||
except_hdr TEXT [] DEFAULT '{}',
|
except_hdr TEXT [] DEFAULT '{}',
|
||||||
match_other TEXT [] DEFAULT '{}',
|
match_other TEXT [] DEFAULT '{}',
|
||||||
except_other TEXT [] DEFAULT '{}',
|
except_other TEXT [] DEFAULT '{}',
|
||||||
years TEXT,
|
years TEXT,
|
||||||
artists TEXT,
|
artists TEXT,
|
||||||
albums TEXT,
|
albums TEXT,
|
||||||
release_types_match TEXT [] DEFAULT '{}',
|
release_types_match TEXT [] DEFAULT '{}',
|
||||||
release_types_ignore TEXT [] DEFAULT '{}',
|
release_types_ignore TEXT [] DEFAULT '{}',
|
||||||
formats TEXT [] DEFAULT '{}',
|
formats TEXT [] DEFAULT '{}',
|
||||||
quality TEXT [] DEFAULT '{}',
|
quality TEXT [] DEFAULT '{}',
|
||||||
media TEXT [] DEFAULT '{}',
|
media TEXT [] DEFAULT '{}',
|
||||||
log_score INTEGER,
|
log_score INTEGER,
|
||||||
has_log BOOLEAN,
|
has_log BOOLEAN,
|
||||||
has_cue BOOLEAN,
|
has_cue BOOLEAN,
|
||||||
perfect_flac BOOLEAN,
|
perfect_flac BOOLEAN,
|
||||||
match_categories TEXT,
|
match_categories TEXT,
|
||||||
except_categories TEXT,
|
except_categories TEXT,
|
||||||
match_uploaders TEXT,
|
match_uploaders TEXT,
|
||||||
except_uploaders TEXT,
|
except_uploaders TEXT,
|
||||||
tags TEXT,
|
tags TEXT,
|
||||||
except_tags TEXT,
|
except_tags TEXT,
|
||||||
origins TEXT [] DEFAULT '{}',
|
origins TEXT [] DEFAULT '{}',
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
external_script_enabled BOOLEAN DEFAULT FALSE,
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
external_script_cmd TEXT,
|
||||||
|
external_script_args TEXT,
|
||||||
|
external_script_expect_status INTEGER,
|
||||||
|
external_webhook_enabled BOOLEAN DEFAULT FALSE,
|
||||||
|
external_webhook_host TEXT,
|
||||||
|
external_webhook_data TEXT,
|
||||||
|
external_webhook_expect_status INTEGER,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE filter_indexer
|
CREATE TABLE filter_indexer
|
||||||
|
@ -494,4 +502,29 @@ CREATE INDEX indexer_identifier_index
|
||||||
ALTER TABLE release_action_status
|
ALTER TABLE release_action_status
|
||||||
ADD COLUMN filter TEXT;
|
ADD COLUMN filter TEXT;
|
||||||
`,
|
`,
|
||||||
|
`
|
||||||
|
ALTER TABLE filter
|
||||||
|
ADD COLUMN external_script_enabled BOOLEAN DEFAULT FALSE;
|
||||||
|
|
||||||
|
ALTER TABLE filter
|
||||||
|
ADD COLUMN external_script_cmd TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE filter
|
||||||
|
ADD COLUMN external_script_args TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE filter
|
||||||
|
ADD COLUMN external_script_expect_status INTEGER;
|
||||||
|
|
||||||
|
ALTER TABLE filter
|
||||||
|
ADD COLUMN external_webhook_enabled BOOLEAN DEFAULT FALSE;
|
||||||
|
|
||||||
|
ALTER TABLE filter
|
||||||
|
ADD COLUMN external_webhook_host TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE filter
|
||||||
|
ADD COLUMN external_webhook_data TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE filter
|
||||||
|
ADD COLUMN external_webhook_expect_status INTEGER;
|
||||||
|
`,
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,55 +60,63 @@ CREATE TABLE irc_channel
|
||||||
|
|
||||||
CREATE TABLE filter
|
CREATE TABLE filter
|
||||||
(
|
(
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
enabled BOOLEAN,
|
enabled BOOLEAN,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
min_size TEXT,
|
min_size TEXT,
|
||||||
max_size TEXT,
|
max_size TEXT,
|
||||||
delay INTEGER,
|
delay INTEGER,
|
||||||
priority INTEGER DEFAULT 0 NOT NULL,
|
priority INTEGER DEFAULT 0 NOT NULL,
|
||||||
max_downloads INTEGER DEFAULT 0,
|
max_downloads INTEGER DEFAULT 0,
|
||||||
max_downloads_unit TEXT,
|
max_downloads_unit TEXT,
|
||||||
match_releases TEXT,
|
match_releases TEXT,
|
||||||
except_releases TEXT,
|
except_releases TEXT,
|
||||||
use_regex BOOLEAN,
|
use_regex BOOLEAN,
|
||||||
match_release_groups TEXT,
|
match_release_groups TEXT,
|
||||||
except_release_groups TEXT,
|
except_release_groups TEXT,
|
||||||
scene BOOLEAN,
|
scene BOOLEAN,
|
||||||
freeleech BOOLEAN,
|
freeleech BOOLEAN,
|
||||||
freeleech_percent TEXT,
|
freeleech_percent TEXT,
|
||||||
shows TEXT,
|
shows TEXT,
|
||||||
seasons TEXT,
|
seasons TEXT,
|
||||||
episodes TEXT,
|
episodes TEXT,
|
||||||
resolutions TEXT [] DEFAULT '{}' NOT NULL,
|
resolutions TEXT [] DEFAULT '{}' NOT NULL,
|
||||||
codecs TEXT [] DEFAULT '{}' NOT NULL,
|
codecs TEXT [] DEFAULT '{}' NOT NULL,
|
||||||
sources TEXT [] DEFAULT '{}' NOT NULL,
|
sources TEXT [] DEFAULT '{}' NOT NULL,
|
||||||
containers TEXT [] DEFAULT '{}' NOT NULL,
|
containers TEXT [] DEFAULT '{}' NOT NULL,
|
||||||
match_hdr TEXT [] DEFAULT '{}',
|
match_hdr TEXT [] DEFAULT '{}',
|
||||||
except_hdr TEXT [] DEFAULT '{}',
|
except_hdr TEXT [] DEFAULT '{}',
|
||||||
match_other TEXT [] DEFAULT '{}',
|
match_other TEXT [] DEFAULT '{}',
|
||||||
except_other TEXT [] DEFAULT '{}',
|
except_other TEXT [] DEFAULT '{}',
|
||||||
years TEXT,
|
years TEXT,
|
||||||
artists TEXT,
|
artists TEXT,
|
||||||
albums TEXT,
|
albums TEXT,
|
||||||
release_types_match TEXT [] DEFAULT '{}',
|
release_types_match TEXT [] DEFAULT '{}',
|
||||||
release_types_ignore TEXT [] DEFAULT '{}',
|
release_types_ignore TEXT [] DEFAULT '{}',
|
||||||
formats TEXT [] DEFAULT '{}',
|
formats TEXT [] DEFAULT '{}',
|
||||||
quality TEXT [] DEFAULT '{}',
|
quality TEXT [] DEFAULT '{}',
|
||||||
media TEXT [] DEFAULT '{}',
|
media TEXT [] DEFAULT '{}',
|
||||||
log_score INTEGER,
|
log_score INTEGER,
|
||||||
has_log BOOLEAN,
|
has_log BOOLEAN,
|
||||||
has_cue BOOLEAN,
|
has_cue BOOLEAN,
|
||||||
perfect_flac BOOLEAN,
|
perfect_flac BOOLEAN,
|
||||||
match_categories TEXT,
|
match_categories TEXT,
|
||||||
except_categories TEXT,
|
except_categories TEXT,
|
||||||
match_uploaders TEXT,
|
match_uploaders TEXT,
|
||||||
except_uploaders TEXT,
|
except_uploaders TEXT,
|
||||||
tags TEXT,
|
tags TEXT,
|
||||||
except_tags TEXT,
|
except_tags TEXT,
|
||||||
origins TEXT [] DEFAULT '{}',
|
origins TEXT [] DEFAULT '{}',
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
external_script_enabled BOOLEAN DEFAULT FALSE,
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
external_script_cmd TEXT,
|
||||||
|
external_script_args TEXT,
|
||||||
|
external_script_expect_status INTEGER,
|
||||||
|
external_webhook_enabled BOOLEAN DEFAULT FALSE,
|
||||||
|
external_webhook_host TEXT,
|
||||||
|
external_webhook_data TEXT,
|
||||||
|
external_webhook_expect_status INTEGER,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE filter_indexer
|
CREATE TABLE filter_indexer
|
||||||
|
@ -814,4 +822,29 @@ CREATE INDEX indexer_identifier_index
|
||||||
ALTER TABLE release_action_status
|
ALTER TABLE release_action_status
|
||||||
ADD COLUMN filter TEXT;
|
ADD COLUMN filter TEXT;
|
||||||
`,
|
`,
|
||||||
|
`
|
||||||
|
ALTER TABLE filter
|
||||||
|
ADD COLUMN external_script_enabled BOOLEAN DEFAULT FALSE;
|
||||||
|
|
||||||
|
ALTER TABLE filter
|
||||||
|
ADD COLUMN external_script_cmd TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE filter
|
||||||
|
ADD COLUMN external_script_args TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE filter
|
||||||
|
ADD COLUMN external_script_expect_status INTEGER;
|
||||||
|
|
||||||
|
ALTER TABLE filter
|
||||||
|
ADD COLUMN external_webhook_enabled BOOLEAN DEFAULT FALSE;
|
||||||
|
|
||||||
|
ALTER TABLE filter
|
||||||
|
ADD COLUMN external_webhook_host TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE filter
|
||||||
|
ADD COLUMN external_webhook_data TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE filter
|
||||||
|
ADD COLUMN external_webhook_expect_status INTEGER;
|
||||||
|
`,
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,61 +49,69 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Filter struct {
|
type Filter struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
MinSize string `json:"min_size"`
|
MinSize string `json:"min_size"`
|
||||||
MaxSize string `json:"max_size"`
|
MaxSize string `json:"max_size"`
|
||||||
Delay int `json:"delay"`
|
Delay int `json:"delay"`
|
||||||
Priority int32 `json:"priority"`
|
Priority int32 `json:"priority"`
|
||||||
MaxDownloads int `json:"max_downloads"`
|
MaxDownloads int `json:"max_downloads"`
|
||||||
MaxDownloadsUnit FilterMaxDownloadsUnit `json:"max_downloads_unit"`
|
MaxDownloadsUnit FilterMaxDownloadsUnit `json:"max_downloads_unit"`
|
||||||
MatchReleases string `json:"match_releases"`
|
MatchReleases string `json:"match_releases"`
|
||||||
ExceptReleases string `json:"except_releases"`
|
ExceptReleases string `json:"except_releases"`
|
||||||
UseRegex bool `json:"use_regex"`
|
UseRegex bool `json:"use_regex"`
|
||||||
MatchReleaseGroups string `json:"match_release_groups"`
|
MatchReleaseGroups string `json:"match_release_groups"`
|
||||||
ExceptReleaseGroups string `json:"except_release_groups"`
|
ExceptReleaseGroups string `json:"except_release_groups"`
|
||||||
Scene bool `json:"scene"`
|
Scene bool `json:"scene"`
|
||||||
Origins []string `json:"origins"`
|
Origins []string `json:"origins"`
|
||||||
Bonus []string `json:"bonus"`
|
Bonus []string `json:"bonus"`
|
||||||
Freeleech bool `json:"freeleech"`
|
Freeleech bool `json:"freeleech"`
|
||||||
FreeleechPercent string `json:"freeleech_percent"`
|
FreeleechPercent string `json:"freeleech_percent"`
|
||||||
Shows string `json:"shows"`
|
Shows string `json:"shows"`
|
||||||
Seasons string `json:"seasons"`
|
Seasons string `json:"seasons"`
|
||||||
Episodes string `json:"episodes"`
|
Episodes string `json:"episodes"`
|
||||||
Resolutions []string `json:"resolutions"` // SD, 480i, 480p, 576p, 720p, 810p, 1080i, 1080p.
|
Resolutions []string `json:"resolutions"` // SD, 480i, 480p, 576p, 720p, 810p, 1080i, 1080p.
|
||||||
Codecs []string `json:"codecs"` // XviD, DivX, x264, h.264 (or h264), mpeg2 (or mpeg-2), VC-1 (or VC1), WMV, Remux, h.264 Remux (or h264 Remux), VC-1 Remux (or VC1 Remux).
|
Codecs []string `json:"codecs"` // XviD, DivX, x264, h.264 (or h264), mpeg2 (or mpeg-2), VC-1 (or VC1), WMV, Remux, h.264 Remux (or h264 Remux), VC-1 Remux (or VC1 Remux).
|
||||||
Sources []string `json:"sources"` // DSR, PDTV, HDTV, HR.PDTV, HR.HDTV, DVDRip, DVDScr, BDr, BD5, BD9, BDRip, BRRip, DVDR, MDVDR, HDDVD, HDDVDRip, BluRay, WEB-DL, TVRip, CAM, R5, TELESYNC, TS, TELECINE, TC. TELESYNC and TS are synonyms (you don't need both). Same for TELECINE and TC
|
Sources []string `json:"sources"` // DSR, PDTV, HDTV, HR.PDTV, HR.HDTV, DVDRip, DVDScr, BDr, BD5, BD9, BDRip, BRRip, DVDR, MDVDR, HDDVD, HDDVDRip, BluRay, WEB-DL, TVRip, CAM, R5, TELESYNC, TS, TELECINE, TC. TELESYNC and TS are synonyms (you don't need both). Same for TELECINE and TC
|
||||||
Containers []string `json:"containers"`
|
Containers []string `json:"containers"`
|
||||||
MatchHDR []string `json:"match_hdr"`
|
MatchHDR []string `json:"match_hdr"`
|
||||||
ExceptHDR []string `json:"except_hdr"`
|
ExceptHDR []string `json:"except_hdr"`
|
||||||
MatchOther []string `json:"match_other"`
|
MatchOther []string `json:"match_other"`
|
||||||
ExceptOther []string `json:"except_other"`
|
ExceptOther []string `json:"except_other"`
|
||||||
Years string `json:"years"`
|
Years string `json:"years"`
|
||||||
Artists string `json:"artists"`
|
Artists string `json:"artists"`
|
||||||
Albums string `json:"albums"`
|
Albums string `json:"albums"`
|
||||||
MatchReleaseTypes []string `json:"match_release_types"` // Album,Single,EP
|
MatchReleaseTypes []string `json:"match_release_types"` // Album,Single,EP
|
||||||
ExceptReleaseTypes string `json:"except_release_types"`
|
ExceptReleaseTypes string `json:"except_release_types"`
|
||||||
Formats []string `json:"formats"` // MP3, FLAC, Ogg, AAC, AC3, DTS
|
Formats []string `json:"formats"` // MP3, FLAC, Ogg, AAC, AC3, DTS
|
||||||
Quality []string `json:"quality"` // 192, 320, APS (VBR), V2 (VBR), V1 (VBR), APX (VBR), V0 (VBR), q8.x (VBR), Lossless, 24bit Lossless, Other
|
Quality []string `json:"quality"` // 192, 320, APS (VBR), V2 (VBR), V1 (VBR), APX (VBR), V0 (VBR), q8.x (VBR), Lossless, 24bit Lossless, Other
|
||||||
Media []string `json:"media"` // CD, DVD, Vinyl, Soundboard, SACD, DAT, Cassette, WEB, Other
|
Media []string `json:"media"` // CD, DVD, Vinyl, Soundboard, SACD, DAT, Cassette, WEB, Other
|
||||||
PerfectFlac bool `json:"perfect_flac"`
|
PerfectFlac bool `json:"perfect_flac"`
|
||||||
Cue bool `json:"cue"`
|
Cue bool `json:"cue"`
|
||||||
Log bool `json:"log"`
|
Log bool `json:"log"`
|
||||||
LogScore int `json:"log_score"`
|
LogScore int `json:"log_score"`
|
||||||
MatchCategories string `json:"match_categories"`
|
MatchCategories string `json:"match_categories"`
|
||||||
ExceptCategories string `json:"except_categories"`
|
ExceptCategories string `json:"except_categories"`
|
||||||
MatchUploaders string `json:"match_uploaders"`
|
MatchUploaders string `json:"match_uploaders"`
|
||||||
ExceptUploaders string `json:"except_uploaders"`
|
ExceptUploaders string `json:"except_uploaders"`
|
||||||
Tags string `json:"tags"`
|
Tags string `json:"tags"`
|
||||||
ExceptTags string `json:"except_tags"`
|
ExceptTags string `json:"except_tags"`
|
||||||
TagsAny string `json:"tags_any"`
|
TagsAny string `json:"tags_any"`
|
||||||
ExceptTagsAny string `json:"except_tags_any"`
|
ExceptTagsAny string `json:"except_tags_any"`
|
||||||
Actions []*Action `json:"actions"`
|
ExternalScriptEnabled bool `json:"external_script_enabled"`
|
||||||
Indexers []Indexer `json:"indexers"`
|
ExternalScriptCmd string `json:"external_script_cmd"`
|
||||||
Downloads *FilterDownloads `json:"-"`
|
ExternalScriptArgs string `json:"external_script_args"`
|
||||||
|
ExternalScriptExpectStatus int `json:"external_script_expect_status"`
|
||||||
|
ExternalWebhookEnabled bool `json:"external_webhook_enabled"`
|
||||||
|
ExternalWebhookHost string `json:"external_webhook_host"`
|
||||||
|
ExternalWebhookData string `json:"external_webhook_data"`
|
||||||
|
ExternalWebhookExpectStatus int `json:"external_webhook_expect_status"`
|
||||||
|
Actions []*Action `json:"actions"`
|
||||||
|
Indexers []Indexer `json:"indexers"`
|
||||||
|
Downloads *FilterDownloads `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f Filter) CheckFilter(r *Release) ([]string, bool) {
|
func (f Filter) CheckFilter(r *Release) ([]string, bool) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package action
|
package domain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -6,7 +6,6 @@ import (
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/autobrr/autobrr/internal/domain"
|
|
||||||
"github.com/autobrr/autobrr/pkg/errors"
|
"github.com/autobrr/autobrr/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -32,7 +31,7 @@ type Macro struct {
|
||||||
CurrentSecond int
|
CurrentSecond int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMacro(release domain.Release) Macro {
|
func NewMacro(release Release) Macro {
|
||||||
currentTime := time.Now()
|
currentTime := time.Now()
|
||||||
|
|
||||||
ma := Macro{
|
ma := Macro{
|
|
@ -1,12 +1,10 @@
|
||||||
package action
|
package domain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/autobrr/autobrr/internal/domain"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -25,14 +23,14 @@ func TestMacros_Parse(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
fields fields
|
fields fields
|
||||||
release domain.Release
|
release Release
|
||||||
args args
|
args args
|
||||||
want string
|
want string
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "test_ok",
|
name: "test_ok",
|
||||||
release: domain.Release{
|
release: Release{
|
||||||
TorrentName: "This movie 2021",
|
TorrentName: "This movie 2021",
|
||||||
TorrentTmpFile: "/tmp/a-temporary-file.torrent",
|
TorrentTmpFile: "/tmp/a-temporary-file.torrent",
|
||||||
Indexer: "mock1",
|
Indexer: "mock1",
|
||||||
|
@ -43,7 +41,7 @@ func TestMacros_Parse(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "test_bad",
|
name: "test_bad",
|
||||||
release: domain.Release{
|
release: Release{
|
||||||
TorrentName: "This movie 2021",
|
TorrentName: "This movie 2021",
|
||||||
TorrentTmpFile: "/tmp/a-temporary-file.torrent",
|
TorrentTmpFile: "/tmp/a-temporary-file.torrent",
|
||||||
Indexer: "mock1",
|
Indexer: "mock1",
|
||||||
|
@ -54,7 +52,7 @@ func TestMacros_Parse(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "test_program_arg",
|
name: "test_program_arg",
|
||||||
release: domain.Release{
|
release: Release{
|
||||||
TorrentName: "This movie 2021",
|
TorrentName: "This movie 2021",
|
||||||
TorrentTmpFile: "/tmp/a-temporary-file.torrent",
|
TorrentTmpFile: "/tmp/a-temporary-file.torrent",
|
||||||
Indexer: "mock1",
|
Indexer: "mock1",
|
||||||
|
@ -65,7 +63,7 @@ func TestMacros_Parse(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "test_program_arg_bad",
|
name: "test_program_arg_bad",
|
||||||
release: domain.Release{
|
release: Release{
|
||||||
TorrentTmpFile: "/tmp/a-temporary-file.torrent",
|
TorrentTmpFile: "/tmp/a-temporary-file.torrent",
|
||||||
Indexer: "mock1",
|
Indexer: "mock1",
|
||||||
},
|
},
|
||||||
|
@ -75,7 +73,7 @@ func TestMacros_Parse(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "test_program_arg",
|
name: "test_program_arg",
|
||||||
release: domain.Release{
|
release: Release{
|
||||||
TorrentName: "This movie 2021",
|
TorrentName: "This movie 2021",
|
||||||
TorrentTmpFile: "/tmp/a-temporary-file.torrent",
|
TorrentTmpFile: "/tmp/a-temporary-file.torrent",
|
||||||
Indexer: "mock1",
|
Indexer: "mock1",
|
||||||
|
@ -86,7 +84,7 @@ func TestMacros_Parse(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "test_args_long",
|
name: "test_args_long",
|
||||||
release: domain.Release{
|
release: Release{
|
||||||
TorrentName: "This movie 2021",
|
TorrentName: "This movie 2021",
|
||||||
TorrentURL: "https://some.site/download/fakeid",
|
TorrentURL: "https://some.site/download/fakeid",
|
||||||
Indexer: "mock1",
|
Indexer: "mock1",
|
||||||
|
@ -97,7 +95,7 @@ func TestMacros_Parse(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "test_args_long_1",
|
name: "test_args_long_1",
|
||||||
release: domain.Release{
|
release: Release{
|
||||||
TorrentName: "This movie 2021",
|
TorrentName: "This movie 2021",
|
||||||
TorrentURL: "https://some.site/download/fakeid",
|
TorrentURL: "https://some.site/download/fakeid",
|
||||||
Indexer: "mock1",
|
Indexer: "mock1",
|
||||||
|
@ -108,7 +106,7 @@ func TestMacros_Parse(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "test_args_category",
|
name: "test_args_category",
|
||||||
release: domain.Release{
|
release: Release{
|
||||||
TorrentName: "This movie 2021",
|
TorrentName: "This movie 2021",
|
||||||
TorrentURL: "https://some.site/download/fakeid",
|
TorrentURL: "https://some.site/download/fakeid",
|
||||||
Indexer: "mock1",
|
Indexer: "mock1",
|
||||||
|
@ -119,7 +117,7 @@ func TestMacros_Parse(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "test_args_category_year",
|
name: "test_args_category_year",
|
||||||
release: domain.Release{
|
release: Release{
|
||||||
TorrentName: "This movie 2021",
|
TorrentName: "This movie 2021",
|
||||||
TorrentURL: "https://some.site/download/fakeid",
|
TorrentURL: "https://some.site/download/fakeid",
|
||||||
Indexer: "mock1",
|
Indexer: "mock1",
|
||||||
|
@ -130,7 +128,7 @@ func TestMacros_Parse(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "test_args_category_year",
|
name: "test_args_category_year",
|
||||||
release: domain.Release{
|
release: Release{
|
||||||
TorrentName: "This movie 2021",
|
TorrentName: "This movie 2021",
|
||||||
TorrentURL: "https://some.site/download/fakeid",
|
TorrentURL: "https://some.site/download/fakeid",
|
||||||
Indexer: "mock1",
|
Indexer: "mock1",
|
||||||
|
@ -143,7 +141,7 @@ func TestMacros_Parse(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "test_args_category_and_if",
|
name: "test_args_category_and_if",
|
||||||
release: domain.Release{
|
release: Release{
|
||||||
TorrentName: "This movie 2021",
|
TorrentName: "This movie 2021",
|
||||||
TorrentURL: "https://some.site/download/fakeid",
|
TorrentURL: "https://some.site/download/fakeid",
|
||||||
Indexer: "mock1",
|
Indexer: "mock1",
|
||||||
|
@ -156,7 +154,7 @@ func TestMacros_Parse(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "test_release_year_1",
|
name: "test_release_year_1",
|
||||||
release: domain.Release{
|
release: Release{
|
||||||
TorrentName: "This movie 2021",
|
TorrentName: "This movie 2021",
|
||||||
TorrentURL: "https://some.site/download/fakeid",
|
TorrentURL: "https://some.site/download/fakeid",
|
||||||
Indexer: "mock1",
|
Indexer: "mock1",
|
|
@ -344,6 +344,10 @@ func (r *Release) addRejection(reason string) {
|
||||||
r.Rejections = append(r.Rejections, reason)
|
r.Rejections = append(r.Rejections, reason)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Release) AddRejectionF(format string, v ...interface{}) {
|
||||||
|
r.addRejectionF(format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Release) addRejectionF(format string, v ...interface{}) {
|
func (r *Release) addRejectionF(format string, v ...interface{}) {
|
||||||
r.Rejections = append(r.Rejections, fmt.Sprintf(format, v...))
|
r.Rejections = append(r.Rejections, fmt.Sprintf(format, v...))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,22 @@
|
||||||
package filter
|
package filter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/autobrr/autobrr/internal/domain"
|
"github.com/autobrr/autobrr/internal/domain"
|
||||||
"github.com/autobrr/autobrr/internal/indexer"
|
"github.com/autobrr/autobrr/internal/indexer"
|
||||||
"github.com/autobrr/autobrr/internal/logger"
|
"github.com/autobrr/autobrr/internal/logger"
|
||||||
|
"github.com/autobrr/autobrr/pkg/errors"
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
|
"github.com/mattn/go-shellwords"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -269,6 +276,37 @@ func (s *service) CheckFilter(f domain.Filter, release *domain.Release) (bool, e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// run external script
|
||||||
|
if f.ExternalScriptEnabled && f.ExternalScriptCmd != "" {
|
||||||
|
exitCode, err := s.execCmd(release, f.ExternalScriptCmd, f.ExternalScriptArgs)
|
||||||
|
if err != nil {
|
||||||
|
s.log.Error().Err(err).Msgf("filter.Service.CheckFilter: error executing external command for filter: %+v", f.Name)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if exitCode != f.ExternalScriptExpectStatus {
|
||||||
|
s.log.Trace().Msgf("filter.Service.CheckFilter: external script unexpected exit code. got: %v want: %v", exitCode, f.ExternalScriptExpectStatus)
|
||||||
|
release.AddRejectionF("external script unexpected exit code. got: %v want: %v", exitCode, f.ExternalScriptExpectStatus)
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// run external webhook
|
||||||
|
if f.ExternalWebhookEnabled && f.ExternalWebhookHost != "" && f.ExternalWebhookData != "" {
|
||||||
|
// run external scripts
|
||||||
|
statusCode, err := s.webhook(release, f.ExternalWebhookHost, f.ExternalWebhookData)
|
||||||
|
if err != nil {
|
||||||
|
s.log.Error().Err(err).Msgf("filter.Service.CheckFilter: error executing external webhook for filter: %v", f.Name)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if statusCode != f.ExternalWebhookExpectStatus {
|
||||||
|
s.log.Trace().Msgf("filter.Service.CheckFilter: external webhook unexpected status code. got: %v want: %v", statusCode, f.ExternalWebhookExpectStatus)
|
||||||
|
release.AddRejectionF("external webhook unexpected status code. got: %v want: %v", statusCode, f.ExternalWebhookExpectStatus)
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// found matching filter, lets find the filter actions and attach
|
// found matching filter, lets find the filter actions and attach
|
||||||
actions, err := s.actionRepo.FindByFilterID(context.TODO(), f.ID)
|
actions, err := s.actionRepo.FindByFilterID(context.TODO(), f.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -279,7 +317,7 @@ func (s *service) CheckFilter(f domain.Filter, release *domain.Release) (bool, e
|
||||||
// if no actions, continue to next filter
|
// if no actions, continue to next filter
|
||||||
if len(actions) == 0 {
|
if len(actions) == 0 {
|
||||||
s.log.Trace().Msgf("filter.Service.CheckFilter: no actions found for filter '%v', trying next one..", f.Name)
|
s.log.Trace().Msgf("filter.Service.CheckFilter: no actions found for filter '%v', trying next one..", f.Name)
|
||||||
return false, err
|
return false, nil
|
||||||
}
|
}
|
||||||
release.Filter.Actions = actions
|
release.Filter.Actions = actions
|
||||||
|
|
||||||
|
@ -371,3 +409,102 @@ func checkSizeFilter(minSize string, maxSize string, releaseSize uint64) (bool,
|
||||||
|
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *service) execCmd(release *domain.Release, cmd string, args string) (int, error) {
|
||||||
|
s.log.Debug().Msgf("filter exec release: %v", release.TorrentName)
|
||||||
|
|
||||||
|
if release.TorrentTmpFile == "" && strings.Contains(args, "TorrentPathName") {
|
||||||
|
if err := release.DownloadTorrentFile(); err != nil {
|
||||||
|
return 0, errors.Wrap(err, "error downloading torrent file for release: %v", release.TorrentName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if program exists
|
||||||
|
cmd, err := exec.LookPath(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.Wrap(err, "exec failed, could not find program: %v", cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle args and replace vars
|
||||||
|
m := domain.NewMacro(*release)
|
||||||
|
|
||||||
|
// parse and replace values in argument string before continuing
|
||||||
|
parsedArgs, err := m.Parse(args)
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.Wrap(err, "could not parse macro")
|
||||||
|
}
|
||||||
|
|
||||||
|
// we need to split on space into a string slice, so we can spread the args into exec
|
||||||
|
p := shellwords.NewParser()
|
||||||
|
p.ParseBacktick = true
|
||||||
|
commandArgs, err := p.Parse(parsedArgs)
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.Wrap(err, "could not parse into shell-words")
|
||||||
|
}
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
// setup command and args
|
||||||
|
command := exec.Command(cmd, commandArgs...)
|
||||||
|
|
||||||
|
err = command.Run()
|
||||||
|
var exitErr *exec.ExitError
|
||||||
|
if errors.As(err, &exitErr) {
|
||||||
|
s.log.Debug().Msgf("filter script command exited with non zero code: %v", exitErr.ExitCode())
|
||||||
|
return exitErr.ExitCode(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
duration := time.Since(start)
|
||||||
|
|
||||||
|
s.log.Debug().Msgf("executed external script: (%v), args: (%v) for release: (%v) indexer: (%v) total time (%v)", cmd, args, release.TorrentName, release.Indexer, duration)
|
||||||
|
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) webhook(release *domain.Release, url string, data string) (int, error) {
|
||||||
|
|
||||||
|
if release.TorrentTmpFile == "" && strings.Contains(data, "TorrentPathName") {
|
||||||
|
if err := release.DownloadTorrentFile(); err != nil {
|
||||||
|
return 0, errors.Wrap(err, "webhook: could not download torrent file for release: %v", release.TorrentName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m := domain.NewMacro(*release)
|
||||||
|
|
||||||
|
// parse and replace values in argument string before continuing
|
||||||
|
dataArgs, err := m.Parse(data)
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.Wrap(err, "could not parse webhook data macro: %v", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
t := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
client := http.Client{Transport: t, Timeout: 15 * time.Second}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodPost, url, bytes.NewBufferString(dataArgs))
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.Wrap(err, "could not build request for webhook")
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("User-Agent", "autobrr")
|
||||||
|
|
||||||
|
res, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.Wrap(err, "could not make request for webhook")
|
||||||
|
}
|
||||||
|
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
if res.StatusCode > 299 {
|
||||||
|
return res.StatusCode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
s.log.Debug().Msgf("successfully ran external webhook filter to: (%v) payload: (%v)", url, dataArgs)
|
||||||
|
|
||||||
|
return res.StatusCode, nil
|
||||||
|
}
|
||||||
|
|
|
@ -114,8 +114,6 @@ func (s *service) Process(release *domain.Release) {
|
||||||
release.FilterName = f.Name
|
release.FilterName = f.Name
|
||||||
release.FilterID = f.ID
|
release.FilterID = f.ID
|
||||||
|
|
||||||
// TODO filter limit checks
|
|
||||||
|
|
||||||
// test filter
|
// test filter
|
||||||
match, err := s.filterSvc.CheckFilter(f, release)
|
match, err := s.filterSvc.CheckFilter(f, release)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -13,6 +13,7 @@ interface TextFieldProps {
|
||||||
columns?: COL_WIDTHS;
|
columns?: COL_WIDTHS;
|
||||||
autoComplete?: string;
|
autoComplete?: string;
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TextField = ({
|
export const TextField = ({
|
||||||
|
@ -22,7 +23,8 @@ export const TextField = ({
|
||||||
placeholder,
|
placeholder,
|
||||||
columns,
|
columns,
|
||||||
autoComplete,
|
autoComplete,
|
||||||
hidden
|
hidden,
|
||||||
|
disabled,
|
||||||
}: TextFieldProps) => (
|
}: TextFieldProps) => (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
|
@ -47,7 +49,12 @@ export const TextField = ({
|
||||||
type="text"
|
type="text"
|
||||||
defaultValue={defaultValue}
|
defaultValue={defaultValue}
|
||||||
autoComplete={autoComplete}
|
autoComplete={autoComplete}
|
||||||
className={classNames(meta.touched && meta.error ? "focus:ring-red-500 focus:border-red-500 border-red-500" : "focus:ring-indigo-500 dark:focus:ring-blue-500 focus:border-indigo-500 dark:focus:border-blue-500 border-gray-300 dark:border-gray-700", "mt-2 block w-full dark:bg-gray-800 dark:text-gray-100 rounded-md")}
|
className={classNames(
|
||||||
|
meta.touched && meta.error ? "focus:ring-red-500 focus:border-red-500 border-red-500" : "focus:ring-indigo-500 dark:focus:ring-blue-500 focus:border-indigo-500 dark:focus:border-blue-500 border-gray-300 dark:border-gray-700",
|
||||||
|
disabled ? "bg-gray-100 dark:bg-gray-700 cursor-not-allowed" : "dark:bg-gray-800",
|
||||||
|
"mt-2 block w-full dark:text-gray-100 rounded-md",
|
||||||
|
)}
|
||||||
|
disabled={disabled}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -60,6 +67,70 @@ export const TextField = ({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
interface TextAreaProps {
|
||||||
|
name: string;
|
||||||
|
defaultValue?: string;
|
||||||
|
label?: string;
|
||||||
|
placeholder?: string;
|
||||||
|
columns?: COL_WIDTHS;
|
||||||
|
rows?: number;
|
||||||
|
autoComplete?: string;
|
||||||
|
hidden?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TextArea = ({
|
||||||
|
name,
|
||||||
|
defaultValue,
|
||||||
|
label,
|
||||||
|
placeholder,
|
||||||
|
columns,
|
||||||
|
rows,
|
||||||
|
autoComplete,
|
||||||
|
hidden,
|
||||||
|
disabled,
|
||||||
|
}: TextAreaProps) => (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
hidden ? "hidden" : "",
|
||||||
|
columns ? `col-span-${columns}` : "col-span-12"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{label && (
|
||||||
|
<label htmlFor={name} className="block text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide">
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
<Field name={name}>
|
||||||
|
{({
|
||||||
|
field,
|
||||||
|
meta
|
||||||
|
}: FieldProps) => (
|
||||||
|
<div>
|
||||||
|
<textarea
|
||||||
|
{...field}
|
||||||
|
id={name}
|
||||||
|
rows={rows}
|
||||||
|
defaultValue={defaultValue}
|
||||||
|
autoComplete={autoComplete}
|
||||||
|
className={classNames(
|
||||||
|
meta.touched && meta.error ? "focus:ring-red-500 focus:border-red-500 border-red-500" : "focus:ring-indigo-500 dark:focus:ring-blue-500 focus:border-indigo-500 dark:focus:border-blue-500 border-gray-300 dark:border-gray-700",
|
||||||
|
disabled ? "bg-gray-100 dark:bg-gray-700 cursor-not-allowed" : "dark:bg-gray-800",
|
||||||
|
"mt-2 block w-full dark:text-gray-100 rounded-md",
|
||||||
|
)}
|
||||||
|
placeholder={placeholder}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{meta.touched && meta.error && (
|
||||||
|
<p className="error text-sm text-red-600 mt-1">* {meta.error}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
interface PasswordFieldProps {
|
interface PasswordFieldProps {
|
||||||
name: string;
|
name: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
|
@ -132,13 +203,15 @@ interface NumberFieldProps {
|
||||||
label?: string;
|
label?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
step?: number;
|
step?: number;
|
||||||
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NumberField = ({
|
export const NumberField = ({
|
||||||
name,
|
name,
|
||||||
label,
|
label,
|
||||||
placeholder,
|
placeholder,
|
||||||
step
|
step,
|
||||||
|
disabled,
|
||||||
}: NumberFieldProps) => (
|
}: NumberFieldProps) => (
|
||||||
<div className="col-span-12 sm:col-span-6">
|
<div className="col-span-12 sm:col-span-6">
|
||||||
<label htmlFor={name} className="block text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide">
|
<label htmlFor={name} className="block text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide">
|
||||||
|
@ -159,9 +232,11 @@ export const NumberField = ({
|
||||||
meta.touched && meta.error
|
meta.touched && meta.error
|
||||||
? "focus:ring-red-500 focus:border-red-500 border-red-500"
|
? "focus:ring-red-500 focus:border-red-500 border-red-500"
|
||||||
: "focus:ring-indigo-500 dark:focus:ring-blue-500 focus:border-indigo-500 dark:focus:border-blue-500 border-gray-300",
|
: "focus:ring-indigo-500 dark:focus:ring-blue-500 focus:border-indigo-500 dark:focus:border-blue-500 border-gray-300",
|
||||||
"mt-2 block w-full dark:bg-gray-800 border border-gray-300 dark:border-gray-700 dark:text-gray-100 rounded-md"
|
"mt-2 block w-full border border-gray-300 dark:border-gray-700 dark:text-gray-100 rounded-md",
|
||||||
|
disabled ? "bg-gray-100 dark:bg-gray-700 cursor-not-allowed" : "dark:bg-gray-800",
|
||||||
)}
|
)}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
{meta.touched && meta.error && (
|
{meta.touched && meta.error && (
|
||||||
<div className="error">{meta.error}</div>
|
<div className="error">{meta.error}</div>
|
||||||
|
|
|
@ -74,16 +74,18 @@ interface SwitchGroupProps {
|
||||||
label?: string;
|
label?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
heading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SwitchGroup = ({
|
const SwitchGroup = ({
|
||||||
name,
|
name,
|
||||||
label,
|
label,
|
||||||
description
|
description,
|
||||||
|
heading,
|
||||||
}: SwitchGroupProps) => (
|
}: SwitchGroupProps) => (
|
||||||
<HeadlessSwitch.Group as="ol" className="py-4 flex items-center justify-between">
|
<HeadlessSwitch.Group as="ol" className="py-4 flex items-center justify-between">
|
||||||
{label && <div className="flex flex-col">
|
{label && <div className="flex flex-col">
|
||||||
<HeadlessSwitch.Label as="p" className="text-sm font-medium text-gray-900 dark:text-gray-100"
|
<HeadlessSwitch.Label as={heading ? "h2" : "p"} className={classNames("font-medium text-gray-900 dark:text-gray-100", heading ? "text-lg" : "text-sm")}
|
||||||
passive>
|
passive>
|
||||||
{label}
|
{label}
|
||||||
</HeadlessSwitch.Label>
|
</HeadlessSwitch.Label>
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
useParams
|
useParams
|
||||||
} from "react-router-dom";
|
} from "react-router-dom";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import { Field, FieldArray, FieldProps, Form, Formik, FormikValues } from "formik";
|
import { Field, FieldArray, FieldProps, Form, Formik, FormikValues, useFormikContext } from "formik";
|
||||||
import { Dialog, Transition, Switch as SwitchBasic } from "@headlessui/react";
|
import { Dialog, Transition, Switch as SwitchBasic } from "@headlessui/react";
|
||||||
import { ChevronDownIcon, ChevronRightIcon } from "@heroicons/react/solid";
|
import { ChevronDownIcon, ChevronRightIcon } from "@heroicons/react/solid";
|
||||||
|
|
||||||
|
@ -50,6 +50,7 @@ import { AlertWarning } from "../../components/alerts";
|
||||||
import { DeleteModal } from "../../components/modals";
|
import { DeleteModal } from "../../components/modals";
|
||||||
import { TitleSubtitle } from "../../components/headings";
|
import { TitleSubtitle } from "../../components/headings";
|
||||||
import { EmptyListState } from "../../components/emptystates";
|
import { EmptyListState } from "../../components/emptystates";
|
||||||
|
import { TextArea } from "../../components/inputs/input";
|
||||||
|
|
||||||
interface tabType {
|
interface tabType {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -61,6 +62,7 @@ const tabs: tabType[] = [
|
||||||
{ name: "Movies and TV", href: "movies-tv" },
|
{ name: "Movies and TV", href: "movies-tv" },
|
||||||
{ name: "Music", href: "music" },
|
{ name: "Music", href: "music" },
|
||||||
{ name: "Advanced", href: "advanced" },
|
{ name: "Advanced", href: "advanced" },
|
||||||
|
{ name: "External", href: "external" },
|
||||||
{ name: "Actions", href: "actions" }
|
{ name: "Actions", href: "actions" }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -280,7 +282,15 @@ export default function FilterDetails() {
|
||||||
albums: filter.albums,
|
albums: filter.albums,
|
||||||
origins: filter.origins || [],
|
origins: filter.origins || [],
|
||||||
indexers: filter.indexers || [],
|
indexers: filter.indexers || [],
|
||||||
actions: filter.actions || []
|
actions: filter.actions || [],
|
||||||
|
external_script_enabled: filter.external_script_enabled || false,
|
||||||
|
external_script_cmd: filter.external_script_cmd || "",
|
||||||
|
external_script_args: filter.external_script_args || "",
|
||||||
|
external_script_expect_status: filter.external_script_expect_status || 0,
|
||||||
|
external_webhook_enabled: filter.external_webhook_enabled || false,
|
||||||
|
external_webhook_host: filter.external_webhook_host || "",
|
||||||
|
external_webhook_data: filter.external_webhook_data ||"",
|
||||||
|
external_webhook_expect_status: filter.external_webhook_expect_status || 0,
|
||||||
} as Filter}
|
} as Filter}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
>
|
>
|
||||||
|
@ -291,6 +301,7 @@ export default function FilterDetails() {
|
||||||
<Route path="movies-tv" element={<MoviesTv />} />
|
<Route path="movies-tv" element={<MoviesTv />} />
|
||||||
<Route path="music" element={<Music />} />
|
<Route path="music" element={<Music />} />
|
||||||
<Route path="advanced" element={<Advanced />} />
|
<Route path="advanced" element={<Advanced />} />
|
||||||
|
<Route path="external" element={<External />} />
|
||||||
<Route path="actions" element={<FilterActions filter={filter} values={values} />}
|
<Route path="actions" element={<FilterActions filter={filter} values={values} />}
|
||||||
/>
|
/>
|
||||||
</Routes>
|
</Routes>
|
||||||
|
@ -527,6 +538,75 @@ function CollapsableSection({ title, subtitle, children }: CollapsableSectionPro
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function External() {
|
||||||
|
const { values } = useFormikContext<Filter>();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
|
||||||
|
<div className="mt-6">
|
||||||
|
<SwitchGroup name="external_script_enabled" heading={true} label="Script" description="Run external script and check status as part of filtering" />
|
||||||
|
|
||||||
|
<div className="mt-6 grid grid-cols-12 gap-6">
|
||||||
|
<TextField
|
||||||
|
name="external_script_cmd"
|
||||||
|
label="Command"
|
||||||
|
columns={6}
|
||||||
|
placeholder="Path to program eg. /bin/test"
|
||||||
|
disabled={!values.external_script_enabled}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
name="external_script_args"
|
||||||
|
label="Arguments"
|
||||||
|
columns={6}
|
||||||
|
placeholder="Arguments eg. --test"
|
||||||
|
disabled={!values.external_script_enabled}
|
||||||
|
/>
|
||||||
|
<NumberField
|
||||||
|
name="external_script_expect_status"
|
||||||
|
label="Expected exit status"
|
||||||
|
placeholder="0"
|
||||||
|
disabled={!values.external_script_enabled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6">
|
||||||
|
<div className="border-t dark:border-gray-700">
|
||||||
|
<SwitchGroup name="external_webhook_enabled" heading={true} label="Webhook" description="Run external webhook and check status as part of filtering" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6 grid grid-cols-12 gap-6">
|
||||||
|
<div className="grid col-span-6 gap-6">
|
||||||
|
<TextField
|
||||||
|
name="external_webhook_host"
|
||||||
|
label="Host"
|
||||||
|
columns={6}
|
||||||
|
placeholder="Host eg. http://localhost/webhook"
|
||||||
|
disabled={!values.external_webhook_enabled}
|
||||||
|
/>
|
||||||
|
<NumberField
|
||||||
|
name="external_webhook_expect_status"
|
||||||
|
label="Expected http status"
|
||||||
|
placeholder="200"
|
||||||
|
disabled={!values.external_webhook_enabled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<TextArea
|
||||||
|
name="external_webhook_data"
|
||||||
|
label="Data (json)"
|
||||||
|
columns={6}
|
||||||
|
rows={5}
|
||||||
|
placeholder={"{ \"key\": \"value\" }"}
|
||||||
|
disabled={!values.external_webhook_enabled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
interface FilterActionsProps {
|
interface FilterActionsProps {
|
||||||
filter: Filter;
|
filter: Filter;
|
||||||
values: FormikValues;
|
values: FormikValues;
|
||||||
|
|
8
web/src/types/Filter.d.ts
vendored
8
web/src/types/Filter.d.ts
vendored
|
@ -52,6 +52,14 @@ interface Filter {
|
||||||
except_tags_any: string;
|
except_tags_any: string;
|
||||||
actions: Action[];
|
actions: Action[];
|
||||||
indexers: Indexer[];
|
indexers: Indexer[];
|
||||||
|
external_script_enabled: boolean;
|
||||||
|
external_script_cmd: string;
|
||||||
|
external_script_args: string;
|
||||||
|
external_script_expect_status: number;
|
||||||
|
external_webhook_enabled: boolean;
|
||||||
|
external_webhook_host: string;
|
||||||
|
external_webhook_data: string;
|
||||||
|
external_webhook_expect_status: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Action {
|
interface Action {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue