|
| 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 | + name: 'Jable 搜索结果', |
| 25 | + maintainers: [], |
| 26 | + handler, |
| 27 | +}; |
| 28 | + |
| 29 | +const GOT_HEADERS = { |
| 30 | + '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', |
| 31 | + 'Accept-Language': 'en-US,en;q=0.9', |
| 32 | + Referer: 'https://jable.tv/', |
| 33 | +}; |
| 34 | + |
| 35 | +// function renderDescription(item) { |
| 36 | +// return ` |
| 37 | +// <a href="${item.link}"> |
| 38 | +// <img src="${item.thumb}" style="max-width:100%" /> |
| 39 | +// </a> |
| 40 | +// <p> |
| 41 | +// ${item.duration ? `<strong>Duration:</strong> ${item.duration}` : ''} |
| 42 | +// ${item.views ? `|<strong>Views:</strong> ${item.views}` : ''} |
| 43 | +// ${item.favorites ? `|<strong>Favorites:</strong> ${item.favorites}` : ''} |
| 44 | +// ${item.author ? `|<strong>Author:</strong> ${item.author}` : ''} |
| 45 | +// </p> |
| 46 | +// `.trim(); |
| 47 | +// } |
| 48 | + |
| 49 | +async function handler(ctx) { |
| 50 | + const { query } = ctx.req.param(); |
| 51 | + const encodedQuery = encodeURIComponent(query); |
| 52 | + |
| 53 | + 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`; |
| 54 | + |
| 55 | + const response = await got(apiUrl, { headers: GOT_HEADERS }); |
| 56 | + const $ = load(response.data); |
| 57 | + |
| 58 | + const pageAuthor = $('section.content-header h2').first().text().trim() || query; |
| 59 | + |
| 60 | + const items = await Promise.all( |
| 61 | + $('.video-img-box') |
| 62 | + .toArray() |
| 63 | + .map((el) => { |
| 64 | + const $el = $(el); |
| 65 | + |
| 66 | + const $titleLink = $el.find('.detail h6.title a'); |
| 67 | + const title = $titleLink.text().trim(); |
| 68 | + const link = $titleLink.attr('href') ?? ''; |
| 69 | + |
| 70 | + const thumb = $el.find('img[data-src]').attr('data-src') ?? ''; |
| 71 | + const preview = $el.find('img[data-preview]').attr('data-preview') ?? ''; |
| 72 | + |
| 73 | + // const duration = $el.find('.label').text().trim(); |
| 74 | + // const subText = $el.find('.sub-title').text().trim(); |
| 75 | + // const nums = subText.split(/\s+/); |
| 76 | + // const views = nums[0] ?? ''; |
| 77 | + // const favorites = nums[1] ?? ''; |
| 78 | + |
| 79 | + const videoId = $el.find('[data-fav-video-id]').attr('data-fav-video-id') ?? link; |
| 80 | + |
| 81 | + return cache.tryGet(`jable:video:${videoId}`, async () => { |
| 82 | + let pubDate; |
| 83 | + const author = pageAuthor; |
| 84 | + let videoUrl; |
| 85 | + |
| 86 | + try { |
| 87 | + const { data } = await got(link, { headers: GOT_HEADERS }); |
| 88 | + const $page = load(data); |
| 89 | + |
| 90 | + const dateText = $page('.video-date').text().trim(); |
| 91 | + if (dateText) { |
| 92 | + pubDate = parseDate(dateText); |
| 93 | + } |
| 94 | + |
| 95 | + const script = $page('script') |
| 96 | + .toArray() |
| 97 | + .map((s) => $(s).html()) |
| 98 | + .find((s) => s && s.includes('sources')); |
| 99 | + |
| 100 | + if (script) { |
| 101 | + const match = script.match(/file:\s*"([^"]+)"/); |
| 102 | + if (match) { |
| 103 | + videoUrl = match[1]; |
| 104 | + } |
| 105 | + } |
| 106 | + } catch { |
| 107 | + // 忽略错误 |
| 108 | + } |
| 109 | + |
| 110 | + return { |
| 111 | + title, |
| 112 | + link, |
| 113 | + guid: `jable:video:${videoId}`, |
| 114 | + pubDate, |
| 115 | + author, |
| 116 | + // description: renderDescription({ |
| 117 | + // title, |
| 118 | + // link, |
| 119 | + // thumb, |
| 120 | + // duration, |
| 121 | + // views, |
| 122 | + // favorites, |
| 123 | + // author, |
| 124 | + // }), |
| 125 | + media: { |
| 126 | + content: preview |
| 127 | + ? { |
| 128 | + url: preview, |
| 129 | + type: 'video/mp4', |
| 130 | + } |
| 131 | + : videoUrl |
| 132 | + ? { |
| 133 | + url: videoUrl, |
| 134 | + type: 'video/mp4', |
| 135 | + } |
| 136 | + : undefined, |
| 137 | + thumbnail: { |
| 138 | + url: thumb, |
| 139 | + }, |
| 140 | + }, |
| 141 | + }; |
| 142 | + }); |
| 143 | + }) |
| 144 | + ); |
| 145 | + |
| 146 | + return { |
| 147 | + title: query, |
| 148 | + link: `https://jable.tv/search/${encodedQuery}/`, |
| 149 | + description: `Search results for ${query}`, |
| 150 | + item: items, |
| 151 | + }; |
| 152 | +} |
0 commit comments