Release v0.4.0: Add YouPorn support #102
|
@ -7,6 +7,11 @@ Proxy Raye is an alternative front-end for adult websites. Watch videos on a cle
|
||||||
- XVideos
|
- XVideos
|
||||||
- XNXX
|
- XNXX
|
||||||
- PornHub (experimental)
|
- PornHub (experimental)
|
||||||
|
- YouPorn
|
||||||
|
|
||||||
|
### How to switch between platforms
|
||||||
|
|
||||||
|
Click on settings icon (gear icon on top-right corner). A pop-up menu will let you choose platform and orientation.
|
||||||
|
|
||||||
## Working demos
|
## Working demos
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { Cookies, Platforms, XVideosOrientations, PornHubOrientations } from '@/meta/settings';
|
import { Cookies, Platforms, XVideosOrientations, PornHubOrientations, YouPornOrientations } from '@/meta/settings';
|
||||||
|
|
||||||
import css from './Orientation.module.scss'
|
import css from './Orientation.module.scss'
|
||||||
|
|
||||||
|
@ -19,7 +19,11 @@ interface Props {
|
||||||
const getOrientations = (platform: Platforms):Object => {
|
const getOrientations = (platform: Platforms):Object => {
|
||||||
if ([Platforms.xnxx, Platforms.xvideos].includes(platform)) {
|
if ([Platforms.xnxx, Platforms.xvideos].includes(platform)) {
|
||||||
return XVideosOrientations
|
return XVideosOrientations
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if([Platforms.youporn].includes(platform)) {
|
||||||
|
return YouPornOrientations
|
||||||
|
}
|
||||||
|
|
||||||
return PornHubOrientations
|
return PornHubOrientations
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { Cookies, Platforms, PornHubOrientations, XVideosOrientations } from '@/meta/settings';
|
import { Cookies, Platforms, PornHubOrientations, XVideosOrientations, YouPornOrientations } from '@/meta/settings';
|
||||||
|
|
||||||
import css from './Platform.module.scss'
|
import css from './Platform.module.scss'
|
||||||
|
|
||||||
|
@ -26,6 +26,10 @@ const mapOrientationToPlatform = (platform: string, orientation: string): string
|
||||||
return Object.keys(PornHubOrientations).includes(orientation) ? orientation : String(PornHubOrientations.generic)
|
return Object.keys(PornHubOrientations).includes(orientation) ? orientation : String(PornHubOrientations.generic)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ([String(Platforms.youporn)].includes(platform)) {
|
||||||
|
return String(YouPornOrientations.generic)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Platform: React.FC<Props> = (props) => {
|
const Platform: React.FC<Props> = (props) => {
|
||||||
|
|
|
@ -4,7 +4,12 @@ const EX_DAILY = 60 * 60 * 24
|
||||||
|
|
||||||
export const DEFAULT_PORNHUB_GALLERY_EXPIRY = { EX: EX_HOURLY };
|
export const DEFAULT_PORNHUB_GALLERY_EXPIRY = { EX: EX_HOURLY };
|
||||||
export const DEFAULT_PORNHUB_VIDEO_EXPIRY = { EX: EX_MIN };
|
export const DEFAULT_PORNHUB_VIDEO_EXPIRY = { EX: EX_MIN };
|
||||||
|
|
||||||
export const DEFAULT_XVIDEOS_CONTENT_EXPIRY = { EX: EX_HOURLY };
|
export const DEFAULT_XVIDEOS_CONTENT_EXPIRY = { EX: EX_HOURLY };
|
||||||
|
|
||||||
export const DEFAULT_XNXX_CONTENT_EXPIRY = { EX: EX_HOURLY };
|
export const DEFAULT_XNXX_CONTENT_EXPIRY = { EX: EX_HOURLY };
|
||||||
|
|
||||||
|
export const DEFAULT_YOUPORN_GALLERY_EXPIRY = { EX: EX_HOURLY };
|
||||||
|
export const DEFAULT_YOUPORN_VIDEO_EXPIRY = { EX: EX_HOURLY };
|
||||||
|
|
||||||
export const DEFAULT_RELATED_VIDEO_KEY_PATH = '/related/'
|
export const DEFAULT_RELATED_VIDEO_KEY_PATH = '/related/'
|
|
@ -22,3 +22,10 @@ export const PORNHUB_BASE_URL_VIDEO: string = 'https://www.pornhub.com/view_vide
|
||||||
|
|
||||||
export const PORNHUB_BASE_URL_GAY: string = 'https://www.pornhub.com/gayporn'
|
export const PORNHUB_BASE_URL_GAY: string = 'https://www.pornhub.com/gayporn'
|
||||||
export const PORNHUB_BASE_URL_GAY_SEARCH: string = 'https://www.pornhub.com/gay'
|
export const PORNHUB_BASE_URL_GAY_SEARCH: string = 'https://www.pornhub.com/gay'
|
||||||
|
|
||||||
|
// YOUPORN
|
||||||
|
|
||||||
|
export const YOUPORN_BASE_URL: string = 'https://www.youporn.com'
|
||||||
|
export const YOUPORN_BASE_URL_VIDEO: string = 'https://www.youporn.com/watch'
|
||||||
|
|
||||||
|
export const YOUPORN_BASE_SEARCH: string = 'https://www.youporn.com/search/?search-btn=&query='
|
|
@ -23,6 +23,11 @@ export interface VideoData {
|
||||||
srcSet?: VideoSourceItem[]
|
srcSet?: VideoSourceItem[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MindGeekVideoSrcElem {
|
||||||
|
videoUrl: string
|
||||||
|
quality: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface VideoAgent {
|
export interface VideoAgent {
|
||||||
getGallery(params?: FetchParams): Promise<GalleryData[]>
|
getGallery(params?: FetchParams): Promise<GalleryData[]>
|
||||||
getVideo(id: string, params?: FetchParams): Promise<[VideoData, GalleryData[]]>
|
getVideo(id: string, params?: FetchParams): Promise<[VideoData, GalleryData[]]>
|
||||||
|
|
|
@ -7,7 +7,8 @@ export enum Cookies {
|
||||||
export enum Platforms {
|
export enum Platforms {
|
||||||
xvideos= 'xvideos',
|
xvideos= 'xvideos',
|
||||||
xnxx= 'xnxx',
|
xnxx= 'xnxx',
|
||||||
pornhub= 'pornhub'
|
pornhub= 'pornhub',
|
||||||
|
youporn= 'youporn'
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum XVideosCatQueryMap {
|
export enum XVideosCatQueryMap {
|
||||||
|
@ -27,6 +28,10 @@ export enum PornHubOrientations {
|
||||||
gay= 'gay'
|
gay= 'gay'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum YouPornOrientations {
|
||||||
|
generic= 'generic'
|
||||||
|
}
|
||||||
|
|
||||||
export enum Themes {
|
export enum Themes {
|
||||||
light= 'light',
|
light= 'light',
|
||||||
dark= 'dark',
|
dark= 'dark',
|
||||||
|
|
|
@ -5,12 +5,13 @@ import { Platforms } from "@/meta/settings";
|
||||||
import { XVideosAgent } from "./scrape/xvideos/agent";
|
import { XVideosAgent } from "./scrape/xvideos/agent";
|
||||||
import { XNXXAgent } from "./scrape/xnxx/agent";
|
import { XNXXAgent } from "./scrape/xnxx/agent";
|
||||||
import { PornHubAgent } from "./scrape/pornhub/agent";
|
import { PornHubAgent } from "./scrape/pornhub/agent";
|
||||||
|
import { YouPornAgent } from "./scrape/youporn/agent";
|
||||||
|
|
||||||
const AgentMapper = {
|
const AgentMapper = {
|
||||||
[Platforms.xvideos]: XVideosAgent,
|
[Platforms.xvideos]: XVideosAgent,
|
||||||
[Platforms.xnxx]: XNXXAgent,
|
[Platforms.xnxx]: XNXXAgent,
|
||||||
[Platforms.pornhub]: PornHubAgent
|
[Platforms.pornhub]: PornHubAgent,
|
||||||
|
[Platforms.youporn]: YouPornAgent,
|
||||||
}
|
}
|
||||||
|
|
||||||
export class VideoAgent {
|
export class VideoAgent {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
export const findGetMediaUrlInTagblock = (
|
export const findGetMediaUrlInTagblock = (
|
||||||
tagBlock: string): string | null => {
|
tagBlock: string, key?: string): string | null => {
|
||||||
|
|
||||||
const getMediaIndex = tagBlock.indexOf('get_media');
|
const getMediaIndex = tagBlock.indexOf(key ?? 'get_media');
|
||||||
|
|
||||||
if (getMediaIndex === -1) {
|
if (getMediaIndex === -1) {
|
||||||
return null
|
return null
|
||||||
|
|
|
@ -1,17 +1,12 @@
|
||||||
import { PORNHUB_BASE_URL, PORNHUB_BASE_URL_GAY, PORNHUB_BASE_URL_GAY_SEARCH } from "@/constants/urls"
|
import { PORNHUB_BASE_URL, PORNHUB_BASE_URL_GAY, PORNHUB_BASE_URL_GAY_SEARCH } from "@/constants/urls"
|
||||||
import axios, { AxiosHeaders } from "axios"
|
import axios, { AxiosHeaders } from "axios"
|
||||||
import { getHeadersWithCookie } from "../common/headers"
|
import { getHeadersWithCookie } from "../common/headers"
|
||||||
import { GalleryData, VideoSourceItem } from "@/meta/data"
|
import { GalleryData, MindGeekVideoSrcElem, VideoSourceItem } from "@/meta/data"
|
||||||
import { Cookies, Platforms, PornHubOrientations } from "@/meta/settings"
|
import { Cookies, Platforms, PornHubOrientations } from "@/meta/settings"
|
||||||
import { getCookie } from "@/utils/cookies/read"
|
import { getCookie } from "@/utils/cookies/read"
|
||||||
import { encodeUrl } from "@/utils/string"
|
import { encodeUrl } from "@/utils/string"
|
||||||
import { DEFAULT_VIDEO_STREAM_ROUTE_PREFIX } from "@/constants/stream"
|
import { DEFAULT_VIDEO_STREAM_ROUTE_PREFIX } from "@/constants/stream"
|
||||||
|
|
||||||
interface PornHubVideoSrcElem {
|
|
||||||
videoUrl: string
|
|
||||||
quality: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getPornHubQueryUrl = async (query?: string): Promise<string> => {
|
export const getPornHubQueryUrl = async (query?: string): Promise<string> => {
|
||||||
const orientation = await getCookie(Cookies.orientation)
|
const orientation = await getCookie(Cookies.orientation)
|
||||||
|
|
||||||
|
@ -49,7 +44,7 @@ export const getPornHubMediaUrlList = async (url: string, sessionCookie: string)
|
||||||
|
|
||||||
if (response.data) {
|
if (response.data) {
|
||||||
|
|
||||||
videos = await response.data.map((elem: PornHubVideoSrcElem) => ({
|
videos = await response.data.map((elem: MindGeekVideoSrcElem) => ({
|
||||||
src: `${DEFAULT_VIDEO_STREAM_ROUTE_PREFIX}/${Platforms.pornhub}/${encodeUrl(elem?.videoUrl)}`,
|
src: `${DEFAULT_VIDEO_STREAM_ROUTE_PREFIX}/${Platforms.pornhub}/${encodeUrl(elem?.videoUrl)}`,
|
||||||
type: 'video/mp4',
|
type: 'video/mp4',
|
||||||
size: elem?.quality
|
size: elem?.quality
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { FetchParams, GalleryData, VideoAgent, VideoData } from "@/meta/data";
|
||||||
|
import { fetchYouPornGalleryData } from "./gallery";
|
||||||
|
import { fetchYouPornVideoData } from "./video";
|
||||||
|
|
||||||
|
export class YouPornAgent implements VideoAgent {
|
||||||
|
|
||||||
|
public getGallery = async (params?: FetchParams): Promise<GalleryData[]> => {
|
||||||
|
return await fetchYouPornGalleryData(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
public getVideo = async (id: string, params?: FetchParams): Promise<[VideoData, GalleryData[]]> => {
|
||||||
|
return await fetchYouPornVideoData(id, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
import { YOUPORN_BASE_URL } from "@/constants/urls";
|
||||||
|
import { FetchParams, GalleryData } from "@/meta/data";
|
||||||
|
import { getHeaders } from "../common/headers";
|
||||||
|
import { getYouPornQueryUrl } from "./url";
|
||||||
|
import { getDataFromRedis, storeDataIntoRedis } from "@/redis/client";
|
||||||
|
|
||||||
|
import * as cheerio from "cheerio";
|
||||||
|
|
||||||
|
import axios, { AxiosError } from "axios";
|
||||||
|
import { Platforms } from "@/meta/settings";
|
||||||
|
import { DEFAULT_YOUPORN_GALLERY_EXPIRY } from "@/constants/redis";
|
||||||
|
|
||||||
|
export const fetchYouPornGalleryData = async (params?: FetchParams): Promise<GalleryData[]> => {
|
||||||
|
|
||||||
|
let data: GalleryData[] = [];
|
||||||
|
|
||||||
|
const reqHeaders = getHeaders(YOUPORN_BASE_URL)
|
||||||
|
|
||||||
|
const queryUrl = await getYouPornQueryUrl(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 = params?.query ? ".searchResults .video-box" : ".tm_mostRecent_videos_section .video-box"
|
||||||
|
|
||||||
|
const thumbs = $(wrapperId);
|
||||||
|
|
||||||
|
thumbs.map((key, thumb) => {
|
||||||
|
|
||||||
|
const videoUrl = $(thumb).find("a.tm_video_link").attr("href")?.split('/')[2];
|
||||||
|
const imgUrl = $(thumb).find("img.thumb-image").attr("data-src")
|
||||||
|
const text = $(thumb).find("a.video-title").text();
|
||||||
|
|
||||||
|
videoUrl && imgUrl && text && data.push({
|
||||||
|
videoUrl,
|
||||||
|
imgUrl,
|
||||||
|
text,
|
||||||
|
platform: Platforms.youporn
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
await storeDataIntoRedis(queryUrl, data, DEFAULT_YOUPORN_GALLERY_EXPIRY);
|
||||||
|
|
||||||
|
}).catch((error: AxiosError) => {
|
||||||
|
// handle errors
|
||||||
|
});
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { YOUPORN_BASE_SEARCH, YOUPORN_BASE_URL } from "@/constants/urls"
|
||||||
|
import { getHeadersWithCookie } from "../common/headers"
|
||||||
|
import axios from "axios"
|
||||||
|
import { MindGeekVideoSrcElem, VideoSourceItem } from "@/meta/data"
|
||||||
|
|
||||||
|
export const getYouPornQueryUrl = async (query?: string): Promise<string> => {
|
||||||
|
|
||||||
|
if (query) {
|
||||||
|
return `${YOUPORN_BASE_SEARCH}${query}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return YOUPORN_BASE_URL
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getYouPornMediaUrlList = async (url: string, sessionCookie: string): Promise<VideoSourceItem[]> => {
|
||||||
|
|
||||||
|
const headersWithCookie = getHeadersWithCookie(YOUPORN_BASE_URL, sessionCookie)
|
||||||
|
|
||||||
|
let videos: VideoSourceItem[] = []
|
||||||
|
|
||||||
|
await axios.get(url, headersWithCookie)
|
||||||
|
.then(async response => {
|
||||||
|
|
||||||
|
if (response.data) {
|
||||||
|
|
||||||
|
videos = await response.data.map((elem: MindGeekVideoSrcElem) => ({
|
||||||
|
src: elem?.videoUrl,
|
||||||
|
type: 'video/mp4',
|
||||||
|
size: elem?.quality
|
||||||
|
})) as VideoSourceItem[]
|
||||||
|
|
||||||
|
return videos
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
.catch(error => console.log(error))
|
||||||
|
|
||||||
|
return videos
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
import { YOUPORN_BASE_URL, YOUPORN_BASE_URL_VIDEO } 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_YOUPORN_VIDEO_EXPIRY, DEFAULT_YOUPORN_GALLERY_EXPIRY } from "@/constants/redis";
|
||||||
|
|
||||||
|
import * as cheerio from "cheerio";
|
||||||
|
|
||||||
|
import axios, { AxiosError } from "axios";
|
||||||
|
import { createSessionCookie, findGetMediaUrlInTagblock } from "../common/mindgeek";
|
||||||
|
import { getYouPornMediaUrlList } from "./url";
|
||||||
|
import { Platforms } from "@/meta/settings";
|
||||||
|
|
||||||
|
export const fetchYouPornVideoData = async (videoId: string, params?: FetchParams): Promise<[VideoData, GalleryData[]]> => {
|
||||||
|
|
||||||
|
let data: VideoData = {
|
||||||
|
hlsUrl: '',
|
||||||
|
srcSet: []
|
||||||
|
}
|
||||||
|
|
||||||
|
let relatedData: GalleryData[] = [];
|
||||||
|
|
||||||
|
let mediaUrl, sessionCookie, convertedData: VideoSourceItem[]
|
||||||
|
|
||||||
|
let reqHeaders = getHeaders(YOUPORN_BASE_URL)
|
||||||
|
|
||||||
|
const queryUrl = `${YOUPORN_BASE_URL_VIDEO}/${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 => {
|
||||||
|
|
||||||
|
sessionCookie = response?.headers["set-cookie"] ? createSessionCookie(response?.headers["set-cookie"]) : '';
|
||||||
|
|
||||||
|
const html = response.data;
|
||||||
|
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
|
||||||
|
const scriptTags = $("script");
|
||||||
|
|
||||||
|
scriptTags.map((idx, elem) => {
|
||||||
|
const getMediaUrl = findGetMediaUrlInTagblock($(elem).toString().replace(/\\/g, ''), 'media/mp4') ?? null
|
||||||
|
|
||||||
|
if (getMediaUrl) {
|
||||||
|
mediaUrl = getMediaUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
const wrapperId = "#relatedVideos .video-box"
|
||||||
|
|
||||||
|
const thumbs = $(wrapperId);
|
||||||
|
|
||||||
|
thumbs.map((key, thumb) => {
|
||||||
|
|
||||||
|
const videoUrl = $(thumb).find("a.tm_video_link").attr("href")?.split('/')[2];
|
||||||
|
const imgUrl = $(thumb).find("img.thumb-image").attr("data-src")
|
||||||
|
const text = $(thumb).find("a.video-title").text();
|
||||||
|
|
||||||
|
videoUrl && imgUrl && text && relatedData.push({
|
||||||
|
videoUrl,
|
||||||
|
imgUrl,
|
||||||
|
text,
|
||||||
|
platform: Platforms.youporn
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
}).catch((error: AxiosError) => {
|
||||||
|
// error handling goes here
|
||||||
|
});
|
||||||
|
|
||||||
|
if (sessionCookie && mediaUrl) {
|
||||||
|
convertedData = await getYouPornMediaUrlList(mediaUrl, sessionCookie)
|
||||||
|
data.srcSet = convertedData.reverse()
|
||||||
|
|
||||||
|
await storeDataIntoRedis(queryUrl, data, DEFAULT_YOUPORN_VIDEO_EXPIRY);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relatedData.length > 0) {
|
||||||
|
await storeDataIntoRedis(queryUrl + DEFAULT_RELATED_VIDEO_KEY_PATH, relatedData, DEFAULT_YOUPORN_GALLERY_EXPIRY);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [ data, relatedData ]
|
||||||
|
}
|
Loading…
Reference in New Issue