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 };
|
||||
export const DEFAULT_XNXX_CONTENT_EXPIRY = { EX: 60 * 60 * 24 };
|
||||
const EX_HOURLY = 60 * 60
|
||||
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/'
|
|
@ -1,11 +1,21 @@
|
|||
// XVIDEOS
|
||||
|
||||
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_TRANS: string = "https://www.xvideos.com/shemale"
|
||||
|
||||
|
||||
// XNXX
|
||||
|
||||
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_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_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
|
||||
}
|
||||
|
||||
export interface VideoSrcSet {
|
||||
|
||||
}
|
||||
|
||||
export interface VideoData {
|
||||
lowResUrl: string,
|
||||
lowResUrl?: string,
|
||||
hiResUrl?: string,
|
||||
hlsUrl?: string
|
||||
srcSet?: VideoSrcSet
|
||||
}
|
||||
|
||||
export interface VideoAgent {
|
||||
|
|
|
@ -6,7 +6,8 @@ export enum Cookies {
|
|||
|
||||
export enum Platforms {
|
||||
xvideos= 'xvideos',
|
||||
xnxx= 'xnxx'
|
||||
xnxx= 'xnxx',
|
||||
pornhub= 'pornhub'
|
||||
}
|
||||
|
||||
export enum XVideosCatQueryMap {
|
||||
|
|
|
@ -4,10 +4,13 @@ import { Platforms } from "@/meta/settings";
|
|||
|
||||
import { XVideosAgent } from "./scrape/xvideos/agent";
|
||||
import { XNXXAgent } from "./scrape/xnxx/agent";
|
||||
import { PornHubAgent } from "./scrape/pornhub/agent";
|
||||
|
||||
|
||||
const AgentMapper = {
|
||||
[Platforms.xvideos]: XVideosAgent,
|
||||
[Platforms.xnxx]: XNXXAgent
|
||||
[Platforms.xnxx]: XNXXAgent,
|
||||
[Platforms.pornhub]: PornHubAgent
|
||||
}
|
||||
|
||||
export class VideoAgent {
|
||||
|
|
|
@ -22,7 +22,7 @@ const getRandomUserAgent = (): string => {
|
|||
return userAgents[rand]
|
||||
}
|
||||
|
||||
export const getHeaders = (host:string = XVIDEOS_BASE_URL) => {
|
||||
export const getHeaders = (host:string) => {
|
||||
return {
|
||||
headers: {
|
||||
"User-Agent": getRandomUserAgent(),
|
||||
|
@ -32,7 +32,23 @@ export const getHeaders = (host:string = XVIDEOS_BASE_URL) => {
|
|||
"Sec-Fetch-Dest": "document",
|
||||
"Sec-Fetch-Mode": "navigate",
|
||||
"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