Skip to content

Commit dfe8723

Browse files
committed
feat jable.tv
1 parent 99e31eb commit dfe8723

2 files changed

Lines changed: 165 additions & 0 deletions

File tree

lib/routes/jable/index.ts

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import { load } from 'cheerio';
2+
3+
import type { Route } from '@/types';
4+
import cache from '@/utils/cache';
5+
import got from '@/utils/got';
6+
import { parseDate } from '@/utils/parse-date';
7+
8+
export const route: Route = {
9+
path: '/search/:query',
10+
categories: ['multimedia'],
11+
example: '/jable/search/みなみ羽琉',
12+
parameters: {
13+
query: 'Search keyword',
14+
},
15+
features: {
16+
requireConfig: false,
17+
requirePuppeteer: false,
18+
antiCrawler: false,
19+
supportBT: false,
20+
supportPodcast: false,
21+
supportScihub: false,
22+
nsfw: true,
23+
},
24+
radar: [
25+
{
26+
source: ['jable.tv/search/:query'],
27+
target: '/:query',
28+
},
29+
],
30+
name: 'Jable 搜索结果',
31+
maintainers: [],
32+
handler,
33+
};
34+
35+
const GOT_HEADERS = {
36+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
37+
'Accept-Language': 'en-US,en;q=0.9',
38+
Referer: 'https://jable.tv/',
39+
};
40+
41+
// function renderDescription(item) {
42+
// return `
43+
// <a href="${item.link}">
44+
// <img src="${item.thumb}" style="max-width:100%" />
45+
// </a>
46+
// <p>
47+
// ${item.duration ? `<strong>Duration:</strong> ${item.duration}` : ''}
48+
// ${item.views ? `|<strong>Views:</strong> ${item.views}` : ''}
49+
// ${item.favorites ? `|<strong>Favorites:</strong> ${item.favorites}` : ''}
50+
// ${item.author ? `|<strong>Author:</strong> ${item.author}` : ''}
51+
// </p>
52+
// `.trim();
53+
// }
54+
55+
async function handler(ctx) {
56+
const { query } = ctx.req.param();
57+
const encodedQuery = encodeURIComponent(query);
58+
59+
const apiUrl = `https://jable.tv/search/${encodedQuery}/` + `?mode=async&function=get_block&block_id=list_videos_videos_list_search_result` + `&q=${encodedQuery}&sort_by=post_date`;
60+
61+
const response = await got(apiUrl, { headers: GOT_HEADERS });
62+
const $ = load(response.data);
63+
64+
const pageAuthor = $('section.content-header h2').first().text().trim() || query;
65+
66+
const items = await Promise.all(
67+
$('.video-img-box')
68+
.toArray()
69+
.map((el) => {
70+
const $el = $(el);
71+
72+
const $titleLink = $el.find('.detail h6.title a');
73+
const title = $titleLink.text().trim();
74+
const link = $titleLink.attr('href') ?? '';
75+
76+
const thumb = $el.find('img[data-src]').attr('data-src') ?? '';
77+
const preview = $el.find('img[data-preview]').attr('data-preview') ?? '';
78+
79+
// const duration = $el.find('.label').text().trim();
80+
// const subText = $el.find('.sub-title').text().trim();
81+
// const nums = subText.split(/\s+/);
82+
// const views = nums[0] ?? '';
83+
// const favorites = nums[1] ?? '';
84+
85+
const videoId = $el.find('[data-fav-video-id]').attr('data-fav-video-id') ?? link;
86+
87+
return cache.tryGet(`jable:video:${videoId}`, async () => {
88+
let pubDate;
89+
const author = pageAuthor;
90+
let videoUrl;
91+
92+
try {
93+
const { data } = await got(link, { headers: GOT_HEADERS });
94+
const $page = load(data);
95+
96+
const dateText = $page('.video-date').text().trim();
97+
if (dateText) {
98+
pubDate = parseDate(dateText);
99+
}
100+
101+
const script = $page('script')
102+
.toArray()
103+
.map((s) => $(s).html())
104+
.find((s) => s && s.includes('sources'));
105+
106+
if (script) {
107+
const match = script.match(/file:\s*"([^"]+)"/);
108+
if (match) {
109+
videoUrl = match[1];
110+
}
111+
}
112+
} catch {
113+
// 忽略错误
114+
}
115+
116+
return {
117+
title,
118+
link,
119+
guid: `jable:video:${videoId}`,
120+
pubDate,
121+
author,
122+
// description: renderDescription({
123+
// title,
124+
// link,
125+
// thumb,
126+
// duration,
127+
// views,
128+
// favorites,
129+
// author,
130+
// }),
131+
media: {
132+
content: preview
133+
? {
134+
url: preview,
135+
type: 'video/mp4',
136+
}
137+
: videoUrl
138+
? {
139+
url: videoUrl,
140+
type: 'video/mp4',
141+
}
142+
: undefined,
143+
thumbnail: {
144+
url: thumb,
145+
},
146+
},
147+
};
148+
});
149+
})
150+
);
151+
152+
return {
153+
title: query,
154+
link: `https://jable.tv/search/${encodedQuery}/`,
155+
description: `Search results for ${query}`,
156+
item: items,
157+
};
158+
}

lib/routes/jable/namaspace.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import type { Namespace } from '@/types';
2+
3+
export const namespace: Namespace = {
4+
name: 'jable',
5+
url: 'jable.tv',
6+
lang: 'zh',
7+
};

0 commit comments

Comments
 (0)