feat(route): javtrailers (#17590)
* feat(route): javtrailers * feat(casts): enhance description with castWiki data
This commit is contained in:
parent
a5263093b9
commit
b857abcf42
|
|
@ -0,0 +1,41 @@
|
|||
import { Route } from '@/types';
|
||||
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import cache from '@/utils/cache';
|
||||
import { baseUrl, getItem, headers, parseList } from './utils';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/casts/:cast',
|
||||
categories: ['multimedia'],
|
||||
example: '/javtrailers/casts/hibiki-otsuki',
|
||||
parameters: { cast: 'Cast name, can be found in the URL of the cast page' },
|
||||
radar: [
|
||||
{
|
||||
source: ['javtrailers.com/casts/:category'],
|
||||
},
|
||||
],
|
||||
name: 'Casts',
|
||||
maintainers: ['TonyRL'],
|
||||
url: 'javtrailers.com/casts',
|
||||
handler,
|
||||
};
|
||||
|
||||
async function handler(ctx) {
|
||||
const { cast } = ctx.req.param();
|
||||
|
||||
const response = await ofetch(`${baseUrl}/api/casts/${cast}?page=0`, {
|
||||
headers,
|
||||
});
|
||||
|
||||
const list = parseList(response.videos);
|
||||
|
||||
const items = await Promise.all(list.map((item) => cache.tryGet(item.link, () => getItem(item))));
|
||||
|
||||
return {
|
||||
title: `Watch ${response.cast.name} Jav Online | Japanese Adult Video - JavTrailers.com`,
|
||||
description: response.cast.castWiki?.description.replaceAll('\n', ' ') ?? `Watch ${response.cast.name} Jav video’s free, we have the largest Jav collections with high definition`,
|
||||
image: response.cast.avatar,
|
||||
link: `${baseUrl}/casts/${cast}`,
|
||||
item: items,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
import { Route } from '@/types';
|
||||
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import cache from '@/utils/cache';
|
||||
import { baseUrl, getItem, headers, parseList } from './utils';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/categories/:category',
|
||||
categories: ['multimedia'],
|
||||
example: '/javtrailers/categories/50001755',
|
||||
parameters: { category: 'Category name, can be found in the URL of the category page' },
|
||||
radar: [
|
||||
{
|
||||
source: ['javtrailers.com/categories/:category'],
|
||||
},
|
||||
],
|
||||
name: 'Categories',
|
||||
maintainers: ['TonyRL'],
|
||||
url: 'javtrailers.com/categories',
|
||||
handler,
|
||||
};
|
||||
|
||||
async function handler(ctx) {
|
||||
const { category } = ctx.req.param();
|
||||
|
||||
const response = await ofetch(`${baseUrl}/api/categories/${category}?page=0`, {
|
||||
headers,
|
||||
});
|
||||
|
||||
const list = parseList(response.videos);
|
||||
|
||||
const items = await Promise.all(list.map((item) => cache.tryGet(item.link, () => getItem(item))));
|
||||
|
||||
return {
|
||||
title: `Watch ${response.category.name} Jav Online | Japanese Adult Video - JavTrailers.com`,
|
||||
description: `Watch ${response.category.name} Jav video’s free, we have the largest Jav collections with high definition`,
|
||||
link: `${baseUrl}/categories/${category}`,
|
||||
item: items,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import type { Namespace } from '@/types';
|
||||
|
||||
export const namespace: Namespace = {
|
||||
name: 'JavTrailers',
|
||||
url: 'javtrailers.com',
|
||||
lang: 'ja',
|
||||
};
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
import { Route } from '@/types';
|
||||
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import cache from '@/utils/cache';
|
||||
import { baseUrl, getItem, headers, parseList } from './utils';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/studios/:studio',
|
||||
categories: ['multimedia'],
|
||||
example: '/javtrailers/studios/s1-no-1-style',
|
||||
parameters: { studio: 'Studio name, can be found in the URL of the studio page' },
|
||||
radar: [
|
||||
{
|
||||
source: ['javtrailers.com/studios/:category'],
|
||||
},
|
||||
],
|
||||
name: 'Studios',
|
||||
maintainers: ['TonyRL'],
|
||||
handler,
|
||||
};
|
||||
|
||||
async function handler(ctx) {
|
||||
const { studio } = ctx.req.param();
|
||||
|
||||
const response = await ofetch(`${baseUrl}/api/studios/${studio}?page=0`, {
|
||||
headers,
|
||||
});
|
||||
|
||||
const list = parseList(response.videos);
|
||||
|
||||
const items = await Promise.all(list.map((item) => cache.tryGet(item.link, () => getItem(item))));
|
||||
|
||||
return {
|
||||
title: `${response.studio.hotDvdIds?.join(' ') ?? response.studio.name} Jav Online | Japanese Adult Video - JavTrailers.com`,
|
||||
description: 'Watch Jav made by Prestige free, with high definition, we have over 4,000 studios available for free streaming.',
|
||||
link: `${baseUrl}/studios/${studio}`,
|
||||
item: items,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
{{ if videoInfo.image }}
|
||||
<img src="{{ videoInfo.image }}"><br>
|
||||
{{ /if }}
|
||||
|
||||
{{ if videoInfo.dvdId }}<b>DVD ID:</b> {{ videoInfo.dvdId }}<br>{{ /if }}
|
||||
{{ if videoInfo.contentId }}<b>Content ID:</b> {{ videoInfo.contentId }}<br>{{ /if }}
|
||||
{{ if videoInfo.releaseDate }}<b>Release Date:</b> {{ videoInfo.releaseDate }}<br>{{ /if }}
|
||||
{{ if videoInfo.duration }}<b>Duration:</b> {{ videoInfo.duration }} mins<br>{{ /if }}
|
||||
{{ if videoInfo.director }}<b>Director:</b> {{ videoInfo.director }} {{ videoInfo.jpDirector }}<br>{{ /if }}
|
||||
{{ if videoInfo.studio }}<b>Studio:</b> {{ videoInfo.studio.name }}<br>{{ /if }}
|
||||
{{ if videoInfo.categories }}
|
||||
<b>Categories:</b>
|
||||
{{ each videoInfo.categories c }}
|
||||
{{ c.name }},
|
||||
{{ /each }}
|
||||
<br>
|
||||
{{ /if }}
|
||||
{{ if videoInfo.casts }}
|
||||
<b>Cast(s):</b>
|
||||
{{ each videoInfo.casts c }}
|
||||
{{ c.name }} {{ c.jpName }}
|
||||
{{ /each }}
|
||||
<br>
|
||||
{{ /if }}
|
||||
|
||||
|
||||
{{ if videoInfo.gallery }}
|
||||
{{ each videoInfo.gallery g }}
|
||||
<img src="{{ g }}" ><br>
|
||||
{{ /each }}
|
||||
{{ /if }}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
export interface Video {
|
||||
_id: string;
|
||||
categories: Category[];
|
||||
casts: Cast[];
|
||||
director: string;
|
||||
gallery: string[];
|
||||
title: string;
|
||||
javLink: JavLink;
|
||||
contentId: string;
|
||||
dvdId: string;
|
||||
studio: Studio;
|
||||
releaseDate: string;
|
||||
duration: number;
|
||||
image: string;
|
||||
jpDirector: string;
|
||||
jpTitle: string;
|
||||
/**
|
||||
* HLS stream URL
|
||||
*/
|
||||
trailer: string;
|
||||
zhTitle: string;
|
||||
__v: number;
|
||||
}
|
||||
|
||||
interface Category {
|
||||
_id: string;
|
||||
slug: string;
|
||||
name: string;
|
||||
jpName: string;
|
||||
zhName: string;
|
||||
}
|
||||
|
||||
interface Cast {
|
||||
_id: string;
|
||||
slug: string;
|
||||
ruby: string;
|
||||
link: string;
|
||||
name: string;
|
||||
jpName: string;
|
||||
avatar: string;
|
||||
__v: number;
|
||||
}
|
||||
|
||||
interface JavLink {
|
||||
_id: string;
|
||||
link: string;
|
||||
processed: boolean;
|
||||
isProfessional: boolean;
|
||||
upcoming: boolean;
|
||||
__v: number;
|
||||
}
|
||||
|
||||
interface Studio {
|
||||
_id: string;
|
||||
slug: string;
|
||||
name: string;
|
||||
link: string;
|
||||
jpName: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
import { Video } from './types';
|
||||
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import { art } from '@/utils/render';
|
||||
import path from 'node:path';
|
||||
import { getCurrentPath } from '@/utils/helpers';
|
||||
const __dirname = getCurrentPath(import.meta.url);
|
||||
|
||||
export const baseUrl = 'https://javtrailers.com';
|
||||
export const headers = {
|
||||
Authorization: 'AELAbPQCh_fifd93wMvf_kxMD_fqkUAVf@BVgb2!md@TNW8bUEopFExyGCoKRcZX',
|
||||
};
|
||||
|
||||
export const hdGallery = (gallery) =>
|
||||
gallery.map((item) => {
|
||||
if (item.startsWith('https://pics.dmm.co.jp/')) {
|
||||
return item.replace(/-(\d+)\.jpg$/, 'jp-$1.jpg');
|
||||
} else if (item.startsWith('https://image.mgstage.com/')) {
|
||||
return item.replace(/cap_t1_/, 'cap_e_');
|
||||
}
|
||||
return item;
|
||||
});
|
||||
|
||||
export const parseList = (videos) =>
|
||||
videos.map((item) => ({
|
||||
title: `${item.dvdId} ${item.title}`,
|
||||
link: `${baseUrl}/video/${item.contentId}`,
|
||||
pubDate: parseDate(item.releaseDate),
|
||||
contentId: item.contentId,
|
||||
}));
|
||||
|
||||
export const getItem = async (item) => {
|
||||
const response = await ofetch(`${baseUrl}/api/video/${item.contentId}`, {
|
||||
headers,
|
||||
});
|
||||
|
||||
const videoInfo: Video = response.video;
|
||||
videoInfo.gallery = hdGallery(videoInfo.gallery);
|
||||
|
||||
item.description = art(path.join(__dirname, 'templates/description.art'), {
|
||||
videoInfo,
|
||||
});
|
||||
item.author = videoInfo.casts.map((cast) => `${cast.name} ${cast.jpName}`).join(', ');
|
||||
item.category = videoInfo.categories.map((category) => `${category.name}/${category.jpName}/${category.zhName}`);
|
||||
|
||||
return item;
|
||||
};
|
||||
Loading…
Reference in New Issue