├── .eslintrc.json ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ └── bug_report.yml ├── PULL_REQUEST_TEMPLATE.md └── dependabot.yml ├── .gitignore ├── .prettierrc.json ├── LICENSE ├── README.md ├── addition.t ├── app ├── (pages) │ ├── anime │ │ ├── [id] │ │ │ └── page.tsx │ │ ├── page.tsx │ │ └── watch │ │ │ └── [id] │ │ │ └── [episode] │ │ │ └── page.tsx │ ├── drama │ │ └── page.tsx │ ├── layout.tsx │ ├── list │ │ ├── anime │ │ │ └── page.tsx │ │ ├── mtv │ │ │ └── page.tsx │ │ └── page.tsx │ ├── manga │ │ ├── [title] │ │ │ └── page.tsx │ │ ├── info │ │ │ └── [id] │ │ │ │ └── page.tsx │ │ ├── page.tsx │ │ └── read │ │ │ └── [id] │ │ │ └── [title] │ │ │ └── [lang] │ │ │ └── [chapter] │ │ │ └── page.tsx │ ├── movie │ │ ├── [id] │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ │ ├── page.tsx │ │ └── watch │ │ │ └── [id] │ │ │ └── page.tsx │ ├── page.tsx │ ├── removed │ │ └── page.tsx │ ├── search │ │ └── page.tsx │ └── tv │ │ ├── [id] │ │ ├── loading.tsx │ │ └── page.tsx │ │ ├── page.tsx │ │ └── watch │ │ └── [id] │ │ └── page.tsx ├── (protected) │ ├── auth │ │ └── [pathname] │ │ │ └── page.tsx │ └── layout.tsx ├── api │ ├── auth │ │ └── [...all] │ │ │ └── route.ts │ └── feedback │ │ └── route.ts ├── layout.tsx ├── not-found.tsx ├── providers.tsx └── robots.ts ├── bun.lock ├── components.json ├── components ├── app-sidebar.tsx ├── carousal │ ├── anime.tsx │ ├── card.tsx │ ├── movie.tsx │ └── tv.tsx ├── command-search.tsx ├── common │ ├── card.tsx │ ├── icons.tsx │ └── poster.tsx ├── containers │ ├── anime │ │ ├── details.tsx │ │ └── watch.tsx │ ├── drama │ │ ├── details.tsx │ │ ├── episode.tsx │ │ └── watch-episode.tsx │ ├── movie │ │ ├── details.tsx │ │ ├── related.tsx │ │ └── videoplayer.tsx │ ├── search-bar.tsx │ └── tv │ │ ├── details.tsx │ │ ├── related.tsx │ │ └── videoplayer.tsx ├── donate.tsx ├── featured │ ├── anime.tsx │ ├── loading-featured.tsx │ ├── movie.tsx │ └── tv.tsx ├── feedback.tsx ├── footer.tsx ├── hero.tsx ├── item-hover-card.tsx ├── loading.tsx ├── magicui │ └── rainbow-button.tsx ├── manga-search.tsx ├── mobile-nav.tsx ├── nav-secondary.tsx ├── nav-user-client.tsx ├── site-header.tsx ├── storage.ts ├── tailwind-indicator.tsx ├── theme-provider.tsx ├── theme-toggle.tsx └── ui │ ├── avatar.tsx │ ├── badge.tsx │ ├── breadcrumb.tsx │ ├── button.tsx │ ├── card.tsx │ ├── carousel.tsx │ ├── chart.tsx │ ├── checkbox.tsx │ ├── collapsible.tsx │ ├── command.tsx │ ├── craft.tsx │ ├── dialog.tsx │ ├── drawer.tsx │ ├── dropdown-menu.tsx │ ├── glowing-stars.tsx │ ├── hover-card.tsx │ ├── input.tsx │ ├── label.tsx │ ├── marquee.tsx │ ├── navigation-menu.tsx │ ├── pagination.tsx │ ├── pattern.tsx │ ├── placeholders-and-vanish-input.tsx │ ├── scroll-area.tsx │ ├── select.tsx │ ├── separator.tsx │ ├── sheet.tsx │ ├── sidebar.tsx │ ├── skeleton.tsx │ ├── sonner.tsx │ ├── spinner.tsx │ ├── switch.tsx │ ├── table.tsx │ ├── tabs.tsx │ ├── textarea.tsx │ ├── timeline.tsx │ ├── toggle-group.tsx │ ├── toggle.tsx │ └── tooltip.tsx ├── config ├── mobile.ts └── site.ts ├── db └── drizzle.ts ├── drizzle.config.ts ├── env.mjs ├── example.env ├── hooks ├── use-mobile.ts ├── use-search.ts └── use-session.ts ├── lib ├── auth-client.ts ├── auth.ts ├── consumet │ └── index.ts ├── download.ts ├── localstorage.ts ├── store.ts ├── tmdb │ ├── api │ │ ├── collections.ts │ │ ├── credits.ts │ │ ├── genres.ts │ │ ├── images.ts │ │ ├── index.ts │ │ ├── keywords.ts │ │ ├── languages.ts │ │ ├── movies.ts │ │ ├── person.ts │ │ ├── search.ts │ │ ├── season.ts │ │ ├── tv-series.ts │ │ ├── videos.ts │ │ └── watch-providers.ts │ ├── index.ts │ ├── models │ │ ├── collections.ts │ │ ├── combined-credits.ts │ │ ├── credits.ts │ │ ├── genres.ts │ │ ├── images.ts │ │ ├── index.ts │ │ ├── keywords.ts │ │ ├── language.ts │ │ ├── movie.ts │ │ ├── person.ts │ │ ├── season.ts │ │ ├── tv-series.ts │ │ ├── videos.ts │ │ └── watch-providers.ts │ └── utils │ │ ├── common.ts │ │ ├── format-combined-credit.ts │ │ ├── list-response.ts │ │ └── with_media_type.ts └── utils.ts ├── middleware.ts ├── next.config.js ├── package.json ├── postcss.config.mjs ├── public ├── DEPLOY.png ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── android │ ├── android-launchericon-144-144.png │ ├── android-launchericon-192-192.png │ ├── android-launchericon-48-48.png │ ├── android-launchericon-512-512.png │ ├── android-launchericon-72-72.png │ └── android-launchericon-96-96.png ├── apple-touch-icon.png ├── avalynndev.png ├── browserconfig.xml ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── image.png ├── ios │ ├── 100.png │ ├── 1024.png │ ├── 114.png │ ├── 120.png │ ├── 128.png │ ├── 144.png │ ├── 152.png │ ├── 16.png │ ├── 167.png │ ├── 180.png │ ├── 192.png │ ├── 20.png │ ├── 256.png │ ├── 29.png │ ├── 32.png │ ├── 40.png │ ├── 50.png │ ├── 512.png │ ├── 57.png │ ├── 58.png │ ├── 60.png │ ├── 64.png │ ├── 72.png │ ├── 76.png │ ├── 80.png │ └── 87.png ├── manga.png ├── manifest.json ├── mstile-144x144.png ├── mstile-150x150.png ├── mstile-310x150.png ├── mstile-310x310.png ├── mstile-70x70.png ├── safari-pinned-tab.svg ├── sw.js ├── sw.js.map ├── windows11 │ ├── LargeTile.scale-100.png │ ├── LargeTile.scale-125.png │ ├── LargeTile.scale-150.png │ ├── LargeTile.scale-200.png │ ├── LargeTile.scale-400.png │ ├── SmallTile.scale-100.png │ ├── SmallTile.scale-125.png │ ├── SmallTile.scale-150.png │ ├── SmallTile.scale-200.png │ ├── SmallTile.scale-400.png │ ├── SplashScreen.scale-100.png │ ├── SplashScreen.scale-125.png │ ├── SplashScreen.scale-150.png │ ├── SplashScreen.scale-200.png │ ├── SplashScreen.scale-400.png │ ├── Square150x150Logo.scale-100.png │ ├── Square150x150Logo.scale-125.png │ ├── Square150x150Logo.scale-150.png │ ├── Square150x150Logo.scale-200.png │ ├── Square150x150Logo.scale-400.png │ ├── Square44x44Logo.altform-lightunplated_targetsize-16.png │ ├── Square44x44Logo.altform-lightunplated_targetsize-20.png │ ├── Square44x44Logo.altform-lightunplated_targetsize-24.png │ ├── Square44x44Logo.altform-lightunplated_targetsize-256.png │ ├── Square44x44Logo.altform-lightunplated_targetsize-30.png │ ├── Square44x44Logo.altform-lightunplated_targetsize-32.png │ ├── Square44x44Logo.altform-lightunplated_targetsize-36.png │ ├── Square44x44Logo.altform-lightunplated_targetsize-40.png │ ├── Square44x44Logo.altform-lightunplated_targetsize-44.png │ ├── Square44x44Logo.altform-lightunplated_targetsize-48.png │ ├── Square44x44Logo.altform-lightunplated_targetsize-60.png │ ├── Square44x44Logo.altform-lightunplated_targetsize-64.png │ ├── Square44x44Logo.altform-lightunplated_targetsize-72.png │ ├── Square44x44Logo.altform-lightunplated_targetsize-80.png │ ├── Square44x44Logo.altform-lightunplated_targetsize-96.png │ ├── Square44x44Logo.altform-unplated_targetsize-16.png │ ├── Square44x44Logo.altform-unplated_targetsize-20.png │ ├── Square44x44Logo.altform-unplated_targetsize-24.png │ ├── Square44x44Logo.altform-unplated_targetsize-256.png │ ├── Square44x44Logo.altform-unplated_targetsize-30.png │ ├── Square44x44Logo.altform-unplated_targetsize-32.png │ ├── Square44x44Logo.altform-unplated_targetsize-36.png │ ├── Square44x44Logo.altform-unplated_targetsize-40.png │ ├── Square44x44Logo.altform-unplated_targetsize-44.png │ ├── Square44x44Logo.altform-unplated_targetsize-48.png │ ├── Square44x44Logo.altform-unplated_targetsize-60.png │ ├── Square44x44Logo.altform-unplated_targetsize-64.png │ ├── Square44x44Logo.altform-unplated_targetsize-72.png │ ├── Square44x44Logo.altform-unplated_targetsize-80.png │ ├── Square44x44Logo.altform-unplated_targetsize-96.png │ ├── Square44x44Logo.scale-100.png │ ├── Square44x44Logo.scale-125.png │ ├── Square44x44Logo.scale-150.png │ ├── Square44x44Logo.scale-200.png │ ├── Square44x44Logo.scale-400.png │ ├── Square44x44Logo.targetsize-16.png │ ├── Square44x44Logo.targetsize-20.png │ ├── Square44x44Logo.targetsize-24.png │ ├── Square44x44Logo.targetsize-256.png │ ├── Square44x44Logo.targetsize-30.png │ ├── Square44x44Logo.targetsize-32.png │ ├── Square44x44Logo.targetsize-36.png │ ├── Square44x44Logo.targetsize-40.png │ ├── Square44x44Logo.targetsize-44.png │ ├── Square44x44Logo.targetsize-48.png │ ├── Square44x44Logo.targetsize-60.png │ ├── Square44x44Logo.targetsize-64.png │ ├── Square44x44Logo.targetsize-72.png │ ├── Square44x44Logo.targetsize-80.png │ ├── Square44x44Logo.targetsize-96.png │ ├── StoreLogo.scale-100.png │ ├── StoreLogo.scale-125.png │ ├── StoreLogo.scale-150.png │ ├── StoreLogo.scale-200.png │ ├── StoreLogo.scale-400.png │ ├── Wide310x150Logo.scale-100.png │ ├── Wide310x150Logo.scale-125.png │ ├── Wide310x150Logo.scale-150.png │ ├── Wide310x150Logo.scale-200.png │ └── Wide310x150Logo.scale-400.png ├── workbox-e43f5367.js └── workbox-e43f5367.js.map ├── schema └── index.ts ├── styles ├── globals.css └── loading.css ├── tsconfig.json └── types └── index.ts /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/core-web-vitals", "plugin:prettier/recommended"] 3 | } 4 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @avalynndev -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: 🐞 Found a Bug? 2 | description: Spotted something off? Let us know. 3 | title: '[Bug]: ' 4 | assignees: 5 | - avalynndev 6 | body: 7 | - type: input 8 | id: bug-description 9 | attributes: 10 | label: What went wrong? 11 | description: Tell us about the bug like you're telling a friend. Keep it simple and clear. 12 | placeholder: 'Like, when I try to play a video, it just won’t start...' 13 | validations: 14 | required: true 15 | 16 | - type: textarea 17 | id: steps-to-reproduce 18 | attributes: 19 | label: How'd you stumble upon it? 20 | description: Walk us through how you found this bug, step by step. 21 | placeholder: "1. I was on the homepage\n2. Clicked on the play button\n3. And boom, nothing happened" 22 | validations: 23 | required: true 24 | 25 | - type: textarea 26 | id: expected-vs-actual 27 | attributes: 28 | label: What you hoped for vs. What actually happened 29 | description: Share what you were expecting and then what really went down. 30 | placeholder: "Hoped for: A cool video starts playing.\nBut actually: Got a whole lot of nothing." 31 | validations: 32 | required: true 33 | 34 | - type: textarea 35 | id: additional-info 36 | attributes: 37 | label: Anything else we should know? 38 | description: Got more details or a screenshot? Throw them in here. 39 | placeholder: 'FYI: This only happens in Chrome for me...' 40 | validations: 41 | required: false 42 | 43 | - type: dropdown 44 | id: browsers-affected 45 | attributes: 46 | label: Which browsers were a bummer? 47 | multiple: true 48 | options: 49 | - Firefox 50 | - Chrome 51 | - Safari 52 | - Microsoft Edge 53 | validations: 54 | required: true 55 | 56 | - type: textarea 57 | id: logs 58 | attributes: 59 | label: Got logs? 60 | description: If you've got some techy details or logs, we'd love to see them. 61 | render: shell 62 | validations: 63 | required: false 64 | 65 | - type: checkboxes 66 | id: code-of-conduct 67 | attributes: 68 | label: Code of Conduct Agreement 69 | options: 70 | - label: I agree to follow this project's Code of Conduct. 71 | required: true 72 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **What kind of change does this PR introduce?** 4 | 5 | 6 | 7 | **If relevant, did you update the documentation?** 8 | 9 | **Summary** 10 | 11 | 12 | 13 | 14 | **Other information** 15 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: 'bun' # See documentation for possible values 9 | directory: '/' # Location of package manifests 10 | schedule: 11 | interval: 'weekly' 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | .env 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/prettierrc", 3 | "singleQuote": true, 4 | "trailingComma": "all", 5 | "semi": true, 6 | "bracketSpacing": true, 7 | "tabWidth": 2, 8 | "printWidth": 100, 9 | "plugins": ["prettier-plugin-tailwindcss"] 10 | } 11 | -------------------------------------------------------------------------------- /addition.t: -------------------------------------------------------------------------------- 1 | GET /embed-2/e-1/h6IbasovfOCp?k=1&autoPlay=1&oa=0&asi=1 HTTP/1.1 2 | Host: megacloud.club 3 | Sec-Ch-Ua: "Chromium";v="133", "Not(A:Brand";v="99" 4 | Sec-Ch-Ua-Mobile: ?0 5 | Sec-Ch-Ua-Platform: "Windows" 6 | Accept-Language: en-US,en;q=0.9 7 | Upgrade-Insecure-Requests: 1 8 | User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36 9 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 10 | Sec-Fetch-Site: cross-site 11 | Sec-Fetch-Mode: navigate 12 | Sec-Fetch-Dest: iframe 13 | Sec-Fetch-Storage-Access: active 14 | Referer: https://hianime.sx/ 15 | Accept-Encoding: gzip, deflate, br 16 | Priority: u=0, i 17 | Connection: keep-alive 18 | 19 | 20 | 21 | 22 | 23 | 24 | curl -X GET "https://sunshinerays93.live/_v7/44317e397742d80fc95c9c7660d83a4a79540f941a1491a9972a9c436118e19ef08e6242764981d88901edccddc6472477651425d01fc555793eec2f3d3b65ab80125154403da9f525193cce30f2cdfe871e8701d2b023f06e42c5ad7c274158e9e1a744dd6190cab767c1da28e478f79a16bed4a7379f9baee9670a2995c763/index-f2-v1-a1.m3u8" \ 25 | -H "Accept: */*" \ 26 | -H "Accept-Encoding: gzip, deflate, br, zstd" \ 27 | -H "Accept-Language: en-US,en;q=0.5" \ 28 | -H "Origin: https://megacloud.club" \ 29 | -H "Referer: https://megacloud.club/" \ 30 | -H "Sec-Ch-Ua: \"Chromium\";v=\"134\", \"Not:A-Brand\";v=\"24\", \"Brave\";v=\"134\"" \ 31 | -H "Sec-Ch-Ua-Mobile: ?0" \ 32 | -H "Sec-Ch-Ua-Platform: \"Windows\"" \ 33 | -H "Sec-Fetch-Dest: empty" \ 34 | -H "Sec-Fetch-Mode: cors" \ 35 | -H "Sec-Fetch-Site: cross-site" \ 36 | -H "Sec-Gpc: 1" \ 37 | -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36" \ 38 | --compressed 39 | 40 | 41 | -------------------------------------------------------------------------------- /app/(pages)/anime/[id]/page.tsx: -------------------------------------------------------------------------------- 1 | import DetailsContainer from '@/components/containers/anime/details'; 2 | import Zoro from 'avalynndev-extensions/dist/providers/anime/zoro'; 3 | import Anilist from 'avalynndev-extensions/dist/providers/meta/anilist'; 4 | 5 | export default async function Info({ params }: any) { 6 | const p = await params; 7 | const anilist = new Anilist(new Zoro()); 8 | const res = await anilist.fetchAnilistInfoById(p.id); 9 | 10 | return ; 11 | } 12 | -------------------------------------------------------------------------------- /app/(pages)/anime/page.tsx: -------------------------------------------------------------------------------- 1 | import FeaturedAnime from '@/components/featured/anime'; 2 | import Carousal from '@/components/carousal/anime'; 3 | import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; 4 | 5 | export default function Home() { 6 | return ( 7 | <> 8 |
9 | 10 | 11 | 12 | Trending 13 | Popular 14 | Recent 15 | 16 | 17 |
18 |
19 |

20 | Trending Anime 21 |

22 | 23 |

Anime ordered by trending.

24 |
25 |
26 | 27 |
28 | 29 |
30 |
31 |

32 | Popular Anime 33 |

34 | 35 |

Anime that are all-time popular.

36 |
37 |
38 | 39 |
40 | 41 |
42 |
43 |

44 | Recent Episodes 45 |

46 | 47 |

Episodes released recently

48 |
49 |
50 | 51 |
52 |
53 |
54 | 55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /app/(pages)/anime/watch/[id]/[episode]/page.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import { HiAnime } from 'aniwatch'; 3 | import { Badge } from '@/components/ui/badge'; 4 | import { Download } from 'lucide-react'; 5 | 6 | import { 7 | Breadcrumb, 8 | BreadcrumbItem, 9 | BreadcrumbLink, 10 | BreadcrumbList, 11 | BreadcrumbPage, 12 | BreadcrumbSeparator, 13 | } from '@/components/ui/breadcrumb'; 14 | import WatchContainer from '@/components/containers/anime/watch'; 15 | 16 | import { MediaPlayer, MediaProvider } from '@vidstack/react'; 17 | import { defaultLayoutIcons, DefaultVideoLayout } from '@vidstack/react/player/layouts/default'; 18 | import '@vidstack/react/player/styles/default/theme.css'; 19 | import '@vidstack/react/player/styles/default/layouts/video.css'; 20 | 21 | export default async function Watch({ params }: any) { 22 | const { id, episode } = await params; 23 | const hianime = new HiAnime.Scraper(); 24 | const data = await hianime.getEpisodeSources( 25 | episode.replace(/\$?episode\$?\d*/gi, '?ep=').replace(/%24/g, ''), 26 | 'hd-2', 27 | 'sub', 28 | ); 29 | 30 | const m3u8Source = data.sources.find((source) => source.type === 'hls')?.url || ''; 31 | const proxy = `${process.env.PROXY_M3U8}`; 32 | const vidURL = proxy + m3u8Source; 33 | return ( 34 |
35 |
36 |
37 |
38 | 39 | 40 | 41 | 42 | {id.charAt(0).toUpperCase() + id.slice(1)} 43 | 44 | 45 | 46 | 47 | {episode} 48 | 49 | 50 | 51 |
52 |
53 |
54 |
55 |
56 |
57 | 58 | 59 | 60 | Download Episode 61 | 62 | 63 |
64 |
65 |
66 |
67 | {m3u8Source ? ( 68 | 69 | 70 | 71 | 72 | ) : ( 73 |

No playable source found.

74 | )} 75 |
76 |
77 | 78 |
79 |
80 | ); 81 | } 82 | -------------------------------------------------------------------------------- /app/(pages)/drama/page.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@/components/ui/button'; 2 | import Link from 'next/link'; 3 | 4 | export default function NotFound() { 5 | return ( 6 |
7 |
8 |

9 | 404 10 |

11 | 12 |

13 | The drama section has been removed. Our previous provider, Dramacool, is 14 | no longer available, and no suitable alternative has been found yet. 15 |

16 |

17 | You can explore other alternative sources listed on the{' '} 18 | 22 | /list 23 | {' '} 24 | page. 25 |

26 | 27 | 34 |
35 |
36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /app/(pages)/layout.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { Footer } from '@/components/footer'; 3 | import { SiteHeader } from '@/components/site-header'; 4 | import { MobileNavBar } from '@/components/mobile-nav'; 5 | import { useIsMobile } from '@/hooks/use-mobile'; 6 | 7 | export default function RootLayout({ 8 | children, 9 | }: Readonly<{ 10 | children: React.ReactNode; 11 | }>) { 12 | const isMobile = useIsMobile(); 13 | return ( 14 |
15 | 16 |
{children}
17 | {isMobile ? <> :
} 18 | 19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /app/(pages)/list/page.tsx: -------------------------------------------------------------------------------- 1 | import * as Craft from '@/components/ui/craft'; 2 | import Link from 'next/link'; 3 | 4 | type FeatureText = { 5 | title: string; 6 | description: string; 7 | href?: string; 8 | }; 9 | 10 | const featureText: FeatureText[] = [ 11 | { 12 | title: 'Movie/TV', 13 | href: '/list/mtv', 14 | description: 'Explore Movie And Tv Shows: Spectacles Await You!', 15 | }, 16 | { 17 | title: 'Anime', 18 | href: '/list/anime', 19 | description: 'Dive into Anime Worlds: Adventures Await!', 20 | }, 21 | ]; 22 | 23 | export default function List() { 24 | return ( 25 |
26 |
27 |
28 |
29 |

List

30 |

31 | Discover and enjoy free streaming content across various genres. 32 |

33 |
34 | 35 | 36 |
37 |
38 | {featureText.map(({ title, description, href }, index) => ( 39 | 44 |
45 |

46 | {title}{' '} 47 | 48 | -> 49 | 50 |

51 |

{description}

52 |
53 | 54 | ))} 55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /app/(pages)/manga/[title]/page.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image'; 2 | import Link from 'next/link'; 3 | import { PreFetchMangaInfo, GetSearchedAnime } from '@/lib/consumet'; 4 | import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; 5 | 6 | export default async function MangaInfo({ params }: any) { 7 | const title = await params.title; 8 | const data = await GetSearchedAnime(title); 9 | 10 | PreFetchMangaInfo(data); 11 | 12 | return ( 13 |
14 | {title && ( 15 |
16 |

Searched for: {decodeURIComponent(title)}

17 |
18 | )} 19 |
20 | {data && 21 | data.results.map(async (item: any, index: any) => { 22 | return ( 23 | 29 | 30 | 31 | 32 | {item.title['english'] || item.title['romaji']} 33 | 34 |
35 | 36 | Vol:{' '} 37 | {item.volumes !== undefined && item.volumes !== null ? item.volumes : '?'} 38 | 39 | 40 | Ch:{' '} 41 | {item.totalChapters !== undefined && item.totalChapters !== null 42 | ? item.totalChapters 43 | : '?'} 44 | 45 |
46 | 47 | {item.status !== undefined && item.status !== null ? item.status : '?'} 48 | 49 |
50 | 51 | Manga Poster 58 | 59 |
60 | 61 | ); 62 | })} 63 |
64 |
65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /app/(pages)/manga/page.tsx: -------------------------------------------------------------------------------- 1 | import SearchBar from '@/components/manga-search'; 2 | import Image from 'next/image'; 3 | 4 | export default function Hero() { 5 | return ( 6 |
7 |
8 |
9 | hero image 16 |
17 | 18 |
19 |

Explore Mangas!

20 |
21 | 22 |
23 |
24 |
25 |
26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /app/(pages)/manga/read/[id]/[title]/[lang]/[chapter]/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { useState, useEffect, useCallback } from 'react'; 3 | import Image from 'next/image'; 4 | import { fetchChapter } from '@/lib/consumet'; 5 | import { useParams } from 'next/navigation'; 6 | 7 | export default function Read() { 8 | const params = useParams(); 9 | const { id, title, lang, chapter } = params as Record; 10 | 11 | const [images, setImages] = useState([]); 12 | const fetchData = useCallback(async () => { 13 | try { 14 | const fetchedResults = await fetchChapter(`${title}/${lang}/${chapter}`); 15 | setImages(fetchedResults.map((page: any) => page.img)); 16 | } catch (error) { 17 | console.error('Error fetching data:', error); 18 | } 19 | }, [chapter, lang, title]); 20 | 21 | useEffect(() => { 22 | fetchData(); 23 | }, [fetchData]); 24 | 25 | return ( 26 |
27 |
28 | {images.map((item, index) => ( 29 |
30 | Pages 40 |
41 | ))} 42 |
43 |
44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /app/(pages)/movie/[id]/loading.tsx: -------------------------------------------------------------------------------- 1 | import { Skeleton } from '@/components/ui/skeleton'; 2 | 3 | export default function Loading() { 4 | return ( 5 |
6 |
7 |
8 | 9 |
10 |
11 |
12 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 |
25 | 26 |
27 |
28 | 29 |
30 |
31 |
32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /app/(pages)/movie/[id]/page.tsx: -------------------------------------------------------------------------------- 1 | import DetailsContainer from '@/components/containers/movie/details'; 2 | import { tmdb } from '@/lib/tmdb'; 3 | 4 | export default async function MovieInfo({ params }: { params: Promise<{ id: string }> }) { 5 | const id = (await params).id; 6 | 7 | try { 8 | const data = await tmdb.movies.details(Number(id), 'en-US'); 9 | return ( 10 |
11 | 12 |
13 | ); 14 | } catch (err: any) { 15 | const errorMessage = err instanceof Error ? err.message : 'An error occurred'; 16 | return
Error: {errorMessage}
; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/(pages)/movie/page.tsx: -------------------------------------------------------------------------------- 1 | import Featured from '@/components/featured/movie'; 2 | import Carousal from '@/components/carousal/movie'; 3 | 4 | import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; 5 | 6 | export default function Home() { 7 | return ( 8 | <> 9 |
10 | 11 | 12 | 13 | Popular 14 | Now Playing 15 | Top Rated 16 | Upcoming 17 | 18 | 19 |
20 |
21 |

22 | Popular Movies 23 |

24 | 25 |

Movies ordered by popularity.

26 |
27 |
28 | 29 |
30 | 31 |
32 |
33 |

34 | Now Playing 35 |

36 | 37 |

Movies that are currently in theatres.

38 |
39 |
40 | 41 |
42 | 43 |
44 |
45 |

46 | Top Rated 47 |

48 | 49 |

Movies ordered by rating.

50 |
51 |
52 | 53 |
54 | 55 |
56 |
57 |

58 | Upcoming 59 |

60 | 61 |

Movies that are being released soon.

62 |
63 |
64 | 65 |
66 |
67 |
68 | 69 | ); 70 | } 71 | -------------------------------------------------------------------------------- /app/(pages)/movie/watch/[id]/page.tsx: -------------------------------------------------------------------------------- 1 | import VideoPlayer from '@/components/containers/movie/videoplayer'; 2 | 3 | export default async function Info({ params }: any) { 4 | const id = (await params).id; 5 | 6 | return ; 7 | } 8 | -------------------------------------------------------------------------------- /app/(pages)/page.tsx: -------------------------------------------------------------------------------- 1 | import { Badge } from '@/components/ui/badge'; 2 | import { Button } from '@/components/ui/button'; 3 | import Link from 'next/link'; 4 | import HeroSection from '@/components/hero'; 5 | 6 | export default async function Home() { 7 | return ( 8 | <> 9 |
10 |
11 |
12 | 13 | 14 | Welcome to the town of enjoyment! 🏙️ 15 | 16 | 17 |

Explore movies, tv series and animes!

18 |

19 | EnjoyTown is a streaming platform for lazy people who like to 20 |
21 | watch millions of movies, series and animes for free. Go down to watch 22 |

23 |
24 | 27 |
28 |
29 |
30 |
31 | 32 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /app/(pages)/removed/page.tsx: -------------------------------------------------------------------------------- 1 | import { Pattern } from '@/components/ui/pattern'; 2 | import { Button } from '@/components/ui/button'; 3 | import Link from 'next/link'; 4 | 5 | export default function Removed() { 6 | return ( 7 |
8 | 9 | 10 |
11 |

12 | 13 | DMCA Takedown Notice 14 | 15 | 16 |

17 | 18 |

19 | The content you’re looking for has been{' '} 20 | removed due to a DMCA takedown 21 | request. We apologize for the inconvenience. 22 |

23 | 24 |

25 | Why was it removed? Under the Digital Millennium 26 | Copyright Act (DMCA), we are required to remove content upon valid copyright complaints. 27 |

28 | 29 |
30 | 31 | 38 | 39 | 40 | 47 | 48 |
49 |
50 |
51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /app/(pages)/search/page.tsx: -------------------------------------------------------------------------------- 1 | import SearchHeader from '@/components/containers/search-bar'; 2 | 3 | export default function SearchPage() { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /app/(pages)/tv/[id]/loading.tsx: -------------------------------------------------------------------------------- 1 | import { Skeleton } from '@/components/ui/skeleton'; 2 | 3 | export default function Loading() { 4 | return ( 5 |
6 |
7 |
8 | 9 |
10 |
11 |
12 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 |
25 | 26 |
27 |
28 | 29 |
30 |
31 |
32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /app/(pages)/tv/[id]/page.tsx: -------------------------------------------------------------------------------- 1 | import DetailsContainer from '@/components/containers/tv/details'; 2 | import { tmdb } from '@/lib/tmdb'; 3 | 4 | export default async function MovieInfo({ params }: { params: Promise<{ id: string }> }) { 5 | const id = (await params).id; 6 | 7 | try { 8 | const data = await tmdb.tv.details(Number(id), 'en-US'); 9 | return ( 10 |
11 | 12 |
13 | ); 14 | } catch (err: any) { 15 | const errorMessage = err instanceof Error ? err.message : 'An error occurred'; 16 | return
Error: {errorMessage}
; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/(pages)/tv/page.tsx: -------------------------------------------------------------------------------- 1 | import Featured from '@/components/featured/tv'; 2 | import Carousal from '@/components/carousal/tv'; 3 | import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; 4 | 5 | export default function Home() { 6 | return ( 7 | <> 8 |
9 | 10 | 11 | 12 | Popular 13 | Airing Today 14 | Top Rated 15 | On the Air 16 | 17 | 18 |
19 |
20 |

21 | Popular 22 |

23 | 24 |

Tv Shows ordered by popularity.

25 |
26 |
27 | 28 |
29 | 30 |
31 |
32 |

33 | Airing Today 34 |

35 | 36 |

Tv Shows that are airing today.

37 |
38 |
39 | 40 |
41 | 42 |
43 |
44 |

45 | Top Rated 46 |

47 | 48 |

Tv Shows ordered by rating.

49 |
50 |
51 | 52 |
53 | 54 |
55 |
56 |

57 | On the air 58 |

59 | 60 |

Tv Shows that are Airing right now

61 |
62 |
63 | 64 |
65 |
66 |
67 | 68 | ); 69 | } 70 | -------------------------------------------------------------------------------- /app/(pages)/tv/watch/[id]/page.tsx: -------------------------------------------------------------------------------- 1 | import VideoPlayer from '@/components/containers/tv/videoplayer'; 2 | 3 | export default async function Info({ params }: any) { 4 | const id = (await params).id; 5 | 6 | return ; 7 | } 8 | -------------------------------------------------------------------------------- /app/(protected)/auth/[pathname]/page.tsx: -------------------------------------------------------------------------------- 1 | import { AuthCard, authViewPaths } from '@daveyplate/better-auth-ui'; 2 | 3 | export function generateStaticParams() { 4 | return Object.values(authViewPaths).map((pathname) => ({ pathname })); 5 | } 6 | 7 | export default async function AuthPage({ params }: { params: Promise<{ pathname: string }> }) { 8 | const { pathname } = await params; 9 | return ( 10 |
11 | 12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /app/(protected)/layout.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { Footer } from '@/components/footer'; 3 | import { SiteHeader } from '@/components/site-header'; 4 | import { MobileNavBar } from '@/components/mobile-nav'; 5 | import { useIsMobile } from '@/hooks/use-mobile'; 6 | 7 | export default function RootLayout({ 8 | children, 9 | }: Readonly<{ 10 | children: React.ReactNode; 11 | }>) { 12 | const isMobile = useIsMobile(); 13 | return ( 14 |
15 | 16 |
{children}
17 | {isMobile ? <> :
} 18 | 19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /app/api/auth/[...all]/route.ts: -------------------------------------------------------------------------------- 1 | import { auth } from '@/lib/auth'; 2 | import { toNextJsHandler } from 'better-auth/next-js'; 3 | 4 | export const { POST, GET } = toNextJsHandler(auth); 5 | -------------------------------------------------------------------------------- /app/api/feedback/route.ts: -------------------------------------------------------------------------------- 1 | import { db } from '@/db/drizzle'; 2 | import { feedback } from '@/schema'; // adjust import as needed 3 | import { NextResponse } from 'next/server'; 4 | 5 | export async function POST(req: Request) { 6 | try { 7 | const body = await req.json(); 8 | 9 | await db.insert(feedback).values({ 10 | type: body.type, 11 | name: body.name, 12 | url: body.url, 13 | issue: body.issue, 14 | reason: body.reason, 15 | logs: body.logs, 16 | message: body.message, 17 | email: body.email, 18 | }); 19 | 20 | return NextResponse.json({ success: true }); 21 | } catch (error) { 22 | console.error('[FEEDBACK_POST_ERROR]', error); 23 | return NextResponse.json({ success: false, error: 'Internal server error' }, { status: 500 }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/not-found.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@/components/ui/button'; 2 | import Link from 'next/link'; 3 | 4 | export default function NotFound() { 5 | return ( 6 |
7 |
8 |

9 | 404 10 |

11 | 12 |

13 | Did you take a wrong turn? 14 |

15 | 22 |
23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /app/providers.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { AuthQueryProvider } from '@daveyplate/better-auth-tanstack'; 4 | import { AuthUIProviderTanstack } from '@daveyplate/better-auth-ui/tanstack'; 5 | import { QueryClient, QueryClientProvider, isServer } from '@tanstack/react-query'; 6 | import Link from 'next/link'; 7 | import { useRouter } from 'next/navigation'; 8 | import type { ReactNode } from 'react'; 9 | import { toast } from 'sonner'; 10 | 11 | import { authClient } from '@/lib/auth-client'; 12 | 13 | function makeQueryClient() { 14 | return new QueryClient({ 15 | defaultOptions: { 16 | queries: { 17 | staleTime: 60 * 1000, 18 | }, 19 | }, 20 | }); 21 | } 22 | 23 | let browserQueryClient: QueryClient | undefined = undefined; 24 | 25 | function getQueryClient() { 26 | if (isServer) { 27 | // Server: always make a new query client 28 | return makeQueryClient(); 29 | } 30 | 31 | // Browser: make a new query client if we don't already have one 32 | // This is very important, so we don't re-make a new client if React 33 | // suspends during the initial render. This may not be needed if we 34 | // have a suspense boundary BELOW the creation of the query client 35 | if (!browserQueryClient) browserQueryClient = makeQueryClient(); 36 | return browserQueryClient; 37 | } 38 | 39 | export function Providers({ children }: { children: ReactNode }) { 40 | // NOTE: Avoid useState when initializing the query client if you don't 41 | // have a suspense boundary between this and the code that may 42 | // suspend because React will throw away the client on the initial 43 | // render if it suspends and there is no boundary 44 | const queryClient = getQueryClient(); 45 | queryClient.getQueryCache().config.onError = (error, query) => { 46 | console.error(error, query); 47 | 48 | if (error.message) toast.error(error.message); 49 | }; 50 | 51 | const router = useRouter(); 52 | 53 | return ( 54 | 55 | 56 | { 61 | router.refresh(); 62 | }} 63 | signUpFields={['']} 64 | magicLink 65 | nameRequired={false} 66 | Link={Link} 67 | multiSession 68 | deleteUser 69 | username 70 | > 71 | {children} 72 | 73 | 74 | 75 | ); 76 | } 77 | -------------------------------------------------------------------------------- /app/robots.ts: -------------------------------------------------------------------------------- 1 | import { MetadataRoute } from 'next'; 2 | 3 | export default function robots(): MetadataRoute.Robots { 4 | return { 5 | rules: { 6 | userAgent: '*', 7 | allow: '/', 8 | }, 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "", 8 | "css": "styles/globals.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } 22 | -------------------------------------------------------------------------------- /components/carousal/anime.tsx: -------------------------------------------------------------------------------- 1 | import { Carousel, CarouselContent, CarouselItem } from '@/components/ui/carousel'; 2 | import Zoro from 'avalynndev-extensions/dist/providers/anime/zoro'; 3 | import Anilist from 'avalynndev-extensions/dist/providers/meta/anilist'; 4 | import CarousalCard from '@/components/carousal/card'; 5 | 6 | export default async function CarousalComponent() { 7 | const anilist = new Anilist(new Zoro()); 8 | const data = await anilist.fetchPopularAnime(1, 20); 9 | 10 | if (!data) return
None Found
; 11 | 12 | return ( 13 | <> 14 | 15 | 16 | {data.results?.map((el: any) => ( 17 | 18 | 19 | 20 | ))} 21 | 22 | 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /components/carousal/card.tsx: -------------------------------------------------------------------------------- 1 | import { AnimeShow } from '@/types'; 2 | import { Badge } from '@/components/ui/badge'; 3 | import { format } from 'date-fns'; 4 | import { Button } from '@/components/ui/button'; 5 | import Link from 'next/link'; 6 | import Image from 'next/image'; 7 | 8 | interface CarouselCardProps { 9 | show: AnimeShow; 10 | } 11 | 12 | const CarouselCard: React.FC = ({ show }) => { 13 | return ( 14 | <> 15 |
16 | {show.title.userPreferred} 23 |
24 |
25 |
26 |
27 | {show.title.english || show.title.romaji} 28 |
29 |
{show.genres.join(', ') || 'Comedy'}
30 |
31 |
32 |
33 |
34 | {show.title.userPreferred} 41 |
42 |
43 |
44 |
45 |
{show.releaseDate}
46 |
47 | {show.title.english || show.title.romaji} 48 |
49 |
{show.description}
50 |
51 | 52 | 55 | 56 | {show.genres.map((genre, index) => ( 57 | 58 | {genre} 59 | 60 | ))} 61 |
62 |
63 |
64 |
65 |
66 | 67 | ); 68 | }; 69 | 70 | export default CarouselCard; 71 | -------------------------------------------------------------------------------- /components/carousal/movie.tsx: -------------------------------------------------------------------------------- 1 | import { Carousel, CarouselContent, CarouselItem } from '@/components/ui/carousel'; 2 | import CarousalCard from '@/components/common/card'; 3 | import { tmdb } from '@/lib/tmdb'; 4 | 5 | export default async function CarousalComponent() { 6 | const trending = await tmdb.movies.trending('day', 'en-US'); 7 | if (!trending) return
None Found
; 8 | 9 | return ( 10 | <> 11 | 12 | 13 | {trending.results?.map((movie) => ( 14 | 15 | 16 | 17 | ))} 18 | 19 | 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /components/carousal/tv.tsx: -------------------------------------------------------------------------------- 1 | import { Carousel, CarouselContent, CarouselItem } from '@/components/ui/carousel'; 2 | import CarousalCard from '@/components/common/card'; 3 | import { tmdb } from '@/lib/tmdb'; 4 | 5 | export default async function CarousalComponent() { 6 | const trending = await tmdb.tv.trending('day', 'en-US'); 7 | if (!trending) return
None Found
; 8 | 9 | return ( 10 | <> 11 | 12 | 13 | {trending.results?.map((tvShow) => ( 14 | 15 | 16 | 17 | ))} 18 | 19 | 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /components/common/card.tsx: -------------------------------------------------------------------------------- 1 | import { format } from 'date-fns'; 2 | import { Button } from '@/components/ui/button'; 3 | import Link from 'next/link'; 4 | import Image from 'next/image'; 5 | import { Show } from '@/types'; 6 | 7 | interface CarousalCardProps { 8 | show: Show; 9 | type?: 'tv' | 'movie' | 'anime'; 10 | id?: string; 11 | } 12 | 13 | export default function CarousalCard({ show, type }: CarousalCardProps) { 14 | const title = show.title || show.name || 'Unknown Title'; 15 | const releaseDate = show.release_date || show.first_air_date || 'Unknown Date'; 16 | 17 | return ( 18 | <> 19 |
20 | {title} 27 |
28 |
29 |
30 |
31 | {title} 32 |
33 |
34 |
35 |
36 |
37 | {title} 44 |
45 |
46 |
47 |
48 |
49 | {releaseDate ? format(new Date(releaseDate), 'PPP') : 'Unknown'} 50 |
51 |
{show.title || show.name}
52 |
{show?.overview}
53 |
54 | 55 | 58 | 59 |
60 |
61 |
62 |
63 |
64 | 65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /components/common/poster.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image'; 2 | import { Image as LucideImage } from 'lucide-react'; 3 | import { ComponentProps } from 'react'; 4 | import { cn } from '@/lib/utils'; 5 | 6 | type PosterProps = { 7 | url?: string; 8 | alt: string; 9 | } & ComponentProps<'div'>; 10 | 11 | export const Poster = ({ url, alt, className, ...props }: PosterProps) => { 12 | return ( 13 |
20 | {url ? ( 21 | {alt} 29 | ) : ( 30 | 31 | )} 32 |
33 | ); 34 | }; 35 | -------------------------------------------------------------------------------- /components/containers/anime/watch.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import { Button } from '@/components/ui/button'; 3 | import { ImageIcon, Play } from 'lucide-react'; 4 | import Image from 'next/image'; 5 | import { ScrollArea } from '@/components/ui/scroll-area'; 6 | import Zoro from 'avalynndev-extensions/dist/providers/anime/zoro'; 7 | import Anilist from 'avalynndev-extensions/dist/providers/meta/anilist'; 8 | 9 | export default async function Watch({ id }: any) { 10 | const anilist = new Anilist(new Zoro()); 11 | const data = await anilist.fetchEpisodesListById(id); 12 | return ( 13 | 14 |
15 | {data.map((episode: any) => ( 16 | 21 |
22 | {episode.image ? ( 23 | {episode.title 30 | ) : ( 31 | 32 | )} 33 |
34 | 37 |
38 |
39 |
40 |

{`Episode ${episode.number}`}

41 |

42 | {episode.title ? episode.title : `Episode ${episode.number}`} 43 |

44 |
45 | 46 | ))} 47 |
48 |
49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /components/containers/drama/episode.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Link from 'next/link'; 3 | import { Button } from '@/components/ui/button'; 4 | 5 | const EpisodeContainer = ({ data, id }: any) => { 6 | return ( 7 |
8 |
9 |
10 | {data && 11 | data.map((episodes: any) => ( 12 | 17 | 18 | 19 | ))} 20 |
21 |
22 |
23 | ); 24 | }; 25 | 26 | export default EpisodeContainer; 27 | -------------------------------------------------------------------------------- /components/containers/drama/watch-episode.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Link from 'next/link'; 3 | import { Button } from '@/components/ui/button'; 4 | 5 | const WatchEpisodes = ({ data, id }: any) => { 6 | return ( 7 |
8 |
9 |
10 | {data && 11 | data.map((episodes: any) => ( 12 | 17 | 18 | 19 | ))} 20 |
21 |
22 |
23 | ); 24 | }; 25 | 26 | export default WatchEpisodes; 27 | -------------------------------------------------------------------------------- /components/containers/movie/related.tsx: -------------------------------------------------------------------------------- 1 | import { Poster } from '@/components/common/poster'; 2 | import { 3 | Carousel, 4 | CarouselContent, 5 | CarouselItem, 6 | CarouselNext, 7 | CarouselPrevious, 8 | } from '@/components/ui/carousel'; 9 | import { MovieRelatedType, tmdb } from '@/lib/tmdb'; 10 | 11 | type RelatedMoviesProps = { 12 | id: string; 13 | type: MovieRelatedType; 14 | }; 15 | 16 | export default async function RelatedMovies({ id, type }: RelatedMoviesProps) { 17 | const related = await tmdb.movies.related(Number(id), type, 'en-US'); 18 | 19 | return ( 20 | 21 | 22 | {related.results.map((movie) => ( 23 | 24 | 25 | 26 | 27 | 28 | ))} 29 | 30 | 31 | 32 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /components/containers/tv/related.tsx: -------------------------------------------------------------------------------- 1 | import { Poster } from '@/components/common/poster'; 2 | import { 3 | Carousel, 4 | CarouselContent, 5 | CarouselItem, 6 | CarouselNext, 7 | CarouselPrevious, 8 | } from '@/components/ui/carousel'; 9 | import { tmdb, TVRelatedType } from '@/lib/tmdb'; 10 | 11 | type RelatedTvProps = { 12 | id: string; 13 | type: TVRelatedType; 14 | }; 15 | 16 | export default async function RelatedTv({ id, type }: RelatedTvProps) { 17 | const related = await tmdb.tv.related(Number(id), type, 'en-US'); 18 | 19 | return ( 20 | 21 | 22 | {related.results.map((movie) => ( 23 | 24 | 25 | 26 | 27 | 28 | ))} 29 | 30 | 31 | 32 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /components/donate.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { useEffect, useState } from 'react'; 3 | import { toast } from 'sonner'; 4 | import { useToastStore } from '@/lib/store'; 5 | import { usePathname } from 'next/navigation'; 6 | 7 | export default function RootLayout() { 8 | const { triggerToast } = useToastStore(); 9 | const pathname = usePathname(); 10 | const [hasToastShown, setHasToastShown] = useState(false); 11 | 12 | useEffect(() => { 13 | const remindLaterTimestamp = localStorage.getItem('remindLater'); 14 | const now = new Date().getTime(); 15 | 16 | if (remindLaterTimestamp && now < parseInt(remindLaterTimestamp, 10)) { 17 | return; 18 | } 19 | 20 | if (!hasToastShown) { 21 | triggerToast(); 22 | toast('Support My Work 💖', { 23 | description: `If you enjoy my work, consider buying me a coffee! ☕`, 24 | cancel: { 25 | label: 'Donate ❤️', 26 | onClick: () => { 27 | window.open('https://www.buymeacoffee.com/avalynndev', '_blank'); 28 | }, 29 | }, 30 | }); 31 | setHasToastShown(true); 32 | } 33 | }, [pathname, triggerToast, hasToastShown]); 34 | 35 | useEffect(() => { 36 | setHasToastShown(false); 37 | }, [pathname]); 38 | 39 | return <>; 40 | } 41 | -------------------------------------------------------------------------------- /components/featured/loading-featured.tsx: -------------------------------------------------------------------------------- 1 | import { Skeleton } from '@/components/ui/skeleton'; 2 | 3 | export default function Loading() { 4 | return Array.from({ length: 18 }).map((_, index) => ( 5 |
6 | 7 |
8 | 9 | 10 | 11 |
12 |
13 | )); 14 | } 15 | -------------------------------------------------------------------------------- /components/footer.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import { Clapperboard } from 'lucide-react'; 3 | 4 | export const Footer = () => { 5 | return ( 6 |
7 |
8 |
9 |
10 | 11 | 12 |

13 | © {new Date().getFullYear()} AniVerse Pte Ltd 14 |

15 | 16 |
17 | 18 | 19 | EnjoyTown doesn't store any media listed; we only link to third-party sources. 20 | 21 |
22 | 23 |
24 | 25 | Privacy Policy 26 | 27 | 28 | 29 | DMCA 30 | 31 | 32 | 33 | Github 34 | 35 | 36 | 37 | X 38 | 39 |
40 |
41 |
42 |
43 | ); 44 | }; 45 | -------------------------------------------------------------------------------- /components/item-hover-card.tsx: -------------------------------------------------------------------------------- 1 | import type { PropsWithChildren } from 'react'; 2 | import { HTMLAttributes } from 'react'; 3 | 4 | interface BannerProps extends HTMLAttributes {} 5 | 6 | const Banner = ({ className, children, ...props }: PropsWithChildren) => { 7 | return ( 8 |
12 | {children} 13 |
14 | ); 15 | }; 16 | 17 | const Information = (props: PropsWithChildren) => { 18 | return
; 19 | }; 20 | 21 | const Poster = (props: PropsWithChildren) => { 22 | return ( 23 |
24 |
28 |
29 | ); 30 | }; 31 | 32 | const Summary = (props: PropsWithChildren) => { 33 | return
; 34 | }; 35 | 36 | const Title = (props: PropsWithChildren) => { 37 | return ; 38 | }; 39 | 40 | const Overview = (props: PropsWithChildren) => { 41 | return ; 42 | }; 43 | 44 | export const ItemHoverCard = { 45 | Banner, 46 | Information, 47 | Poster, 48 | Summary, 49 | Title, 50 | Overview, 51 | }; 52 | -------------------------------------------------------------------------------- /components/loading.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '@/styles/loading.css'; 3 | 4 | const Loader = () => { 5 | return ( 6 |
7 |
8 | 9 | 10 | 11 |
12 |
13 | 14 | 15 | 16 |
17 |
18 | 19 | 20 | 21 |
22 |
23 | ); 24 | }; 25 | 26 | export default Loader; 27 | -------------------------------------------------------------------------------- /components/magicui/rainbow-button.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '@/lib/utils'; 2 | import React from 'react'; 3 | 4 | interface RainbowButtonProps extends React.ButtonHTMLAttributes {} 5 | 6 | export const RainbowButton = React.forwardRef( 7 | ({ children, className, ...props }, ref) => { 8 | return ( 9 | 25 | ); 26 | }, 27 | ); 28 | 29 | RainbowButton.displayName = 'RainbowButton'; 30 | -------------------------------------------------------------------------------- /components/manga-search.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { cn } from '@/lib/utils'; 3 | import { useState } from 'react'; 4 | import { useRouter } from 'next/navigation'; 5 | import { Input } from '@/components/ui/input'; 6 | 7 | export default function SearchBar() { 8 | const router = useRouter(); 9 | const [title, setMangaTitle] = useState(''); 10 | 11 | return ( 12 |
13 |
14 | setMangaTitle(e.target.value)} 23 | onKeyDown={(event: any) => { 24 | if ( 25 | (event.key === 'Enter' || event.code === '13' || event.code === 'Enter') && 26 | title !== '' 27 | ) { 28 | router.push(`/manga/${title}`); 29 | } 30 | }} 31 | /> 32 |
33 |
34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /components/nav-secondary.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import { type Icon } from '@tabler/icons-react'; 5 | 6 | import { 7 | SidebarGroup, 8 | SidebarGroupContent, 9 | SidebarMenu, 10 | SidebarMenuButton, 11 | SidebarMenuItem, 12 | } from '@/components/ui/sidebar'; 13 | 14 | export function NavSecondary({ 15 | items, 16 | ...props 17 | }: { 18 | items: { 19 | title: string; 20 | url: string; 21 | icon: Icon; 22 | }[]; 23 | } & React.ComponentPropsWithoutRef) { 24 | return ( 25 | 26 | 27 | 28 | {items.map((item) => ( 29 | 30 | 31 | 32 | 33 | {item.title} 34 | 35 | 36 | 37 | ))} 38 | 39 | 40 | 41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /components/nav-user-client.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useEffect, useState } from 'react'; 4 | import { SidebarMenu, SidebarMenuItem, SidebarMenuButton } from '@/components/ui/sidebar'; 5 | import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; 6 | import { 7 | DropdownMenu, 8 | DropdownMenuContent, 9 | DropdownMenuItem, 10 | DropdownMenuTrigger, 11 | } from '@/components/ui/dropdown-menu'; 12 | import { IconDotsVertical, IconLogout } from '@tabler/icons-react'; 13 | import Link from 'next/link'; 14 | import { authClient } from '@/lib/auth-client'; 15 | 16 | type User = { 17 | name: string; 18 | email: string; 19 | image?: string; 20 | }; 21 | 22 | export function NavUserClient() { 23 | const [user, setUser] = useState(null); 24 | 25 | useEffect(() => { 26 | const fetchSession = async () => { 27 | const res = await fetch('/api/auth/session'); 28 | const data = await res.json(); 29 | setUser(data.user); 30 | }; 31 | 32 | fetchSession(); 33 | }, []); 34 | 35 | return ( 36 | 37 | 38 | {user ? ( 39 | 40 | 41 | 42 | 43 | 44 | AV 45 | 46 |
47 | {user.name} 48 | {user.email} 49 |
50 | 51 |
52 |
53 | 54 | authClient.signOut()}> 55 | 56 | Log out 57 | 58 | 59 |
60 | ) : ( 61 | 62 | 63 | Login 64 | 65 | 66 | )} 67 |
68 |
69 | ); 70 | } 71 | -------------------------------------------------------------------------------- /components/site-header.tsx: -------------------------------------------------------------------------------- 1 | import { ThemeToggle } from '@/components/theme-toggle'; 2 | import { Separator } from '@/components/ui/separator'; 3 | import { SidebarTrigger } from '@/components/ui/sidebar'; 4 | import { useIsMobile } from '@/hooks/use-mobile'; 5 | import { Clapperboard } from 'lucide-react'; 6 | import { CommandSearch } from './command-search'; 7 | import { usePathname } from 'next/navigation'; 8 | 9 | export function SiteHeader() { 10 | const isMobile = useIsMobile(); 11 | const pathname = usePathname(); 12 | 13 | const isSearchPage = pathname.includes('/search'); 14 | 15 | return ( 16 |
17 |
18 | {isMobile ? : } 19 | 20 |
21 | {isMobile || isSearchPage ? ( 22 |

Enjoytown

23 | ) : ( 24 | 25 | )} 26 |
27 |
28 | 29 |
30 |
31 |
32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /components/storage.ts: -------------------------------------------------------------------------------- 1 | const STORAGE_KEY = 'recentSearches'; 2 | 3 | type RecentSearch = { 4 | term: string; 5 | category: 'movie' | 'tv' | 'anime' | 'recent'; 6 | }; 7 | 8 | export const saveSearchToLocalStorage = ( 9 | searchTerm: string, 10 | category: RecentSearch['category'], 11 | ): void => { 12 | const existingSearches: RecentSearch[] = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); 13 | const updatedSearches: RecentSearch[] = [ 14 | { term: searchTerm, category }, 15 | ...existingSearches.filter((item) => item.term !== searchTerm || item.category !== category), 16 | ]; 17 | localStorage.setItem(STORAGE_KEY, JSON.stringify(updatedSearches)); 18 | }; 19 | 20 | export const getRecentSearchesFromLocalStorage = (): RecentSearch[] => { 21 | const searches: RecentSearch[] = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); 22 | return searches; 23 | }; 24 | -------------------------------------------------------------------------------- /components/tailwind-indicator.tsx: -------------------------------------------------------------------------------- 1 | export function TailwindIndicator() { 2 | if (process.env.NODE_ENV === 'production') return null; 3 | 4 | return ( 5 |
6 |
xs
7 |
sm
8 |
md
9 |
lg
10 |
xl
11 |
2xl
12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /components/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import { ThemeProvider as NextThemesProvider } from 'next-themes'; 5 | 6 | export function ThemeProvider({ 7 | children, 8 | ...props 9 | }: React.ComponentProps) { 10 | return {children}; 11 | } 12 | -------------------------------------------------------------------------------- /components/theme-toggle.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import { MoonIcon, SunIcon } from '@radix-ui/react-icons'; 5 | import { useTheme } from 'next-themes'; 6 | 7 | import { Button } from '@/components/ui/button'; 8 | 9 | export function ThemeToggle() { 10 | const { setTheme, theme } = useTheme(); 11 | 12 | return ( 13 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /components/ui/avatar.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import * as AvatarPrimitive from '@radix-ui/react-avatar'; 5 | 6 | import { cn } from '@/lib/utils'; 7 | 8 | function Avatar({ className, ...props }: React.ComponentProps) { 9 | return ( 10 | 15 | ); 16 | } 17 | 18 | function AvatarImage({ className, ...props }: React.ComponentProps) { 19 | return ( 20 | 25 | ); 26 | } 27 | 28 | function AvatarFallback({ 29 | className, 30 | ...props 31 | }: React.ComponentProps) { 32 | return ( 33 | 38 | ); 39 | } 40 | 41 | export { Avatar, AvatarImage, AvatarFallback }; 42 | -------------------------------------------------------------------------------- /components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Slot } from '@radix-ui/react-slot'; 3 | import { cva, type VariantProps } from 'class-variance-authority'; 4 | 5 | import { cn } from '@/lib/utils'; 6 | 7 | const badgeVariants = cva( 8 | 'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden', 9 | { 10 | variants: { 11 | variant: { 12 | default: 'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90', 13 | secondary: 14 | 'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90', 15 | destructive: 16 | 'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60', 17 | outline: 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground', 18 | }, 19 | }, 20 | defaultVariants: { 21 | variant: 'default', 22 | }, 23 | }, 24 | ); 25 | 26 | function Badge({ 27 | className, 28 | variant, 29 | asChild = false, 30 | ...props 31 | }: React.ComponentProps<'span'> & VariantProps & { asChild?: boolean }) { 32 | const Comp = asChild ? Slot : 'span'; 33 | 34 | return ( 35 | 36 | ); 37 | } 38 | 39 | export { Badge, badgeVariants }; 40 | -------------------------------------------------------------------------------- /components/ui/breadcrumb.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Slot } from '@radix-ui/react-slot'; 3 | import { ChevronRight, MoreHorizontal } from 'lucide-react'; 4 | 5 | import { cn } from '@/lib/utils'; 6 | 7 | function Breadcrumb({ ...props }: React.ComponentProps<'nav'>) { 8 | return