diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..6a9c051
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,5 @@
+node_modules
+.git
+.gitignore
+.next
+package-lock.json
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index cda677f..560fed2 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,23 +1,21 @@
-# Usa la versione alpine più recente di Node.js compatibile con Next.js 14 come base
-FROM node:alpine
+# PHASE 1: copy and build
+
+FROM node:alpine AS build
-# Imposta la directory di lavoro nel container
WORKDIR /app
-# Copia i file esistenti nella root del progetto nel container
COPY . .
-# Rimuovi la cartella node_modules (se presente)
-RUN rm -rf node_modules
+RUN rm -rf node_modules && npm install && npm run build
-# Installa le dipendenze
-RUN npm install
+# PHASE 2: prepare for exec
-# Esegui la build del progetto
-RUN npm run build
+FROM node:alpine AS exec
+
+WORKDIR /app
+
+COPY --from=build /app/. .
-# Esponi la porta 3000
EXPOSE 3000
-# Avvia il server in modalità di produzione
CMD ["npm", "run", "start"]
\ No newline at end of file
diff --git a/README.md b/README.md
index e7473c5..2303af9 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,12 @@
# 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
@@ -29,11 +35,6 @@ npm run start
```
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
If you want to edit the project you can start development mode by opening root folder via console and running:
diff --git a/locale/en.json b/locale/en.json
index 96989e4..21888ac 100644
--- a/locale/en.json
+++ b/locale/en.json
@@ -1,7 +1,7 @@
{
"Header": {
"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_1": "Platform capitalism makes money on desire flow. Proxies avoid this to happen.",
"disclaimer_2": "Platform capitalism is narcissism-driven",
@@ -22,7 +22,10 @@
"Settings": {
"lang_title": "Please select language",
"lang_it": "Italian",
- "lang_en": "English"
+ "lang_en": "English",
+ "settings_title": "Settings",
+ "platform_title": "Platform:",
+ "orientation_title": "Orientation:"
},
"Results": {
"query": "Search results for: {{ query }}",
diff --git a/locale/it.json b/locale/it.json
index 129662a..4283216 100644
--- a/locale/it.json
+++ b/locale/it.json
@@ -1,7 +1,7 @@
{
"Header": {
"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_1": "Le piattaforme monetizzano i flussi di desiderio. I proxy impediscono che questo accada.",
"disclaimer_2": "Le piattaforme si alimentano del narcisisismo degli utenti.",
@@ -22,7 +22,10 @@
"Settings": {
"lang_title": "Seleziona la lingua",
"lang_it": "Italiano",
- "lang_en": "Inglese"
+ "lang_en": "Inglese",
+ "settings_title": "Impostazioni",
+ "platform_title": "Piattaforma:",
+ "orientation_title": "Orientamento:"
},
"Results": {
"query": "Risultati della ricerca per: {{ query }}",
diff --git a/package-lock.json b/package-lock.json
index 4e527c8..9d6ffc9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "proxyraye-next",
- "version": "0.1.0",
+ "version": "0.2.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "proxyraye-next",
- "version": "0.1.0",
+ "version": "0.2.0",
"dependencies": {
"@picocss/pico": "^2.0.6",
"@reduxjs/toolkit": "^2.2.3",
@@ -17,6 +17,7 @@
"next-intl": "^3.11.3",
"next-nprogress-bar": "^2.3.11",
"react": "^18",
+ "react-cookie": "^7.1.4",
"react-dom": "^18",
"react-icons": "^5.1.0",
"react-image": "^4.1.0",
@@ -519,6 +520,20 @@
"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": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
@@ -537,14 +552,12 @@
"node_modules/@types/prop-types": {
"version": "15.7.12",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
- "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==",
- "devOptional": true
+ "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q=="
},
"node_modules/@types/react": {
"version": "18.2.79",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.79.tgz",
"integrity": "sha512-RwGAGXPl9kSXwdNTafkOEuFrTBD5SA2B3iEB96xi8+xu5ddUa/cpvyVCSNn+asgLCTHkb5ZxN8gbuibYJi4s1w==",
- "devOptional": true,
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.0.2"
@@ -1339,6 +1352,14 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"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": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -1382,8 +1403,7 @@
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
- "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "devOptional": true
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
},
"node_modules/damerau-levenshtein": {
"version": "1.0.8",
@@ -2711,6 +2731,14 @@
"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": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
@@ -4087,6 +4115,19 @@
"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": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
@@ -4120,8 +4161,7 @@
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
- "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
- "dev": true
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/react-redux": {
"version": "9.1.1",
@@ -4964,6 +5004,15 @@
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"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": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
diff --git a/package.json b/package.json
index 71e001e..014efba 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "proxyraye-next",
- "version": "0.1.0",
+ "version": "0.2.0",
"private": true,
"scripts": {
"dev": "next dev",
@@ -18,6 +18,7 @@
"next-intl": "^3.11.3",
"next-nprogress-bar": "^2.3.11",
"react": "^18",
+ "react-cookie": "^7.1.4",
"react-dom": "^18",
"react-icons": "^5.1.0",
"react-image": "^4.1.0",
diff --git a/public/android-chrome-192x192.png b/public/android-chrome-192x192.png
index 93ec75e..af9c633 100644
Binary files a/public/android-chrome-192x192.png and b/public/android-chrome-192x192.png differ
diff --git a/public/android-chrome-512x512.png b/public/android-chrome-512x512.png
index 65bf59f..33832d1 100644
Binary files a/public/android-chrome-512x512.png and b/public/android-chrome-512x512.png differ
diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png
index 44380a0..8c6e2c5 100644
Binary files a/public/apple-touch-icon.png and b/public/apple-touch-icon.png differ
diff --git a/public/favicon-16x16.png b/public/favicon-16x16.png
index e58304f..57d230d 100644
Binary files a/public/favicon-16x16.png and b/public/favicon-16x16.png differ
diff --git a/public/favicon-32x32.png b/public/favicon-32x32.png
index 07643ea..f2220df 100644
Binary files a/public/favicon-32x32.png and b/public/favicon-32x32.png differ
diff --git a/public/favicon.ico b/public/favicon.ico
index b7024dc..4c02e55 100644
Binary files a/public/favicon.ico and b/public/favicon.ico differ
diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx
index 0fd507b..304cf5b 100644
--- a/src/app/[locale]/layout.tsx
+++ b/src/app/[locale]/layout.tsx
@@ -1,8 +1,11 @@
-'use client'
-
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({
children,
@@ -14,9 +17,7 @@ export default function RootLayout({
return (
<>
-
- {children}
-
+ {children}
>
);
}
diff --git a/src/app/[locale]/page.tsx b/src/app/[locale]/page.tsx
index 53adf1e..21cfbfc 100644
--- a/src/app/[locale]/page.tsx
+++ b/src/app/[locale]/page.tsx
@@ -1,13 +1,16 @@
import Layout from "@/components/Layout";
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() {
-
- const data = await fetchGalleryData()
+ const platform = await getPlatformCookie()
+
+ const data = await new VideoAgent(platform).getGallery()
+
return (
diff --git a/src/app/[locale]/search/[query]/page.tsx b/src/app/[locale]/search/[query]/page.tsx
index 520d0bc..b0dd7da 100644
--- a/src/app/[locale]/search/[query]/page.tsx
+++ b/src/app/[locale]/search/[query]/page.tsx
@@ -2,13 +2,16 @@ import Layout from "@/components/Layout";
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 } }) {
- const data = await fetchGalleryData({ query: params.query })
+ const platform = await getPlatformCookie()
+
+ const data = await new VideoAgent(platform).getGallery({ query: params.query })
return
-
+
}
\ No newline at end of file
diff --git a/src/app/[locale]/video/[id]/page.tsx b/src/app/[locale]/video/[id]/page.tsx
deleted file mode 100644
index 26d9593..0000000
--- a/src/app/[locale]/video/[id]/page.tsx
+++ /dev/null
@@ -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
-
-}
\ No newline at end of file
diff --git a/src/app/[locale]/video/[platform]/[id]/page.tsx b/src/app/[locale]/video/[platform]/[id]/page.tsx
new file mode 100644
index 0000000..38c0abe
--- /dev/null
+++ b/src/app/[locale]/video/[platform]/[id]/page.tsx
@@ -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
+
+}
\ No newline at end of file
diff --git a/src/components/Layout/Content/Body/index.tsx b/src/components/Layout/Content/Body/index.tsx
new file mode 100644
index 0000000..ab25981
--- /dev/null
+++ b/src/components/Layout/Content/Body/index.tsx
@@ -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) => {
+
+ const { theme, children } = props;
+
+ return (
+
+ {children}
+
+
+ );
+};
+
+export default Body;
\ No newline at end of file
diff --git a/src/components/Layout/Content/index.tsx b/src/components/Layout/Content/index.tsx
new file mode 100644
index 0000000..bf06d57
--- /dev/null
+++ b/src/components/Layout/Content/index.tsx
@@ -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) => {
+
+ const { theme, children } = props;
+
+ return (
+
+
+
+
+
+ {children}
+
+ );
+};
+
+export default Content;
\ No newline at end of file
diff --git a/src/components/Layout/Header/Menu/Repo/index.tsx b/src/components/Layout/Header/Menu/Repo/index.tsx
index 64ee07a..c049730 100644
--- a/src/components/Layout/Header/Menu/Repo/index.tsx
+++ b/src/components/Layout/Header/Menu/Repo/index.tsx
@@ -2,17 +2,17 @@
import React from 'react';
-import { SiGitea } from "react-icons/si";
+import { SiCodeberg } from "react-icons/si";
import Icon from '../Icon';
-import { REPO_GITEA_URL } from '@/constants/repo';
+import { REPO_CODEBERG_URL } from '@/constants/repo';
const Menu: React.FC = () => {
return (
- {window.location.href = REPO_GITEA_URL;}}>
- {}
+ {window.location.href = REPO_CODEBERG_URL;}}>
+ {}
);
};
diff --git a/src/components/Layout/Header/Menu/Settings/Modal/Modal.module.scss b/src/components/Layout/Header/Menu/Settings/Modal/Modal.module.scss
new file mode 100644
index 0000000..0df5801
--- /dev/null
+++ b/src/components/Layout/Header/Menu/Settings/Modal/Modal.module.scss
@@ -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;
+ }
+}
+
diff --git a/src/components/Layout/Header/Menu/Settings/Modal/Orientation/Orientation.module.scss b/src/components/Layout/Header/Menu/Settings/Modal/Orientation/Orientation.module.scss
new file mode 100644
index 0000000..cc534dd
--- /dev/null
+++ b/src/components/Layout/Header/Menu/Settings/Modal/Orientation/Orientation.module.scss
@@ -0,0 +1,11 @@
+@import 'fontsize';
+@import 'spacing';
+
+
+.container {
+
+ .title {
+ margin-bottom: $spacing_16;
+ }
+
+}
\ No newline at end of file
diff --git a/src/components/Layout/Header/Menu/Settings/Modal/Orientation/index.tsx b/src/components/Layout/Header/Menu/Settings/Modal/Orientation/index.tsx
new file mode 100644
index 0000000..20d03db
--- /dev/null
+++ b/src/components/Layout/Header/Menu/Settings/Modal/Orientation/index.tsx
@@ -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) => {
+
+ const { labels, handleClose } = props
+
+ const [cookies] = useCookies([Cookies.orientation]);
+
+ const handleChange = async (event: React.ChangeEvent) => {
+ const value = event.target.value;
+
+ await setCookie(Cookies.orientation, value)
+
+ handleClose()
+ }
+
+ return (
+
+
{labels.title}
+
+
+ );
+};
+
+export default Orientation;
\ No newline at end of file
diff --git a/src/components/Layout/Header/Menu/Settings/Modal/Platform/Platform.module.scss b/src/components/Layout/Header/Menu/Settings/Modal/Platform/Platform.module.scss
new file mode 100644
index 0000000..cc534dd
--- /dev/null
+++ b/src/components/Layout/Header/Menu/Settings/Modal/Platform/Platform.module.scss
@@ -0,0 +1,11 @@
+@import 'fontsize';
+@import 'spacing';
+
+
+.container {
+
+ .title {
+ margin-bottom: $spacing_16;
+ }
+
+}
\ No newline at end of file
diff --git a/src/components/Layout/Header/Menu/Settings/Modal/Platform/index.tsx b/src/components/Layout/Header/Menu/Settings/Modal/Platform/index.tsx
new file mode 100644
index 0000000..dae4b7e
--- /dev/null
+++ b/src/components/Layout/Header/Menu/Settings/Modal/Platform/index.tsx
@@ -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) => {
+
+ const { labels, handleClose } = props
+
+ const [cookies] = useCookies([Cookies.platform]);
+
+ const handleChange = async (event: React.ChangeEvent) => {
+ const value = event.target.value;
+
+ await setCookie(Cookies.platform, value)
+
+ handleClose()
+ }
+
+ return (
+
+
{labels.title}
+
+
+ );
+};
+
+export default Platform;
\ No newline at end of file
diff --git a/src/components/Layout/Header/Menu/Settings/Modal/index.tsx b/src/components/Layout/Header/Menu/Settings/Modal/index.tsx
new file mode 100644
index 0000000..0a2c17a
--- /dev/null
+++ b/src/components/Layout/Header/Menu/Settings/Modal/index.tsx
@@ -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) => {
+
+ const { labels, handleClose } = props
+
+ return (
+
+
+ );
+};
+
+export default LangSwitcher;
\ No newline at end of file
diff --git a/src/components/Layout/Header/Menu/Settings/index.tsx b/src/components/Layout/Header/Menu/Settings/index.tsx
new file mode 100644
index 0000000..407df7b
--- /dev/null
+++ b/src/components/Layout/Header/Menu/Settings/index.tsx
@@ -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) => {
+
+ const { labels } = props
+
+ const [showModal, setShowModal] = useState(false)
+
+ return (
+ <>
+ setShowModal(true)}>
+ {}
+
+
+ {showModal && setShowModal(false)} labels={labels} />}
+ >
+ );
+};
+
+export default Settings;
\ No newline at end of file
diff --git a/src/components/Layout/Header/Menu/Theme/index.tsx b/src/components/Layout/Header/Menu/Theme/index.tsx
index f3ffabd..94bcbca 100644
--- a/src/components/Layout/Header/Menu/Theme/index.tsx
+++ b/src/components/Layout/Header/Menu/Theme/index.tsx
@@ -3,23 +3,29 @@
import React from 'react';
import { TbMoon, TbSun } from 'react-icons/tb';
-import { useAppDispatch, useAppSelector } from '@/store/store';
-import { Themes } from '@/meta/settings';
-import { setCurrentTheme } from '@/store/settingsSlice';
+import { Cookies, DEFAULT_THEME, Themes } from '@/meta/settings';
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 dispatch = useAppDispatch();
+ const [cookies] = useCookies([Cookies.theme]);
+
+ const theme = cookies.theme ?? DEFAULT_THEME
+
+ const handleClick = async () => {
+ const newTheme = theme == Themes.dark ? Themes.light : Themes.dark
+ await setCookie(Cookies.theme, newTheme)
+ }
return (
- dispatch(setCurrentTheme(theme == Themes.dark ? Themes.light : Themes.dark))}>
+
{theme == Themes.dark && }
{theme == Themes.light && }
);
};
-export default Menu;
\ No newline at end of file
+export default Theme;
\ No newline at end of file
diff --git a/src/components/Layout/Header/Menu/index.tsx b/src/components/Layout/Header/Menu/index.tsx
index 51cf79a..563bb6c 100644
--- a/src/components/Layout/Header/Menu/index.tsx
+++ b/src/components/Layout/Header/Menu/index.tsx
@@ -8,6 +8,7 @@ import Theme from './Theme';
import Repo from './Repo';
import Language from './Language';
import { LangOption } from '@/meta/settings';
+import Settings from './Settings';
const Menu: React.FC = () => {
@@ -23,11 +24,22 @@ const Menu: React.FC = () => {
langs: options
}
+ const settingsLabels = {
+ title: t('settings_title'),
+ platform: {
+ title: t('platform_title')
+ },
+ orientation: {
+ title: t('orientation_title')
+ }
+ }
+
return (
+
);
};
diff --git a/src/components/Layout/Header/Title/index.tsx b/src/components/Layout/Header/Title/index.tsx
index a3bdb25..ade98c8 100644
--- a/src/components/Layout/Header/Title/index.tsx
+++ b/src/components/Layout/Header/Title/index.tsx
@@ -2,15 +2,17 @@ import React from 'react';
import style from './Title.module.scss'
-import {useTranslations} from 'next-intl';
+import { useLocale, useTranslations } from 'next-intl';
+
import Link from 'next/link';
const Title: React.FC = () => {
const t = useTranslations('Header');
+ const locale = useLocale()
return (
- {t('title')}
+ {t('title')}
);
};
diff --git a/src/components/Layout/Results/Wrapper/Gallery/Thumbnail/index.tsx b/src/components/Layout/Results/Wrapper/Gallery/Thumbnail/index.tsx
index 0dfa939..acc71c0 100644
--- a/src/components/Layout/Results/Wrapper/Gallery/Thumbnail/index.tsx
+++ b/src/components/Layout/Results/Wrapper/Gallery/Thumbnail/index.tsx
@@ -9,25 +9,28 @@ import classNames from 'classnames';
import Link from 'next/link'
import style from './Thumbnail.module.scss'
+
import { encodeVideoUrlPath } from '@/utils/string';
+import { Platforms } from '@/meta/settings';
interface Props {
locale: string
videoUrl: string
imgUrl: string
+ platform: Platforms
text: string
show: boolean
}
const Thumbnail: React.FC = (props) => {
- const { locale, videoUrl, imgUrl, text, show } = props
+ const { locale, platform, videoUrl, imgUrl, text, show } = props
const encodedUri = encodeVideoUrlPath(videoUrl)
return (
-
+
} />
{text}
diff --git a/src/components/Layout/Results/Wrapper/Gallery/index.tsx b/src/components/Layout/Results/Wrapper/Gallery/index.tsx
index 7953758..63d8b77 100644
--- a/src/components/Layout/Results/Wrapper/Gallery/index.tsx
+++ b/src/components/Layout/Results/Wrapper/Gallery/index.tsx
@@ -27,6 +27,7 @@ const Gallery: React.FC = (props) => {
key={key}
imgUrl={elem.imgUrl}
videoUrl={elem.videoUrl}
+ platform={elem.platform}
text={elem.text}
locale={locale} />
})}
diff --git a/src/components/Layout/index.tsx b/src/components/Layout/index.tsx
index 8ce895a..26baba8 100644
--- a/src/components/Layout/index.tsx
+++ b/src/components/Layout/index.tsx
@@ -1,38 +1,25 @@
-'use client'
-
import React from 'react';
-import Head from 'next/head';
+import { cookies } from 'next/headers'
-import { AppProgressBar as ProgressBar } from 'next-nprogress-bar';
-
-import { useAppSelector } from '@/store/store';
-import { LAYOUT_COLORS_PINK, LAYOUT_COLORS_YELLOW } from '@/constants/colors';
-import { Themes } from '@/meta/settings';
+import WithRedux from '@/store/withRedux';
+import Content from './Content';
+import { DEFAULT_THEME, Themes } from '@/meta/settings';
const Layout: React.FC = (props) => {
const { children } = props;
- const theme = useAppSelector((state) => state.settings.theme);
+ const cookieStore = cookies()
+
+ const theme = cookieStore.get('theme')
return (
-
-
-
-
- Proxy Raye: watch porn videos without tracking or annoying ads!
-
-
- {children}
-
-
-
+
+
+ {children}
+
+
);
};
diff --git a/src/constants/repo.ts b/src/constants/repo.ts
index 60e3321..1634600 100644
--- a/src/constants/repo.ts
+++ b/src/constants/repo.ts
@@ -1 +1,2 @@
-export const REPO_GITEA_URL = 'https://git.lamacchinadesiderante.org/lamacchinadesiderante/proxyraye-nextjs'
\ No newline at end of file
+export const REPO_GITEA_URL = 'https://git.lamacchinadesiderante.org/lamacchinadesiderante/proxyraye-nextjs'
+export const REPO_CODEBERG_URL = 'https://codeberg.org/lamacchinadesiderante/proxyraye'
\ No newline at end of file
diff --git a/src/constants/urls.ts b/src/constants/urls.ts
index 87953cd..fd3f981 100644
--- a/src/constants/urls.ts
+++ b/src/constants/urls.ts
@@ -1 +1,11 @@
-export const XVIDEOS_BASE_URL: string = "https://www.xvideos.com"
\ No newline at end of file
+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'
\ No newline at end of file
diff --git a/src/meta/data.ts b/src/meta/data.ts
index f8bc93e..80ca26e 100644
--- a/src/meta/data.ts
+++ b/src/meta/data.ts
@@ -1,11 +1,24 @@
+import { Platforms } from "./settings"
+
+export interface FetchParams {
+ baseUrl?: string
+ query?: string
+}
+
export interface GalleryData {
videoUrl: string
imgUrl: string
text: string
+ platform: Platforms
}
export interface VideoData {
lowResUrl: string,
hiResUrl?: string,
hlsUrl?: string
+}
+
+export interface VideoAgent {
+ getGallery(params?: FetchParams): Promise
+ getVideo(id: string, params?: FetchParams): Promise<[VideoData, GalleryData[]]>
}
\ No newline at end of file
diff --git a/src/meta/settings.ts b/src/meta/settings.ts
index 375d1c1..1b8811b 100644
--- a/src/meta/settings.ts
+++ b/src/meta/settings.ts
@@ -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 {
light= 'light',
- dark= 'dark'
+ dark= 'dark',
}
+export const DEFAULT_THEME = Themes.light
+export const DEFAULT_PLATFORM = Platforms.xvideos
+
export interface LangOption {
label: string;
code: string;
diff --git a/src/store/store.ts b/src/store/store.ts
index 1d6befc..fd9804c 100644
--- a/src/store/store.ts
+++ b/src/store/store.ts
@@ -4,7 +4,24 @@ import { useDispatch, TypedUseSelectorHook, useSelector } from "react-redux";
import { settingsReducer } from "@/store/settingsSlice";
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 = {
key: "settings",
diff --git a/src/store/withRedux.tsx b/src/store/withRedux.tsx
new file mode 100644
index 0000000..d65bc68
--- /dev/null
+++ b/src/store/withRedux.tsx
@@ -0,0 +1,18 @@
+'use client'
+
+import ReduxProvider from "@/store/redux-provider";
+
+export default function WithRedux({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+
+ return (
+ <>
+
+ {children}
+
+ >
+ );
+}
diff --git a/src/utils/agent.ts b/src/utils/agent.ts
new file mode 100644
index 0000000..32f766a
--- /dev/null
+++ b/src/utils/agent.ts
@@ -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 => {
+ 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)
+ }
+}
\ No newline at end of file
diff --git a/src/utils/cookies/read.ts b/src/utils/cookies/read.ts
new file mode 100644
index 0000000..0d69ef3
--- /dev/null
+++ b/src/utils/cookies/read.ts
@@ -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 {
+ return cookies().has(name)
+}
+
+export async function getPlatformCookie(): Promise {
+ const platformCookie = await getCookie(AppCookies.platform)
+ return platformCookie && platformCookie.value ? platformCookie.value as Platforms : DEFAULT_PLATFORM
+}
\ No newline at end of file
diff --git a/src/utils/cookies/write.ts b/src/utils/cookies/write.ts
new file mode 100644
index 0000000..b93d1b2
--- /dev/null
+++ b/src/utils/cookies/write.ts
@@ -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
+ })
+}
\ No newline at end of file
diff --git a/src/utils/scrape/headers.ts b/src/utils/scrape/common/headers.ts
similarity index 97%
rename from src/utils/scrape/headers.ts
rename to src/utils/scrape/common/headers.ts
index c66bc7a..87287be 100644
--- a/src/utils/scrape/headers.ts
+++ b/src/utils/scrape/common/headers.ts
@@ -1,5 +1,5 @@
import { XVIDEOS_BASE_URL } from "@/constants/urls";
-import { removeHttpS } from "../string";
+import { removeHttpS } from "@/utils/string";
const getRandomUserAgent = (): string => {
diff --git a/src/utils/scrape/common/wgcz.ts b/src/utils/scrape/common/wgcz.ts
new file mode 100644
index 0000000..79f572b
--- /dev/null
+++ b/src/utils/scrape/common/wgcz.ts
@@ -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;
+}
diff --git a/src/utils/scrape/xnxx/agent.ts b/src/utils/scrape/xnxx/agent.ts
new file mode 100644
index 0000000..9006390
--- /dev/null
+++ b/src/utils/scrape/xnxx/agent.ts
@@ -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 => {
+ return await fetchXNXXGalleryData(params)
+ }
+
+ public getVideo = async (id: string, params?: FetchParams): Promise<[VideoData, GalleryData[]]> => {
+ return await fetchXNXXVideoData(id, params)
+ }
+
+}
\ No newline at end of file
diff --git a/src/utils/scrape/xnxx/gallery.ts b/src/utils/scrape/xnxx/gallery.ts
new file mode 100644
index 0000000..3f7fec1
--- /dev/null
+++ b/src/utils/scrape/xnxx/gallery.ts
@@ -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 => {
+
+ 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
+}
\ No newline at end of file
diff --git a/src/utils/scrape/xnxx/url.ts b/src/utils/scrape/xnxx/url.ts
new file mode 100644
index 0000000..cd2e397
--- /dev/null
+++ b/src/utils/scrape/xnxx/url.ts
@@ -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
+
+}
\ No newline at end of file
diff --git a/src/utils/scrape/xnxx/video.ts b/src/utils/scrape/xnxx/video.ts
new file mode 100644
index 0000000..1cf066e
--- /dev/null
+++ b/src/utils/scrape/xnxx/video.ts
@@ -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];
+}
\ No newline at end of file
diff --git a/src/utils/scrape/xvideos/agent.ts b/src/utils/scrape/xvideos/agent.ts
new file mode 100644
index 0000000..c144486
--- /dev/null
+++ b/src/utils/scrape/xvideos/agent.ts
@@ -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 => {
+ return await fetchXVideosGalleryData(params)
+ }
+
+ public getVideo = async (id: string, params?: FetchParams): Promise<[VideoData, GalleryData[]]> => {
+ return await fetchXvideosVideoData(id, params)
+ }
+
+}
\ No newline at end of file
diff --git a/src/utils/scrape/xvideos/gallery.ts b/src/utils/scrape/xvideos/gallery.ts
index 51d617b..2a941e0 100644
--- a/src/utils/scrape/xvideos/gallery.ts
+++ b/src/utils/scrape/xvideos/gallery.ts
@@ -1,22 +1,18 @@
-import { XVIDEOS_BASE_URL } from '@/constants/urls';
-import { GalleryData } from '@/meta/data';
+import { FetchParams, GalleryData } from '@/meta/data';
import axios, { AxiosError } from 'axios';
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 {
- baseUrl?: string
- query?: string
-}
-
-export const fetchGalleryData = async (params?: FetchParams): Promise => {
+export const fetchXVideosGalleryData = async (params?: FetchParams): Promise => {
let data: GalleryData[] = [];
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)
@@ -37,7 +33,8 @@ export const fetchGalleryData = async (params?: FetchParams): Promise {
+
+ 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
+
+}
\ No newline at end of file
diff --git a/src/utils/scrape/xvideos/video.ts b/src/utils/scrape/xvideos/video.ts
index 5c4d04b..c323be1 100644
--- a/src/utils/scrape/xvideos/video.ts
+++ b/src/utils/scrape/xvideos/video.ts
@@ -1,18 +1,15 @@
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 * as cheerio from "cheerio";
-import { findRelatedVideos, findVideoUrlInsideTagStringByFunctionNameAndExtension } from '../../string';
-import { getHeaders } from '../headers';
+import { getHeaders } from '@/utils/scrape/common/headers';
+import { Platforms } from '@/meta/settings';
-interface FetchParams {
- baseUrl?: string
- query?: string
-}
+import { findRelatedVideos, findVideoUrlInsideTagStringByFunctionNameAndExtension } from '@/utils/scrape/common/wgcz';
-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 = {
lowResUrl: ''
@@ -20,17 +17,18 @@ export const fetchVideoData = async (videoId: string, params?: FetchParams): Pro
let related: GalleryData[] = [];
- const reqHeaders = getHeaders()
+ const host = XVIDEOS_BASE_URL
- const queryUrl = `${(params && params.baseUrl) ?? XVIDEOS_BASE_URL}${videoId}`
+ const reqHeaders = getHeaders(host)
- await axios.get(queryUrl, reqHeaders)
+ const queryUrl = `${host}${videoId}`
+
+ await axios.get(queryUrl, reqHeaders)
.then(response => {
const html = response.data;
-
const $ = cheerio.load(html);
const scriptTags = $("script");
@@ -58,7 +56,7 @@ export const fetchVideoData = async (videoId: string, params?: FetchParams): Pro
// populate related gallery
scriptTags.map((idx, elem) => {
- const relatedVideos = findRelatedVideos($(elem).toString())
+ const relatedVideos = findRelatedVideos($(elem).toString(), Platforms.xvideos)
if (relatedVideos) {
related = relatedVideos
diff --git a/src/utils/string.ts b/src/utils/string.ts
index a1c0225..3c71166 100644
--- a/src/utils/string.ts
+++ b/src/utils/string.ts
@@ -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 => {
if (url.startsWith("http://")) {
return url.slice(7);