Release v0.2.0 #74
|
@ -0,0 +1,5 @@
|
||||||
|
node_modules
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
.next
|
||||||
|
package-lock.json
|
22
Dockerfile
|
@ -1,23 +1,21 @@
|
||||||
# Usa la versione alpine più recente di Node.js compatibile con Next.js 14 come base
|
# PHASE 1: copy and build
|
||||||
FROM node:alpine
|
|
||||||
|
FROM node:alpine AS build
|
||||||
|
|
||||||
# Imposta la directory di lavoro nel container
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Copia i file esistenti nella root del progetto nel container
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Rimuovi la cartella node_modules (se presente)
|
RUN rm -rf node_modules && npm install && npm run build
|
||||||
RUN rm -rf node_modules
|
|
||||||
|
|
||||||
# Installa le dipendenze
|
# PHASE 2: prepare for exec
|
||||||
RUN npm install
|
|
||||||
|
|
||||||
# Esegui la build del progetto
|
FROM node:alpine AS exec
|
||||||
RUN npm run build
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY --from=build /app/. .
|
||||||
|
|
||||||
# Esponi la porta 3000
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
# Avvia il server in modalità di produzione
|
|
||||||
CMD ["npm", "run", "start"]
|
CMD ["npm", "run", "start"]
|
13
README.md
|
@ -1,6 +1,12 @@
|
||||||
# Proxy Raye
|
# Proxy Raye
|
||||||
|
|
||||||
Proxy Raye is an alternative front-end for **XVideos** and (soon) other adult sites. Watch videos on a clean UI without annoying ads popping up from everywhere!
|
Proxy Raye is an alternative front-end for adult websites. Watch videos on a clean UI without tracking and without annoying ads popping up from everywhere!
|
||||||
|
|
||||||
|
## Currently supported platforms:
|
||||||
|
|
||||||
|
- XVideos
|
||||||
|
- XNXX
|
||||||
|
- (...more coming soon!)
|
||||||
|
|
||||||
## Working demos
|
## Working demos
|
||||||
|
|
||||||
|
@ -29,11 +35,6 @@ npm run start
|
||||||
```
|
```
|
||||||
And head browser to `localhost:3000`.
|
And head browser to `localhost:3000`.
|
||||||
|
|
||||||
### WARNING:
|
|
||||||
Proxy Raye tries to avoid ip blacklisting by setting random human-request-like headers at every call. But in the long run (after several hours of continuous requests) XVideos might **temporarily blacklist** your IP address. When this happen, it will stop returning HD videos from pages. Only low quality (SD) videos will be shown.
|
|
||||||
|
|
||||||
Using a VPN can avoid such issue.
|
|
||||||
|
|
||||||
# Modify
|
# Modify
|
||||||
If you want to edit the project you can start development mode by opening root folder via console and running:
|
If you want to edit the project you can start development mode by opening root folder via console and running:
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"Header": {
|
"Header": {
|
||||||
"title": "Proxy Raye",
|
"title": "Proxy Raye",
|
||||||
"description": "A proxy for XVideos",
|
"description": "A proxy for porn websites",
|
||||||
"disclaimer_0": "Genital sexuality is only one of the many possible conceptions of sexuality",
|
"disclaimer_0": "Genital sexuality is only one of the many possible conceptions of sexuality",
|
||||||
"disclaimer_1": "Platform capitalism makes money on desire flow. Proxies avoid this to happen.",
|
"disclaimer_1": "Platform capitalism makes money on desire flow. Proxies avoid this to happen.",
|
||||||
"disclaimer_2": "Platform capitalism is narcissism-driven",
|
"disclaimer_2": "Platform capitalism is narcissism-driven",
|
||||||
|
@ -22,7 +22,10 @@
|
||||||
"Settings": {
|
"Settings": {
|
||||||
"lang_title": "Please select language",
|
"lang_title": "Please select language",
|
||||||
"lang_it": "Italian",
|
"lang_it": "Italian",
|
||||||
"lang_en": "English"
|
"lang_en": "English",
|
||||||
|
"settings_title": "Settings",
|
||||||
|
"platform_title": "Platform:",
|
||||||
|
"orientation_title": "Orientation:"
|
||||||
},
|
},
|
||||||
"Results": {
|
"Results": {
|
||||||
"query": "Search results for: {{ query }}",
|
"query": "Search results for: {{ query }}",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"Header": {
|
"Header": {
|
||||||
"title": "Proxy Raye",
|
"title": "Proxy Raye",
|
||||||
"description": "Un proxy per XVideos",
|
"description": "Un proxy per i siti porno",
|
||||||
"disclaimer_0": "Quella genitale è solo una delle possibili concezioni della sessualità.",
|
"disclaimer_0": "Quella genitale è solo una delle possibili concezioni della sessualità.",
|
||||||
"disclaimer_1": "Le piattaforme monetizzano i flussi di desiderio. I proxy impediscono che questo accada.",
|
"disclaimer_1": "Le piattaforme monetizzano i flussi di desiderio. I proxy impediscono che questo accada.",
|
||||||
"disclaimer_2": "Le piattaforme si alimentano del narcisisismo degli utenti.",
|
"disclaimer_2": "Le piattaforme si alimentano del narcisisismo degli utenti.",
|
||||||
|
@ -22,7 +22,10 @@
|
||||||
"Settings": {
|
"Settings": {
|
||||||
"lang_title": "Seleziona la lingua",
|
"lang_title": "Seleziona la lingua",
|
||||||
"lang_it": "Italiano",
|
"lang_it": "Italiano",
|
||||||
"lang_en": "Inglese"
|
"lang_en": "Inglese",
|
||||||
|
"settings_title": "Impostazioni",
|
||||||
|
"platform_title": "Piattaforma:",
|
||||||
|
"orientation_title": "Orientamento:"
|
||||||
},
|
},
|
||||||
"Results": {
|
"Results": {
|
||||||
"query": "Risultati della ricerca per: {{ query }}",
|
"query": "Risultati della ricerca per: {{ query }}",
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "proxyraye-next",
|
"name": "proxyraye-next",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "proxyraye-next",
|
"name": "proxyraye-next",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@picocss/pico": "^2.0.6",
|
"@picocss/pico": "^2.0.6",
|
||||||
"@reduxjs/toolkit": "^2.2.3",
|
"@reduxjs/toolkit": "^2.2.3",
|
||||||
|
@ -17,6 +17,7 @@
|
||||||
"next-intl": "^3.11.3",
|
"next-intl": "^3.11.3",
|
||||||
"next-nprogress-bar": "^2.3.11",
|
"next-nprogress-bar": "^2.3.11",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
|
"react-cookie": "^7.1.4",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
"react-icons": "^5.1.0",
|
"react-icons": "^5.1.0",
|
||||||
"react-image": "^4.1.0",
|
"react-image": "^4.1.0",
|
||||||
|
@ -519,6 +520,20 @@
|
||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/cookie": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="
|
||||||
|
},
|
||||||
|
"node_modules/@types/hoist-non-react-statics": {
|
||||||
|
"version": "3.3.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz",
|
||||||
|
"integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"hoist-non-react-statics": "^3.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/json5": {
|
"node_modules/@types/json5": {
|
||||||
"version": "0.0.29",
|
"version": "0.0.29",
|
||||||
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
|
||||||
|
@ -537,14 +552,12 @@
|
||||||
"node_modules/@types/prop-types": {
|
"node_modules/@types/prop-types": {
|
||||||
"version": "15.7.12",
|
"version": "15.7.12",
|
||||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
|
||||||
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==",
|
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q=="
|
||||||
"devOptional": true
|
|
||||||
},
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "18.2.79",
|
"version": "18.2.79",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.79.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.79.tgz",
|
||||||
"integrity": "sha512-RwGAGXPl9kSXwdNTafkOEuFrTBD5SA2B3iEB96xi8+xu5ddUa/cpvyVCSNn+asgLCTHkb5ZxN8gbuibYJi4s1w==",
|
"integrity": "sha512-RwGAGXPl9kSXwdNTafkOEuFrTBD5SA2B3iEB96xi8+xu5ddUa/cpvyVCSNn+asgLCTHkb5ZxN8gbuibYJi4s1w==",
|
||||||
"devOptional": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
|
@ -1339,6 +1352,14 @@
|
||||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/cookie": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.3",
|
"version": "7.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||||
|
@ -1382,8 +1403,7 @@
|
||||||
"node_modules/csstype": {
|
"node_modules/csstype": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
|
||||||
"devOptional": true
|
|
||||||
},
|
},
|
||||||
"node_modules/damerau-levenshtein": {
|
"node_modules/damerau-levenshtein": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
|
@ -2711,6 +2731,14 @@
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/hoist-non-react-statics": {
|
||||||
|
"version": "3.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||||
|
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
|
||||||
|
"dependencies": {
|
||||||
|
"react-is": "^16.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/htmlparser2": {
|
"node_modules/htmlparser2": {
|
||||||
"version": "8.0.2",
|
"version": "8.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
|
||||||
|
@ -4087,6 +4115,19 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-cookie": {
|
||||||
|
"version": "7.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-7.1.4.tgz",
|
||||||
|
"integrity": "sha512-wDxxa/HYaSXSMlyWJvJ5uZTzIVtQTPf1gMksFgwAz/2/W3lCtY8r4OChCXMPE7wax0PAdMY97UkNJedGv7KnDw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/hoist-non-react-statics": "^3.3.5",
|
||||||
|
"hoist-non-react-statics": "^3.3.2",
|
||||||
|
"universal-cookie": "^7.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">= 16.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-dom": {
|
"node_modules/react-dom": {
|
||||||
"version": "18.2.0",
|
"version": "18.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
||||||
|
@ -4120,8 +4161,7 @@
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "16.13.1",
|
"version": "16.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/react-redux": {
|
"node_modules/react-redux": {
|
||||||
"version": "9.1.1",
|
"version": "9.1.1",
|
||||||
|
@ -4964,6 +5004,15 @@
|
||||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/universal-cookie": {
|
||||||
|
"version": "7.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-7.1.4.tgz",
|
||||||
|
"integrity": "sha512-Q+DVJsdykStWRMtXr2Pdj3EF98qZHUH/fXv/gwFz/unyToy1Ek1w5GsWt53Pf38tT8Gbcy5QNsj61Xe9TggP4g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/cookie": "^0.6.0",
|
||||||
|
"cookie": "^0.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/uri-js": {
|
"node_modules/uri-js": {
|
||||||
"version": "4.4.1",
|
"version": "4.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "proxyraye-next",
|
"name": "proxyraye-next",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
|
@ -18,6 +18,7 @@
|
||||||
"next-intl": "^3.11.3",
|
"next-intl": "^3.11.3",
|
||||||
"next-nprogress-bar": "^2.3.11",
|
"next-nprogress-bar": "^2.3.11",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
|
"react-cookie": "^7.1.4",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
"react-icons": "^5.1.0",
|
"react-icons": "^5.1.0",
|
||||||
"react-image": "^4.1.0",
|
"react-image": "^4.1.0",
|
||||||
|
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 578 B After Width: | Height: | Size: 729 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
@ -1,8 +1,11 @@
|
||||||
'use client'
|
|
||||||
|
|
||||||
import "@/styles/globals.scss"
|
import "@/styles/globals.scss"
|
||||||
|
|
||||||
import ReduxProvider from "@/store/redux-provider";
|
import type { Metadata } from 'next'
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'ProxyRaye',
|
||||||
|
description: 'Watch porn videos without tracking or annoying ads!',
|
||||||
|
}
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
children,
|
children,
|
||||||
|
@ -14,9 +17,7 @@ export default function RootLayout({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ReduxProvider>
|
|
||||||
{children}
|
{children}
|
||||||
</ReduxProvider>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
import Layout from "@/components/Layout";
|
import Layout from "@/components/Layout";
|
||||||
|
|
||||||
import Home from "@/components/Pages/Home";
|
import Home from "@/components/Pages/Home";
|
||||||
|
import { VideoAgent } from "@/utils/agent";
|
||||||
|
|
||||||
import { fetchGalleryData } from '@/utils/scrape/xvideos/gallery';
|
import { getPlatformCookie } from "@/utils/cookies/read";
|
||||||
|
|
||||||
export default async function HomePage() {
|
export default async function HomePage() {
|
||||||
|
|
||||||
const data = await fetchGalleryData()
|
const platform = await getPlatformCookie()
|
||||||
|
|
||||||
|
const data = await new VideoAgent(platform).getGallery()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
|
|
|
@ -2,13 +2,16 @@ import Layout from "@/components/Layout";
|
||||||
|
|
||||||
import Search from "@/components/Pages/Search";
|
import Search from "@/components/Pages/Search";
|
||||||
|
|
||||||
import { fetchGalleryData } from "@/utils/scrape/xvideos/gallery";
|
import { VideoAgent } from "@/utils/agent";
|
||||||
|
import { getPlatformCookie } from "@/utils/cookies/read";
|
||||||
|
|
||||||
export default async function SearchPage({ params }: { params: { query: string } }) {
|
export default async function SearchPage({ params }: { params: { query: string } }) {
|
||||||
|
|
||||||
const data = await fetchGalleryData({ query: params.query })
|
const platform = await getPlatformCookie()
|
||||||
|
|
||||||
|
const data = await new VideoAgent(platform).getGallery({ query: params.query })
|
||||||
|
|
||||||
return <Layout>
|
return <Layout>
|
||||||
<Search data={data} query={params.query} />
|
<Search data={data} query={decodeURIComponent(params.query)} />
|
||||||
</Layout>
|
</Layout>
|
||||||
}
|
}
|
|
@ -1,28 +0,0 @@
|
||||||
import { redirect } from 'next/navigation';
|
|
||||||
|
|
||||||
import Layout from "@/components/Layout";
|
|
||||||
|
|
||||||
import Video from "@/components/Pages/Video";
|
|
||||||
|
|
||||||
import { decodeVideoUrlPath } from '@/utils/string';
|
|
||||||
|
|
||||||
import { fetchVideoData } from '@/utils/scrape/xvideos/video';
|
|
||||||
|
|
||||||
import { useLocale } from 'next-intl';
|
|
||||||
|
|
||||||
export default async function VideoPage({ params }: { params: { id: string } }) {
|
|
||||||
|
|
||||||
const locale = useLocale()
|
|
||||||
|
|
||||||
const decodedId = decodeVideoUrlPath(params.id)
|
|
||||||
|
|
||||||
const [data, related] = await fetchVideoData(decodedId)
|
|
||||||
|
|
||||||
if (!data.lowResUrl) {
|
|
||||||
redirect(`/${locale}/404`)
|
|
||||||
}
|
|
||||||
|
|
||||||
return <Layout>
|
|
||||||
<Video id={params.id} data={data} related={related}/>
|
|
||||||
</Layout>
|
|
||||||
}
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { redirect } from 'next/navigation';
|
||||||
|
|
||||||
|
import Layout from "@/components/Layout";
|
||||||
|
|
||||||
|
import Video from "@/components/Pages/Video";
|
||||||
|
|
||||||
|
import { decodeVideoUrlPath } from '@/utils/string';
|
||||||
|
|
||||||
|
import { useLocale } from 'next-intl';
|
||||||
|
import { Platforms } from '@/meta/settings';
|
||||||
|
import { VideoAgent } from '@/utils/agent';
|
||||||
|
|
||||||
|
export default async function VideoPage({ params }: { params: { platform: Platforms, id: string } }) {
|
||||||
|
|
||||||
|
const { platform, id } = params
|
||||||
|
|
||||||
|
const locale = useLocale()
|
||||||
|
|
||||||
|
if (!platform || !Object.keys(Platforms).includes(platform)) {
|
||||||
|
redirect(`/${locale}/404`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const decodedId = decodeVideoUrlPath(id)
|
||||||
|
|
||||||
|
const [data, related] = await new VideoAgent(platform).getVideo(decodedId)
|
||||||
|
|
||||||
|
//const [data, related] = await fetchVideoData(decodedId)
|
||||||
|
|
||||||
|
if (!data.lowResUrl) {
|
||||||
|
redirect(`/${locale}/404`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Layout>
|
||||||
|
<Video id={id} data={data} related={related}/>
|
||||||
|
</Layout>
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { AppProgressBar as ProgressBar } from 'next-nprogress-bar';
|
||||||
|
|
||||||
|
import { LAYOUT_COLORS_PINK, LAYOUT_COLORS_YELLOW } from '@/constants/colors';
|
||||||
|
import { Themes } from '@/meta/settings';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
theme?: Themes
|
||||||
|
}
|
||||||
|
|
||||||
|
const Body: React.FC<Props & React.PropsWithChildren> = (props) => {
|
||||||
|
|
||||||
|
const { theme, children } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<body>
|
||||||
|
<main className="container">{children}</main>
|
||||||
|
<ProgressBar
|
||||||
|
height="4px"
|
||||||
|
color={theme == Themes.dark ? LAYOUT_COLORS_YELLOW : LAYOUT_COLORS_PINK}
|
||||||
|
options={{ showSpinner: false }}
|
||||||
|
shallowRouting
|
||||||
|
/>
|
||||||
|
</body>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Body;
|
|
@ -0,0 +1,28 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import Head from 'next/head';
|
||||||
|
|
||||||
|
import { Themes } from '@/meta/settings';
|
||||||
|
|
||||||
|
import Body from './Body';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
theme?: Themes
|
||||||
|
}
|
||||||
|
|
||||||
|
const Content: React.FC<Props & React.PropsWithChildren> = (props) => {
|
||||||
|
|
||||||
|
const { theme, children } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<html data-theme={theme}>
|
||||||
|
<Head>
|
||||||
|
<meta charSet="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
</Head>
|
||||||
|
<Body theme={theme}>{children}</Body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Content;
|
|
@ -2,17 +2,17 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { SiGitea } from "react-icons/si";
|
import { SiCodeberg } from "react-icons/si";
|
||||||
|
|
||||||
import Icon from '../Icon';
|
import Icon from '../Icon';
|
||||||
|
|
||||||
import { REPO_GITEA_URL } from '@/constants/repo';
|
import { REPO_CODEBERG_URL } from '@/constants/repo';
|
||||||
|
|
||||||
const Menu: React.FC = () => {
|
const Menu: React.FC = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Icon handleClick={() => {window.location.href = REPO_GITEA_URL;}}>
|
<Icon handleClick={() => {window.location.href = REPO_CODEBERG_URL;}}>
|
||||||
{<SiGitea size={24} />}
|
{<SiCodeberg size={24} />}
|
||||||
</Icon>
|
</Icon>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
@import 'spacing';
|
||||||
|
|
||||||
|
.container {
|
||||||
|
|
||||||
|
overflow: visible !important;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.close {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
@import 'fontsize';
|
||||||
|
@import 'spacing';
|
||||||
|
|
||||||
|
|
||||||
|
.container {
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin-bottom: $spacing_16;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import { Cookies, XVideosOrientations } from '@/meta/settings';
|
||||||
|
|
||||||
|
import css from './Orientation.module.scss'
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { setCookie } from '@/utils/cookies/write';
|
||||||
|
import { useCookies } from 'react-cookie';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
handleClose(): void
|
||||||
|
labels: {
|
||||||
|
title: string,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Orientation: React.FC<Props> = (props) => {
|
||||||
|
|
||||||
|
const { labels, handleClose } = props
|
||||||
|
|
||||||
|
const [cookies] = useCookies([Cookies.orientation]);
|
||||||
|
|
||||||
|
const handleChange = async (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
|
const value = event.target.value;
|
||||||
|
|
||||||
|
await setCookie(Cookies.orientation, value)
|
||||||
|
|
||||||
|
handleClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={css.container}>
|
||||||
|
<div className={css.title}>{labels.title}</div>
|
||||||
|
<select defaultValue={ cookies.orientation ?? XVideosOrientations.etero } onChange={handleChange} name={'orientation'} aria-label={labels.title}>
|
||||||
|
{Object.keys(XVideosOrientations).map((elem, key) => {
|
||||||
|
return <option className={css.option} key={key} value={elem}>{elem.toUpperCase()}</option>
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Orientation;
|
|
@ -0,0 +1,11 @@
|
||||||
|
@import 'fontsize';
|
||||||
|
@import 'spacing';
|
||||||
|
|
||||||
|
|
||||||
|
.container {
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin-bottom: $spacing_16;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import { Cookies, Platforms } from '@/meta/settings';
|
||||||
|
|
||||||
|
import css from './Platform.module.scss'
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { setCookie } from '@/utils/cookies/write';
|
||||||
|
import { useCookies } from 'react-cookie';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
handleClose(): void
|
||||||
|
labels: {
|
||||||
|
title: string,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Platform: React.FC<Props> = (props) => {
|
||||||
|
|
||||||
|
const { labels, handleClose } = props
|
||||||
|
|
||||||
|
const [cookies] = useCookies([Cookies.platform]);
|
||||||
|
|
||||||
|
const handleChange = async (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
|
const value = event.target.value;
|
||||||
|
|
||||||
|
await setCookie(Cookies.platform, value)
|
||||||
|
|
||||||
|
handleClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={css.container}>
|
||||||
|
<div className={css.title}>{labels.title}</div>
|
||||||
|
<select defaultValue={ cookies.platform ?? Platforms.xvideos } onChange={handleChange} name={'platform'} aria-label={labels.title}>
|
||||||
|
{Object.keys(Platforms).map((elem, key) => {
|
||||||
|
return <option className={css.option} key={key} value={elem}>{elem.toUpperCase()}</option>
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Platform;
|
|
@ -0,0 +1,41 @@
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { IoCloseCircleOutline } from "react-icons/io5";
|
||||||
|
|
||||||
|
import style from './Modal.module.scss'
|
||||||
|
import Platform from './Platform';
|
||||||
|
import Orientation from './Orientation';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
handleClose(): void
|
||||||
|
labels: {
|
||||||
|
title: string
|
||||||
|
platform: any
|
||||||
|
orientation: any
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const LangSwitcher: React.FC<Props> = (props) => {
|
||||||
|
|
||||||
|
const { labels, handleClose } = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<dialog open>
|
||||||
|
<article className={style.container}>
|
||||||
|
<header className={style.header}>
|
||||||
|
<div className={style.title}>{labels.title}</div>
|
||||||
|
<div className={style.close} onClick={() => { handleClose() }}><IoCloseCircleOutline size={24} /></div>
|
||||||
|
</header>
|
||||||
|
<div className={style.content}>
|
||||||
|
<Platform handleClose={handleClose} labels={{ title: labels.platform.title }} />
|
||||||
|
<Orientation handleClose={handleClose} labels={{ title: labels.orientation.title }} />
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</dialog >
|
||||||
|
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LangSwitcher;
|
|
@ -0,0 +1,31 @@
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
import { IoSettingsOutline } from 'react-icons/io5';
|
||||||
|
|
||||||
|
import Icon from '../Icon';
|
||||||
|
import Modal from './Modal';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
labels: any
|
||||||
|
}
|
||||||
|
|
||||||
|
const Settings: React.FC<Props> = (props) => {
|
||||||
|
|
||||||
|
const { labels } = props
|
||||||
|
|
||||||
|
const [showModal, setShowModal] = useState<boolean>(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Icon handleClick={() => setShowModal(true)}>
|
||||||
|
{<IoSettingsOutline size={24} />}
|
||||||
|
</Icon>
|
||||||
|
|
||||||
|
{showModal && <Modal handleClose={() => setShowModal(false)} labels={labels} />}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Settings;
|
|
@ -3,23 +3,29 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { TbMoon, TbSun } from 'react-icons/tb';
|
import { TbMoon, TbSun } from 'react-icons/tb';
|
||||||
import { useAppDispatch, useAppSelector } from '@/store/store';
|
import { Cookies, DEFAULT_THEME, Themes } from '@/meta/settings';
|
||||||
import { Themes } from '@/meta/settings';
|
|
||||||
import { setCurrentTheme } from '@/store/settingsSlice';
|
|
||||||
|
|
||||||
import Icon from '../Icon';
|
import Icon from '../Icon';
|
||||||
|
import { setCookie } from '@/utils/cookies/write';
|
||||||
|
import { useCookies } from 'react-cookie';
|
||||||
|
|
||||||
const Menu: React.FC = () => {
|
const Theme: React.FC = () => {
|
||||||
|
|
||||||
const theme = useAppSelector((state) => state.settings.theme);
|
const [cookies] = useCookies([Cookies.theme]);
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
|
const theme = cookies.theme ?? DEFAULT_THEME
|
||||||
|
|
||||||
|
const handleClick = async () => {
|
||||||
|
const newTheme = theme == Themes.dark ? Themes.light : Themes.dark
|
||||||
|
await setCookie(Cookies.theme, newTheme)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Icon handleClick={() => dispatch(setCurrentTheme(theme == Themes.dark ? Themes.light : Themes.dark))}>
|
<Icon handleClick={handleClick}>
|
||||||
{theme == Themes.dark && <TbMoon size={24} />}
|
{theme == Themes.dark && <TbMoon size={24} />}
|
||||||
{theme == Themes.light && <TbSun size={24} />}
|
{theme == Themes.light && <TbSun size={24} />}
|
||||||
</Icon>
|
</Icon>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Menu;
|
export default Theme;
|
|
@ -8,6 +8,7 @@ import Theme from './Theme';
|
||||||
import Repo from './Repo';
|
import Repo from './Repo';
|
||||||
import Language from './Language';
|
import Language from './Language';
|
||||||
import { LangOption } from '@/meta/settings';
|
import { LangOption } from '@/meta/settings';
|
||||||
|
import Settings from './Settings';
|
||||||
|
|
||||||
const Menu: React.FC = () => {
|
const Menu: React.FC = () => {
|
||||||
|
|
||||||
|
@ -23,11 +24,22 @@ const Menu: React.FC = () => {
|
||||||
langs: options
|
langs: options
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const settingsLabels = {
|
||||||
|
title: t('settings_title'),
|
||||||
|
platform: {
|
||||||
|
title: t('platform_title')
|
||||||
|
},
|
||||||
|
orientation: {
|
||||||
|
title: t('orientation_title')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.container}>
|
<div className={style.container}>
|
||||||
<Repo />
|
<Repo />
|
||||||
<Language labels={languageLabels} />
|
<Language labels={languageLabels} />
|
||||||
<Theme />
|
<Theme />
|
||||||
|
<Settings labels={settingsLabels} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,15 +2,17 @@ import React from 'react';
|
||||||
|
|
||||||
import style from './Title.module.scss'
|
import style from './Title.module.scss'
|
||||||
|
|
||||||
import {useTranslations} from 'next-intl';
|
import { useLocale, useTranslations } from 'next-intl';
|
||||||
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
const Title: React.FC = () => {
|
const Title: React.FC = () => {
|
||||||
|
|
||||||
const t = useTranslations('Header');
|
const t = useTranslations('Header');
|
||||||
|
const locale = useLocale()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link href={'/'}><h1 className={style.title}>{t('title')}</h1></Link>
|
<Link href={`/${locale}`}><h1 className={style.title}>{t('title')}</h1></Link>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -9,25 +9,28 @@ import classNames from 'classnames';
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
|
|
||||||
import style from './Thumbnail.module.scss'
|
import style from './Thumbnail.module.scss'
|
||||||
|
|
||||||
import { encodeVideoUrlPath } from '@/utils/string';
|
import { encodeVideoUrlPath } from '@/utils/string';
|
||||||
|
import { Platforms } from '@/meta/settings';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
locale: string
|
locale: string
|
||||||
videoUrl: string
|
videoUrl: string
|
||||||
imgUrl: string
|
imgUrl: string
|
||||||
|
platform: Platforms
|
||||||
text: string
|
text: string
|
||||||
show: boolean
|
show: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const Thumbnail: React.FC<Props> = (props) => {
|
const Thumbnail: React.FC<Props> = (props) => {
|
||||||
|
|
||||||
const { locale, videoUrl, imgUrl, text, show } = props
|
const { locale, platform, videoUrl, imgUrl, text, show } = props
|
||||||
|
|
||||||
const encodedUri = encodeVideoUrlPath(videoUrl)
|
const encodedUri = encodeVideoUrlPath(videoUrl)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(style.thumbnailContainer, { [style.show]: show } )}>
|
<div className={classNames(style.thumbnailContainer, { [style.show]: show } )}>
|
||||||
<Link href={`/${locale}/video/${encodedUri}`}>
|
<Link href={`/${locale}/video/${platform}/${encodedUri}`}>
|
||||||
<Img className={style.image} src={imgUrl} unloader={<div className={style.imgPlaceholder}></div>} />
|
<Img className={style.image} src={imgUrl} unloader={<div className={style.imgPlaceholder}></div>} />
|
||||||
<div className={style.text}>{text}</div>
|
<div className={style.text}>{text}</div>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
@ -27,6 +27,7 @@ const Gallery: React.FC<Props> = (props) => {
|
||||||
key={key}
|
key={key}
|
||||||
imgUrl={elem.imgUrl}
|
imgUrl={elem.imgUrl}
|
||||||
videoUrl={elem.videoUrl}
|
videoUrl={elem.videoUrl}
|
||||||
|
platform={elem.platform}
|
||||||
text={elem.text}
|
text={elem.text}
|
||||||
locale={locale} />
|
locale={locale} />
|
||||||
})}
|
})}
|
||||||
|
|
|
@ -1,38 +1,25 @@
|
||||||
'use client'
|
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import Head from 'next/head';
|
import { cookies } from 'next/headers'
|
||||||
|
|
||||||
import { AppProgressBar as ProgressBar } from 'next-nprogress-bar';
|
import WithRedux from '@/store/withRedux';
|
||||||
|
import Content from './Content';
|
||||||
import { useAppSelector } from '@/store/store';
|
import { DEFAULT_THEME, Themes } from '@/meta/settings';
|
||||||
import { LAYOUT_COLORS_PINK, LAYOUT_COLORS_YELLOW } from '@/constants/colors';
|
|
||||||
import { Themes } from '@/meta/settings';
|
|
||||||
|
|
||||||
const Layout: React.FC<React.PropsWithChildren> = (props) => {
|
const Layout: React.FC<React.PropsWithChildren> = (props) => {
|
||||||
|
|
||||||
const { children } = props;
|
const { children } = props;
|
||||||
|
|
||||||
const theme = useAppSelector((state) => state.settings.theme);
|
const cookieStore = cookies()
|
||||||
|
|
||||||
|
const theme = cookieStore.get('theme')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html data-theme={theme} /*lang={locale}*/>
|
<WithRedux>
|
||||||
<Head>
|
<Content theme={theme ? theme?.value as Themes : DEFAULT_THEME}>
|
||||||
<meta charSet="utf-8" />
|
{children}
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
</Content>
|
||||||
<title>Proxy Raye: watch porn videos without tracking or annoying ads!</title>
|
</WithRedux>
|
||||||
</Head>
|
|
||||||
<body>
|
|
||||||
<main className="container">{children}</main>
|
|
||||||
<ProgressBar
|
|
||||||
height="4px"
|
|
||||||
color={theme == Themes.dark ? LAYOUT_COLORS_YELLOW : LAYOUT_COLORS_PINK }
|
|
||||||
options={{ showSpinner: false }}
|
|
||||||
shallowRouting
|
|
||||||
/>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
export const REPO_GITEA_URL = 'https://git.lamacchinadesiderante.org/lamacchinadesiderante/proxyraye-nextjs'
|
export const REPO_GITEA_URL = 'https://git.lamacchinadesiderante.org/lamacchinadesiderante/proxyraye-nextjs'
|
||||||
|
export const REPO_CODEBERG_URL = 'https://codeberg.org/lamacchinadesiderante/proxyraye'
|
|
@ -1 +1,11 @@
|
||||||
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_TRANS: string = "https://www.xvideos.com/shemale"
|
||||||
|
|
||||||
|
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'
|
|
@ -1,7 +1,15 @@
|
||||||
|
import { Platforms } from "./settings"
|
||||||
|
|
||||||
|
export interface FetchParams {
|
||||||
|
baseUrl?: string
|
||||||
|
query?: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface GalleryData {
|
export interface GalleryData {
|
||||||
videoUrl: string
|
videoUrl: string
|
||||||
imgUrl: string
|
imgUrl: string
|
||||||
text: string
|
text: string
|
||||||
|
platform: Platforms
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VideoData {
|
export interface VideoData {
|
||||||
|
@ -9,3 +17,8 @@ export interface VideoData {
|
||||||
hiResUrl?: string,
|
hiResUrl?: string,
|
||||||
hlsUrl?: string
|
hlsUrl?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface VideoAgent {
|
||||||
|
getGallery(params?: FetchParams): Promise<GalleryData[]>
|
||||||
|
getVideo(id: string, params?: FetchParams): Promise<[VideoData, GalleryData[]]>
|
||||||
|
}
|
|
@ -1,8 +1,34 @@
|
||||||
|
export enum Cookies {
|
||||||
|
theme= 'theme',
|
||||||
|
orientation= 'orientation',
|
||||||
|
platform= 'platform'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Platforms {
|
||||||
|
xvideos= 'xvideos',
|
||||||
|
xnxx= 'xnxx'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum XVideosCatQueryMap {
|
||||||
|
etero= 'straight',
|
||||||
|
gay= 'gay',
|
||||||
|
trans= 'shemale'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum XVideosOrientations {
|
||||||
|
etero= 'etero',
|
||||||
|
gay= 'gay',
|
||||||
|
trans= 'trans'
|
||||||
|
}
|
||||||
|
|
||||||
export enum Themes {
|
export enum Themes {
|
||||||
light= 'light',
|
light= 'light',
|
||||||
dark= 'dark'
|
dark= 'dark',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const DEFAULT_THEME = Themes.light
|
||||||
|
export const DEFAULT_PLATFORM = Platforms.xvideos
|
||||||
|
|
||||||
export interface LangOption {
|
export interface LangOption {
|
||||||
label: string;
|
label: string;
|
||||||
code: string;
|
code: string;
|
||||||
|
|
|
@ -4,7 +4,24 @@ import { useDispatch, TypedUseSelectorHook, useSelector } from "react-redux";
|
||||||
import { settingsReducer } from "@/store/settingsSlice";
|
import { settingsReducer } from "@/store/settingsSlice";
|
||||||
|
|
||||||
import { persistReducer } from "redux-persist";
|
import { persistReducer } from "redux-persist";
|
||||||
import storage from "redux-persist/lib/storage";
|
|
||||||
|
import createWebStorage from "redux-persist/lib/storage/createWebStorage";
|
||||||
|
|
||||||
|
const createNoopStorage = () => {
|
||||||
|
return {
|
||||||
|
getItem(_key: any) {
|
||||||
|
return Promise.resolve(null);
|
||||||
|
},
|
||||||
|
setItem(_key: any, value: any) {
|
||||||
|
return Promise.resolve(value);
|
||||||
|
},
|
||||||
|
removeItem(_key: any) {
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const storage = typeof window !== "undefined" ? createWebStorage("local") : createNoopStorage();
|
||||||
|
|
||||||
const settingsPersistConfig = {
|
const settingsPersistConfig = {
|
||||||
key: "settings",
|
key: "settings",
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import ReduxProvider from "@/store/redux-provider";
|
||||||
|
|
||||||
|
export default function WithRedux({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}>) {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ReduxProvider>
|
||||||
|
{children}
|
||||||
|
</ReduxProvider>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { FetchParams, GalleryData, VideoData } from "@/meta/data";
|
||||||
|
|
||||||
|
import { Platforms } from "@/meta/settings";
|
||||||
|
|
||||||
|
import { XVideosAgent } from "./scrape/xvideos/agent";
|
||||||
|
import { XNXXAgent } from "./scrape/xnxx/agent";
|
||||||
|
|
||||||
|
const AgentMapper = {
|
||||||
|
[Platforms.xvideos]: XVideosAgent,
|
||||||
|
[Platforms.xnxx]: XNXXAgent
|
||||||
|
}
|
||||||
|
|
||||||
|
export class VideoAgent {
|
||||||
|
platform: Platforms;
|
||||||
|
|
||||||
|
constructor(platform: Platforms) {
|
||||||
|
this.platform = platform
|
||||||
|
}
|
||||||
|
|
||||||
|
public getGallery = async (params?: FetchParams): Promise<GalleryData[]> => {
|
||||||
|
return await new AgentMapper[this.platform]().getGallery(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
public getVideo = async (id: string, params?: FetchParams): Promise<[VideoData, GalleryData[]]> => {
|
||||||
|
return await new AgentMapper[this.platform]().getVideo(id, params)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
'use server'
|
||||||
|
|
||||||
|
import { Cookies as AppCookies, DEFAULT_PLATFORM, Platforms } from '@/meta/settings';
|
||||||
|
import { cookies } from 'next/headers'
|
||||||
|
|
||||||
|
export async function getCookie(name: AppCookies) {
|
||||||
|
return cookies().get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function hasCookie(name: AppCookies):Promise<boolean> {
|
||||||
|
return cookies().has(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPlatformCookie(): Promise<Platforms> {
|
||||||
|
const platformCookie = await getCookie(AppCookies.platform)
|
||||||
|
return platformCookie && platformCookie.value ? platformCookie.value as Platforms : DEFAULT_PLATFORM
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
'use server'
|
||||||
|
|
||||||
|
import { Cookies as AppCookies } from '@/meta/settings';
|
||||||
|
import { cookies } from 'next/headers'
|
||||||
|
|
||||||
|
export async function deleteCookie(name: AppCookies) {
|
||||||
|
cookies().delete(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setCookie(name: AppCookies, value: string) {
|
||||||
|
cookies().set(name, value, {
|
||||||
|
sameSite: 'lax',
|
||||||
|
secure: false
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import { XVIDEOS_BASE_URL } from "@/constants/urls";
|
import { XVIDEOS_BASE_URL } from "@/constants/urls";
|
||||||
import { removeHttpS } from "../string";
|
import { removeHttpS } from "@/utils/string";
|
||||||
|
|
||||||
const getRandomUserAgent = (): string => {
|
const getRandomUserAgent = (): string => {
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { GalleryData } from "@/meta/data";
|
||||||
|
import { Platforms } from "@/meta/settings";
|
||||||
|
|
||||||
|
export const findVideoUrlInsideTagStringByFunctionNameAndExtension = (
|
||||||
|
tagBlock: string, functionName: string, extension: string): string | null => {
|
||||||
|
const start = tagBlock.indexOf(`html5player.${functionName}('`) + `html5player.${functionName}('`.length;
|
||||||
|
const end = tagBlock.toString().indexOf("'", start);
|
||||||
|
|
||||||
|
const substr = tagBlock.substring(start, end);
|
||||||
|
|
||||||
|
if (substr.includes(extension)) {
|
||||||
|
return substr
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export const findRelatedVideos = (tagBlock: string, platform: Platforms): GalleryData[] | null => {
|
||||||
|
if (!(tagBlock.includes('video_related=['))) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trova l'inizio e la fine dell'array di oggetti nell'input
|
||||||
|
const start = tagBlock.indexOf('[{');
|
||||||
|
const end = tagBlock.lastIndexOf('}]') + 2;
|
||||||
|
|
||||||
|
// Estrai la sottostringa contenente l'array di oggetti
|
||||||
|
const jsonString = tagBlock.substring(start, end);
|
||||||
|
|
||||||
|
// Parsea la stringa JSON in un array di oggetti
|
||||||
|
const videoRelatedArray = JSON.parse(jsonString);
|
||||||
|
|
||||||
|
// Mappa ogni oggetto nell'array per rinominare le chiavi
|
||||||
|
//@ts-ignore
|
||||||
|
const parsedArray = videoRelatedArray.map(obj => ({
|
||||||
|
//@ts-ignore
|
||||||
|
videoUrl: obj.u,
|
||||||
|
imgUrl: obj.i,
|
||||||
|
text: obj.tf,
|
||||||
|
platform
|
||||||
|
}));
|
||||||
|
|
||||||
|
return parsedArray;
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { FetchParams, GalleryData, VideoAgent, VideoData } from "@/meta/data";
|
||||||
|
import { fetchXNXXGalleryData, } from "./gallery";
|
||||||
|
import { fetchXNXXVideoData } from "./video";
|
||||||
|
|
||||||
|
export class XNXXAgent implements VideoAgent {
|
||||||
|
|
||||||
|
public getGallery = async (params?: FetchParams): Promise<GalleryData[]> => {
|
||||||
|
return await fetchXNXXGalleryData(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
public getVideo = async (id: string, params?: FetchParams): Promise<[VideoData, GalleryData[]]> => {
|
||||||
|
return await fetchXNXXVideoData(id, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { FetchParams, GalleryData } from '@/meta/data';
|
||||||
|
import axios, { AxiosError } from 'axios';
|
||||||
|
|
||||||
|
import * as cheerio from "cheerio";
|
||||||
|
|
||||||
|
import { getHeaders } from '@/utils/scrape/common/headers';
|
||||||
|
import { getXNXXQueryUrl } from './url';
|
||||||
|
|
||||||
|
import { Platforms } from '@/meta/settings';
|
||||||
|
import { XNXX_BASE_URL } from '@/constants/urls';
|
||||||
|
|
||||||
|
export const fetchXNXXGalleryData = async (params?: FetchParams): Promise<GalleryData[]> => {
|
||||||
|
|
||||||
|
let data: GalleryData[] = [];
|
||||||
|
|
||||||
|
const reqHeaders = getHeaders(XNXX_BASE_URL)
|
||||||
|
|
||||||
|
const queryUrl = await getXNXXQueryUrl(params?.query)
|
||||||
|
|
||||||
|
await axios.get(queryUrl, reqHeaders)
|
||||||
|
|
||||||
|
.then(response => {
|
||||||
|
|
||||||
|
const html = response.data;
|
||||||
|
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
|
||||||
|
const thumbs = $(".thumb-block");
|
||||||
|
|
||||||
|
thumbs.map((key, thumb) => {
|
||||||
|
|
||||||
|
const videoUrl = $(thumb).find(".thumb a").attr("href")
|
||||||
|
const imgUrl = $(thumb).find(".thumb img").attr("data-src")
|
||||||
|
const text = $(thumb).find(".thumb-under a").attr("title")
|
||||||
|
|
||||||
|
videoUrl && imgUrl && text && data.push({
|
||||||
|
videoUrl,
|
||||||
|
imgUrl,
|
||||||
|
text,
|
||||||
|
platform: Platforms.xnxx
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
}).catch((error: AxiosError) => {
|
||||||
|
// handle errors
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { XNXX_BASE_SEARCH, XNXX_BASE_URL_ETERO, XNXX_BASE_URL_GAY, XNXX_BASE_URL_TRANS } from '@/constants/urls';
|
||||||
|
import { Cookies, XVideosCatQueryMap, XVideosOrientations } from '@/meta/settings';
|
||||||
|
import { getCookie } from '@/utils/cookies/read';
|
||||||
|
|
||||||
|
export const getXNXXQueryUrl = async (query?: string) => {
|
||||||
|
|
||||||
|
const category = await getCookie(Cookies.orientation)
|
||||||
|
|
||||||
|
if (!category && !query) {
|
||||||
|
return XNXX_BASE_URL_ETERO
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!category && query) {
|
||||||
|
return `${XNXX_BASE_SEARCH}/${query}`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (category && !Object.values(XVideosOrientations).includes(category.value as XVideosOrientations)) {
|
||||||
|
return XNXX_BASE_URL_ETERO
|
||||||
|
}
|
||||||
|
|
||||||
|
if (category && !query) {
|
||||||
|
switch (category.value) {
|
||||||
|
case XVideosOrientations.etero:
|
||||||
|
return XNXX_BASE_URL_ETERO
|
||||||
|
case XVideosOrientations.gay:
|
||||||
|
return XNXX_BASE_URL_GAY
|
||||||
|
case XVideosOrientations.trans:
|
||||||
|
return XNXX_BASE_URL_TRANS
|
||||||
|
default:
|
||||||
|
return XNXX_BASE_URL_ETERO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (category && query) {
|
||||||
|
return `${XNXX_BASE_SEARCH}/${XVideosCatQueryMap[category.value as XVideosOrientations]}/${query}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return XNXX_BASE_URL_ETERO
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
import { XNXX_BASE_URL } from '@/constants/urls';
|
||||||
|
import { FetchParams, GalleryData, VideoData } from '@/meta/data';
|
||||||
|
|
||||||
|
import axios, { AxiosError } from 'axios';
|
||||||
|
|
||||||
|
import * as cheerio from "cheerio";
|
||||||
|
|
||||||
|
import { Platforms } from '@/meta/settings';
|
||||||
|
import { findRelatedVideos, findVideoUrlInsideTagStringByFunctionNameAndExtension } from '@/utils/scrape/common/wgcz';
|
||||||
|
import { getHeaders } from '@/utils/scrape/common/headers';
|
||||||
|
|
||||||
|
export const fetchXNXXVideoData = async (videoId: string, params?: FetchParams): Promise<[VideoData, GalleryData[]]> => {
|
||||||
|
|
||||||
|
let data: VideoData = {
|
||||||
|
lowResUrl: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
let related: GalleryData[] = [];
|
||||||
|
|
||||||
|
const host = XNXX_BASE_URL
|
||||||
|
|
||||||
|
const reqHeaders = getHeaders(host)
|
||||||
|
|
||||||
|
const queryUrl = `${host}${videoId}`
|
||||||
|
|
||||||
|
await axios.get(queryUrl, reqHeaders)
|
||||||
|
|
||||||
|
.then(response => {
|
||||||
|
|
||||||
|
const html = response.data;
|
||||||
|
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
|
||||||
|
const scriptTags = $("script");
|
||||||
|
|
||||||
|
// populate video data object
|
||||||
|
scriptTags.map((idx, elem) => {
|
||||||
|
|
||||||
|
const lowResUrl = findVideoUrlInsideTagStringByFunctionNameAndExtension($(elem).toString(), 'setVideoUrlLow', '.mp4')
|
||||||
|
const hiResUrl = findVideoUrlInsideTagStringByFunctionNameAndExtension($(elem).toString(), 'setVideoUrlHigh', '.mp4')
|
||||||
|
const hlsUrl = findVideoUrlInsideTagStringByFunctionNameAndExtension($(elem).toString(), 'setVideoHLS', '.m3u8')
|
||||||
|
|
||||||
|
if (lowResUrl) {
|
||||||
|
data.lowResUrl = lowResUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hiResUrl) {
|
||||||
|
data.hiResUrl = hiResUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hlsUrl) {
|
||||||
|
data.hlsUrl = hlsUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
// populate related gallery
|
||||||
|
scriptTags.map((idx, elem) => {
|
||||||
|
const relatedVideos = findRelatedVideos($(elem).toString(), Platforms.xnxx)
|
||||||
|
|
||||||
|
if (relatedVideos) {
|
||||||
|
related = relatedVideos
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}).catch((error: AxiosError) => {
|
||||||
|
// handle errors
|
||||||
|
});
|
||||||
|
|
||||||
|
return [data, related];
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { FetchParams, GalleryData, VideoAgent, VideoData } from "@/meta/data";
|
||||||
|
import { fetchXVideosGalleryData } from "./gallery";
|
||||||
|
import { fetchXvideosVideoData } from "./video";
|
||||||
|
|
||||||
|
export class XVideosAgent implements VideoAgent {
|
||||||
|
|
||||||
|
public getGallery = async (params?: FetchParams): Promise<GalleryData[]> => {
|
||||||
|
return await fetchXVideosGalleryData(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
public getVideo = async (id: string, params?: FetchParams): Promise<[VideoData, GalleryData[]]> => {
|
||||||
|
return await fetchXvideosVideoData(id, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,22 +1,18 @@
|
||||||
import { XVIDEOS_BASE_URL } from '@/constants/urls';
|
import { FetchParams, GalleryData } from '@/meta/data';
|
||||||
import { GalleryData } from '@/meta/data';
|
|
||||||
import axios, { AxiosError } from 'axios';
|
import axios, { AxiosError } from 'axios';
|
||||||
|
|
||||||
import * as cheerio from "cheerio";
|
import * as cheerio from "cheerio";
|
||||||
import { getHeaders } from '../headers';
|
import { getHeaders } from '@/utils/scrape/common/headers';
|
||||||
|
import { getXVideosQueryUrl } from './url';
|
||||||
|
import { Platforms } from '@/meta/settings';
|
||||||
|
|
||||||
interface FetchParams {
|
export const fetchXVideosGalleryData = async (params?: FetchParams): Promise<GalleryData[]> => {
|
||||||
baseUrl?: string
|
|
||||||
query?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const fetchGalleryData = async (params?: FetchParams): Promise<GalleryData[]> => {
|
|
||||||
|
|
||||||
let data: GalleryData[] = [];
|
let data: GalleryData[] = [];
|
||||||
|
|
||||||
const reqHeaders = getHeaders()
|
const reqHeaders = getHeaders()
|
||||||
|
|
||||||
const queryUrl = `${(params && params.baseUrl) ?? XVIDEOS_BASE_URL}${params && params.query ? '/?k=' + params.query : ''}`
|
const queryUrl = await getXVideosQueryUrl(params?.query)
|
||||||
|
|
||||||
await axios.get(queryUrl, reqHeaders)
|
await axios.get(queryUrl, reqHeaders)
|
||||||
|
|
||||||
|
@ -37,7 +33,8 @@ export const fetchGalleryData = async (params?: FetchParams): Promise<GalleryDat
|
||||||
videoUrl && imgUrl && text && data.push({
|
videoUrl && imgUrl && text && data.push({
|
||||||
videoUrl,
|
videoUrl,
|
||||||
imgUrl,
|
imgUrl,
|
||||||
text
|
text,
|
||||||
|
platform: Platforms.xvideos
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { XVIDEOS_BASE_URL, XVIDEOS_BASE_URL_GAY, XVIDEOS_BASE_URL_TRANS } from '@/constants/urls';
|
||||||
|
import { Cookies, XVideosCatQueryMap, XVideosOrientations } from '@/meta/settings';
|
||||||
|
import { getCookie } from '@/utils/cookies/read';
|
||||||
|
|
||||||
|
export const getXVideosQueryUrl = async (query?: string) => {
|
||||||
|
|
||||||
|
const category = await getCookie(Cookies.orientation)
|
||||||
|
|
||||||
|
if (!category && !query) {
|
||||||
|
return XVIDEOS_BASE_URL
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!category && query) {
|
||||||
|
return `${XVIDEOS_BASE_URL}/?k=${query}`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (category && !Object.values(XVideosOrientations).includes(category.value as XVideosOrientations)) {
|
||||||
|
return XVIDEOS_BASE_URL
|
||||||
|
}
|
||||||
|
|
||||||
|
if (category && !query) {
|
||||||
|
switch (category.value) {
|
||||||
|
case XVideosOrientations.etero:
|
||||||
|
return XVIDEOS_BASE_URL
|
||||||
|
case XVideosOrientations.gay:
|
||||||
|
return XVIDEOS_BASE_URL_GAY
|
||||||
|
case XVideosOrientations.trans:
|
||||||
|
return XVIDEOS_BASE_URL_TRANS
|
||||||
|
default:
|
||||||
|
return XVIDEOS_BASE_URL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (category && query) {
|
||||||
|
return `${XVIDEOS_BASE_URL}/?k=${query}&typef=${XVideosCatQueryMap[category.value as XVideosOrientations]}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return XVIDEOS_BASE_URL
|
||||||
|
|
||||||
|
}
|
|
@ -1,18 +1,15 @@
|
||||||
import { XVIDEOS_BASE_URL } from '@/constants/urls';
|
import { XVIDEOS_BASE_URL } from '@/constants/urls';
|
||||||
import { GalleryData, VideoData } from '@/meta/data';
|
import { FetchParams, GalleryData, VideoData } from '@/meta/data';
|
||||||
|
|
||||||
import axios, { AxiosError } from 'axios';
|
import axios, { AxiosError } from 'axios';
|
||||||
|
|
||||||
import * as cheerio from "cheerio";
|
import * as cheerio from "cheerio";
|
||||||
import { findRelatedVideos, findVideoUrlInsideTagStringByFunctionNameAndExtension } from '../../string';
|
import { getHeaders } from '@/utils/scrape/common/headers';
|
||||||
import { getHeaders } from '../headers';
|
import { Platforms } from '@/meta/settings';
|
||||||
|
|
||||||
interface FetchParams {
|
import { findRelatedVideos, findVideoUrlInsideTagStringByFunctionNameAndExtension } from '@/utils/scrape/common/wgcz';
|
||||||
baseUrl?: string
|
|
||||||
query?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const fetchVideoData = async (videoId: string, params?: FetchParams): Promise<[VideoData, GalleryData[]]> => {
|
export const fetchXvideosVideoData = async (videoId: string, params?: FetchParams): Promise<[VideoData, GalleryData[]]> => {
|
||||||
|
|
||||||
let data: VideoData = {
|
let data: VideoData = {
|
||||||
lowResUrl: ''
|
lowResUrl: ''
|
||||||
|
@ -20,9 +17,11 @@ export const fetchVideoData = async (videoId: string, params?: FetchParams): Pro
|
||||||
|
|
||||||
let related: GalleryData[] = [];
|
let related: GalleryData[] = [];
|
||||||
|
|
||||||
const reqHeaders = getHeaders()
|
const host = XVIDEOS_BASE_URL
|
||||||
|
|
||||||
const queryUrl = `${(params && params.baseUrl) ?? XVIDEOS_BASE_URL}${videoId}`
|
const reqHeaders = getHeaders(host)
|
||||||
|
|
||||||
|
const queryUrl = `${host}${videoId}`
|
||||||
|
|
||||||
await axios.get(queryUrl, reqHeaders)
|
await axios.get(queryUrl, reqHeaders)
|
||||||
|
|
||||||
|
@ -30,7 +29,6 @@ export const fetchVideoData = async (videoId: string, params?: FetchParams): Pro
|
||||||
|
|
||||||
const html = response.data;
|
const html = response.data;
|
||||||
|
|
||||||
|
|
||||||
const $ = cheerio.load(html);
|
const $ = cheerio.load(html);
|
||||||
|
|
||||||
const scriptTags = $("script");
|
const scriptTags = $("script");
|
||||||
|
@ -58,7 +56,7 @@ export const fetchVideoData = async (videoId: string, params?: FetchParams): Pro
|
||||||
|
|
||||||
// populate related gallery
|
// populate related gallery
|
||||||
scriptTags.map((idx, elem) => {
|
scriptTags.map((idx, elem) => {
|
||||||
const relatedVideos = findRelatedVideos($(elem).toString())
|
const relatedVideos = findRelatedVideos($(elem).toString(), Platforms.xvideos)
|
||||||
|
|
||||||
if (relatedVideos) {
|
if (relatedVideos) {
|
||||||
related = relatedVideos
|
related = relatedVideos
|
||||||
|
|
|
@ -1,46 +1,3 @@
|
||||||
import { GalleryData } from "@/meta/data";
|
|
||||||
|
|
||||||
export const findVideoUrlInsideTagStringByFunctionNameAndExtension = (
|
|
||||||
tagBlock: string, functionName: string, extension: string): string | null => {
|
|
||||||
const start = tagBlock.indexOf(`html5player.${functionName}('`) + `html5player.${functionName}('`.length;
|
|
||||||
const end = tagBlock.toString().indexOf("'", start);
|
|
||||||
|
|
||||||
const substr = tagBlock.substring(start, end);
|
|
||||||
|
|
||||||
if (substr.includes(extension)) {
|
|
||||||
return substr
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
export const findRelatedVideos = (tagBlock: string): GalleryData[] | null => {
|
|
||||||
if (!(tagBlock.includes('video_related=['))) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trova l'inizio e la fine dell'array di oggetti nell'input
|
|
||||||
const start = tagBlock.indexOf('[{');
|
|
||||||
const end = tagBlock.lastIndexOf('}]') + 2;
|
|
||||||
|
|
||||||
// Estrai la sottostringa contenente l'array di oggetti
|
|
||||||
const jsonString = tagBlock.substring(start, end);
|
|
||||||
|
|
||||||
// Parsea la stringa JSON in un array di oggetti
|
|
||||||
const videoRelatedArray = JSON.parse(jsonString);
|
|
||||||
|
|
||||||
// Mappa ogni oggetto nell'array per rinominare le chiavi
|
|
||||||
//@ts-ignore
|
|
||||||
const parsedArray = videoRelatedArray.map(obj => ({
|
|
||||||
//@ts-ignore
|
|
||||||
videoUrl: obj.u,
|
|
||||||
imgUrl: obj.i,
|
|
||||||
text: obj.tf
|
|
||||||
}));
|
|
||||||
|
|
||||||
return parsedArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
||||||
|
|