mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 06:25:41 +00:00
19fdba006a
* fix(search): reject FTS syntax chars in local index live search queries FTS5 treats `=` and similar characters as query syntax, so tokens like 1=2 produced unrelated prefix hits. Skip FTS for unsafe tokens instead. * docs: CHANGELOG for local index FTS syntax query fix (PR #983) * fix(search): reject syntax junk in server search3 and live search race Share FTS-safe token guard with search3; skip network invoke for ** and similar queries; show empty dropdown without misleading source badge. * fix(search): allow censorship stars in queries, reject wildcard-only tokens Block ** and **** but keep ***Flawless-style title searches working in local FTS and search3.
74 lines
2.3 KiB
TypeScript
74 lines
2.3 KiB
TypeScript
import { api, libraryFilterParams } from './subsonicClient';
|
|
import { searchQueryIsFtsSafe } from '../utils/library/searchQueryFtsSafe';
|
|
import type {
|
|
SearchResults,
|
|
SubsonicAlbum,
|
|
SubsonicArtist,
|
|
SubsonicSong,
|
|
} from './subsonicTypes';
|
|
|
|
/**
|
|
* search3 sometimes returns duplicate or junk artist rows with **zero** albums (e.g. Navidrome indexing).
|
|
* Drop only rows that explicitly report `albumCount === 0`; keep artists when the field is absent.
|
|
* Thanks to zunoz for the report on the Psysonic Discord.
|
|
*/
|
|
export function filterSearchArtistsWithNoAlbums(artists: SubsonicArtist[]): SubsonicArtist[] {
|
|
return artists.filter((a) => a.albumCount !== 0);
|
|
}
|
|
|
|
export async function search(
|
|
query: string,
|
|
options?: {
|
|
albumCount?: number;
|
|
artistCount?: number;
|
|
songCount?: number;
|
|
signal?: AbortSignal;
|
|
timeout?: number;
|
|
},
|
|
): Promise<SearchResults> {
|
|
if (!query.trim()) return { artists: [], albums: [], songs: [] };
|
|
if (!searchQueryIsFtsSafe(query)) return { artists: [], albums: [], songs: [] };
|
|
const data = await api<{
|
|
searchResult3: {
|
|
artist?: SubsonicArtist[];
|
|
album?: SubsonicAlbum[];
|
|
song?: SubsonicSong[];
|
|
};
|
|
}>(
|
|
'search3.view',
|
|
{
|
|
query,
|
|
artistCount: options?.artistCount ?? 5,
|
|
albumCount: options?.albumCount ?? 5,
|
|
songCount: options?.songCount ?? 10,
|
|
...libraryFilterParams(),
|
|
},
|
|
options?.timeout ?? 15000,
|
|
options?.signal,
|
|
);
|
|
const r = data.searchResult3 ?? {};
|
|
return {
|
|
artists: filterSearchArtistsWithNoAlbums(r.artist ?? []),
|
|
albums: r.album ?? [],
|
|
songs: r.song ?? [],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Song-only paginated search3. Tolerates empty query — Navidrome returns all songs
|
|
* ordered by title in that case; strict Subsonic implementations may return nothing.
|
|
* Caller handles empty results gracefully (Tracks page falls back to its random pool).
|
|
*/
|
|
export async function searchSongsPaged(query: string, songCount: number, songOffset: number): Promise<SubsonicSong[]> {
|
|
if (!searchQueryIsFtsSafe(query.trim())) return [];
|
|
const data = await api<{ searchResult3: { song?: SubsonicSong[] } }>('search3.view', {
|
|
query,
|
|
artistCount: 0,
|
|
albumCount: 0,
|
|
songCount,
|
|
songOffset,
|
|
...libraryFilterParams(),
|
|
});
|
|
return data.searchResult3?.song ?? [];
|
|
}
|