fix(blacklist): handle invalid keywords gracefully

This commit is contained in:
0xsysr3ll
2025-07-28 23:51:00 +02:00
parent e98f31e66c
commit c36b6d5436
6 changed files with 97 additions and 21 deletions

View File

@@ -7273,6 +7273,26 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/Keyword'
'404':
description: Keyword not found
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: 'Keyword not found'
'500':
description: Internal server error
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: 'Unable to retrieve keyword data.'
/watchproviders/regions:
get:
summary: Get watch provider regions

View File

@@ -1064,6 +1064,9 @@ class TheMovieDb extends ExternalAPI {
return data;
} catch (e) {
if (e.response?.status === 404) {
throw e;
}
throw new Error(`[TMDB] Failed to fetch keyword: ${e.message}`);
}
}

View File

@@ -72,6 +72,7 @@ class BlacklistedTagProcessor implements RunnableScanner<StatusBase> {
const blacklistedTagsArr = blacklistedTags.split(',');
const pageLimit = settings.main.blacklistedTagsLimit;
const invalidKeywords = new Set<string>();
if (blacklistedTags.length === 0) {
return;
@@ -87,6 +88,17 @@ class BlacklistedTagProcessor implements RunnableScanner<StatusBase> {
// Iterate for each tag
for (const tag of blacklistedTagsArr) {
try {
await tmdb.getKeywordDetails({ keywordId: Number(tag) });
} catch (error) {
logger.warn('Skipping invalid keyword in blacklisted tags', {
label: 'Blacklisted Tags Processor',
keywordId: tag,
});
invalidKeywords.add(tag);
continue;
}
let queryMax = pageLimit * SortOptionsIterable.length;
let fixedSortMode = false; // Set to true when the page limit allows for getting every page of tag
@@ -102,24 +114,51 @@ class BlacklistedTagProcessor implements RunnableScanner<StatusBase> {
throw new AbortTransaction();
}
const response = await getDiscover({
page,
sortBy,
keywords: tag,
});
await this.processResults(response, tag, type, em);
await new Promise((res) => setTimeout(res, TMDB_API_DELAY_MS));
try {
const response = await getDiscover({
page,
sortBy,
keywords: tag,
});
this.progress++;
if (page === 1 && response.total_pages <= queryMax) {
// We will finish the tag with less queries than expected, move progress accordingly
this.progress += queryMax - response.total_pages;
fixedSortMode = true;
queryMax = response.total_pages;
await this.processResults(response, tag, type, em);
await new Promise((res) => setTimeout(res, TMDB_API_DELAY_MS));
this.progress++;
if (page === 1 && response.total_pages <= queryMax) {
// We will finish the tag with less queries than expected, move progress accordingly
this.progress += queryMax - response.total_pages;
fixedSortMode = true;
queryMax = response.total_pages;
}
} catch (error) {
logger.error('Error processing keyword in blacklisted tags', {
label: 'Blacklisted Tags Processor',
keywordId: tag,
errorMessage: error.message,
});
}
}
}
}
if (invalidKeywords.size > 0) {
const currentTags = blacklistedTagsArr.filter(
(tag) => !invalidKeywords.has(tag)
);
const cleanedTags = currentTags.join(',');
if (cleanedTags !== blacklistedTags) {
settings.main.blacklistedTags = cleanedTags;
await settings.save();
logger.info('Cleaned up invalid keywords from settings', {
label: 'Blacklisted Tags Processor',
removedKeywords: Array.from(invalidKeywords),
newBlacklistedTags: cleanedTags,
});
}
}
}
private async processResults(

View File

@@ -330,6 +330,10 @@ router.get('/keyword/:keywordId', async (req, res, next) => {
return res.status(200).json(result);
} catch (e) {
if (e.response?.status === 404) {
return res.status(404).json({ message: 'Keyword not found' });
}
logger.debug('Something went wrong retrieving keyword data', {
label: 'API',
errorMessage: e.message,

View File

@@ -35,11 +35,15 @@ const BlacklistedTagsBadge = ({ data }: BlacklistedTagsBadgeProps) => {
);
return data.name;
} catch (err) {
if (err.response?.status === 404) {
return `[Invalid: ${keywordId}]`;
}
return '';
}
})
).then((keywords) => {
setTagNamesBlacklistedFor(keywords.join(', '));
const validKeywords = keywords.filter((name) => name !== '');
setTagNamesBlacklistedFor(validKeywords.join(', '));
});
}, [data.blacklistedTags]);

View File

@@ -124,17 +124,23 @@ const ControlledKeywordSelector = ({
const keywords = await Promise.all(
defaultValue.split(',').map(async (keywordId) => {
const { data } = await axios.get<Keyword>(
`/api/v1/keyword/${keywordId}`
);
return data;
try {
const { data } = await axios.get<Keyword>(
`/api/v1/keyword/${keywordId}`
);
return data;
} catch (error) {
return null;
}
})
);
const validKeywords = keywords.filter((keyword) => keyword !== null);
onChange(
keywords.map((keyword) => ({
label: keyword.name,
value: keyword.id,
validKeywords.map((keyword) => ({
label: keyword!.name,
value: keyword!.id,
}))
);
};