From ffd9dbb042d158fda6165b0a8efa8fcefecae296 Mon Sep 17 00:00:00 2001 From: Viren070 Date: Wed, 4 Jun 2025 20:26:27 +0100 Subject: [PATCH] some fixes --- packages/core/src/main.ts | 86 ++++--- packages/core/src/parser/streams.ts | 4 +- packages/core/src/presets/stremthruStore.ts | 4 +- packages/core/src/presets/torbox.ts | 6 +- packages/core/src/utils/config.ts | 21 +- .../frontend/src/components/menu/addons.tsx | 114 ++++++--- .../frontend/src/components/menu/filters.tsx | 228 +++++++++++++----- 7 files changed, 325 insertions(+), 138 deletions(-) diff --git a/packages/core/src/main.ts b/packages/core/src/main.ts index 36daffa9..ca7fc6c0 100644 --- a/packages/core/src/main.ts +++ b/packages/core/src/main.ts @@ -808,7 +808,7 @@ export class AIOStreams { ); const shouldFetch = await parser.parse(group.condition); if (shouldFetch) { - logger.info(`Condition met for group ${i}, fetching streams`); + logger.info(`Condition met for group ${i + 1}, fetching streams`); const groupAddons = supportedAddons.filter( (addon) => addon.id && group.addons.includes(addon.id) @@ -868,12 +868,15 @@ export class AIOStreams { requiredUncachedFromServices: { total: 0, details: {} }, excludedUncachedMode: { total: 0, details: {} }, requiredUncachedMode: { total: 0, details: {} }, + excludedEncode: { total: 0, details: {} }, + requiredEncode: { total: 0, details: {} }, excludedCachedFromAddons: { total: 0, details: {} }, requiredCachedFromAddons: { total: 0, details: {} }, excludedCachedFromServices: { total: 0, details: {} }, requiredCachedFromServices: { total: 0, details: {} }, excludedCachedMode: { total: 0, details: {} }, requiredCachedMode: { total: 0, details: {} }, + requiredLanguage: { total: 0, details: {} }, }; const start = Date.now(); @@ -945,40 +948,25 @@ export class AIOStreams { serviceIds: string[] | undefined, cached: boolean ) => { - if (this.userData.excludeCached && cached && stream.service?.cached) { - return false; - } - if ( - this.userData.excludeUncached && - cached === false && - stream.service?.cached === false - ) { - return false; - } + const addonCriteriaMet = + !addonIds || + addonIds.length === 0 || + (addonIds.some((addonId) => stream.addon.id === addonId) && + stream.service?.cached === cached); + const serviceCriteriaMet = + !serviceIds || + serviceIds.length === 0 || + (serviceIds.some((serviceId) => stream.service?.id === serviceId) && + stream.service?.cached === cached); + if (mode === 'and') { - return ( - (!addonIds || - addonIds.length === 0 || - addonIds.some((addonId) => stream.addon.id === addonId)) && - (!serviceIds || - serviceIds.length === 0 || - serviceIds.some((serviceId) => stream.service?.id === serviceId)) && - stream.service?.cached === cached - ); + return addonCriteriaMet && serviceCriteriaMet; } else { - return ( - (!addonIds || - addonIds.length === 0 || - addonIds.some((addonId) => stream.addon.id === addonId)) && - (!serviceIds || - serviceIds.length === 0 || - serviceIds.some((serviceId) => stream.service?.id === serviceId)) && - stream.service?.cached === cached - ); + return addonCriteriaMet || serviceCriteriaMet; } }; - const filteredStreams = streams.filter(async (stream) => { + const shouldKeepStream = async (stream: ParsedStream): Promise => { const file = stream.parsedFile; // carry out include checks first @@ -1278,7 +1266,7 @@ export class AIOStreams { this.userData.excludeCachedFromAddons, this.userData.excludeCachedFromServices, true - ) + ) === false ) { skipReasons.excludedCached.total++; return false; @@ -1291,7 +1279,7 @@ export class AIOStreams { this.userData.excludeUncachedFromAddons, this.userData.excludeUncachedFromServices, false - ) + ) === false ) { skipReasons.excludedUncached.total++; return false; @@ -1327,22 +1315,45 @@ export class AIOStreams { // TODO: size filters return true; - }); + }; + + const filterResults = await Promise.all(streams.map(shouldKeepStream)); + const filteredStreams = streams.filter((_, index) => filterResults[index]); // Log filter summary const totalFiltered = streams.length - filteredStreams.length; if (totalFiltered > 0) { - const summary = [`Filtered out ${totalFiltered} streams:`]; + const summary = [ + '\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', + ` 🔍 Filter Summary`, + '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', + ` 📊 Total Streams : ${streams.length}`, + ` ✔️ Kept : ${filteredStreams.length}`, + ` ❌ Filtered : ${totalFiltered}`, + ]; + // Add filter details if any streams were filtered + const filterDetails: string[] = []; for (const [reason, stats] of Object.entries(skipReasons)) { if (stats.total > 0) { - summary.push(` - ${stats.total} due to ${reason}:`); + // Convert camelCase to Title Case with spaces + const formattedReason = reason + .replace(/([A-Z])/g, ' $1') + .replace(/^./, (str) => str.toUpperCase()); + + filterDetails.push(`\n 📌 ${formattedReason} (${stats.total})`); for (const [detail, count] of Object.entries(stats.details)) { - summary.push(` • ${count} ${detail}`); + filterDetails.push(` • ${count}× ${detail}`); } } } + if (filterDetails.length > 0) { + summary.push('\n 🔎 Filter Details:'); + summary.push(...filterDetails); + } + + summary.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); logger.info(summary.join('\n')); } @@ -1684,7 +1695,7 @@ export class AIOStreams { (stream) => stream.service?.cached || stream.service === undefined // streams without a service can be considered as 'cached' ); const uncachedStreams = streams.filter( - (stream) => !stream.service?.cached + (stream) => stream.service?.cached === false ); // sort the 2 lists separately, and put them after the other, depending on the direction of cached @@ -1714,6 +1725,7 @@ export class AIOStreams { sortedStreams = [...uncachedSorted, ...cachedSorted]; } } else { + logger.debug(`using sort criteria: ${JSON.stringify(sortCriteria)}`); sortedStreams = streams.slice().sort((a, b) => { const aKey = this.dynamicSortKey(a, sortCriteria, type); const bKey = this.dynamicSortKey(b, sortCriteria, type); diff --git a/packages/core/src/parser/streams.ts b/packages/core/src/parser/streams.ts index f40f8827..480e8082 100644 --- a/packages/core/src/parser/streams.ts +++ b/packages/core/src/parser/streams.ts @@ -1,7 +1,7 @@ import { Stream, ParsedStream, Addon } from '../db'; -import { constants } from '../utils'; +import { constants, createLogger } from '../utils'; import FileParser from './file'; - +const logger = createLogger('parser'); class StreamParser { get errorRegexes(): { pattern: RegExp; message: string }[] | undefined { return [ diff --git a/packages/core/src/presets/stremthruStore.ts b/packages/core/src/presets/stremthruStore.ts index c13a4aba..40acd586 100644 --- a/packages/core/src/presets/stremthruStore.ts +++ b/packages/core/src/presets/stremthruStore.ts @@ -1,7 +1,8 @@ -import { Addon, Option, UserData, Resource } from '../db'; +import { Addon, Option, UserData, Resource, Stream } from '../db'; import { baseOptions, Preset } from './preset'; import { Env } from '../utils'; import { constants, ServiceId } from '../utils'; +import { StreamParser } from '../parser'; export class StremthruStorePreset extends Preset { static override get METADATA() { @@ -103,6 +104,7 @@ export class StremthruStorePreset extends Preset { : options.name || this.METADATA.NAME, manifestUrl: this.generateManifestUrl(userData, options, serviceId), enabled: true, + library: true, resources: options.resources || this.METADATA.SUPPORTED_RESOURCES, timeout: options.timeout || this.METADATA.TIMEOUT, fromPresetId: this.METADATA.ID, diff --git a/packages/core/src/presets/torbox.ts b/packages/core/src/presets/torbox.ts index 139a5717..61f9b634 100644 --- a/packages/core/src/presets/torbox.ts +++ b/packages/core/src/presets/torbox.ts @@ -21,11 +21,7 @@ class TorboxStreamParser extends StreamParser { return (stream as any).hash; } override getInLibrary(stream: Stream): boolean { - return ( - ((stream as any).is_your_media || - stream.description?.includes('Your Media')) ?? - false - ); + return (stream as any).is_your_media || stream.name?.includes('Your Media'); } protected override getService( stream: Stream diff --git a/packages/core/src/utils/config.ts b/packages/core/src/utils/config.ts index a5cb4153..e8d44e98 100644 --- a/packages/core/src/utils/config.ts +++ b/packages/core/src/utils/config.ts @@ -258,7 +258,7 @@ export async function validateConfig( if (config.groups) { for (const group of config.groups) { - validateGroup(group); + await validateGroup(group); } } @@ -383,7 +383,7 @@ function validatePreset(preset: PresetObject) { } } -function validateGroup(group: Group) { +async function validateGroup(group: Group) { if (!group) { return; } @@ -394,13 +394,18 @@ function validateGroup(group: Group) { } // we must be able to parse the condition + let result; try { - const result = ConditionParser.testParse(group.condition); - if (typeof result !== 'boolean') { - throw new Error('Group condition must evaluate to a boolean'); - } - } catch (error) { - throw new Error(`Group condition is invalid: ${error}`); + result = await ConditionParser.testParse(group.condition); + } catch (error: any) { + throw new Error( + `Your group condition - '${group.condition}' - is invalid: ${error.message}` + ); + } + if (typeof result !== 'boolean') { + throw new Error( + `Your group condition - '${group.condition}' - is invalid. Expected evaluation to a boolean, instead got '${typeof result}'` + ); } } diff --git a/packages/frontend/src/components/menu/addons.tsx b/packages/frontend/src/components/menu/addons.tsx index 20a81c66..ef99dd4f 100644 --- a/packages/frontend/src/components/menu/addons.tsx +++ b/packages/frontend/src/components/menu/addons.tsx @@ -33,7 +33,12 @@ import { Option, Resource } from '@aiostreams/core'; import { toast } from 'sonner'; import { Tooltip } from '../ui/tooltip'; import { StaticTabs } from '../ui/tabs'; -import { LuDownload, LuGlobe } from 'react-icons/lu'; +import { + LuDownload, + LuGlobe, + LuChevronsUp, + LuChevronsDown, +} from 'react-icons/lu'; import { AnimatePresence } from 'framer-motion'; import { PageControls } from '../shared/page-controls'; import Image from 'next/image'; @@ -44,6 +49,7 @@ import { ConfirmationDialog, useConfirmationDialog, } from '../shared/confirmation-dialog'; +import { MdRefresh } from 'react-icons/md'; interface CatalogModification { id: string; @@ -514,14 +520,14 @@ function SortableAddonItem({ }; return (
  • -
    +
    -
    -
    +
    +
    {preset.ID === 'custom' ? ( ) : ( @@ -534,19 +540,26 @@ function SortableAddonItem({ )}
    -

    {addon.options.name}

    +

    + {addon.options.name} +

    -
    - +
    + } intent="primary-subtle" onClick={onEdit} /> } intent="alert-subtle" onClick={onRemove} @@ -1020,7 +1033,7 @@ function CatalogSettingsCard() { type: catalog.type, enabled: true, shuffle: false, - rpdb: false, + rpdb: userData.rpdbApiKey ? true : false, })); setUserData((prev) => ({ ...prev, @@ -1152,9 +1165,11 @@ function CatalogSettingsCard() {

    Catalogs

    Manage your catalogs

    - + />
    {!userData.catalogModifications?.length && ( @@ -1230,9 +1241,12 @@ function CatalogSettingsCard() { + Edit Catalog: {editingCatalog?.name || editingCatalog?.id} -{' '} + {capitalise(editingCatalog?.type)} +
    + } >
    { + setUserData((prev) => { + if (!prev.catalogModifications) return prev; + const index = prev.catalogModifications.findIndex( + (c) => c.id === catalog.id && c.type === catalog.type + ); + if (index <= 0) return prev; + const newMods = [...prev.catalogModifications]; + const [item] = newMods.splice(index, 1); + newMods.unshift(item); + return { ...prev, catalogModifications: newMods }; + }); + }; + + const moveToBottom = () => { + setUserData((prev) => { + if (!prev.catalogModifications) return prev; + const index = prev.catalogModifications.findIndex( + (c) => c.id === catalog.id && c.type === catalog.type + ); + if (index === prev.catalogModifications.length - 1) return prev; + const newMods = [...prev.catalogModifications]; + const [item] = newMods.splice(index, 1); + newMods.push(item); + return { ...prev, catalogModifications: newMods }; + }); + }; + return (
    -
    +
    -
    -

    - {catalog.name || catalog.id} - {capitalise(catalog.type)} +

    +

    + {catalog.name ?? catalog.id} - {capitalise(catalog.type)}

    -
    +
    } intent="primary-subtle" onClick={onEdit} /> + } + intent="primary-subtle" + onClick={moveToTop} + /> + } + intent="primary-subtle" + onClick={moveToBottom} + />
    diff --git a/packages/frontend/src/components/menu/filters.tsx b/packages/frontend/src/components/menu/filters.tsx index d738ac9f..08ff65b8 100644 --- a/packages/frontend/src/components/menu/filters.tsx +++ b/packages/frontend/src/components/menu/filters.tsx @@ -740,6 +740,16 @@ function Content() { requiredKeywords: values, })); }} + onValueChange={(value, index) => { + setUserData((prev) => ({ + ...prev, + requiredKeywords: [ + ...(prev.requiredKeywords || []).slice(0, index), + value, + ...(prev.requiredKeywords || []).slice(index + 1), + ], + })); + }} /> { + setUserData((prev) => ({ + ...prev, + excludedKeywords: [ + ...(prev.excludedKeywords || []).slice(0, index), + value, + ...(prev.excludedKeywords || []).slice(index + 1), + ], + })); + }} /> { + setUserData((prev) => ({ + ...prev, + includedKeywords: [ + ...(prev.includedKeywords || []).slice(0, index), + value, + ...(prev.includedKeywords || []).slice(index + 1), + ], + })); + }} /> { + setUserData((prev) => ({ + ...prev, + preferredKeywords: [ + ...(prev.preferredKeywords || []).slice(0, index), + value, + ...(prev.preferredKeywords || []).slice(index + 1), + ], + })); + }} />
    @@ -818,6 +858,16 @@ function Content() { requiredRegexPatterns: values, })); }} + onValueChange={(value, index) => { + setUserData((prev) => ({ + ...prev, + requiredRegexPatterns: [ + ...(prev.requiredRegexPatterns || []).slice(0, index), + value, + ...(prev.requiredRegexPatterns || []).slice(index + 1), + ], + })); + }} /> { + setUserData((prev) => ({ + ...prev, + excludedRegexPatterns: [ + ...(prev.excludedRegexPatterns || []).slice(0, index), + value, + ...(prev.excludedRegexPatterns || []).slice(index + 1), + ], + })); + }} /> { + setUserData((prev) => ({ + ...prev, + includedRegexPatterns: [ + ...(prev.includedRegexPatterns || []).slice(0, index), + value, + ...(prev.includedRegexPatterns || []).slice(index + 1), + ], + })); + }} /> ({ name: pattern.name, @@ -861,12 +931,38 @@ function Content() { onValuesChange={(values) => { setUserData((prev) => ({ ...prev, - preferredRegexPatterns: values.map((value) => ({ - name: value.name, - pattern: value.value, + preferredRegexPatterns: values.map((v) => ({ + name: v.name, + pattern: v.value, })), })); }} + onValueChange={(value, index) => { + setUserData((prev) => ({ + ...prev, + preferredRegexPatterns: [ + ...(prev.preferredRegexPatterns || []).slice(0, index), + { + ...(prev.preferredRegexPatterns || [])[index], + pattern: value, + }, + ...(prev.preferredRegexPatterns || []).slice(index + 1), + ], + })); + }} + onKeyChange={(key, index) => { + setUserData((prev) => ({ + ...prev, + preferredRegexPatterns: [ + ...(prev.preferredRegexPatterns || []).slice(0, index), + { + ...(prev.preferredRegexPatterns || [])[index], + name: key, + }, + ...(prev.preferredRegexPatterns || []).slice(index + 1), + ], + })); + }} />
    @@ -885,12 +981,15 @@ function Content() { help="Global limit for all results" label="Global Limit" value={userData.resultLimits?.global || undefined} - min={1} + min={0} defaultValue={undefined} onValueChange={(value) => { setUserData((prev) => ({ ...prev, - resultLimits: { ...prev.resultLimits, global: value }, + resultLimits: { + ...prev.resultLimits, + global: value || undefined, + }, })); }} /> @@ -898,12 +997,15 @@ function Content() { help="Limit for results by service" label="Service Limit" value={userData.resultLimits?.service || undefined} - min={1} + min={0} defaultValue={undefined} onValueChange={(value) => { setUserData((prev) => ({ ...prev, - resultLimits: { ...prev.resultLimits, service: value }, + resultLimits: { + ...prev.resultLimits, + service: value || undefined, + }, })); }} /> @@ -911,12 +1013,15 @@ function Content() { help="Limit for results by addon" label="Addon Limit" value={userData.resultLimits?.addon || undefined} - min={1} + min={0} defaultValue={undefined} onValueChange={(value) => { setUserData((prev) => ({ ...prev, - resultLimits: { ...prev.resultLimits, addon: value }, + resultLimits: { + ...prev.resultLimits, + addon: value || undefined, + }, })); }} /> @@ -924,14 +1029,14 @@ function Content() { help="Limit for results by resolution" label="Resolution Limit" value={userData.resultLimits?.resolution || undefined} - min={1} + min={0} defaultValue={undefined} onValueChange={(value) => { setUserData((prev) => ({ ...prev, resultLimits: { ...prev.resultLimits, - resolution: value, + resolution: value || undefined, }, })); }} @@ -940,12 +1045,15 @@ function Content() { help="Limit for results by quality" label="Quality Limit" value={userData.resultLimits?.quality || undefined} - min={1} + min={0} defaultValue={undefined} onValueChange={(value) => { setUserData((prev) => ({ ...prev, - resultLimits: { ...prev.resultLimits, quality: value }, + resultLimits: { + ...prev.resultLimits, + quality: value || undefined, + }, })); }} /> @@ -953,12 +1061,15 @@ function Content() { help="Limit for results by indexer" label="Indexer Limit" value={userData.resultLimits?.indexer || undefined} - min={1} + min={0} defaultValue={undefined} onValueChange={(value) => { setUserData((prev) => ({ ...prev, - resultLimits: { ...prev.resultLimits, indexer: value }, + resultLimits: { + ...prev.resultLimits, + indexer: value || undefined, + }, })); }} /> @@ -966,14 +1077,14 @@ function Content() { help="Limit for results by release group" label="Release Group Limit" value={userData.resultLimits?.releaseGroup || undefined} - min={1} + min={0} defaultValue={undefined} onValueChange={(value) => { setUserData((prev) => ({ ...prev, resultLimits: { ...prev.resultLimits, - releaseGroup: value, + releaseGroup: value || undefined, }, })); }} @@ -1410,6 +1521,7 @@ type TextInputProps = { help: string; // help text that shows below the label values: string[]; onValuesChange: (values: string[]) => void; + onValueChange: (value: string, index: number) => void; placeholder?: string; }; @@ -1421,6 +1533,7 @@ function TextInputs({ help, values, onValuesChange, + onValueChange, placeholder, }: TextInputProps) { const fileInputRef = useRef(null); @@ -1471,13 +1584,14 @@ function TextInputs({ value={value} label={itemName} placeholder={placeholder} - onValueChange={(value) => - onValuesChange([ - ...values.slice(0, index), - value, - ...values.slice(index + 1), - ]) - } + onValueChange={(value) => onValueChange(value, index)} + // onValueChange={(value) => + // onValuesChange([ + // ...values.slice(0, index), + // value, + // ...values.slice(index + 1), + // ]) + // } />
    void; + onValueChange: (value: string, index: number) => void; + onKeyChange: (key: string, index: number) => void; }; function TwoTextInputs({ @@ -1569,6 +1685,8 @@ function TwoTextInputs({ valuePlaceholder, values, onValuesChange, + onValueChange, + onKeyChange, }: KeyValueInputProps) { const fileInputRef = useRef(null); @@ -1602,11 +1720,17 @@ function TwoTextInputs({ if ( Array.isArray(data) && data.every( - (value: { name: string; value: string }) => - typeof value.name === 'string' && typeof value.value === 'string' + (value: { [key: string]: string }) => + typeof value[keyId] === 'string' && + typeof value[valueId] === 'string' ) ) { - onValuesChange(data); + onValuesChange( + data.map((v: { [key: string]: string }) => ({ + name: v[keyId], + value: v[valueId], + })) + ); } } catch (error) { console.error('Error importing file:', error); @@ -1622,31 +1746,21 @@ function TwoTextInputs({ {values.map((value, index) => (
    -
    -
    - { - const newValues = [...values]; - newValues[index].name = value; - onValuesChange(newValues); - }} - /> -
    -
    - { - const newValues = [...values]; - newValues[index].value = value; - onValuesChange(newValues); - }} - /> -
    +
    + onKeyChange(newValue, index)} + /> +
    +
    + onValueChange(newValue, index)} + />