diff --git a/lib/router.js b/lib/router.js index d1f5dda1ab6038..9ef5ac95027b31 100644 --- a/lib/router.js +++ b/lib/router.js @@ -93,12 +93,12 @@ router.get('/konachan.com/post/popular_recent/:period', lazyloadRouteHandler('./ router.get('/konachan.net/post/popular_recent/:period', lazyloadRouteHandler('./routes/konachan/post_popular_recent')); // PornHub -router.get('/pornhub/category/:caty', lazyloadRouteHandler('./routes/pornhub/category')); -router.get('/pornhub/search/:keyword', lazyloadRouteHandler('./routes/pornhub/search')); -router.get('/pornhub/:language?/category_url/:url?', lazyloadRouteHandler('./routes/pornhub/category_url')); -router.get('/pornhub/:language?/users/:username', lazyloadRouteHandler('./routes/pornhub/users')); -router.get('/pornhub/:language?/model/:username/:sort?', lazyloadRouteHandler('./routes/pornhub/model')); -router.get('/pornhub/:language?/pornstar/:username/:sort?', lazyloadRouteHandler('./routes/pornhub/pornstar')); +// router.get('/pornhub/category/:caty', lazyloadRouteHandler('./routes/pornhub/category')); +// router.get('/pornhub/search/:keyword', lazyloadRouteHandler('./routes/pornhub/search')); +// router.get('/pornhub/:language?/category_url/:url?', lazyloadRouteHandler('./routes/pornhub/category_url')); +// router.get('/pornhub/:language?/users/:username', lazyloadRouteHandler('./routes/pornhub/users')); +// router.get('/pornhub/:language?/model/:username/:sort?', lazyloadRouteHandler('./routes/pornhub/model')); +// router.get('/pornhub/:language?/pornstar/:username/:sort?', lazyloadRouteHandler('./routes/pornhub/pornstar')); // yande.re router.get('/yande.re/post/popular_recent', lazyloadRouteHandler('./routes/yande.re/post_popular_recent')); diff --git a/lib/routes/pornhub/category.js b/lib/routes/pornhub/category.js deleted file mode 100644 index 5dbc5a95767566..00000000000000 --- a/lib/routes/pornhub/category.js +++ /dev/null @@ -1,22 +0,0 @@ -const got = require('@/utils/got'); - -module.exports = async (ctx) => { - const currentUrl = `https://pornhub.com/webmasters/search?category=${ctx.params.caty}`; - const response = await got({ - method: 'get', - url: currentUrl, - }); - - const list = response.data.videos.map((item) => ({ - title: item.title, - link: item.url, - description: ``, - pubDate: new Date(item.publish_date).toUTCString(), - })); - - ctx.state.data = { - title: `Pornhub - ${ctx.params.caty}`, - link: currentUrl, - item: list, - }; -}; diff --git a/lib/routes/pornhub/category_url.js b/lib/routes/pornhub/category_url.js deleted file mode 100644 index f6a4e5ce91578e..00000000000000 --- a/lib/routes/pornhub/category_url.js +++ /dev/null @@ -1,34 +0,0 @@ -const got = require('@/utils/got'); -const cheerio = require('cheerio'); -const { isValidHost } = require('@/utils/valid-host'); - -module.exports = async (ctx) => { - const language = ctx.params.language || 'www'; - const url = ctx.params.url || 'video'; - const link = `https://${language}.pornhub.com/${url}`; - if (!isValidHost(language)) { - throw Error('Invalid language'); - } - - const response = await got.get(link); - const $ = cheerio.load(response.data); - const list = $('#videoCategory .videoBox'); - - ctx.state.data = { - title: $('title').first().text(), - link, - item: - list && - list - .map((_, e) => { - e = $(e); - - return { - title: e.find('span.title a').text(), - link: `https://www.pornhub.com` + e.find('span.title a').attr('href'), - description: ``, - }; - }) - .get(), - }; -}; diff --git a/lib/routes/pornhub/model.js b/lib/routes/pornhub/model.js deleted file mode 100644 index 468db7cf1ec386..00000000000000 --- a/lib/routes/pornhub/model.js +++ /dev/null @@ -1,35 +0,0 @@ -const got = require('@/utils/got'); -const cheerio = require('cheerio'); -const { isValidHost } = require('@/utils/valid-host'); - -module.exports = async (ctx) => { - const language = ctx.params.language || 'www'; - const username = ctx.params.username; - const sort = ctx.params.sort || 'mr'; - const link = `https://${language}.pornhub.com/model/${username}/videos?o=${sort}`; - if (!isValidHost(language)) { - throw Error('Invalid language'); - } - - const response = await got.get(link); - const $ = cheerio.load(response.data); - const list = $('#mostRecentVideosSection .videoBox'); - - ctx.state.data = { - title: $('title').first().text(), - link, - item: - list && - list - .map((_, e) => { - e = $(e); - - return { - title: e.find('span.title a').text(), - link: `https://www.pornhub.com` + e.find('span.title a').attr('href'), - description: ``, - }; - }) - .get(), - }; -}; diff --git a/lib/routes/pornhub/pornstar.js b/lib/routes/pornhub/pornstar.js deleted file mode 100644 index 771ae277674cf8..00000000000000 --- a/lib/routes/pornhub/pornstar.js +++ /dev/null @@ -1,35 +0,0 @@ -const got = require('@/utils/got'); -const cheerio = require('cheerio'); -const { isValidHost } = require('@/utils/valid-host'); - -module.exports = async (ctx) => { - const language = ctx.params.language || 'www'; - const username = ctx.params.username; - const sort = ctx.params.sort || 'mr'; - const link = `https://${language}.pornhub.com/pornstar/${username}/videos?o=${sort}`; - if (!isValidHost(language)) { - throw Error('Invalid language'); - } - - const response = await got.get(link); - const $ = cheerio.load(response.data); - const list = $('#mostRecentVideosSection .videoBox'); - - ctx.state.data = { - title: $('title').first().text(), - link, - item: - list && - list - .map((_, e) => { - e = $(e); - - return { - title: e.find('span.title a').text(), - link: `https://www.pornhub.com` + e.find('span.title a').attr('href'), - description: ``, - }; - }) - .get(), - }; -}; diff --git a/lib/routes/pornhub/search.js b/lib/routes/pornhub/search.js deleted file mode 100644 index 4d8dc925d94088..00000000000000 --- a/lib/routes/pornhub/search.js +++ /dev/null @@ -1,22 +0,0 @@ -const got = require('@/utils/got'); - -module.exports = async (ctx) => { - const currentUrl = `https://pornhub.com/webmasters/search?search=${ctx.params.keyword}`; - const response = await got({ - method: 'get', - url: currentUrl, - }); - - const list = response.data.videos.map((item) => ({ - title: item.title, - link: item.url, - description: ``, - pubDate: new Date(item.publish_date).toUTCString(), - })); - - ctx.state.data = { - title: `Pornhub - ${ctx.params.keyword}`, - link: currentUrl, - item: list, - }; -}; diff --git a/lib/routes/pornhub/users.js b/lib/routes/pornhub/users.js deleted file mode 100644 index a632b166375ce5..00000000000000 --- a/lib/routes/pornhub/users.js +++ /dev/null @@ -1,35 +0,0 @@ -const got = require('@/utils/got'); -const cheerio = require('cheerio'); -const { isValidHost } = require('@/utils/valid-host'); - -module.exports = async (ctx) => { - const language = ctx.params.language || 'www'; - const username = ctx.params.username; - const link = `https://${language}.pornhub.com/users/${username}/videos`; - if (!isValidHost(language)) { - throw Error('Invalid language'); - } - - const response = await got.get(link); - const $ = cheerio.load(response.data); - const list = $('.videoUList .videoBox'); - - ctx.state.data = { - title: $('title').first().text(), - link, - allowEmpty: true, - item: - list && - list - .map((_, e) => { - e = $(e); - - return { - title: e.find('span.title a').text(), - link: `https://www.pornhub.com` + e.find('span.title a').attr('href'), - description: ``, - }; - }) - .get(), - }; -}; diff --git a/lib/v2/pornhub/category.js b/lib/v2/pornhub/category.js new file mode 100644 index 00000000000000..df09774424e338 --- /dev/null +++ b/lib/v2/pornhub/category.js @@ -0,0 +1,44 @@ +const got = require('@/utils/got'); +const { parseDate } = require('@/utils/parse-date'); +const { defaultDomain, renderDescription } = require('./utils'); +const config = require('@/config').value; + +module.exports = async (ctx) => { + const category = ctx.params.caty; + + const categories = await ctx.cache.tryGet('pornhub:categories', async () => { + const { data } = await got(`${defaultDomain}/webmasters/categories`); + return data.categories; + }); + + const categoryId = isNaN(category) ? categories.find((item) => item.category === category)?.id : category; + const categoryName = isNaN(category) ? category : categories.find((item) => item.id === parseInt(category)).category; + + const response = await ctx.cache.tryGet( + `pornhub:category:${categoryName}`, + async () => { + const { data } = await got(`${defaultDomain}/webmasters/search?category=${categoryName}`); + return data; + }, + config.cache.routeExpire, + false + ); + + if (response.code) { + throw Error(response.message); + } + + const list = response.videos.map((item) => ({ + title: item.title, + link: item.url, + description: renderDescription({ thumbs: item.thumbs }), + pubDate: parseDate(item.publish_date), + category: [...new Set([...item.tags.map((t) => t.tag_name), ...item.categories.map((c) => c.category)])], + })); + + ctx.state.data = { + title: `Pornhub - ${categoryName}`, + link: `${defaultDomain}/video?c=${categoryId}`, + item: list, + }; +}; diff --git a/lib/v2/pornhub/category_url.js b/lib/v2/pornhub/category_url.js new file mode 100644 index 00000000000000..bdfe0fc85dca9b --- /dev/null +++ b/lib/v2/pornhub/category_url.js @@ -0,0 +1,25 @@ +const got = require('@/utils/got'); +const cheerio = require('cheerio'); +const { isValidHost } = require('@/utils/valid-host'); +const { headers, parseItems } = require('./utils'); + +module.exports = async (ctx) => { + const { language = 'www', url = 'video' } = ctx.params; + const link = `https://${language}.pornhub.com/${url}`; + if (!isValidHost(language)) { + throw Error('Invalid language'); + } + + const { data: response } = await got(link, { headers }); + const $ = cheerio.load(response); + const items = $('#videoCategory .videoBox') + .toArray() + .map((e) => parseItems($(e))); + + ctx.state.data = { + title: $('title').first().text(), + link, + language: $('html').attr('lang'), + item: items, + }; +}; diff --git a/lib/v2/pornhub/maintainer.js b/lib/v2/pornhub/maintainer.js new file mode 100644 index 00000000000000..a3b4c367abb13e --- /dev/null +++ b/lib/v2/pornhub/maintainer.js @@ -0,0 +1,8 @@ +module.exports = { + '/category/:caty': ['nczitzk'], + '/search/:keyword': ['nczitzk'], + '/:language?/category_url/:url?': ['I2IMk', 'queensferryme'], + '/:language?/model/:username/:sort?': ['I2IMk', 'queensferryme'], + '/:language?/pornstar/:username/:sort?': ['I2IMk', 'queensferryme'], + '/:language?/users/:username': ['I2IMk', 'queensferryme'], +}; diff --git a/lib/v2/pornhub/model.js b/lib/v2/pornhub/model.js new file mode 100644 index 00000000000000..950d0f2607c3f5 --- /dev/null +++ b/lib/v2/pornhub/model.js @@ -0,0 +1,29 @@ +const got = require('@/utils/got'); +const cheerio = require('cheerio'); +const { isValidHost } = require('@/utils/valid-host'); +const { headers, parseItems } = require('./utils'); + +module.exports = async (ctx) => { + const { language = 'www', username, sort = '' } = ctx.params; + const link = `https://${language}.pornhub.com/model/${username}/videos${sort ? `?o=${sort}` : ''}`; + if (!isValidHost(language)) { + throw Error('Invalid language'); + } + + const { data: response } = await got(link, { headers }); + const $ = cheerio.load(response); + const items = $('#mostRecentVideosSection .videoBox') + .toArray() + .map((e) => parseItems($(e))); + + ctx.state.data = { + title: $('title').first().text(), + description: $('section.aboutMeSection').text().trim(), + link, + image: $('#coverPictureDefault').attr('src'), + logo: $('#getAvatar').attr('src'), + icon: $('#getAvatar').attr('src'), + language: $('html').attr('lang'), + item: items, + }; +}; diff --git a/lib/v2/pornhub/pornstar.js b/lib/v2/pornhub/pornstar.js new file mode 100644 index 00000000000000..23fd812a32087e --- /dev/null +++ b/lib/v2/pornhub/pornstar.js @@ -0,0 +1,29 @@ +const got = require('@/utils/got'); +const cheerio = require('cheerio'); +const { isValidHost } = require('@/utils/valid-host'); +const { headers, parseItems } = require('./utils'); + +module.exports = async (ctx) => { + const { language = 'www', username, sort = 'mr' } = ctx.params; + const link = `https://${language}.pornhub.com/pornstar/${username}/videos?o=${sort}`; + if (!isValidHost(language)) { + throw Error('Invalid language'); + } + + const { data: response } = await got(link, { headers }); + const $ = cheerio.load(response); + const items = $('#mostRecentVideosSection .videoBox') + .toArray() + .map((e) => parseItems($(e))); + + ctx.state.data = { + title: $('title').first().text(), + description: $('section.aboutMeSection').text().trim(), + link, + image: $('#coverPictureDefault').attr('src'), + logo: $('#getAvatar').attr('src'), + icon: $('#getAvatar').attr('src'), + language: $('html').attr('lang'), + item: items, + }; +}; diff --git a/lib/v2/pornhub/radar.js b/lib/v2/pornhub/radar.js new file mode 100644 index 00000000000000..48913ac590cfa9 --- /dev/null +++ b/lib/v2/pornhub/radar.js @@ -0,0 +1,46 @@ +module.exports = { + 'pornhub.com': { + _name: 'PornHub', + '.': [ + { + title: 'Category', + docs: 'https://docs.rsshub.app/routes/multimedia#pornhub', + source: ['/categories/:caty', '/video'], + target: (params, url) => { + if (params.caty) { + return `/pornhub/category/${params.caty}`; + } + return `/pornhub/category/${new URL(url).searchParams.get('c')}`; + }, + }, + { + title: 'Keyword Search', + docs: 'https://docs.rsshub.app/routes/multimedia#pornhub', + source: ['/video/search'], + target: (_, url) => `/pornhub/category/${new URL(url).searchParams.get('search')}`, + }, + { + title: 'Users', + docs: 'https://docs.rsshub.app/routes/multimedia#pornhub', + source: ['/users/:username/*'], + target: '/pornhub/users/:username', + }, + { + title: 'Verified amateur / Model', + docs: 'https://docs.rsshub.app/routes/multimedia#pornhub', + source: ['/model/:username/*'], + target: '/pornhub/model/:username', + }, + { + title: 'Verified model / Pornstar', + docs: 'https://docs.rsshub.app/routes/multimedia#pornhub', + source: ['/pornstar/:username/*'], + target: '/pornhub/pornstar/:username', + }, + { + title: 'Video List', + docs: 'https://docs.rsshub.app/routes/multimedia#pornhub', + }, + ], + }, +}; diff --git a/lib/v2/pornhub/router.js b/lib/v2/pornhub/router.js new file mode 100644 index 00000000000000..4ebf5156dc4529 --- /dev/null +++ b/lib/v2/pornhub/router.js @@ -0,0 +1,8 @@ +module.exports = (router) => { + router.get('/category/:caty', require('./category')); + router.get('/search/:keyword', require('./search')); + router.get('/:language?/category_url/:url?', require('./category_url')); + router.get('/:language?/model/:username/:sort?', require('./model')); + router.get('/:language?/pornstar/:username/:sort?', require('./pornstar')); + router.get('/:language?/users/:username', require('./users')); +}; diff --git a/lib/v2/pornhub/search.js b/lib/v2/pornhub/search.js new file mode 100644 index 00000000000000..6aaa5d7d4b6416 --- /dev/null +++ b/lib/v2/pornhub/search.js @@ -0,0 +1,23 @@ +const got = require('@/utils/got'); +const { parseDate } = require('@/utils/parse-date'); +const { defaultDomain, renderDescription } = require('./utils'); + +module.exports = async (ctx) => { + const { keyword } = ctx.params; + const currentUrl = `${defaultDomain}/webmasters/search?search=${keyword}`; + const response = await got(currentUrl); + + const list = response.data.videos.map((item) => ({ + title: item.title, + link: item.url, + description: renderDescription({ thumbs: item.thumbs }), + pubDate: parseDate(item.publish_date), + category: [...new Set([...item.tags.map((t) => t.tag_name), ...item.categories.map((c) => c.category)])], + })); + + ctx.state.data = { + title: `Pornhub - ${keyword}`, + link: currentUrl, + item: list, + }; +}; diff --git a/lib/v2/pornhub/templates/description.art b/lib/v2/pornhub/templates/description.art new file mode 100644 index 00000000000000..33aea6d4e73fc0 --- /dev/null +++ b/lib/v2/pornhub/templates/description.art @@ -0,0 +1,11 @@ +{{ if previewVideo }} + +{{ /if }} + +{{ if thumbs }} + {{ each thumbs t }} + + {{ /each }} +{{ /if }} diff --git a/lib/v2/pornhub/users.js b/lib/v2/pornhub/users.js new file mode 100644 index 00000000000000..6fd8b98a3227d4 --- /dev/null +++ b/lib/v2/pornhub/users.js @@ -0,0 +1,30 @@ +const got = require('@/utils/got'); +const cheerio = require('cheerio'); +const { isValidHost } = require('@/utils/valid-host'); +const { headers, parseItems } = require('./utils'); + +module.exports = async (ctx) => { + const { language = 'www', username } = ctx.params; + const link = `https://${language}.pornhub.com/users/${username}/videos`; + if (!isValidHost(language)) { + throw Error('Invalid language'); + } + + const { data: response } = await got(link, { headers }); + const $ = cheerio.load(response); + const items = $('.videoUList .videoBox') + .toArray() + .map((e) => parseItems($(e))); + + ctx.state.data = { + title: $('title').first().text(), + description: $('.aboutMeText').text().trim(), + link, + image: $('#coverPictureDefault').attr('src'), + logo: $('#getAvatar').attr('src'), + icon: $('#getAvatar').attr('src'), + language: $('html').attr('lang'), + allowEmpty: true, + item: items, + }; +}; diff --git a/lib/v2/pornhub/utils.js b/lib/v2/pornhub/utils.js new file mode 100644 index 00000000000000..10d4af2b4663c3 --- /dev/null +++ b/lib/v2/pornhub/utils.js @@ -0,0 +1,30 @@ +const { art } = require('@/utils/render'); +const { join } = require('path'); +const { parseRelativeDate } = require('@/utils/parse-date'); + +const defaultDomain = 'https://www.pornhub.com'; + +const headers = { + accessAgeDisclaimerPH: 1, + hasVisited: 1, +}; + +const renderDescription = (data) => art(join(__dirname, 'templates/description.art'), data); + +const parseItems = (e) => ({ + title: e.find('span.title a').text().trim(), + link: defaultDomain + e.find('span.title a').attr('href'), + description: renderDescription({ + poster: e.find('img').data('mediumthumb'), + previewVideo: e.find('img').data('mediabook'), + }), + author: e.find('.usernameWrap a').text(), + pubDate: parseRelativeDate(e.find('.added').text()), +}); + +module.exports = { + defaultDomain, + headers, + renderDescription, + parseItems, +}; diff --git a/website/docs/routes/multimedia.md b/website/docs/routes/multimedia.md index 50fd691be6f974..543cb03b76df36 100644 --- a/website/docs/routes/multimedia.md +++ b/website/docs/routes/multimedia.md @@ -1119,41 +1119,45 @@ See [Directory](https://www.javlibrary.com/en/star_list.php) to view all stars. ### Category {#pornhub-category} - + ### Keyword Search {#pornhub-keyword-search} - + ### Users {#pornhub-users} - + ### Verified amateur / Model {#pornhub-verified-amateur-%2F-model} - + ### Verified model / Pornstar {#pornhub-verified-model-%2F-pornstar} - + **`sort`** -| mr | mv | tr | lg | cm | -| ----------- | ----------- | --------- | ------- | ------ | -| Most Recent | Most Viewed | Top Rated | Longest | Newest | +| Most Recent | Most Viewed | Top Rated | Longest | Best | +| ----------- | ----------- | --------- | ------- | ---- | +| mr | mv | tr | lg | | + + ### Video List {#pornhub-video-list} - + **`language`** Refer to [Pornhub F.A.Qs](https://help.pornhub.com/hc/en-us/articles/360044327034-How-do-I-change-the-language-), English by default. For example: -- `cn` (Chinese), for Pornhub in China ; +- `cn` (Chinese), for Pornhub in China ; -- `jp` (Japanese), for Pornhub in Japan etc. +- `jp` (Japanese), for Pornhub in Japan etc. + + ## PRESTIGE(プレステージ 蚊香社) {#prestige(%E3%83%97%E3%83%AC%E3%82%B9%E3%83%86%E3%83%BC%E3%82%B8-wen-xiang-she-)}