Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 22 additions & 9 deletions lib/routes/javdb/actors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ import { Route } from '@/types';
import utils from './utils';

export const route: Route = {
path: '/actors/:id/:filter?',
path: '/actors/:id/:filter?/:addon_tags?',
categories: ['multimedia'],
example: '/javdb/actors/R2Vg',
parameters: { id: '编号,可在演员页 URL 中找到', filter: '过滤,见下表,默认为 `全部`' },
parameters: {
id: '编号,可在演员页 URL 中找到',
filter: '过滤,见下表,默认为 `全部`',
addon_tags: '类别过滤规则,默认为不过滤类别',
},
features: {
requireConfig: [
{
Expand All @@ -32,19 +36,28 @@ export const route: Route = {
url: 'javdb.com/',
description: `| 全部 | 可播放 | 單體作品 | 可下載 | 含字幕 |
| ---- | ------ | -------- | ------ | ------ |
| | p | s | d | c |
| a | p | s | d | c |

所有演员编号参见 [演員庫](https://javdb.com/actors)

可用 addon_tags 参数添加额外的过滤 tag,可从网页 url 中获取,例如 \`/javdb/actors/R2Vg?addon_tags=212,18\` 可筛选 \`VR\` 和 \`中出\`。`,
可使用 addon_tags 添加额外的 tag 过滤规则,\`+xxx\` 代表包含 id 为 \`xxx\` 的 tag,\`-xxx\`代表排除。当包含所有 \`+xxx\` 视为包含,在此基础上包含任意 \`-xxx\` 视为排除。

例如 \`/javdb/actors/R2Vg/d/+20,-8\` 可在筛选 \`可下載\` 的基础上继续筛选 \`VR\`,并排除 \`精選綜合\`。

当包含 \`-xxx\` 排除规则时,响应时间可能变长。`,
};

async function handler(ctx) {
const id = ctx.req.param('id');
const filter = ctx.req.param('filter') ?? '';
const addonTags = ctx.req.query('addon_tags') ?? '';

const finalTags = addonTags && filter ? `${filter},${addonTags}` : `${filter}${addonTags}`;
let filter = ctx.req.param('filter') ?? '';
filter = filter === 'a' ? '' : filter;
const addonTags = (ctx.req.param('addon_tags') ?? '').split(',');
const includeTags = addonTags
.filter((item) => item[0] === '+')
.map((item) => item.slice(1))
.join(',');
const excludeTags = new Set(addonTags.filter((item) => item[0] === '-').map((item) => item.slice(1)));
const finalTags = includeTags && filter ? `${filter},${includeTags}` : `${filter}${includeTags}`;
const currentUrl = `/actors/${id}${finalTags ? `?t=${finalTags}` : ''}`;

const filters = {
Expand All @@ -56,5 +69,5 @@ async function handler(ctx) {
};

const title = `JavDB${filters[filter] === '' ? '' : ` - ${filters[filter]}`} `;
return await utils.ProcessItems(ctx, currentUrl, title);
return await utils.ProcessItems(ctx, currentUrl, title, excludeTags);
}
99 changes: 83 additions & 16 deletions lib/routes/javdb/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ import { config } from '@/config';
import { Cookie, CookieJar } from 'tough-cookie';

import ConfigNotFoundError from '@/errors/types/config-not-found';
import { DataItem } from '@/types';
const allowDomain = new Set(['javdb.com', 'javdb36.com', 'javdb007.com', 'javdb521.com']);
const itemCategoryRegex = /c(\d+)=(\d+)/;

const ProcessItems = async (ctx, currentUrl, title) => {
const ProcessItems = async (ctx, currentUrl, title, excludeTags = new Set()) => {
const domain = ctx.req.query('domain') ?? 'javdb.com';
const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 20;
const url = new URL(currentUrl, `https://${domain}`);
if (!config.feature.allow_user_supply_unsafe_domain && !allowDomain.has(url.hostname)) {
throw new ConfigNotFoundError(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
Expand All @@ -29,6 +32,20 @@ const ProcessItems = async (ctx, currentUrl, title) => {
cookie && cookieJar.setCookie(cookie, rootUrl);
}

const results = [] as DataItem[];
const batchSize = excludeTags.size > 0 ? 5 : limit;
const subject = await processPages(rootUrl, url, 1, cookieJar, batchSize, limit, excludeTags, results);

return {
title: subject === '' ? title : `${subject} - ${title}`,
link: url.href,
item: results,
};
};

const processPages = async (rootUrl, url, page, cookieJar, batchSize, limit, excludeTags, results) => {
url.searchParams.set('page', String(page));

const response = await got({
method: 'get',
url: url.href,
Expand All @@ -42,8 +59,7 @@ const ProcessItems = async (ctx, currentUrl, title) => {

$('.tags, .tag-can-play, .over18-modal').remove();

let items = $('div.item')
.slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 20)
const items = $('div.item')
.toArray()
.map((item) => {
item = $(item);
Expand All @@ -54,8 +70,33 @@ const ProcessItems = async (ctx, currentUrl, title) => {
};
});

items = await Promise.all(
items.map((item) =>
if (items.length === 0) {
return null;
}

await processSinglePage(cookieJar, items, 0, limit, batchSize, excludeTags, results);

if ($('a.pagination-next').length !== 0 && results.length < limit) {
await processPages(rootUrl, url, page + 1, cookieJar, batchSize, limit, excludeTags, results);
}

if (page !== 1) {
return null;
}

const htmlTitle = $('title').text();
return htmlTitle.includes('|') ? htmlTitle.split('|')[0] : '';
};

const processSinglePage = async (cookieJar, items, index, limit, batchSize, excludeTags, results) => {
if (index >= items.length) {
return;
}

let batch = items.slice(index, index + batchSize);

batch = await Promise.all(
batch.map((item) =>
cache.tryGet(item.link, async () => {
const detailResponse = await got({
method: 'get',
Expand All @@ -80,25 +121,51 @@ const ProcessItems = async (ctx, currentUrl, title) => {
content(this).attr('src', content(this).parent().attr('href'));
});

item.category = content('.panel-block .value a')
.toArray()
.map((v) => content(v).text());
const itemCategories = content('.panel-block .value a').toArray();
const categoryIds: string[] = [];
const category: string[] = [];
for (const item_category of itemCategories) {
if ('href' in item_category.attribs) {
const match = item_category.attribs.href.match(itemCategoryRegex);
if (match !== null) {
categoryIds.push(match[2]);
}
}
category.push(content(item_category).text());
}
item.category = category;
item.author = content('.panel-block .value').last().parent().find('.value a').first().text();
item.description = content('.cover-container, .column-video-cover').html() + content('.movie-panel-info').html() + content('#magnets-content').html() + content('.preview-images').html();

item._extra = {
category_ids: categoryIds,
};

return item;
})
)
);
for (const item of batch) {
if (results.length >= limit) {
break;
}
let shouldExclude = false;
if (excludeTags.size > 0 && 'category_ids' in item._extra) {
for (const categoryId of item._extra.category_ids) {
if (excludeTags.has(categoryId)) {
shouldExclude = true;
break;
}
}
}
if (!shouldExclude) {
results.push(item);
}
}

const htmlTitle = $('title').text();
const subject = htmlTitle.includes('|') ? htmlTitle.split('|')[0] : '';

return {
title: subject === '' ? title : `${subject} - ${title}`,
link: url.href,
item: items,
};
if (results.length < limit) {
await processSinglePage(cookieJar, items, index + batchSize, limit, batchSize, excludeTags, results);
}
};

export default { ProcessItems };
Loading