From 6a26e11efcab5d96c4c96c8c79b06c44b42db2af Mon Sep 17 00:00:00 2001 From: Viren070 Date: Tue, 29 Jul 2025 14:20:21 +0100 Subject: [PATCH] feat: add force to top option for custom addons --- packages/core/src/db/schemas.ts | 1 + packages/core/src/presets/custom.ts | 10 ++++++++ packages/core/src/streams/sorter.ts | 40 +++++++++++++++++++++++++---- 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/packages/core/src/db/schemas.ts b/packages/core/src/db/schemas.ts index fdeb6422..357e1fd6 100644 --- a/packages/core/src/db/schemas.ts +++ b/packages/core/src/db/schemas.ts @@ -122,6 +122,7 @@ const AddonSchema = z.object({ library: z.boolean().optional(), streamPassthrough: z.boolean().optional(), resultPassthrough: z.boolean().optional(), + forceToTop: z.boolean().optional(), headers: z.record(z.string().min(1), z.string().min(1)).optional(), ip: z.string().ip().optional(), }); diff --git a/packages/core/src/presets/custom.ts b/packages/core/src/presets/custom.ts index e0b1a757..6f2f3a19 100644 --- a/packages/core/src/presets/custom.ts +++ b/packages/core/src/presets/custom.ts @@ -45,6 +45,15 @@ export class CustomPreset extends Preset { required: false, default: false, }, + { + id: 'forceToTop', + name: 'Force to Top', + description: + 'Whether to force results from this addon to be pushed to the top of the stream list.', + type: 'boolean', + required: false, + default: false, + }, { id: 'timeout', name: 'Timeout', @@ -125,6 +134,7 @@ export class CustomPreset extends Preset { }, streamPassthrough: options.streamPassthrough ?? false, resultPassthrough: options.resultPassthrough ?? false, + forceToTop: options.forceToTop ?? false, headers: { 'User-Agent': this.METADATA.USER_AGENT, }, diff --git a/packages/core/src/streams/sorter.ts b/packages/core/src/streams/sorter.ts index cbaf552d..9cd7f604 100644 --- a/packages/core/src/streams/sorter.ts +++ b/packages/core/src/streams/sorter.ts @@ -5,6 +5,10 @@ import { AUDIO_TAGS } from '../utils/constants'; const logger = createLogger('sorter'); +type InternalSortCriterion = + | SortCriterion + | { key: 'forceToTop'; direction: 'asc' | 'desc' }; + class StreamSorter { private userData: UserData; @@ -16,9 +20,12 @@ class StreamSorter { streams: ParsedStream[], type: string ): Promise { - let primarySortCriteria = this.userData.sortCriteria.global; - let cachedSortCriteria = this.userData.sortCriteria.cached; - let uncachedSortCriteria = this.userData.sortCriteria.uncached; + let primarySortCriteria: InternalSortCriterion[] = + this.userData.sortCriteria.global; + let cachedSortCriteria: InternalSortCriterion[] | undefined = + this.userData.sortCriteria.cached; + let uncachedSortCriteria: InternalSortCriterion[] | undefined = + this.userData.sortCriteria.uncached; const start = Date.now(); @@ -60,6 +67,24 @@ class StreamSorter { let sortedStreams = []; + // append the forcedToTop criteria to all sort criteria at the start + primarySortCriteria = [ + { key: 'forceToTop', direction: 'desc' }, + ...primarySortCriteria, + ]; + if (cachedSortCriteria && cachedSortCriteria.length > 0) { + cachedSortCriteria = [ + { key: 'forceToTop', direction: 'desc' }, + ...cachedSortCriteria, + ]; + } + if (uncachedSortCriteria && uncachedSortCriteria.length > 0) { + uncachedSortCriteria = [ + { key: 'forceToTop', direction: 'desc' }, + ...uncachedSortCriteria, + ]; + } + if ( cachedSortCriteria?.length && uncachedSortCriteria?.length && @@ -126,13 +151,18 @@ class StreamSorter { private dynamicSortKey( stream: ParsedStream, - sortCriteria: SortCriterion[], + sortCriteria: InternalSortCriterion[], type: string ): any[] { - function keyValue(sortCriterion: SortCriterion, userData: UserData) { + function keyValue( + sortCriterion: InternalSortCriterion, + userData: UserData + ) { const { key, direction } = sortCriterion; const multiplier = direction === 'asc' ? 1 : -1; switch (key) { + case 'forceToTop': + return multiplier * (stream.addon.forceToTop ? 1 : 0); case 'cached': return multiplier * (stream.service?.cached !== false ? 1 : 0);