Release v0.4: Add PornHub support / Server-side streaming / Plyr.js / Video srcset #96

Merged
lamacchinadesiderante merged 18 commits from feature/pornhub-support into develop 2024-05-25 11:46:42 +00:00
12 changed files with 278 additions and 8 deletions
Showing only changes of commit b1dc47d461 - Show all commits

View File

@ -0,0 +1,22 @@
import axios from "axios";
type GetParams = {
params: {
platform: string
encodedUrl: string
};
};
export async function GET(req: Request, { params }: GetParams) {
const { platform, encodedUrl } = params;
const decodedUrl = decodeURIComponent(encodedUrl)
const response = await axios.get<ReadableStream>(decodedUrl, {
responseType: "stream",
});
return new Response(response.data);
}

View File

@ -1,4 +1,10 @@
export const DEFAULT_XVIDEOS_CONTENT_EXPIRY = { EX: 60 * 60 * 24 }; const EX_HOURLY = 60 * 60
export const DEFAULT_XNXX_CONTENT_EXPIRY = { EX: 60 * 60 * 24 }; const EX_DAILY = 60 * 60 * 24
export const DEFAULT_XVIDEOS_CONTENT_EXPIRY = { EX: EX_DAILY };
export const DEFAULT_XNXX_CONTENT_EXPIRY = { EX: EX_DAILY };
export const DEFAULT_PORNHUB_GALLERY_EXPIRY = { EX: EX_DAILY};
export const DEFAULT_PORNHUB_VIDEO_EXPIRY = { EX: EX_HOURLY };
export const DEFAULT_RELATED_VIDEO_KEY_PATH = '/related/' export const DEFAULT_RELATED_VIDEO_KEY_PATH = '/related/'

View File

@ -1,11 +1,21 @@
// XVIDEOS
export const XVIDEOS_BASE_URL: string = "https://www.xvideos.com" export const XVIDEOS_BASE_URL: string = "https://www.xvideos.com"
export const XVIDEOS_BASE_URL_GAY: string = "https://www.xvideos.com/gay" export const XVIDEOS_BASE_URL_GAY: string = "https://www.xvideos.com/gay"
export const XVIDEOS_BASE_URL_TRANS: string = "https://www.xvideos.com/shemale" export const XVIDEOS_BASE_URL_TRANS: string = "https://www.xvideos.com/shemale"
// XNXX
export const XNXX_BASE_URL: string = 'https://www.xnxx.com' export const XNXX_BASE_URL: string = 'https://www.xnxx.com'
export const XNXX_BASE_URL_ETERO: string = 'https://www.xnxx.com/best' export const XNXX_BASE_URL_ETERO: string = 'https://www.xnxx.com/best'
export const XNXX_BASE_URL_GAY: string = 'https://www.xnxx.com/best-of-gay' export const XNXX_BASE_URL_GAY: string = 'https://www.xnxx.com/best-of-gay'
export const XNXX_BASE_URL_TRANS: string = 'https://www.xnxx.com/best-of-shemale' export const XNXX_BASE_URL_TRANS: string = 'https://www.xnxx.com/best-of-shemale'
export const XNXX_BASE_SEARCH: string = 'https://www.xnxx.com/search' export const XNXX_BASE_SEARCH: string = 'https://www.xnxx.com/search'
// PORNHUB
export const PORNHUB_BASE_URL: string = 'https://www.pornhub.com'
export const PORNHUB_BASE_URL_VIDEO: string = 'https://www.pornhub.com/view_video.php?viewkey='

View File

@ -12,10 +12,15 @@ export interface GalleryData {
platform: Platforms platform: Platforms
} }
export interface VideoSrcSet {
}
export interface VideoData { export interface VideoData {
lowResUrl: string, lowResUrl?: string,
hiResUrl?: string, hiResUrl?: string,
hlsUrl?: string hlsUrl?: string
srcSet?: VideoSrcSet
} }
export interface VideoAgent { export interface VideoAgent {

View File

@ -6,7 +6,8 @@ export enum Cookies {
export enum Platforms { export enum Platforms {
xvideos= 'xvideos', xvideos= 'xvideos',
xnxx= 'xnxx' xnxx= 'xnxx',
pornhub= 'pornhub'
} }
export enum XVideosCatQueryMap { export enum XVideosCatQueryMap {

View File

@ -4,10 +4,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";
const AgentMapper = { const AgentMapper = {
[Platforms.xvideos]: XVideosAgent, [Platforms.xvideos]: XVideosAgent,
[Platforms.xnxx]: XNXXAgent [Platforms.xnxx]: XNXXAgent,
[Platforms.pornhub]: PornHubAgent
} }
export class VideoAgent { export class VideoAgent {

View File

@ -22,7 +22,7 @@ const getRandomUserAgent = (): string => {
return userAgents[rand] return userAgents[rand]
} }
export const getHeaders = (host:string = XVIDEOS_BASE_URL) => { export const getHeaders = (host:string) => {
return { return {
headers: { headers: {
"User-Agent": getRandomUserAgent(), "User-Agent": getRandomUserAgent(),
@ -32,7 +32,23 @@ export const getHeaders = (host:string = XVIDEOS_BASE_URL) => {
"Sec-Fetch-Dest": "document", "Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate", "Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none", "Sec-Fetch-Site": "none",
"Host": removeHttpS(host) "Host": removeHttpS(host),
},
}
};
export const getHeadersWithCookie = (host:string, cookie: string) => {
return {
headers: {
"User-Agent": getRandomUserAgent(),
"Accept-Language": "en-gb, en, en-US, it",
"Accept-Encoding": "gzip, deflate, br",
"Connection": "keep-alive",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Host": removeHttpS(host),
"Cookie": cookie
}, },
} }
}; };

View File

@ -0,0 +1,35 @@
export const findGetMediaUrlInTagblock = (
tagBlock: string): string | null => {
const getMediaIndex = tagBlock.indexOf('get_media');
if (getMediaIndex === -1) {
return null
}
const start = tagBlock.lastIndexOf('"', getMediaIndex);
const end = tagBlock.indexOf('"', getMediaIndex);
const substr = tagBlock.substring(start, end);
if (substr.length > 0) {
return substr.replace(/\\/g, '').replace(/"/g, '');
}
return null
}
export const createSessionCookie = (responseSetCookies: string[]): string => {
let pieces: string[] = []
responseSetCookies.map((elem, key) => {
if (elem.includes('platform=') || elem.includes('ss=') || elem.includes('fg_')) {
pieces.push(elem.split(';')[0])
console.log()
}
})
const sessionCookie = pieces.join('; ');
return sessionCookie
}

View File

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

View File

@ -0,0 +1,64 @@
import { PORNHUB_BASE_URL } from "@/constants/urls";
import { FetchParams, GalleryData } from "@/meta/data";
import { getHeaders } from "../common/headers";
import { getDataFromRedis, storeDataIntoRedis } from "@/redis/client";
import { getPornHubQueryUrl } from "./url";
import * as cheerio from "cheerio";
import axios, { AxiosError } from "axios";
import { Platforms } from "@/meta/settings";
import { DEFAULT_PORNHUB_GALLERY_EXPIRY } from "@/constants/redis";
export const fetchPornHubGalleryData = async (params?: FetchParams): Promise<GalleryData[]> => {
let data: GalleryData[] = [];
const reqHeaders = getHeaders(PORNHUB_BASE_URL)
const queryUrl = await getPornHubQueryUrl(params?.query)
console.log(queryUrl)
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 ? "#videoSearchResult li" : "#singleFeedSection li"
const thumbs = $(wrapperId);
thumbs.map((key, thumb) => {
const videoUrl = $(thumb).find(".pcVideoListItem a").attr("href")?.split('=')[1];
const imgUrl = $(thumb).find(".pcVideoListItem a img").attr("src")
const text = $(thumb).find(".pcVideoListItem a").attr("title")
videoUrl && imgUrl && text && data.push({
videoUrl,
imgUrl,
text,
platform: Platforms.pornhub
})
})
await storeDataIntoRedis(queryUrl, data, DEFAULT_PORNHUB_GALLERY_EXPIRY);
}).catch((error: AxiosError) => {
// handle errors
});
return data
}

View File

@ -0,0 +1,9 @@
import { PORNHUB_BASE_URL } from "@/constants/urls"
export const getPornHubQueryUrl = async (query?: string): Promise<string> => {
if (query) {
return `${PORNHUB_BASE_URL}/video/search?search=${query}`
}
return PORNHUB_BASE_URL
}

View File

@ -0,0 +1,84 @@
import { PORNHUB_BASE_URL, PORNHUB_BASE_URL_VIDEO } from "@/constants/urls";
import { FetchParams, GalleryData, VideoData } from "@/meta/data";
import { getHeaders, getHeadersWithCookie } from "../common/headers";
import { getDataFromRedis, storeDataIntoRedis } from "@/redis/client";
import { DEFAULT_PORNHUB_VIDEO_EXPIRY, DEFAULT_RELATED_VIDEO_KEY_PATH } from "@/constants/redis";
import * as cheerio from "cheerio";
import axios, { AxiosError } from "axios";
import { createSessionCookie, findGetMediaUrlInTagblock } from "../common/mindgeek";
export const fetchPornHubVideoData = async (videoId: string, params?: FetchParams): Promise<[VideoData, GalleryData[]]> => {
let data: VideoData = {
lowResUrl: ''
}
let related: GalleryData[] = [];
let reqHeaders = getHeaders(PORNHUB_BASE_URL)
const queryUrl = `${PORNHUB_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 => {
const sessionCookie = response?.headers["set-cookie"] ? createSessionCookie(response?.headers["set-cookie"]) : '';
const html = response.data;
const $ = cheerio.load(html);
const scriptTags = $("script");
// populate video data object
scriptTags.map(async (idx, elem) => {
const getMediaUrl = findGetMediaUrlInTagblock($(elem).toString())
if (getMediaUrl) {
const headersWithCookie = getHeadersWithCookie(PORNHUB_BASE_URL, sessionCookie)
await axios.get(getMediaUrl, headersWithCookie)
.then(response => {
console.log(response.data)
// magic goes here
})
.catch(error => console.log(error))
}
})
await storeDataIntoRedis(queryUrl, data, DEFAULT_PORNHUB_VIDEO_EXPIRY);
// populate related gallery
scriptTags.map((idx, elem) => {
// magic goes here
})
await storeDataIntoRedis(queryUrl + DEFAULT_RELATED_VIDEO_KEY_PATH, related, DEFAULT_PORNHUB_VIDEO_EXPIRY);
}).catch((error: AxiosError) => {
console.log(queryUrl)
console.log(error.message)
});
return [ { lowResUrl: 'https://www.w3schools.com/html/mov_bbb.mp4' } ,[]]
}