├── .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 |
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 | //
39 | // {videoSrc && (
40 | //
41 | // {subtitleSrc && (
42 | //
49 | // )}
50 | //
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 |
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 |
25 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
43 |
44 | How to use
45 |
46 |
47 |
48 | {theme === "dark" ? (
49 |
50 | ) : (
51 |
52 | )}
53 |
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 |
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 | //
39 | // {videoSrc && (
40 | //
41 | // {subtitleSrc && (
42 | //
49 | // )}
50 | //
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 | setShowFullSpeed(true)}
54 | onMouseLeave={() => setShowFullSpeed(false)}
55 | onClick={() => setAlwaysShow(!alwaysShow)}
56 | >
57 |
58 |
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 | {
29 | e.stopPropagation()
30 | setCurrentEpisode(file.name)
31 | handleStreamBrowser(file.name)
32 | }}
33 | size="1"
34 | color="violet"
35 | variant="soft"
36 | type="submit"
37 | >
38 | Stream on App
39 |
40 | {
45 | e.stopPropagation()
46 | setCurrentEpisode(file.name)
47 | handleStreamVlc(file.name)
48 | }}
49 | >
50 | Open in External Player
51 |
52 | {
57 | e.stopPropagation()
58 | setCurrentEpisode('')
59 | stopEpisodeDownload(file.name)
60 | }}
61 | >
62 | Stop downloading the episode
63 |
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 |
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 |
12 |
13 | {/* New Releases
*/}
14 | New
15 |
16 |
17 | {/*
*/}
18 | {/*
19 |
26 |
27 |
28 | */}
29 |
navigate('/animepahe')}
35 | >
36 | {/* */}
37 |
38 |
39 |
40 | {/*
*/}
41 |
42 |
navigate('/anilist')}
48 | style={{
49 | padding: '0 .4rem'
50 | }}
51 | >
52 | {/* */}
53 |
54 |
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 |
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 |
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 |
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 |
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 |
40 |
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 |
--------------------------------------------------------------------------------