Release v0.4: Add PornHub support / Server-side streaming / Plyr.js / Video srcset #96
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
@ -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/'
|
|
@ -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='
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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' } ,[]]
|
||||||
|
}
|
Loading…
Reference in New Issue