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 ( + +
+
+
{labels.title}
+
{ handleClose() }}>
+
+
+ + +
+
+
+ + ); +}; + +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);