feat(search): add basic search functionality
this commit adds basic search feature. fix: https://codeberg.org/zyachel/libremdb/issues/9, https://github.com/zyachel/libremdb/issues/10
This commit is contained in:
71
src/utils/cleaners/find.ts
Normal file
71
src/utils/cleaners/find.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import RawFind from '../../interfaces/misc/rawFind';
|
||||
|
||||
const formatSAndE = (
|
||||
season: string | undefined,
|
||||
episode: string | undefined
|
||||
) => {
|
||||
if (season && season !== 'Unknown' && episode && episode !== 'Unknown')
|
||||
return `S${season} E${episode}`;
|
||||
return null;
|
||||
};
|
||||
|
||||
const cleanFind = (rawFind: RawFind) => {
|
||||
const {
|
||||
props: { pageProps: d },
|
||||
} = rawFind;
|
||||
|
||||
const cleanData = {
|
||||
meta: {
|
||||
exact: d.findPageMeta.isExactMatch,
|
||||
type: d.findPageMeta.searchType || null,
|
||||
titleType: d.findPageMeta.titleSearchType?.[0] || null,
|
||||
},
|
||||
people: d.nameResults.results.map(person => ({
|
||||
id: person.id,
|
||||
name: person.displayNameText,
|
||||
aka: person.akaName || null,
|
||||
jobCateogry: person.knownForJobCategory || null,
|
||||
knownForTitle: person.knownForTitleText || null,
|
||||
knownInYear: person.knownForTitleYear || null,
|
||||
...(person.avatarImageModel && {
|
||||
image: {
|
||||
url: person.avatarImageModel.url,
|
||||
caption: person.avatarImageModel.caption,
|
||||
},
|
||||
}),
|
||||
})),
|
||||
titles: d.titleResults.results.map(title => ({
|
||||
id: title.id,
|
||||
name: title.titleNameText,
|
||||
type: title.titleTypeText,
|
||||
releaseYear: title.titleReleaseText || null,
|
||||
credits: title.topCredits,
|
||||
...(title.titlePosterImageModel && {
|
||||
image: {
|
||||
url: title.titlePosterImageModel.url,
|
||||
caption: title.titlePosterImageModel.caption,
|
||||
},
|
||||
}),
|
||||
seriesId: title.seriesId || null,
|
||||
seriesName: title.seriesNameText || null,
|
||||
seriesType: title.seriesTypeText || null,
|
||||
seriesReleaseYear: title.seriesReleaseText || null,
|
||||
sAndE: formatSAndE(title.seriesSeasonText, title.seriesEpisodeText),
|
||||
})),
|
||||
companies: d.companyResults.results.map(company => ({
|
||||
id: company.id,
|
||||
name: company.companyName,
|
||||
type: company.typeText,
|
||||
country: company.countryText,
|
||||
})),
|
||||
keywords: d.keywordResults.results.map(keyword => ({
|
||||
id: keyword.id,
|
||||
text: keyword.keywordText,
|
||||
numTitles: keyword.numTitles,
|
||||
})),
|
||||
};
|
||||
|
||||
return cleanData;
|
||||
};
|
||||
|
||||
export default cleanFind;
|
36
src/utils/constants/find.ts
Normal file
36
src/utils/constants/find.ts
Normal file
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* @constant
|
||||
*
|
||||
* key: the key for the query that we make to fetch results
|
||||
*
|
||||
* name: Nice name to display on the client side
|
||||
*
|
||||
* val: the value that is associated with the key. also used to fetch results.
|
||||
*
|
||||
* **IMPORTANT**: see sample response from backend, and form submission url to better understand how these objects are used.
|
||||
*/
|
||||
export const resultTypes = {
|
||||
types: [
|
||||
{ name: 'Titles', val: 'tt', id: 'TITLE' },
|
||||
{ name: 'People', val: 'nm', id: 'NAME' },
|
||||
{ name: 'Companies', val: 'co', id: 'COMPANY' },
|
||||
{ name: 'Keywords', val: 'kw', id: 'KEYWORD' },
|
||||
],
|
||||
key: 's',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* same as {@link resultTypes}.
|
||||
*/
|
||||
export const resultTitleTypes = {
|
||||
types: [
|
||||
{ name: 'Movies', val: 'ft', id: 'MOVIE' },
|
||||
{ name: 'TV', val: 'tv', id: 'TV' },
|
||||
{ name: 'TV Episodes', val: 'ep', id: 'TV_EPISODE' },
|
||||
{ name: 'Music Videos', val: 'mu', id: 'MUSIC_VIDEO' },
|
||||
{ name: 'Podcasts', val: 'ps', id: 'PODCAST_SERIES' },
|
||||
{ name: 'Podcast Episodes', val: 'pe', id: 'PODCAST_EPISODE' },
|
||||
{ name: 'Video Games', val: 'vg', id: 'VIDEO_GAME' },
|
||||
],
|
||||
key: 'ttype',
|
||||
} as const;
|
27
src/utils/fetchers/basicSearch.ts
Normal file
27
src/utils/fetchers/basicSearch.ts
Normal file
@ -0,0 +1,27 @@
|
||||
// external deps
|
||||
import * as cheerio from 'cheerio';
|
||||
// local files
|
||||
import axiosInstance from '../axiosInstance';
|
||||
import { AppError } from '../helpers';
|
||||
import RawFind from '../../interfaces/misc/rawFind';
|
||||
import cleanFind from '../cleaners/find';
|
||||
|
||||
const basicSearch = async (queryStr: string) => {
|
||||
try {
|
||||
const res = await axiosInstance(`/find?${queryStr}`);
|
||||
const $ = cheerio.load(res.data);
|
||||
const rawData = $('script#__NEXT_DATA__').text();
|
||||
|
||||
const parsedRawData: RawFind = JSON.parse(rawData);
|
||||
const cleanData = cleanFind(parsedRawData);
|
||||
|
||||
return cleanData;
|
||||
} catch (err: any) {
|
||||
if (err.response?.status === 404)
|
||||
throw new AppError('not found', 404, err.cause);
|
||||
|
||||
throw new AppError('something went wrong', 500, err.cause);
|
||||
}
|
||||
};
|
||||
|
||||
export default basicSearch;
|
@ -1,3 +1,9 @@
|
||||
import {
|
||||
ResultMetaTitleTypes,
|
||||
ResultMetaTypes,
|
||||
} from '../interfaces/shared/search';
|
||||
import { resultTitleTypes } from './constants/find';
|
||||
|
||||
export const formatTime = (timeInSecs: number) => {
|
||||
if (!timeInSecs) return;
|
||||
// year, month, date, hours, minutes, seconds
|
||||
@ -50,8 +56,14 @@ export const formatMoney = (num: number, cur: string) => {
|
||||
}).format(num);
|
||||
};
|
||||
|
||||
const imageRegex = /https:\/\/m\.media-amazon\.com\/images\/M\/[^.]*/;
|
||||
|
||||
export const modifyIMDbImg = (url: string, widthInPx = 600) => {
|
||||
return url.replace(/\.jpg/g, `UX${widthInPx}.jpg`);
|
||||
// as match returns either array or null, returning array in case it returns null. and destructuring it right away.
|
||||
const [cleanImg] = url.match(imageRegex) || [];
|
||||
|
||||
if (cleanImg) return `${cleanImg}.UX${widthInPx}.jpg`;
|
||||
return url;
|
||||
};
|
||||
|
||||
export const getProxiedIMDbImgUrl = (url: string) => {
|
||||
@ -65,3 +77,29 @@ export const AppError = class extends Error {
|
||||
Error.captureStackTrace(this, AppError);
|
||||
}
|
||||
};
|
||||
|
||||
export const cleanQueryStr = (
|
||||
entries: [string, string][],
|
||||
filterable = ['q', 's', 'exact', 'ttype']
|
||||
) => {
|
||||
let queryStr = '';
|
||||
|
||||
entries.forEach(([key, val], i) => {
|
||||
if (!val || !filterable.includes(key)) return;
|
||||
queryStr += `${i > 0 ? '&' : ''}${key}=${val.trim()}`;
|
||||
});
|
||||
|
||||
return queryStr;
|
||||
};
|
||||
|
||||
export const getResTitleTypeHeading = (
|
||||
type: ResultMetaTypes,
|
||||
titleType: ResultMetaTitleTypes
|
||||
) => {
|
||||
if (type !== 'TITLE') return 'Titles';
|
||||
|
||||
for (let i = 0; i < resultTitleTypes.types.length; i++) {
|
||||
const el = resultTitleTypes.types[i];
|
||||
if (el.id === titleType) return el.name;
|
||||
}
|
||||
};
|
||||
|
Reference in New Issue
Block a user