Release v0.4: Add PornHub support / Server-side streaming / Plyr.js / Video srcset #96
|
@ -6,3 +6,10 @@ ENABLE_REDIS=true
|
||||||
|
|
||||||
# if cache enabled, set redis url for external redis
|
# if cache enabled, set redis url for external redis
|
||||||
REDIS_URL='redis://127.0.0.1:6379'
|
REDIS_URL='redis://127.0.0.1:6379'
|
||||||
|
|
||||||
|
# this key is necessary to encrypt/decrypt urls for server-side stream
|
||||||
|
# if not set, a default value (check const DEFAULT_ENCODING_KEY inside src/constants/encoding.ts ) will be used
|
||||||
|
# please generate a new one with command `pwgen 20 1` (pwgen command needs to be installed)
|
||||||
|
# uncomment variable below and add generated value
|
||||||
|
|
||||||
|
# ENCODING_KEY=''
|
|
@ -23,6 +23,8 @@ services:
|
||||||
environment:
|
environment:
|
||||||
- ENABLE_REDIS=true
|
- ENABLE_REDIS=true
|
||||||
- REDIS_URL=redis://redis:6379
|
- REDIS_URL=redis://redis:6379
|
||||||
|
# Please generate a new encoding key with command `pwgen 20 1`, decomment following variable and insert result into it:
|
||||||
|
# - ENCODING_KEY=
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
image: redis:alpine
|
image: redis:alpine
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Platforms } from "@/meta/settings";
|
import { Platforms } from "@/meta/settings";
|
||||||
|
import { decodeUrl } from "@/utils/string";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
type GetParams = {
|
type GetParams = {
|
||||||
|
@ -22,7 +23,7 @@ export async function GET(req: Request, { params }: GetParams) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const decodedUrl = decodeURIComponent(encodedUrl)
|
const decodedUrl = decodeUrl(encodedUrl)
|
||||||
|
|
||||||
const response = await axios.get<ReadableStream>(decodedUrl, {
|
const response = await axios.get<ReadableStream>(decodedUrl, {
|
||||||
responseType: "stream",
|
responseType: "stream",
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export const DEFAULT_ENCODING_KEY = 'oom2oz8ut0ieshie1Hae'
|
|
@ -0,0 +1 @@
|
||||||
|
export const DEFAULT_VIDEO_STREAM_ROUTE_PREFIX = '/api/stream'
|
|
@ -4,6 +4,8 @@ import { getHeadersWithCookie } from "../common/headers"
|
||||||
import { GalleryData, VideoSourceItem } from "@/meta/data"
|
import { GalleryData, 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 { DEFAULT_VIDEO_STREAM_ROUTE_PREFIX } from "@/constants/stream"
|
||||||
|
|
||||||
interface PornHubVideoSrcElem {
|
interface PornHubVideoSrcElem {
|
||||||
videoUrl: string
|
videoUrl: string
|
||||||
|
@ -48,7 +50,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: PornHubVideoSrcElem) => ({
|
||||||
src: `/api/stream/${Platforms.pornhub}/${encodeURIComponent(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
|
||||||
})) as VideoSourceItem[]
|
})) as VideoSourceItem[]
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { DEFAULT_ENCODING_KEY } from "@/constants/encoding";
|
||||||
|
|
||||||
export const removeHttpS = (url: string): string => {
|
export const removeHttpS = (url: string): string => {
|
||||||
if (url.startsWith("http://")) {
|
if (url.startsWith("http://")) {
|
||||||
return url.slice(7);
|
return url.slice(7);
|
||||||
|
@ -14,3 +16,36 @@ export const encodeVideoUrlPath = (input: string): string => {
|
||||||
export const decodeVideoUrlPath = (input: string): string => {
|
export const decodeVideoUrlPath = (input: string): string => {
|
||||||
return `/${decodeURIComponent(input)}`;
|
return `/${decodeURIComponent(input)}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getEncodingKey = ():string => {
|
||||||
|
return process.env.ENCODING_KEY ?? DEFAULT_ENCODING_KEY;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function encodeUrl(url: string): string {
|
||||||
|
const key = getEncodingKey()
|
||||||
|
|
||||||
|
// Convert the URL and key to UTF-8 bytes
|
||||||
|
const urlBytes = new TextEncoder().encode(url);
|
||||||
|
const keyBytes = new TextEncoder().encode(key);
|
||||||
|
|
||||||
|
// XOR the bytes of the URL with the key bytes
|
||||||
|
const encodedBytes = urlBytes.map((byte, index) => byte ^ keyBytes[index % keyBytes.length]);
|
||||||
|
|
||||||
|
// Convert the XORed bytes to a base64 string
|
||||||
|
//@ts-ignore
|
||||||
|
return btoa(String.fromCharCode(...encodedBytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decodeUrl(encodedUrl: string): string {
|
||||||
|
const key = getEncodingKey()
|
||||||
|
|
||||||
|
// Decode the base64 string to get the XORed bytes
|
||||||
|
const encodedBytes = Uint8Array.from(atob(encodedUrl), char => char.charCodeAt(0));
|
||||||
|
const keyBytes = new TextEncoder().encode(key);
|
||||||
|
|
||||||
|
// XOR the encoded bytes with the key bytes to get the original URL bytes
|
||||||
|
const urlBytes = encodedBytes.map((byte, index) => byte ^ keyBytes[index % keyBytes.length]);
|
||||||
|
|
||||||
|
// Convert the bytes back to a string
|
||||||
|
return new TextDecoder().decode(urlBytes);
|
||||||
|
}
|
Loading…
Reference in New Issue