Compare commits
No commits in common. "main" and "v0.4.0" have entirely different histories.
|
@ -9,7 +9,6 @@ Proxy Raye is an alternative front-end for adult websites. Watch videos on a cle
|
||||||
- PornHub (experimental)
|
- PornHub (experimental)
|
||||||
- YouPorn
|
- YouPorn
|
||||||
- RedTube
|
- RedTube
|
||||||
- XHamster
|
|
||||||
|
|
||||||
### How to switch between platforms
|
### How to switch between platforms
|
||||||
|
|
||||||
|
@ -132,7 +131,7 @@ Due to Vercel's *serverless* nature (which makes every request to XVideos and ot
|
||||||
|
|
||||||
You can self host the project on your local server via docker-compose and reverse-proxy exposed port to nginx.
|
You can self host the project on your local server via docker-compose and reverse-proxy exposed port to nginx.
|
||||||
|
|
||||||
# Disabling platforms
|
### Disabling platforms (both Vercel / Self-host)
|
||||||
|
|
||||||
For several reason you might want to disable some platforms. You can do it by adding `DISABLED_PLATFORMS` environment variable.
|
For several reason you might want to disable some platforms. You can do it by adding `DISABLED_PLATFORMS` environment variable.
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "proxyraye-next",
|
"name": "proxyraye-next",
|
||||||
"version": "0.4.1",
|
"version": "0.4.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { Cookies, OrientationMapper, Platforms, XVideosOrientations } from '@/meta/settings';
|
import { Cookies, Platforms, XVideosOrientations, PornHubOrientations, YouPornOrientations, RedTubeOrientations } from '@/meta/settings';
|
||||||
|
|
||||||
import css from './Orientation.module.scss'
|
import css from './Orientation.module.scss'
|
||||||
|
|
||||||
|
@ -17,7 +17,19 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
const getOrientations = (platform: Platforms):Object => {
|
const getOrientations = (platform: Platforms):Object => {
|
||||||
return OrientationMapper[platform];
|
if ([Platforms.xnxx, Platforms.xvideos].includes(platform)) {
|
||||||
|
return XVideosOrientations
|
||||||
|
}
|
||||||
|
|
||||||
|
if([Platforms.youporn].includes(platform)) {
|
||||||
|
return YouPornOrientations
|
||||||
|
}
|
||||||
|
|
||||||
|
if([Platforms.redtube].includes(platform)) {
|
||||||
|
return RedTubeOrientations
|
||||||
|
}
|
||||||
|
|
||||||
|
return PornHubOrientations
|
||||||
}
|
}
|
||||||
|
|
||||||
const Orientation: React.FC<Props> = (props) => {
|
const Orientation: React.FC<Props> = (props) => {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { Cookies, OrientationMapper, Platforms } from '@/meta/settings';
|
import { Cookies, Platforms, PornHubOrientations, RedTubeOrientations, XVideosOrientations, YouPornOrientations } from '@/meta/settings';
|
||||||
|
|
||||||
import css from './Platform.module.scss'
|
import css from './Platform.module.scss'
|
||||||
|
|
||||||
|
@ -18,8 +18,23 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapOrientationToPlatform = (platform: string, orientation: string): string | undefined => {
|
const mapOrientationToPlatform = (platform: string, orientation: string): string | undefined => {
|
||||||
const orientations = OrientationMapper[platform as Platforms]
|
|
||||||
return Object.keys(orientations).includes(orientation) ? orientation : String(Object.keys(orientations)[0])
|
if ([String(Platforms.xnxx), String(Platforms.xvideos)].includes(platform)) {
|
||||||
|
return Object.keys(XVideosOrientations).includes(orientation) ? orientation : String(XVideosOrientations.etero)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([String(Platforms.pornhub)].includes(platform)) {
|
||||||
|
return Object.keys(PornHubOrientations).includes(orientation) ? orientation : String(PornHubOrientations.generic)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([String(Platforms.redtube)].includes(platform)) {
|
||||||
|
return Object.keys(RedTubeOrientations).includes(orientation) ? orientation : String(RedTubeOrientations.etero)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([String(Platforms.youporn)].includes(platform)) {
|
||||||
|
return String(YouPornOrientations.generic)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Platform: React.FC<Props> = (props) => {
|
const Platform: React.FC<Props> = (props) => {
|
||||||
|
|
|
@ -15,7 +15,4 @@ export const DEFAULT_YOUPORN_VIDEO_EXPIRY = { EX: EX_HOURLY };
|
||||||
export const DEFAULT_REDTUBE_GALLERY_EXPIRY = { EX: EX_HOURLY };
|
export const DEFAULT_REDTUBE_GALLERY_EXPIRY = { EX: EX_HOURLY };
|
||||||
export const DEFAULT_REDTUBE_VIDEO_EXPIRY = { EX: EX_HOURLY };
|
export const DEFAULT_REDTUBE_VIDEO_EXPIRY = { EX: EX_HOURLY };
|
||||||
|
|
||||||
export const DEFAULT_XHAMSTER_GALLERY_EXPIRY = { EX: EX_HOURLY };
|
|
||||||
export const DEFAULT_XHAMSTER_VIDEO_EXPIRY = { EX: EX_HOURLY };
|
|
||||||
|
|
||||||
export const DEFAULT_RELATED_VIDEO_KEY_PATH = '/related/'
|
export const DEFAULT_RELATED_VIDEO_KEY_PATH = '/related/'
|
|
@ -38,15 +38,3 @@ export const REDTUBE_BASE_URL_TRANS: string = 'https://www.redtube.com/redtube/t
|
||||||
|
|
||||||
export const REDTUBE_BASE_SEARCH: string = 'https://www.redtube.com/?search='
|
export const REDTUBE_BASE_SEARCH: string = 'https://www.redtube.com/?search='
|
||||||
export const REDTUBE_BASE_GAY_SEARCH: string = 'https://www.redtube.com/gay?search='
|
export const REDTUBE_BASE_GAY_SEARCH: string = 'https://www.redtube.com/gay?search='
|
||||||
|
|
||||||
// XHAMSTER
|
|
||||||
export const XHAMSTER_BASE_URL = 'https://xhamster.com'
|
|
||||||
export const XHAMSTER_BASE_URL_VIDEOS = 'https://xhamster.com/videos'
|
|
||||||
|
|
||||||
export const XHAMSTER_BASE_URL_ETERO = 'https://xhamster.com/newest'
|
|
||||||
export const XHAMSTER_BASE_URL_GAY = 'https://xhamster.com/gay/newest'
|
|
||||||
export const XHAMSTER_BASE_URL_TRANS = 'https://xhamster.com/shemale/newest'
|
|
||||||
|
|
||||||
export const XHAMSTER_BASE_SEARCH = 'https://xhamster.com/search/'
|
|
||||||
export const XHAMSTER_BASE_SEARCH_GAY = 'https://xhamster.com/gay/search/'
|
|
||||||
export const XHAMSTER_BASE_SEARCH_TRANS = 'https://xhamster.com/shemale/search/'
|
|
|
@ -9,8 +9,7 @@ export enum Platforms {
|
||||||
xnxx= 'xnxx',
|
xnxx= 'xnxx',
|
||||||
pornhub= 'pornhub',
|
pornhub= 'pornhub',
|
||||||
youporn= 'youporn',
|
youporn= 'youporn',
|
||||||
redtube= 'redtube',
|
redtube= 'redtube'
|
||||||
xhamster= 'xhamster'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum XVideosCatQueryMap {
|
export enum XVideosCatQueryMap {
|
||||||
|
@ -40,12 +39,6 @@ export enum RedTubeOrientations {
|
||||||
trans= 'trans'
|
trans= 'trans'
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum XHamsterOrientations {
|
|
||||||
etero= 'etero',
|
|
||||||
gay= 'gay',
|
|
||||||
trans= 'trans'
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum Themes {
|
export enum Themes {
|
||||||
light= 'light',
|
light= 'light',
|
||||||
dark= 'dark',
|
dark= 'dark',
|
||||||
|
@ -58,12 +51,3 @@ export interface LangOption {
|
||||||
label: string;
|
label: string;
|
||||||
code: string;
|
code: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const OrientationMapper = {
|
|
||||||
[Platforms.xvideos]: XVideosOrientations,
|
|
||||||
[Platforms.xnxx]: XVideosOrientations,
|
|
||||||
[Platforms.pornhub]: PornHubOrientations,
|
|
||||||
[Platforms.youporn]: YouPornOrientations,
|
|
||||||
[Platforms.redtube]: RedTubeOrientations,
|
|
||||||
[Platforms.xhamster]: XHamsterOrientations
|
|
||||||
}
|
|
|
@ -7,15 +7,13 @@ import { XNXXAgent } from "./scrape/xnxx/agent";
|
||||||
import { PornHubAgent } from "./scrape/pornhub/agent";
|
import { PornHubAgent } from "./scrape/pornhub/agent";
|
||||||
import { YouPornAgent } from "./scrape/youporn/agent";
|
import { YouPornAgent } from "./scrape/youporn/agent";
|
||||||
import { RedTubeAgent } from "./scrape/redtube/agent";
|
import { RedTubeAgent } from "./scrape/redtube/agent";
|
||||||
import { XHamsterAgent } from "./scrape/xhamster/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,
|
[Platforms.youporn]: YouPornAgent,
|
||||||
[Platforms.redtube]: RedTubeAgent,
|
[Platforms.redtube]: RedTubeAgent
|
||||||
[Platforms.xhamster]: XHamsterAgent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class VideoAgent {
|
export class VideoAgent {
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
import { FetchParams, GalleryData, VideoAgent, VideoData } from "@/meta/data";
|
|
||||||
import { fetchXHamsterGalleryData } from "./gallery";
|
|
||||||
import { fetchXHamsterVideoData } from "./video";
|
|
||||||
|
|
||||||
export class XHamsterAgent implements VideoAgent {
|
|
||||||
|
|
||||||
public getGallery = async (params?: FetchParams): Promise<GalleryData[]> => {
|
|
||||||
return await fetchXHamsterGalleryData(params)
|
|
||||||
}
|
|
||||||
|
|
||||||
public getVideo = async (id: string, params?: FetchParams): Promise<[VideoData, GalleryData[]]> => {
|
|
||||||
return await fetchXHamsterVideoData(id, params)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
import { XHAMSTER_BASE_URL, XHAMSTER_BASE_URL_VIDEOS } from "@/constants/urls";
|
|
||||||
import { FetchParams, GalleryData } from "@/meta/data";
|
|
||||||
import { getHeaders } from "../common/headers";
|
|
||||||
import { getXHamsterQueryUrl } from "./url";
|
|
||||||
import { getDataFromRedis, storeDataIntoRedis } from "@/redis/client";
|
|
||||||
|
|
||||||
import * as cheerio from "cheerio";
|
|
||||||
|
|
||||||
import axios, { AxiosError } from "axios";
|
|
||||||
import { DEFAULT_XHAMSTER_GALLERY_EXPIRY } from "@/constants/redis";
|
|
||||||
import { Platforms } from "@/meta/settings";
|
|
||||||
|
|
||||||
export const fetchXHamsterGalleryData = async (params?: FetchParams): Promise<GalleryData[]> => {
|
|
||||||
|
|
||||||
let data: GalleryData[] = [];
|
|
||||||
|
|
||||||
const reqHeaders = getHeaders(XHAMSTER_BASE_URL)
|
|
||||||
|
|
||||||
const queryUrl = await getXHamsterQueryUrl(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 = '.thumb-list .thumb-list__item'
|
|
||||||
|
|
||||||
const thumbs = $(wrapperId);
|
|
||||||
|
|
||||||
thumbs.map((key, thumb) => {
|
|
||||||
|
|
||||||
const videoUrl = $(thumb).find("a.video-thumb__image-container").attr("href")?.replace(XHAMSTER_BASE_URL_VIDEOS, '')
|
|
||||||
const imgUrl = $(thumb).find("a.video-thumb__image-container img").attr("src")
|
|
||||||
const text = $(thumb).find("a.video-thumb-info__name").attr("title")
|
|
||||||
|
|
||||||
videoUrl && imgUrl && text && data.push({
|
|
||||||
videoUrl,
|
|
||||||
imgUrl,
|
|
||||||
text,
|
|
||||||
platform: Platforms.xhamster
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
if (data.length > 0) {
|
|
||||||
await storeDataIntoRedis(queryUrl, data, DEFAULT_XHAMSTER_GALLERY_EXPIRY);
|
|
||||||
}
|
|
||||||
|
|
||||||
}).catch((error: AxiosError) => {
|
|
||||||
// handle errors
|
|
||||||
});
|
|
||||||
|
|
||||||
return data
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
import { XHAMSTER_BASE_SEARCH, XHAMSTER_BASE_SEARCH_GAY, XHAMSTER_BASE_SEARCH_TRANS, XHAMSTER_BASE_URL_ETERO, XHAMSTER_BASE_URL_GAY, XHAMSTER_BASE_URL_TRANS } from "@/constants/urls"
|
|
||||||
import { Cookies, XHamsterOrientations } from "@/meta/settings"
|
|
||||||
import { getCookie } from "@/utils/cookies/read"
|
|
||||||
|
|
||||||
export const getXHamsterQueryUrl = async (query?: string): Promise<string> => {
|
|
||||||
const orientation = await getCookie(Cookies.orientation)
|
|
||||||
|
|
||||||
if (query) {
|
|
||||||
if (orientation && orientation.value == XHamsterOrientations.gay) {
|
|
||||||
return XHAMSTER_BASE_SEARCH_GAY + query + '?revert=orientation'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (orientation && orientation.value == XHamsterOrientations.trans) {
|
|
||||||
return XHAMSTER_BASE_SEARCH_TRANS + query + '?revert=orientation'
|
|
||||||
}
|
|
||||||
|
|
||||||
return XHAMSTER_BASE_SEARCH + query
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if (orientation && orientation.value == XHamsterOrientations.gay) {
|
|
||||||
return XHAMSTER_BASE_URL_GAY
|
|
||||||
}
|
|
||||||
|
|
||||||
if (orientation && orientation.value == XHamsterOrientations.trans) {
|
|
||||||
return XHAMSTER_BASE_URL_TRANS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return XHAMSTER_BASE_URL_ETERO
|
|
||||||
}
|
|
|
@ -1,94 +0,0 @@
|
||||||
import { XHAMSTER_BASE_URL, XHAMSTER_BASE_URL_VIDEOS } 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_XHAMSTER_GALLERY_EXPIRY, DEFAULT_XHAMSTER_VIDEO_EXPIRY } from "@/constants/redis";
|
|
||||||
|
|
||||||
import * as cheerio from "cheerio";
|
|
||||||
|
|
||||||
import axios, { AxiosError } from "axios";
|
|
||||||
import { findGetMediaUrlInTagblock } from "../common/mindgeek";
|
|
||||||
import { Platforms } from "@/meta/settings";
|
|
||||||
import { encodeUrl } from "@/utils/string";
|
|
||||||
import { DEFAULT_VIDEO_STREAM_ROUTE_PREFIX } from "@/constants/stream";
|
|
||||||
|
|
||||||
export const fetchXHamsterVideoData = async (videoId: string, params?: FetchParams): Promise<[VideoData, GalleryData[]]> => {
|
|
||||||
|
|
||||||
let data: VideoData = {
|
|
||||||
srcSet: []
|
|
||||||
}
|
|
||||||
|
|
||||||
let relatedData: GalleryData[] = [];
|
|
||||||
|
|
||||||
let reqHeaders = getHeaders(XHAMSTER_BASE_URL);
|
|
||||||
|
|
||||||
const queryUrl = `${XHAMSTER_BASE_URL_VIDEOS}/${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 html = response.data;
|
|
||||||
|
|
||||||
const $ = cheerio.load(html);
|
|
||||||
|
|
||||||
const scriptTags = $("script");
|
|
||||||
|
|
||||||
scriptTags.map((idx, elem) => {
|
|
||||||
const hlsUrl = findGetMediaUrlInTagblock($(elem).toString().replace(/\\/g, ''), 'media=hls4') ?? null
|
|
||||||
|
|
||||||
if (hlsUrl) {
|
|
||||||
['144', '240', '360', '480', '720', '1080'].map((res: string) => {
|
|
||||||
let resUrl = findGetMediaUrlInTagblock($(elem).toString().replace(/\\/g, ''), `${res}p.h264.mp4`) ?? null
|
|
||||||
|
|
||||||
if (resUrl) {
|
|
||||||
data.srcSet?.push({
|
|
||||||
src: `${DEFAULT_VIDEO_STREAM_ROUTE_PREFIX}/${Platforms.xhamster}/${encodeUrl(resUrl)}`,
|
|
||||||
type: 'video/mp4',
|
|
||||||
size: res
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
const wrapperId = '.thumb-list .thumb-list__item'
|
|
||||||
|
|
||||||
const thumbs = $(wrapperId);
|
|
||||||
|
|
||||||
thumbs.map((key, thumb) => {
|
|
||||||
|
|
||||||
const videoUrl = $(thumb).find("a.video-thumb__image-container").attr("href")?.replace(XHAMSTER_BASE_URL_VIDEOS, '')
|
|
||||||
const imgUrl = $(thumb).find("a.video-thumb__image-container img").attr("src")
|
|
||||||
const text = $(thumb).find("a.video-thumb-info__name").attr("title")
|
|
||||||
|
|
||||||
videoUrl && imgUrl && text && relatedData.push({
|
|
||||||
videoUrl,
|
|
||||||
imgUrl,
|
|
||||||
text,
|
|
||||||
platform: Platforms.xhamster
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
}).catch((error: AxiosError) => {
|
|
||||||
// error handling goes here
|
|
||||||
});
|
|
||||||
|
|
||||||
if (data.srcSet && data.srcSet?.length > 0) {
|
|
||||||
await storeDataIntoRedis(queryUrl, data, DEFAULT_XHAMSTER_VIDEO_EXPIRY);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (relatedData.length > 0) {
|
|
||||||
await storeDataIntoRedis(queryUrl + DEFAULT_RELATED_VIDEO_KEY_PATH, relatedData, DEFAULT_XHAMSTER_GALLERY_EXPIRY);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [ data, relatedData ]
|
|
||||||
}
|
|
|
@ -21,30 +21,8 @@ const getEncodingKey = ():string => {
|
||||||
return process.env.ENCODING_KEY ?? DEFAULT_ENCODING_KEY;
|
return process.env.ENCODING_KEY ?? DEFAULT_ENCODING_KEY;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Funzione per codifica Base64 URL-safe
|
|
||||||
const base64UrlEncode = (input: string): string => {
|
|
||||||
return btoa(input)
|
|
||||||
.replace(/\+/g, '-')
|
|
||||||
.replace(/\//g, '_')
|
|
||||||
.replace(/=+$/, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Funzione per decodifica Base64 URL-safe
|
|
||||||
const base64UrlDecode = (input: string): string => {
|
|
||||||
let base64 = input
|
|
||||||
.replace(/-/g, '+')
|
|
||||||
.replace(/_/g, '/');
|
|
||||||
|
|
||||||
// Aggiungi padding se necessario
|
|
||||||
while (base64.length % 4) {
|
|
||||||
base64 += '=';
|
|
||||||
}
|
|
||||||
|
|
||||||
return atob(base64);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function encodeUrl(url: string): string {
|
export function encodeUrl(url: string): string {
|
||||||
const key = getEncodingKey();
|
const key = getEncodingKey()
|
||||||
|
|
||||||
// Convert the URL and key to UTF-8 bytes
|
// Convert the URL and key to UTF-8 bytes
|
||||||
const urlBytes = new TextEncoder().encode(url);
|
const urlBytes = new TextEncoder().encode(url);
|
||||||
|
@ -53,16 +31,16 @@ export function encodeUrl(url: string): string {
|
||||||
// XOR the bytes of the URL with the key bytes
|
// XOR the bytes of the URL with the key bytes
|
||||||
const encodedBytes = urlBytes.map((byte, index) => byte ^ keyBytes[index % keyBytes.length]);
|
const encodedBytes = urlBytes.map((byte, index) => byte ^ keyBytes[index % keyBytes.length]);
|
||||||
|
|
||||||
// Convert the XORed bytes to a base64 URL-safe string
|
// Convert the XORed bytes to a base64 string
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
return base64UrlEncode(String.fromCharCode(...encodedBytes));
|
return btoa(String.fromCharCode(...encodedBytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function decodeUrl(encodedUrl: string): string {
|
export function decodeUrl(encodedUrl: string): string {
|
||||||
const key = getEncodingKey();
|
const key = getEncodingKey()
|
||||||
|
|
||||||
// Decode the base64 URL-safe string to get the XORed bytes
|
// Decode the base64 string to get the XORed bytes
|
||||||
const encodedBytes = Uint8Array.from(base64UrlDecode(encodedUrl), char => char.charCodeAt(0));
|
const encodedBytes = Uint8Array.from(atob(encodedUrl), char => char.charCodeAt(0));
|
||||||
const keyBytes = new TextEncoder().encode(key);
|
const keyBytes = new TextEncoder().encode(key);
|
||||||
|
|
||||||
// XOR the encoded bytes with the key bytes to get the original URL bytes
|
// XOR the encoded bytes with the key bytes to get the original URL bytes
|
||||||
|
|
Loading…
Reference in New Issue