diff --git a/docker-compose.yaml b/docker-compose.yaml index bcc6b79..c60fbcc 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -8,6 +8,13 @@ services: restart: always + healthcheck: + test: wget --no-verbose --tries=1 --spider http://localhost:3000/api/status || exit 1 + interval: 60s + retries: 5 + start_period: 20s + timeout: 10s + build: . ports: diff --git a/locale/en.json b/locale/en.json index c6b4f4a..96989e4 100644 --- a/locale/en.json +++ b/locale/en.json @@ -10,6 +10,11 @@ "disclaimer_5": "No banners or annoying popups. You can jerk off with no hassle!", "disclaimer_6": "You're choosing image over imagination. What if they're not in antithesis?" }, + "NotFound": { + "uh_oh": "Uh Oh...", + "something_wrong": "Something went wrong :|", + "back_to_home": "Back to homepage" + }, "Search": { "placeholder": "categories, pornostars, etc...", "submit": "Search" diff --git a/locale/it.json b/locale/it.json index 96dcac4..129662a 100644 --- a/locale/it.json +++ b/locale/it.json @@ -10,6 +10,11 @@ "disclaimer_5": "Niente banner o popup fastidiosi. Puoi masturbarti in santa pace.", "disclaimer_6": "Stai preferendo l'immagine all'immaginazione. E se immagine e immaginazione non fossero in antitesi?" }, + "NotFound": { + "uh_oh": "Uh Oh...", + "something_wrong": "Qualcosa รจ andato storto :|", + "back_to_home": "Torna alla home" + }, "Search": { "placeholder": "categorie, pornostar, ecc...", "submit": "Cerca" diff --git a/next.config.js b/next.config.js index bb0375e..6d722af 100644 --- a/next.config.js +++ b/next.config.js @@ -1,11 +1,25 @@ const createNextIntlPlugin = require('next-intl/plugin'); - + const withNextIntl = createNextIntlPlugin(); const path = require('path') - + module.exports = withNextIntl({ sassOptions: { includePaths: [path.join(__dirname, 'src/styles')], }, + async headers() { + return [ + { + // matching all API routes + source: "/api/:path*", + headers: [ + { key: "Access-Control-Allow-Credentials", value: "true" }, + { key: "Access-Control-Allow-Origin", value: "*" }, + { key: "Access-Control-Allow-Methods", value: "GET,DELETE,PATCH,POST,PUT" }, + { key: "Access-Control-Allow-Headers", value: "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version" }, + ] + } + ] + } }) \ No newline at end of file diff --git a/public/android-chrome-192x192.png b/public/android-chrome-192x192.png new file mode 100644 index 0000000..93ec75e Binary files /dev/null and b/public/android-chrome-192x192.png differ diff --git a/public/android-chrome-512x512.png b/public/android-chrome-512x512.png new file mode 100644 index 0000000..65bf59f Binary files /dev/null and b/public/android-chrome-512x512.png differ diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000..44380a0 Binary files /dev/null and b/public/apple-touch-icon.png differ diff --git a/public/favicon-16x16.png b/public/favicon-16x16.png new file mode 100644 index 0000000..e58304f Binary files /dev/null and b/public/favicon-16x16.png differ diff --git a/public/favicon-32x32.png b/public/favicon-32x32.png new file mode 100644 index 0000000..07643ea Binary files /dev/null and b/public/favicon-32x32.png differ diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..b7024dc Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/site.webmanifest b/public/site.webmanifest new file mode 100644 index 0000000..45dc8a2 --- /dev/null +++ b/public/site.webmanifest @@ -0,0 +1 @@ +{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file diff --git a/src/app/[locale]/404/page.tsx b/src/app/[locale]/404/page.tsx new file mode 100644 index 0000000..8a1050a --- /dev/null +++ b/src/app/[locale]/404/page.tsx @@ -0,0 +1,11 @@ +import Layout from "@/components/Layout"; +import NotFound from "@/components/Pages/NotFound"; + +export default function NotFoundPage() { + + return ( + + + + ); +} diff --git a/src/app/[locale]/page.tsx b/src/app/[locale]/page.tsx index 3ea0f04..53adf1e 100644 --- a/src/app/[locale]/page.tsx +++ b/src/app/[locale]/page.tsx @@ -1,12 +1,8 @@ -import axios from 'axios'; - -import * as cheerio from "cheerio"; - import Layout from "@/components/Layout"; import Home from "@/components/Pages/Home"; -import { fetchGalleryData } from '@/utils/scrape/gallery'; +import { fetchGalleryData } from '@/utils/scrape/xvideos/gallery'; export default async function HomePage() { diff --git a/src/app/[locale]/search/[query]/page.tsx b/src/app/[locale]/search/[query]/page.tsx index 50a69a9..520d0bc 100644 --- a/src/app/[locale]/search/[query]/page.tsx +++ b/src/app/[locale]/search/[query]/page.tsx @@ -2,7 +2,7 @@ import Layout from "@/components/Layout"; import Search from "@/components/Pages/Search"; -import { fetchGalleryData } from "@/utils/scrape/gallery"; +import { fetchGalleryData } from "@/utils/scrape/xvideos/gallery"; export default async function SearchPage({ params }: { params: { query: string } }) { diff --git a/src/app/[locale]/video/[id]/page.tsx b/src/app/[locale]/video/[id]/page.tsx index 0aba08d..26d9593 100644 --- a/src/app/[locale]/video/[id]/page.tsx +++ b/src/app/[locale]/video/[id]/page.tsx @@ -4,7 +4,9 @@ import Layout from "@/components/Layout"; import Video from "@/components/Pages/Video"; -import { fetchVideoData } from "@/utils/scrape/video"; +import { decodeVideoUrlPath } from '@/utils/string'; + +import { fetchVideoData } from '@/utils/scrape/xvideos/video'; import { useLocale } from 'next-intl'; @@ -12,7 +14,7 @@ export default async function VideoPage({ params }: { params: { id: string } }) const locale = useLocale() - const decodedId = decodeURIComponent(params.id) + const decodedId = decodeVideoUrlPath(params.id) const [data, related] = await fetchVideoData(decodedId) diff --git a/src/app/api/info/route.ts b/src/app/api/info/route.ts new file mode 100644 index 0000000..e5e92d3 --- /dev/null +++ b/src/app/api/info/route.ts @@ -0,0 +1,7 @@ +import { getAppVersion } from '@/utils/info/version' +import { NextResponse } from 'next/server' + +export async function GET() { + const version = await getAppVersion() + return NextResponse.json({ version }) +} \ No newline at end of file diff --git a/src/app/api/status/route.ts b/src/app/api/status/route.ts new file mode 100644 index 0000000..25695fe --- /dev/null +++ b/src/app/api/status/route.ts @@ -0,0 +1,5 @@ +import { NextResponse } from 'next/server' + +export async function GET(request: Request) { + return NextResponse.json({ msg: 'OK' }) +} \ No newline at end of file diff --git a/src/components/Layout/Results/Wrapper/Gallery/Thumbnail/index.tsx b/src/components/Layout/Results/Wrapper/Gallery/Thumbnail/index.tsx index 12ea2ea..0dfa939 100644 --- a/src/components/Layout/Results/Wrapper/Gallery/Thumbnail/index.tsx +++ b/src/components/Layout/Results/Wrapper/Gallery/Thumbnail/index.tsx @@ -9,6 +9,7 @@ import classNames from 'classnames'; import Link from 'next/link' import style from './Thumbnail.module.scss' +import { encodeVideoUrlPath } from '@/utils/string'; interface Props { locale: string @@ -22,7 +23,7 @@ const Thumbnail: React.FC = (props) => { const { locale, videoUrl, imgUrl, text, show } = props - const encodedUri = encodeURIComponent(videoUrl) + const encodedUri = encodeVideoUrlPath(videoUrl) return (
diff --git a/src/components/Layout/SearchBar/SearchBarForm/SearchBarForm.module.scss b/src/components/Layout/SearchBar/SearchBarForm/SearchBarForm.module.scss index b868e96..f5c440c 100644 --- a/src/components/Layout/SearchBar/SearchBarForm/SearchBarForm.module.scss +++ b/src/components/Layout/SearchBar/SearchBarForm/SearchBarForm.module.scss @@ -9,6 +9,7 @@ .query{ flex: 6; margin-right: $spacing_16; + transition: none !important; } .query:hover { @@ -24,6 +25,7 @@ background-color: var(--primary); border-color: var(--primary); color: var(--primary-inverse); + transition: none !important; } .submitBtn:hover { diff --git a/src/components/Layout/index.tsx b/src/components/Layout/index.tsx index ae6c3bd..8ce895a 100644 --- a/src/components/Layout/index.tsx +++ b/src/components/Layout/index.tsx @@ -21,7 +21,7 @@ const Layout: React.FC = (props) => { - Proxy Raye: un proxy per XVideos basato su PornInvidious + Proxy Raye: watch porn videos without tracking or annoying ads!
{children}
diff --git a/src/components/Pages/NotFound/Msg/Msg.module.scss b/src/components/Pages/NotFound/Msg/Msg.module.scss new file mode 100644 index 0000000..dca88b8 --- /dev/null +++ b/src/components/Pages/NotFound/Msg/Msg.module.scss @@ -0,0 +1,13 @@ +@import 'fontsize'; + +.header { + font-size: $font-size-xlarge; +} + +.msg { + font-size: $font-size-large; +} + +.link { + color: var(--primary); +} \ No newline at end of file diff --git a/src/components/Pages/NotFound/Msg/index.tsx b/src/components/Pages/NotFound/Msg/index.tsx new file mode 100644 index 0000000..fa1cf09 --- /dev/null +++ b/src/components/Pages/NotFound/Msg/index.tsx @@ -0,0 +1,22 @@ +import React, { } from 'react'; + +import style from './Msg.module.scss'; + +import { useTranslations } from 'next-intl'; +import Link from 'next/link'; + +const Msg: React.FC = () => { + + const t = useTranslations('NotFound'); + + return ( +
+
{t('uh_oh')}
+

{t('something_wrong')}

+ {t('back_to_home')} +
+ ); + +}; + +export default Msg; \ No newline at end of file diff --git a/src/components/Pages/NotFound/index.tsx b/src/components/Pages/NotFound/index.tsx new file mode 100644 index 0000000..ab79a9d --- /dev/null +++ b/src/components/Pages/NotFound/index.tsx @@ -0,0 +1,18 @@ +import React from 'react'; + +import Header from '@/components/Layout/Header'; +import SearchBar from '@/components/Layout/SearchBar'; +import Msg from './Msg'; + +const NotFound: React.FC = (props) => { + + return ( + <> +
+ + + + ); +}; + +export default NotFound; \ No newline at end of file diff --git a/src/utils/info/version.ts b/src/utils/info/version.ts new file mode 100644 index 0000000..4f76523 --- /dev/null +++ b/src/utils/info/version.ts @@ -0,0 +1,23 @@ +import fs from 'fs/promises'; +import path from 'path'; + +export const getAppVersion = async (): Promise => { + + let version = '' + + try { + + const packageJsonPath = path.resolve(process.cwd(), 'package.json'); + + const data = await fs.readFile(packageJsonPath, 'utf8'); + + const packageJson = JSON.parse(data); + + version = packageJson.version + + } catch (error) { + // handle error + } + + return version +} diff --git a/src/utils/scrape/headers.ts b/src/utils/scrape/headers.ts new file mode 100644 index 0000000..c66bc7a --- /dev/null +++ b/src/utils/scrape/headers.ts @@ -0,0 +1,38 @@ +import { XVIDEOS_BASE_URL } from "@/constants/urls"; +import { removeHttpS } from "../string"; + +const getRandomUserAgent = (): string => { + + const userAgents: string[] = [ + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_14) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.5397.215 Safari/537.36', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.2420.81', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 OPR/109.0.0.0', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14.4; rv:124.0) Gecko/20100101 Firefox/124.0', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_4_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4.1 Safari/605.1.15', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_4_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 OPR/109.0.0.0', + 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', + 'Mozilla/5.0 (X11; Linux i686; rv:124.0) Gecko/20100101 Firefox/124.0' + ]; + + const rand = Math.floor(Math.random() * userAgents.length); + + return userAgents[rand] +} + +export const getHeaders = (host:string = XVIDEOS_BASE_URL) => { + return { + headers: { + "User-Agent": getRandomUserAgent(), + "Accept-Language": "en-gb, en, en-US, it", + "Accept-Encoding": "gzip, deflate, br", + "Connection": "keep-alive", + "Sec-Fetch-Dest": "document", + "Sec-Fetch-Mode": "navigate", + "Sec-Fetch-Site": "none", + "Host": removeHttpS(host) + }, + } +}; \ No newline at end of file diff --git a/src/utils/scrape/gallery.ts b/src/utils/scrape/xvideos/gallery.ts similarity index 82% rename from src/utils/scrape/gallery.ts rename to src/utils/scrape/xvideos/gallery.ts index a53aec7..51d617b 100644 --- a/src/utils/scrape/gallery.ts +++ b/src/utils/scrape/xvideos/gallery.ts @@ -1,8 +1,9 @@ import { XVIDEOS_BASE_URL } from '@/constants/urls'; -import { GalleryData, VideoData } from '@/meta/data'; +import { GalleryData } from '@/meta/data'; import axios, { AxiosError } from 'axios'; import * as cheerio from "cheerio"; +import { getHeaders } from '../headers'; interface FetchParams { baseUrl?: string @@ -13,11 +14,7 @@ export const fetchGalleryData = async (params?: FetchParams): Promise { - const start = tagBlock.indexOf(`html5player.${functionName}('`) + `html5player.${functionName}('`.length; - const end = tagBlock.toString().indexOf("'", start); + 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); + const substr = tagBlock.substring(start, end); - if (substr.includes(extension)) { - return substr - } + if (substr.includes(extension)) { + return substr + } - return null + return null } -export const findRelatedVideos = (tagBlock: string): GalleryData[]|null => { +export const findRelatedVideos = (tagBlock: string): GalleryData[] | null => { if (!(tagBlock.includes('video_related=['))) { return null } @@ -22,13 +22,13 @@ export const findRelatedVideos = (tagBlock: string): GalleryData[]|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 => ({ @@ -37,6 +37,23 @@ export const findRelatedVideos = (tagBlock: string): GalleryData[]|null => { imgUrl: obj.i, text: obj.tf })); - + return parsedArray; -} \ No newline at end of file +} + +export const removeHttpS = (url: string): string => { + if (url.startsWith("http://")) { + return url.slice(7); + } else if (url.startsWith("https://")) { + return url.slice(8); + } + return url; +}; + +export const encodeVideoUrlPath = (input: string): string => { + return encodeURIComponent(input.replace(/^\/+/, '')) +}; + +export const decodeVideoUrlPath = (input: string): string => { + return `/${decodeURIComponent(input)}`; +}; \ No newline at end of file