Patch: Migrate bypasser to pure CDP + Misc fixes (#575)

Bypasser:
- Refactored internal bypasser logic to use SeleniumBase Pure CDP mode,
removed chromedriver dependencies and UC code.
- Added dedicated threading for internal bypasser functions, fixes any
potential asyncio CPU spike behavior
- Fixed WebGL issue with Chromium 144. Reverted 1.0.3 hotfix and updated
to latest Chromium

Misc: 
- Added M4A color mapping
- Fix frontend language filtering with multi-language releases
- Added "days" age for usenet/torrent releases
- Improved entrypoint chown efficiency
- Added `ONBOARDING` env variable, default true
This commit is contained in:
Alex
2026-02-02 20:32:19 +00:00
committed by GitHub
parent f6dba959c9
commit b10458a48b
14 changed files with 518 additions and 500 deletions
+3 -9
View File
@@ -165,7 +165,7 @@ export const ReleaseCell = ({ column, release, compact = false, onlineServers }:
const torznabAttrs = extra?.torznab_attrs as Record<string, string> | undefined;
const publishDate = extra?.publish_date as string | undefined;
// Helper to format relative time
// Helper to format age in days
const formatRelativeTime = (dateStr: string): string | null => {
try {
const date = new Date(dateStr);
@@ -174,14 +174,8 @@ export const ReleaseCell = ({ column, release, compact = false, onlineServers }:
const diffMs = now.getTime() - date.getTime();
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
if (diffDays === 0) return 'Today';
if (diffDays === 1) return '1 day ago';
if (diffDays < 30) return `${diffDays} days ago`;
const diffMonths = Math.floor(diffDays / 30);
if (diffMonths === 1) return '1 month ago';
if (diffMonths < 12) return `${diffMonths} months ago`;
const diffYears = Math.floor(diffDays / 365);
if (diffYears === 1) return '1 year ago';
return `${diffYears} years ago`;
if (diffDays === 1) return '1 day';
return `${diffDays} days`;
} catch {
return null;
}
+8 -3
View File
@@ -9,7 +9,7 @@ import { ReleaseCell } from './ReleaseCell';
import { getColorStyleFromHint } from '../utils/colorMaps';
import { getNestedValue } from '../utils/objectHelpers';
import { LanguageMultiSelect } from './LanguageMultiSelect';
import { LANGUAGE_OPTION_ALL, LANGUAGE_OPTION_DEFAULT, getLanguageFilterValues, releaseLanguageMatchesFilter } from '../utils/languageFilters';
import { LANGUAGE_OPTION_ALL, LANGUAGE_OPTION_DEFAULT, getLanguageFilterValues, releaseLanguageMatchesFilter, buildLanguageNormalizer } from '../utils/languageFilters';
// Module-level cache for release search results
// Key format: `${provider}:${provider_id}:${source}:${contentType}`
@@ -1069,6 +1069,11 @@ export const ReleaseModal = ({
return getLanguageFilterValues(languageFilter, bookLanguages, defaultLanguages);
}, [languageFilter, bookLanguages, defaultLanguages]);
// Build language normalizer for release filtering (handles both codes like "en" and names like "English")
const languageNormalizer = useMemo(() => {
return buildLanguageNormalizer(bookLanguages);
}, [bookLanguages]);
// Get column config from response or use default (moved before filteredReleases for sorting)
const columnConfig = useMemo((): ReleaseColumnConfig => {
const response = releasesBySource[activeTab];
@@ -1164,7 +1169,7 @@ export const ReleaseModal = ({
// Language filtering - use r.language when provided by enriched indexers
// Releases with no language (null/undefined) always pass
const releaseLang = r.language as string | undefined;
if (!releaseLanguageMatchesFilter(releaseLang, resolvedLanguageCodes ?? defaultLanguages)) {
if (!releaseLanguageMatchesFilter(releaseLang, resolvedLanguageCodes ?? defaultLanguages, languageNormalizer)) {
return false;
}
@@ -1184,7 +1189,7 @@ export const ReleaseModal = ({
}
return filtered;
}, [releasesBySource, activeTab, formatFilter, resolvedLanguageCodes, effectiveFormats, defaultLanguages, indexerFilter, currentSort, sortableColumns, columnConfig]);
}, [releasesBySource, activeTab, formatFilter, resolvedLanguageCodes, effectiveFormats, defaultLanguages, languageNormalizer, indexerFilter, currentSort, sortableColumns, columnConfig]);
// Pre-compute display field lookups to avoid repeated .find() calls in JSX
const displayFields = useMemo(() => {
+1
View File
@@ -17,6 +17,7 @@ const FORMAT_COLORS: Record<string, ColorStyle> = {
cbz: { bg: 'bg-amber-500/20', text: 'text-amber-700 dark:text-amber-300' },
// Audiobook formats
m4b: { bg: 'bg-violet-500/20', text: 'text-violet-700 dark:text-violet-300' },
m4a: { bg: 'bg-violet-500/20', text: 'text-violet-700 dark:text-violet-300' },
mp3: { bg: 'bg-rose-500/20', text: 'text-rose-700 dark:text-rose-300' },
flac: { bg: 'bg-indigo-500/20', text: 'text-indigo-700 dark:text-indigo-300' },
};
+33 -1
View File
@@ -87,12 +87,33 @@ export const formatDefaultLanguageLabel = (
return `Default (${joined}${suffix})`;
};
/**
* Build a mapping from language names to codes for normalization.
* Handles both directions: "english" -> "en" and "en" -> "en"
*/
export const buildLanguageNormalizer = (languages: Language[]): Map<string, string> => {
const map = new Map<string, string>();
for (const lang of languages) {
const code = lang.code.toLowerCase();
map.set(code, code); // code -> code
map.set(lang.language.toLowerCase(), code); // name -> code
}
return map;
};
/**
* Check if ALL languages in a multi-language release match the selected filter.
* Multi-language releases use separators like comma, slash, plus, or ampersand
* (e.g., "English, Spanish", "English/Spanish", "English + Spanish", "English & Spanish").
*
* @param releaseLang - Language string from the release (can be code or full name)
* @param selectedCodes - Array of selected ISO language codes
* @param languageNormalizer - Optional map to normalize language names to codes
*/
export const releaseLanguageMatchesFilter = (
releaseLang: string | undefined,
selectedCodes: string[] | null,
languageNormalizer?: Map<string, string>,
): boolean => {
if (!releaseLang || !selectedCodes) {
return true;
@@ -100,7 +121,18 @@ export const releaseLanguageMatchesFilter = (
if (selectedCodes.includes(LANGUAGE_OPTION_ALL)) {
return true;
}
const releaseCodes = releaseLang.split(/[,/]/).map(l => l.trim().toLowerCase()).filter(Boolean);
// Split by common multi-language separators: comma, slash, plus, ampersand
const releaseParts = releaseLang.split(/[,/+&]/).map(l => l.trim().toLowerCase()).filter(Boolean);
// Normalize release language parts to codes (handles both "en" and "english")
const releaseCodes = releaseParts.map(part => {
if (languageNormalizer) {
return languageNormalizer.get(part) ?? part;
}
return part;
});
const selectedSet = new Set(selectedCodes.map(c => c.toLowerCase()));
return releaseCodes.every(code => selectedSet.has(code));
};