v0.4.1/add-xhamster-support #114

Merged
lamacchinadesiderante merged 3 commits from v0.4.1/add-xhamster-support into develop 2024-05-28 19:44:15 +00:00
10 changed files with 238 additions and 5 deletions

View File

@ -1,6 +1,6 @@
'use client'
import { Cookies, Platforms, XVideosOrientations, PornHubOrientations, YouPornOrientations, RedTubeOrientations } from '@/meta/settings';
import { Cookies, Platforms, XVideosOrientations, PornHubOrientations, YouPornOrientations, RedTubeOrientations, XHamsterOrientations } from '@/meta/settings';
import css from './Orientation.module.scss'
@ -29,6 +29,10 @@ const getOrientations = (platform: Platforms):Object => {
return RedTubeOrientations
}
if([Platforms.xhamster].includes(platform)) {
return XHamsterOrientations
}
return PornHubOrientations
}

View File

@ -1,6 +1,6 @@
'use client'
import { Cookies, Platforms, PornHubOrientations, RedTubeOrientations, XVideosOrientations, YouPornOrientations } from '@/meta/settings';
import { Cookies, Platforms, PornHubOrientations, RedTubeOrientations, XHamsterOrientations, XVideosOrientations, YouPornOrientations } from '@/meta/settings';
import css from './Platform.module.scss'
@ -31,6 +31,10 @@ const mapOrientationToPlatform = (platform: string, orientation: string): string
return Object.keys(RedTubeOrientations).includes(orientation) ? orientation : String(RedTubeOrientations.etero)
}
if ([String(Platforms.xhamster)].includes(platform)) {
return Object.keys(XHamsterOrientations).includes(orientation) ? orientation : String(XHamsterOrientations.etero)
}
if ([String(Platforms.youporn)].includes(platform)) {
return String(YouPornOrientations.generic)
}

View File

@ -15,4 +15,7 @@ export const DEFAULT_YOUPORN_VIDEO_EXPIRY = { EX: EX_HOURLY };
export const DEFAULT_REDTUBE_GALLERY_EXPIRY = { EX: EX_HOURLY };
export const DEFAULT_REDTUBE_VIDEO_EXPIRY = { EX: EX_HOURLY };
export const DEFAULT_XHAMSTER_GALLERY_EXPIRY = { EX: EX_HOURLY };
export const DEFAULT_XHAMSTER_VIDEO_EXPIRY = { EX: EX_HOURLY };
export const DEFAULT_RELATED_VIDEO_KEY_PATH = '/related/'

View File

@ -37,4 +37,16 @@ export const REDTUBE_BASE_URL_GAY: string = 'https://www.redtube.com/gay'
export const REDTUBE_BASE_URL_TRANS: string = 'https://www.redtube.com/redtube/transgender'
export const REDTUBE_BASE_SEARCH: string = 'https://www.redtube.com/?search='
export const REDTUBE_BASE_GAY_SEARCH: string = 'https://www.redtube.com/gay?search='
export const REDTUBE_BASE_GAY_SEARCH: string = 'https://www.redtube.com/gay?search='
// XHAMSTER
export const XHAMSTER_BASE_URL = 'https://xhamster.com'
export const XHAMSTER_BASE_URL_VIDEOS = 'https://xhamster.com/videos'
export const XHAMSTER_BASE_URL_ETERO = 'https://xhamster.com/newest'
export const XHAMSTER_BASE_URL_GAY = 'https://xhamster.com/gay/newest'
export const XHAMSTER_BASE_URL_TRANS = 'https://xhamster.com/shemale/newest'
export const XHAMSTER_BASE_SEARCH = 'https://xhamster.com/search/'
export const XHAMSTER_BASE_SEARCH_GAY = 'https://xhamster.com/gay/search/'
export const XHAMSTER_BASE_SEARCH_TRANS = 'https://xhamster.com/shemale/search/'

View File

@ -9,7 +9,8 @@ export enum Platforms {
xnxx= 'xnxx',
pornhub= 'pornhub',
youporn= 'youporn',
redtube= 'redtube'
redtube= 'redtube',
xhamster= 'xhamster'
}
export enum XVideosCatQueryMap {
@ -39,6 +40,12 @@ export enum RedTubeOrientations {
trans= 'trans'
}
export enum XHamsterOrientations {
etero= 'etero',
gay= 'gay',
trans= 'trans'
}
export enum Themes {
light= 'light',
dark= 'dark',

View File

@ -7,13 +7,15 @@ import { XNXXAgent } from "./scrape/xnxx/agent";
import { PornHubAgent } from "./scrape/pornhub/agent";
import { YouPornAgent } from "./scrape/youporn/agent";
import { RedTubeAgent } from "./scrape/redtube/agent";
import { XHamsterAgent } from "./scrape/xhamster/agent";
const AgentMapper = {
[Platforms.xvideos]: XVideosAgent,
[Platforms.xnxx]: XNXXAgent,
[Platforms.pornhub]: PornHubAgent,
[Platforms.youporn]: YouPornAgent,
[Platforms.redtube]: RedTubeAgent
[Platforms.redtube]: RedTubeAgent,
[Platforms.xhamster]: XHamsterAgent
}
export class VideoAgent {

View File

@ -0,0 +1,15 @@
import { FetchParams, GalleryData, VideoAgent, VideoData } from "@/meta/data";
import { fetchXHamsterGalleryData } from "./gallery";
import { fetchXHamsterVideoData } from "./video";
export class XHamsterAgent implements VideoAgent {
public getGallery = async (params?: FetchParams): Promise<GalleryData[]> => {
return await fetchXHamsterGalleryData(params)
}
public getVideo = async (id: string, params?: FetchParams): Promise<[VideoData, GalleryData[]]> => {
return await fetchXHamsterVideoData(id, params)
}
}

View File

@ -0,0 +1,62 @@
import { XHAMSTER_BASE_URL, XHAMSTER_BASE_URL_VIDEOS } from "@/constants/urls";
import { FetchParams, GalleryData } from "@/meta/data";
import { getHeaders } from "../common/headers";
import { getXHamsterQueryUrl } from "./url";
import { getDataFromRedis, storeDataIntoRedis } from "@/redis/client";
import * as cheerio from "cheerio";
import axios, { AxiosError } from "axios";
import { DEFAULT_XHAMSTER_GALLERY_EXPIRY } from "@/constants/redis";
import { Platforms } from "@/meta/settings";
export const fetchXHamsterGalleryData = async (params?: FetchParams): Promise<GalleryData[]> => {
let data: GalleryData[] = [];
const reqHeaders = getHeaders(XHAMSTER_BASE_URL)
const queryUrl = await getXHamsterQueryUrl(params?.query)
const cachedData = await getDataFromRedis(queryUrl)
if (cachedData) {
return cachedData as GalleryData[]
}
await axios.get(queryUrl, reqHeaders)
.then(async response => {
const html = response.data;
const $ = cheerio.load(html);
const wrapperId = '.thumb-list .thumb-list__item'
const thumbs = $(wrapperId);
thumbs.map((key, thumb) => {
const videoUrl = $(thumb).find("a.video-thumb__image-container").attr("href")?.replace(XHAMSTER_BASE_URL_VIDEOS, '')
const imgUrl = $(thumb).find("a.video-thumb__image-container img").attr("src")
const text = $(thumb).find("a.video-thumb-info__name").attr("title")
videoUrl && imgUrl && text && data.push({
videoUrl,
imgUrl,
text,
platform: Platforms.xhamster
})
})
if (data.length > 0) {
await storeDataIntoRedis(queryUrl, data, DEFAULT_XHAMSTER_GALLERY_EXPIRY);
}
}).catch((error: AxiosError) => {
// handle errors
});
return data
}

View File

@ -0,0 +1,30 @@
import { XHAMSTER_BASE_SEARCH, XHAMSTER_BASE_SEARCH_GAY, XHAMSTER_BASE_SEARCH_TRANS, XHAMSTER_BASE_URL_ETERO, XHAMSTER_BASE_URL_GAY, XHAMSTER_BASE_URL_TRANS } from "@/constants/urls"
import { Cookies, XHamsterOrientations } from "@/meta/settings"
import { getCookie } from "@/utils/cookies/read"
export const getXHamsterQueryUrl = async (query?: string): Promise<string> => {
const orientation = await getCookie(Cookies.orientation)
if (query) {
if (orientation && orientation.value == XHamsterOrientations.gay) {
return XHAMSTER_BASE_SEARCH_GAY + query + '?revert=orientation'
}
if (orientation && orientation.value == XHamsterOrientations.trans) {
return XHAMSTER_BASE_SEARCH_TRANS + query + '?revert=orientation'
}
return XHAMSTER_BASE_SEARCH + query
} else {
if (orientation && orientation.value == XHamsterOrientations.gay) {
return XHAMSTER_BASE_URL_GAY
}
if (orientation && orientation.value == XHamsterOrientations.trans) {
return XHAMSTER_BASE_URL_TRANS
}
}
return XHAMSTER_BASE_URL_ETERO
}

View File

@ -0,0 +1,94 @@
import { XHAMSTER_BASE_URL, XHAMSTER_BASE_URL_VIDEOS } from "@/constants/urls";
import { FetchParams, GalleryData, VideoData, VideoSourceItem } from "@/meta/data";
import { getHeaders } from "../common/headers";
import { getDataFromRedis, storeDataIntoRedis } from "@/redis/client";
import { DEFAULT_RELATED_VIDEO_KEY_PATH, DEFAULT_XHAMSTER_GALLERY_EXPIRY, DEFAULT_XHAMSTER_VIDEO_EXPIRY } from "@/constants/redis";
import * as cheerio from "cheerio";
import axios, { AxiosError } from "axios";
import { findGetMediaUrlInTagblock } from "../common/mindgeek";
import { Platforms } from "@/meta/settings";
import { encodeUrl } from "@/utils/string";
import { DEFAULT_VIDEO_STREAM_ROUTE_PREFIX } from "@/constants/stream";
export const fetchXHamsterVideoData = async (videoId: string, params?: FetchParams): Promise<[VideoData, GalleryData[]]> => {
let data: VideoData = {
srcSet: []
}
let relatedData: GalleryData[] = [];
let reqHeaders = getHeaders(XHAMSTER_BASE_URL);
const queryUrl = `${XHAMSTER_BASE_URL_VIDEOS}/${videoId.replace(/\//g, '')}`
const cachedVideoData = await getDataFromRedis(queryUrl)
const cachedRelatedData = await getDataFromRedis(queryUrl + DEFAULT_RELATED_VIDEO_KEY_PATH)
if (cachedVideoData) {
return [cachedVideoData as VideoData, cachedRelatedData as GalleryData[] ?? []]
}
await axios.get(queryUrl, reqHeaders)
.then(async response => {
const html = response.data;
const $ = cheerio.load(html);
const scriptTags = $("script");
scriptTags.map((idx, elem) => {
const hlsUrl = findGetMediaUrlInTagblock($(elem).toString().replace(/\\/g, ''), 'media=hls4') ?? null
if (hlsUrl) {
['144', '240', '360', '480', '720', '1080'].map((res: string) => {
let resUrl = findGetMediaUrlInTagblock($(elem).toString().replace(/\\/g, ''), `${res}p.h264.mp4`) ?? null
if (resUrl) {
data.srcSet?.push({
src: `${DEFAULT_VIDEO_STREAM_ROUTE_PREFIX}/${Platforms.xhamster}/${encodeUrl(resUrl)}`,
type: 'video/mp4',
size: res
})
}
});
}
})
const wrapperId = '.thumb-list .thumb-list__item'
const thumbs = $(wrapperId);
thumbs.map((key, thumb) => {
const videoUrl = $(thumb).find("a.video-thumb__image-container").attr("href")?.replace(XHAMSTER_BASE_URL_VIDEOS, '')
const imgUrl = $(thumb).find("a.video-thumb__image-container img").attr("src")
const text = $(thumb).find("a.video-thumb-info__name").attr("title")
videoUrl && imgUrl && text && relatedData.push({
videoUrl,
imgUrl,
text,
platform: Platforms.xhamster
})
})
}).catch((error: AxiosError) => {
// error handling goes here
});
if (data.srcSet && data.srcSet?.length > 0) {
await storeDataIntoRedis(queryUrl, data, DEFAULT_XHAMSTER_VIDEO_EXPIRY);
}
if (relatedData.length > 0) {
await storeDataIntoRedis(queryUrl + DEFAULT_RELATED_VIDEO_KEY_PATH, relatedData, DEFAULT_XHAMSTER_GALLERY_EXPIRY);
}
return [ data, relatedData ]
}