feat(routes): Add img=1 option to pornhub handlers to pass through images (#21324)

* Add img=1 option to pornhub handlers to pass through images

* Move img=1 to a path parameter, and add clarifications on :lang, etc because each must be set to set the following

---------

Co-authored-by: acidicxjrl <acidicxjrl>
This commit is contained in:
acidicxjrl 2026-03-08 12:28:42 -07:00 committed by GitHub
parent 1ad606f40f
commit 7c5c66a75b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 60 additions and 39 deletions

View File

@ -8,10 +8,10 @@ import { isValidHost } from '@/utils/valid-host';
import { headers, parseItems } from './utils';
export const route: Route = {
path: '/category_url/:url?/:language?',
path: '/category_url/:url?/:language?/:img?',
categories: ['multimedia'],
example: '/pornhub/category_url/video%3Fc%3D15%26o%3Dmv%26t%3Dw%26cc%3Djp',
parameters: { language: 'language, see below', url: 'relative path after `pornhub.com/`, need to be URL encoded' },
parameters: { language: 'language, see below. defaults to `www` (English)', url: 'relative path after `pornhub.com/`, need to be URL encoded', img: 'show images, set to `img=1` to enable' },
features: {
requireConfig: false,
requirePuppeteer: false,
@ -34,7 +34,7 @@ export const route: Route = {
};
async function handler(ctx) {
const { language = 'www', url = 'video' } = ctx.req.param();
const { language = 'www', url = 'video', img } = ctx.req.param();
const link = `https://${language}.pornhub.com/${url}`;
if (!isValidHost(language)) {
throw new InvalidParameterError('Invalid language');
@ -42,14 +42,15 @@ async function handler(ctx) {
const { data: response } = await got(link, { headers });
const $ = load(response);
const showImages = img === 'img=1';
const items = $('#videoCategory .videoBox')
.toArray()
.map((e) => parseItems($(e)));
.map((e) => parseItems($(e), showImages));
return {
title: $('title').first().text(),
link,
language: $('html').attr('lang'),
language: $('html').attr('lang') as any,
item: items,
};
}

View File

@ -8,11 +8,11 @@ import { parseDate } from '@/utils/parse-date';
import { defaultDomain, renderDescription } from './utils';
export const route: Route = {
path: '/category/:caty',
path: '/category/:caty/:img?',
categories: ['multimedia'],
view: ViewType.Videos,
example: '/pornhub/category/popular-with-women',
parameters: { caty: 'category, see [categories](https://www.pornhub.com/webmasters/categories)' },
parameters: { caty: 'category, see [categories](https://www.pornhub.com/webmasters/categories)', img: 'show images, set to `img=1` to enable' },
features: {
requireConfig: false,
requirePuppeteer: false,
@ -28,7 +28,7 @@ export const route: Route = {
};
async function handler(ctx) {
const category = ctx.req.param('caty');
const { caty: category, img } = ctx.req.param();
const categories = await cache.tryGet('pornhub:categories', async () => {
const { data } = await got(`${defaultDomain}/webmasters/categories`);
@ -52,10 +52,12 @@ async function handler(ctx) {
throw new Error(response.message);
}
const showImages = img === 'img=1';
const list = response.videos.map((item) => ({
title: item.title,
link: item.url,
description: renderDescription({ thumbs: item.thumbs }),
description: renderDescription({ thumbs: item.thumbs }, showImages),
pubDate: parseDate(item.publish_date),
category: [...new Set([...item.tags.map((t) => t.tag_name), ...item.categories.map((c) => c.category)])],
}));

View File

@ -9,11 +9,16 @@ import { isValidHost } from '@/utils/valid-host';
import { getRadarDomin, headers, parseItems } from './utils';
export const route: Route = {
path: '/model/:username/:language?/:sort?',
path: '/model/:username/:language?/:sort?/:img?',
categories: ['multimedia'],
view: ViewType.Videos,
example: '/pornhub/model/stacy-starando',
parameters: { language: 'language, see below', username: 'username, part of the url e.g. `pornhub.com/model/stacy-starando`', sort: 'sorting method, see below' },
parameters: {
language: 'language, see below. defaults to www',
username: 'username, part of the url e.g. `pornhub.com/model/stacy-starando`',
sort: 'sorting method, see below. Defaults to mr (most recent)',
img: 'show images, set to `img=1` to enable',
},
features: {
requireConfig: false,
requirePuppeteer: false,
@ -30,7 +35,7 @@ export const route: Route = {
};
async function handler(ctx): Promise<Data> {
const { language = 'www', username, sort = '' } = ctx.req.param();
const { language = 'www', username, sort = '', img } = ctx.req.param();
const link = `https://${language}.pornhub.com/model/${username}/videos${sort ? `?o=${sort}` : ''}`;
if (!isValidHost(language)) {
throw new InvalidParameterError('Invalid language');
@ -38,16 +43,17 @@ async function handler(ctx): Promise<Data> {
const { data: response } = await got(link, { headers });
const $ = load(response);
const showImages = img === 'img=1';
const items = $('#mostRecentVideosSection .videoBox')
.toArray()
.map((e) => parseItems($(e)));
.map((e) => parseItems($(e), showImages));
return {
title: $('h1').first().text(),
description: $('section.aboutMeSection').text().trim(),
link,
image: $('#getAvatar').attr('src'),
language: $('html').attr('lang'),
language: $('html').attr('lang') as any,
item: items,
};
}

View File

@ -9,7 +9,7 @@ import { isValidHost } from '@/utils/valid-host';
import { getRadarDomin, headers, parseItems } from './utils';
export const route: Route = {
path: '/pornstar/:username/:language?/:sort?',
path: '/pornstar/:username/:language?/:sort?/:img?',
categories: ['multimedia'],
view: ViewType.Videos,
example: '/pornhub/pornstar/june-liu/www/mr',
@ -18,7 +18,7 @@ export const route: Route = {
description: 'username, part of the url e.g. `pornhub.com/pornstar/june-liu`',
},
language: {
description: 'language',
description: 'language, defaults to `www` (English)',
options: [
{ value: 'www', label: 'English' },
{ value: 'de', label: 'Deutsch' },
@ -36,7 +36,7 @@ export const route: Route = {
default: 'www',
},
sort: {
description: 'sorting method, leave empty for `Best`',
description: 'sorting method, defaults to `mr` (Most Recent)',
options: [
{
label: 'Most Recent',
@ -56,6 +56,7 @@ export const route: Route = {
},
],
},
img: 'show images, set to `img=1` to enable',
},
features: {
requireConfig: false,
@ -73,7 +74,7 @@ export const route: Route = {
};
async function handler(ctx): Promise<Data> {
const { language = 'www', username, sort = 'mr' } = ctx.req.param();
const { language = 'www', username, sort = 'mr', img } = ctx.req.param();
let link = `https://${language}.pornhub.com/pornstar/${username}?o=${sort}`;
if (!isValidHost(language)) {
throw new InvalidParameterError('Invalid language');
@ -83,17 +84,19 @@ async function handler(ctx): Promise<Data> {
let $ = load(response);
let items;
const showImages = img === 'img=1';
if ($('.withBio').length === 0) {
link = `https://${language}.pornhub.com/pornstar/${username}/videos?o=${sort}`;
const { data: response } = await got(link, { headers });
$ = load(response);
items = $('#mostRecentVideosSection .videoBox')
.toArray()
.map((e) => parseItems($(e)));
.map((e) => parseItems($(e), showImages));
} else {
items = $('#pornstarsVideoSection .videoBox')
.toArray()
.map((e) => parseItems($(e)));
.map((e) => parseItems($(e), showImages));
}
return {
@ -101,7 +104,7 @@ async function handler(ctx): Promise<Data> {
description: $('section.aboutMeSection').text().trim(),
link,
image: $('#getAvatar').attr('src'),
language: $('html').attr('lang'),
language: $('html').attr('lang') as any,
item: items,
};
}

View File

@ -6,11 +6,11 @@ import { parseDate } from '@/utils/parse-date';
import { defaultDomain, renderDescription } from './utils';
export const route: Route = {
path: '/search/:keyword',
path: '/search/:keyword/:img?',
categories: ['multimedia'],
view: ViewType.Videos,
example: '/pornhub/search/stepsister',
parameters: { keyword: 'keyword' },
parameters: { keyword: 'keyword', img: 'show images, set to `img=1` to enable' },
features: {
requireConfig: false,
requirePuppeteer: false,
@ -26,14 +26,16 @@ export const route: Route = {
};
async function handler(ctx) {
const keyword = ctx.req.param('keyword');
const { keyword, img } = ctx.req.param();
const currentUrl = `${defaultDomain}/webmasters/search?search=${keyword}`;
const response = await got(currentUrl);
const showImages = img === 'img=1';
const list = response.data.videos.map((item) => ({
title: item.title,
link: item.url,
description: renderDescription({ thumbs: item.thumbs }),
description: renderDescription({ thumbs: item.thumbs }, showImages),
pubDate: parseDate(item.publish_date),
category: [...new Set([...item.tags.map((t) => t.tag_name), ...item.categories.map((c) => c.category)])],
}));

View File

@ -8,10 +8,10 @@ import { isValidHost } from '@/utils/valid-host';
import { getRadarDomin, headers, parseItems } from './utils';
export const route: Route = {
path: '/users/:username/:language?',
path: '/users/:username/:language?/:img?',
categories: ['multimedia'],
example: '/pornhub/users/pornhubmodels',
parameters: { language: 'language, see below', username: 'username, part of the url e.g. `pornhub.com/users/pornhubmodels`' },
parameters: { language: 'language, see below. defaults to `www` (English)', username: 'username, part of the url e.g. `pornhub.com/users/pornhubmodels`', img: 'show images, set to `img=1` to enable' },
features: {
requireConfig: false,
requirePuppeteer: false,
@ -28,7 +28,7 @@ export const route: Route = {
};
async function handler(ctx): Promise<Data> {
const { language = 'www', username } = ctx.req.param();
const { language = 'www', username, img } = ctx.req.param();
const link = `https://${language}.pornhub.com/users/${username}/videos`;
if (!isValidHost(language)) {
throw new InvalidParameterError('Invalid language');
@ -36,16 +36,17 @@ async function handler(ctx): Promise<Data> {
const { data: response } = await got(link, { headers });
const $ = load(response);
const showImages = img === 'img=1';
const items = $('.videoUList .videoBox')
.toArray()
.map((e) => parseItems($(e)));
.map((e) => parseItems($(e), showImages));
return {
title: $('.profileUserName a').text(),
description: $('.aboutMeText').text().trim(),
link,
image: $('#getAvatar').attr('src'),
language: $('html').attr('lang'),
language: $('html').attr('lang') as any,
allowEmpty: true,
item: items,
};

View File

@ -10,7 +10,7 @@ const headers = {
hasVisited: 1,
};
const renderDescription = (data): string =>
const renderDescription = (data, showImages = false): string =>
renderToString(
<>
{data.previewVideo ? (
@ -18,9 +18,12 @@ const renderDescription = (data): string =>
<source src={data.previewVideo} type="video/webm" />
</video>
) : null}
{data.thumbs?.map((thumb, index) => (
<img key={`${thumb.src}-${index}`} src={thumb.src} />
))}
{showImages &&
(data.thumbs ? (
data.thumbs.map((thumb, index) => <img key={`${thumb.src}-${index}`} src={thumb.src} />)
) : (
<img src={data.poster} />
))}
</>
);
const extractDateFromImageUrl = (imageUrl) => {
@ -28,13 +31,16 @@ const extractDateFromImageUrl = (imageUrl) => {
return matchResult ? matchResult.slice(1, 3).join('') : null;
};
const parseItems = (e) => ({
const parseItems = (e, showImages = false) => ({
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'),
}),
description: renderDescription(
{
poster: e.find('img').data('mediumthumb'),
previewVideo: e.find('img').data('mediabook'),
},
showImages
),
author: e.find('.usernameWrap a').text(),
pubDate: dayjs(extractDateFromImageUrl(e.find('img').data('mediumthumb'))).toDate() || parseRelativeDate(e.find('.added').text()),
});