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:
Ben Beauchamp
2025-04-11 09:48:44 -05:00
committed by GitHub
parent a488f850f3
commit 4a5ac3cc42
21 changed files with 1105 additions and 100 deletions

View File

@@ -68,11 +68,14 @@ const messages: { [messageName: string]: MessageDescriptor } = defineMessages(
'download-sync': 'Download Sync',
'download-sync-reset': 'Download Sync Reset',
'image-cache-cleanup': 'Image Cache Cleanup',
'process-blacklisted-tags': 'Process Blacklisted Tags',
editJobSchedule: 'Modify Job',
jobScheduleEditSaved: 'Job edited successfully!',
jobScheduleEditFailed: 'Something went wrong while saving the job.',
editJobScheduleCurrent: 'Current Frequency',
editJobSchedulePrompt: 'New Frequency',
editJobScheduleSelectorDays:
'Every {jobScheduleDays, plural, one {day} other {{jobScheduleDays} days}}',
editJobScheduleSelectorHours:
'Every {jobScheduleHours, plural, one {hour} other {{jobScheduleHours} hours}}',
editJobScheduleSelectorMinutes:
@@ -92,7 +95,7 @@ interface Job {
id: JobId;
name: string;
type: 'process' | 'command';
interval: 'seconds' | 'minutes' | 'hours' | 'fixed';
interval: 'seconds' | 'minutes' | 'hours' | 'days' | 'fixed';
cronSchedule: string;
nextExecutionTime: string;
running: boolean;
@@ -101,13 +104,20 @@ interface Job {
type JobModalState = {
isOpen?: boolean;
job?: Job;
scheduleDays: number;
scheduleHours: number;
scheduleMinutes: number;
scheduleSeconds: number;
};
type JobModalAction =
| { type: 'set'; hours?: number; minutes?: number; seconds?: number }
| {
type: 'set';
days?: number;
hours?: number;
minutes?: number;
seconds?: number;
}
| {
type: 'close';
}
@@ -128,6 +138,7 @@ const jobModalReducer = (
return {
isOpen: true,
job: action.job,
scheduleDays: 1,
scheduleHours: 1,
scheduleMinutes: 5,
scheduleSeconds: 30,
@@ -136,6 +147,7 @@ const jobModalReducer = (
case 'set':
return {
...state,
scheduleDays: action.days ?? state.scheduleDays,
scheduleHours: action.hours ?? state.scheduleHours,
scheduleMinutes: action.minutes ?? state.scheduleMinutes,
scheduleSeconds: action.seconds ?? state.scheduleSeconds,
@@ -164,6 +176,7 @@ const SettingsJobs = () => {
const [jobModalState, dispatch] = useReducer(jobModalReducer, {
isOpen: false,
scheduleDays: 1,
scheduleHours: 1,
scheduleMinutes: 5,
scheduleSeconds: 30,
@@ -239,6 +252,9 @@ const SettingsJobs = () => {
jobScheduleCron[1] = `*/${jobModalState.scheduleMinutes}`;
} else if (jobModalState.job?.interval === 'hours') {
jobScheduleCron[2] = `*/${jobModalState.scheduleHours}`;
} else if (jobModalState.job?.interval === 'days') {
jobScheduleCron[2] = '1';
jobScheduleCron[3] = `*/${jobModalState.scheduleDays}`;
} else {
// jobs with interval: fixed should not be editable
throw new Error();
@@ -367,6 +383,29 @@ const SettingsJobs = () => {
</option>
))}
</select>
) : jobModalState.job?.interval === 'days' ? (
<select
name="jobScheduleDays"
className="inline"
value={jobModalState.scheduleDays}
onChange={(e) =>
dispatch({
type: 'set',
days: Number(e.target.value),
})
}
>
{[1, 2, 3, 4, 5, 6, 7, 10, 14, 21].map((v) => (
<option value={v} key={`jobScheduleDays-${v}`}>
{intl.formatMessage(
messages.editJobScheduleSelectorDays,
{
jobScheduleDays: v,
}
)}
</option>
))}
</select>
) : (
<select
name="jobScheduleHours"