rsshub
Version:
Make RSS Great Again!
229 lines (194 loc) • 8.95 kB
text/typescript
import { Route } from '@/types';
import cache from '@/utils/cache';
import ofetch from '@/utils/ofetch';
import { load } from 'cheerio';
import timezone from '@/utils/timezone';
import { parseDate } from '@/utils/parse-date';
import { art } from '@/utils/render';
import path from 'node:path';
import md5 from '@/utils/md5';
export const route: Route = {
path: '/:id?',
name: '分类',
url: '4ksj.com',
maintainers: ['nczitzk'],
handler,
example: '/4ksj/4k-uhd-1',
parameters: { id: '分类 id,默认为最新4K电影' },
description: `::: tip
若订阅 [最新 4K 电影](https://www.4ksj.com/4k-uhd-1.html),网址为 \`https://www.4ksj.com/4k-uhd-1.html\`。截取 \`https://www.4ksj.com/\` 到末尾 \`.html\` 的部分 \`4k-uhd-1\` 作为参数,此时路由为 [\`/4ksj/4k-uhd-1\`](https://rsshub.app/4ksj/4k-uhd-1)。
若订阅子分类 [Dolby Vision 动作 4K 电影](https://www.4ksj.com/4k-uhd-s7-display-3-dytypes-1-1.html),网址为 \`https://www.4ksj.com/4k-uhd-s7-display-3-dytypes-1-1.html\`。截取 \`https://www.4ksj.com/forum-\` 到末尾 \`.html\` 的部分 \`4kdianying-s7-dianyingbiaozhun-3-dytypes-9-1\` 作为参数,此时路由为 [\`/4ksj/4k-uhd-s7-display-3-dytypes-1-1\`](https://rsshub.app/4ksj/4k-uhd-s7-display-3-dytypes-1-1)。
:::`,
categories: ['multimedia'],
};
function stringtoHex(acSTR) {
let val = '';
for (let i = 0; i <= acSTR.length - 1; i++) {
const str = acSTR.charAt(i);
const code = str.codePointAt();
val += code;
}
return val;
}
async function handler(ctx) {
const { id = '4k-uhd-1' } = ctx.req.param();
const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 25;
const rootUrl = 'https://www.4ksj.com';
const currentUrl = new URL(`${id}.html`, rootUrl).href;
const response = await ofetch(currentUrl, {
responseType: 'arrayBuffer',
});
const decoder = new TextDecoder('gbk');
const $ = load(decoder.decode(response));
const language = 'zh';
const image = $('div.nexlogo img').prop('src');
let items = $('div.nex_cmo_piv a')
.slice(0, limit)
.toArray()
.map((item) => {
item = $(item);
return {
link: new URL(item.prop('href'), rootUrl).href,
};
});
const getCookie = () =>
cache.tryGet('4ksj:cookie', async () => {
const response = await ofetch(items[0].link);
const $ = load(response);
const scriptPath = $('script').attr('src')!;
const scriptUrl = new URL(scriptPath, rootUrl).href;
const scriptResponse = await ofetch(scriptUrl);
const key = scriptResponse.match(/{var key="(.*?)"/)?.[1];
const value = scriptResponse.match(/",value="(.*?)"/)?.[1];
const getPath = scriptResponse.match(/\.get\("(.*?&key=)"/)?.[1];
if (!key || !value || !getPath) {
throw new Error('Failed to get cookie');
}
const cookieResponse = await ofetch.raw(`${rootUrl}${getPath}${key}&value=${md5(stringtoHex(value))}`);
return cookieResponse.headers
.getSetCookie()
.map((c) => c.split(';')[0])
.join('; ');
});
const cookie = await getCookie();
items = await Promise.all(
items.map((item) =>
cache.tryGet(item.link, async () => {
const detailResponse = await ofetch(item.link, {
responseType: 'arrayBuffer',
headers: {
Cookie: cookie as string,
},
});
const $$ = load(decoder.decode(detailResponse));
$$('div.nex_drama_intros em').first().remove();
$$('strong font').each((_, el) => {
el = $$(el);
el.parent().remove();
});
const title = $$('div.nex_drama_Top h5').text();
const description = $$('div.nex_drama_intros').html();
const picture =
$$('div.nex_drama_pic')
.html()
.match(/background:url\((.*?)\)/)?.[1] ?? '';
const details = $$('li.nex_drama_Detail_li, li.nex_drama_Detail_lis dd')
.toArray()
.map((li) => {
li = $$(li);
const key = li.find('em').text().replaceAll(/:|\s/g, '');
const value = li.find('span').length === 0 ? li.contents().last().text().trim() : li.find('span').text().trim();
return { [key]: value };
});
const mergedDetails = Object.assign({}, ...details);
const links =
$$('td.t_f ignore_js_op').length === 0
? $$('td.t_f strong')
.toArray()
.map((l) => {
l = $$(l);
const title = l.contents().first().text();
const link = l.next().prop('href') ?? l.nextUntil('a').next().prop('href');
item.enclosure_url = item.enclosure_url ?? link;
item.enclosure_type = item.enclosure_type ?? 'application/x-bittorrent';
item.enclosure_title = item.enclosure_title ?? title;
return {
title,
tags: l
.contents()
.last()
.text()
.match(/【(.*?)】/g),
link,
};
})
: $$('div.newfujian')
.toArray()
.map((l) => {
l = $$(l);
return {
title: l.find('p.filename').prop('title') || l.find('p.filename').text(),
tags: l
.find('div.fileaq')
.text()
.match(/【(.*?)】/g),
link: l.find('div.down_2 a').prop('href'),
};
});
const pubDateEl = $$('table.boxtable em').first();
const pubDate =
pubDateEl.find('span[title]').length === 0
? pubDateEl
.first()
.text()
.replace(/发表于\s/, '')
: pubDateEl.find('span[title]').prop('title');
item.title = title;
item.description = art(path.join(__dirname, 'templates/description.art'), {
images: picture
? [
{
src: picture,
alt: title,
},
]
: undefined,
title,
keys: Object.keys(mergedDetails),
details: mergedDetails,
description,
info: $$('div.nex_drama_sums').html(),
links,
});
item.pubDate = timezone(parseDate(pubDate, 'YYYY-M-D HH:mm:ss'), +8);
item.category = Object.values(mergedDetails)
.flatMap((c) => c.split(/\s/))
.filter(Boolean);
item.author = mergedDetails['导演'];
item.content = {
html: description,
text: $$('div.nex_drama_intros').text(),
};
item.image = picture;
item.banner = picture;
item.language = language;
return item;
})
)
);
return {
title: `4k世界 - ${
$('#fontsearch ul.cl li.a')
.toArray()
.map((a) => $(a).text())
.join('+') || '不限'
}`,
description: $('meta[name="description"]').prop('content'),
link: currentUrl,
item: items,
allowEmpty: true,
image,
author: $('meta[name="application-name"]').prop('content'),
language,
};
}