mirror of
https://github.com/calibrain/shelfmark.git
synced 2026-10-04 22:05:45 +01:00
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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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' },
|
||||
};
|
||||
|
||||
@@ -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));
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user