Add sort and exclusion filters to the media grid

This commit is contained in:
Fisto
2026-09-17 11:01:50 +02:00
parent cc14140f34
commit 515af01e84
13 changed files with 561 additions and 96 deletions
+52 -1
View File
@@ -102,6 +102,9 @@ export function uniqueScrapeId(root: string, channelId: string, now = new Date()
return id;
}
/** Global ordering applied once candidates from every scrape are merged. */
export type MediaSort = "newest" | "oldest" | "user" | "size" | "length";
export interface MediaFilterOptions {
kind?: MediaKind;
source?: string;
@@ -110,12 +113,25 @@ export interface MediaFilterOptions {
cachedOnly?: boolean;
exts?: string[];
excludeHosts?: string[];
/** URL extension or content type to hide. */
excludeExts?: string[];
/** Provider hostnames to hide (youtube, twitch, ...). */
excludeProviders?: string[];
/** Row sources to hide (attachment, sticker, embed). */
excludeSources?: string[];
/** Bytes; rows with no known size drop out while this is set. */
sizeLessThan?: number;
sizeGreaterThan?: number;
/** Seconds; rows with no known duration drop out while this is set. */
lengthLessThan?: number;
lengthGreaterThan?: number;
aspect?: string;
resolution?: string;
after?: string;
before?: string;
limit?: number;
offset?: number;
sort?: MediaSort;
}
export interface MediaRow {
@@ -564,7 +580,9 @@ export class ScrapeStore {
conditions.push("preview_hash IS NOT NULL");
}
if (options.exts?.length) {
const clauses = options.exts.map(() => "(lower(url) LIKE ? OR lower(content_type) LIKE ?)");
const clauses = options.exts.map(
() => "(lower(url) LIKE ? OR lower(COALESCE(content_type, '')) LIKE ?)"
);
conditions.push(`(${clauses.join(" OR ")})`);
for (const ext of options.exts) {
values.push(`%.${ext.toLowerCase()}%`, `%/${ext.toLowerCase()}%`);
@@ -574,6 +592,39 @@ export class ScrapeStore {
conditions.push("lower(url) NOT LIKE ?");
values.push(`%${host.toLowerCase()}%`);
}
if (options.excludeExts?.length) {
const clauses = options.excludeExts.map(
() => "(lower(url) LIKE ? OR lower(COALESCE(content_type, '')) LIKE ?)"
);
conditions.push(`NOT (${clauses.join(" OR ")})`);
for (const ext of options.excludeExts) {
values.push(`%.${ext.toLowerCase()}%`, `%/${ext.toLowerCase()}%`);
}
}
for (const provider of options.excludeProviders ?? []) {
conditions.push("lower(COALESCE(provider, '')) NOT LIKE ?");
values.push(`%${provider.toLowerCase()}%`);
}
if (options.excludeSources?.length) {
conditions.push(`COALESCE(source, '') NOT IN (${options.excludeSources.map(() => "?").join(", ")})`);
values.push(...options.excludeSources);
}
if (options.sizeLessThan !== undefined) {
conditions.push("COALESCE(file_bytes, size) < ?");
values.push(options.sizeLessThan);
}
if (options.sizeGreaterThan !== undefined) {
conditions.push("COALESCE(file_bytes, size) > ?");
values.push(options.sizeGreaterThan);
}
if (options.lengthLessThan !== undefined) {
conditions.push("duration_secs < ?");
values.push(options.lengthLessThan);
}
if (options.lengthGreaterThan !== undefined) {
conditions.push("duration_secs > ?");
values.push(options.lengthGreaterThan);
}
if (options.after) {
conditions.push("posted_at >= ?");
values.push(options.after);