feat(blacklist): Automatically add media with blacklisted tags to the blacklist (#1306)
* feat(blacklist): add blacktag settings to main settings page * feat(blacklist): create blacktag logic and infrastructure * feat(blacklist): add scheduling for blacktags job * feat(blacklist): create blacktag ui badge for blacklist * docs(blacklist): document blacktags in using-jellyseerr * fix(blacklist): batch blacklist and media db removes to avoid expression tree too large error * feat(blacklist): allow easy import and export of blacktag configuration * fix(settings): don't copy the API key every time you press enter on the main settings * fix(blacklist): move filter inline with page title to match all the other pages * feat(blacklist): allow filtering between manually blacklisted and automatically blacklisted entries * docs(blacklist): reword blacktag documentation a little * refactor(blacklist): remove blacktag settings from public settings interfaces There's no reason for it to be there * refactor(blacklist): remove unused variable from processResults in blacktagsProcessor * refactor(blacklist): change all instances of blacktag to blacklistedTag and update doc to match * docs(blacklist): update general documentation for blacklisted tag settings * fix(blacklist): update setting use of "blacklisted tag" to match between modals * perf(blacklist): remove media type constraint from existing blacklist entry query Doesn't make sense to keep it because tmdbid has a unique constraint on it * fix(blacklist): remove whitespace line causing prettier to fail in CI * refactor(blacklist): swap out some != and == for !s and _s * fix(blacklist): merge back CopyButton changes, disable button when there's nothing to copy * refactor(blacklist): use axios instead of fetch for blacklisted tag queries * style(blacklist): use templated axios types and remove redundant try-catches
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import BlacklistedTagsSelector from '@app/components/BlacklistedTagsSelector';
|
||||
import Button from '@app/components/Common/Button';
|
||||
import LoadingSpinner from '@app/components/Common/LoadingSpinner';
|
||||
import PageTitle from '@app/components/Common/PageTitle';
|
||||
@@ -29,12 +30,19 @@ const messages = defineMessages('components.Settings.SettingsMain', {
|
||||
generalsettingsDescription:
|
||||
'Configure global and default settings for Jellyseerr.',
|
||||
apikey: 'API Key',
|
||||
apikeyCopied: 'Copied API key to clipboard.',
|
||||
applicationTitle: 'Application Title',
|
||||
applicationurl: 'Application URL',
|
||||
discoverRegion: 'Discover Region',
|
||||
discoverRegionTip: 'Filter content by regional availability',
|
||||
originallanguage: 'Discover Language',
|
||||
originallanguageTip: 'Filter content by original language',
|
||||
blacklistedTags: 'Blacklist Content with Tags',
|
||||
blacklistedTagsTip:
|
||||
'Automatically add content with tags to the blacklist using the "Process Blacklisted Tags" job',
|
||||
blacklistedTagsLimit: 'Limit Content Blacklisted per Tag',
|
||||
blacklistedTagsLimitTip:
|
||||
'The "Process Blacklisted Tags" job will blacklist this many pages into each sort. Larger numbers will create a more accurate blacklist, but use more space.',
|
||||
streamingRegion: 'Streaming Region',
|
||||
streamingRegionTip: 'Show streaming sites by regional availability',
|
||||
toastApiKeySuccess: 'New API key generated successfully!',
|
||||
@@ -81,6 +89,17 @@ const SettingsMain = () => {
|
||||
intl.formatMessage(messages.validationApplicationUrlTrailingSlash),
|
||||
(value) => !value || !value.endsWith('/')
|
||||
),
|
||||
blacklistedTagsLimit: Yup.number()
|
||||
.test(
|
||||
'positive',
|
||||
'Number must be greater than 0.',
|
||||
(value) => (value ?? 0) >= 0
|
||||
)
|
||||
.test(
|
||||
'lte-250',
|
||||
'Number must be less than or equal to 250.',
|
||||
(value) => (value ?? 0) <= 250
|
||||
),
|
||||
});
|
||||
|
||||
const regenerate = async () => {
|
||||
@@ -130,6 +149,8 @@ const SettingsMain = () => {
|
||||
discoverRegion: data?.discoverRegion,
|
||||
originalLanguage: data?.originalLanguage,
|
||||
streamingRegion: data?.streamingRegion || 'US',
|
||||
blacklistedTags: data?.blacklistedTags,
|
||||
blacklistedTagsLimit: data?.blacklistedTagsLimit || 50,
|
||||
partialRequestsEnabled: data?.partialRequestsEnabled,
|
||||
enableSpecialEpisodes: data?.enableSpecialEpisodes,
|
||||
cacheImages: data?.cacheImages,
|
||||
@@ -146,6 +167,8 @@ const SettingsMain = () => {
|
||||
discoverRegion: values.discoverRegion,
|
||||
streamingRegion: values.streamingRegion,
|
||||
originalLanguage: values.originalLanguage,
|
||||
blacklistedTags: values.blacklistedTags,
|
||||
blacklistedTagsLimit: values.blacklistedTagsLimit,
|
||||
partialRequestsEnabled: values.partialRequestsEnabled,
|
||||
enableSpecialEpisodes: values.enableSpecialEpisodes,
|
||||
cacheImages: values.cacheImages,
|
||||
@@ -201,6 +224,9 @@ const SettingsMain = () => {
|
||||
/>
|
||||
<CopyButton
|
||||
textToCopy={data?.apiKey ?? ''}
|
||||
toastMessage={intl.formatMessage(
|
||||
messages.apikeyCopied
|
||||
)}
|
||||
key={data?.apiKey}
|
||||
/>
|
||||
<button
|
||||
@@ -209,6 +235,7 @@ const SettingsMain = () => {
|
||||
regenerate();
|
||||
}}
|
||||
className="input-action"
|
||||
type="button"
|
||||
>
|
||||
<ArrowPathIcon />
|
||||
</button>
|
||||
@@ -352,6 +379,49 @@ const SettingsMain = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="blacklistedTags" className="text-label">
|
||||
<span>{intl.formatMessage(messages.blacklistedTags)}</span>
|
||||
<span className="label-tip">
|
||||
{intl.formatMessage(messages.blacklistedTagsTip)}
|
||||
</span>
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<div className="form-input-field relative z-10">
|
||||
<BlacklistedTagsSelector
|
||||
defaultValue={values.blacklistedTags}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="blacklistedTagsLimit" className="text-label">
|
||||
<span className="mr-2">
|
||||
{intl.formatMessage(messages.blacklistedTagsLimit)}
|
||||
</span>
|
||||
<SettingsBadge badgeType="advanced" />
|
||||
<span className="label-tip">
|
||||
{intl.formatMessage(messages.blacklistedTagsLimitTip)}
|
||||
</span>
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<Field
|
||||
id="blacklistedTagsLimit"
|
||||
name="blacklistedTagsLimit"
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
className="short"
|
||||
placeholder={50}
|
||||
/>
|
||||
{errors.blacklistedTagsLimit &&
|
||||
touched.blacklistedTagsLimit &&
|
||||
typeof errors.blacklistedTagsLimit === 'string' && (
|
||||
<div className="error">
|
||||
{errors.blacklistedTagsLimit}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="hideAvailable" className="checkbox-label">
|
||||
<span className="mr-2">
|
||||
|
||||
Reference in New Issue
Block a user