feat(list): add list route
adds ability to see titles, names, and images lists closes https://github.com/zyachel/libremdb/issues/6
This commit is contained in:
@ -1,4 +1,5 @@
|
||||
export const titleKey = (titleId: string) => `title:${titleId}`;
|
||||
export const nameKey = (nameId: string) => `name:${nameId}`;
|
||||
export const listKey = (listId: string, pageNum = '1') => `list:${listId}?page=${pageNum}`;
|
||||
export const findKey = (query: string) => `find:${query}`;
|
||||
export const mediaKey = (url: string) => `media:${url}`;
|
||||
|
141
src/utils/fetchers/list.ts
Normal file
141
src/utils/fetchers/list.ts
Normal file
@ -0,0 +1,141 @@
|
||||
import axios, { AxiosError } from 'axios';
|
||||
import * as cheerio from 'cheerio';
|
||||
import type { Data, DataKind } from 'src/interfaces/shared/list';
|
||||
import axiosInstance from 'src/utils/axiosInstance';
|
||||
import { AppError, isIMDbImgPlaceholder } from 'src/utils/helpers';
|
||||
|
||||
const list = async (listId: string, pageNum = '1') => {
|
||||
try {
|
||||
const res = await axiosInstance(`/list/${listId}?page=${pageNum}`);
|
||||
const $ = cheerio.load(res.data);
|
||||
|
||||
const $main = $('#main > .article');
|
||||
const $meta = $main.children('#list-overview-summary');
|
||||
const $footer = $main.find('.footer .desc .list-pagination');
|
||||
|
||||
const title = clean($main.children('h1.list-name'));
|
||||
const numWithtype = clean($main.find('.sub-list .header .nav .desc')).split(' ');
|
||||
|
||||
if (numWithtype.length < 2) throw new AppError('invalid list', 400);
|
||||
|
||||
const [num, type] = numWithtype as [string, DataKind];
|
||||
|
||||
const meta = {
|
||||
by: {
|
||||
name: clean($meta.children('a')),
|
||||
link: $meta.children('a').attr('href') ?? null,
|
||||
},
|
||||
created: clean($meta.find('#list-overview-created')),
|
||||
updated: clean($meta.find('#list-overview-lastupdated')),
|
||||
num,
|
||||
type,
|
||||
};
|
||||
const description = clean($main.children('.list-description'));
|
||||
|
||||
const pagination = {
|
||||
prev: $footer.children('a.prev-page').attr('href') ?? null,
|
||||
range: clean($footer.children('.pagination-range')),
|
||||
next: $footer.children('a.next-page').attr('href') ?? null,
|
||||
};
|
||||
|
||||
const $imagesContainer = $main.find('.lister-list .media_index_thumb_list');
|
||||
const $listItems = $main.find('.lister-list').children();
|
||||
let data: Data<typeof type>[] = [];
|
||||
|
||||
// 1. images list
|
||||
if (type === 'images') {
|
||||
data = $imagesContainer
|
||||
.find('a > img')
|
||||
.map((_i, el) => $(el).attr('src'))
|
||||
.toArray();
|
||||
}
|
||||
|
||||
// 2. movies list
|
||||
else if (type === 'titles') {
|
||||
$listItems.each((_i, el) => {
|
||||
let image = $(el).find('.lister-item-image > a > img.loadlate').attr('loadlate') ?? null;
|
||||
if (image && isIMDbImgPlaceholder(image)) image = null;
|
||||
|
||||
const $content = $(el).children('.lister-item-content');
|
||||
const $heading = $content.find('h3.lister-item-header > a');
|
||||
const name = clean($heading);
|
||||
const url = $heading.attr('href') ?? null;
|
||||
const year = clean($heading.next('.lister-item-year'));
|
||||
const $itemMeta = $content.find('h3.lister-item-header + p');
|
||||
const certificate = clean($itemMeta.children('.certificate'));
|
||||
const runtime = clean($itemMeta.children('.runtime'));
|
||||
const genre = clean($itemMeta.children('.genre'));
|
||||
const rating = clean($content.find('.ipl-rating-star__rating').first());
|
||||
const metascore = clean($content.find('.metascore'));
|
||||
const plot = clean($content.children('p[class=""]'));
|
||||
|
||||
// eg: [["Director", "Nabwana I.G.G."], ["Stars", "Kakule William, Sserunya Ernest, G. Puffs"]]
|
||||
const otherInfo = $content
|
||||
.children('p.text-muted.text-small')
|
||||
.nextAll('p.text-muted.text-small')
|
||||
.map((__i, infoEl) => {
|
||||
const arr = clean($(infoEl)).replace(/\s+/g, ' ').split('|');
|
||||
|
||||
return arr.map(i => i.split(':'));
|
||||
})
|
||||
.toArray();
|
||||
|
||||
data.push({
|
||||
image,
|
||||
name,
|
||||
url,
|
||||
year,
|
||||
certificate,
|
||||
runtime,
|
||||
genre,
|
||||
plot,
|
||||
rating,
|
||||
metascore,
|
||||
otherInfo,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 3. actors list
|
||||
else if (type === 'names') {
|
||||
$listItems.each((_i, el) => {
|
||||
let image = $(el).find('.lister-item-image > a > img').attr('src') ?? null;
|
||||
if (image && isIMDbImgPlaceholder(image)) image = null;
|
||||
|
||||
const $content = $(el).children('.lister-item-content');
|
||||
const $heading = $content.find('h3.lister-item-header > a');
|
||||
const name = clean($heading);
|
||||
const url = $heading.attr('href') ?? null;
|
||||
const $itemMeta = $content.find('h3.lister-item-header + p');
|
||||
const jobNKnownForRaw = clean($itemMeta.first()).split('|');
|
||||
const job = jobNKnownForRaw.at(0) ?? null;
|
||||
const knownFor = jobNKnownForRaw.at(1) ?? null;
|
||||
const knownForLink = $itemMeta.children('a').attr('href') ?? null;
|
||||
const about = clean($content.children('p:not([class])'));
|
||||
|
||||
data.push({
|
||||
image,
|
||||
name,
|
||||
url,
|
||||
job,
|
||||
knownFor,
|
||||
knownForLink,
|
||||
about,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return { title, meta, description, pagination, data };
|
||||
} catch (err: any) {
|
||||
if (err instanceof AxiosError && err.response?.status === 404)
|
||||
throw new AppError('not found', 404, err.cause);
|
||||
|
||||
if (err instanceof AppError) throw err;
|
||||
|
||||
throw new AppError('something went wrong', 500, err.cause);
|
||||
}
|
||||
};
|
||||
|
||||
export default list;
|
||||
|
||||
const clean = <T extends cheerio.Cheerio<any>>(item: T) => item.text().trim();
|
@ -66,6 +66,9 @@ export const modifyIMDbImg = (url: string, widthInPx = 600) => {
|
||||
return url;
|
||||
};
|
||||
|
||||
const placeholderImageRegex = /https:\/\/m\.media-amazon.com\/images\/.{1}\/sash\/.*/;
|
||||
export const isIMDbImgPlaceholder = (url?: string | null) => url ? placeholderImageRegex.test(url) : false;
|
||||
|
||||
export const getProxiedIMDbImgUrl = (url: string) => {
|
||||
return `/api/media_proxy?url=${encodeURIComponent(url)}`;
|
||||
};
|
||||
|
Reference in New Issue
Block a user