feat: add projectjav actress (#21698)

This commit is contained in:
Exat1979 2026-04-11 16:14:37 +02:00 committed by GitHub
parent d375d79d8b
commit 72d4ee7ab4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 141 additions and 0 deletions

View File

@ -0,0 +1,36 @@
import type { Route } from '@/types';
import { processItems, rootUrl } from './utils';
export const route: Route = {
path: '/actress/:id',
categories: ['multimedia'],
example: '/projectjav/actress/rima-arai-22198',
parameters: { id: 'Actress ID or slug, can be found in the actress page URL' },
features: {
requireConfig: false,
requirePuppeteer: false,
antiCrawler: false,
supportBT: false,
supportPodcast: false,
supportScihub: false,
nsfw: true,
},
radar: [
{
source: ['projectjav.com/actress/:id'],
target: '/actress/:id',
},
],
name: 'Actress',
maintainers: ['Exat1979'],
handler,
url: 'projectjav.com/',
description: 'Fetches the latest movies from a specific actress page on ProjectJAV.',
};
async function handler(ctx) {
const id = ctx.req.param('id').replace(/\/$/, '');
const currentUrl = `${rootUrl}/actress/${id}`;
return await processItems(currentUrl);
}

View File

@ -0,0 +1,8 @@
import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: 'ProjectJAV',
url: 'projectjav.com',
description: 'ProjectJAV provides adult video content information and streaming.',
lang: 'en',
};

View File

@ -0,0 +1,97 @@
import { load } from 'cheerio';
import type { DataItem } from '@/types';
import cache from '@/utils/cache';
import got from '@/utils/got';
import { parseDate } from '@/utils/parse-date';
const rootUrl = 'https://projectjav.com';
const processItems = async (currentUrl: string) => {
const response = await got({
method: 'get',
url: currentUrl,
});
const $ = load(response.data);
let items: DataItem[] = $('div.video-item')
.toArray()
.map((element) => {
const item = $(element);
const link = item.find('a').attr('href');
return {
title: item.find('div.name span').text() || '',
link: link?.startsWith('http') ? link : `${rootUrl}${link}`,
};
})
.filter((item) => item.link && /\/movie\/.*/.test(item.link));
items = await Promise.all(
items.map((item) =>
cache.tryGet(item.link!, async () => {
const detailResponse = await got({
method: 'get',
url: item.link,
});
const content = load(detailResponse.data);
// Remove ads
content('div.top-banner-ads, div.bottom-content-ads').remove();
// Get main content
const mainContent = content('main');
// Extract title
const h1Title = mainContent.find('h1').text().trim();
if (h1Title) {
item.title = h1Title;
}
// Extract categories
const categories = mainContent
.find('div.badge a')
.toArray()
.map((v) => content(v).text().trim())
.filter(Boolean);
if (categories.length > 0) {
item.category = categories;
}
// Extract author/actress (support multiple actresses)
const actresses = mainContent
.find('div.actress-item a')
.toArray()
.map((v) => content(v).text().trim())
.filter(Boolean);
if (actresses.length > 0) {
item.author = actresses.join(', ');
}
// Extract date
const dateEl = mainContent
.find('div.second-main~div.row>div.col-3')
.toArray()
.find((el) => content(el).text().includes('Date added'));
const dateText = dateEl ? content(dateEl).next().text().trim() : null;
if (dateText) {
// ProjectJAV uses DD/MM/YYYY format
item.pubDate = parseDate(dateText, 'DD/MM/YYYY');
}
// Get description
item.description = mainContent.html() || '';
return item;
})
)
);
return {
title: $('title').text() || 'ProjectJAV',
link: currentUrl,
item: items,
};
};
export { processItems, rootUrl };