├── .gitignore ├── Archive └── Web Version │ ├── BACKEND │ ├── package-lock.json │ ├── package.json │ ├── server.js │ ├── server2.js │ └── server3.js │ ├── FRONTEND │ ├── .eslintrc.json │ ├── .gitattributes │ ├── .github │ │ └── workflows │ │ │ └── deploy.yml │ ├── .prettierrc │ ├── README.md │ ├── index.html │ ├── jsconfig.json │ ├── package-lock.json │ ├── package.json │ ├── postcss.config.js │ ├── public │ │ ├── 404.html │ │ ├── vite.svg │ │ ├── zenshinLogoSmall.png │ │ └── zenshinLogoSmall2.png │ ├── src │ │ ├── App.jsx │ │ ├── assets │ │ │ ├── anime.gif │ │ │ ├── gradient1.jpg │ │ │ ├── loundraw.jpg │ │ │ ├── psyduck.png │ │ │ ├── zenshin1.png │ │ │ ├── zenshin2.png │ │ │ ├── zenshinLogo.png │ │ │ └── zenshinLogoLowPx.png │ │ ├── components │ │ │ ├── AnilistAuthCallback.jsx │ │ │ ├── AnimeCard.jsx │ │ │ ├── Episode.jsx │ │ │ ├── EpisodesPlayer.jsx │ │ │ ├── GlobalActivityCard.jsx │ │ │ ├── Header.jsx │ │ │ ├── NewReleaseCard.jsx │ │ │ ├── RecentActivity.jsx │ │ │ ├── RecentActivityCard.jsx │ │ │ ├── SearchBar.jsx │ │ │ ├── SearchResults.jsx │ │ │ ├── StreamStats.jsx │ │ │ └── StreamStatsEpisode.jsx │ │ ├── hooks │ │ │ ├── useGetAniZipMappings.js │ │ │ ├── useGetAnilistProfile.js │ │ │ ├── useGetAnimeById.js │ │ │ ├── useGetAnimeByMalId.js │ │ │ ├── useGetAnimeEpisodesById.js │ │ │ ├── useGetMultipleAnilistIds.js │ │ │ ├── useGetNewReleases.js │ │ │ ├── useGetRecentGlobalActivity.js │ │ │ ├── useGetTopAnime.js │ │ │ ├── useGetToshoEpisodes.js │ │ │ ├── useNyaaTracker.js │ │ │ └── useTopAiringAnime.js │ │ ├── index.css │ │ ├── main.jsx │ │ ├── pages │ │ │ ├── AnimePage.jsx │ │ │ ├── ErrorPage.jsx │ │ │ ├── Home.jsx │ │ │ ├── NewReleases.jsx │ │ │ ├── Player.jsx │ │ │ ├── PlayerOld.txt │ │ │ └── VideoJs.jsx │ │ ├── skeletons │ │ │ └── SkeletonAnimeCard.jsx │ │ ├── ui │ │ │ ├── AppLayout.jsx │ │ │ ├── CenteredLoader.jsx │ │ │ ├── ErrorElement.jsx │ │ │ └── Loader.jsx │ │ └── utils │ │ │ ├── ContextProvider.jsx │ │ │ ├── api.js │ │ │ ├── auth.js │ │ │ ├── currentSeason.js │ │ │ ├── formatBytes.js │ │ │ ├── helper.js │ │ │ └── nFormatter.js │ ├── src_mal │ │ ├── App.jsx │ │ ├── assets │ │ │ ├── anime.gif │ │ │ ├── psyduck.png │ │ │ ├── zenshin1.png │ │ │ ├── zenshin2.png │ │ │ └── zenshinLogo.png │ │ ├── components │ │ │ ├── AnimeCard.jsx │ │ │ ├── Episode.jsx │ │ │ ├── Header.jsx │ │ │ ├── SearchBar.jsx │ │ │ ├── SearchResults.jsx │ │ │ └── StreamStats.jsx │ │ ├── hooks │ │ │ ├── useGetAnimeById.js │ │ │ ├── useGetAnimeEpisodesById.js │ │ │ ├── useGetTopAnime.js │ │ │ ├── useNyaaTracker.js │ │ │ └── useTopAiringAnime.js │ │ ├── index.css │ │ ├── main.jsx │ │ ├── pages │ │ │ ├── AnimePage.jsx │ │ │ ├── ErrorPage.jsx │ │ │ ├── Home.jsx │ │ │ ├── Player.jsx │ │ │ ├── PlayerOld.txt │ │ │ └── VideoJs.jsx │ │ ├── ui │ │ │ ├── AppLayout.jsx │ │ │ ├── CenteredLoader.jsx │ │ │ ├── ErrorElement.jsx │ │ │ └── Loader.jsx │ │ └── utils │ │ │ ├── api.js │ │ │ └── helper.js │ ├── tailwind.config.js │ └── vite.config.js │ ├── Releases │ └── v1.0.0 │ │ └── zenshin v1.0.0.zip │ └── readme.md ├── Electron └── zenshin-electron │ ├── .editorconfig │ ├── .eslintignore │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── .prettierignore │ ├── .prettierrc.yaml │ ├── .vscode │ ├── extensions.json │ ├── launch.json │ └── settings.json │ ├── README.md │ ├── build │ ├── entitlements.mac.plist │ ├── icon.ico │ ├── icon.png │ ├── icon2.icns │ ├── iconOld.ico │ └── iconOld.png │ ├── common │ ├── announce.js │ ├── unpacker.js │ └── utils.js │ ├── electron-builder.yml │ ├── electron.vite.config.mjs │ ├── package-lock.json │ ├── package.json │ ├── postcss.config.js │ ├── resources │ ├── icon.ico │ └── iconOld.png │ ├── src │ ├── main │ │ ├── animepahe │ │ │ ├── middlewares │ │ │ │ └── cookies.js │ │ │ └── routes │ │ │ │ └── search.js │ │ ├── index copy.js │ │ ├── index.js │ │ ├── server.mjs │ │ └── settings.js │ ├── preload │ │ └── index.js │ └── renderer │ │ ├── index.html │ │ ├── public │ │ ├── 404.html │ │ ├── vite.svg │ │ ├── zenshinLogoSmall.png │ │ └── zenshinLogoSmall2.png │ │ ├── src │ │ ├── App.jsx │ │ ├── assets │ │ │ ├── anime.gif │ │ │ ├── gradient1.jpg │ │ │ ├── loundraw.jpg │ │ │ ├── pikacon.ico │ │ │ ├── psyduck.png │ │ │ ├── symbols │ │ │ │ ├── AniListLogo.jsx │ │ │ │ ├── BooksLogo.jsx │ │ │ │ ├── CassetteTapeLogo.jsx │ │ │ │ ├── EyeLogo.jsx │ │ │ │ ├── LeafLogo.jsx │ │ │ │ ├── MountainsLogo.jsx │ │ │ │ ├── MyAnimeListLogo.jsx │ │ │ │ ├── SortDescLogo.jsx │ │ │ │ ├── TVLogo.jsx │ │ │ │ └── YouTubeLogo.jsx │ │ │ ├── zenshin1.png │ │ │ ├── zenshin2.png │ │ │ ├── zenshinLogo.png │ │ │ └── zenshinLogoLowPx.png │ │ ├── components │ │ │ ├── AnilistAuthCallback.jsx │ │ │ ├── AnilistEditorModal.jsx │ │ │ ├── AnimeCard copy.jsx │ │ │ ├── AnimeCard.jsx │ │ │ ├── CurrentlyWatchingCard.jsx │ │ │ ├── DownloadMeter.jsx │ │ │ ├── Episode.jsx │ │ │ ├── EpisodesPlayer.jsx │ │ │ ├── Header.jsx │ │ │ ├── NewReleaseCard.jsx │ │ │ ├── Pagination.jsx │ │ │ ├── RecentActivity.jsx │ │ │ ├── RecentActivityCard.jsx │ │ │ ├── SearchBar.jsx │ │ │ ├── SearchResults.jsx │ │ │ ├── Sidebar.jsx │ │ │ ├── StreamStats.jsx │ │ │ └── StreamStatsEpisode.jsx │ │ ├── extensions │ │ │ └── animepahe │ │ │ │ ├── components │ │ │ │ ├── AnimePaheEpisode.jsx │ │ │ │ ├── AnimePaheSearchBar.jsx │ │ │ │ ├── AnimePaheSearchResults.jsx │ │ │ │ └── AnimepaheEpisodeCard.jsx │ │ │ │ ├── hooks │ │ │ │ ├── useGetAnimeAnimePahe.js │ │ │ │ ├── useGetAnimePaheEps.js │ │ │ │ ├── useGetAnimePaheEpsOfPage.js │ │ │ │ ├── useGetAnimePahePlayData.js │ │ │ │ └── useGetAnimePaheSearch.js │ │ │ │ ├── pages │ │ │ │ ├── AnimePahe.jsx │ │ │ │ ├── AnimePahePage.jsx │ │ │ │ ├── AnimePahePlayer.jsx │ │ │ │ └── AnimePahePlayerEmbedded.jsx │ │ │ │ ├── utils.js │ │ │ │ └── utils │ │ │ │ └── parseAnimepaheImage.js │ │ ├── hooks │ │ │ ├── useGetAniZipMappings.js │ │ │ ├── useGetAnilistProfile.js │ │ │ ├── useGetAnilistSearch.js │ │ │ ├── useGetAnimeById.js │ │ │ ├── useGetAnimeByMalId.js │ │ │ ├── useGetAnimeEpisodesById.js │ │ │ ├── useGetAnimepaheReleases.js │ │ │ ├── useGetMultipleAnilistIds.js │ │ │ ├── useGetNewReleases.js │ │ │ ├── useGetRecentGlobalActivity.js │ │ │ ├── useGetTopAnime.js │ │ │ ├── useGetToshoEpisodes.js │ │ │ ├── useNyaaTracker.js │ │ │ └── useTopAiringAnime.js │ │ ├── index.css │ │ ├── main.jsx │ │ ├── pages │ │ │ ├── Anilist.jsx │ │ │ ├── AnimePage.jsx │ │ │ ├── Bookmarks.jsx │ │ │ ├── Downloads.jsx │ │ │ ├── ErrorPage.jsx │ │ │ ├── Home copy.jsx │ │ │ ├── Home.jsx │ │ │ ├── NewReleases.jsx │ │ │ ├── Player.jsx │ │ │ ├── Settings.jsx │ │ │ ├── Test.jsx │ │ │ └── VideoJs.jsx │ │ ├── skeletons │ │ │ ├── NewReleaseCardSkeleton.jsx │ │ │ └── SkeletonAnimeCard.jsx │ │ ├── sliding-pane.css │ │ ├── ui │ │ │ ├── AppLayout.jsx │ │ │ ├── CenteredLoader.jsx │ │ │ ├── ErrorElement.jsx │ │ │ └── Loader.jsx │ │ ├── utils │ │ │ ├── ContextProvider.jsx │ │ │ ├── anilistQueryObject.js │ │ │ ├── api.js │ │ │ ├── auth.js │ │ │ ├── currentSeason.js │ │ │ ├── discord.js │ │ │ ├── formatBytes.js │ │ │ ├── helper.js │ │ │ ├── nFormatter.js │ │ │ └── objectEmpty.js │ │ └── workers │ │ │ └── worker.js │ │ └── webtorrent.js │ ├── tailwind.config.js │ ├── test.json │ └── todo.md ├── LICENSE ├── README.md └── disclaimer.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | downloads 27 | BACKEND/server2.js 28 | BACKEND/server3.js 29 | -------------------------------------------------------------------------------- /Archive/Web Version/BACKEND/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "chalk": "^5.3.0", 5 | "cors": "^2.8.5", 6 | "dev-null": "^0.1.1", 7 | "express": "^4.19.2", 8 | "matroska-subtitles": "^3.3.2", 9 | "readable-stream-clone": "^1.0.0", 10 | "webtorrent": "^2.4.12" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "react-app" 3 | } 4 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | 15 | - name: Checkout 16 | uses: actions/checkout@v1 17 | 18 | - name: Build 19 | run: | 20 | npm install 21 | npm run build 22 | env: 23 | REACT_APP_APIKey: ${{ secrets.ANILIST_SECRET }} 24 | 25 | - name: Deploy 26 | uses: JamesIves/github-pages-deploy-action@releases/v3 27 | with: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | BRANCH: gh-pages 30 | FOLDER: dist 31 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["prettier-plugin-tailwindcss"] 3 | } 4 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/README.md: -------------------------------------------------------------------------------- 1 | # React + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | zenshin. 8 | 9 | 37 | 38 | 39 | 40 |
41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "homepage": "https://hitarth-gg.github.io/zenshin/", 3 | "name": "zenshin", 4 | "private": true, 5 | "version": "0.1.1", 6 | "type": "module", 7 | "scripts": { 8 | "dev": "vite", 9 | "build": "vite build", 10 | "predploy": "npm run build", 11 | "deploy": "gh-pages -d dist", 12 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", 13 | "preview": "vite preview" 14 | }, 15 | "dependencies": { 16 | "@fontsource/inter": "^5.0.19", 17 | "@fontsource/space-mono": "^5.0.20", 18 | "@radix-ui/react-icons": "^1.3.0", 19 | "@radix-ui/themes": "^3.1.1", 20 | "@studio-freight/react-lenis": "^0.0.47", 21 | "@tailwindcss/line-clamp": "^0.4.4", 22 | "@tanstack/react-query": "^5.51.1", 23 | "@tanstack/react-query-devtools": "^5.51.1", 24 | "@videojs/http-streaming": "^3.13.2", 25 | "@wordpress/autop": "^4.6.0", 26 | "axios": "^1.7.4", 27 | "date-fns": "^3.6.0", 28 | "dotenv": "^16.4.5", 29 | "events": "^3.3.0", 30 | "html-react-parser": "^5.1.12", 31 | "ldrs": "^1.0.2", 32 | "react": "^18.3.1", 33 | "react-dom": "^18.3.1", 34 | "react-infinite-scroll-component": "^6.1.0", 35 | "react-responsive-carousel": "^3.2.23", 36 | "react-router-dom": "^6.25.0", 37 | "react-spinners": "^0.14.1", 38 | "sonner": "^1.5.0", 39 | "tailwindcss-animated": "^1.1.2", 40 | "xml2js": "^0.6.2" 41 | }, 42 | "devDependencies": { 43 | "@types/react": "^18.3.3", 44 | "@types/react-dom": "^18.3.0", 45 | "@vitejs/plugin-react": "^4.3.1", 46 | "autoprefixer": "^10.4.19", 47 | "eslint": "^8.57.0", 48 | "eslint-config-react-app": "^7.0.1", 49 | "eslint-plugin-react": "^7.34.3", 50 | "eslint-plugin-react-hooks": "^4.6.2", 51 | "eslint-plugin-react-refresh": "^0.4.7", 52 | "gh-pages": "^6.1.1", 53 | "postcss": "^8.4.39", 54 | "prettier": "^3.3.3", 55 | "prettier-plugin-tailwindcss": "^0.6.5", 56 | "tailwindcss": "^3.4.5", 57 | "video.js": "^8.17.3", 58 | "vite": "^5.3.4", 59 | "vite-plugin-eslint": "^1.8.1" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Single Page Apps for GitHub Pages 6 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/public/zenshinLogoSmall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hitarth-gg/zenshin/fbd14c17ad9bd67b8643b96e866f70105ab586d7/Archive/Web Version/FRONTEND/public/zenshinLogoSmall.png -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/public/zenshinLogoSmall2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hitarth-gg/zenshin/fbd14c17ad9bd67b8643b96e866f70105ab586d7/Archive/Web Version/FRONTEND/public/zenshinLogoSmall2.png -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/App.jsx: -------------------------------------------------------------------------------- 1 | import { createBrowserRouter, RouterProvider } from "react-router-dom"; 2 | import AppLayout from "./ui/AppLayout"; 3 | import Home from "./pages/Home"; 4 | import ErrorPage from "./pages/ErrorPage"; 5 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 6 | import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; 7 | import AnimePage from "./pages/AnimePage"; 8 | import Player from "./pages/Player"; 9 | import NewReleases from "./pages/NewReleases"; 10 | import AnilistAuthCallback from "./components/AnilistAuthCallback"; 11 | 12 | // import { lazy } from "react"; 13 | // const AnimePage = lazy(() => import("./pages/AnimePage")); 14 | // const Player = lazy(() => import("./pages/Player")); 15 | 16 | const router = createBrowserRouter( 17 | [ 18 | { 19 | element: , 20 | errorElement: } />, 21 | children: [ 22 | { 23 | path: "/", 24 | element: , 25 | errorElement: , 26 | }, 27 | { 28 | path: "/anime/:animeId", 29 | element: , 30 | errorElement: , 31 | }, 32 | { 33 | path: "/player/:magnetId/:animeId?/:priorProgress?/:currentEpisodeNum?", 34 | element: , 35 | errorElement: , 36 | }, 37 | { 38 | path: "/newreleases", 39 | element: , 40 | errorElement: , 41 | }, 42 | { 43 | path: "/anilistauthcallback", 44 | element: , 45 | errorElement: , 46 | }, 47 | ], 48 | }, 49 | ], 50 | { 51 | basename: "/zenshin", 52 | }, 53 | ); 54 | 55 | 56 | function App() { 57 | // the idea of integrating react-query is similar to that of context api 58 | 59 | const queryClient = new QueryClient({ 60 | defaultOptions: { 61 | queries: { 62 | // staleTime: 60 * 1000, // staleTime is the time in milliseconds after which the data is considered stale 63 | staleTime: 0, 64 | }, 65 | }, 66 | }); 67 | 68 | return ( 69 | 70 | {/* */} 71 | 72 | 73 | ); 74 | } 75 | 76 | export default App; 77 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/assets/anime.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hitarth-gg/zenshin/fbd14c17ad9bd67b8643b96e866f70105ab586d7/Archive/Web Version/FRONTEND/src/assets/anime.gif -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/assets/gradient1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hitarth-gg/zenshin/fbd14c17ad9bd67b8643b96e866f70105ab586d7/Archive/Web Version/FRONTEND/src/assets/gradient1.jpg -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/assets/loundraw.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hitarth-gg/zenshin/fbd14c17ad9bd67b8643b96e866f70105ab586d7/Archive/Web Version/FRONTEND/src/assets/loundraw.jpg -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/assets/psyduck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hitarth-gg/zenshin/fbd14c17ad9bd67b8643b96e866f70105ab586d7/Archive/Web Version/FRONTEND/src/assets/psyduck.png -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/assets/zenshin1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hitarth-gg/zenshin/fbd14c17ad9bd67b8643b96e866f70105ab586d7/Archive/Web Version/FRONTEND/src/assets/zenshin1.png -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/assets/zenshin2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hitarth-gg/zenshin/fbd14c17ad9bd67b8643b96e866f70105ab586d7/Archive/Web Version/FRONTEND/src/assets/zenshin2.png -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/assets/zenshinLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hitarth-gg/zenshin/fbd14c17ad9bd67b8643b96e866f70105ab586d7/Archive/Web Version/FRONTEND/src/assets/zenshinLogo.png -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/assets/zenshinLogoLowPx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hitarth-gg/zenshin/fbd14c17ad9bd67b8643b96e866f70105ab586d7/Archive/Web Version/FRONTEND/src/assets/zenshinLogoLowPx.png -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/components/AnilistAuthCallback.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import CenteredLoader from "../ui/CenteredLoader"; 3 | import { toast } from "sonner"; 4 | import { ExclamationTriangleIcon } from "@radix-ui/react-icons"; 5 | import { useNavigate } from "react-router-dom"; 6 | 7 | export default function AnilistAuthCallback() { 8 | const navigate = useNavigate(); 9 | 10 | useEffect(() => { 11 | const getAccessToken = async () => { 12 | const hash = window.location.hash; 13 | 14 | if (hash) { 15 | const params = new URLSearchParams(hash.substring(1)); // Remove the # at the beginning 16 | const accessToken = params.get("access_token"); 17 | const tokenType = params.get("token_type"); 18 | const expiresIn = params.get("expires_in"); 19 | 20 | if (accessToken) { 21 | // Store the access token in local storage 22 | localStorage.setItem("anilist_token", accessToken); 23 | console.log(`Access Token: ${accessToken}`); 24 | console.log(`Token Type: ${tokenType}`); 25 | console.log(`Expires In: ${expiresIn}`); 26 | 27 | window.location.replace("/zenshin"); 28 | 29 | toast.success("Successfully logged in to AniList", { 30 | icon: ( 31 | 32 | ), 33 | classNames: { 34 | title: "text-green-500", 35 | }, 36 | }); 37 | } 38 | } 39 | }; 40 | 41 | getAccessToken(); 42 | }, []); 43 | 44 | return ( 45 |
46 | 47 |
48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/components/AnimeCard.jsx: -------------------------------------------------------------------------------- 1 | import { format } from 'date-fns' 2 | import { useNavigate } from 'react-router-dom' 3 | import '../index.css' 4 | import { useZenshinContext } from '../utils/ContextProvider' 5 | import { Tooltip } from '@radix-ui/themes' 6 | 7 | export default function AnimeCard({ data }) { 8 | // console.log(data); 9 | const navigate = useNavigate() 10 | 11 | function handleClick() { 12 | navigate(`/anime/${data.id}`, { state: { data } }) 13 | } 14 | 15 | const zenshinContext = useZenshinContext() 16 | const { glow } = zenshinContext 17 | 18 | const date = data?.startDate 19 | ? new Date(data.startDate.year, data.startDate.month - 1, data.startDate.day) 20 | : null 21 | 22 | const status = data?.mediaListEntry?.status 23 | 24 | return ( 25 |
handleClick()} 27 | className="group relative mt-6 flex w-48 cursor-pointer flex-col items-center justify-center gap-y-2 transition-all ease-in-out hover:scale-110" 28 | > 29 | 34 | 35 |
36 |
37 | {data?.title?.romaji} 38 |
39 | 40 |
41 |
42 |

{date && format(new Date(date), 'MMMM yyyy')}

43 | 44 | {status === 'CURRENT' && ( 45 | 46 |

47 |
48 | )} 49 | {status === 'PLANNING' && ( 50 | 51 |

52 |
53 | )} 54 | {status === 'COMPLETED' && ( 55 | 56 |

57 |
58 | )} 59 | {status === 'DROPPED' && ( 60 | 61 |

62 |
63 | )} 64 | {status === 'PAUSED' && ( 65 | 66 |

67 |
68 | )} 69 |
70 |

{data.format.slice(0, 3)}

71 |
72 |
73 |
74 | 75 | {/* FOR IMAGE GLOW */} 76 | {glow && ( 77 | 82 | )} 83 |
84 | ) 85 | } 86 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/components/EpisodesPlayer.jsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@radix-ui/themes"; 2 | import { useState } from "react"; 3 | 4 | export default function EpisodesPlayer({ 5 | file, 6 | handleStreamBrowser, 7 | handleStreamVlc, 8 | stopEpisodeDownload, 9 | setCurrentEpisode, 10 | }) { 11 | const [isActive, setIsActive] = useState(false); 12 | 13 | return ( 14 |
setIsActive(!isActive)} 16 | className="relative m-2 animate-fade-down cursor-default border border-gray-700 p-2 font-space-mono transition-all duration-100 ease-in-out animate-duration-500 hover:bg-[#1e1e20]" 17 | > 18 |
19 |
20 |
21 |

22 | 23 | {file.name} 24 |

25 | {isActive && ( 26 |
27 | 40 | 52 | 64 |
65 | )} 66 |
67 |
68 |
69 |
70 | ); 71 | } 72 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/components/GlobalActivityCard.jsx: -------------------------------------------------------------------------------- 1 | export default function GlobalActivityCard() { 2 | return ( 3 |
4 | 5 |
6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/components/RecentActivity.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import RecentActivityCard from "./RecentActivityCard"; 3 | 4 | export default function RecentActivity({ data }) { 5 | const [scrollY, setScrollY] = useState(0); 6 | 7 | useEffect(() => { 8 | const handleScroll = () => { 9 | setScrollY(window.scrollY); 10 | }; 11 | 12 | window.addEventListener("scroll", handleScroll); 13 | 14 | return () => { 15 | window.removeEventListener("scroll", handleScroll); 16 | }; 17 | }, []); 18 | 19 | return ( 20 |
21 | {/* Top and bottom gradient for depth effect */} 22 | {scrollY < 900 && ( 23 | <> 24 | {/*
*/} 25 | {/*
*/} 26 | {/*
*/} 27 | {/*
*/} 28 | 29 |
30 |
37 | {data.slice(0, 3).map((item, index) => ( 38 | 39 | ))} 40 |
41 |
42 |
43 |
50 | {data.slice(3, 6).map((item, index) => ( 51 | 52 | ))} 53 |
54 |
55 | 56 | )} 57 |
58 | ); 59 | } 60 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/components/RecentActivityCard.jsx: -------------------------------------------------------------------------------- 1 | import { format } from 'date-fns' 2 | import { useNavigate } from 'react-router-dom' 3 | import '../index.css' 4 | import { useZenshinContext } from '../utils/ContextProvider' 5 | import { Tooltip } from '@radix-ui/themes' 6 | 7 | export default function RecentActivityCard({ data }) { 8 | const navigate = useNavigate() 9 | 10 | function handleClick() { 11 | navigate(`/anime/${data.media.id}`, { state: { data } }) 12 | } 13 | 14 | const zenshinContext = useZenshinContext() 15 | const { glow } = zenshinContext 16 | 17 | return ( 18 |
handleClick()} 20 | className="skew3d group relative mt-6 flex w-56 cursor-pointer flex-col items-center justify-center gap-y-2 transition-all ease-in-out hover:scale-110" 21 | > 22 |
23 |
24 | {data?.media?.title?.romaji} 25 |
26 |
27 | 32 | 33 |
34 |
35 | 40 | {data?.user?.name} 41 |
42 |
43 | 44 | {/* FOR IMAGE GLOW */} 45 | {/* {glow && ( 46 | 51 | )} */} 52 |
53 | ) 54 | } 55 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/components/SearchResults.jsx: -------------------------------------------------------------------------------- 1 | import { format } from "date-fns"; 2 | import { useNavigate } from "react-router-dom"; 3 | 4 | export default function SearchResults({ data, setIsActive }) { 5 | const navigate = useNavigate(); 6 | function handleClick() { 7 | navigate(`/anime/${data.id}`); 8 | setIsActive(false); 9 | } 10 | const date = data?.startDate 11 | ? new Date( 12 | data.startDate.year, 13 | data.startDate.month - 1, 14 | data.startDate.day, 15 | ) 16 | : null; 17 | 18 | return ( 19 |
handleClick()} 21 | className="flex animate-fade cursor-pointer gap-x-5 bg-[#111113] px-2 py-1 font-inter transition-all duration-200 ease-in-out hover:scale-105 hover:bg-[#232326] hover:z-10 hover:rounded-md hover:drop-shadow-xl" 22 | > 23 | img 28 |
29 |
30 | {data.title.romaji} 31 |
32 | 33 |
34 |

35 | {data.format} - {`${data.episodes ? data.episodes : "?"} episodes`} ( 36 | {data.status}) 37 |

38 | {date &&

39 | {format(new Date(date), "MMMM yyyy")} 40 |

} 41 |
42 |
43 |
44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/components/StreamStats.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | const formatBytes = (bytes, decimals = 2) => { 4 | if (bytes === 0) return "0 Bytes"; 5 | 6 | const k = 1024; 7 | const dm = decimals < 0 ? 0 : decimals; 8 | const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; 9 | 10 | const i = Math.floor(Math.log(bytes) / Math.log(k)); 11 | 12 | return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i]; 13 | }; 14 | 15 | export default function StreamStats({ magnetURI }) { 16 | const [details, setDetails] = useState(null); 17 | 18 | useEffect(() => { 19 | const fetchDetails = () => { 20 | fetch(`http://localhost:64621/details/${encodeURIComponent(magnetURI)}`) 21 | .then((response) => response.json()) 22 | .then((data) => setDetails(data)) 23 | .catch((error) => 24 | console.error("Error fetching torrent details:", error), 25 | ); 26 | }; 27 | 28 | // Fetch details immediately 29 | fetchDetails(); 30 | 31 | // Set interval to fetch details every 1 second 32 | const intervalId = setInterval(fetchDetails, 1000); 33 | 34 | // Clear interval on component unmount 35 | return () => clearInterval(intervalId); 36 | }, [magnetURI]); 37 | 38 | return ( 39 |
40 |
{details?.name}
41 |
42 |
43 |

44 | Size: {formatBytes(details?.length)} 45 |

46 |

47 | Downloaded: {formatBytes(details?.downloaded)} 48 |

49 |

50 | Uploaded: {formatBytes(details?.uploaded)} 51 |

52 |
53 |
54 | {/*
*/} 55 |

56 |

Download Speed:

{" "} 57 |

{formatBytes(details?.downloadSpeed)} /sec

58 |

59 |

60 |

Upload Speed:

61 |

{formatBytes(details?.uploadSpeed)} /sec

62 |

63 |

64 |

Progress:

65 |

{(details?.progress * 100)?.toFixed(2)}%

66 |

67 |
68 |
69 |

70 | Ratio: {details?.ratio?.toFixed(2)} 71 |

72 |

73 | Peers: {details?.numPeers} 74 |

75 |
76 |
77 |
78 | ); 79 | } 80 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/hooks/useGetAniZipMappings.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from '@tanstack/react-query' 2 | import { getAniZipMappings } from '../utils/helper' 3 | 4 | export default function useGetAniZipMappings(id, anidb = false) { 5 | const { isLoading, data, error, status } = useQuery({ 6 | queryKey: ['cur_anime_anizip', id], 7 | queryFn: () => { 8 | if (id) return getAniZipMappings(id, anidb) 9 | return null 10 | }, 11 | staleTime: 1000 * 60 * 5 // 5 mins 12 | }) 13 | 14 | return { isLoading, data, error, status } 15 | } 16 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/hooks/useGetAnilistProfile.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { getAnilistProfile } from "../utils/auth"; 3 | 4 | export default function useGetAnilistProfile(token) { 5 | const { isLoading, data, error, status } = useQuery({ 6 | queryKey: ["cur_profile"], 7 | queryFn: () => { 8 | if (token) return getAnilistProfile(token); 9 | return null; 10 | }, 11 | staleTime: 1000 * 60 * 20, // 20 mins 12 | }); 13 | 14 | return { isLoading, data, error, status }; 15 | } 16 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/hooks/useGetAnimeById.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { getAnimeById } from "../utils/helper"; 3 | 4 | export default function useGetAnimeById(id) { 5 | console.log("Fetching anime with id:", id); 6 | 7 | const { 8 | isLoading, 9 | data: animeData, 10 | error, 11 | status, 12 | } = useQuery({ 13 | queryKey: ["cur_anime", id], 14 | queryFn: () => getAnimeById(id), 15 | staleTime: 1000 * 60 * 20, // 20 mins 16 | }); 17 | 18 | return { isLoading, animeData, error, status }; 19 | } 20 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/hooks/useGetAnimeByMalId.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { getAnimeByMalId } from "../utils/helper"; 3 | 4 | export default function useGetAnimeByMalId(id) { 5 | console.log("Fetching anime with id:", id); 6 | 7 | const { isLoading, data, error, status } = useQuery({ 8 | queryKey: ["cur_anime_mal", id], 9 | queryFn: () => { 10 | if (id) return getAnimeByMalId(id); 11 | return null; 12 | }, 13 | staleTime: 1000 * 60 * 20, // 20 mins 14 | }); 15 | 16 | return { isLoading, data, error, status }; 17 | } 18 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/hooks/useGetAnimeEpisodesById.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { getAnimeById, getAnimeEpisodesById } from "../utils/helper"; 3 | 4 | export default function useGetAnimeEpisodesById(id) { 5 | console.log("Fetching anime with id:", id); 6 | 7 | const { 8 | isLoading, 9 | data: animeEpisodes, 10 | error, 11 | status, 12 | } = useQuery({ 13 | queryKey: ["cur_anime_episodes", id], 14 | queryFn: () => getAnimeEpisodesById(id), 15 | staleTime: 1000 * 60 * 5, 16 | 17 | }); 18 | 19 | return { isLoading, animeEpisodes, error, status }; 20 | } 21 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/hooks/useGetMultipleAnilistIds.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from '@tanstack/react-query' 2 | 3 | // // Array of anime IDs 4 | // const ids = [100, 101, 102] // Replace with actual anime IDs you want to fetch 5 | 6 | // // Dynamically construct the GraphQL query 7 | // let query = `query {` 8 | 9 | // ids.forEach((id, index) => { 10 | // query += ` 11 | // anime${index + 1}: Media(id: ${id}) { 12 | // id 13 | // title { 14 | // romaji 15 | // english 16 | // native 17 | // } 18 | // episodes 19 | // streamingEpisodes { 20 | // title 21 | // thumbnail 22 | // url 23 | // site 24 | // } 25 | // description 26 | // }` 27 | // }) 28 | 29 | // query += `}` 30 | 31 | async function getMultipleAnilistIds(ids) { 32 | // Fetch request to the AniList GraphQL API 33 | 34 | console.log('ids:', ids) 35 | 36 | let query = `query {` 37 | ids.forEach((id, index) => { 38 | query += ` 39 | anime${index + 1}: Media(id: ${id}) { 40 | id 41 | coverImage { 42 | extraLarge 43 | large 44 | medium 45 | } 46 | title { 47 | romaji 48 | english 49 | native 50 | } 51 | episodes 52 | streamingEpisodes { 53 | title 54 | thumbnail 55 | url 56 | site 57 | } 58 | description 59 | }` 60 | }) 61 | 62 | query += `}` 63 | 64 | try { 65 | const response = await fetch('https://graphql.anilist.co', { 66 | method: 'POST', 67 | headers: { 68 | 'Content-Type': 'application/json', 69 | Accept: 'application/json' 70 | }, 71 | body: JSON.stringify({ query }) 72 | }) 73 | const data = await response.json() 74 | console.log('Fetched Data:', data); 75 | return data 76 | 77 | } catch (error) { 78 | console.error('Error:', error) 79 | throw new Error(error) 80 | } 81 | } 82 | 83 | export default function useGetMultipleAnilistIds(ids) { 84 | const { isLoading, data, error, status } = useQuery({ 85 | queryKey: ['multiple_ids_anilist', ids.toString()], 86 | queryFn: () => { 87 | if (ids && ids.length > 0) return getMultipleAnilistIds(ids) 88 | return null 89 | }, 90 | staleTime: 1000 * 60 * 5 // 5 mins 91 | }) 92 | 93 | return { isLoading, data, error, status } 94 | } 95 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/hooks/useGetNewReleases.js: -------------------------------------------------------------------------------- 1 | //getNewReleases 2 | import { useQuery } from "@tanstack/react-query"; 3 | import { getNewReleases } from "../utils/helper"; 4 | 5 | export default function useGetNewReleases(packer = "[SubsPlease]") { 6 | const { 7 | isLoading, 8 | data, 9 | error, 10 | status 11 | } = useQuery({ 12 | queryKey: ["new_releases", packer], 13 | queryFn: () => getNewReleases(packer), 14 | staleTime: 1000 * 60 * 5, // 5 minutes 15 | }); 16 | 17 | return { isLoading, data, error, status }; 18 | } 19 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/hooks/useGetRecentGlobalActivity.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { getRecentActivity, getTopAnime } from "../utils/helper"; 3 | 4 | export default function useGetRecentGlobalActivity() { 5 | const { 6 | isLoading, 7 | data, 8 | error, 9 | status 10 | } = useQuery({ 11 | queryKey: ["recent_activity"], 12 | queryFn: getRecentActivity, 13 | staleTime: 1000 * 60 * 10, // 10 min 14 | }); 15 | 16 | return { isLoading, data, error, status }; 17 | } 18 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/hooks/useGetTopAnime.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { getTopAnime } from "../utils/helper"; 3 | 4 | export default function useGetTopAnime(page) { 5 | const { 6 | isLoading, 7 | data: topAnime, 8 | error, 9 | status 10 | } = useQuery({ 11 | queryKey: ["top_anime"], 12 | queryFn: () => getTopAnime(page), 13 | staleTime: 1000 * 60 * 60, // 1 hour 14 | }); 15 | 16 | return { isLoading, topAnime, error, status }; 17 | } 18 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/hooks/useGetToshoEpisodes.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from '@tanstack/react-query' 2 | import { getToshoEpisodes } from '../utils/helper' 3 | 4 | export default function useGetoToshoEpisodes(quality, aids, eids) { 5 | const { isLoading, data, error, status } = useQuery({ 6 | queryKey: ['tosho_episode_tracker', quality, aids, eids], 7 | queryFn: () => { 8 | if (quality && aids) { 9 | return getToshoEpisodes(quality, aids, eids) 10 | } 11 | return null 12 | }, 13 | enabled: !!(quality && aids), // Ensures the query only runs when the quality + aids is not null, 14 | staleTime: 1000 * 60 * 10 // 10 mins 15 | }) 16 | 17 | return { isLoading, data, error, status } 18 | } 19 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/hooks/useNyaaTracker.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { searchTorrent } from "../utils/helper"; 3 | 4 | export default function useNyaaTracker(query) { 5 | const { 6 | isLoading, 7 | data: torrents, 8 | error, 9 | status, 10 | } = useQuery({ 11 | queryKey: ["nyaa_episode_tracker", query], 12 | queryFn: () => { 13 | if (query) { 14 | return searchTorrent(query); 15 | } 16 | return null; 17 | }, 18 | enabled: !!query, // Ensures the query only runs when the query string is not null 19 | staleTime: 1000 * 60 * 10, // 10 mins 20 | }); 21 | 22 | // console.log("Query Status:", status); // Log to check if the query is running 23 | // console.log("Torrent Data:", torrents); // Log the returned data to see its structure 24 | 25 | return { isLoading, torrents, error, status }; 26 | } 27 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/hooks/useTopAiringAnime.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { getTopAiringAnime } from "../utils/helper"; 3 | 4 | export default function useTopAiringAnime() { 5 | const { 6 | isLoading, 7 | data: topAiringAnime, 8 | error, 9 | status 10 | } = useQuery({ 11 | queryKey: ["top_airing_anime"], 12 | queryFn: getTopAiringAnime, 13 | staleTime: 1000 * 60 * 10, // 10 mins 14 | }); 15 | 16 | return { isLoading, topAiringAnime, error, status }; 17 | } 18 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | /* ------------------ CUSTOM SCROLL BAR ----------------- */ 6 | /* Apply custom scrollbar styles */ 7 | ::-webkit-scrollbar { 8 | width: 7px; /* Scrollbar width */ 9 | height: 7px; /* Scrollbar height */ 10 | } 11 | 12 | /* Scrollbar track */ 13 | ::-webkit-scrollbar-track { 14 | background: transparent; 15 | } 16 | 17 | /* Scrollbar thumb */ 18 | ::-webkit-scrollbar-thumb { 19 | background: #4a4a4a; /* Thumb color */ 20 | border-radius: 0px; /* Thumb border-radius */ 21 | } 22 | 23 | /* Scrollbar thumb states */ 24 | ::-webkit-scrollbar-thumb:hover { 25 | background: #555; /* Hover thumb color */ 26 | } 27 | 28 | ::-webkit-scrollbar-thumb:active { 29 | background: #444; /* Active thumb color */ 30 | } 31 | /* ------------------------------------------------------ */ 32 | 33 | .draggable { 34 | -webkit-app-region: drag; 35 | } 36 | .nodrag { 37 | -webkit-app-region: no-drag; 38 | } 39 | 40 | /* For blur mask of banner image */ 41 | .mask { 42 | -webkit-mask: -webkit-gradient( 43 | linear, 44 | left top, 45 | /* Start point of the gradient */ right top, 46 | /* End point of the gradient */ from(rgba(0, 0, 0, 1)), 47 | /* Fully opaque at the left */ to(rgba(0, 0, 0, 0)) /* Fully transparent at the right */ 48 | ); 49 | mask: linear-gradient(to right, rgba(0, 0, 0, 1) 30%, rgba(0, 0, 0, 0) 60%); 50 | } 51 | 52 | .skew3d { 53 | transform: matrix(1, 0, -0.05, 1, 0, 0); 54 | } 55 | 56 | .tripp { 57 | mask-image: linear-gradient(to bottom, transparent, black 10%, black 90%, transparent); 58 | } 59 | 60 | .stroke-text { 61 | -webkit-text-stroke: 1px rgba(255, 255, 255, 0.086); /* Stroke width and color */ 62 | color: transparent; /* Text fill color */ 63 | } 64 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App.jsx"; 4 | import "./index.css"; 5 | import "@radix-ui/themes/styles.css"; 6 | 7 | /* ------------------------ fonts ----------------------- */ 8 | // inter 9 | import "@fontsource/inter/100.css"; 10 | import "@fontsource/inter/200.css"; 11 | import "@fontsource/inter/300.css"; 12 | import "@fontsource/inter/400.css"; 13 | import "@fontsource/inter/500.css"; 14 | import "@fontsource/inter/600.css"; 15 | import "@fontsource/inter/700.css"; 16 | import "@fontsource/inter/800.css"; 17 | import "@fontsource/inter/900.css"; 18 | // space mono 19 | import "@fontsource/space-mono/400.css"; 20 | import "@fontsource/space-mono/700.css"; 21 | import ZenshinProvider from "./utils/ContextProvider.jsx"; 22 | 23 | 24 | /* ------------------------------------------------------ */ 25 | 26 | 27 | ReactDOM.createRoot(document.getElementById("root")).render( 28 | 29 | 30 | , 31 | ); 32 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/pages/ErrorPage.jsx: -------------------------------------------------------------------------------- 1 | import ErrorElement from "../ui/ErrorElement"; 2 | 3 | export default function ErrorPage({title, text, type}) { 4 | return ( 5 |
6 | 7 |
8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/pages/NewReleases.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | import useGetNewReleases from '../hooks/useGetNewReleases' 3 | import CenteredLoader from '../ui/CenteredLoader' 4 | import NewReleaseCard from '../components/NewReleaseCard' 5 | import useGetMultipleAnilistIds from '../hooks/useGetMultipleAnilistIds' 6 | 7 | export default function NewReleases() { 8 | const packer = '[SubsPlease]' 9 | const { isLoading, data, error, status } = useGetNewReleases(packer) 10 | const [newReleases, setNewReleases] = useState([]) 11 | const [displayedReleases, setDisplayedReleases] = useState([]) 12 | const [cardErrorShown, setCardErrorShown] = useState(false) // Track whether error toast was shown 13 | const totalCards = 20 14 | 15 | useEffect(() => { 16 | if (data) { 17 | const HQ_Releases = data.filter((release) => release.title.includes('1080p')) 18 | setNewReleases(HQ_Releases.slice(0, 12)) 19 | // setNewReleases(HQ_Releases); 20 | setDisplayedReleases(HQ_Releases.slice(0, totalCards)) 21 | } 22 | }, [data]) 23 | 24 | const [anilistIds, setAnilistIds] = useState([]) 25 | 26 | const { 27 | isLoading: isLoadingAnilist, 28 | data: dataAnilist, 29 | error: errorAnilist 30 | } = useGetMultipleAnilistIds(anilistIds.length === totalCards ? anilistIds : []) 31 | 32 | // useEffect(() => { 33 | // async function insertReleases() { 34 | // const chunkSize = 5 35 | // for (let i = 0; i < newReleases.length; i += chunkSize) { 36 | // const chunk = newReleases.slice(i, i + chunkSize) 37 | // for (let j = 0; j < chunk.length; j++) { 38 | // await new Promise((resolve) => setTimeout(resolve, 300)) // 200ms delay between each insertion 39 | // setDisplayedReleases((prev) => [...prev, chunk[j]]) 40 | // } 41 | // if (i + chunkSize < newReleases.length) { 42 | // await new Promise((resolve) => setTimeout(resolve, 2000)) // Wait 2 seconds before next chunk 43 | // } 44 | // } 45 | // } 46 | 47 | // if (newReleases.length > 0) { 48 | // insertReleases() 49 | // } 50 | // }, [newReleases]) 51 | 52 | console.log(anilistIds) 53 | 54 | if (isLoading) { 55 | return 56 | } 57 | 58 | if (error) { 59 | throw new Error(error) 60 | } 61 | 62 | return ( 63 |
64 |
65 | New Releases 66 |
67 |
68 | {displayedReleases.map((release) => ( 69 | 78 | ))} 79 |
80 |
81 | ) 82 | } 83 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/pages/PlayerOld.txt: -------------------------------------------------------------------------------- 1 | // import axios from "axios"; 2 | // import { useEffect, useState } from "react"; 3 | // import { useParams } from "react-router-dom"; 4 | 5 | // export default function Player(query) { 6 | // const magnetURI = useParams().magnetId; 7 | // const [videoSrc, setVideoSrc] = useState(""); 8 | // const [subtitleSrc, setSubtitleSrc] = useState(""); 9 | // console.log(magnetURI); 10 | 11 | // const handleSubmit = async (e) => { 12 | // e.preventDefault(); 13 | // try { 14 | // // Step 1: Add the torrent 15 | // await axios.get( 16 | // `http://localhost:64621/add/${encodeURIComponent(magnetURI)}`, 17 | // ); 18 | // // Step 2: Set the video source for streaming 19 | // setVideoSrc( 20 | // `http://localhost:64621/stream/${encodeURIComponent(magnetURI)}`, 21 | // ); 22 | // // Step 3: Set the subtitle source 23 | // setSubtitleSrc( 24 | // `http://localhost:64621/subtitles/${encodeURIComponent(magnetURI)}`, 25 | // ); 26 | // } catch (error) { 27 | // console.error("Error adding the torrent or streaming video", error); 28 | // } 29 | // }; 30 | 31 | // console.log(videoSrc, subtitleSrc); 32 | 33 | // return ( 34 | //
35 | //
36 | //
37 | // 38 | //
39 | // {videoSrc && ( 40 | // 51 | // )} 52 | //
53 | //
54 | // ); 55 | // } 56 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/pages/VideoJs.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import videojs from 'video.js'; 3 | import 'video.js/dist/video-js.css'; 4 | 5 | export const VideoJS = (props) => { 6 | const videoRef = React.useRef(null); 7 | const playerRef = React.useRef(null); 8 | const {options, onReady} = props; 9 | 10 | useEffect(() => { 11 | 12 | if (!playerRef.current) { 13 | const videoElement = document.createElement("video-js"); 14 | 15 | videoElement.classList.add('vjs-big-play-centered'); 16 | videoRef.current.appendChild(videoElement); 17 | 18 | const player = playerRef.current = videojs(videoElement, options, () => { 19 | videojs.log('player is ready'); 20 | onReady && onReady(player); 21 | }); 22 | } else { 23 | const player = playerRef.current; 24 | 25 | player.autoplay(options.autoplay); 26 | player.src(options.sources); 27 | } 28 | }, [options, videoRef]); 29 | 30 | // Dispose the Video.js player when the functional component unmounts, otherwise memory leak ho jayega lmao 31 | useEffect(() => { 32 | const player = playerRef.current; 33 | 34 | return () => { 35 | if (player && !player.isDisposed()) { 36 | player.dispose(); 37 | playerRef.current = null; 38 | } 39 | }; 40 | }, [playerRef]); 41 | 42 | return ( 43 |
44 |
45 |
46 | ); 47 | } 48 | 49 | export default VideoJS; 50 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/skeletons/SkeletonAnimeCard.jsx: -------------------------------------------------------------------------------- 1 | import { Skeleton } from '@radix-ui/themes' 2 | 3 | export default function SkeletonAnimeCard() { 4 | return ( 5 |
6 | 7 |
8 | 9 | 10 |
11 |
12 | 13 | 14 |
15 |
16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/ui/AppLayout.jsx: -------------------------------------------------------------------------------- 1 | import { Theme } from '@radix-ui/themes' 2 | import { Outlet, useNavigate, useNavigation } from 'react-router-dom' 3 | import Loader from './Loader' 4 | import { Toaster } from 'sonner' 5 | import { useEffect, useState } from 'react' 6 | import Header from '../components/Header' 7 | import { ReactLenis } from '@studio-freight/react-lenis' 8 | 9 | export default function AppLayout({ props }) { 10 | const navigation = useNavigation() 11 | const isLoading = navigation.state === 'loading' 12 | const [theme, setTheme] = useState('dark') 13 | 14 | function toggleTheme() { 15 | setTheme((prev) => (prev === 'dark' ? 'light' : 'dark')) 16 | console.log(theme) 17 | } 18 | 19 | // use alt + arrow keys to navigate between pages 20 | const navigate = useNavigate() 21 | useEffect(() => { 22 | document.addEventListener('keydown', (e) => { 23 | if (e.altKey && e.key === 'ArrowLeft') { 24 | navigate(-1) 25 | } 26 | if (e.altKey && e.key === 'ArrowRight') { 27 | navigate(1) 28 | } 29 | }) 30 | 31 | return () => { 32 | document.removeEventListener('keydown', () => {}) 33 | } 34 | }, [navigation]) 35 | 36 | return ( 37 | 38 | 39 | 52 |
53 | {isLoading && } 54 |
55 |
{props || }
56 |
57 |
58 |
59 | ) 60 | } 61 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/ui/CenteredLoader.jsx: -------------------------------------------------------------------------------- 1 | import Loader from "./Loader" 2 | 3 | export default function CenteredLoader() { 4 | return ( 5 |
6 | 7 |
8 | ) 9 | } 10 | 11 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/ui/ErrorElement.jsx: -------------------------------------------------------------------------------- 1 | import { useRouteError } from "react-router-dom"; 2 | import psyduck from "../assets/psyduck.png"; 3 | 4 | export default function ErrorElement({ title="Something Went wrong !", text, type }) { 5 | const error = useRouteError(); 6 | 7 | const style = { 8 | outer: `border-rose-500 bg-rose-500`, 9 | inner: `text-rose-500`, 10 | }; 11 | 12 | if (type === "warning") { 13 | style.outer = `border-orange-500 bg-orange-500`; 14 | style.inner = `text-orange-500`; 15 | } else if (type === "info") { 16 | style.outer = `border-sky-500 bg-sky-500`; 17 | style.inner = `text-sky-500 18 | `; 19 | } 20 | 21 | return ( 22 |
25 |
26 | 27 |
28 | {title} 29 |
30 |

31 | {text || error?.data || error?.message || "Unknown Error"} 32 |

33 |
34 |
35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/ui/Loader.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | // import "ldrs/lineWobble"; 3 | import animeGif from "../assets/anime.gif"; 4 | import { BarLoader } from "react-spinners"; 5 | export default function Loader() { 6 | return ( 7 |
8 | 9 | 10 |
11 | {/* */} 18 | 19 |
20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/utils/ContextProvider.jsx: -------------------------------------------------------------------------------- 1 | import { createContext, useContext, useState } from "react"; 2 | 3 | const ZenshinContext = createContext(); 4 | 5 | export function useZenshinContext() { 6 | const context = useContext(ZenshinContext); 7 | if (context === undefined) { 8 | throw new Error("useZenshinContext must be used within a ZenshinProvider"); 9 | } 10 | return context; 11 | } 12 | 13 | export default function ZenshinProvider({ children }) { 14 | const [glow, setGlow] = useState(false); 15 | 16 | return ( 17 | 18 | {children} 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/utils/api.js: -------------------------------------------------------------------------------- 1 | const BASE_URL_JIKAN = 'https://api.jikan.moe/v4' 2 | const BASE_URL_NYAA = decodeURIComponent( 3 | atob("aHR0cHM6Ly9ueWFhYXBpLm9ucmVuZGVyLmNvbS9ueWFh") 4 | ) 5 | export const BASE_URL_ANILIST = 'https://graphql.anilist.co' 6 | const BASE_URL_ANIZIP = 'https://api.ani.zip' 7 | const TOSHO = decodeURIComponent(atob("aHR0cHM6Ly9mZWVkLmFuaW1ldG9zaG8ub3Jn")) 8 | 9 | export function SEARCH_ANIME(query, limit = 10) { 10 | return `${BASE_URL_JIKAN}/anime?q=${query}&limit=${limit}` 11 | } 12 | 13 | export function SEARCH_TORRENT(query) { 14 | return `${BASE_URL_NYAA}/?q=${query}&sort=seeders&order=desc&page=1&category=anime` 15 | } 16 | 17 | export function TOP_AIRING_ANIME() { 18 | return `${BASE_URL_JIKAN}/top/anime?&filter=airing&limit=25&sfw=true&type=tv` 19 | } 20 | 21 | export function TOP_ANIME(page = 1) { 22 | return `${BASE_URL_JIKAN}/top/anime?page=${page}&limit=25&sfw=true` 23 | } 24 | 25 | export function GET_ANIME_DETAILS_BY_ID(id) { 26 | return `${BASE_URL_JIKAN}/anime/${id}/full` 27 | } 28 | 29 | export function GET_ANIME_MAPPING_BY_ANILIST_ID(anilist_id, anidb = false) { 30 | console.log(`${BASE_URL_ANIZIP}/mappings?anilist_id=${anilist_id}`) 31 | if (anidb) return `${BASE_URL_ANIZIP}/mappings?anidb_id=${anilist_id}` 32 | return `${BASE_URL_ANIZIP}/mappings?anilist_id=${anilist_id}` 33 | } 34 | 35 | export function GET_ANIME_EPISODES_BY_ID(id) { 36 | return `${BASE_URL_JIKAN}/anime/${id}/episodes` 37 | } 38 | 39 | export function GET_TOSHO_RSS(packer = '"[SubsPlease]"') { 40 | return `${TOSHO}/json?qx=1&q=${packer}` 41 | } 42 | 43 | export function GET_TOSHO_RSS_BY_QUERY(quality = 'all', aids, eids) { 44 | if (eids === 0 || eids === null) { 45 | if (quality.toLowerCase() === 'all') return `${TOSHO}/json?qx=1&aids=${aids}` 46 | return `${TOSHO}/json?qx=1&q=${quality}&aids=${aids}` 47 | } 48 | if (quality.toLowerCase() === 'all') return `${TOSHO}/json?qx=1&aids=${aids}&eids=${eids}` 49 | return `${TOSHO}/json?qx=1&q=${quality}&aids=${aids}&eids=${eids}` 50 | } 51 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/utils/auth.js: -------------------------------------------------------------------------------- 1 | // export const ANILIST_CLIENT_ID = () => { 2 | // const url = window.location.href; 3 | // if (url.includes("localhost")) { 4 | // console.log("Localhost detected"); 5 | // return 20876; 6 | // } else return 20866; 7 | // }; 8 | 9 | // export const ANILIST_CLIENT_ID = 20876; 10 | // export const ANILIST_CLIENT_ID = 20866; 11 | export const ANILIST_CLIENT_ID = window.location.href.includes("localhost") ? 20876 : 20866; 12 | export const anilistAuthUrl = `https://anilist.co/api/v2/oauth/authorize?client_id=${ANILIST_CLIENT_ID}&response_type=token`; 13 | 14 | export async function getAnilistProfile(anilistToken) { 15 | try { 16 | if (!localStorage.getItem("anilist_token")) return null; 17 | 18 | // Fetch user data from AniList API 19 | const response = await fetch("https://graphql.anilist.co", { 20 | method: "POST", 21 | headers: { 22 | "Content-Type": "application/json", 23 | Authorization: `Bearer ${anilistToken}`, 24 | }, 25 | body: JSON.stringify({ 26 | query: ` 27 | query { 28 | Viewer { 29 | id 30 | name 31 | avatar { 32 | large 33 | } 34 | } 35 | } 36 | `, 37 | }), 38 | }); 39 | 40 | console.log("AniList Profile Response: ", response); 41 | 42 | if (response.status === 429) { 43 | throw new Error( 44 | "Too many requests to the API. You are being rate-limited. Please wait a minute and refresh the page.", 45 | ); 46 | } else if (!response.ok) { 47 | const errorData = await response.json(); 48 | throw new Error( 49 | `Error ${response.status}: ${response.statusText} - ${errorData.message}`, 50 | ); 51 | } 52 | 53 | const data = await response.json(); 54 | localStorage.setItem("anilist_id", data.data.Viewer.id); 55 | localStorage.setItem("anilist_name", data.data.Viewer.name); 56 | 57 | return data.data.Viewer; 58 | } catch (error) { 59 | console.log("Error in getAnilistProfile: ", error); 60 | throw new Error(error); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/utils/currentSeason.js: -------------------------------------------------------------------------------- 1 | export function getCurrentSeason() { 2 | const month = new Date().getMonth() + 1 // getMonth() returns 0-11, so add 1 3 | 4 | if (month >= 3 && month <= 5) { 5 | return 'SPRING' 6 | } else if (month >= 6 && month <= 9) { 7 | return 'SUMMER' 8 | } else if (month >= 9 && month <= 11) { 9 | return 'FALL' 10 | } else { 11 | return 'WINTER' 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/utils/formatBytes.js: -------------------------------------------------------------------------------- 1 | export default function formatBytes(bytes, decimals = 2) { 2 | if (bytes === 0) return '0 Bytes' 3 | 4 | const k = 1024 5 | const dm = decimals < 0 ? 0 : decimals 6 | const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] 7 | 8 | const i = Math.floor(Math.log(bytes) / Math.log(k)) 9 | 10 | return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i] 11 | } 12 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src/utils/nFormatter.js: -------------------------------------------------------------------------------- 1 | export default function nFormatter(num, digits) { 2 | const lookup = [ 3 | { value: 1, symbol: '' }, 4 | { value: 1e3, symbol: 'k' }, 5 | { value: 1e6, symbol: 'M' }, 6 | { value: 1e9, symbol: 'G' }, 7 | { value: 1e12, symbol: 'T' }, 8 | { value: 1e15, symbol: 'P' }, 9 | { value: 1e18, symbol: 'E' } 10 | ] 11 | const regexp = /\.0+$|(?<=\.[0-9]*[1-9])0+$/ 12 | const item = lookup.findLast((item) => num >= item.value) 13 | return item ? (num / item.value).toFixed(digits).replace(regexp, '').concat(item.symbol) : '0' 14 | } 15 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src_mal/App.jsx: -------------------------------------------------------------------------------- 1 | import { createBrowserRouter, RouterProvider } from "react-router-dom"; 2 | import AppLayout from "./ui/AppLayout"; 3 | import Home from "./pages/Home"; 4 | import ErrorPage from "./pages/ErrorPage"; 5 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 6 | import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; 7 | import AnimePage from "./pages/AnimePage"; 8 | import Player from "./pages/Player"; 9 | 10 | const router = createBrowserRouter( 11 | [ 12 | { 13 | element: , 14 | errorElement: } />, 15 | children: [ 16 | { 17 | path: "/", 18 | element: , 19 | errorElement: , 20 | }, 21 | { 22 | path: "/anime/:animeId", 23 | element: , 24 | errorElement: , 25 | }, 26 | { 27 | path: "/player/:magnetId", 28 | element: , 29 | errorElement: , 30 | } 31 | 32 | ], 33 | }, 34 | ], 35 | { 36 | basename: "/zenshin", 37 | }, 38 | ); 39 | 40 | function App() { 41 | 42 | // the idea of integrating react-query is similar to that of context api 43 | const queryClient = new QueryClient({ 44 | defaultOptions: { 45 | queries: { 46 | // staleTime: 60 * 1000, // staleTime is the time in milliseconds after which the data is considered stale 47 | staleTime: 0, 48 | }, 49 | }, 50 | }); 51 | 52 | 53 | return ( 54 | 55 | 56 | 57 | 58 | ); 59 | } 60 | 61 | export default App; 62 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src_mal/assets/anime.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hitarth-gg/zenshin/fbd14c17ad9bd67b8643b96e866f70105ab586d7/Archive/Web Version/FRONTEND/src_mal/assets/anime.gif -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src_mal/assets/psyduck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hitarth-gg/zenshin/fbd14c17ad9bd67b8643b96e866f70105ab586d7/Archive/Web Version/FRONTEND/src_mal/assets/psyduck.png -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src_mal/assets/zenshin1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hitarth-gg/zenshin/fbd14c17ad9bd67b8643b96e866f70105ab586d7/Archive/Web Version/FRONTEND/src_mal/assets/zenshin1.png -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src_mal/assets/zenshin2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hitarth-gg/zenshin/fbd14c17ad9bd67b8643b96e866f70105ab586d7/Archive/Web Version/FRONTEND/src_mal/assets/zenshin2.png -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src_mal/assets/zenshinLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hitarth-gg/zenshin/fbd14c17ad9bd67b8643b96e866f70105ab586d7/Archive/Web Version/FRONTEND/src_mal/assets/zenshinLogo.png -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src_mal/components/AnimeCard.jsx: -------------------------------------------------------------------------------- 1 | import { format } from "date-fns"; 2 | import { useNavigate } from "react-router-dom"; 3 | 4 | export default function AnimeCard({ data }) { 5 | // console.log(data); 6 | const navigate = useNavigate(); 7 | 8 | function handleClick() { 9 | navigate(`/anime/${data.mal_id}`); 10 | } 11 | 12 | return ( 13 |
handleClick()} 15 | className="m-4 flex w-48 cursor-pointer flex-col items-center justify-center gap-y-2 transition-all ease-in-out hover:scale-110" 16 | > 17 | 22 |
23 |
24 | {data.title} 25 |
26 | 27 |
28 |

{format(new Date(data.aired.from), "MMMM yyyy")}

29 |

{data.type}

30 |
31 |
32 |
33 |
34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src_mal/components/Header.jsx: -------------------------------------------------------------------------------- 1 | import SearchBar from "./SearchBar"; 2 | import { Link } from "react-router-dom"; 3 | import zenshinLogo from "../assets/zenshinLogo.png"; 4 | import { 5 | DividerVerticalIcon, 6 | GitHubLogoIcon, 7 | MoonIcon, 8 | SunIcon, 9 | } from "@radix-ui/react-icons"; 10 | import { Button } from "@radix-ui/themes"; 11 | 12 | export default function Header({ theme, toggleTheme }) { 13 | return ( 14 |
15 |
16 | 20 | {/* zenshin | 全身 */} 21 | 22 | 23 | 24 | 33 |
34 | 35 |
36 | 37 |
38 |
39 | 43 | 46 | 47 | 54 |
55 |
56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src_mal/components/SearchResults.jsx: -------------------------------------------------------------------------------- 1 | import { format } from "date-fns"; 2 | import { SEARCH_TORRENT } from "../utils/api"; 3 | import { useNavigate } from "react-router-dom"; 4 | 5 | export default function SearchResults({ data, setIsActive }) { 6 | const navigate = useNavigate(); 7 | function handleClick() { 8 | navigate(`/anime/${data.mal_id}`); 9 | setIsActive(false); 10 | } 11 | 12 | return ( 13 |
handleClick()} 15 | className="flex animate-fade cursor-pointer gap-x-5 rounded-md bg-[#111113] px-2 py-1 font-inter transition-all duration-200 ease-in-out hover:scale-105 hover:bg-[#232326] hover:z-10" 16 | > 17 | img 22 |
23 |
24 | {data.title} 25 |
26 | 27 |
28 |

29 | {data.type} - {`${data.episodes ? data.episodes : "?"} episodes`} ( 30 | {data.status}) 31 |

32 |

33 | {format(new Date(data.aired.from), "MMMM yyyy")} 34 |

35 |
36 |
37 |
38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src_mal/components/StreamStats.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | const formatBytes = (bytes, decimals = 2) => { 4 | if (bytes === 0) return "0 Bytes"; 5 | 6 | const k = 1024; 7 | const dm = decimals < 0 ? 0 : decimals; 8 | const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; 9 | 10 | const i = Math.floor(Math.log(bytes) / Math.log(k)); 11 | 12 | return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i]; 13 | }; 14 | 15 | export default function StreamStats({ magnetURI }) { 16 | const [details, setDetails] = useState(null); 17 | 18 | useEffect(() => { 19 | const fetchDetails = () => { 20 | fetch(`http://localhost:64621/details/${encodeURIComponent(magnetURI)}`) 21 | .then((response) => response.json()) 22 | .then((data) => setDetails(data)) 23 | .catch((error) => 24 | console.error("Error fetching torrent details:", error), 25 | ); 26 | }; 27 | 28 | // Fetch details immediately 29 | fetchDetails(); 30 | 31 | // Set interval to fetch details every 1 second 32 | const intervalId = setInterval(fetchDetails, 1000); 33 | 34 | // Clear interval on component unmount 35 | return () => clearInterval(intervalId); 36 | }, [magnetURI]); 37 | 38 | return ( 39 |
40 |
{details?.name}
41 |
42 |
43 |

44 | Size: {formatBytes(details?.length)} 45 |

46 |

47 | Downloaded: {formatBytes(details?.downloaded)} 48 |

49 |

50 | Uploaded: {formatBytes(details?.uploaded)} 51 |

52 |
53 |
54 |

55 | Download Speed: {" "} 56 | {formatBytes(details?.downloadSpeed)} /sec 57 |

58 |

59 | Upload Speed: {formatBytes(details?.uploadSpeed)}{" "} 60 | /sec 61 |

62 |

63 | Progress: {(details?.progress * 100)?.toFixed(2)}% 64 |

65 |
66 |
67 |

68 | Ratio: {details?.ratio?.toFixed(2)} 69 |

70 |

71 | Peers: {details?.numPeers} 72 |

73 |
74 |
75 |
76 | ); 77 | } 78 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src_mal/hooks/useGetAnimeById.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { getAnimeById } from "../utils/helper"; 3 | 4 | export default function useGetAnimeById(id) { 5 | console.log("Fetching anime with id:", id); 6 | 7 | const { 8 | isLoading, 9 | data: animeData, 10 | error, 11 | status, 12 | } = useQuery({ 13 | queryKey: ["cur_anime", id], 14 | queryFn: () => getAnimeById(id), 15 | staleTime: 0, 16 | }); 17 | 18 | return { isLoading, animeData, error, status }; 19 | } 20 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src_mal/hooks/useGetAnimeEpisodesById.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { getAnimeById, getAnimeEpisodesById } from "../utils/helper"; 3 | 4 | export default function useGetAnimeEpisodesById(id) { 5 | console.log("Fetching anime with id:", id); 6 | 7 | const { 8 | isLoading, 9 | data: animeEpisodes, 10 | error, 11 | status, 12 | } = useQuery({ 13 | queryKey: ["cur_anime_episodes", id], 14 | queryFn: () => getAnimeEpisodesById(id), 15 | staleTime: 0, 16 | 17 | }); 18 | 19 | return { isLoading, animeEpisodes, error, status }; 20 | } 21 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src_mal/hooks/useGetTopAnime.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { getTopAnime } from "../utils/helper"; 3 | 4 | export default function useGetTopAnime(page) { 5 | const { 6 | isLoading, 7 | data: topAnime, 8 | error, 9 | status 10 | } = useQuery({ 11 | queryKey: ["top_anime"], 12 | queryFn: () => getTopAnime(page), 13 | staleTime: 1000 * 60 * 60, // 1 hour 14 | }); 15 | 16 | return { isLoading, topAnime, error, status }; 17 | } 18 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src_mal/hooks/useNyaaTracker.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { searchTorrent } from "../utils/helper"; 3 | 4 | export default function useNyaaTracker(query) { 5 | const { 6 | isLoading, 7 | data: torrents, 8 | error, 9 | status, 10 | } = useQuery({ 11 | queryKey: ["nyaa_episode_tracker", query], 12 | queryFn: () => { 13 | if (query) { 14 | return searchTorrent(query); 15 | } 16 | return null; 17 | }, 18 | enabled: !!query, // Ensures the query only runs when the query string is not null 19 | staleTime: 0, 20 | }); 21 | 22 | // console.log("Query Status:", status); // Log to check if the query is running 23 | // console.log("Torrent Data:", torrents); // Log the returned data to see its structure 24 | 25 | return { isLoading, torrents, error, status }; 26 | } 27 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src_mal/hooks/useTopAiringAnime.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { getTopAiringAnime } from "../utils/helper"; 3 | 4 | export default function useTopAiringAnime() { 5 | const { 6 | isLoading, 7 | data: topAiringAnime, 8 | error, 9 | status 10 | } = useQuery({ 11 | queryKey: ["top_airing_anime"], 12 | queryFn: getTopAiringAnime, 13 | staleTime: 1000 * 60 * 60, // 1 hour 14 | }); 15 | 16 | return { isLoading, topAiringAnime, error, status }; 17 | } 18 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src_mal/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src_mal/main.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App.jsx"; 4 | import "./index.css"; 5 | import "@radix-ui/themes/styles.css"; 6 | 7 | /* ------------------------ fonts ----------------------- */ 8 | // inter 9 | import "@fontsource/inter/100.css"; 10 | import "@fontsource/inter/200.css"; 11 | import "@fontsource/inter/300.css"; 12 | import "@fontsource/inter/400.css"; 13 | import "@fontsource/inter/500.css"; 14 | import "@fontsource/inter/600.css"; 15 | import "@fontsource/inter/700.css"; 16 | import "@fontsource/inter/800.css"; 17 | import "@fontsource/inter/900.css"; 18 | // space mono 19 | import "@fontsource/space-mono/400.css"; 20 | import "@fontsource/space-mono/700.css"; 21 | /* ------------------------------------------------------ */ 22 | 23 | ReactDOM.createRoot(document.getElementById("root")).render( 24 | 25 | ); 26 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src_mal/pages/ErrorPage.jsx: -------------------------------------------------------------------------------- 1 | import ErrorElement from "../ui/ErrorElement"; 2 | 3 | export default function ErrorPage({title, text, type}) { 4 | return ( 5 |
6 | 7 |
8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src_mal/pages/PlayerOld.txt: -------------------------------------------------------------------------------- 1 | // import axios from "axios"; 2 | // import { useEffect, useState } from "react"; 3 | // import { useParams } from "react-router-dom"; 4 | 5 | // export default function Player(query) { 6 | // const magnetURI = useParams().magnetId; 7 | // const [videoSrc, setVideoSrc] = useState(""); 8 | // const [subtitleSrc, setSubtitleSrc] = useState(""); 9 | // console.log(magnetURI); 10 | 11 | // const handleSubmit = async (e) => { 12 | // e.preventDefault(); 13 | // try { 14 | // // Step 1: Add the torrent 15 | // await axios.get( 16 | // `http://localhost:64621/add/${encodeURIComponent(magnetURI)}`, 17 | // ); 18 | // // Step 2: Set the video source for streaming 19 | // setVideoSrc( 20 | // `http://localhost:64621/stream/${encodeURIComponent(magnetURI)}`, 21 | // ); 22 | // // Step 3: Set the subtitle source 23 | // setSubtitleSrc( 24 | // `http://localhost:64621/subtitles/${encodeURIComponent(magnetURI)}`, 25 | // ); 26 | // } catch (error) { 27 | // console.error("Error adding the torrent or streaming video", error); 28 | // } 29 | // }; 30 | 31 | // console.log(videoSrc, subtitleSrc); 32 | 33 | // return ( 34 | //
35 | //
36 | //
37 | // 38 | //
39 | // {videoSrc && ( 40 | // 51 | // )} 52 | //
53 | //
54 | // ); 55 | // } 56 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src_mal/pages/VideoJs.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import videojs from 'video.js'; 3 | import 'video.js/dist/video-js.css'; 4 | 5 | export const VideoJS = (props) => { 6 | const videoRef = React.useRef(null); 7 | const playerRef = React.useRef(null); 8 | const {options, onReady} = props; 9 | 10 | useEffect(() => { 11 | 12 | if (!playerRef.current) { 13 | const videoElement = document.createElement("video-js"); 14 | 15 | videoElement.classList.add('vjs-big-play-centered'); 16 | videoRef.current.appendChild(videoElement); 17 | 18 | const player = playerRef.current = videojs(videoElement, options, () => { 19 | videojs.log('player is ready'); 20 | onReady && onReady(player); 21 | }); 22 | } else { 23 | const player = playerRef.current; 24 | 25 | player.autoplay(options.autoplay); 26 | player.src(options.sources); 27 | } 28 | }, [options, videoRef]); 29 | 30 | // Dispose the Video.js player when the functional component unmounts, otherwise memory leak ho jayega lmao 31 | useEffect(() => { 32 | const player = playerRef.current; 33 | 34 | return () => { 35 | if (player && !player.isDisposed()) { 36 | player.dispose(); 37 | playerRef.current = null; 38 | } 39 | }; 40 | }, [playerRef]); 41 | 42 | return ( 43 |
44 |
45 |
46 | ); 47 | } 48 | 49 | export default VideoJS; 50 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src_mal/ui/AppLayout.jsx: -------------------------------------------------------------------------------- 1 | import { Theme } from "@radix-ui/themes"; 2 | import { Outlet, useNavigation } from "react-router-dom"; 3 | import Loader from "./Loader"; 4 | import { Toaster } from "sonner"; 5 | import { useState } from "react"; 6 | import Header from "../components/Header"; 7 | 8 | export default function AppLayout({ props }) { 9 | const navigation = useNavigation(); 10 | const isLoading = navigation.state === "loading"; 11 | const [theme, setTheme] = useState("dark"); 12 | 13 | function toggleTheme() { 14 | setTheme((prev) => (prev === "dark" ? "light" : "dark")); 15 | console.log(theme); 16 | } 17 | 18 | return ( 19 | 20 | {/*
*/} 21 | {/* */} 38 | 51 |
52 | {isLoading && } 53 |
54 |
{props || }
55 |
56 |
57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src_mal/ui/CenteredLoader.jsx: -------------------------------------------------------------------------------- 1 | import Loader from "./Loader" 2 | 3 | export default function CenteredLoader() { 4 | return ( 5 |
6 | 7 |
8 | ) 9 | } 10 | 11 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src_mal/ui/ErrorElement.jsx: -------------------------------------------------------------------------------- 1 | import { useRouteError } from "react-router-dom"; 2 | import psyduck from "../assets/psyduck.png"; 3 | 4 | export default function ErrorElement({ title="Something Went wrong !", text, type }) { 5 | const error = useRouteError(); 6 | 7 | const style = { 8 | outer: `border-rose-500 bg-rose-500`, 9 | inner: `text-rose-500`, 10 | }; 11 | 12 | if (type === "warning") { 13 | style.outer = `border-orange-500 bg-orange-500`; 14 | style.inner = `text-orange-500`; 15 | } else if (type === "info") { 16 | style.outer = `border-sky-500 bg-sky-500`; 17 | style.inner = `text-sky-500 18 | `; 19 | } 20 | 21 | return ( 22 |
25 |
26 | 27 |
28 | {title} 29 |
30 |

31 | {text || error?.data || error?.message || "Unknown Error"} 32 |

33 |
34 |
35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src_mal/ui/Loader.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | // import "ldrs/lineWobble"; 3 | import animeGif from "../assets/anime.gif"; 4 | import { BarLoader } from "react-spinners"; 5 | export default function Loader() { 6 | return ( 7 |
8 | 9 | 10 |
11 | {/* */} 18 | 19 |
20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/src_mal/utils/api.js: -------------------------------------------------------------------------------- 1 | // jikan api 2 | const BASE_URL_JIKAN = 'https://api.jikan.moe/v4' 3 | const BASE_URL_NYAA = 'https://nyaaapi.onrender.com/nyaa' 4 | 5 | export function SEARCH_ANIME(query, limit=10) { 6 | return `${BASE_URL_JIKAN}/anime?q=${query}&limit=${limit}` 7 | } 8 | 9 | // 'https://nyaaapi.onrender.com/nyaa?q=one%20piece&sort=seeders&order=desc&page=1' 10 | export function SEARCH_TORRENT(query) { 11 | return `${BASE_URL_NYAA}/?q=${query}&sort=seeders&order=desc&page=1&category=anime` 12 | } 13 | 14 | // https://api.jikan.moe/v4/top/anime?&filter=airing&limit=25 15 | export function TOP_AIRING_ANIME() { 16 | return `${BASE_URL_JIKAN}/top/anime?&filter=airing&limit=25&sfw=true&type=tv` 17 | } 18 | 19 | // https://api.jikan.moe/v4/top/anime 20 | // export function TOP_ANIME() { 21 | // return `${BASE_URL_JIKAN}/top/anime` 22 | // } 23 | 24 | // https://api.jikan.moe/v4/top/anime 25 | export function TOP_ANIME(page = 1) { 26 | return `${BASE_URL_JIKAN}/top/anime?page=${page}&limit=25&sfw=true` 27 | } 28 | 29 | // https://api.jikan.moe/v4/anime/{id}/full 30 | export function GET_ANIME_DETAILS_BY_ID(id) { 31 | return `${BASE_URL_JIKAN}/anime/${id}/full` 32 | } 33 | 34 | // https://api.jikan.moe/v4/anime/{id}/episodes 35 | export function GET_ANIME_EPISODES_BY_ID(id) { 36 | return `${BASE_URL_JIKAN}/anime/${id}/episodes` 37 | } 38 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-anonymous-default-export */ 2 | 3 | /** @type {import('tailwindcss').Config} */ 4 | export default { 5 | content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], 6 | theme: { 7 | fontFamily: { 8 | sans: ['ui-sans-serif', 'system-ui'], 9 | inter: ['Inter', 'ui-sans-serif', 'system-ui'], 10 | 'space-mono': ['Space Mono', 'ui-sans-serif', 'system-ui'] 11 | }, 12 | screens: { 13 | sm: '640px', 14 | // => @media (min-width: 640px) { ... } 15 | 16 | md: '768px', 17 | // => @media (min-width: 768px) { ... } 18 | 19 | lg: '1024px', 20 | // => @media (min-width: 1024px) { ... } 21 | 22 | lg2: '1152px', 23 | 24 | xl: '1380px', 25 | // => @media (min-width: 1280px) { ... } 26 | 27 | '2xl': '1836px' 28 | // => @media (min-width: 1536px) { ... } 29 | }, 30 | boxShadow: { 31 | '3xl': '0 35px 60px 0px rgba(0, 0, 0, 0.8)' 32 | }, 33 | dropShadow: { 34 | '3xl': '0 3px 3px rgba(0, 0, 0, .45)', 35 | '4xl': ['0 35px 35px rgba(0, 0, 0, 0.25)', '0 45px 65px rgba(0, 0, 0, 0.15)'] 36 | }, 37 | extend: {} 38 | }, 39 | plugins: [require("tailwindcss-animated", "@tailwindcss/line-clamp")], 40 | }; 41 | -------------------------------------------------------------------------------- /Archive/Web Version/FRONTEND/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | import eslint from "vite-plugin-eslint"; 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | base: "/zenshin", 7 | plugins: [react(), eslint()], 8 | }); 9 | -------------------------------------------------------------------------------- /Archive/Web Version/Releases/v1.0.0/zenshin v1.0.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hitarth-gg/zenshin/fbd14c17ad9bd67b8643b96e866f70105ab586d7/Archive/Web Version/Releases/v1.0.0/zenshin v1.0.0.zip -------------------------------------------------------------------------------- /Archive/Web Version/readme.md: -------------------------------------------------------------------------------- 1 | - Web Version of Zenshin is outdated and is no longer being worked on upon. Please switch to electron version of Zenshin. 2 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /Electron/zenshin-electron/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | out 4 | .gitignore 5 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'eslint:recommended', 4 | 'plugin:react/recommended', 5 | 'plugin:react/jsx-runtime', 6 | '@electron-toolkit', 7 | '@electron-toolkit/eslint-config-prettier' 8 | ], 9 | rules: { 10 | // Example rules set to 'warn' instead of 'error' 11 | 'no-console': 'warn', 12 | 'no-unused-vars': 'warn', 13 | 'react/prop-types': 'warn', 14 | }, 15 | 16 | } 17 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | out 4 | .DS_Store 5 | *.log* 6 | downloads 7 | test 8 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/.prettierignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | pnpm-lock.yaml 4 | LICENSE.md 5 | tsconfig.json 6 | tsconfig.*.json 7 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/.prettierrc.yaml: -------------------------------------------------------------------------------- 1 | singleQuote: true 2 | semi: false 3 | printWidth: 100 4 | trailingComma: none 5 | plugins: 6 | - prettier-plugin-tailwindcss 7 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint"] 3 | } 4 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Debug Main Process", 6 | "type": "node", 7 | "request": "launch", 8 | "cwd": "${workspaceRoot}", 9 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite", 10 | "windows": { 11 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite.cmd" 12 | }, 13 | "runtimeArgs": ["--sourcemap"], 14 | "env": { 15 | "REMOTE_DEBUGGING_PORT": "9222" 16 | } 17 | }, 18 | { 19 | "name": "Debug Renderer Process", 20 | "port": 9222, 21 | "request": "attach", 22 | "type": "chrome", 23 | "webRoot": "${workspaceFolder}/src/renderer", 24 | "timeout": 60000, 25 | "presentation": { 26 | "hidden": true 27 | } 28 | } 29 | ], 30 | "compounds": [ 31 | { 32 | "name": "Debug All", 33 | "configurations": ["Debug Main Process", "Debug Renderer Process"], 34 | "presentation": { 35 | "order": 1 36 | } 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[typescript]": { 3 | "editor.defaultFormatter": "esbenp.prettier-vscode" 4 | }, 5 | "[javascript]": { 6 | "editor.defaultFormatter": "esbenp.prettier-vscode" 7 | }, 8 | "[json]": { 9 | "editor.defaultFormatter": "esbenp.prettier-vscode" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/README.md: -------------------------------------------------------------------------------- 1 | # zenshin-electron 2 | 3 | An Electron application with React 4 | 5 | ## Recommended IDE Setup 6 | 7 | - [VSCode](https://code.visualstudio.com/) + [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) 8 | 9 | ## Project Setup 10 | 11 | ### Install 12 | 13 | ```bash 14 | $ npm install 15 | ``` 16 | 17 | ### Development 18 | 19 | ```bash 20 | $ npm run dev 21 | ``` 22 | 23 | ### Build 24 | 25 | ```bash 26 | # For windows 27 | $ npm run build:win 28 | 29 | # For macOS 30 | $ npm run build:mac 31 | 32 | # For Linux 33 | $ npm run build:linux 34 | ``` 35 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/build/entitlements.mac.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-jit 6 | 7 | com.apple.security.cs.allow-unsigned-executable-memory 8 | 9 | com.apple.security.cs.allow-dyld-environment-variables 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/build/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hitarth-gg/zenshin/fbd14c17ad9bd67b8643b96e866f70105ab586d7/Electron/zenshin-electron/build/icon.ico -------------------------------------------------------------------------------- /Electron/zenshin-electron/build/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hitarth-gg/zenshin/fbd14c17ad9bd67b8643b96e866f70105ab586d7/Electron/zenshin-electron/build/icon.png -------------------------------------------------------------------------------- /Electron/zenshin-electron/build/icon2.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hitarth-gg/zenshin/fbd14c17ad9bd67b8643b96e866f70105ab586d7/Electron/zenshin-electron/build/icon2.icns -------------------------------------------------------------------------------- /Electron/zenshin-electron/build/iconOld.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hitarth-gg/zenshin/fbd14c17ad9bd67b8643b96e866f70105ab586d7/Electron/zenshin-electron/build/iconOld.ico -------------------------------------------------------------------------------- /Electron/zenshin-electron/build/iconOld.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hitarth-gg/zenshin/fbd14c17ad9bd67b8643b96e866f70105ab586d7/Electron/zenshin-electron/build/iconOld.png -------------------------------------------------------------------------------- /Electron/zenshin-electron/common/announce.js: -------------------------------------------------------------------------------- 1 | const announce = [ 2 | atob('d3NzOi8vdHJhY2tlci5vcGVud2VidG9ycmVudC5jb20='), 3 | atob('d3NzOi8vdHJhY2tlci53ZWJ0b3JyZW50LmRldg=='), 4 | atob('d3NzOi8vdHJhY2tlci5maWxlcy5mbTo3MDczL2Fubm91bmNl'), 5 | atob('d3NzOi8vdHJhY2tlci5idG9ycmVudC54eXov'), 6 | atob('dWRwOi8vb3Blbi5zdGVhbHRoLnNpOjgwL2Fubm91bmNl'), 7 | atob('aHR0cDovL255YWEudHJhY2tlci53Zjo3Nzc3L2Fubm91bmNl'), 8 | atob('dWRwOi8vdHJhY2tlci5vcGVudHJhY2tyLm9yZzoxMzM3L2Fubm91bmNl'), 9 | atob('dWRwOi8vZXhvZHVzLmRlc3luYy5jb206Njk2OS9hbm5vdW5jZQ=='), 10 | atob('dWRwOi8vdHJhY2tlci5jb3BwZXJzdXJmZXIudGs6Njk2OS9hbm5vdW5jZQ=='), 11 | atob('dWRwOi8vOS5yYXJiZy50bzoyNzEwL2Fubm91bmNl'), 12 | atob('dWRwOi8vdHJhY2tlci50b3JyZW50LmV1Lm9yZzo0NTEvYW5ub3VuY2U='), 13 | atob('aHR0cDovL29wZW4uYWNnbnh0cmFja2VyLmNvbTo4MC9hbm5vdW5jZQ=='), 14 | atob('aHR0cDovL2FuaWRleC5tb2U6Njk2OS9hbm5vdW5jZQ=='), 15 | atob('aHR0cDovL3RyYWNrZXIuYW5pcmVuYS5jb206ODAvYW5ub3VuY2U=') 16 | ] 17 | 18 | export default announce 19 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/common/unpacker.js: -------------------------------------------------------------------------------- 1 | function unpackJS(p, a, c, k, e, d) { 2 | e = function (c) { 3 | return ( 4 | (c < a ? '' : e(parseInt(c / a))) + 5 | ((c = c % a) > 35 ? String.fromCharCode(c + 29) : c.toString(36)) 6 | ) 7 | } 8 | if (!''.replace(/^/, String)) { 9 | while (c--) { 10 | d[e(c)] = k[c] || e(c) 11 | } 12 | k = [ 13 | function (e) { 14 | return d[e] 15 | } 16 | ] 17 | e = function () { 18 | return '\\w+' 19 | } 20 | c = 1 21 | } 22 | while (c--) { 23 | if (k[c]) { 24 | p = p.replace(new RegExp('\\b' + e(c) + '\\b', 'g'), k[c]) 25 | } 26 | } 27 | return p 28 | } 29 | 30 | export default function rageParse(str) { 31 | const pos = str.indexOf('return p}') 32 | let res = '[' + str.slice(pos + 9 + 1, str.length - 2) + ']' 33 | 34 | let ix1 = res.lastIndexOf(";'") + 2 35 | const a1 = res.slice(1, ix1) 36 | 37 | let tempString = res.slice(ix1 + 1) 38 | let ix2 = tempString.indexOf(',') + ix1 + 1 39 | const a2 = res.slice(ix1 + 1, ix2) 40 | 41 | tempString = res.slice(ix2 + 1) 42 | let ix3 = tempString.indexOf(',') + ix2 + 1 43 | const a3 = res.slice(ix2 + 1, ix3) 44 | 45 | tempString = res.slice(ix3 + 1) 46 | tempString = tempString.slice(1, tempString.lastIndexOf(".split('|')") - 1) 47 | const a4 = tempString.split('|') 48 | 49 | tempString = res.slice(res.indexOf(".split('|'),") + 12) 50 | const a5 = tempString.slice(0, tempString.indexOf(',')) 51 | 52 | const a6 = {} 53 | 54 | const url = unpackJS(a1, a2, a3, a4, a5, a6) 55 | return url 56 | } 57 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/common/utils.js: -------------------------------------------------------------------------------- 1 | const encUrls = { 2 | tosho: decodeURIComponent(atob('aHR0cHM6Ly9mZWVkLmFuaW1ldG9zaG8ub3Jn')), 3 | pahe: decodeURIComponent(atob('aHR0cHM6Ly9hbmltZXBhaGUucnU=')), 4 | paheimages: decodeURIComponent(atob('aHR0cHM6Ly9pLmFuaW1lcGFoZS5ydQ==')), 5 | zenshinSupabase: decodeURIComponent( 6 | atob('aHR0cDovL3plbnNoaW4tc3VwYWJhc2UtYXBpLW15aWcub25yZW5kZXIuY29t') 7 | ), 8 | nyaaApi: decodeURIComponent(atob('aHR0cHM6Ly9ueWFhYXBpLm9ucmVuZGVyLmNvbS9ueWFh')) 9 | } 10 | 11 | function isTruthyWithZero(value) { 12 | return Boolean(value) || value === 0 13 | } 14 | 15 | export { isTruthyWithZero } 16 | 17 | export default encUrls 18 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/electron-builder.yml: -------------------------------------------------------------------------------- 1 | appId: com.zenshin.app 2 | productName: zenshin 3 | directories: 4 | buildResources: build 5 | files: 6 | - '!**/.vscode/*' 7 | - '!src/*' 8 | - '!electron.vite.config.{js,ts,mjs,cjs}' 9 | - '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}' 10 | - '!{.env,.env.*,.npmrc,pnpm-lock.yaml}' 11 | asarUnpack: 12 | - resources/** 13 | win: 14 | executableName: Zenshin 15 | nsis: 16 | artifactName: ${name}-${version}-setup.${ext} 17 | shortcutName: ${productName} 18 | uninstallDisplayName: ${productName} 19 | createDesktopShortcut: always 20 | mac: 21 | entitlementsInherit: build/entitlements.mac.plist 22 | extendInfo: 23 | - NSCameraUsageDescription: Application requests access to the device's camera. 24 | - NSMicrophoneUsageDescription: Application requests access to the device's microphone. 25 | - NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder. 26 | - NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder. 27 | notarize: false 28 | dmg: 29 | artifactName: ${name}-${version}.${ext} 30 | linux: 31 | target: 32 | - AppImage 33 | - snap 34 | - deb 35 | maintainer: electronjs.org 36 | category: Utility 37 | appImage: 38 | artifactName: ${name}-${version}.${ext} 39 | npmRebuild: false 40 | publish: 41 | provider: generic 42 | url: https://example.com/auto-updates 43 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/electron.vite.config.mjs: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path' 2 | import { defineConfig, externalizeDepsPlugin } from 'electron-vite' 3 | import react from '@vitejs/plugin-react' 4 | 5 | export default defineConfig({ 6 | main: { 7 | plugins: [externalizeDepsPlugin()] 8 | }, 9 | preload: { 10 | plugins: [externalizeDepsPlugin()] 11 | }, 12 | renderer: { 13 | resolve: { 14 | alias: { 15 | '@renderer': resolve('src/renderer/src') 16 | } 17 | }, 18 | plugins: [react()] 19 | } 20 | }) 21 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zenshin", 3 | "version": "2.2.2", 4 | "description": "zenshin", 5 | "main": "./out/main/index.js", 6 | "author": "hitarth-gg", 7 | "homepage": "https://github.com/hitarth-gg/zenshin", 8 | "scripts": { 9 | "format": "prettier --write .", 10 | "lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix", 11 | "start": "electron-vite preview", 12 | "dev": "electron-vite dev", 13 | "build": "electron-vite build", 14 | "postinstall": "electron-builder install-app-deps", 15 | "build:unpack": "npm run build && electron-builder --dir", 16 | "build:win": "npm run build && electron-builder --win", 17 | "build:mac": "npm run build && electron-builder --mac", 18 | "build:linux": "npm run build && electron-builder --linux" 19 | }, 20 | "dependencies": { 21 | "@electron-toolkit/preload": "^3.0.1", 22 | "@electron-toolkit/utils": "^3.0.0", 23 | "@fontsource/inter": "^5.0.20", 24 | "@fontsource/space-mono": "^5.0.21", 25 | "@radix-ui/react-icons": "^1.3.0", 26 | "@radix-ui/themes": "^3.1.1", 27 | "@studio-freight/react-lenis": "^0.0.47", 28 | "@tailwindcss/line-clamp": "^0.4.4", 29 | "@tanstack/react-query": "^5.51.1", 30 | "@tanstack/react-query-devtools": "^5.51.1", 31 | "@wordpress/autop": "^4.6.0", 32 | "axios": "^1.7.4", 33 | "chalk": "^5.0.0", 34 | "comlink": "^4.4.2", 35 | "cors": "^2.8.5", 36 | "date-fns": "^3.6.0", 37 | "discord-rpc": "^4.0.1", 38 | "electron-deeplink": "^1.0.10", 39 | "electron-is-dev": "^3.0.1", 40 | "events": "^3.3.0", 41 | "express": "^4.18.2", 42 | "framer-motion": "^11.11.10", 43 | "fs-extra": "^11.1.1", 44 | "hls.js": "^1.5.17", 45 | "html-react-parser": "^5.1.12", 46 | "node-fetch": "^3.3.0", 47 | "path": "^0.12.7", 48 | "plyr": "^3.7.8", 49 | "plyr-react": "^5.3.0", 50 | "react-infinite-scroll-component": "^6.1.0", 51 | "react-intersection-observer": "^9.13.1", 52 | "react-responsive-carousel": "^3.2.23", 53 | "react-router-dom": "^6.25.0", 54 | "react-sliding-pane": "^7.3.0", 55 | "react-spinners": "^0.14.1", 56 | "sonner": "^1.5.0", 57 | "tailwindcss-animated": "^1.1.2", 58 | "webtorrent": "^2.5.19", 59 | "ws": "^8.18.0", 60 | "xml2js": "^0.6.2" 61 | }, 62 | "devDependencies": { 63 | "@electron-toolkit/eslint-config": "^1.0.2", 64 | "@electron-toolkit/eslint-config-prettier": "^2.0.0", 65 | "@vitejs/plugin-react": "^4.3.1", 66 | "autoprefixer": "^10.4.20", 67 | "electron": "^34.0.2", 68 | "electron-builder": "^25.1.8", 69 | "electron-vite": "^2.3.0", 70 | "eslint": "^8.57.0", 71 | "eslint-plugin-react": "^7.34.3", 72 | "postcss": "^8.4.41", 73 | "prettier": "^3.3.3", 74 | "prettier-plugin-tailwindcss": "^0.6.8", 75 | "react": "^18.3.1", 76 | "react-dom": "^18.3.1", 77 | "tailwindcss": "^3.4.10", 78 | "vite": "^5.3.1" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/resources/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hitarth-gg/zenshin/fbd14c17ad9bd67b8643b96e866f70105ab586d7/Electron/zenshin-electron/resources/icon.ico -------------------------------------------------------------------------------- /Electron/zenshin-electron/resources/iconOld.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hitarth-gg/zenshin/fbd14c17ad9bd67b8643b96e866f70105ab586d7/Electron/zenshin-electron/resources/iconOld.png -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/main/animepahe/middlewares/cookies.js: -------------------------------------------------------------------------------- 1 | import { app } from 'electron' 2 | import fs from 'fs' 3 | import path from 'path' 4 | import encUrls from '../../../../common/utils.js' 5 | 6 | const baseUrl = encUrls.pahe 7 | 8 | // Middleware to read cookies from cookies.json and attach the base URL and cookies to the request object 9 | async function cookieMiddleware(req, res, next) { 10 | try { 11 | // Get the path to the downloads folder 12 | const zenshinPathDocuments = app.getPath('documents') + '/Zenshin' 13 | 14 | // Read the cookies.json file asynchronously 15 | const data = await fs.promises.readFile(path.join(zenshinPathDocuments, 'cookies.json'), 'utf8') 16 | 17 | // Parse the cookies and generate the cookie string 18 | const cookieString = JSON.parse(data) 19 | .map((cookie) => `${cookie.name}=${cookie.value}`) 20 | .join('; ') 21 | 22 | // Attach the cookieString and baseUrl to the request object 23 | req.cookiesString = cookieString 24 | req.baseUrl = baseUrl 25 | 26 | next() 27 | } catch (error) { 28 | console.error('Failed to read cookies or base URL:', error) 29 | res.status(500).send({ error: 'Could not read cookies or base URL.' }) 30 | } 31 | } 32 | 33 | export default cookieMiddleware 34 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/main/settings.js: -------------------------------------------------------------------------------- 1 | import { app, dialog } from 'electron' 2 | import path from 'path' 3 | import fs from 'fs' 4 | 5 | const zenshinPathDocuments = app.getPath('documents') + '/Zenshin' 6 | const settingsPath = path.join(zenshinPathDocuments, 'settings.json') 7 | let defaultDownloadsDir = path.join(app.getPath('downloads'), 'ZenshinDownloads') 8 | 9 | let backendPort = 64621 10 | 11 | export default class Settings { 12 | constructor() { 13 | this.defaultSettings = { 14 | uploadLimit: -1, 15 | downloadLimit: -1, 16 | downloadsFolderPath: defaultDownloadsDir, 17 | backendPort: backendPort, 18 | broadcastDiscordRpc: true, 19 | extensionUrls: {} 20 | } 21 | 22 | this.settings = this.loadSettings() 23 | } 24 | 25 | loadSettings() { 26 | // create settings.json file if it doesn't exist 27 | let settings = this.defaultSettings 28 | if (!fs.existsSync(settingsPath)) { 29 | try { 30 | this.settings = settings 31 | this.saveSettings() 32 | } catch (error) { 33 | console.error('Error creating settings.json file:', error) 34 | dialog.showErrorBox('Error creating settings.json file:', error.message) 35 | } 36 | } else { 37 | try { 38 | settings = JSON.parse(fs.readFileSync(settingsPath)) 39 | } catch (error) { 40 | console.error('Error reading settings.json file:', error) 41 | dialog.showErrorBox('Error reading settings.json file:', error.message) 42 | } 43 | } 44 | return settings 45 | } 46 | 47 | saveSettings() { 48 | try { 49 | fs.writeFileSync(settingsPath, JSON.stringify(this.settings, null, 2)) // null, 2 is for pretty printing 50 | } catch (error) { 51 | console.error('Error writing settings.json file:', error) 52 | dialog.showErrorBox('Error writing settings.json file:', error.message) 53 | } 54 | } 55 | 56 | get(key) { 57 | return this.settings[key] 58 | } 59 | 60 | set(key, value) { 61 | this.settings[key] = value 62 | this.saveSettings() 63 | } 64 | 65 | getSettings() { 66 | return this.settings 67 | } 68 | 69 | getDefaultSettings() { 70 | return this.defaultSettings 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/preload/index.js: -------------------------------------------------------------------------------- 1 | import { contextBridge, ipcRenderer } from 'electron' 2 | import { electronAPI } from '@electron-toolkit/preload' 3 | 4 | // Custom APIs for renderer 5 | const api = { 6 | minimize: () => ipcRenderer.send('minimize-window'), 7 | maximize: () => ipcRenderer.send('maximize-window'), 8 | close: () => ipcRenderer.send('close-window'), 9 | oauth: (url) => ipcRenderer.send('oauth-login', url), 10 | openVlc: (url) => ipcRenderer.send('open-vlc', url), 11 | openAnimePahe: (url) => ipcRenderer.send('open-animepahe', url), 12 | windowReload: () => ipcRenderer.send('reload-window'), 13 | changeBackendPort: (port) => ipcRenderer.send('change-backend-port', port), 14 | openFolder: (folder) => ipcRenderer.send('open-folder', folder), 15 | changeDownloadsFolder: () => { 16 | ipcRenderer.send('change-downloads-folder') 17 | return new Promise((resolve) => { 18 | ipcRenderer.once('receive-settings-json', (event, updatedSettings) => 19 | resolve(updatedSettings) 20 | ) 21 | }) 22 | }, 23 | saveToSettings: (key, value) => { 24 | ipcRenderer.send('save-to-settings', key, value) 25 | }, 26 | getSettingsJson: () => { 27 | ipcRenderer.send('get-settings-json') 28 | return new Promise((resolve) => { 29 | ipcRenderer.once('receive-settings-json', (event, data) => resolve(data)) 30 | }) 31 | }, 32 | setDiscordRpc: (activityDetails) => { 33 | ipcRenderer.send('set-discord-rpc', activityDetails) 34 | }, 35 | broadcastDiscordRpc: (value) => { 36 | ipcRenderer.send('broadcast-discord-rpc', value) 37 | } 38 | } 39 | 40 | const deeplinks = { 41 | receiveDeeplink: (callback) => 42 | ipcRenderer.on('deeplink-received', (event, link) => callback(link)) 43 | } 44 | 45 | const combinedAPI = { 46 | ...electronAPI, 47 | ...deeplinks, 48 | ...api // Include your custom APIs 49 | } 50 | 51 | if (process.contextIsolated) { 52 | try { 53 | contextBridge.exposeInMainWorld('electron', combinedAPI) 54 | contextBridge.exposeInMainWorld('api', api) 55 | } catch (error) { 56 | console.error(error) 57 | } 58 | } else { 59 | window.electron = electronAPI 60 | window.api = api 61 | } 62 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | zenshin 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Single Page Apps for GitHub Pages 6 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/public/zenshinLogoSmall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hitarth-gg/zenshin/fbd14c17ad9bd67b8643b96e866f70105ab586d7/Electron/zenshin-electron/src/renderer/public/zenshinLogoSmall.png -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/public/zenshinLogoSmall2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hitarth-gg/zenshin/fbd14c17ad9bd67b8643b96e866f70105ab586d7/Electron/zenshin-electron/src/renderer/public/zenshinLogoSmall2.png -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/assets/anime.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hitarth-gg/zenshin/fbd14c17ad9bd67b8643b96e866f70105ab586d7/Electron/zenshin-electron/src/renderer/src/assets/anime.gif -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/assets/gradient1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hitarth-gg/zenshin/fbd14c17ad9bd67b8643b96e866f70105ab586d7/Electron/zenshin-electron/src/renderer/src/assets/gradient1.jpg -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/assets/loundraw.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hitarth-gg/zenshin/fbd14c17ad9bd67b8643b96e866f70105ab586d7/Electron/zenshin-electron/src/renderer/src/assets/loundraw.jpg -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/assets/pikacon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hitarth-gg/zenshin/fbd14c17ad9bd67b8643b96e866f70105ab586d7/Electron/zenshin-electron/src/renderer/src/assets/pikacon.ico -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/assets/psyduck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hitarth-gg/zenshin/fbd14c17ad9bd67b8643b96e866f70105ab586d7/Electron/zenshin-electron/src/renderer/src/assets/psyduck.png -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/assets/symbols/AniListLogo.jsx: -------------------------------------------------------------------------------- 1 | function AniListLogo({ style = '' }) { 2 | return ( 3 |
4 | 5 |
6 | ) 7 | } 8 | 9 | export default AniListLogo 10 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/assets/symbols/BooksLogo.jsx: -------------------------------------------------------------------------------- 1 | function BooksLogo({ style = '' }) { 2 | return ( 3 |
4 | 11 | 12 | 13 |
14 | ) 15 | } 16 | 17 | export default BooksLogo 18 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/assets/symbols/CassetteTapeLogo.jsx: -------------------------------------------------------------------------------- 1 | function CassetteTapeLogo({ style = '' }) { 2 | return ( 3 |
4 | 11 | 12 | 13 |
14 | ) 15 | } 16 | 17 | export default CassetteTapeLogo 18 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/assets/symbols/EyeLogo.jsx: -------------------------------------------------------------------------------- 1 | function EyeLogo({ style = '' }) { 2 | return ( 3 |
4 | 11 | 12 | 13 |
14 | ) 15 | } 16 | 17 | export default EyeLogo 18 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/assets/symbols/LeafLogo.jsx: -------------------------------------------------------------------------------- 1 | function LeafLogo({ style = '' }) { 2 | return ( 3 |
4 | 11 | 12 | 13 |
14 | ) 15 | } 16 | 17 | export default LeafLogo 18 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/assets/symbols/MountainsLogo.jsx: -------------------------------------------------------------------------------- 1 | function MountainsLogo({ style = '' }) { 2 | return ( 3 |
4 | 11 | 12 | 13 |
14 | ) 15 | } 16 | 17 | export default MountainsLogo 18 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/assets/symbols/MyAnimeListLogo.jsx: -------------------------------------------------------------------------------- 1 | function MyAnimeListLogo({ style = '' }) { 2 | return ( 3 |
4 | 8 |
9 | ) 10 | } 11 | 12 | export default MyAnimeListLogo 13 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/assets/symbols/SortDescLogo.jsx: -------------------------------------------------------------------------------- 1 | function SortDescLogo({ style = '' }) { 2 | return ( 3 |
4 | 11 | 12 | 13 |
14 | ) 15 | } 16 | 17 | export default SortDescLogo 18 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/assets/symbols/TVLogo.jsx: -------------------------------------------------------------------------------- 1 | function TVLogo({ style = '' }) { 2 | return ( 3 |
4 | 11 | 12 | 13 |
14 | ) 15 | } 16 | 17 | export default TVLogo 18 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/assets/symbols/YouTubeLogo.jsx: -------------------------------------------------------------------------------- 1 | function YouTubeLogo({ style = '' }) { 2 | return ( 3 |
4 | 8 |
9 | ) 10 | } 11 | 12 | export default YouTubeLogo 13 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/assets/zenshin1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hitarth-gg/zenshin/fbd14c17ad9bd67b8643b96e866f70105ab586d7/Electron/zenshin-electron/src/renderer/src/assets/zenshin1.png -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/assets/zenshin2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hitarth-gg/zenshin/fbd14c17ad9bd67b8643b96e866f70105ab586d7/Electron/zenshin-electron/src/renderer/src/assets/zenshin2.png -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/assets/zenshinLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hitarth-gg/zenshin/fbd14c17ad9bd67b8643b96e866f70105ab586d7/Electron/zenshin-electron/src/renderer/src/assets/zenshinLogo.png -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/assets/zenshinLogoLowPx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hitarth-gg/zenshin/fbd14c17ad9bd67b8643b96e866f70105ab586d7/Electron/zenshin-electron/src/renderer/src/assets/zenshinLogoLowPx.png -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/components/AnilistAuthCallback.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import CenteredLoader from '../ui/CenteredLoader' 3 | import { toast } from 'sonner' 4 | import { ExclamationTriangleIcon } from '@radix-ui/react-icons' 5 | import { useNavigate } from 'react-router-dom' 6 | 7 | export default function AnilistAuthCallback() { 8 | const navigate = useNavigate() 9 | 10 | useEffect(() => { 11 | const getAccessToken = async () => { 12 | const hash = window.location.hash 13 | 14 | if (hash) { 15 | const params = new URLSearchParams(hash.substring(1)) // Remove the # at the beginning 16 | const accessToken = params.get('access_token') 17 | const tokenType = params.get('token_type') 18 | const expiresIn = params.get('expires_in') 19 | 20 | if (accessToken) { 21 | // Store the access token in local storage 22 | localStorage.setItem('anilist_token', accessToken) 23 | console.log(`Access Token: ${accessToken}`) 24 | console.log(`Token Type: ${tokenType}`) 25 | console.log(`Expires In: ${expiresIn}`) 26 | 27 | window.location.replace('/zenshin') 28 | 29 | toast.success('Successfully logged in to AniList', { 30 | icon: , 31 | classNames: { 32 | title: 'text-green-500' 33 | } 34 | }) 35 | } 36 | } 37 | } 38 | 39 | getAccessToken() 40 | }, []) 41 | 42 | return ( 43 |
44 | 45 |
46 | ) 47 | } 48 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/components/CurrentlyWatchingCard.jsx: -------------------------------------------------------------------------------- 1 | import { Skeleton } from '@radix-ui/themes' 2 | import { format, fromUnixTime } from 'date-fns' 3 | import { useState } from 'react' 4 | import { useNavigate } from 'react-router-dom' 5 | 6 | function CurrentlyWatchingCard({ data }) { 7 | const [imageIsLoading, setImageIsLoading] = useState(true) 8 | const navigate = useNavigate() 9 | 10 | console.log(data) 11 | 12 | return ( 13 |
navigate(`/anime/${data?.id}`)}> 14 |
15 |
16 |
17 | {imageIsLoading && ( 18 | 19 | )} 20 | 25 |
26 |
27 |
{data?.title?.romaji}
28 |
29 | {data?.title?.english} • {data?.title?.native} 30 |
31 |
32 | Episode: {data?.mediaListEntry?.progress} / {data?.episodes || '?'} 33 |
34 |
35 | {data?.airingSchedule?.nodes[0]?.episode 36 | ? `Episode ${data?.airingSchedule.nodes[0].episode} : ${format(fromUnixTime(data?.airingSchedule.nodes[0].airingAt), 'dd-LLL-yyyy hh:mm a')}` 37 | : ''} 38 |
39 |
40 | setImageIsLoading(false)} 44 | onError={() => setImageIsLoading(false)} 45 | /> 46 |
47 |
48 |
49 |
50 |
51 | ) 52 | } 53 | 54 | export default CurrentlyWatchingCard 55 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/components/DownloadMeter.jsx: -------------------------------------------------------------------------------- 1 | import { ArrowDownIcon, ArrowUpIcon, BarChartIcon } from '@radix-ui/react-icons' 2 | import { useEffect, useState } from 'react' 3 | import formatBytes from '../utils/formatBytes' 4 | import { Button, Tooltip } from '@radix-ui/themes' 5 | 6 | function DownloadMeter() { 7 | const [message, setMessage] = useState([]) 8 | const [statusWs, setStatus] = useState('Disconnected') 9 | 10 | useEffect(() => { 11 | // Create a new WebSocket connection 12 | // const socket = new WebSocket('ws://localhost:64622') 13 | const socket = new WebSocket('ws://localhost:64621/ws') 14 | 15 | // Handle WebSocket events 16 | socket.onopen = () => { 17 | console.log('WebSocket connected') 18 | setStatus('Connected') 19 | } 20 | 21 | socket.onmessage = (event) => { 22 | const data = JSON.parse(event.data) 23 | // console.log('Received data:', data) 24 | setMessage(data) 25 | } 26 | 27 | socket.onerror = (error) => { 28 | console.error('WebSocket error:', error) 29 | } 30 | 31 | socket.onclose = () => { 32 | console.log('WebSocket disconnected') 33 | setStatus('Disconnected') 34 | } 35 | 36 | // Cleanup the WebSocket on unmount 37 | return () => { 38 | socket.close() 39 | } 40 | }, []) 41 | 42 | let clientDownloadSpeed = message[0]?.clientDownloadSpeed || 0 43 | let clientUploadSpeed = message[0]?.clientUploadSpeed || 0 44 | const [showFullSpeed, setShowFullSpeed] = useState(false) 45 | const [alwaysShow, setAlwaysShow] = useState(false) 46 | return ( 47 |
48 | 49 | 59 | 60 | {(showFullSpeed || alwaysShow) && ( 61 |
62 |
63 |
64 | {formatBytes(clientDownloadSpeed)}/{/* asddasddas */} 65 | 66 |
67 | 68 |
69 | {formatBytes(clientUploadSpeed)}/s 70 | 71 |
72 |
73 |
74 | )} 75 |
76 | ) 77 | } 78 | 79 | export default DownloadMeter 80 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/components/EpisodesPlayer.jsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@radix-ui/themes' 2 | import { useState } from 'react' 3 | 4 | export default function EpisodesPlayer({ 5 | file, 6 | handleStreamBrowser, 7 | handleStreamVlc, 8 | stopEpisodeDownload, 9 | setCurrentEpisode 10 | }) { 11 | const [isActive, setIsActive] = useState(false) 12 | 13 | return ( 14 |
setIsActive(!isActive)} 16 | className="relative m-2 animate-fade-down cursor-default border border-gray-700 p-2 font-space-mono transition-all duration-100 ease-in-out animate-duration-500 hover:bg-[#1e1e20]" 17 | > 18 |
19 |
20 |
21 |

22 | 23 | {file.name} 24 |

25 | {isActive && ( 26 |
27 | 40 | 52 | 64 |
65 | )} 66 |
67 |
68 |
69 |
70 | ) 71 | } 72 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/components/RecentActivity.jsx: -------------------------------------------------------------------------------- 1 | import { useScroll, motion, useTransform } from 'framer-motion' 2 | import RecentActivityCard from './RecentActivityCard' 3 | 4 | export default function RecentActivity({ data }) { 5 | const { scrollY } = useScroll() 6 | 7 | const yTop = useTransform(scrollY, [0, 1000], [0, -360]) 8 | const yBottom = useTransform(scrollY, [0, 800], [0, 360]) 9 | 10 | return ( 11 |
12 | {/* Only render gradients if scrollY is less than 900 */} 13 | 14 |
15 | {data.slice(0, 3).map((item, index) => ( 16 | 17 | ))} 18 |
19 |
20 | 21 |
22 | {data.slice(3, 6).map((item, index) => ( 23 | 24 | ))} 25 |
26 |
27 |
28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/components/RecentActivityCard.jsx: -------------------------------------------------------------------------------- 1 | import { format } from 'date-fns' 2 | import { useNavigate } from 'react-router-dom' 3 | import '../index.css' 4 | import { useZenshinContext } from '../utils/ContextProvider' 5 | import { Tooltip } from '@radix-ui/themes' 6 | 7 | export default function RecentActivityCard({ data }) { 8 | const navigate = useNavigate() 9 | 10 | function handleClick() { 11 | navigate(`/anime/${data.media.id}`, { state: { data } }) 12 | } 13 | 14 | const zenshinContext = useZenshinContext() 15 | const { glow } = zenshinContext 16 | 17 | return ( 18 |
handleClick()} 20 | className="skew3d group relative mt-6 flex w-56 cursor-pointer flex-col items-center justify-center gap-y-2 transition-all ease-in-out hover:scale-110" 21 | > 22 |
23 |
24 | {data?.media?.title?.romaji} 25 |
26 |
27 | 32 | 33 |
34 |
35 | 40 | {data?.user?.name} 41 |
42 |
43 | 44 | {/* FOR IMAGE GLOW */} 45 | {/* {glow && ( 46 | 51 | )} */} 52 |
53 | ) 54 | } 55 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/components/SearchResults.jsx: -------------------------------------------------------------------------------- 1 | import { format } from 'date-fns' 2 | import { useNavigate } from 'react-router-dom' 3 | 4 | export default function SearchResults({ data, setIsActive }) { 5 | const navigate = useNavigate() 6 | function handleClick() { 7 | navigate(`/anime/${data.id}`) 8 | setIsActive(false) 9 | } 10 | const date = data?.startDate 11 | ? new Date(data.startDate.year, data.startDate.month - 1, data.startDate.day) 12 | : null 13 | 14 | return ( 15 |
handleClick()} 17 | className="hover:drop-shadow-xl flex animate-fade cursor-pointer gap-x-5 bg-[#111113] px-2 py-1 font-inter transition-all duration-200 ease-in-out hover:z-10 hover:scale-105 hover:rounded-md hover:bg-[#232326]" 18 | > 19 | img 24 |
25 |
{data.title.romaji}
26 | 27 |
28 |

29 | {data.format} - {`${data.episodes ? data.episodes : '?'} episodes`} ({data.status}) 30 |

31 | {date &&

{format(new Date(date), 'MMMM yyyy')}

} 32 |
33 |
34 |
35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/components/Sidebar.jsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@radix-ui/themes' 2 | import AniListLogo from '../assets/symbols/AniListLogo' 3 | import { Link, useNavigate } from 'react-router-dom' 4 | import Pikacon from '../assets/pikacon.ico' 5 | 6 | function Sidebar() { 7 | const navigate = useNavigate() 8 | return ( 9 |
10 |
11 | 17 | {/* */} 18 | {/* 19 | 28 | */} 29 | 39 | 40 | {/* */} 41 | 42 | 55 |
56 |
57 | ) 58 | } 59 | 60 | export default Sidebar 61 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/components/StreamStats.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | import formatBytes from '../utils/formatBytes' 3 | import { useZenshinContext } from '../utils/ContextProvider' 4 | 5 | export default function StreamStats({ magnetURI }) { 6 | const [details, setDetails] = useState(null) 7 | const { backendPort } = useZenshinContext() 8 | useEffect(() => { 9 | const fetchDetails = () => { 10 | fetch(`http://localhost:${backendPort}/details/${encodeURIComponent(magnetURI)}`) 11 | .then((response) => response.json()) 12 | .then((data) => setDetails(data)) 13 | .catch((error) => console.error('Error fetching torrent details:', error)) 14 | } 15 | 16 | // Fetch details immediately 17 | fetchDetails() 18 | 19 | // Set interval to fetch details every 1 second 20 | const intervalId = setInterval(fetchDetails, 1000) 21 | 22 | // Clear interval on component unmount 23 | return () => clearInterval(intervalId) 24 | }, [magnetURI]) 25 | 26 | return ( 27 |
28 |
{details?.name}
29 |
30 |
31 |

32 | Size: {formatBytes(details?.length)} 33 |

34 |

35 | Downloaded: {formatBytes(details?.downloaded)} 36 |

37 |

38 | Uploaded: {formatBytes(details?.uploaded)} 39 |

40 |
41 |
42 | {/*
*/} 43 |

44 |

Download Speed:

{' '} 45 |

{formatBytes(details?.downloadSpeed)} /sec

46 |

47 |

48 |

Upload Speed:

49 |

{formatBytes(details?.uploadSpeed)} /sec

50 |

51 |

52 |

Progress:

53 |

{(details?.progress * 100)?.toFixed(2)}%

54 |

55 |
56 |
57 |

58 | Ratio: {details?.ratio?.toFixed(2)} 59 |

60 |

61 | Peers: {details?.numPeers} 62 |

63 |
64 |
65 |
66 | ) 67 | } 68 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/extensions/animepahe/components/AnimePaheSearchBar.jsx: -------------------------------------------------------------------------------- 1 | import { MagnifyingGlassIcon } from '@radix-ui/react-icons' 2 | import { Code, Skeleton, Spinner, TextField } from '@radix-ui/themes' 3 | import { useCallback, useEffect, useRef, useState } from 'react' 4 | import { toast } from 'sonner' 5 | import useGetAnimePaheSearch from '../hooks/useGetAnimePaheSearch' 6 | import AnimePaheSearchResults from './AnimePaheSearchResults' 7 | // import Pikacon from '../../../assets/pikacon.ico' 8 | 9 | export default function AnimePaheSearchBar() { 10 | const [searchText, setSearchText] = useState('') 11 | const [isActive, setIsActive] = useState(false) 12 | 13 | const inputRef = useRef(null) 14 | const searchBarRef = useRef(null) 15 | 16 | useEffect(() => { 17 | function handleClickOutside(event) { 18 | if (searchBarRef.current && !searchBarRef.current.contains(event.target)) { 19 | setIsActive(false) 20 | } 21 | } 22 | 23 | document.addEventListener('mousedown', handleClickOutside) 24 | return () => { 25 | document.removeEventListener('mousedown', handleClickOutside) 26 | } 27 | }, [searchBarRef]) 28 | 29 | const { data: searchData, error, isLoading, status } = useGetAnimePaheSearch(searchText) 30 | 31 | useEffect(() => { 32 | const handleKeyPress = (event) => { 33 | if (event.ctrlKey && event.key === 'k') { 34 | event.preventDefault() 35 | inputRef.current.select() 36 | inputRef.current.focus() 37 | } 38 | } 39 | 40 | document.addEventListener('keydown', handleKeyPress) 41 | 42 | return () => { 43 | document.removeEventListener('keydown', handleKeyPress) 44 | } 45 | }, [searchText]) 46 | 47 | return ( 48 |
49 | setSearchText(e.target.value)} 52 | ref={inputRef} 53 | type="text" 54 | value={searchText} 55 | onFocus={() => setIsActive(true)} 56 | // onBlur={() => setIsActive(false)} 57 | > 58 | 59 | 60 | 61 | 62 | 63 | ctrl 64 | 65 | 66 | k 67 | 68 | 69 | 70 | 71 | {isActive && ( 72 |
73 | {/* {true && ( */} 74 | {isLoading && ( 75 |
76 |
77 | 78 |
79 | 80 | 81 | 82 |
83 |
84 | {/* */} 85 |
86 | )} 87 | 88 | {searchData?.data?.map((x) => ( 89 | 90 | ))} 91 |
92 | )} 93 |
94 | ) 95 | } 96 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/extensions/animepahe/components/AnimePaheSearchResults.jsx: -------------------------------------------------------------------------------- 1 | import { format } from 'date-fns' 2 | import { useNavigate } from 'react-router-dom' 3 | import { parseAnimepaheImage } from '../utils/parseAnimepaheImage' 4 | 5 | export default function AnimePaheSearchResults({ data, setIsActive }) { 6 | const { id, title, type, episodes, status, season, year, score, poster, session } = data 7 | 8 | const navigate = useNavigate() 9 | function handleClick() { 10 | navigate(`/animepahe/anime/${session}`) 11 | setIsActive(false) 12 | } 13 | const date = data?.startDate 14 | ? new Date(data.startDate.year, data.startDate.month - 1, data.startDate.day) 15 | : null 16 | 17 | console.log(parseAnimepaheImage(poster)) 18 | 19 | return ( 20 |
handleClick()} 22 | className="hover:drop-shadow-xl flex animate-fade cursor-pointer gap-x-5 bg-[#111113] px-2 py-1 font-inter transition-all duration-200 ease-in-out hover:z-10 hover:scale-105 hover:rounded-md hover:bg-[#232326]" 23 | > 24 | img 29 |
30 |
{title}
31 | 32 |
33 |

34 | {type} - {`${episodes ? episodes : '?'} episodes`} ({status}) 35 |

36 | {year &&

{`${season} ${year}`}

} 37 |
38 |
39 |
40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/extensions/animepahe/components/AnimepaheEpisodeCard.jsx: -------------------------------------------------------------------------------- 1 | import { PlayIcon } from '@radix-ui/react-icons' 2 | import { Skeleton } from '@radix-ui/themes' 3 | import { formatDistanceToNow } from 'date-fns' 4 | import { useState } from 'react' 5 | import { parseAnimepaheImage } from '../utils/parseAnimepaheImage' 6 | import { useNavigate } from 'react-router-dom' 7 | 8 | function AnimepaheEpisodeCard({ data }) { 9 | const [imageIsLoading, setImageIsLoading] = useState(true) 10 | 11 | //2024-10-13 02:54:45 12 | const timeAgo = (timestamp) => { 13 | return formatDistanceToNow(new Date(timestamp), { addSuffix: true }).replace('about', '') 14 | } 15 | 16 | const navigate = useNavigate() 17 | 18 | const { 19 | id, 20 | anime_id, 21 | anime_title, 22 | anime_session, 23 | episode, 24 | episode2, 25 | snapshot, 26 | session, 27 | created_at 28 | } = data 29 | 30 | return ( 31 |
handleClick()} 33 | className="m-4 flex animate-fade cursor-pointer flex-col items-center justify-center gap-y-2 font-space-mono transition-all ease-in-out hover:scale-110" 34 | > 35 |
navigate(`/animepahe/anime/${anime_session}`)} 38 | > 39 | {snapshot ? ( 40 |
41 | {imageIsLoading && ( 42 | 43 | )} 44 | {`${anime_title}`} setImageIsLoading(false)} 49 | onError={() => setImageIsLoading(false)} 50 | /> 51 |
52 | ) : ( 53 | 54 | )} 55 |
56 |
{ 59 | e.stopPropagation() 60 | navigate(`/animepahe/anime/${anime_session}`) 61 | }} 62 | > 63 |
64 | {anime_title} 65 |
66 | 67 |
68 |

{timeAgo(created_at)}

69 |

Episode {episode}

70 |
71 |
72 |
73 | ) 74 | } 75 | 76 | export default AnimepaheEpisodeCard 77 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/extensions/animepahe/hooks/useGetAnimeAnimePahe.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from '@tanstack/react-query' 2 | import { animepaheDetails } from '../utils' 3 | 4 | export default function useGetAnimeAnimePahe(id) { 5 | const { isLoading, data, error, status } = useQuery({ 6 | queryKey: ['animepahe_cur_anime', id], 7 | queryFn: () => { 8 | if (id) return animepaheDetails(id) 9 | return null 10 | }, 11 | staleTime: 1000 * 60 * 5 // 5 mins 12 | }) 13 | 14 | return { isLoading, data, error, status } 15 | } 16 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/extensions/animepahe/hooks/useGetAnimePaheEps.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from '@tanstack/react-query' 2 | import { animepaheEpisodes } from '../utils' 3 | 4 | export default function useGetAnimePaheEps(id, page) { 5 | const { isLoading, data, error, status } = useQuery({ 6 | queryKey: ['animepahe_cur_anime_eps', id, page], 7 | queryFn: () => { 8 | if (id) return animepaheEpisodes(id, page) 9 | return null 10 | }, 11 | staleTime: 1000 * 60 * 10 // 5 mins 12 | }) 13 | 14 | return { isLoading, data, error, status } 15 | } 16 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/extensions/animepahe/hooks/useGetAnimePaheEpsOfPage.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from '@tanstack/react-query' 2 | import { animepaheEpisodesOfPage } from '../utils' 3 | 4 | export default function useGetAnimePaheEpsOfPage(id, page) { 5 | const { isLoading, data, error, status } = useQuery({ 6 | queryKey: ['animepahe_cur_anime_eps_of_page', id, page], 7 | queryFn: () => { 8 | if (id) return animepaheEpisodesOfPage(id, page) 9 | return null 10 | }, 11 | staleTime: 1000 * 60 * 3 // mins 12 | }) 13 | 14 | return { isLoading, data, error, status } 15 | } 16 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/extensions/animepahe/hooks/useGetAnimePahePlayData.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from '@tanstack/react-query' 2 | import { animepahePlay } from '../utils' 3 | 4 | export default function useGetAnimePaheEps(id, epid) { 5 | const { isLoading, data, error, status } = useQuery({ 6 | queryKey: ['animepahe_play', id, epid], 7 | queryFn: () => { 8 | if (id && epid) return animepahePlay(id, epid) 9 | return null 10 | }, 11 | staleTime: 1000 * 60 * 5 // 5 mins 12 | }) 13 | 14 | return { isLoading, data, error, status } 15 | } 16 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/extensions/animepahe/hooks/useGetAnimePaheSearch.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from '@tanstack/react-query' 2 | import { animepaheSearch } from '../utils' 3 | 4 | export default function useGetAnimePaheSearch(query) { 5 | const { isLoading, data, error, status } = useQuery({ 6 | queryKey: ['animepahe_search', query], 7 | queryFn: () => { 8 | if (query) return animepaheSearch(query) 9 | return null 10 | }, 11 | staleTime: 1000 * 60 * 2 // 2 mins 12 | }) 13 | 14 | return { isLoading, data, error, status } 15 | } 16 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/extensions/animepahe/pages/AnimePahePlayer.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react' 2 | import Hls from 'hls.js' 3 | import Plyr from 'plyr-react' 4 | import { useParams } from 'react-router-dom' 5 | import 'plyr-react/plyr.css' 6 | 7 | const MyComponent = () => { 8 | const ref = useRef(null) 9 | const { videoSrc } = useParams() 10 | 11 | useEffect(() => { 12 | const loadVideo = async () => { 13 | const video = document.getElementById('plyr') 14 | const hls = new Hls() 15 | hls.loadSource(videoSrc) 16 | hls.attachMedia(video) 17 | 18 | if (ref.current) { 19 | ref.current.plyr.media = video 20 | } 21 | 22 | hls.on(Hls.Events.MANIFEST_PARSED, function () { 23 | ref.current.plyr.play() 24 | }) 25 | } 26 | 27 | loadVideo() 28 | 29 | const handleKeyDown = (event) => { 30 | if (ref.current && ref.current.plyr) { 31 | const player = ref.current.plyr 32 | switch (event.key) { 33 | case 'ArrowLeft': 34 | event.preventDefault() // Prevent default browser behavior (scrolling) 35 | player.currentTime = Math.max(player.currentTime - 5, 0) // Seek backward 5 seconds 36 | break 37 | case 'ArrowRight': 38 | event.preventDefault() // Prevent default browser behavior 39 | player.currentTime = Math.min(player.currentTime + 5, player.duration) // Seek forward 5 seconds 40 | break 41 | default: 42 | break 43 | } 44 | } 45 | } 46 | 47 | // Attach event listener to the entire window 48 | window.addEventListener('keydown', handleKeyDown) 49 | 50 | return () => { 51 | window.removeEventListener('keydown', handleKeyDown) 52 | } 53 | }, [videoSrc]) 54 | 55 | return ( 56 |
57 | 77 |
78 | ) 79 | } 80 | 81 | export default function AnimePahePlayer() { 82 | const supported = Hls.isSupported() 83 | 84 | return ( 85 |
86 | {supported ? : 'HLS is not supported in your browser'} 87 |
88 | ) 89 | } 90 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/extensions/animepahe/pages/AnimePahePlayerEmbedded.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react' 2 | import Hls from 'hls.js' 3 | import Plyr from 'plyr-react' 4 | import 'plyr-react/plyr.css' 5 | 6 | const MyComponent = ({ videoSrc }) => { 7 | const ref = useRef(null) 8 | 9 | useEffect(() => { 10 | const loadVideo = async () => { 11 | const video = document.getElementById('plyr') 12 | const hls = new Hls() 13 | hls.loadSource(videoSrc) 14 | hls.attachMedia(video) 15 | 16 | if (ref.current) { 17 | ref.current.plyr.media = video 18 | } 19 | 20 | hls.on(Hls.Events.MANIFEST_PARSED, function () { 21 | // ref.current.plyr.play() 22 | video.play() 23 | }) 24 | } 25 | 26 | loadVideo() 27 | 28 | const handleKeyDown = (event) => { 29 | if (ref.current && ref.current.plyr) { 30 | const player = ref.current.plyr 31 | switch (event.key) { 32 | case 'ArrowLeft': 33 | event.preventDefault() // Prevent default browser behavior (scrolling) 34 | player.currentTime = Math.max(player.currentTime - 5, 0) // Seek backward 5 seconds 35 | break 36 | case 'ArrowRight': 37 | event.preventDefault() // Prevent default browser behavior 38 | player.currentTime = Math.min(player.currentTime + 5, player.duration) // Seek forward 5 seconds 39 | break 40 | default: 41 | break 42 | } 43 | } 44 | } 45 | 46 | // Attach event listener to the entire window 47 | window.addEventListener('keydown', handleKeyDown) 48 | 49 | return () => { 50 | window.removeEventListener('keydown', handleKeyDown) 51 | } 52 | }, [videoSrc]) 53 | 54 | return ( 55 |
56 | 77 |
78 | ) 79 | } 80 | 81 | export default function AnimePahePlayerEmbedded({ videoSrc }) { 82 | const supported = Hls.isSupported() 83 | 84 | return ( 85 |
86 | {supported ? : 'HLS is not supported in your browser'} 87 |
88 | ) 89 | } 90 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/extensions/animepahe/utils.js: -------------------------------------------------------------------------------- 1 | let BASE_URL = 'http://localhost:64621/animepahe' 2 | 3 | async function backendPort() { 4 | const response = await window.api.getSettingsJson() 5 | const backendPort = response.backendPort 6 | BASE_URL = `http://localhost:${backendPort}/animepahe` 7 | } 8 | 9 | backendPort() 10 | 11 | export const animepaheLatest = async (page = 1) => { 12 | const response = await fetch(`${BASE_URL}/latest?page=${page}`) 13 | return response.json() 14 | } 15 | export const animepaheDetails = async (page = 1) => { 16 | const response = await fetch(`${BASE_URL}/details?id=${page}`) 17 | return response.json() 18 | } 19 | 20 | export const animepaheEpisodes = async (id, page = 1) => { 21 | const response = await fetch(`${BASE_URL}/getallepisodes?id=${id}`) 22 | const data = await response.json() 23 | return { data: data.episodes } 24 | } 25 | export const animepaheEpisodesOfPage = async (id, page = 1) => { 26 | const response = await fetch(`${BASE_URL}/getepisodesofpage?id=${id}&page=${page}`) 27 | const data = await response.json() 28 | return { data: data.episodes } 29 | } 30 | 31 | export const animepahePlay = async (id, episode) => { 32 | const response = await fetch(`${BASE_URL}/play?id=${id}&episode=${episode}`) 33 | return response.json() 34 | } 35 | 36 | export const animepaheSearch = async (query) => { 37 | const response = await fetch(`${BASE_URL}/search?q=${query}`) 38 | return response.json() 39 | } 40 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/extensions/animepahe/utils/parseAnimepaheImage.js: -------------------------------------------------------------------------------- 1 | let BASE_URL = 'http://localhost:64621/animepahe' 2 | 3 | async function backendPort() { 4 | const response = await window.api.getSettingsJson() 5 | const backendPort = response.backendPort 6 | BASE_URL = `http://localhost:${backendPort}/animepahe` 7 | } 8 | backendPort() 9 | 10 | // convert the image URL to the localhost animepahe image URL 11 | export const parseAnimepaheImage = (url) => { 12 | // if the url is a poster image 13 | if (url && url.includes('poster')) { 14 | const id = url.split('/').pop() 15 | return `${BASE_URL}/image/poster/${id}` 16 | } 17 | 18 | // if the url is a snapshot image 19 | if (url && url.includes('snapshot')) { 20 | const id = url.split('/').pop() 21 | return `${BASE_URL}/image/snapshot/${id}` 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/hooks/useGetAniZipMappings.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from '@tanstack/react-query' 2 | import { getAniZipMappings } from '../utils/helper' 3 | 4 | export default function useGetAniZipMappings(id, anidb = false) { 5 | const { isLoading, data, error, status } = useQuery({ 6 | queryKey: ['cur_anime_anizip', id], 7 | queryFn: () => { 8 | if (id) return getAniZipMappings(id, anidb) 9 | return null 10 | }, 11 | staleTime: 1000 * 60 * 5 // 5 mins 12 | }) 13 | 14 | return { isLoading, data, error, status } 15 | } 16 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/hooks/useGetAnilistProfile.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { getAnilistProfile } from "../utils/auth"; 3 | 4 | export default function useGetAnilistProfile(token) { 5 | const { isLoading, data, error, status } = useQuery({ 6 | queryKey: ["cur_profile"], 7 | queryFn: () => { 8 | if (token) return getAnilistProfile(token); 9 | return null; 10 | }, 11 | staleTime: 1000 * 60 * 20, // 20 mins 12 | }); 13 | 14 | return { isLoading, data, error, status }; 15 | } 16 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/hooks/useGetAnilistSearch.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from '@tanstack/react-query' 2 | import { searchAnilist } from '../utils/helper' 3 | import { isObjectEmpty } from '../utils/objectEmpty' 4 | 5 | export default function useGetAnilistSearch(searchObject, page = 1) { 6 | const { isLoading, data, error, status } = useQuery({ 7 | queryKey: ['anilist_search', searchObject, page], 8 | queryFn: () => { 9 | if (!isObjectEmpty(searchObject)) return searchAnilist(searchObject, page) 10 | return null 11 | }, 12 | staleTime: 1000 * 60 * 20 // 20 mins 13 | }) 14 | 15 | return { isLoading, data, error, status } 16 | } 17 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/hooks/useGetAnimeById.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { getAnimeById } from "../utils/helper"; 3 | 4 | export default function useGetAnimeById(id) { 5 | console.log("Fetching anime with id:", id); 6 | 7 | const { 8 | isLoading, 9 | data: animeData, 10 | error, 11 | status, 12 | } = useQuery({ 13 | queryKey: ["cur_anime", id], 14 | queryFn: () => getAnimeById(id), 15 | staleTime: 1000 * 60 * 20, // 20 mins 16 | }); 17 | 18 | return { isLoading, animeData, error, status }; 19 | } 20 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/hooks/useGetAnimeByMalId.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from '@tanstack/react-query' 2 | import { getAnimeByMalId } from '../utils/helper' 3 | 4 | export default function useGetAnimeByMalId(id) { 5 | console.log('Fetching anime with id:', id) 6 | 7 | const { isLoading, data, error, status } = useQuery({ 8 | queryKey: ['cur_anime_mal', id], 9 | queryFn: () => { 10 | if (id) return getAnimeByMalId(id) 11 | return null 12 | }, 13 | staleTime: 1000 * 60 * 20 // 20 mins 14 | }) 15 | 16 | return { isLoading, data, error, status } 17 | } 18 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/hooks/useGetAnimeEpisodesById.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { getAnimeById, getAnimeEpisodesById } from "../utils/helper"; 3 | 4 | export default function useGetAnimeEpisodesById(id) { 5 | console.log("Fetching anime with id:", id); 6 | 7 | const { 8 | isLoading, 9 | data: animeEpisodes, 10 | error, 11 | status, 12 | } = useQuery({ 13 | queryKey: ["cur_anime_episodes", id], 14 | queryFn: () => getAnimeEpisodesById(id), 15 | staleTime: 1000 * 60 * 5, 16 | 17 | }); 18 | 19 | return { isLoading, animeEpisodes, error, status }; 20 | } 21 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/hooks/useGetAnimepaheReleases.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from '@tanstack/react-query' 2 | import { animepaheLatest } from '../extensions/animepahe/utils' 3 | 4 | export default function useGetAnimepaheReleases(page) { 5 | const { isLoading, data, error, status } = useQuery({ 6 | queryKey: ['animepahe_releases', page], 7 | queryFn: () => { 8 | if (page) { 9 | return animepaheLatest(page) 10 | } 11 | return null 12 | }, 13 | staleTime: 1000 * 60 * 3 // 3 minutes 14 | }) 15 | 16 | return { isLoading, data, error, status } 17 | } 18 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/hooks/useGetMultipleAnilistIds.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from '@tanstack/react-query' 2 | 3 | // // Array of anime IDs 4 | // const ids = [100, 101, 102] // Replace with actual anime IDs you want to fetch 5 | 6 | // // Dynamically construct the GraphQL query 7 | // let query = `query {` 8 | 9 | // ids.forEach((id, index) => { 10 | // query += ` 11 | // anime${index + 1}: Media(id: ${id}) { 12 | // id 13 | // title { 14 | // romaji 15 | // english 16 | // native 17 | // } 18 | // episodes 19 | // streamingEpisodes { 20 | // title 21 | // thumbnail 22 | // url 23 | // site 24 | // } 25 | // description 26 | // }` 27 | // }) 28 | 29 | // query += `}` 30 | 31 | async function getMultipleAnilistIds(ids) { 32 | // Fetch request to the AniList GraphQL API 33 | 34 | console.log('ids:', ids) 35 | 36 | let query = `query {` 37 | ids.forEach((id, index) => { 38 | query += ` 39 | anime${index + 1}: Media(id: ${id}) { 40 | id 41 | coverImage { 42 | extraLarge 43 | large 44 | medium 45 | } 46 | title { 47 | romaji 48 | english 49 | native 50 | } 51 | episodes 52 | streamingEpisodes { 53 | title 54 | thumbnail 55 | url 56 | site 57 | } 58 | description 59 | }` 60 | }) 61 | 62 | query += `}` 63 | 64 | try { 65 | const response = await fetch('https://graphql.anilist.co', { 66 | method: 'POST', 67 | headers: { 68 | 'Content-Type': 'application/json', 69 | Accept: 'application/json' 70 | }, 71 | body: JSON.stringify({ query }) 72 | }) 73 | const data = await response.json() 74 | console.log('Fetched Data:', data); 75 | return data 76 | 77 | } catch (error) { 78 | console.error('Error:', error) 79 | throw new Error(error) 80 | } 81 | } 82 | 83 | export default function useGetMultipleAnilistIds(ids) { 84 | const { isLoading, data, error, status } = useQuery({ 85 | queryKey: ['multiple_ids_anilist', ids.toString()], 86 | queryFn: () => { 87 | if (ids && ids.length > 0) return getMultipleAnilistIds(ids) 88 | return null 89 | }, 90 | staleTime: 1000 * 60 * 5 // 5 mins 91 | }) 92 | 93 | return { isLoading, data, error, status } 94 | } 95 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/hooks/useGetNewReleases.js: -------------------------------------------------------------------------------- 1 | //getNewReleases 2 | import { useQuery } from '@tanstack/react-query' 3 | import { getNewReleases } from '../utils/helper' 4 | 5 | export default function useGetNewReleases(packer = '[SubsPlease]') { 6 | const { isLoading, data, error, status } = useQuery({ 7 | queryKey: ['new_releases', packer], 8 | queryFn: () => getNewReleases(packer), 9 | staleTime: 1000 * 60 * 5 // 5 minutes 10 | }) 11 | 12 | return { isLoading, data, error, status } 13 | } 14 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/hooks/useGetRecentGlobalActivity.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from '@tanstack/react-query' 2 | import { getRecentActivity } from '../utils/helper' 3 | 4 | export default function useGetRecentGlobalActivity() { 5 | const { isLoading, data, error, status } = useQuery({ 6 | queryKey: ['recent_activity'], 7 | queryFn: getRecentActivity, 8 | staleTime: 1000 * 60 * 10 // 10 min 9 | }) 10 | 11 | return { isLoading, data, error, status } 12 | } 13 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/hooks/useGetTopAnime.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from '@tanstack/react-query' 2 | import { getTopAnime } from '../utils/helper' 3 | 4 | export default function useGetTopAnime(page) { 5 | const { 6 | isLoading, 7 | data: topAnime, 8 | error, 9 | status 10 | } = useQuery({ 11 | queryKey: ['top_anime'], 12 | queryFn: () => getTopAnime(page), 13 | staleTime: 1000 * 60 * 60 // 1 hour 14 | }) 15 | 16 | return { isLoading, topAnime, error, status } 17 | } 18 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/hooks/useGetToshoEpisodes.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from '@tanstack/react-query' 2 | import { getToshoEpisodes } from '../utils/helper' 3 | 4 | export default function useGetoToshoEpisodes(quality, aids, eids) { 5 | const { isLoading, data, error, status } = useQuery({ 6 | queryKey: ['tosho_episode_tracker', quality, aids, eids], 7 | queryFn: () => { 8 | if (quality && aids) { 9 | return getToshoEpisodes(quality, aids, eids) 10 | } 11 | return null 12 | }, 13 | enabled: !!(quality && aids), // Ensures the query only runs when the quality + aids is not null, 14 | staleTime: 1000 * 60 * 10 // 10 mins 15 | }) 16 | 17 | return { isLoading, data, error, status } 18 | } 19 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/hooks/useNyaaTracker.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from '@tanstack/react-query' 2 | import { searchTorrent } from '../utils/helper' 3 | 4 | export default function useNyaaTracker(query) { 5 | const { 6 | isLoading, 7 | data: torrents, 8 | error, 9 | status 10 | } = useQuery({ 11 | queryKey: ['nyaa_episode_tracker', query], 12 | queryFn: () => { 13 | if (query) { 14 | return searchTorrent(query) 15 | } 16 | return null 17 | }, 18 | enabled: !!query, // Ensures the query only runs when the query string is not null 19 | staleTime: 1000 * 60 * 10 // 10 mins 20 | }) 21 | 22 | // console.log("Query Status:", status); // Log to check if the query is running 23 | // console.log("Torrent Data:", torrents); // Log the returned data to see its structure 24 | 25 | return { isLoading, torrents, error, status } 26 | } 27 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/hooks/useTopAiringAnime.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { getTopAiringAnime } from "../utils/helper"; 3 | 4 | export default function useTopAiringAnime() { 5 | const { 6 | isLoading, 7 | data: topAiringAnime, 8 | error, 9 | status 10 | } = useQuery({ 11 | queryKey: ["top_airing_anime"], 12 | queryFn: getTopAiringAnime, 13 | staleTime: 1000 * 60 * 10, // 10 mins 14 | }); 15 | 16 | return { isLoading, topAiringAnime, error, status }; 17 | } 18 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | /* ------------------ CUSTOM SCROLL BAR ----------------- */ 6 | /* Apply custom scrollbar styles */ 7 | ::-webkit-scrollbar { 8 | width: 8px; /* Scrollbar width */ 9 | height: 7px; /* Scrollbar height */ 10 | } 11 | 12 | /* Scrollbar track */ 13 | ::-webkit-scrollbar-track { 14 | background: transparent; 15 | margin-top: 2.8rem; 16 | } 17 | 18 | /* Scrollbar thumb */ 19 | ::-webkit-scrollbar-thumb { 20 | background: #4a4a4a; /* Thumb color */ 21 | border-radius: 0px; /* Thumb border-radius */ 22 | } 23 | 24 | /* Scrollbar thumb states */ 25 | ::-webkit-scrollbar-thumb:hover { 26 | background: #555; /* Hover thumb color */ 27 | } 28 | 29 | ::-webkit-scrollbar-thumb:active { 30 | background: #444; /* Active thumb color */ 31 | } 32 | /* ------------------------------------------------------ */ 33 | 34 | .draggable { 35 | -webkit-app-region: drag; 36 | } 37 | .nodrag { 38 | -webkit-app-region: no-drag; 39 | } 40 | 41 | /* For blur mask of banner image */ 42 | .mask { 43 | -webkit-mask: -webkit-gradient( 44 | linear, 45 | left top, 46 | /* Start point of the gradient */ right top, 47 | /* End point of the gradient */ from(rgba(0, 0, 0, 1)), 48 | /* Fully opaque at the left */ to(rgba(0, 0, 0, 0)) /* Fully transparent at the right */ 49 | ); 50 | mask: linear-gradient(to right, rgba(0, 0, 0, 1) 30%, rgba(0, 0, 0, 0) 60%); 51 | } 52 | 53 | .skew3d { 54 | transform: matrix(1, 0, -0.05, 1, 0, 0); 55 | } 56 | 57 | .tripp { 58 | mask-image: linear-gradient(to bottom, transparent, black 10%, black 90%, transparent); 59 | } 60 | 61 | .stroke-text { 62 | -webkit-text-stroke: 1px rgba(255, 255, 255, 0.086); /* Stroke width and color */ 63 | color: transparent; /* Text fill color */ 64 | } 65 | 66 | /* -------------- FOR PLYR LOADING SPINNER -------------- */ 67 | .plyr.plyr--loading::after { 68 | content: ''; 69 | position: absolute; 70 | top: 50%; 71 | left: 50%; 72 | width: 40px; /* Adjust size of the spinner */ 73 | height: 40px; /* Adjust size of the spinner */ 74 | border: 5px solid transparent; /* Create border for spinner */ 75 | border-top: 5px solid #ffffff; /* Color of the spinner */ 76 | border-radius: 50%; /* Make it circular */ 77 | animation: spin 1s linear infinite; /* Spin animation */ 78 | transform: translate(-50%, -50%); /* Center the spinner */ 79 | z-index: 10; 80 | } 81 | 82 | /* Keyframes for spinning animation */ 83 | @keyframes spin { 84 | 0% { 85 | transform: translate(-50%, -50%) rotate(0deg); 86 | } 87 | 100% { 88 | transform: translate(-50%, -50%) rotate(360deg); 89 | } 90 | } 91 | /* ------------------------------------------------------ */ 92 | .plyr audio, 93 | .plyr iframe, 94 | .plyr video { 95 | display: block; 96 | height: 100%; 97 | width: 100%; 98 | } 99 | 100 | /* * { 101 | outline: 1px solid #f00; 102 | &:hover { 103 | outline: 2px solid #f00; 104 | } 105 | } */ 106 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/main.jsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom/client' 2 | import App from './App.jsx' 3 | import './index.css' 4 | import '@radix-ui/themes/styles.css' 5 | 6 | /* ------------------------ fonts ----------------------- */ 7 | // inter 8 | import '@fontsource/inter/100.css' 9 | import '@fontsource/inter/200.css' 10 | import '@fontsource/inter/300.css' 11 | import '@fontsource/inter/400.css' 12 | import '@fontsource/inter/500.css' 13 | import '@fontsource/inter/600.css' 14 | import '@fontsource/inter/700.css' 15 | import '@fontsource/inter/800.css' 16 | import '@fontsource/inter/900.css' 17 | // space mono 18 | import '@fontsource/space-mono/400.css' 19 | import '@fontsource/space-mono/700.css' 20 | import ZenshinProvider from './utils/ContextProvider.jsx' 21 | /* ------------------------------------------------------ */ 22 | ReactDOM.createRoot(document.getElementById('root')).render( 23 | 24 | 25 | 26 | ) 27 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/pages/ErrorPage.jsx: -------------------------------------------------------------------------------- 1 | import ErrorElement from "../ui/ErrorElement"; 2 | 3 | export default function ErrorPage({title, text, type}) { 4 | return ( 5 |
6 | 7 |
8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/pages/NewReleases.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | import useGetNewReleases from '../hooks/useGetNewReleases' 3 | import CenteredLoader from '../ui/CenteredLoader' 4 | import NewReleaseCard from '../components/NewReleaseCard' 5 | import useGetMultipleAnilistIds from '../hooks/useGetMultipleAnilistIds' 6 | 7 | export default function NewReleases() { 8 | const packer = '[SubsPlease]' 9 | const { isLoading, data, error, status } = useGetNewReleases(packer) 10 | const [newReleases, setNewReleases] = useState([]) 11 | const [displayedReleases, setDisplayedReleases] = useState([]) 12 | const [cardErrorShown, setCardErrorShown] = useState(false) // Track whether error toast was shown 13 | const [totalCards, setTotalCards] = useState(25) 14 | const [totalCards2, setTotalCards2] = useState(17) 15 | 16 | useEffect(() => { 17 | if (data) { 18 | const HQ_Releases = data.filter((release) => release.title.includes('1080p')) 19 | setNewReleases(HQ_Releases.slice(0, 12)) 20 | // setNewReleases(HQ_Releases); 21 | setDisplayedReleases(HQ_Releases.slice(0, totalCards)) 22 | } 23 | }, [data]) 24 | 25 | const [anilistIds, setAnilistIds] = useState([]) 26 | 27 | const { 28 | isLoading: isLoadingAnilist, 29 | data: dataAnilist, 30 | error: errorAnilist 31 | } = useGetMultipleAnilistIds(anilistIds.length >= totalCards2 ? anilistIds : []) 32 | // } = useGetMultipleAnilistIds(anilistIds) 33 | 34 | // useEffect(() => { 35 | // async function insertReleases() { 36 | // const chunkSize = 5 37 | // for (let i = 0; i < newReleases.length; i += chunkSize) { 38 | // const chunk = newReleases.slice(i, i + chunkSize) 39 | // for (let j = 0; j < chunk.length; j++) { 40 | // await new Promise((resolve) => setTimeout(resolve, 300)) // 200ms delay between each insertion 41 | // setDisplayedReleases((prev) => [...prev, chunk[j]]) 42 | // } 43 | // if (i + chunkSize < newReleases.length) { 44 | // await new Promise((resolve) => setTimeout(resolve, 2000)) // Wait 2 seconds before next chunk 45 | // } 46 | // } 47 | // } 48 | 49 | // if (newReleases.length > 0) { 50 | // insertReleases() 51 | // } 52 | // }, [newReleases]) 53 | 54 | if (isLoading) { 55 | return 56 | } 57 | 58 | if (error) { 59 | throw new Error(error) 60 | } 61 | 62 | return ( 63 |
64 |
65 | New Releases 66 |
67 |
68 | {displayedReleases.map((release) => ( 69 | 78 | ))} 79 |
80 |
81 | ) 82 | } 83 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/pages/VideoJs.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import Plyr from 'plyr' 3 | import 'plyr-react/plyr.css' 4 | 5 | export const PlyrPlayer = (props) => { 6 | const videoRef = React.useRef(null) 7 | const playerRef = React.useRef(null) 8 | const { options, onReady } = props 9 | 10 | useEffect(() => { 11 | if (!playerRef.current) { 12 | const player = (playerRef.current = new Plyr(videoRef.current, options)) 13 | 14 | // Log when the player is ready 15 | player.on('ready', () => { 16 | console.log('Plyr player is ready') 17 | onReady && onReady(player) 18 | }) 19 | } else { 20 | // Update source or other options when props change 21 | playerRef.current.source = options.sources 22 | } 23 | }, [options]) 24 | 25 | // Clean up Plyr instance on component unmount 26 | useEffect(() => { 27 | const player = playerRef.current 28 | 29 | return () => { 30 | if (player) { 31 | player.destroy() 32 | playerRef.current = null 33 | } 34 | } 35 | }, []) 36 | 37 | return ( 38 |
39 |
41 | ) 42 | } 43 | 44 | export default PlyrPlayer 45 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/skeletons/NewReleaseCardSkeleton.jsx: -------------------------------------------------------------------------------- 1 | import { Skeleton } from '@radix-ui/themes' 2 | 3 | export default function NewReleaseCardSkeleton() { 4 | return ( 5 |
6 | 10 |
11 | 12 | 13 |
14 |
15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/skeletons/SkeletonAnimeCard.jsx: -------------------------------------------------------------------------------- 1 | import { Skeleton } from '@radix-ui/themes' 2 | 3 | export default function SkeletonAnimeCard() { 4 | return ( 5 |
6 | 7 |
8 | 9 | 10 |
11 |
12 | 13 | 14 |
15 |
16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/sliding-pane.css: -------------------------------------------------------------------------------- 1 | /* .slide-pane { 2 | display: flex; 3 | flex-direction: column; 4 | min-width: 100px; 5 | height: calc(100% - 43px); 6 | box-shadow: 0 8px 8px rgba(0, 0, 0, 0.5); 7 | transition: transform 0.5s; 8 | will-change: transform; 9 | position: fixed; 10 | top: 43px; 11 | right: 0; 12 | } */ 13 | 14 | .slide-pane { 15 | display: flex; 16 | flex-direction: column; 17 | min-width: 100px; 18 | height: calc(100% - 43px); /* Subtract the height of the header */ 19 | box-shadow: 0 8px 8px rgba(0, 0, 0, 0.5); 20 | transition: transform 0.5s; 21 | will-change: transform; 22 | position: fixed; /* Make it fixed so it doesn't scroll with the page */ 23 | top: 43px; /* Push it below the header */ 24 | right: 0; /* Adjust based on the direction (left or right) */ 25 | } 26 | 27 | .slide-pane:focus { 28 | outline-style: none; 29 | } 30 | .slide-pane_from_right { 31 | margin-left: auto; 32 | transform: translateX(100%); 33 | } 34 | .slide-pane_from_right.content-after-open { 35 | transform: translateX(0%); 36 | } 37 | .slide-pane_from_right.content-before-close { 38 | transform: translateX(100%); 39 | } 40 | .slide-pane_from_left { 41 | margin-right: auto; 42 | transform: translateX(-100%); 43 | } 44 | .slide-pane_from_left.content-after-open { 45 | transform: translateX(0%); 46 | } 47 | .slide-pane_from_left.content-before-close { 48 | transform: translateX(-100%); 49 | } 50 | .slide-pane_from_bottom { 51 | height: 90vh; 52 | margin-top: 10vh; 53 | transform: translateY(100%); 54 | } 55 | .slide-pane_from_bottom.content-after-open { 56 | transform: translateY(0%); 57 | } 58 | .slide-pane_from_bottom.content-before-close { 59 | transform: translateY(100%); 60 | } 61 | .slide-pane__overlay { 62 | position: fixed; 63 | top: 0px; 64 | left: 0px; 65 | right: 0px; 66 | bottom: 0px; 67 | background-color: rgba(0, 0, 0, 0); 68 | } 69 | .slide-pane__overlay.overlay-after-open { 70 | background-color: rgba(0, 0, 0, 0.3); 71 | transition: background-color 0.5s; 72 | } 73 | .slide-pane__overlay.overlay-before-close { 74 | background-color: rgba(0, 0, 0, 0); 75 | } 76 | .slide-pane__header { 77 | display: flex; 78 | flex: 0 0 40px; 79 | align-items: center; 80 | background: #1e1e20; 81 | height: 32px; 82 | border-bottom: 1px solid rgba(0, 0, 0, 0.1); 83 | } 84 | .slide-pane__title-wrapper { 85 | display: flex; 86 | flex: 1; 87 | flex-direction: column; 88 | margin-left: 32px; 89 | min-width: 0; 90 | } 91 | .slide-pane .slide-pane__title { 92 | font-size: 15px; 93 | font-weight: normal; 94 | max-width: 80%; 95 | white-space: nowrap; 96 | overflow: hidden; 97 | text-overflow: ellipsis; 98 | margin: 0; 99 | padding: 0; 100 | } 101 | .slide-pane__close { 102 | margin-left: 24px; 103 | padding: 16px; 104 | opacity: 0.7; 105 | cursor: pointer; 106 | } 107 | .slide-pane__close svg { 108 | width: 8px; 109 | padding: 0; 110 | } 111 | .slide-pane__content { 112 | position: relative; 113 | overflow-y: auto; 114 | /* padding: 24px 32px; */ 115 | flex: 1 1 auto; 116 | } 117 | .slide-pane__subtitle { 118 | font-size: 12px; 119 | margin-top: 2px; 120 | } 121 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/ui/CenteredLoader.jsx: -------------------------------------------------------------------------------- 1 | import Loader from './Loader' 2 | 3 | export default function CenteredLoader() { 4 | return ( 5 |
6 | 7 |
8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/ui/ErrorElement.jsx: -------------------------------------------------------------------------------- 1 | import { useRouteError } from 'react-router-dom' 2 | import psyduck from '../assets/psyduck.png' 3 | 4 | export default function ErrorElement({ title = 'Something Went wrong !', text, type }) { 5 | const error = useRouteError() 6 | 7 | const style = { 8 | outer: `border-rose-500 bg-rose-500`, 9 | inner: `text-rose-500` 10 | } 11 | 12 | if (type === 'warning') { 13 | style.outer = `border-orange-500 bg-orange-500` 14 | style.inner = `text-orange-500` 15 | } else if (type === 'info') { 16 | style.outer = `border-sky-500 bg-sky-500` 17 | style.inner = `text-sky-500 18 | ` 19 | } 20 | 21 | return ( 22 |
23 |
24 | 25 |
{title}
26 |

27 | {text || error?.data || error?.message || 'Unknown Error'} 28 |

29 |
30 |
31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/ui/Loader.jsx: -------------------------------------------------------------------------------- 1 | import animeGif from '../assets/anime.gif' 2 | import { BarLoader } from 'react-spinners' 3 | export default function Loader() { 4 | return ( 5 |
6 | 7 | 8 |
9 | {/* */} 16 | 17 |
18 |
19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/utils/anilistQueryObject.js: -------------------------------------------------------------------------------- 1 | export const anilistQueryObject = ` 2 | id, 3 | idMal, 4 | title { 5 | romaji, 6 | english, 7 | native, 8 | userPreferred 9 | }, 10 | description(asHtml: false), 11 | season, 12 | seasonYear, 13 | format, 14 | status, 15 | episodes, 16 | duration, 17 | averageScore, 18 | popularity, 19 | genres, 20 | isFavourite, 21 | coverImage { 22 | extraLarge, 23 | medium, 24 | color 25 | }, 26 | source, 27 | countryOfOrigin, 28 | isAdult, 29 | bannerImage, 30 | synonyms, 31 | nextAiringEpisode { 32 | timeUntilAiring, 33 | episode 34 | }, 35 | startDate { 36 | year, 37 | month, 38 | day 39 | }, 40 | trailer { 41 | id, 42 | site 43 | }, 44 | streamingEpisodes { 45 | title, 46 | thumbnail 47 | }, 48 | mediaListEntry { 49 | id, 50 | progress, 51 | repeat, 52 | status, 53 | customLists(asArray: true), 54 | score(format: POINT_10) 55 | }, 56 | studios(isMain: true) { 57 | nodes { 58 | name 59 | } 60 | }, 61 | airingSchedule(page: 1, perPage: 1, notYetAired: true) { 62 | nodes { 63 | episode, 64 | airingAt 65 | } 66 | }, 67 | relations { 68 | edges { 69 | relationType(version:2), 70 | node { 71 | id, 72 | title {userPreferred}, 73 | coverImage {medium}, 74 | type, 75 | status, 76 | format, 77 | episodes, 78 | synonyms, 79 | season, 80 | seasonYear, 81 | startDate { 82 | year, 83 | month, 84 | day 85 | }, 86 | endDate { 87 | year, 88 | month, 89 | day 90 | } 91 | } 92 | } 93 | }` 94 | 95 | /* ------------------------------------------------------ */ 96 | 97 | const query = `type: ANIME, search: $search, sort: $sort, onList: $onList, status: $status, status_not: $status_not, season: $season, seasonYear: $year, genre: $genre, format: $format, format_not: MUSIC` 98 | 99 | export const getParsedAnilistQuery = (variables) => { 100 | let queryStr = `type: ANIME` 101 | 102 | for (const key in variables) { 103 | // generate the query string 104 | if (variables[key] && variables[key] && key !== 'watchStatus' && key !== 'userId') { 105 | queryStr += `, ${key}: ${variables[key]}` 106 | } 107 | } 108 | 109 | return queryStr 110 | } 111 | 112 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/utils/api.js: -------------------------------------------------------------------------------- 1 | import encUrls from '../../../../common/utils.js' 2 | const BASE_URL_JIKAN = 'https://api.jikan.moe/v4' 3 | const BASE_URL_NYAA = encUrls.nyaaApi 4 | export const BASE_URL_ANILIST = 'https://graphql.anilist.co' 5 | const BASE_URL_ANIZIP = encUrls.zenshinSupabase 6 | const TOSHO = encUrls.tosho 7 | 8 | export function SEARCH_ANIME(query, limit = 10) { 9 | return `${BASE_URL_JIKAN}/anime?q=${query}&limit=${limit}` 10 | } 11 | 12 | export function SEARCH_TORRENT(query) { 13 | return `${BASE_URL_NYAA}/?q=${query}&sort=seeders&order=desc&page=1&category=anime` 14 | } 15 | 16 | export function TOP_AIRING_ANIME() { 17 | return `${BASE_URL_JIKAN}/top/anime?&filter=airing&limit=25&sfw=true&type=tv` 18 | } 19 | 20 | export function TOP_ANIME(page = 1) { 21 | return `${BASE_URL_JIKAN}/top/anime?page=${page}&limit=25&sfw=true` 22 | } 23 | 24 | export function GET_ANIME_DETAILS_BY_ID(id) { 25 | return `${BASE_URL_JIKAN}/anime/${id}/full` 26 | } 27 | 28 | export function GET_ANIME_MAPPING_BY_ANILIST_ID(anilist_id, anidb = false) { 29 | console.log(`${BASE_URL_ANIZIP}/mappings?anilist_id=${anilist_id}`) 30 | if (anidb) return `${BASE_URL_ANIZIP}/mappings?anidb_id=${anilist_id}` 31 | return `${BASE_URL_ANIZIP}/mappings?anilist_id=${anilist_id}` 32 | } 33 | 34 | export function GET_ANIME_EPISODES_BY_ID(id) { 35 | return `${BASE_URL_JIKAN}/anime/${id}/episodes` 36 | } 37 | 38 | export function GET_TOSHO_RSS(packer = '"[SubsPlease]"') { 39 | return `${TOSHO}/json?qx=1&q=${packer}` 40 | } 41 | 42 | export function GET_TOSHO_RSS_BY_QUERY(quality = 'all', aids, eids) { 43 | if (eids === 0 || eids === null) { 44 | if (quality.toLowerCase() === 'all') return `${TOSHO}/json?qx=1&aids=${aids}` 45 | return `${TOSHO}/json?qx=1&q=${quality}&aids=${aids}` 46 | } 47 | if (quality.toLowerCase() === 'all') return `${TOSHO}/json?qx=1&aids=${aids}&eids=${eids}` 48 | return `${TOSHO}/json?qx=1&q=${quality}&aids=${aids}&eids=${eids}` 49 | } 50 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/utils/auth.js: -------------------------------------------------------------------------------- 1 | // export const ANILIST_CLIENT_ID = () => { 2 | // const url = window.location.href; 3 | // if (url.includes("localhost")) { 4 | // console.log("Localhost detected"); 5 | // return 20876; 6 | // } else return 20866; 7 | // }; 8 | 9 | // export const ANILIST_CLIENT_ID = 20876; 10 | // export const ANILIST_CLIENT_ID = 20866; 11 | export const ANILIST_CLIENT_ID = window.location.href.includes('localhost') ? 21536 : 21536 12 | export const anilistAuthUrl = `https://anilist.co/api/v2/oauth/authorize?client_id=${ANILIST_CLIENT_ID}&response_type=token` 13 | 14 | export async function getAnilistProfile(anilistToken) { 15 | try { 16 | if (!localStorage.getItem('anilist_token')) return null 17 | 18 | // Fetch user data from AniList API 19 | const response = await fetch('https://graphql.anilist.co', { 20 | method: 'POST', 21 | headers: { 22 | 'Content-Type': 'application/json', 23 | Authorization: `Bearer ${anilistToken}` 24 | }, 25 | body: JSON.stringify({ 26 | query: ` 27 | query { 28 | Viewer { 29 | id 30 | name 31 | avatar { 32 | large 33 | } 34 | } 35 | } 36 | ` 37 | }) 38 | }) 39 | 40 | if (response.status === 429) { 41 | throw new Error( 42 | 'Too many requests to the API. You are being rate-limited. Please wait a minute and refresh the page.' 43 | ) 44 | } else if (!response.ok) { 45 | // const errorData = await response.json(); 46 | let { errors: errorData } = await response.json() 47 | errorData = errorData[0] 48 | 49 | throw new Error(`Error ${response.status}: ${response.statusText} - ${errorData.message}`) 50 | } 51 | 52 | const data = await response.json() 53 | localStorage.setItem('anilist_id', data.data.Viewer.id) 54 | localStorage.setItem('anilist_name', data.data.Viewer.name) 55 | 56 | return data.data.Viewer 57 | } catch (error) { 58 | console.log('Error in getAnilistProfile: ', error) 59 | throw new Error(error) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/utils/currentSeason.js: -------------------------------------------------------------------------------- 1 | export function getCurrentSeason() { 2 | const date = new Date() 3 | const currentSeason = ['WINTER', 'SPRING', 'SUMMER', 'FALL'][ 4 | Math.floor((date.getMonth() / 12) * 4) % 4 5 | ] 6 | return currentSeason 7 | } 8 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/utils/discord.js: -------------------------------------------------------------------------------- 1 | // DiscordRPC.js 2 | import { Client, register } from 'discord-rpc' 3 | class DiscordRPC { 4 | constructor(clientId) { 5 | this.clientId = clientId 6 | this.client = new Client({ transport: 'ipc' }) 7 | 8 | this.client.on('ready', () => { 9 | console.log('Discord RPC is ready!') 10 | }) 11 | } 12 | 13 | initialize() { 14 | register(this.clientId) 15 | this.client.login({ clientId: this.clientId }).catch(() => { 16 | setTimeout(() => this.initialize(), 5000).unref() 17 | }) 18 | } 19 | 20 | setActivity(activityDetails) { 21 | if (!this.client || !this.client.user) return 22 | 23 | this.client.request('SET_ACTIVITY', { 24 | pid: process.pid, 25 | activity: { 26 | timestamps: { start: Date.now() }, 27 | details: activityDetails.details || 'Browsing Anime', 28 | state: activityDetails.state || 'Looking for anime to watch', 29 | assets: { 30 | // large_image: activityDetails.largeImageKey || 'logo', 31 | // large_text: activityDetails.largeImageText || 'Anime Time!', 32 | // small_image: activityDetails.smallImageKey || 'logo', 33 | // small_text: activityDetails.smallImageText || 'Zenshin' 34 | ...activityDetails.assets 35 | }, 36 | 37 | buttons: [ 38 | { 39 | label: 'Download app', 40 | url: 'https://github.com/hitarth-gg/zenshin/releases/latest' 41 | } 42 | ], 43 | instance: true, 44 | type: 3 45 | } 46 | }) 47 | } 48 | disconnect() { 49 | this.client.destroy().then(() => { 50 | console.log('RPC client disconnected.') 51 | }) 52 | } 53 | } 54 | 55 | export default DiscordRPC 56 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/utils/formatBytes.js: -------------------------------------------------------------------------------- 1 | export default function formatBytes(bytes, decimals = 1) { 2 | if (bytes === 0) return '0 Bytes' 3 | // if(bytes < 1024) return '0 KB' 4 | const k = 1024 5 | const dm = decimals < 0 ? 0 : decimals 6 | const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] 7 | 8 | const i = Math.floor(Math.log(bytes) / Math.log(k)) 9 | 10 | return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i] 11 | } 12 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/utils/nFormatter.js: -------------------------------------------------------------------------------- 1 | export default function nFormatter(num, digits) { 2 | const lookup = [ 3 | { value: 1, symbol: '' }, 4 | { value: 1e3, symbol: 'k' }, 5 | { value: 1e6, symbol: 'M' }, 6 | { value: 1e9, symbol: 'G' }, 7 | { value: 1e12, symbol: 'T' }, 8 | { value: 1e15, symbol: 'P' }, 9 | { value: 1e18, symbol: 'E' } 10 | ] 11 | const regexp = /\.0+$|(?<=\.[0-9]*[1-9])0+$/ 12 | const item = lookup.findLast((item) => num >= item.value) 13 | return item ? (num / item.value).toFixed(digits).replace(regexp, '').concat(item.symbol) : '0' 14 | } 15 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/utils/objectEmpty.js: -------------------------------------------------------------------------------- 1 | // Object.keys(yourObject).length 2 | 3 | export function isObjectEmpty(obj) { 4 | return Object.keys(obj).length === 0 5 | } 6 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/src/workers/worker.js: -------------------------------------------------------------------------------- 1 | import * as Comlink from 'comlink' 2 | 3 | async function loadExtensions(extension) { 4 | console.log('Loading extensions:', extension) 5 | 6 | try { 7 | const extensionUrl = extension.startsWith('http') ? extension : `https://esm.sh/${extension}` 8 | const exts = await import(/* @vite-ignore */ extensionUrl) // Dynamically import the extension 9 | const { Tosho } = exts // Assuming `default` is the main export 10 | console.log('Extensions: ', Tosho) 11 | //export { default as Tosho } from "./extensions/tosho.js"; : tosho is the default export which is an instance of Class containing the getToshoEpisodes function 12 | return Tosho 13 | } catch (error) { 14 | console.error('Error loading extensions:', error) 15 | return null 16 | } 17 | } 18 | 19 | async function initialize(extension) { 20 | const Tosho = await loadExtensions(extension) 21 | console.log('initialization') 22 | 23 | if (Tosho) { 24 | const toshoExtension = { 25 | getToshoEpisodes: async (options) => { 26 | try { 27 | const { quality, aids, eids } = options 28 | const data = await Tosho.getToshoEpisodes(quality, aids, eids) 29 | 30 | return data 31 | } catch (error) { 32 | console.error('Error fetching episodes:', error) 33 | throw error 34 | } 35 | }, 36 | getNewReleases: async (options) => { 37 | try { 38 | const { packer } = options 39 | const data = await Tosho.getNewReleases(packer) 40 | console.log(data) 41 | 42 | return data 43 | } catch (error) { 44 | console.error('Error fetching episodes:', error) 45 | throw error 46 | } 47 | } 48 | } 49 | 50 | Comlink.expose(toshoExtension) 51 | } else { 52 | console.error('Failed to load extension.') 53 | } 54 | } 55 | initialize('gh/hitarth-gg/mizukijs') 56 | console.log('Worker initialized') 57 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/src/renderer/webtorrent.js: -------------------------------------------------------------------------------- 1 | // const WebTorrent = require('webtorrent') 2 | // const electron = require('electron') 3 | // const fs = require('fs') 4 | // const path = require('path') 5 | 6 | // // Send & receive messages from the main window 7 | // const ipc = electron.ipcRenderer 8 | 9 | // let client = new WebTorrent() 10 | // // const downloadsDir = path.join(__dirname, 'downloads') 11 | 12 | // // WebTorrent-to-HTTP streaming sever 13 | // let server = null 14 | 15 | // // init() is called when the window is loaded 16 | // function init() { 17 | // listenToClientEvents() 18 | 19 | // ipc.on('wt-add-torrent', (e, magnet) => { 20 | // let files = addTorrent(magnet) 21 | // ipc.send('wt-torrent-files', files) // Send files back to the main window 22 | // }) 23 | // } 24 | 25 | // // Listen to messages from the main window 26 | // function listenToClientEvents() { 27 | // client.on('warning', (err) => ipc.send('wt-warning', null, err.message)) 28 | // client.on('error', (err) => ipc.send('wt-error', null, err.message)) 29 | // } 30 | 31 | // function addTorrent(magnet) { 32 | // let existingTorrent = client.get(magnet) 33 | 34 | // if (existingTorrent) { 35 | // // If torrent is already added, return its file information 36 | // let files = existingTorrent.files.map((file) => ({ 37 | // name: file.name, 38 | // length: file.length 39 | // })) 40 | // console.log('Existing torrent files:', files) 41 | 42 | // return files 43 | // } 44 | 45 | // // Add torrent to client 46 | 47 | // /* ADD DOWNLOAD PATH HERE */ 48 | // const torrent = client.add(magnet, { deselect: true }) 49 | 50 | // torrent.on('metadata', () => { 51 | // const files = torrent.files.map((file) => ({ 52 | // name: file.name, 53 | // length: file.length 54 | // })) 55 | 56 | // console.log('New torrent files:', files) 57 | // return files 58 | // }) 59 | // } 60 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], 4 | theme: { 5 | fontFamily: { 6 | sans: ['ui-sans-serif', 'system-ui'], 7 | inter: ['Inter', 'ui-sans-serif', 'system-ui'], 8 | 'space-mono': ['Space Mono', 'ui-sans-serif', 'system-ui'] 9 | }, 10 | screens: { 11 | sm: '640px', 12 | // => @media (min-width: 640px) { ... } 13 | 14 | md: '768px', 15 | // => @media (min-width: 768px) { ... } 16 | 17 | lg: '1024px', 18 | // => @media (min-width: 1024px) { ... } 19 | 20 | lg2: '1152px', 21 | 22 | xl: '1380px', 23 | // => @media (min-width: 1280px) { ... } 24 | 25 | '2xl': '1836px' 26 | // => @media (min-width: 1536px) { ... } 27 | }, 28 | boxShadow: { 29 | '3xl': '0 35px 60px 0px rgba(0, 0, 0, 0.8)' 30 | }, 31 | dropShadow: { 32 | '3xl': '0 3px 3px rgba(0, 0, 0, .45)', 33 | '4xl': ['0 35px 35px rgba(0, 0, 0, 0.25)', '0 45px 65px rgba(0, 0, 0, 0.15)'] 34 | }, 35 | extend: {} 36 | }, 37 | plugins: [require('tailwindcss-animated', '@tailwindcss/line-clamp')] 38 | } 39 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "edges": [ 4 | { 5 | "relationType": "SOURCE", 6 | "node": { 7 | "id": 30023, 8 | "title": { "userPreferred": "Ranma ½" }, 9 | "coverImage": { 10 | "medium": "https://s4.anilist.co/file/anilistcdn/media/manga/cover/small/bx30023-NWoD28ZKC73c.jpg" 11 | }, 12 | "type": "MANGA", 13 | "status": "FINISHED", 14 | "format": "MANGA", 15 | "episodes": null, 16 | "synonyms": ["รันม่า1/2 ไอ้หนุ่มกังฟู ", "란마1/2"], 17 | "season": null, 18 | "seasonYear": null, 19 | "startDate": { "year": 1987, "month": 8, "day": 5 }, 20 | "endDate": { "year": 1996, "month": 2, "day": 21 } 21 | } 22 | }, 23 | { 24 | "relationType": "ALTERNATIVE", 25 | "node": { 26 | "id": 149939, 27 | "title": { "userPreferred": "Ranma ½ (1989)" }, 28 | "coverImage": { 29 | "medium": "https://s4.anilist.co/file/anilistcdn/media/anime/cover/small/bx149939-3lDyYgDZ0AbI.png" 30 | }, 31 | "type": "ANIME", 32 | "status": "FINISHED", 33 | "format": "TV", 34 | "episodes": 143, 35 | "synonyms": ["ينبوع الأحلام", "รันม่า1/2 ไอ้หนุ่มกังฟู"], 36 | "season": "FALL", 37 | "seasonYear": 1989, 38 | "startDate": { "year": 1989, "month": 10, "day": 20 }, 39 | "endDate": { "year": 1992, "month": 9, "day": 25 } 40 | } 41 | }, 42 | { 43 | "relationType": "ALTERNATIVE", 44 | "node": { 45 | "id": 210, 46 | "title": { "userPreferred": "Ranma ½" }, 47 | "coverImage": { 48 | "medium": "https://s4.anilist.co/file/anilistcdn/media/anime/cover/small/bx210-qgahLDYT0t9b.png" 49 | }, 50 | "type": "ANIME", 51 | "status": "FINISHED", 52 | "format": "TV", 53 | "episodes": 18, 54 | "synonyms": ["ينبوع الأحلام", "รันม่า1/2 ไอ้หนุ่มกังฟู "], 55 | "season": "SPRING", 56 | "seasonYear": 1989, 57 | "startDate": { "year": 1989, "month": 4, "day": 15 }, 58 | "endDate": { "year": 1989, "month": 9, "day": 16 } 59 | } 60 | }, 61 | { 62 | "relationType": "OTHER", 63 | "node": { 64 | "id": 183390, 65 | "title": { "userPreferred": "Anta Nante." }, 66 | "coverImage": { 67 | "medium": "https://s4.anilist.co/file/anilistcdn/media/anime/cover/small/bx183390-Ds74xbI3pafK.png" 68 | }, 69 | "type": "ANIME", 70 | "status": "FINISHED", 71 | "format": "MUSIC", 72 | "episodes": 1, 73 | "synonyms": [], 74 | "season": null, 75 | "seasonYear": null, 76 | "startDate": { "year": 2024, "month": 10, "day": 19 }, 77 | "endDate": { "year": 2024, "month": 10, "day": 19 } 78 | } 79 | } 80 | ] 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Electron/zenshin-electron/todo.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | - AniList updation conditional rendering 3 | - Make filtering anilist user list better 4 | - Add filter for genre in user anilist 5 | - Allow settings to be reset 6 | - Announce torrent 7 | -------------------------------------------------------------------------------- /disclaimer.md: -------------------------------------------------------------------------------- 1 | 2 |

Disclaimer

3 | 4 | ## Project: zenshin. 5 | 6 | The core aim of this project is to explore various technologies like HLS Video Streaming, WebTorrent, ElectronJS and WebScraping to extract what is already provided to a user on the internet. 7 | All content available through the project is hosted by external non-affiliated sources and is not under our control. 8 | 9 | Any content served through this project is publicly accessible. If your site is listed in this project, the code is pretty much public. Take necessary measures to counter the exploits used to extract content from your site. 10 | 11 | This project can be considered as a combination of a browser and a webscraper. While an average browser makes hundreds of requests to get everything from a site, this project goes on to make requests associated with only getting the content served by the sites. 12 | 13 | This project has no control on the content it is serving, using copyrighted content from the providers is not going to be accounted for by the developer. It is the user's own risk. 14 | 15 | 16 |

DMCA and Copyright Infrigements

17 | 18 | A browser is a tool, and the maliciousness of the tool is directly based on the user. 19 | This project uses client-side content access mechanisms. Hence, the copyright infrigements or DMCA in this project's regards are to be forwarded to the associated site by the associated notifier of any such claims. As of writing this is [animepahe](https://animepahe.ru/) and [animetosho](https://animetosho.org/). 20 | 21 | Do not harass the maintainer. 22 | 23 | ### Contacting the maintainer 24 | Begin by making a GitHub issue or sending an email to "go2for2hit2arth2@gmail.com" : **remove** all '2's from the mail (for e.g. "rem2o2ve2" becomes "remove") 25 | --------------------------------------------------------------------------------