├── .env.example ├── .github └── FUNDING.yml ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── config.js ├── consumet-api ├── .dockerignore ├── .editorconfig ├── .env.example ├── .github │ ├── ISSUE_TEMPLATE │ │ ├── bug-report.yml │ │ └── config.yml │ ├── dependabot.yml │ └── workflows │ │ ├── codeql-analysis.yml │ │ └── docker-build.yml ├── .gitignore ├── .prettierignore ├── .prettierrc.yml ├── Dockerfile ├── Dockerfile.alpine ├── Dockerfile.distroless ├── LICENSE ├── README.md ├── app.json ├── package.json ├── render.yaml ├── renovate.json ├── src │ ├── main.ts │ ├── models │ │ ├── index.ts │ │ └── types.ts │ ├── routes │ │ ├── anime │ │ │ ├── 9anime.ts │ │ │ ├── animefox.ts │ │ │ ├── animepahe.ts │ │ │ ├── bilibili.ts │ │ │ ├── crunchyroll.ts │ │ │ ├── enime.ts │ │ │ ├── gogoanime.ts │ │ │ ├── index.ts │ │ │ ├── marin.ts │ │ │ └── zoro.ts │ │ ├── books │ │ │ ├── index.ts │ │ │ └── libgen.ts │ │ ├── comics │ │ │ ├── getComics.ts │ │ │ └── index.ts │ │ ├── light-novels │ │ │ ├── index.ts │ │ │ └── readlightnovels.ts │ │ ├── manga │ │ │ ├── index.ts │ │ │ ├── managreader.ts │ │ │ ├── mangadex.ts │ │ │ ├── mangahere.ts │ │ │ ├── mangakakalot.ts │ │ │ ├── mangapark.ts │ │ │ ├── mangapill.ts │ │ │ └── mangasee123.ts │ │ ├── meta │ │ │ ├── anilist-manga.ts │ │ │ ├── anilist.ts │ │ │ ├── index.ts │ │ │ ├── mal.ts │ │ │ └── tmdb.ts │ │ ├── movies │ │ │ ├── dramacool.ts │ │ │ ├── flixhq.ts │ │ │ ├── fmovies.ts │ │ │ ├── index.ts │ │ │ └── viewasian.ts │ │ └── news │ │ │ ├── ann.ts │ │ │ └── index.ts │ └── utils │ │ ├── bilibili.ts │ │ ├── cache.ts │ │ ├── image-proxy.ts │ │ ├── index.ts │ │ ├── m3u8-proxy.ts │ │ ├── providers.ts │ │ └── rapid-cloud.ts ├── tsconfig.json ├── vercel.json └── yarn.lock ├── consumet_start.bat ├── consumet_start.sh ├── db.js ├── docs ├── How to create an instance in Docker Compose └── create_an_instance.md ├── index.js ├── package-lock.json ├── package.json ├── pnpm-lock.yaml ├── public ├── 404.ejs ├── HLS_NOT_SUPPORTED.mp4 ├── assets │ ├── images │ │ ├── pick-home.svg │ │ ├── pick-movies.svg │ │ ├── pick-popular.svg │ │ └── pick-show.svg │ └── search.svg ├── contribute.ejs ├── css │ ├── 404.css │ ├── 404.min.css │ ├── base.css │ ├── donate.css │ ├── genres.css │ ├── index-redesign.css │ ├── index.css │ ├── login.css │ ├── m.min.css │ ├── styles.css │ ├── styles.css.map │ ├── styles.scss │ ├── w2g.css │ ├── watch.css │ ├── watchlist.css │ ├── watchpage.css │ ├── youtube-theme.css │ └── youtube-w2g-theme.css ├── dmca.ejs ├── donate.ejs ├── error.ejs ├── favicon.ico ├── genre.ejs ├── genres.ejs ├── genres.json ├── img │ ├── 36629.jpg │ ├── HaiKeiBG.png │ ├── Untitled.pdn │ ├── haikei_ad.pdn │ ├── logo.png │ ├── logo.webp │ ├── logoCircle.png │ ├── logo_old.webp │ └── logo_small.png ├── index.ejs ├── index_old.ejs ├── index_superold.ejs ├── js │ ├── af │ │ ├── app.min.js │ │ ├── common.js │ │ ├── movie.js │ │ ├── random.js │ │ ├── search_list.js │ │ └── sources.js │ ├── app.js │ ├── donate.js │ ├── genres.js │ ├── index.js │ ├── jquery_debounce.js │ ├── kaomoji.js │ ├── login.js │ ├── main.js │ ├── modals.js │ ├── recentReleases.js │ ├── romajiEngHandler.js │ ├── shaka-player-ui.js │ ├── signup.js │ ├── splide__fn.js │ └── trending.js ├── login.ejs ├── robots.txt ├── search.ejs ├── signup.ejs ├── sitemap.xml ├── status.ejs ├── templates │ ├── analytics.ejs │ ├── footer.ejs │ ├── header.ejs │ ├── login_modals.ejs │ ├── navbar.ejs │ ├── new-footer.ejs │ ├── sidenav.ejs │ └── user-modals.ejs ├── test.ejs ├── trending-old.ejs ├── trending.ejs ├── vendor │ ├── splide │ │ └── css │ │ │ └── splide.css │ └── swiper.js │ │ └── swiper-bundle.css ├── w2g.ejs ├── w2g_room.ejs ├── watch.ejs ├── watch_artplayer.ejs ├── watch_bak.ejs ├── watch_old.ejs ├── watch_plyr.ejs ├── watch_zoro.ejs └── watchlist.ejs ├── routers ├── ajax.js ├── api.js ├── auth.js ├── genre │ ├── genre.js │ └── genres.js ├── index.js ├── releases.js ├── search.js ├── status │ └── status.js ├── trending.js ├── w2g.js ├── watch │ ├── watch.js │ └── watch_old.js └── watchlist.js ├── server.js ├── server_init.bat ├── utils ├── getSources.js ├── makeID.js └── tokenSender.js ├── webserver.bat ├── webserver.sh └── yarn.lock /.env.example: -------------------------------------------------------------------------------- 1 | WEBSITE_URL=http://localhost:3000 2 | PORT=3000 3 | AUTH_SECRET=tacocat 4 | JWT_SECRET=EXAMPLESECRET 5 | JWT_MSG=DONT SHARE THIS TOKEN! 6 | SMTP_PROVIDER= 7 | SMTP_PORT= 8 | MAIL_USERNAME= 9 | MAIL_PASSWORD= 10 | REDIS_URL=redis://localhost:6379 -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: wearrrrr 4 | custom: https://www.buymeacoffee.com/haikei 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | var 3 | db 4 | node_modules/ 5 | npm-debug.log* 6 | .DS_Store 7 | dump.rdb 8 | package-lock.json 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "compile-hero.disable-compile-files-on-did-save-code": false 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![logo](https://user-images.githubusercontent.com/99224452/210157888-0c2eded2-6e59-40a5-8f12-71c4a4dfd837.png)
2 | HaiKei is an anime streaming website that uses the [consumet](https://github.com/consumet/api.consumet.org) API, and runs ontop of express and ejs, with [redis](https://redis.io/) for caching. 3 | 4 | ## No more preview exists. 5 | 6 | # What is this good for? 7 | HaiKei is a streaming website that focuses on minimial loading times and an interface that is optimized for any device 🚀 8 | 9 | # Why don't I just use 9anime or another alternative? 10 | 11 | HaiKei runs faster than a good majority of alternatives, is more lightweight, and is completely open source! Unlike Zoro and 9anime. 12 | 13 | HaiKei is still very actively maintained and looking for contributors! If you know javascript, especially NodeJS and server side work, feel free to make a contribution! 14 | 15 | # Screenshots! 16 | ![HaiKei](https://user-images.githubusercontent.com/99224452/217099723-6d985dfc-aa0a-430f-aa44-6744e95f6fd8.png) 17 | ![hk_player](https://user-images.githubusercontent.com/99224452/210157881-c297bcd0-2806-43b2-b55b-e31da4187d77.png) 18 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | app: { 3 | port: 3000, 4 | api_url3: 'https://api.fl-anime.com/', 5 | }, 6 | }; 7 | 8 | module.exports = config; -------------------------------------------------------------------------------- /consumet-api/.dockerignore: -------------------------------------------------------------------------------- 1 | README.md 2 | Dockerfile 3 | .dockerignore 4 | .git 5 | .gitignore 6 | /.vscode 7 | .env 8 | .envrc 9 | node_modules -------------------------------------------------------------------------------- /consumet-api/.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | end_of_line = lf 4 | insert_final_newline = true 5 | indent_style = space 6 | indent_size = 2 7 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /consumet-api/.env.example: -------------------------------------------------------------------------------- 1 | PORT=Port number of the server. (optional) 2 | BILIBILI_COOKIE=Cookie for Bilibili (optional) 3 | PROXY=Proxies for requests (optional) (e.g. ["https://proxy1.com", "https://proxy2.com"]) 4 | REDIS_HOST=Redis host (optional) 5 | REDIS_PORT=Redis port (optional) 6 | REDIS_PASSWORD=Redis password (optional 7 | NINE_ANIME_HELPER_URL=9anime (optional) 8 | -------------------------------------------------------------------------------- /consumet-api/.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | name: 🐞 Bug report 2 | description: Create a report to help us improve 3 | labels: [bug] 4 | body: 5 | - type: input 6 | id: describe-the-bug 7 | attributes: 8 | label: Describe the bug 9 | description: | 10 | A clear and concise description of what the bug is. 11 | placeholder: | 12 | Example: "This provider is not working..." 13 | validations: 14 | required: true 15 | 16 | - type: textarea 17 | id: reproduce-steps 18 | attributes: 19 | label: Steps to reproduce 20 | description: Provide an example of the issue. 21 | placeholder: | 22 | Example: 23 | 1. First step 24 | 2. Second step 25 | 3. Issue here 26 | validations: 27 | required: true 28 | 29 | - type: textarea 30 | id: expected-behavior 31 | attributes: 32 | label: Expected behavior 33 | placeholder: | 34 | Example: 35 | "This should happen..." 36 | validations: 37 | required: true 38 | 39 | - type: textarea 40 | id: actual-behavior 41 | attributes: 42 | label: Actual behavior 43 | placeholder: | 44 | Example: 45 | "This happened instead..." 46 | validations: 47 | required: true 48 | 49 | - type: textarea 50 | id: additional-context 51 | attributes: 52 | label: Additional context 53 | description: | 54 | Add any other context about the problem here. 55 | placeholder: | 56 | Example: 57 | "Also ..." -------------------------------------------------------------------------------- /consumet-api/.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | contact_links: 2 | - name: 🍕 Provider Request 3 | url: https://github.com/consumet/extensions/issues/new?assignees=&labels=provider+request&template=provider-request.yml 4 | about: Request a new provider 5 | 6 | - name: 🙋 Question 7 | url: https://discord.gg/qTPfvMxzNH 8 | about: Ask your question or suggestion in Discord server 9 | 10 | - name: 🍔 Providers list 11 | url: https://consumet.org/extensions/list/ 12 | about: A list of the available providers 13 | -------------------------------------------------------------------------------- /consumet-api/.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: / 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 3 8 | allow: 9 | - dependency-type: 'production' 10 | ignore: 11 | - dependency-name: "axios" 12 | - package-ecosystem: github-actions 13 | directory: '/' 14 | schedule: 15 | interval: daily 16 | open-pull-requests-limit: 2 17 | -------------------------------------------------------------------------------- /consumet-api/.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "main" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "main" ] 20 | schedule: 21 | - cron: '16 10 * * 2' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'typescript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 -------------------------------------------------------------------------------- /consumet-api/.github/workflows/docker-build.yml: -------------------------------------------------------------------------------- 1 | name: Docker Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | jobs: 8 | docker: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Set up QEMU 12 | uses: docker/setup-qemu-action@v2 13 | - name: Set up Docker Buildx 14 | uses: docker/setup-buildx-action@v2 15 | - name: Login to DockerHub 16 | uses: docker/login-action@v2 17 | with: 18 | username: ${{ secrets.DOCKERHUB_USERNAME }} 19 | password: ${{ secrets.DOCKERHUB_TOKEN }} 20 | - name: Build and push 21 | uses: docker/build-push-action@v3 22 | with: 23 | push: true 24 | tags: ${{ secrets.DOCKERHUB_TAGS }} 25 | -------------------------------------------------------------------------------- /consumet-api/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | yarn-error.log 3 | .env 4 | src/scripts 5 | .vscode -------------------------------------------------------------------------------- /consumet-api/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.js 3 | *.md 4 | *.json 5 | *.lock 6 | *.yml 7 | *.yaml 8 | Dockerfile 9 | Dockerfile.* -------------------------------------------------------------------------------- /consumet-api/.prettierrc.yml: -------------------------------------------------------------------------------- 1 | parser: typescript 2 | tabWidth: 2 3 | singleQuote: true 4 | printWidth: 90 5 | bracketSpacing: true 6 | semi: true 7 | endOfLine: 'lf' 8 | -------------------------------------------------------------------------------- /consumet-api/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:19 as builder 2 | 3 | LABEL version="1.0.0" 4 | LABEL description="Consumet API (fastify) Docker Image" 5 | 6 | # update packages, to reduce risk of vulnerabilities 7 | RUN apt-get update && apt-get upgrade -y && apt-get autoclean -y && apt-get autoremove -y 8 | 9 | # set a non privileged user to use when running this image 10 | RUN groupadd -r nodejs && useradd -g nodejs -s /bin/bash -d /home/nodejs -m nodejs 11 | USER nodejs 12 | # set right (secure) folder permissions 13 | RUN mkdir -p /home/nodejs/app/node_modules && chown -R nodejs:nodejs /home/nodejs/app 14 | 15 | WORKDIR /home/nodejs/app 16 | 17 | # set default node env 18 | ARG NODE_ENV=development 19 | ARG PORT=3000 20 | # ARG NODE_ENV=production 21 | # to be able to run tests (for example in CI), do not set production as environment 22 | ENV NODE_ENV=${NODE_ENV} 23 | ENV PORT=${PORT} 24 | 25 | ENV NPM_CONFIG_LOGLEVEL=warn 26 | 27 | # copy project definition/dependencies files, for better reuse of layers 28 | COPY --chown=nodejs:nodejs package*.json ./ 29 | 30 | # install dependencies here, for better reuse of layers 31 | RUN npm install && npm update && npm cache clean --force 32 | 33 | # copy all sources in the container (exclusions in .dockerignore file) 34 | COPY --chown=nodejs:nodejs . . 35 | 36 | # build/pack binaries from sources 37 | 38 | # This results in a single layer image 39 | # FROM node:lts-alpine AS release 40 | # COPY --from=builder /dist /dist 41 | 42 | # exposed port/s 43 | EXPOSE 3000 44 | 45 | # add an healthcheck, useful 46 | # healthcheck with curl, but not recommended 47 | # HEALTHCHECK CMD curl --fail http://localhost:3000/health || exit 1 48 | # healthcheck by calling the additional script exposed by the plugin 49 | HEALTHCHECK --interval=30s --timeout=10s --start-period=5s CMD npm run healthcheck-manual 50 | 51 | # ENTRYPOINT [ "node" ] 52 | CMD [ "npm", "start" ] 53 | 54 | # end. -------------------------------------------------------------------------------- /consumet-api/Dockerfile.alpine: -------------------------------------------------------------------------------- 1 | FROM node:19.1-alpine as builder 2 | 3 | LABEL version="1.0.0" 4 | LABEL description="Consumet API (fastify) Docker Image" 5 | 6 | # update packages, to reduce risk of vulnerabilities 7 | RUN apk update && apk upgrade 8 | # RUN apk cache clean 9 | 10 | # set a non privileged user to use when running this image 11 | RUN addgroup -S nodejs && adduser -S nodejs -G nodejs 12 | USER nodejs 13 | # set right (secure) folder permissions 14 | RUN mkdir -p /home/nodejs/app/node_modules && chown -R nodejs:nodejs /home/nodejs/app 15 | 16 | WORKDIR /home/nodejs/app 17 | 18 | # set default node env 19 | # to be able to run tests (for example in CI), do not set production as environment 20 | ENV NODE_ENV=production 21 | ENV PORT=3000 22 | 23 | ENV NPM_CONFIG_LOGLEVEL=warn 24 | 25 | # copy project definition/dependencies files, for better reuse of layers 26 | COPY --chown=nodejs:nodejs package*.json ./ 27 | 28 | RUN apk add --no-cache git 29 | 30 | # install dependencies here, for better reuse of layers 31 | RUN npm install && npm update && npm cache clean --force 32 | 33 | # copy all sources in the container (exclusions in .dockerignore file) 34 | COPY --chown=nodejs:nodejs . . 35 | 36 | EXPOSE 3000 37 | 38 | # add an healthcheck, useful 39 | # healthcheck by calling the additional script exposed by the plugin 40 | HEALTHCHECK --interval=30s --timeout=10s --start-period=5s CMD npm run healthcheck-manual 41 | 42 | # ENTRYPOINT [ "npm" ] 43 | CMD [ "npm", "start" ] -------------------------------------------------------------------------------- /consumet-api/Dockerfile.distroless: -------------------------------------------------------------------------------- 1 | FROM node:19 as builder 2 | 3 | LABEL version="1.0.0" 4 | LABEL description="Consumet API (fastify) Docker Image" 5 | 6 | WORKDIR /app 7 | 8 | # set default node env 9 | ENV NODE_ENV=production 10 | ENV NPM_CONFIG_LOGLEVEL=warn 11 | ENV PORT=3000 12 | 13 | # copy project definition/dependencies files, for better reuse of layers 14 | COPY package*.json ./ 15 | 16 | # install dependencies here, for better reuse of layers 17 | RUN npm install && npm audit fix 18 | 19 | # copy all sources in the container (exclusions in .dockerignore file) 20 | COPY . . 21 | 22 | 23 | # release layer (the only one in the final image) 24 | FROM gcr.io/distroless/nodejs:18 AS release 25 | COPY --from=builder /app /app 26 | WORKDIR /app 27 | 28 | EXPOSE 3000 29 | 30 | CMD [ "./src/main" ] -------------------------------------------------------------------------------- /consumet-api/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Consumet API", 3 | "description": "A Modern Search Engine API for Anime, Movies, Books, etc.", 4 | "website": "https://consumet.org", 5 | "repository": "https://github.com/consumet/consumet-api", 6 | "logo": "https://consumet.org/images/consumetlogo.png", 7 | "keywords": ["node", "fastify", "mongodb", "anime", "movies", "books", "manga", "api"], 8 | "env": { 9 | "PORT": { 10 | "description": "Set PORT for the fastify application to run on.", 11 | "value": "3000" 12 | }, 13 | "REDIS_HOST": { 14 | "description": "Set redis host, you can create a redis instance on your own or use upstash (https://upstash.com) its for free.", 15 | "value": "" 16 | }, 17 | "REDIS_PASS": { 18 | "description": "Set redis password, you can create a redis instance on your own or use upstash (https://upstash.com) its for free.", 19 | "value": "" 20 | }, 21 | "REDIS_PORT": { 22 | "description": "Set redis port, you can create a redis instance on your own or use upstash (https://upstash.com) its for free.", 23 | "value": "" 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /consumet-api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api.consumet.org", 3 | "version": "1.0.0", 4 | "main": "src/main.ts", 5 | "scripts": { 6 | "start": "ts-node src/main.ts", 7 | "dev": "nodemon src/main.ts", 8 | "lint": "prettier --write ." 9 | }, 10 | "engines": { 11 | "node": ">=12.5.0", 12 | "yarn": ">=1.17.3" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/consumet/consumet.ts.git" 17 | }, 18 | "author": "prince-ao ", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/consumet/consumet.ts/issues" 22 | }, 23 | "homepage": "https://github.com/consumet/consumet.ts#readme", 24 | "dependencies": { 25 | "@consumet/extensions": "https://github.com/consumet/consumet.ts.git", 26 | "@fastify/cors": "^8.2.0", 27 | "@types/fastify-cors": "^2.1.0", 28 | "@types/node": "^18.11.17", 29 | "@types/ws": "^8.5.3", 30 | "axios": "^0.27.2", 31 | "chalk": "4.1.2", 32 | "cheerio": "1.0.0-rc.12", 33 | "dotenv": "^16.0.3", 34 | "fastify": "^4.10.2", 35 | "fastify-cors": "^6.1.0", 36 | "ioredis": "^5.2.4", 37 | "reconnecting-websocket": "^4.4.0", 38 | "ts-node": "^10.9.1", 39 | "typescript": "5.0.2", 40 | "ws": "^8.8.1" 41 | }, 42 | "devDependencies": { 43 | "nodemon": "2.0.20", 44 | "prettier": "^2.8.1" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /consumet-api/render.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | - type: web 3 | name: consumet-api # Name of the service 4 | env: docker 5 | repo: https://github.com/consumet/api.consumet.org.git 6 | plan: free 7 | branch: main 8 | envVars: 9 | - key: PORT 10 | value: 3000 -------------------------------------------------------------------------------- /consumet-api/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ], 6 | "lockFileMaintenance": { 7 | "enabled": true, 8 | "automerge": true, 9 | "automergeType": "pr", 10 | "platformAutomerge": true 11 | }, 12 | "packageRules": [{ 13 | "matchDatasources": [ 14 | "npm", "yarn" 15 | ], 16 | "matchDepTypes": ["dependencies"], 17 | "matchPackagePatterns": ["*"], 18 | "automerge": false, 19 | "schedule": "before 10pm" 20 | }] 21 | } -------------------------------------------------------------------------------- /consumet-api/src/main.ts: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | import Redis from 'ioredis'; 3 | import Fastify from 'fastify'; 4 | import FastifyCors from '@fastify/cors'; 5 | import books from './routes/books'; 6 | import anime from './routes/anime'; 7 | import manga from './routes/manga'; 8 | import comics from './routes/comics'; 9 | import lightnovels from './routes/light-novels'; 10 | import movies from './routes/movies'; 11 | import meta from './routes/meta'; 12 | import news from './routes/news'; 13 | import chalk from 'chalk'; 14 | import Utils from './utils'; 15 | 16 | export const redis = 17 | process.env.REDIS_HOST && 18 | new Redis({ 19 | host: process.env.REDIS_HOST, 20 | port: Number(process.env.REDIS_PORT), 21 | username: process.env.REDIS_USERNAME, 22 | }); 23 | 24 | export const tmdbApi = process.env.apiKey && process.env.apiKey; 25 | (async () => { 26 | const PORT = Number(process.env.PORT) || 3000; 27 | 28 | console.log(chalk.green(`Starting server on port ${PORT}... 🚀`)); 29 | if (!process.env.REDIS_HOST) 30 | console.warn(chalk.yellowBright('Redis not found. Cache disabled.')); 31 | if (!process.env.tmdbApi) 32 | console.warn( 33 | chalk.yellowBright('TMDB api key not found. the TMDB meta route may not work.') 34 | ); 35 | 36 | const fastify = Fastify({ 37 | maxParamLength: 1000, 38 | logger: true, 39 | }); 40 | await fastify.register(FastifyCors, { 41 | origin: '*', 42 | methods: 'GET', 43 | }); 44 | 45 | await fastify.register(books, { prefix: '/books' }); 46 | await fastify.register(anime, { prefix: '/anime' }); 47 | await fastify.register(manga, { prefix: '/manga' }); 48 | //await fastify.register(comics, { prefix: '/comics' }); 49 | await fastify.register(lightnovels, { prefix: '/light-novels' }); 50 | await fastify.register(movies, { prefix: '/movies' }); 51 | await fastify.register(meta, { prefix: '/meta' }); 52 | await fastify.register(news, { prefix: '/news' }); 53 | 54 | await fastify.register(Utils, { prefix: '/utils' }); 55 | 56 | try { 57 | fastify.get('/', (_, rp) => { 58 | rp.status(200).send('Welcome to consumet api! 🎉'); 59 | }); 60 | fastify.get('*', (request, reply) => { 61 | reply.status(404).send({ 62 | message: '', 63 | error: 'page not found', 64 | }); 65 | }); 66 | 67 | fastify.listen({ port: PORT, host: '0.0.0.0' }, (e, address) => { 68 | if (e) throw e; 69 | console.log(`server listening on ${address}`); 70 | }); 71 | } catch (err: any) { 72 | fastify.log.error(err); 73 | process.exit(1); 74 | } 75 | })(); 76 | -------------------------------------------------------------------------------- /consumet-api/src/models/index.ts: -------------------------------------------------------------------------------- 1 | import { IBookProviderParams, LibgenBook } from './types'; 2 | 3 | export { IBookProviderParams, LibgenBook }; 4 | -------------------------------------------------------------------------------- /consumet-api/src/models/types.ts: -------------------------------------------------------------------------------- 1 | export interface IBookProviderParams { 2 | bookProvider: string; 3 | page?: number; 4 | } 5 | 6 | export interface LibgenBook { 7 | id: string; 8 | title: string; 9 | tempAuthor: string; 10 | author: string[]; 11 | publisher: string; 12 | year: string; 13 | language: string; 14 | format: string; 15 | size: string; 16 | pages: string; 17 | link: string; 18 | image: string; 19 | description: string; 20 | tableOfContents: string; 21 | edition: string; 22 | volume: string; 23 | topic: string; 24 | series: string; 25 | isbn: string[]; 26 | hashes: Hashes; 27 | } 28 | 29 | interface Hashes { 30 | AICH: string; 31 | CRC32: string; 32 | eDonkey: string; 33 | MD5: string; 34 | SHA1: string; 35 | SHA256: string[]; 36 | TTH: string; 37 | } 38 | -------------------------------------------------------------------------------- /consumet-api/src/routes/anime/animefox.ts: -------------------------------------------------------------------------------- 1 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 2 | import { ANIME } from '@consumet/extensions'; 3 | 4 | const routes = async (fastify: FastifyInstance, options: RegisterOptions) => { 5 | const animefox = new ANIME.AnimeFox(); 6 | 7 | fastify.get('/', (_, rp) => { 8 | rp.status(200).send({ 9 | intro: 10 | "Welcome to the animefox provider: check out the provider's website @ https://animefox.tv/", 11 | routes: ['/:query', '/info/:id', '/watch/:episodeId'], 12 | documentation: 'https://docs.consumet.org/#tag/animefox', 13 | }); 14 | }); 15 | 16 | fastify.get( 17 | '/recent-episodes', 18 | async (request: FastifyRequest, reply: FastifyReply) => { 19 | const page = (request.params as { page: number }).page; 20 | 21 | const res = await animefox.fetchRecentEpisodes(page); 22 | 23 | reply.status(200).send(res); 24 | } 25 | ); 26 | 27 | fastify.get('/:query', async (request: FastifyRequest, reply: FastifyReply) => { 28 | const query = (request.params as { query: string }).query; 29 | 30 | const res = await animefox.search(query); 31 | 32 | reply.status(200).send(res); 33 | }); 34 | 35 | fastify.get('/info', async (request: FastifyRequest, reply: FastifyReply) => { 36 | const id = (request.query as { id: string }).id; 37 | 38 | if (typeof id === 'undefined') 39 | return reply.status(400).send({ message: 'id is required' }); 40 | 41 | try { 42 | const res = await animefox 43 | .fetchAnimeInfo(id) 44 | .catch((err) => reply.status(404).send({ message: err })); 45 | 46 | reply.status(200).send(res); 47 | } catch (err) { 48 | reply 49 | .status(500) 50 | .send({ message: 'Something went wrong. Contact developer for help.' }); 51 | } 52 | }); 53 | 54 | fastify.get('/watch', async (request: FastifyRequest, reply: FastifyReply) => { 55 | const episodeId = (request.query as { episodeId: string }).episodeId; 56 | 57 | if (typeof episodeId === 'undefined') 58 | return reply.status(400).send({ message: 'episodeId is required' }); 59 | 60 | try { 61 | const res = await animefox 62 | .fetchEpisodeSources(episodeId) 63 | .catch((err) => reply.status(404).send({ message: err })); 64 | 65 | reply.status(200).send(res); 66 | } catch (err) { 67 | reply 68 | .status(500) 69 | .send({ message: 'Something went wrong. Contact developer for help.' }); 70 | } 71 | }); 72 | }; 73 | 74 | export default routes; 75 | -------------------------------------------------------------------------------- /consumet-api/src/routes/anime/animepahe.ts: -------------------------------------------------------------------------------- 1 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 2 | import { ANIME } from '@consumet/extensions'; 3 | 4 | const routes = async (fastify: FastifyInstance, options: RegisterOptions) => { 5 | const animepahe = new ANIME.AnimePahe(); 6 | 7 | fastify.get('/', (_, rp) => { 8 | rp.status(200).send({ 9 | intro: 10 | "Welcome to the animepahe provider: check out the provider's website @ https://animepahe.com/", 11 | routes: ['/:query', '/info/:id', '/watch/:episodeId'], 12 | documentation: 'https://docs.consumet.org/#tag/animepahe', 13 | }); 14 | }); 15 | 16 | fastify.get('/:query', async (request: FastifyRequest, reply: FastifyReply) => { 17 | const query = (request.params as { query: string }).query; 18 | 19 | const res = await animepahe.search(query); 20 | 21 | reply.status(200).send(res); 22 | }); 23 | 24 | fastify.get('/info/:id', async (request: FastifyRequest, reply: FastifyReply) => { 25 | const id = decodeURIComponent((request.params as { id: string }).id); 26 | 27 | const episodePage = (request.query as { episodePage: number }).episodePage; 28 | 29 | try { 30 | const res = await animepahe 31 | .fetchAnimeInfo(id, episodePage) 32 | .catch((err) => reply.status(404).send({ message: err })); 33 | 34 | reply.status(200).send(res); 35 | } catch (err) { 36 | reply 37 | .status(500) 38 | .send({ message: 'Something went wrong. Contact developer for help.' }); 39 | } 40 | }); 41 | 42 | fastify.get( 43 | '/watch/:episodeId', 44 | async (request: FastifyRequest, reply: FastifyReply) => { 45 | const episodeId = (request.params as { episodeId: string }).episodeId; 46 | 47 | try { 48 | const res = await animepahe.fetchEpisodeSources(episodeId); 49 | 50 | reply.status(200).send(res); 51 | } catch (err) { 52 | console.log(err); 53 | reply 54 | .status(500) 55 | .send({ message: 'Something went wrong. Contact developer for help.' }); 56 | } 57 | } 58 | ); 59 | }; 60 | 61 | export default routes; 62 | -------------------------------------------------------------------------------- /consumet-api/src/routes/anime/bilibili.ts: -------------------------------------------------------------------------------- 1 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 2 | import { ANIME } from '@consumet/extensions'; 3 | 4 | const routes = async (fastify: FastifyInstance, options: RegisterOptions) => { 5 | if (!process.env.BILIBILI_COOKIE) return; 6 | const bilibili = new ANIME.Bilibili(process.env.BILIBILI_COOKIE); 7 | 8 | fastify.get('/', (_, rp) => { 9 | rp.status(200).send({ 10 | intro: 11 | "Welcome to the bilibili provider: check out the provider's website @ https://bilibili.to/", 12 | routes: ['/:query', '/info/:id', '/watch/:episodeId'], 13 | documentation: 'https://docs.consumet.org/#tag/bilibili', 14 | }); 15 | }); 16 | 17 | fastify.get('/:query', async (request: FastifyRequest, reply: FastifyReply) => { 18 | const query = (request.params as { query: string }).query; 19 | 20 | const res = await bilibili.search(query); 21 | 22 | reply.status(200).send(res); 23 | }); 24 | 25 | fastify.get('/info', async (request: FastifyRequest, reply: FastifyReply) => { 26 | const id = (request.query as { id: string }).id; 27 | 28 | if (typeof id === 'undefined') 29 | return reply.status(400).send({ message: 'id is required' }); 30 | 31 | try { 32 | const res = await bilibili 33 | .fetchAnimeInfo(id) 34 | .catch((err) => reply.status(404).send({ message: err })); 35 | 36 | reply.status(200).send(res); 37 | } catch (err) { 38 | reply 39 | .status(500) 40 | .send({ message: 'Something went wrong. Contact developer for help.' }); 41 | } 42 | }); 43 | 44 | fastify.get('/watch', async (request: FastifyRequest, reply: FastifyReply) => { 45 | const episodeId = (request.query as { episodeId: string }).episodeId; 46 | 47 | if (typeof episodeId === 'undefined') 48 | return reply.status(400).send({ message: 'episodeId is required' }); 49 | 50 | try { 51 | const res = await bilibili 52 | .fetchEpisodeSources(episodeId) 53 | .catch((err) => reply.status(404).send({ message: err })); 54 | 55 | reply.status(200).send(res); 56 | } catch (err) { 57 | reply 58 | .status(500) 59 | .send({ message: 'Something went wrong. Contact developer for help.' }); 60 | } 61 | }); 62 | }; 63 | 64 | export default routes; 65 | -------------------------------------------------------------------------------- /consumet-api/src/routes/anime/crunchyroll.ts: -------------------------------------------------------------------------------- 1 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 2 | import { ANIME } from '@consumet/extensions'; 3 | 4 | const routes = async (fastify: FastifyInstance, options: RegisterOptions) => { 5 | fastify.get('/', (_, rp) => { 6 | rp.status(200).send({ 7 | intro: 'Welcome to the Crunchyroll provider.', 8 | routes: ['/:query', '/info/:id:mediaType', '/watch/:episodeId'], 9 | documentation: 'https://docs.consumet.org/#tag/crunchyroll', 10 | }); 11 | }); 12 | 13 | fastify.get('/:query', async (request: FastifyRequest, reply: FastifyReply) => { 14 | const query = (request.params as { query: string }).query; 15 | 16 | const crunchyroll = await ANIME.Crunchyroll.create(); 17 | const res = await crunchyroll.search(query); 18 | 19 | reply.status(200).send(res); 20 | }); 21 | 22 | fastify.get('/info', async (request: FastifyRequest, reply: FastifyReply) => { 23 | const id = (request.query as { id: string }).id; 24 | const mediaType = (request.query as { mediaType: string }).mediaType; 25 | const allSeasons = (request.query as { allSeasons: boolean }).allSeasons ?? false; 26 | 27 | const crunchyroll = await ANIME.Crunchyroll.create(); 28 | 29 | if (typeof id === 'undefined') 30 | return reply.status(400).send({ message: 'id is required' }); 31 | 32 | if (typeof mediaType === 'undefined') 33 | return reply.status(400).send({ message: 'mediaType is required' }); 34 | 35 | try { 36 | const res = await crunchyroll 37 | .fetchAnimeInfo(id, mediaType, allSeasons) 38 | .catch((err) => reply.status(404).send({ message: err })); 39 | 40 | reply.status(200).send(res); 41 | } catch (err) { 42 | reply 43 | .status(500) 44 | .send({ message: 'Something went wrong. Contact developer for help.' }); 45 | } 46 | }); 47 | 48 | fastify.get('/watch', async (request: FastifyRequest, reply: FastifyReply) => { 49 | const episodeId = (request.query as { episodeId: string }).episodeId; 50 | 51 | if (typeof episodeId === 'undefined') 52 | return reply.status(400).send({ message: 'episodeId is required' }); 53 | 54 | const crunchyroll = await ANIME.Crunchyroll.create(); 55 | 56 | try { 57 | const res = await crunchyroll 58 | .fetchEpisodeSources(episodeId) 59 | .catch((err) => reply.status(404).send({ message: err })); 60 | 61 | reply.status(200).send(res); 62 | } catch (err) { 63 | reply 64 | .status(500) 65 | .send({ message: 'Something went wrong. Contact developer for help.' }); 66 | } 67 | }); 68 | }; 69 | 70 | export default routes; 71 | -------------------------------------------------------------------------------- /consumet-api/src/routes/anime/enime.ts: -------------------------------------------------------------------------------- 1 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 2 | import { ANIME } from '@consumet/extensions'; 3 | 4 | const routes = async (fastify: FastifyInstance, options: RegisterOptions) => { 5 | const enime = new ANIME.Enime(); 6 | 7 | fastify.get('/', (_, rp) => { 8 | rp.status(200).send({ 9 | intro: 10 | "Welcome to the enime provider: check out the provider's website @ https://enime.com/", 11 | routes: ['/:query', '/info/:id', '/watch/:episodeId'], 12 | documentation: 'https://docs.consumet.org/#tag/enime', 13 | }); 14 | }); 15 | 16 | fastify.get('/:query', async (request: FastifyRequest, reply: FastifyReply) => { 17 | const query = (request.params as { query: string }).query; 18 | 19 | const res = await enime.search(query); 20 | 21 | reply.status(200).send(res); 22 | }); 23 | 24 | fastify.get('/info', async (request: FastifyRequest, reply: FastifyReply) => { 25 | const id = (request.query as { id: string }).id; 26 | 27 | if (typeof id === 'undefined') 28 | return reply.status(400).send({ message: 'id is required' }); 29 | 30 | try { 31 | const res = await enime 32 | .fetchAnimeInfo(id) 33 | .catch((err) => reply.status(404).send({ message: err })); 34 | 35 | reply.status(200).send(res); 36 | } catch (err) { 37 | reply 38 | .status(500) 39 | .send({ message: 'Something went wrong. Contact developer for help.' }); 40 | } 41 | }); 42 | 43 | fastify.get('/watch', async (request: FastifyRequest, reply: FastifyReply) => { 44 | const episodeId = (request.query as { episodeId: string }).episodeId; 45 | 46 | if (typeof episodeId === 'undefined') 47 | return reply.status(400).send({ message: 'episodeId is required' }); 48 | 49 | try { 50 | const res = await enime 51 | .fetchEpisodeSources(episodeId) 52 | .catch((err) => reply.status(404).send({ message: err })); 53 | 54 | reply.status(200).send(res); 55 | } catch (err) { 56 | reply 57 | .status(500) 58 | .send({ message: 'Something went wrong. Contact developer for help.' }); 59 | } 60 | }); 61 | }; 62 | 63 | export default routes; 64 | -------------------------------------------------------------------------------- /consumet-api/src/routes/anime/index.ts: -------------------------------------------------------------------------------- 1 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 2 | import { PROVIDERS_LIST } from '@consumet/extensions'; 3 | 4 | import gogoanime from './gogoanime'; 5 | import animepahe from './animepahe'; 6 | import zoro from './zoro'; 7 | import nineanime from './9anime'; 8 | import animefox from './animefox'; 9 | import enime from './enime'; 10 | import crunchyroll from './crunchyroll'; 11 | import bilibili from './bilibili'; 12 | import marin from './marin'; 13 | 14 | const routes = async (fastify: FastifyInstance, options: RegisterOptions) => { 15 | await fastify.register(gogoanime, { prefix: '/gogoanime' }); 16 | await fastify.register(animepahe, { prefix: '/animepahe' }); 17 | await fastify.register(zoro, { prefix: '/zoro' }); 18 | await fastify.register(nineanime, { prefix: '/9anime' }); 19 | await fastify.register(animefox, { prefix: '/animefox' }); 20 | await fastify.register(enime, { prefix: '/enime' }); 21 | await fastify.register(crunchyroll, { prefix: '/crunchyroll' }); 22 | await fastify.register(bilibili, { prefix: '/bilibili' }); 23 | await fastify.register(marin, { prefix: '/marin' }); 24 | 25 | fastify.get('/', async (request: any, reply: any) => { 26 | reply.status(200).send('Welcome to Consumet Anime 🗾'); 27 | }); 28 | 29 | fastify.get('/:animeProvider', async (request: FastifyRequest, reply: FastifyReply) => { 30 | const queries: { animeProvider: string; page: number } = { 31 | animeProvider: '', 32 | page: 1, 33 | }; 34 | 35 | queries.animeProvider = decodeURIComponent( 36 | (request.params as { animeProvider: string; page: number }).animeProvider 37 | ); 38 | 39 | queries.page = (request.query as { animeProvider: string; page: number }).page; 40 | 41 | if (queries.page! < 1) queries.page = 1; 42 | 43 | const provider = PROVIDERS_LIST.ANIME.find( 44 | (provider: any) => provider.toString.name === queries.animeProvider 45 | ); 46 | 47 | try { 48 | if (provider) { 49 | reply.redirect(`/anime/${provider.toString.name}`); 50 | } else { 51 | reply 52 | .status(404) 53 | .send({ message: 'Provider not found, please check the providers list.' }); 54 | } 55 | } catch (err) { 56 | reply.status(500).send('Something went wrong. Please try again later.'); 57 | } 58 | }); 59 | }; 60 | 61 | export default routes; 62 | -------------------------------------------------------------------------------- /consumet-api/src/routes/anime/marin.ts: -------------------------------------------------------------------------------- 1 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 2 | import { ANIME } from '@consumet/extensions'; 3 | 4 | const routes = async (fastify: FastifyInstance, options: RegisterOptions) => { 5 | const marin = new ANIME.Marin(); 6 | 7 | fastify.get('/', (_, rp) => { 8 | rp.status(200).send({ 9 | intro: 10 | "Welcome to the animefox provider: check out the provider's website @ https://marin,moe", 11 | routes: ['/:query', '/info/:id', '/watch/:id/:number'], 12 | documentation: 'https://docs.consumet.org/#tag/marin', 13 | }); 14 | }); 15 | 16 | fastify.get( 17 | '/recent-episodes', 18 | async (request: FastifyRequest, reply: FastifyReply) => { 19 | const page = (request.query as { page: number }).page; 20 | reply.status(200).send(await marin.recentEpisodes(page)); 21 | } 22 | ); 23 | 24 | fastify.get('/:query', async (request: FastifyRequest, reply: FastifyReply) => { 25 | const query = (request.params as { query: string }).query; 26 | 27 | const res = await marin.search(query); 28 | 29 | reply.status(200).send(res); 30 | }); 31 | 32 | fastify.get('/info/:id', async (request: FastifyRequest, reply: FastifyReply) => { 33 | const id = (request.params as { id: string }).id; 34 | 35 | if (typeof id === 'undefined') 36 | return reply.status(400).send({ message: 'id is required' }); 37 | 38 | try { 39 | const res = await marin 40 | .fetchAnimeInfo(id) 41 | .catch((err) => reply.status(404).send({ message: err })); 42 | 43 | reply.status(200).send(res); 44 | } catch (err) { 45 | reply 46 | .status(500) 47 | .send({ message: 'Something went wrong. Contact developer for help.' }); 48 | } 49 | }); 50 | 51 | fastify.get( 52 | '/watch/:id/:number', 53 | async (request: FastifyRequest, reply: FastifyReply) => { 54 | const id = (request.params as { id: string }).id; 55 | const number = (request.params as { number: number }).number; 56 | 57 | if (typeof id === 'undefined') 58 | return reply.status(400).send({ message: 'id is required' }); 59 | 60 | if (typeof number === 'undefined') 61 | return reply.status(400).send({ message: 'number is required' }); 62 | 63 | try { 64 | const res = await marin 65 | .fetchEpisodeSources(`${id}/${number}`) 66 | .catch((err) => reply.status(404).send({ message: err })); 67 | 68 | reply.status(200).send(res); 69 | } catch (err) { 70 | reply 71 | .status(500) 72 | .send({ message: 'Something went wrong. Contact developer for help.' }); 73 | } 74 | } 75 | ); 76 | }; 77 | 78 | export default routes; 79 | -------------------------------------------------------------------------------- /consumet-api/src/routes/anime/zoro.ts: -------------------------------------------------------------------------------- 1 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 2 | import { ANIME } from '@consumet/extensions'; 3 | import { StreamingServers } from '@consumet/extensions/dist/models'; 4 | 5 | const routes = async (fastify: FastifyInstance, options: RegisterOptions) => { 6 | const zoro = new ANIME.Zoro(); 7 | 8 | fastify.get('/', (_, rp) => { 9 | rp.status(200).send({ 10 | intro: 11 | "Welcome to the zoro provider: check out the provider's website @ https://zoro.to/", 12 | routes: ['/:query', '/info/:id', '/watch/:episodeId'], 13 | documentation: 'https://docs.consumet.org/#tag/zoro', 14 | }); 15 | }); 16 | 17 | fastify.get('/:query', async (request: FastifyRequest, reply: FastifyReply) => { 18 | const query = (request.params as { query: string }).query; 19 | 20 | const page = (request.query as { page: number }).page; 21 | 22 | const res = await zoro.search(query, page); 23 | 24 | reply.status(200).send(res); 25 | }); 26 | 27 | fastify.get( 28 | '/recent-episodes', 29 | async (request: FastifyRequest, reply: FastifyReply) => { 30 | const page = (request.query as { page: number }).page; 31 | 32 | const res = await zoro.fetchRecentEpisodes(page); 33 | 34 | reply.status(200).send(res); 35 | } 36 | ); 37 | 38 | fastify.get('/info', async (request: FastifyRequest, reply: FastifyReply) => { 39 | const id = (request.query as { id: string }).id; 40 | 41 | if (typeof id === 'undefined') 42 | return reply.status(400).send({ message: 'id is required' }); 43 | 44 | try { 45 | const res = await zoro 46 | .fetchAnimeInfo(id) 47 | .catch((err) => reply.status(404).send({ message: err })); 48 | 49 | return reply.status(200).send(res); 50 | } catch (err) { 51 | reply 52 | .status(500) 53 | .send({ message: 'Something went wrong. Contact developer for help.' }); 54 | } 55 | }); 56 | 57 | fastify.get('/watch', async (request: FastifyRequest, reply: FastifyReply) => { 58 | const episodeId = (request.query as { episodeId: string }).episodeId; 59 | 60 | const server = (request.query as { server: string }).server as StreamingServers; 61 | 62 | if (server && !Object.values(StreamingServers).includes(server)) 63 | return reply.status(400).send({ message: 'server is invalid' }); 64 | 65 | if (typeof episodeId === 'undefined') 66 | return reply.status(400).send({ message: 'id is required' }); 67 | 68 | try { 69 | const res = await zoro 70 | .fetchEpisodeSources(episodeId, server) 71 | .catch((err) => reply.status(404).send({ message: err })); 72 | 73 | reply.status(200).send(res); 74 | } catch (err) { 75 | reply 76 | .status(500) 77 | .send({ message: 'Something went wrong. Contact developer for help.' }); 78 | } 79 | }); 80 | }; 81 | 82 | export default routes; 83 | -------------------------------------------------------------------------------- /consumet-api/src/routes/books/index.ts: -------------------------------------------------------------------------------- 1 | import { BOOKS } from '@consumet/extensions'; 2 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 3 | 4 | import libgen from './libgen'; 5 | const routes = async (fastify: FastifyInstance, options: RegisterOptions) => { 6 | const lbgen = new BOOKS.Libgen(); 7 | 8 | fastify.get('/', async (request: any, reply: any) => { 9 | reply.status(200).send('Welcome to Consumet Books 📚'); 10 | }); 11 | 12 | fastify.get('/s', async (request: FastifyRequest, reply: FastifyReply) => { 13 | const { bookTitle, page } = request.query as { 14 | bookTitle: string; 15 | page: number; 16 | }; 17 | if (!bookTitle) 18 | return reply.status(400).send({ 19 | message: 'bookTitle query needed', 20 | error: 'invalid_input', 21 | }); 22 | try { 23 | const data = await lbgen.search(bookTitle, page); 24 | return reply.status(200).send(data); 25 | } catch (e) { 26 | return reply.status(500).send({ 27 | message: e, 28 | error: 'internal_error', 29 | }); 30 | } 31 | }); 32 | 33 | await fastify.register(libgen, { prefix: '/libgen' }); 34 | }; 35 | 36 | export default routes; 37 | -------------------------------------------------------------------------------- /consumet-api/src/routes/books/libgen.ts: -------------------------------------------------------------------------------- 1 | import { BOOKS } from '@consumet/extensions'; 2 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 3 | 4 | const routes = async (fastify: FastifyInstance, options: RegisterOptions) => { 5 | const libgen = new BOOKS.Libgen(); 6 | 7 | fastify.get('/', (_, rp) => { 8 | rp.status(200).send({ 9 | intro: 10 | "Welcome to the libgen provider. check out the provider's website @ http://libgen.rs/", 11 | routes: ['/s', '/fs'], 12 | documentation: 'https://docs.consumet.org/#tag/libgen (needs to be updated)', 13 | }); 14 | }); 15 | 16 | fastify.get('/s', async (request: FastifyRequest, reply: FastifyReply) => { 17 | const { bookTitle, page } = request.query as { 18 | bookTitle: string; 19 | page: number; 20 | }; 21 | if (bookTitle.length < 4) 22 | return reply.status(400).send({ 23 | message: 'length of bookTitle must be > 4 characters', 24 | error: 'short_length', 25 | }); 26 | if (isNaN(page)) { 27 | return reply.status(400).send({ 28 | message: 'page is missing', 29 | error: 'invalid_input', 30 | }); 31 | } 32 | try { 33 | const data = await libgen.search(bookTitle, page); 34 | return reply.status(200).send(data); 35 | } catch (e) { 36 | return reply.status(400).send(e); 37 | } 38 | }); 39 | }; 40 | 41 | export default routes; 42 | -------------------------------------------------------------------------------- /consumet-api/src/routes/comics/getComics.ts: -------------------------------------------------------------------------------- 1 | import { COMICS } from '@consumet/extensions'; 2 | import { RedisFunctionFlags } from '@redis/client/dist/lib/commands/generic-transformers'; 3 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 4 | import { createClient } from 'redis'; 5 | 6 | const client = createClient({ 7 | url: `redis://default:${process.env.REDIS_PASS}@${process.env.REDIS_HOST}:${process.env.REDIS_PORT}`, 8 | }); 9 | 10 | client.on('error', (err) => console.log('Redis Client Error', err)); 11 | 12 | const routes = async (fastify: FastifyInstance, options: RegisterOptions) => { 13 | // fastify.log.info( 14 | // `redis://${process.env.REDIS_PASS}@${process.env.REDIS_HOST}:${process.env.REDIS_PORT}` 15 | // ); 16 | fastify.get('/s', async (request: FastifyRequest, reply: FastifyReply) => { 17 | let { comicTitle, page } = request.query as { 18 | comicTitle: string; 19 | page: number | undefined; 20 | }; 21 | await client.connect(); 22 | if (page == undefined) page = 1; 23 | if (await client.exists(`${comicTitle}:${page}`)) { 24 | const result = await client.get(`${comicTitle}:${page}`); 25 | client.disconnect(); 26 | 27 | const resultParsed = JSON.parse(result!!); 28 | return reply.status(200).send(resultParsed); 29 | } 30 | if (comicTitle.length < 4) 31 | return reply.status(400).send({ 32 | message: 'length of comicTitle must be > 4 charactes', 33 | error: 'short_length', 34 | }); 35 | const getComics = new COMICS.GetComics(); 36 | const result = await getComics 37 | .search(comicTitle, page == undefined ? 1 : page) 38 | .catch((err) => { 39 | return reply.status(400).send({ 40 | // temp 41 | message: 'page query must be defined', 42 | error: 'invalid_input', 43 | // temp 44 | }); 45 | }); 46 | 47 | client.set(`${comicTitle}:${page}`, JSON.stringify(result)); 48 | 49 | return reply.status(200).send(result); 50 | }); 51 | 52 | fastify.get('/', (_, rp) => { 53 | rp.status(200).send({ 54 | intro: 55 | "Welcome to the getComics provider: check out the provider's website @ https://getcomics.info/", 56 | routes: ['/s'], 57 | documentation: 'https://docs.consumet.org/#tag/getComics (need to be updated)', 58 | }); 59 | }); 60 | }; 61 | 62 | export default routes; 63 | -------------------------------------------------------------------------------- /consumet-api/src/routes/comics/index.ts: -------------------------------------------------------------------------------- 1 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 2 | import getComics from './getComics'; 3 | 4 | const routes = async (fastify: FastifyInstance, options: RegisterOptions) => { 5 | await fastify.register(getComics, { prefix: '/getComics' }); 6 | 7 | fastify.get('/', async (request: FastifyRequest, reply: FastifyReply) => { 8 | reply.status(200).send('Welcome to Consumet Comics 🦸‍♂️'); 9 | }); 10 | 11 | fastify.get('/s', async (request: FastifyRequest, reply: FastifyReply) => { 12 | const { comicTitle, page } = request.query as { comicTitle: string; page: number }; 13 | reply.status(300).redirect(`getComics/s?comicTitle=${comicTitle}&page=${page}`); 14 | }); 15 | }; 16 | 17 | export default routes; 18 | -------------------------------------------------------------------------------- /consumet-api/src/routes/light-novels/index.ts: -------------------------------------------------------------------------------- 1 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 2 | import { PROVIDERS_LIST } from '@consumet/extensions'; 3 | 4 | import readlightnovels from './readlightnovels'; 5 | 6 | const routes = async (fastify: FastifyInstance, options: RegisterOptions) => { 7 | await fastify.register(readlightnovels, { prefix: '/readlightnovels' }); 8 | 9 | fastify.get('/', async (request: any, reply: any) => { 10 | reply.status(200).send('Welcome to Consumet Light Novels'); 11 | }); 12 | 13 | fastify.get( 14 | '/:lightNovelProvider', 15 | async (request: FastifyRequest, reply: FastifyReply) => { 16 | const queries: { lightNovelProvider: string; page: number } = { 17 | lightNovelProvider: '', 18 | page: 1, 19 | }; 20 | 21 | queries.lightNovelProvider = decodeURIComponent( 22 | (request.params as { lightNovelProvider: string; page: number }) 23 | .lightNovelProvider 24 | ); 25 | 26 | queries.page = (request.query as { lightNovelProvider: string; page: number }).page; 27 | 28 | if (queries.page! < 1) queries.page = 1; 29 | 30 | const provider = PROVIDERS_LIST.LIGHT_NOVELS.find( 31 | (provider: any) => provider.toString.name === queries.lightNovelProvider 32 | ); 33 | 34 | try { 35 | if (provider) { 36 | reply.redirect(`/light-novels/${provider.toString.name}`); 37 | } else { 38 | reply 39 | .status(404) 40 | .send({ message: 'Page not found, please check the providers list.' }); 41 | } 42 | } catch (err) { 43 | reply.status(500).send('Something went wrong. Please try again later.'); 44 | } 45 | } 46 | ); 47 | }; 48 | 49 | export default routes; 50 | -------------------------------------------------------------------------------- /consumet-api/src/routes/light-novels/readlightnovels.ts: -------------------------------------------------------------------------------- 1 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 2 | import { LIGHT_NOVELS } from '@consumet/extensions'; 3 | 4 | const routes = async (fastify: FastifyInstance, options: RegisterOptions) => { 5 | const readlightnovels = new LIGHT_NOVELS.ReadLightNovels(); 6 | 7 | fastify.get('/', (_, rp) => { 8 | rp.status(200).send({ 9 | intro: 10 | "Welcome to the readlightnovels provider: check out the provider's website @ https://readlightnovels.net/", 11 | routes: ['/:query', '/info', '/read'], 12 | documentation: 'https://docs.consumet.org/#tag/readlightnovels', 13 | }); 14 | }); 15 | 16 | fastify.get('/:query', async (request: FastifyRequest, reply: FastifyReply) => { 17 | const query = (request.params as { query: string }).query; 18 | 19 | const res = await readlightnovels.search(query); 20 | 21 | reply.status(200).send(res); 22 | }); 23 | 24 | fastify.get('/info', async (request: FastifyRequest, reply: FastifyReply) => { 25 | const id = (request.query as { id: string }).id; 26 | const chapterPage = (request.query as { chapterPage: number }).chapterPage; 27 | 28 | if (typeof id === 'undefined') { 29 | return reply.status(400).send({ 30 | message: 'id is required', 31 | }); 32 | } 33 | 34 | try { 35 | const res = await readlightnovels 36 | .fetchLightNovelInfo(id, chapterPage) 37 | .catch((err) => reply.status(404).send({ message: err })); 38 | 39 | reply.status(200).send(res); 40 | } catch (err) { 41 | reply 42 | .status(500) 43 | .send({ message: 'Something went wrong. Please try again later.' }); 44 | } 45 | }); 46 | 47 | fastify.get('/read', async (request: FastifyRequest, reply: FastifyReply) => { 48 | const chapterId = (request.query as { chapterId: string }).chapterId; 49 | 50 | if (typeof chapterId === 'undefined') { 51 | return reply.status(400).send({ 52 | message: 'chapterId is required', 53 | }); 54 | } 55 | 56 | try { 57 | const res = await readlightnovels 58 | .fetchChapterContent(chapterId) 59 | .catch((err) => reply.status(404).send(err)); 60 | 61 | reply.status(200).send(res); 62 | } catch (err) { 63 | reply 64 | .status(500) 65 | .send({ message: 'Something went wrong. Please try again later.' }); 66 | } 67 | }); 68 | }; 69 | 70 | export default routes; 71 | -------------------------------------------------------------------------------- /consumet-api/src/routes/manga/index.ts: -------------------------------------------------------------------------------- 1 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 2 | import { PROVIDERS_LIST } from '@consumet/extensions'; 3 | import mangapill from './mangapill'; 4 | import managreader from './managreader'; 5 | import mangadex from './mangadex'; 6 | import mangahere from './mangahere'; 7 | import mangakakalot from './mangakakalot'; 8 | import mangasee123 from './mangasee123'; 9 | import mangapark from './mangapark'; 10 | const routes = async (fastify: FastifyInstance, options: RegisterOptions) => { 11 | await fastify.register(mangadex, { prefix: '/mangadex' }); 12 | await fastify.register(mangahere, { prefix: '/mangahere' }); 13 | await fastify.register(mangakakalot, { prefix: '/mangakakalot' }); 14 | await fastify.register(mangasee123, { prefix: '/mangasee123' }); 15 | await fastify.register(mangapill, { prefix: '/mangapill' }); 16 | await fastify.register(managreader, { prefix: '/managreader' }); 17 | await fastify.register(mangapark, { prefix: '/mangapark' }); 18 | fastify.get('/', async (request: any, reply: any) => { 19 | reply.status(200).send('Welcome to Consumet Manga'); 20 | }); 21 | 22 | fastify.get('/:mangaProvider', async (request: FastifyRequest, reply: FastifyReply) => { 23 | const queries: { mangaProvider: string; page: number } = { 24 | mangaProvider: '', 25 | page: 1, 26 | }; 27 | 28 | queries.mangaProvider = decodeURIComponent( 29 | (request.params as { mangaProvider: string; page: number }).mangaProvider 30 | ); 31 | 32 | queries.page = (request.query as { mangaProvider: string; page: number }).page; 33 | 34 | if (queries.page! < 1) queries.page = 1; 35 | 36 | const provider = PROVIDERS_LIST.MANGA.find( 37 | (provider: any) => provider.toString.name === queries.mangaProvider 38 | ); 39 | 40 | try { 41 | if (provider) { 42 | reply.redirect(`/manga/${provider.toString.name}`); 43 | } else { 44 | reply 45 | .status(404) 46 | .send({ message: 'Page not found, please check the provider list.' }); 47 | } 48 | } catch (err) { 49 | reply.status(500).send('Something went wrong. Please try again later.'); 50 | } 51 | }); 52 | }; 53 | 54 | export default routes; 55 | -------------------------------------------------------------------------------- /consumet-api/src/routes/manga/managreader.ts: -------------------------------------------------------------------------------- 1 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 2 | import { MANGA } from '@consumet/extensions'; 3 | const routes = async (fastify: FastifyInstance, options: RegisterOptions) => { 4 | const managreader = new MANGA.MangaReader(); 5 | 6 | fastify.get('/', (_, rp) => { 7 | rp.status(200).send({ 8 | intro: `Welcome to the Mangapill provider: check out the provider's website @ ${managreader.toString.baseUrl}`, 9 | routes: ['/:query', '/info', '/read'], 10 | documentation: 'https://docs.consumet.org/#tag/mangapill', 11 | }); 12 | }); 13 | 14 | fastify.get('/:query', async (request: FastifyRequest, reply: FastifyReply) => { 15 | const query = (request.params as { query: string }).query; 16 | 17 | const res = await managreader.search(query); 18 | 19 | reply.status(200).send(res); 20 | }); 21 | 22 | fastify.get('/info', async (request: FastifyRequest, reply: FastifyReply) => { 23 | const id = (request.query as { id: string }).id; 24 | 25 | if (typeof id === 'undefined') 26 | return reply.status(400).send({ message: 'id is required' }); 27 | 28 | try { 29 | const res = await managreader.fetchMangaInfo(id); 30 | 31 | reply.status(200).send(res); 32 | } catch (err) { 33 | reply 34 | .status(500) 35 | .send({ message: 'Something went wrong. Please try again later.' }); 36 | } 37 | }); 38 | 39 | fastify.get('/read', async (request: FastifyRequest, reply: FastifyReply) => { 40 | const chapterId = (request.query as { chapterId: string }).chapterId; 41 | 42 | if (typeof chapterId === 'undefined') 43 | return reply.status(400).send({ message: 'chapterId is required' }); 44 | 45 | try { 46 | const res = await managreader 47 | .fetchChapterPages(chapterId) 48 | .catch((err: Error) => reply.status(404).send({ message: err.message })); 49 | 50 | reply.status(200).send(res); 51 | } catch (err) { 52 | reply 53 | .status(500) 54 | .send({ message: 'Something went wrong. Please try again later.' }); 55 | } 56 | }); 57 | }; 58 | 59 | export default routes; 60 | -------------------------------------------------------------------------------- /consumet-api/src/routes/manga/mangadex.ts: -------------------------------------------------------------------------------- 1 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 2 | import { MANGA } from '@consumet/extensions'; 3 | 4 | const routes = async (fastify: FastifyInstance, options: RegisterOptions) => { 5 | const mangadex = new MANGA.MangaDex(); 6 | 7 | fastify.get('/', (_, rp) => { 8 | rp.status(200).send({ 9 | intro: 10 | "Welcome to the mangadex provider: check out the provider's website @ https://mangadex.org/", 11 | routes: ['/:query', '/info/:id', '/read/:chapterId'], 12 | documentation: 'https://docs.consumet.org/#tag/mangadex', 13 | }); 14 | }); 15 | 16 | fastify.get('/:query', async (request: FastifyRequest, reply: FastifyReply) => { 17 | const query = (request.params as { query: string }).query; 18 | 19 | const page = (request.query as { page: number }).page; 20 | 21 | const res = await mangadex.search(query, page); 22 | 23 | reply.status(200).send(res); 24 | }); 25 | 26 | fastify.get('/info/:id', async (request: FastifyRequest, reply: FastifyReply) => { 27 | const id = decodeURIComponent((request.params as { id: string }).id); 28 | 29 | try { 30 | const res = await mangadex 31 | .fetchMangaInfo(id) 32 | .catch((err) => reply.status(404).send({ message: err })); 33 | 34 | reply.status(200).send(res); 35 | } catch (err) { 36 | reply 37 | .status(500) 38 | .send({ message: 'Something went wrong. Please try again later.' }); 39 | } 40 | }); 41 | 42 | fastify.get( 43 | '/read/:chapterId', 44 | async (request: FastifyRequest, reply: FastifyReply) => { 45 | const chapterId = (request.params as { chapterId: string }).chapterId; 46 | 47 | try { 48 | const res = await mangadex.fetchChapterPages(chapterId); 49 | 50 | reply.status(200).send(res); 51 | } catch (err) { 52 | reply 53 | .status(500) 54 | .send({ message: 'Something went wrong. Please try again later.' }); 55 | } 56 | } 57 | ); 58 | }; 59 | 60 | export default routes; 61 | -------------------------------------------------------------------------------- /consumet-api/src/routes/manga/mangahere.ts: -------------------------------------------------------------------------------- 1 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 2 | import { MANGA } from '@consumet/extensions'; 3 | 4 | const routes = async (fastify: FastifyInstance, options: RegisterOptions) => { 5 | const mangahere = new MANGA.MangaHere(); 6 | 7 | fastify.get('/', (_, rp) => { 8 | rp.status(200).send({ 9 | intro: `Welcome to the MangaHere provider: check out the provider's website @ ${mangahere.toString.baseUrl}`, 10 | routes: ['/:query', '/info', '/read'], 11 | documentation: 'https://docs.consumet.org/#tag/mangahere', 12 | }); 13 | }); 14 | 15 | fastify.get('/:query', async (request: FastifyRequest, reply: FastifyReply) => { 16 | const query = (request.params as { query: string }).query; 17 | 18 | const page = (request.query as { page: number }).page; 19 | 20 | const res = await mangahere.search(query, page); 21 | 22 | reply.status(200).send(res); 23 | }); 24 | 25 | fastify.get('/info', async (request: FastifyRequest, reply: FastifyReply) => { 26 | const id = (request.query as { id: string }).id; 27 | 28 | if (typeof id === 'undefined') 29 | return reply.status(400).send({ message: 'id is required' }); 30 | 31 | try { 32 | const res = await mangahere 33 | .fetchMangaInfo(id) 34 | .catch((err) => reply.status(404).send({ message: err })); 35 | 36 | reply.status(200).send(res); 37 | } catch (err) { 38 | reply 39 | .status(500) 40 | .send({ message: 'Something went wrong. Please try again later.' }); 41 | } 42 | }); 43 | 44 | fastify.get('/read', async (request: FastifyRequest, reply: FastifyReply) => { 45 | const chapterId = (request.query as { chapterId: string }).chapterId; 46 | 47 | if (typeof chapterId === 'undefined') 48 | return reply.status(400).send({ message: 'chapterId is required' }); 49 | 50 | try { 51 | const res = await mangahere 52 | .fetchChapterPages(chapterId) 53 | .catch((err: Error) => reply.status(404).send({ message: err.message })); 54 | 55 | reply.status(200).send(res); 56 | } catch (err) { 57 | reply 58 | .status(500) 59 | .send({ message: 'Something went wrong. Please try again later.' }); 60 | } 61 | }); 62 | }; 63 | 64 | export default routes; 65 | -------------------------------------------------------------------------------- /consumet-api/src/routes/manga/mangakakalot.ts: -------------------------------------------------------------------------------- 1 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 2 | import { MANGA } from '@consumet/extensions'; 3 | 4 | const routes = async (fastify: FastifyInstance, options: RegisterOptions) => { 5 | const mangakakalot = new MANGA.MangaKakalot(); 6 | 7 | fastify.get('/', (_, rp) => { 8 | rp.status(200).send({ 9 | intro: `Welcome to the MangaKakalot provider: check out the provider's website @ ${mangakakalot.toString.baseUrl}`, 10 | routes: ['/:query', '/info', '/read'], 11 | documentation: 'https://docs.consumet.org/#tag/mangakakalot', 12 | }); 13 | }); 14 | 15 | fastify.get('/:query', async (request: FastifyRequest, reply: FastifyReply) => { 16 | const query = (request.params as { query: string }).query; 17 | 18 | const res = await mangakakalot.search(query); 19 | 20 | reply.status(200).send(res); 21 | }); 22 | 23 | fastify.get('/info', async (request: FastifyRequest, reply: FastifyReply) => { 24 | const id = (request.query as { id: string }).id; 25 | 26 | if (typeof id === 'undefined') 27 | return reply.status(400).send({ message: 'id is required' }); 28 | 29 | try { 30 | const res = await mangakakalot 31 | .fetchMangaInfo(id) 32 | .catch((err) => reply.status(404).send({ message: err })); 33 | 34 | reply.status(200).send(res); 35 | } catch (err) { 36 | reply 37 | .status(500) 38 | .send({ message: 'Something went wrong. Please try again later.' }); 39 | } 40 | }); 41 | 42 | fastify.get('/read', async (request: FastifyRequest, reply: FastifyReply) => { 43 | const chapterId = (request.query as { chapterId: string }).chapterId; 44 | 45 | if (typeof chapterId === 'undefined') 46 | return reply.status(400).send({ message: 'chapterId is required' }); 47 | 48 | try { 49 | const res = await mangakakalot 50 | .fetchChapterPages(chapterId) 51 | .catch((err: Error) => reply.status(404).send({ message: err.message })); 52 | 53 | reply.status(200).send(res); 54 | } catch (err) { 55 | reply 56 | .status(500) 57 | .send({ message: 'Something went wrong. Please try again later.' }); 58 | } 59 | }); 60 | }; 61 | 62 | export default routes; 63 | -------------------------------------------------------------------------------- /consumet-api/src/routes/manga/mangapark.ts: -------------------------------------------------------------------------------- 1 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 2 | import { MANGA } from '@consumet/extensions'; 3 | 4 | const routes = async (fastify: FastifyInstance, options: RegisterOptions) => { 5 | const mangapark = new MANGA.Mangapark(); 6 | 7 | fastify.get('/', (_, rp) => { 8 | rp.status(200).send({ 9 | intro: `Welcome to the MangaKakalot provider: check out the provider's website @ ${mangapark.toString.baseUrl}`, 10 | routes: ['/:query', '/info', '/read'], 11 | documentation: 'https://docs.consumet.org/#tag/mangakakalot', 12 | }); 13 | }); 14 | 15 | fastify.get('/:query', async (request: FastifyRequest, reply: FastifyReply) => { 16 | const query = (request.params as { query: string }).query; 17 | 18 | const res = await mangapark.search(query); 19 | 20 | reply.status(200).send(res); 21 | }); 22 | 23 | fastify.get('/info', async (request: FastifyRequest, reply: FastifyReply) => { 24 | const id = (request.query as { id: string }).id; 25 | 26 | if (typeof id === 'undefined') 27 | return reply.status(400).send({ message: 'id is required' }); 28 | 29 | try { 30 | const res = await mangapark 31 | .fetchMangaInfo(id) 32 | .catch((err) => reply.status(404).send({ message: err })); 33 | 34 | reply.status(200).send(res); 35 | } catch (err) { 36 | reply 37 | .status(500) 38 | .send({ message: 'Something went wrong. Please try again later.' }); 39 | } 40 | }); 41 | 42 | fastify.get('/read', async (request: FastifyRequest, reply: FastifyReply) => { 43 | const chapterId = (request.query as { chapterId: string }).chapterId; 44 | 45 | if (typeof chapterId === 'undefined') 46 | return reply.status(400).send({ message: 'chapterId is required' }); 47 | 48 | try { 49 | const res = await mangapark 50 | .fetchChapterPages(chapterId) 51 | .catch((err: Error) => reply.status(404).send({ message: err.message })); 52 | 53 | reply.status(200).send(res); 54 | } catch (err) { 55 | reply 56 | .status(500) 57 | .send({ message: 'Something went wrong. Please try again later.' }); 58 | } 59 | }); 60 | }; 61 | 62 | export default routes; 63 | -------------------------------------------------------------------------------- /consumet-api/src/routes/manga/mangapill.ts: -------------------------------------------------------------------------------- 1 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 2 | import { MANGA } from '@consumet/extensions'; 3 | const routes = async (fastify: FastifyInstance, options: RegisterOptions) => { 4 | const mangapill = new MANGA.MangaPill(); 5 | 6 | fastify.get('/', (_, rp) => { 7 | rp.status(200).send({ 8 | intro: `Welcome to the Mangapill provider: check out the provider's website @ ${mangapill.toString.baseUrl}`, 9 | routes: ['/:query', '/info', '/read'], 10 | documentation: 'https://docs.consumet.org/#tag/mangapill', 11 | }); 12 | }); 13 | 14 | fastify.get('/:query', async (request: FastifyRequest, reply: FastifyReply) => { 15 | const query = (request.params as { query: string }).query; 16 | 17 | const res = await mangapill.search(query); 18 | 19 | reply.status(200).send(res); 20 | }); 21 | 22 | fastify.get('/info', async (request: FastifyRequest, reply: FastifyReply) => { 23 | const id = (request.query as { id: string }).id; 24 | 25 | if (typeof id === 'undefined') 26 | return reply.status(400).send({ message: 'id is required' }); 27 | 28 | try { 29 | const res = await mangapill.fetchMangaInfo(id); 30 | 31 | reply.status(200).send(res); 32 | } catch (err) { 33 | reply 34 | .status(500) 35 | .send({ message: 'Something went wrong. Please try again later.' }); 36 | } 37 | }); 38 | 39 | fastify.get('/read', async (request: FastifyRequest, reply: FastifyReply) => { 40 | const chapterId = (request.query as { chapterId: string }).chapterId; 41 | 42 | if (typeof chapterId === 'undefined') 43 | return reply.status(400).send({ message: 'chapterId is required' }); 44 | 45 | try { 46 | const res = await mangapill 47 | .fetchChapterPages(chapterId) 48 | .catch((err: Error) => reply.status(404).send({ message: err.message })); 49 | 50 | reply.status(200).send(res); 51 | } catch (err) { 52 | reply 53 | .status(500) 54 | .send({ message: 'Something went wrong. Please try again later.' }); 55 | } 56 | }); 57 | }; 58 | 59 | export default routes; 60 | -------------------------------------------------------------------------------- /consumet-api/src/routes/manga/mangasee123.ts: -------------------------------------------------------------------------------- 1 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 2 | import { MANGA } from '@consumet/extensions'; 3 | 4 | const routes = async (fastify: FastifyInstance, options: RegisterOptions) => { 5 | const mangasee123 = new MANGA.Mangasee123(); 6 | 7 | fastify.get('/', (_, rp) => { 8 | rp.status(200).send({ 9 | intro: `Welcome to the mangasee123 provider: check out the provider's website @ ${mangasee123.toString.baseUrl}`, 10 | routes: ['/:query', '/info', '/read'], 11 | documentation: 'https://docs.consumet.org/#tag/mangasee123', 12 | }); 13 | }); 14 | 15 | fastify.get('/:query', async (request: FastifyRequest, reply: FastifyReply) => { 16 | const query = (request.params as { query: string }).query; 17 | 18 | const res = await mangasee123.search(query); 19 | 20 | reply.status(200).send(res); 21 | }); 22 | 23 | fastify.get('/info', async (request: FastifyRequest, reply: FastifyReply) => { 24 | const id = (request.query as { id: string }).id; 25 | 26 | if (typeof id === 'undefined') 27 | return reply.status(400).send({ message: 'id is required' }); 28 | 29 | try { 30 | const res = await mangasee123 31 | .fetchMangaInfo(id) 32 | .catch((err) => reply.status(404).send({ message: err })); 33 | 34 | reply.status(200).send(res); 35 | } catch (err) { 36 | reply 37 | .status(500) 38 | .send({ message: 'Something went wrong. Please try again later.' }); 39 | } 40 | }); 41 | 42 | fastify.get('/read', async (request: FastifyRequest, reply: FastifyReply) => { 43 | const chapterId = (request.query as { chapterId: string }).chapterId; 44 | 45 | if (typeof chapterId === 'undefined') 46 | return reply.status(400).send({ message: 'chapterId is required' }); 47 | 48 | try { 49 | const res = await mangasee123 50 | .fetchChapterPages(chapterId) 51 | .catch((err: Error) => reply.status(404).send({ message: err.message })); 52 | 53 | reply.status(200).send(res); 54 | } catch (err) { 55 | reply 56 | .status(500) 57 | .send({ message: 'Something went wrong. Please try again later.' }); 58 | } 59 | }); 60 | }; 61 | 62 | export default routes; 63 | -------------------------------------------------------------------------------- /consumet-api/src/routes/meta/anilist-manga.ts: -------------------------------------------------------------------------------- 1 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 2 | import { META } from '@consumet/extensions'; 3 | import { PROVIDERS_LIST } from '@consumet/extensions'; 4 | 5 | const routes = async (fastify: FastifyInstance, options: RegisterOptions) => { 6 | // TODO: Allocate new provider per request rather 7 | // than global 8 | let anilist = new META.Anilist.Manga(); 9 | 10 | fastify.get('/', (_, rp) => { 11 | rp.status(200).send({ 12 | intro: `Welcome to the anilist manga provider: check out the provider's website @ ${anilist.provider.toString.baseUrl}`, 13 | routes: ['/:query', '/info', '/read'], 14 | documentation: 'https://docs.consumet.org/#tag/anilist', 15 | }); 16 | }); 17 | 18 | fastify.get('/:query', async (request: FastifyRequest, reply: FastifyReply) => { 19 | const query = (request.params as { query: string }).query; 20 | 21 | const res = await anilist.search(query); 22 | 23 | reply.status(200).send(res); 24 | }); 25 | 26 | fastify.get('/info/:id', async (request: FastifyRequest, reply: FastifyReply) => { 27 | const id = (request.params as { id: string }).id; 28 | const provider = (request.query as { provider: string }).provider; 29 | 30 | if (typeof provider !== 'undefined') { 31 | const possibleProvider = PROVIDERS_LIST.MANGA.find( 32 | (p) => p.name.toLowerCase() === provider.toLocaleLowerCase() 33 | ); 34 | anilist = new META.Anilist.Manga(possibleProvider); 35 | } 36 | 37 | if (typeof id === 'undefined') 38 | return reply.status(400).send({ message: 'id is required' }); 39 | 40 | try { 41 | const res = await anilist 42 | .fetchMangaInfo(id) 43 | .catch((err) => reply.status(404).send({ message: err })); 44 | 45 | reply.status(200).send(res); 46 | anilist = new META.Anilist.Manga(); 47 | } catch (err) { 48 | reply 49 | .status(500) 50 | .send({ message: 'Something went wrong. Please try again later.' }); 51 | } 52 | }); 53 | 54 | fastify.get('/read', async (request: FastifyRequest, reply: FastifyReply) => { 55 | const chapterId = (request.query as { chapterId: string }).chapterId; 56 | const provider = (request.query as { provider: string }).provider; 57 | 58 | if (typeof provider !== 'undefined') { 59 | const possibleProvider = PROVIDERS_LIST.MANGA.find( 60 | (p) => p.name.toLowerCase() === provider.toLocaleLowerCase() 61 | ); 62 | anilist = new META.Anilist.Manga(possibleProvider); 63 | } 64 | 65 | if (typeof chapterId === 'undefined') 66 | return reply.status(400).send({ message: 'chapterId is required' }); 67 | 68 | try { 69 | const res = await anilist 70 | .fetchChapterPages(chapterId) 71 | .catch((err: Error) => reply.status(404).send({ message: err.message })); 72 | 73 | anilist = new META.Anilist.Manga(); 74 | reply.status(200).send(res); 75 | } catch (err) { 76 | reply 77 | .status(500) 78 | .send({ message: 'Something went wrong. Please try again later.' }); 79 | } 80 | }); 81 | }; 82 | 83 | export default routes; 84 | -------------------------------------------------------------------------------- /consumet-api/src/routes/meta/index.ts: -------------------------------------------------------------------------------- 1 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 2 | import { PROVIDERS_LIST } from '@consumet/extensions'; 3 | 4 | import anilist from './anilist'; 5 | import anilistManga from './anilist-manga'; 6 | import mal from './mal'; 7 | import tmdb from './tmdb'; 8 | const routes = async (fastify: FastifyInstance, options: RegisterOptions) => { 9 | await fastify.register(anilist, { prefix: '/anilist' }); 10 | await fastify.register(anilistManga, { prefix: '/anilist-manga' }); 11 | await fastify.register(mal, { prefix: '/mal' }); 12 | await fastify.register(tmdb, { prefix: '/tmdb' }); 13 | 14 | fastify.get('/', async (request: any, reply: any) => { 15 | reply.status(200).send('Welcome to Consumet Meta'); 16 | }); 17 | 18 | fastify.get('/:metaProvider', async (request: FastifyRequest, reply: FastifyReply) => { 19 | const queries: { metaProvider: string; page: number } = { 20 | metaProvider: '', 21 | page: 1, 22 | }; 23 | 24 | queries.metaProvider = decodeURIComponent( 25 | (request.params as { metaProvider: string; page: number }).metaProvider 26 | ); 27 | 28 | queries.page = (request.query as { metaProvider: string; page: number }).page; 29 | 30 | if (queries.page! < 1) queries.page = 1; 31 | 32 | const provider = PROVIDERS_LIST.META.find( 33 | (provider: any) => provider.toString.name === queries.metaProvider 34 | ); 35 | 36 | try { 37 | if (provider) { 38 | reply.redirect(`/anime/${provider.toString.name}`); 39 | } else { 40 | reply 41 | .status(404) 42 | .send({ message: 'Provider not found, please check the providers list.' }); 43 | } 44 | } catch (err) { 45 | reply.status(500).send('Something went wrong. Please try again later.'); 46 | } 47 | }); 48 | }; 49 | 50 | export default routes; 51 | -------------------------------------------------------------------------------- /consumet-api/src/routes/meta/mal.ts: -------------------------------------------------------------------------------- 1 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 2 | import { META, PROVIDERS_LIST } from '@consumet/extensions'; 3 | 4 | const routes = async (fastify: FastifyInstance, options: RegisterOptions) => { 5 | let mal = new META.Myanimelist(); 6 | 7 | fastify.get('/', (_, rp) => { 8 | rp.status(200).send({ 9 | intro: 10 | "Welcome to the mal provider: check out the provider's website @ https://mal.co/", 11 | routes: ['/:query', '/info/:id', '/watch/:episodeId'], 12 | documentation: 'https://docs.consumet.org/#tag/mal', 13 | }); 14 | }); 15 | 16 | fastify.get('/:query', async (request: FastifyRequest, reply: FastifyReply) => { 17 | const query = (request.params as { query: string }).query; 18 | 19 | const page = (request.query as { page: number }).page; 20 | const perPage = (request.query as { perPage: number }).perPage; 21 | 22 | const res = await mal.search(query, page); 23 | 24 | reply.status(200).send(res); 25 | }); 26 | 27 | // mal info with episodes 28 | fastify.get('/info/:id', async (request: FastifyRequest, reply: FastifyReply) => { 29 | const id = (request.params as { id: string }).id; 30 | 31 | const provider = (request.query as { provider?: string }).provider; 32 | let fetchFiller = (request.query as { fetchFiller?: string | boolean }).fetchFiller; 33 | let isDub = (request.query as { dub?: string | boolean }).dub; 34 | const locale = (request.query as { locale?: string }).locale; 35 | 36 | if (typeof provider !== 'undefined') { 37 | const possibleProvider = PROVIDERS_LIST.ANIME.find( 38 | (p) => p.name.toLowerCase() === provider.toLocaleLowerCase() 39 | ); 40 | 41 | mal = new META.Myanimelist(possibleProvider); 42 | } 43 | 44 | if (isDub === 'true' || isDub === '1') isDub = true; 45 | else isDub = false; 46 | 47 | if (fetchFiller === 'true' || fetchFiller === '1') fetchFiller = true; 48 | else fetchFiller = false; 49 | 50 | try { 51 | const res = await mal.fetchAnimeInfo(id, isDub as boolean, fetchFiller as boolean); 52 | 53 | mal = new META.Myanimelist(undefined); 54 | reply.status(200).send(res); 55 | } catch (err: any) { 56 | reply.status(500).send({ message: err.message }); 57 | } 58 | }); 59 | 60 | fastify.get( 61 | '/watch/:episodeId', 62 | async (request: FastifyRequest, reply: FastifyReply) => { 63 | const episodeId = (request.params as { episodeId: string }).episodeId; 64 | const provider = (request.query as { provider?: string }).provider; 65 | 66 | if (typeof provider !== 'undefined') { 67 | const possibleProvider = PROVIDERS_LIST.ANIME.find( 68 | (p) => p.name.toLowerCase() === provider.toLocaleLowerCase() 69 | ); 70 | 71 | mal = new META.Myanimelist(possibleProvider); 72 | } 73 | try { 74 | const res = await mal 75 | .fetchEpisodeSources(episodeId) 76 | .catch((err) => reply.status(404).send({ message: err })); 77 | 78 | mal = new META.Myanimelist(undefined); 79 | reply.status(200).send(res); 80 | } catch (err) { 81 | reply 82 | .status(500) 83 | .send({ message: 'Something went wrong. Contact developer for help.' }); 84 | } 85 | } 86 | ); 87 | }; 88 | 89 | export default routes; 90 | -------------------------------------------------------------------------------- /consumet-api/src/routes/meta/tmdb.ts: -------------------------------------------------------------------------------- 1 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 2 | import { META, PROVIDERS_LIST } from '@consumet/extensions'; 3 | import { tmdbApi } from '../../main'; 4 | const routes = async (fastify: FastifyInstance, options: RegisterOptions) => { 5 | fastify.get('/', (_, rp) => { 6 | rp.status(200).send({ 7 | intro: 8 | "Welcome to the tmdb provider: check out the provider's website @ https://www.themoviedb.org/", 9 | routes: ['/:query', '/info/:id', '/watch/:episodeId'], 10 | documentation: 'https://docs.consumet.org/#tag/tmdb', 11 | }); 12 | }); 13 | 14 | fastify.get('/:query', async (request: FastifyRequest, reply: FastifyReply) => { 15 | const query = (request.params as { query: string }).query; 16 | const page = (request.query as { page: number }).page; 17 | const tmdb = new META.TMDB(tmdbApi); 18 | 19 | const res = await tmdb.search(query, page); 20 | 21 | reply.status(200).send(res); 22 | }); 23 | 24 | fastify.get('/info/:id', async (request: FastifyRequest, reply: FastifyReply) => { 25 | const id = (request.params as { id: string }).id; 26 | const type = (request.query as { type: string }).type; 27 | const provider = (request.query as { provider?: string }).provider; 28 | let tmdb = new META.TMDB(tmdbApi); 29 | 30 | if (!type) return reply.status(400).send({ message: "The 'type' query is required" }); 31 | 32 | if (typeof provider !== 'undefined') { 33 | const possibleProvider = PROVIDERS_LIST.MOVIES.find( 34 | (p) => p.name.toLowerCase() === provider.toLocaleLowerCase() 35 | ); 36 | tmdb = new META.TMDB(tmdbApi, possibleProvider); 37 | } 38 | 39 | const res = await tmdb.fetchMediaInfo(id, type); 40 | reply.status(200).send(res); 41 | }); 42 | 43 | fastify.get( 44 | '/watch/:episodeId', 45 | async (request: FastifyRequest, reply: FastifyReply) => { 46 | const episodeId = (request.params as { episodeId: string }).episodeId; 47 | const id = (request.query as { id: string }).id; 48 | const tmdb = new META.TMDB(tmdbApi); 49 | 50 | try { 51 | const res = await tmdb 52 | .fetchEpisodeSources(episodeId, id) 53 | .catch((err) => reply.status(404).send({ message: err })); 54 | 55 | reply.status(200).send(res); 56 | } catch (err) { 57 | reply 58 | .status(500) 59 | .send({ message: 'Something went wrong. Contact developer for help.' }); 60 | } 61 | } 62 | ); 63 | }; 64 | 65 | export default routes; 66 | -------------------------------------------------------------------------------- /consumet-api/src/routes/movies/dramacool.ts: -------------------------------------------------------------------------------- 1 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 2 | import { MOVIES } from '@consumet/extensions'; 3 | import { StreamingServers } from '@consumet/extensions/dist/models'; 4 | 5 | const routes = async (fastify: FastifyInstance, options: RegisterOptions) => { 6 | const dramacool = new MOVIES.DramaCool(); 7 | 8 | fastify.get('/', (_, rp) => { 9 | rp.status(200).send({ 10 | intro: 11 | "Welcome to the flixhq provider: check out the provider's website @ https://flixhq.to/", 12 | routes: ['/:query', '/info', '/watch'], 13 | documentation: 'https://docs.consumet.org/#tag/flixhq', 14 | }); 15 | }); 16 | 17 | fastify.get('/:query', async (request: FastifyRequest, reply: FastifyReply) => { 18 | const query = decodeURIComponent((request.params as { query: string }).query); 19 | 20 | const page = (request.query as { page: number }).page; 21 | 22 | const res = await dramacool.search(query, page); 23 | 24 | reply.status(200).send(res); 25 | }); 26 | 27 | fastify.get('/info', async (request: FastifyRequest, reply: FastifyReply) => { 28 | const id = (request.query as { id: string }).id; 29 | 30 | if (typeof id === 'undefined') 31 | return reply.status(400).send({ 32 | message: 'id is required', 33 | }); 34 | 35 | try { 36 | const res = await dramacool 37 | .fetchMediaInfo(id) 38 | .catch((err) => reply.status(404).send({ message: err })); 39 | 40 | reply.status(200).send(res); 41 | } catch (err) { 42 | reply.status(500).send({ 43 | message: 44 | 'Something went wrong. Please try again later. or contact the developers.', 45 | }); 46 | } 47 | }); 48 | 49 | fastify.get('/watch', async (request: FastifyRequest, reply: FastifyReply) => { 50 | const episodeId = (request.query as { episodeId: string }).episodeId; 51 | // const mediaId = (request.query as { mediaId: string }).mediaId; 52 | // const server = (request.query as { server: StreamingServers }).server; 53 | 54 | if (typeof episodeId === 'undefined') 55 | return reply.status(400).send({ message: 'episodeId is required' }); 56 | try { 57 | const res = await dramacool 58 | .fetchEpisodeSources(episodeId) 59 | .catch((err) => reply.status(404).send({ message: 'Media Not found.' })); 60 | 61 | reply.status(200).send(res); 62 | } catch (err) { 63 | reply 64 | .status(500) 65 | .send({ message: 'Something went wrong. Please try again later.' }); 66 | } 67 | }); 68 | }; 69 | 70 | export default routes; 71 | -------------------------------------------------------------------------------- /consumet-api/src/routes/movies/fmovies.ts: -------------------------------------------------------------------------------- 1 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 2 | import { MOVIES } from '@consumet/extensions'; 3 | import { StreamingServers } from '@consumet/extensions/dist/models'; 4 | 5 | import cache from '../../utils/cache'; 6 | import { redis } from '../../main'; 7 | import { Redis } from 'ioredis'; 8 | 9 | const routes = async (fastify: FastifyInstance, options: RegisterOptions) => { 10 | const fmovies = new MOVIES.Fmovies( 11 | process.env.NINE_ANIME_HELPER_URL, 12 | { 13 | url: process.env.NINE_ANIME_PROXY as string, 14 | }, 15 | process.env?.NINE_ANIME_HELPER_KEY 16 | ); 17 | 18 | fastify.get('/', (_, rp) => { 19 | rp.status(200).send({ 20 | intro: 21 | "Welcome to the fmovies provider: check out the provider's website @ https://fmovies.to/", 22 | routes: ['/:query', '/info', '/watch'], 23 | documentation: 'https://docs.consumet.org/#tag/fmovies', 24 | }); 25 | }); 26 | 27 | fastify.get('/:query', async (request: FastifyRequest, reply: FastifyReply) => { 28 | const query = decodeURIComponent((request.params as { query: string }).query); 29 | 30 | const page = (request.query as { page: number }).page; 31 | 32 | let res = redis ? await cache.fetch( 33 | redis as Redis, 34 | `fmovies:${query}:${page}`, 35 | async () => await fmovies.search(query, page ? page : 1), 36 | 60 * 60 * 6 37 | ) : await fmovies.search(query, page ? page : 1) 38 | 39 | reply.status(200).send(res); 40 | }); 41 | 42 | 43 | fastify.get('/info', async (request: FastifyRequest, reply: FastifyReply) => { 44 | const id = (request.query as { id: string }).id; 45 | 46 | if (typeof id === 'undefined') 47 | return reply.status(400).send({ 48 | message: 'id is required', 49 | }); 50 | 51 | try { 52 | let res = redis ? await cache.fetch( 53 | redis as Redis, 54 | `fmovies:info:${id}`, 55 | async () => await fmovies.fetchMediaInfo(id), 56 | 60 * 60 * 3 57 | ) : await fmovies.fetchMediaInfo(id) 58 | 59 | reply.status(200).send(res); 60 | } catch (err) { 61 | reply.status(500).send({ 62 | message: 63 | 'Something went wrong. Please try again later. or contact the developers.', 64 | }); 65 | } 66 | }); 67 | 68 | fastify.get('/watch', async (request: FastifyRequest, reply: FastifyReply) => { 69 | const episodeId = (request.query as { episodeId: string }).episodeId; 70 | const mediaId = (request.query as { mediaId: string }).mediaId; 71 | const server = (request.query as { server: StreamingServers }).server; 72 | 73 | if (typeof episodeId === 'undefined') 74 | return reply.status(400).send({ message: 'episodeId is required' }); 75 | if (typeof mediaId === 'undefined') 76 | return reply.status(400).send({ message: 'mediaId is required' }); 77 | 78 | if (server && !Object.values(StreamingServers).includes(server)) 79 | return reply.status(400).send({ message: 'Invalid server query' }); 80 | 81 | try { 82 | let res = redis ? await cache.fetch( 83 | redis as Redis, 84 | `fmovies:watch:${episodeId}:${mediaId}:${server}`, 85 | async () => await fmovies.fetchEpisodeSources(episodeId, mediaId, server), 86 | 60 * 30 87 | ) : await fmovies.fetchEpisodeSources(episodeId, mediaId, server) 88 | 89 | reply.status(200).send(res); 90 | } catch (err) { 91 | reply 92 | .status(500) 93 | .send({ message: 'Something went wrong. Please try again later.' }); 94 | } 95 | }); 96 | 97 | }; 98 | 99 | export default routes; 100 | -------------------------------------------------------------------------------- /consumet-api/src/routes/movies/index.ts: -------------------------------------------------------------------------------- 1 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 2 | import { PROVIDERS_LIST } from '@consumet/extensions'; 3 | 4 | import flixhq from './flixhq'; 5 | import viewasian from './viewasian'; 6 | import dramacool from './dramacool'; 7 | import fmovies from './fmovies'; 8 | const routes = async (fastify: FastifyInstance, options: RegisterOptions) => { 9 | await fastify.register(flixhq, { prefix: '/flixhq' }); 10 | await fastify.register(viewasian, { prefix: '/viewasian' }); 11 | await fastify.register(dramacool, { prefix: '/dramacool' }); 12 | await fastify.register(fmovies, { prefix: '/fmovies' }); 13 | fastify.get('/', async (request: any, reply: any) => { 14 | reply.status(200).send('Welcome to Consumet Movies and TV Shows'); 15 | }); 16 | 17 | fastify.get('/:movieProvider', async (request: FastifyRequest, reply: FastifyReply) => { 18 | const queries: { movieProvider: string; page: number } = { 19 | movieProvider: '', 20 | page: 1, 21 | }; 22 | 23 | queries.movieProvider = decodeURIComponent( 24 | (request.params as { movieProvider: string; page: number }).movieProvider 25 | ); 26 | 27 | queries.page = (request.query as { movieProvider: string; page: number }).page; 28 | 29 | if (queries.page! < 1) queries.page = 1; 30 | 31 | const provider = PROVIDERS_LIST.MOVIES.find( 32 | (provider: any) => provider.toString.name === queries.movieProvider 33 | ); 34 | 35 | try { 36 | if (provider) { 37 | reply.redirect(`/movies/${provider.toString.name}`); 38 | } else { 39 | reply 40 | .status(404) 41 | .send({ message: 'Page not found, please check the providers list.' }); 42 | } 43 | } catch (err) { 44 | reply 45 | .status(500) 46 | .send({ message: 'Something went wrong. Please try again later.' }); 47 | } 48 | }); 49 | }; 50 | 51 | export default routes; 52 | -------------------------------------------------------------------------------- /consumet-api/src/routes/movies/viewasian.ts: -------------------------------------------------------------------------------- 1 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 2 | import { MOVIES } from '@consumet/extensions'; 3 | import { StreamingServers } from '@consumet/extensions/dist/models'; 4 | 5 | const routes = async (fastify: FastifyInstance, options: RegisterOptions) => { 6 | const viewAsian = new MOVIES.ViewAsian(); 7 | 8 | fastify.get('/', (_, rp) => { 9 | rp.status(200).send({ 10 | intro: 11 | "Welcome to the viewAsian provider: check out the provider's website @ https://viewAsian.to/", 12 | routes: ['/:query', '/info', '/watch'], 13 | documentation: 'https://docs.consumet.org/#tag/viewAsian', 14 | }); 15 | }); 16 | 17 | fastify.get('/:query', async (request: FastifyRequest, reply: FastifyReply) => { 18 | const query = decodeURIComponent((request.params as { query: string }).query); 19 | 20 | const page = (request.query as { page: number }).page; 21 | 22 | const res = await viewAsian.search(query, page); 23 | 24 | reply.status(200).send(res); 25 | }); 26 | 27 | fastify.get('/info', async (request: FastifyRequest, reply: FastifyReply) => { 28 | const id = (request.query as { id: string }).id; 29 | 30 | if (typeof id === 'undefined') 31 | return reply.status(400).send({ 32 | message: 'id is required', 33 | }); 34 | 35 | try { 36 | const res = await viewAsian 37 | .fetchMediaInfo(id) 38 | .catch((err) => reply.status(404).send({ message: err })); 39 | 40 | reply.status(200).send(res); 41 | } catch (err) { 42 | reply.status(500).send({ 43 | message: 44 | 'Something went wrong. Please try again later. or contact the developers.', 45 | }); 46 | } 47 | }); 48 | 49 | fastify.get('/watch', async (request: FastifyRequest, reply: FastifyReply) => { 50 | const episodeId = (request.query as { episodeId: string }).episodeId; 51 | const server = (request.query as { server: StreamingServers }).server; 52 | 53 | if (typeof episodeId === 'undefined') 54 | return reply.status(400).send({ message: 'episodeId is required' }); 55 | 56 | if (server && !Object.values(StreamingServers).includes(server)) 57 | return reply.status(400).send({ message: 'Invalid server query' }); 58 | 59 | try { 60 | const res = await viewAsian 61 | .fetchEpisodeSources(episodeId, server) 62 | .catch((err) => reply.status(404).send({ message: 'Media Not found.' })); 63 | 64 | reply.status(200).send(res); 65 | } catch (err) { 66 | reply 67 | .status(500) 68 | .send({ message: 'Something went wrong. Please try again later.' }); 69 | } 70 | }); 71 | }; 72 | 73 | export default routes; 74 | -------------------------------------------------------------------------------- /consumet-api/src/routes/news/ann.ts: -------------------------------------------------------------------------------- 1 | import { NEWS, Topics } from '@consumet/extensions'; 2 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 3 | 4 | const routes = async (fastify: FastifyInstance, options: RegisterOptions) => { 5 | const ann = new NEWS.ANN(); 6 | 7 | fastify.get('/', (_, rp) => { 8 | rp.status(200).send({ 9 | intro: 10 | "Welcome to the Anime News Network provider: check out the provider's website @ https://www.animenewsnetwork.com/", 11 | routes: ['/recent-feeds', '/info'], 12 | documentation: 'https://docs.consumet.org/#tag/animenewsnetwork', 13 | }); 14 | }); 15 | 16 | fastify.get('/recent-feeds', async (req: FastifyRequest, reply: FastifyReply) => { 17 | let { topic } = req.query as { topic?: Topics }; 18 | 19 | try { 20 | const feeds = await ann.fetchNewsFeeds(topic); 21 | reply.status(200).send(feeds); 22 | } catch (e) { 23 | reply.status(500).send({ 24 | message: (e as Error).message, 25 | }); 26 | } 27 | }); 28 | 29 | fastify.get('/info', async (req: FastifyRequest, reply: FastifyReply) => { 30 | const { id } = req.query as { id: string }; 31 | 32 | if (typeof id === 'undefined') 33 | return reply.status(400).send({ 34 | message: 'id is required', 35 | }); 36 | 37 | try { 38 | const info = await ann.fetchNewsInfo(id); 39 | reply.status(200).send(info); 40 | } catch (error) { 41 | reply.status(500).send({ 42 | message: (error as Error).message, 43 | }); 44 | } 45 | }); 46 | }; 47 | 48 | export default routes; 49 | -------------------------------------------------------------------------------- /consumet-api/src/routes/news/index.ts: -------------------------------------------------------------------------------- 1 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 2 | 3 | import ann from './ann'; 4 | 5 | const routes = async (fastify: FastifyInstance, options: RegisterOptions) => { 6 | // register news routes 7 | fastify.register(ann, { prefix: '/ann' }); 8 | 9 | //default route message 10 | fastify.get('/', async (_request: FastifyRequest, reply: FastifyReply) => { 11 | reply.status(200).send('Welcome to Consumet News'); 12 | }); 13 | }; 14 | 15 | export default routes; 16 | -------------------------------------------------------------------------------- /consumet-api/src/utils/bilibili.ts: -------------------------------------------------------------------------------- 1 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 2 | import axios from 'axios'; 3 | import { BilibiliExtractor } from '@consumet/extensions/dist/utils'; 4 | 5 | class BilibiliUtilis { 6 | private apiUrl = 'https://api.bilibili.tv/intl/gateway/web'; 7 | private sgProxy = 'https://cors.proxy.consumet.org'; 8 | 9 | constructor(private locale: string = 'en_US') {} 10 | returnDASH = async (fastify: FastifyInstance, options: RegisterOptions) => { 11 | fastify.get( 12 | '/bilibili/playurl', 13 | async (request: FastifyRequest, reply: FastifyReply) => { 14 | const episodeId = (request.query as { episode_id: string }).episode_id; 15 | 16 | if (typeof episodeId === 'undefined') 17 | return reply.status(400).send({ message: 'episodeId is required' }); 18 | 19 | try { 20 | // const ss = await axios.get( 21 | // `${this.sgProxy}/${this.apiUrl}/playurl?s_locale=${this.locale}&platform=web&ep_id=${episodeId}`, 22 | // { headers: { cookie: String(process.env.BILIBILI_COOKIE) } } 23 | // ); 24 | const ss = await axios.get( 25 | `https://kaguya.app/server/source?episode_id=${episodeId}&source_media_id=1&source_id=bilibili`, 26 | { headers: { cookie: String(process.env.BILIBILI_COOKIE) } } 27 | ); 28 | //kaguya.app/server/source?episode_id=11560397&source_media_id=1&source_id=bilibili 29 | //console.log(ss.data); 30 | if (!ss.data.sources) 31 | return reply.status(404).send({ message: 'No sources found' }); 32 | 33 | const dash = await axios.get(ss.data.sources[0].file); 34 | //const dash = new BilibiliExtractor().toDash(ss.data.data.playurl); 35 | 36 | return reply.status(200).send(dash.data); 37 | } catch (err) { 38 | console.log(err); 39 | reply 40 | .status(500) 41 | .send({ message: 'Something went wrong. Contact developer for help.' }); 42 | } 43 | } 44 | ); 45 | }; 46 | 47 | returnVTT = async (fastify: FastifyInstance, options: RegisterOptions) => { 48 | fastify.get( 49 | '/bilibili/subtitle', 50 | async (request: FastifyRequest, reply: FastifyReply) => { 51 | const url = (request.query as { url: string }).url; 52 | try { 53 | const jsonVtt = await axios.get(url); 54 | const vtt = new VTT(); 55 | jsonVtt.data.body.map((subtitle: any) => { 56 | vtt.add(subtitle.from, subtitle.to, subtitle.content); 57 | }); 58 | 59 | reply.status(200).send(vtt.toString()); 60 | } catch (err) { 61 | reply 62 | .status(500) 63 | .send({ message: 'Something went wrong. Contact developer for help.' }); 64 | } 65 | } 66 | ); 67 | }; 68 | } 69 | 70 | class VTT { 71 | counter = 0; 72 | content = 'WEBVTT\r\n'; 73 | 74 | private pad = (num: any) => { 75 | if (num < 10) { 76 | return '0' + num; 77 | } 78 | 79 | return num; 80 | }; 81 | 82 | private secondsToTime = (sec: any) => { 83 | if (typeof sec !== 'number') { 84 | throw new Error('Invalid type: expected number'); 85 | } 86 | 87 | var seconds = (sec % 60).toFixed(3); 88 | var minutes = Math.floor(sec / 60) % 60; 89 | var hours = Math.floor(sec / 60 / 60); 90 | 91 | return this.pad(hours) + ':' + this.pad(minutes) + ':' + this.pad(seconds); 92 | }; 93 | 94 | add = (from: any, to: any, lines: any, settings?: any) => { 95 | ++this.counter; 96 | lines = lines.constructor === Array ? lines : [lines]; 97 | 98 | this.content += 99 | '\r\n' + 100 | this.counter + 101 | '\r\n' + 102 | this.secondsToTime(from) + 103 | ' --> ' + 104 | this.secondsToTime(to) + 105 | (settings ? ' ' + settings : '') + 106 | '\r\n'; 107 | 108 | lines.forEach((line: any) => { 109 | this.content += line + '\r\n'; 110 | }); 111 | }; 112 | 113 | toString = () => { 114 | return this.content; 115 | }; 116 | } 117 | 118 | export default BilibiliUtilis; 119 | -------------------------------------------------------------------------------- /consumet-api/src/utils/cache.ts: -------------------------------------------------------------------------------- 1 | import { Redis } from 'ioredis'; 2 | /* eslint-disable import/no-anonymous-default-export */ 3 | 4 | /* 5 | TLDR; " Expires " is seconds based. for example 60*60 would = 3600 (an hour) 6 | */ 7 | 8 | const fetch = async (redis: Redis, key: string, fetcher: () => T, expires: number) => { 9 | const existing = await get(redis, key); 10 | if (existing !== null) return existing; 11 | 12 | return set(redis, key, fetcher, expires); 13 | }; 14 | 15 | const get = async (redis: Redis, key: string): Promise => { 16 | console.log('GET: ' + key); 17 | const value = await redis.get(key); 18 | if (value === null) return null as any; 19 | 20 | return JSON.parse(value); 21 | }; 22 | 23 | const set = async (redis: Redis, key: string, fetcher: () => T, expires: number) => { 24 | console.log(`SET: ${key}, EXP: ${expires}`); 25 | const value = await fetcher(); 26 | await redis.set(key, JSON.stringify(value), 'EX', expires); 27 | return value; 28 | }; 29 | 30 | const del = async (redis: Redis, key: string) => { 31 | await redis.del(key); 32 | }; 33 | 34 | export default { fetch, set, get, del }; 35 | -------------------------------------------------------------------------------- /consumet-api/src/utils/image-proxy.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 3 | 4 | class ImageProxy { 5 | #image = undefined; 6 | private getImage = async (url: string, options: {}): Promise => { 7 | const data = await axios.get(url, { 8 | responseType: 'arraybuffer', 9 | ...options, 10 | }); 11 | return data.data; 12 | }; 13 | 14 | public getImageProxy = async (fastify: FastifyInstance, options: RegisterOptions) => { 15 | fastify.get('/image-proxy', async (request: FastifyRequest, reply: FastifyReply) => { 16 | const { url } = request.query as { url: string }; 17 | // get headers from the query 18 | const { referer } = request.query as { referer: string }; 19 | 20 | if (!url) { 21 | reply.status(400).send('No URL provided'); 22 | return; 23 | } 24 | 25 | // return the image 26 | reply.header('Content-Type', 'image/jpeg'); 27 | reply.header('Cache-Control', 'public, max-age=31536000'); 28 | reply.header('Access-Control-Allow-Origin', '*'); 29 | reply.header('Access-Control-Allow-Methods', 'GET'); 30 | reply.header( 31 | 'Access-Control-Allow-Headers', 32 | 'Origin, X-Requested-With, Content-Type, Accept' 33 | ); 34 | reply.header('Access-Control-Allow-Credentials', 'true'); 35 | reply.header('Referer', referer); 36 | reply.send(await this.getImage(url, { headers: { referer } })); 37 | }); 38 | }; 39 | } 40 | 41 | export default ImageProxy; 42 | -------------------------------------------------------------------------------- /consumet-api/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 2 | import { PROVIDERS_LIST } from '@consumet/extensions'; 3 | 4 | import RapidCloud from './rapid-cloud'; 5 | import BilibiliUtilis from './bilibili'; 6 | import ImageProxy from './image-proxy'; 7 | import M3U8Proxy from './m3u8-proxy'; 8 | import Providers from './providers'; 9 | 10 | const routes = async (fastify: FastifyInstance, options: RegisterOptions) => { 11 | //await fastify.register(new RapidCloud().returnSID); 12 | await fastify.register(new BilibiliUtilis('en_US').returnDASH); 13 | await fastify.register(new BilibiliUtilis('en_US').returnVTT); 14 | await fastify.register(new ImageProxy().getImageProxy); 15 | await fastify.register(new M3U8Proxy().getM3U8Proxy); 16 | await fastify.register(new Providers().getProviders); 17 | 18 | fastify.get('/', async (request: any, reply: any) => { 19 | reply.status(200).send('Welcome to Consumet Utils!'); 20 | }); 21 | }; 22 | 23 | export default routes; 24 | -------------------------------------------------------------------------------- /consumet-api/src/utils/m3u8-proxy.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosRequestConfig } from 'axios'; 2 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 3 | 4 | class M3U8Proxy { 5 | private getM3U8 = async (url: string, options: AxiosRequestConfig): Promise => { 6 | const data = await axios.get(url, options); 7 | 8 | return data.data; 9 | }; 10 | 11 | private toQueryString = (obj: any) => { 12 | const parts = []; 13 | for (const i in obj) { 14 | if (obj[i]) { 15 | parts.push(encodeURIComponent(i) + '=' + encodeURIComponent(obj[i])); 16 | } 17 | } 18 | return parts.join('&'); 19 | }; 20 | 21 | public getM3U8Proxy = async (fastify: FastifyInstance, options: RegisterOptions) => { 22 | fastify.get('/m3u8-proxy/*', async (request: FastifyRequest, reply: FastifyReply) => { 23 | // split params 24 | const params = (request.params as any)['*'].split('/'); 25 | const queries = request.query as any; 26 | console.log('params', params); 27 | 28 | // last element is the url 29 | const url = params.pop(); 30 | // decode safe base64 31 | const decodedUrl = Buffer.from(url, 'base64').toString('ascii'); 32 | 33 | const domain = Buffer.from(params.join(''), 'base64').toString('ascii'); 34 | 35 | // queries to object 36 | const data = await this.getM3U8( 37 | decodedUrl.startsWith('https') 38 | ? decodedUrl 39 | : domain + url + '?' + this.toQueryString(queries), 40 | { 41 | headers: { 42 | 'User-Agent': 43 | 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.35', 44 | watchsb: 'streamsb', 45 | }, 46 | } 47 | ); 48 | 49 | //const decodedData = Buffer.from(data, 'binary').toString('utf8'); 50 | reply.header( 51 | 'Content-Type', 52 | decodedUrl.startsWith('https') ? 'application/vnd.apple.mpegurl' : 'video/mp2t' 53 | ); 54 | reply.header('Access-Control-Allow-Origin', '*'); 55 | reply.header('Access-Control-Allow-Headers', '*'); 56 | reply.header('Access-Control-Allow-Methods', '*'); 57 | 58 | reply.send(data); 59 | }); 60 | 61 | fastify.get('/m3u8/*', async (request: FastifyRequest, reply: FastifyReply) => { 62 | const params = (request.params as any)['*']; 63 | 64 | var url = Buffer.from(params, 'base64').toString('utf8'); 65 | try { 66 | var req = await axios.get(url, { 67 | headers: { 68 | 'User-Agent': 69 | 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36', 70 | }, 71 | }); 72 | 73 | const pattern = new RegExp('https://', 'g'); 74 | const final = req.data 75 | .toString() 76 | .replace(pattern, `https://cors.proxy.consumet.org/https://`); 77 | 78 | reply 79 | .header('Content-Type', 'application/vnd.apple.mpegurl') 80 | .header('Content-Disposition', 'attachment; filename=stream.m3u8') 81 | .status(200) 82 | .send(Buffer.from(final)); 83 | } catch (error) { 84 | reply.status(400).send(error); 85 | } 86 | }); 87 | }; 88 | } 89 | 90 | export default M3U8Proxy; 91 | -------------------------------------------------------------------------------- /consumet-api/src/utils/providers.ts: -------------------------------------------------------------------------------- 1 | import { PROVIDERS_LIST } from '@consumet/extensions'; 2 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 3 | 4 | type ProvidersRequest = FastifyRequest<{ 5 | Querystring: { type: keyof typeof PROVIDERS_LIST }; 6 | }>; 7 | 8 | export default class Providers { 9 | public getProviders = async (fastify: FastifyInstance, options: RegisterOptions) => { 10 | fastify.get( 11 | '/providers', 12 | { 13 | preValidation: (request, reply, done) => { 14 | const { type } = request.query; 15 | 16 | const providerTypes = Object.keys(PROVIDERS_LIST).map((element) => element); 17 | 18 | if (type === undefined) { 19 | reply.status(400); 20 | done( 21 | new Error( 22 | 'Type must not be empty. Available types: ' + providerTypes.toString() 23 | ) 24 | ); 25 | } 26 | 27 | if (!providerTypes.includes(type)) { 28 | reply.status(400); 29 | done(new Error('Type must be either: ' + providerTypes.toString())); 30 | } 31 | 32 | done(undefined); 33 | }, 34 | }, 35 | async (request: ProvidersRequest, reply: FastifyReply) => { 36 | const { type } = request.query; 37 | const providers = Object.values(PROVIDERS_LIST[type]).sort((one, two) => 38 | one.name.localeCompare(two.name) 39 | ); 40 | reply.status(200).send(providers.map((element) => element.toString)); 41 | } 42 | ); 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /consumet-api/src/utils/rapid-cloud.ts: -------------------------------------------------------------------------------- 1 | import ReconnectingWebSocket from 'reconnecting-websocket'; 2 | import { WebSocket } from 'ws'; 3 | import { FastifyRequest, FastifyReply, FastifyInstance, RegisterOptions } from 'fastify'; 4 | 5 | class RapidCloud { 6 | private readonly baseUrl = 7 | 'wss://ws1.rapid-cloud.co/socket.io/?EIO=4&transport=websocket'; 8 | 9 | private socket: ReconnectingWebSocket; 10 | 11 | public sId = undefined; 12 | constructor() { 13 | this.socket = new ReconnectingWebSocket(this.baseUrl, undefined, { 14 | WebSocket: WebSocket, 15 | }); 16 | try { 17 | this.socket.onopen = () => { 18 | this.socket.send('40'); 19 | }; 20 | 21 | this.socket.onmessage = ({ data }) => { 22 | if (data?.startsWith('40')) { 23 | this.sId = JSON.parse(data.split('40')[1]).sid; 24 | } else if (data == '2') { 25 | console.log("recieved pong from RapidCloud's server"); 26 | this.socket.send('3'); 27 | } 28 | }; 29 | 30 | this.socket.onerror = (err) => { 31 | console.error('Websocket error: ', err); 32 | }; 33 | 34 | setInterval(() => { 35 | this.socket.send('3'); 36 | }, 25000); 37 | 38 | setInterval(() => { 39 | this.socket.reconnect(); 40 | }, 7200000); 41 | } catch (err) { 42 | console.log(err); 43 | } 44 | } 45 | 46 | returnSID = async (fastify: FastifyInstance, options: RegisterOptions) => { 47 | fastify.get('/rapid-cloud', async (request: FastifyRequest, reply: FastifyReply) => { 48 | reply.status(200).send(this.sId); 49 | }); 50 | }; 51 | } 52 | 53 | export default RapidCloud; 54 | -------------------------------------------------------------------------------- /consumet-api/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "builds": [ 4 | { 5 | "src": "/src/main.ts", 6 | "use": "@vercel/node" 7 | } 8 | ], 9 | "routes": [ 10 | { 11 | "src": "/(.*)", 12 | "dest": "/src/main.ts" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /consumet_start.bat: -------------------------------------------------------------------------------- 1 | cd ./consumet-api 2 | npm start -------------------------------------------------------------------------------- /consumet_start.sh: -------------------------------------------------------------------------------- 1 | cd ./consumet-api && npm start 2 | -------------------------------------------------------------------------------- /db.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('sqlite3'); 2 | var mkdirp = require('mkdirp'); 3 | var crypto = require('crypto'); 4 | 5 | mkdirp.sync('var/db'); 6 | 7 | var db = new sqlite3.Database('var/db/users.db'); 8 | 9 | db.serialize(function() { 10 | db.run("CREATE TABLE IF NOT EXISTS users ( \ 11 | email TEXT UNIQUE, \ 12 | id INTEGER PRIMARY KEY, \ 13 | username TEXT UNIQUE, \ 14 | email_verified TEXT, \ 15 | hashed_password BLOB, \ 16 | salt BLOB, \ 17 | watchlist TEXT, \ 18 | currentlyWatchingTitle TEXT, \ 19 | currentlyWatchingTime TEXT, \ 20 | currentlyWatchingThumbnail TEXT \ 21 | )"); 22 | }); 23 | 24 | module.exports = db; 25 | -------------------------------------------------------------------------------- /docs/How to create an instance in Docker Compose: -------------------------------------------------------------------------------- 1 | 2 | 3 | First Download the Git Repo. 4 | `git clone https://github.com/wearrrrr/HaiKei.git` 5 | Then create a folder called `cache` for redis. 6 | `mkdir cache` 7 | 8 | Then create a file called `docker-compose.yaml`. 9 | `nano docker-compose.yaml` 10 | This file should contain the following lines: 11 | 12 | ``` 13 | version: "3" 14 | services: 15 | node: 16 | image: "node:18" 17 | user: "root" 18 | working_dir: /home/node/app/ 19 | environment: 20 | - NODE_ENV=production 21 | volumes: 22 | - ./HaiKei:/home/node/app 23 | ports: 24 | - "3002:3000" 25 | command: bash -c "npm install -g npm@9.6.0 && npm install -g yarn --force && yarn && node server.js" 26 | restart: unless-stopped 27 | redis: 28 | image: "redis:latest" 29 | restart: unless-stopped 30 | volumes: 31 | - ./cache:/data 32 | command: redis-server --save 20 1 --loglevel warning 33 | environment: 34 | - ALLOW_EMPTY_PASSWORD=yes 35 | ``` 36 | ### Configuring HaiKei 37 | Before starting it up, please be sure to configure HaiKei correctly! 38 | In the HaiKei Directory is a file called .env.example. 39 | Rename it to .env 40 | `mv HaiKei/.env.example HaiKei/.env` 41 | Then edit the .env. 42 | `nano HaiKei/.env` 43 | 44 | Change the variables `AUTH_SECRET` `JWT_SECRET` to be equal to a strong/long string like `C6JE8DA0hqP18hLQh72u9Fqro`. 45 | Add a mail server if you want/need it. 46 | Change `REDIS_URL` to be equal to `redis://redis:6379`. 47 | You can also change the website URL in this file but it is not necessary. 48 | In the end the `.env` file could look like this: 49 | 50 | ``` 51 | 52 | WEBSITE_URL=http://localhost:3000 53 | PORT=3000 54 | AUTH_SECRET=asdasdasdasdasdasdasdasd 55 | JWT_SECRET=EXAMPLESECRETasdasdasdasd 56 | JWT_MSG=basic JWT message 57 | SMTP_PROVIDER= 58 | SMTP_PORT= 59 | MAIL_USERNAME= 60 | MAIL_PASSWORD= 61 | REDIS_URL=redis://redis:6379 62 | 63 | 64 | 65 | ``` 66 | ### Starting HaiKei 67 | 68 | You can now start it up by executing `docker compose up`. 69 | Required for it to work are the packages: `docker.io docker-compose docker-compose-plugin docker-ce` 70 | 71 | ### Making Changes to HaiKei 72 | 73 | Before changing anything you first have to take your instance of HaiKei offline: `docker compose down` 74 | Then you can make changes to the .env or docker-compose.yaml. 75 | After making your changes you can start your instance of HaiKei up again by executing: `docker compose up` 76 | 77 | ### You now have your own instance of HaiKei! 78 | 79 | If you want to host your own API, there's a docker container for that as well at https://hub.docker.com/r/riimuru/consumet-api 80 | You can also add that container to your docker-compose.yaml. 81 | Just be sure to add the ports for the docker container and change the config.js at ./HaiKei/config.js to contain the correct consumet api. 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /docs/create_an_instance.md: -------------------------------------------------------------------------------- 1 | # Create an instance of HaiKei 2 | 3 | Creating a personal instance of HaiKei is really simple as long as you have basic nodeJS knowledge 4 | 5 | ## Prerequisites: 6 | 7 | Prerequisites for hosting a HaiKei instance are 8 | ``` 9 | 1. NodeJS / npm 10 | 2. Yarn (node module) 11 | 3. Redis (or Memurai for Windows users.) 12 | 4. Git 13 | 5. Patience (optional) 14 | 6. Incredibly basic terminal knowledge 15 | ``` 16 | 17 | ## Putting it all together 18 | 19 | First run `git clone https://github.com/wearrrrr/HaiKei` to create a local copy of the repository
20 | Then run `cd HaiKei` to get into the HaiKei directory.
21 | Then run `npm install -g yarn` if you haven't already
22 | Then `cd consumet-api`
23 | Now simply `yarn` to install all the node modules for consumet
24 | Then `cd ../` to go back one directory and run `yarn` again to install the HaiKei node modules.
25 | 26 | Almost there!
27 | 28 | Now finally you can run `npm start` and the script will automatically start Memurai or Redis (platform dependent), consumet-api (included in the repo) and the HaiKei frontend.
29 | 30 | Enjoy your private instance of HaiKei! (^///^)
31 | 32 | Don't you feel proud of yourself?
33 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const cp = require('child_process') 2 | const os = require('os') 3 | const chalk = require('chalk') 4 | 5 | 6 | // Chalk template for console output. 7 | const error = chalk.bold.red; 8 | const success = chalk.bold.green; 9 | 10 | console.log(success("Starting Web Server...")) 11 | 12 | if (os.platform == "win32") { 13 | console.log("platform is " + os.platform) 14 | console.log(success("Starting Consumet...")) 15 | let consumetServer = cp.spawn('consumet_start.bat') 16 | consumetServer.stdout.on('data', function (data) { 17 | console.log(data.toString()); 18 | }); 19 | 20 | consumetServer.stderr.on('data', function (data) { 21 | console.log(data.toString()); 22 | }); 23 | 24 | console.log(success("Starting Redis...")) 25 | 26 | try { 27 | cp.exec('memurai') 28 | } catch { 29 | error("Failed to start memurai! Is it installed?") 30 | } 31 | try { 32 | console.log(success("Starting frontend...")) 33 | let webserver = cp.spawn('webserver.bat') 34 | webserver.stdout.on('data', (data) => {console.log(data.toString())}); 35 | } catch { 36 | console.log(error("Failed to start frontend! Try running yarn again.")) 37 | } 38 | } 39 | if (os.platform == 'darwin' || os.platform == 'linux') { 40 | console.log("platform is " + os.platform) 41 | console.log(success("Starting Consumet...")) 42 | let consumetServer = cp.spawn('./consumet_start.sh') 43 | consumetServer.stdout.on('data', function (data) { 44 | console.log(data.toString()); 45 | }); 46 | 47 | consumetServer.stderr.on('data', function (data) { 48 | console.log(data.toString()); 49 | }); 50 | let webserver = cp.spawn('./webserver.sh') 51 | webserver.stdout.on('data', (data) => {console.log(data.toString())}); 52 | } 53 | if (os.platform == "linux" ) { 54 | console.log(success("Starting Redis...")) 55 | try { 56 | cp.spawn("redis-server") 57 | } catch { 58 | console.log(error("Failed to start redis! Is it installed?")) 59 | } 60 | 61 | } 62 | 63 | 64 | // let aioServer = cp.fork("bin.js", { cwd: "./all-in-one/"}) 65 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "haikei", 3 | "version": "1.2.0", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node index.js", 8 | "dev": "nodemon server.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "@consumet/extensions": "1.5.6", 15 | "@sendgrid/mail": "^8.1.2", 16 | "art-template": "^4.13.2", 17 | "axios": "^1.7.4", 18 | "bcrypt": "^5.1.0", 19 | "chalk": "^4.1.2", 20 | "config": "^3.3.8", 21 | "connect-flash": "^0.1.1", 22 | "connect-sqlite3": "^0.9.13", 23 | "cookie-parser": "^1.4.6", 24 | "cors": "^2.8.5", 25 | "csurf": "^1.11.0", 26 | "datauri": "^4.1.0", 27 | "dotenv": "^16.0.3", 28 | "ejs": "^3.1.10", 29 | "eta": "^2.0.1", 30 | "express": "^4.20.0", 31 | "express-art-template": "^1.0.1", 32 | "express-limit": "^0.1.0", 33 | "express-session": "^1.17.3", 34 | "express-validator": "^6.15.0", 35 | "helmet": "^6.0.1", 36 | "jsonwebtoken": "^9.0.0", 37 | "leo-profanity": "^1.7.0", 38 | "mkdirp": "^1.0.4", 39 | "mongoose": "^8.0.0", 40 | "multer": "^1.4.5-lts.1", 41 | "node-fetch": "^2.6.7", 42 | "nodemailer": "^6.9.9", 43 | "passport": "^0.6.0", 44 | "passport-local": "^1.0.0", 45 | "pluralize": "^8.0.0", 46 | "redis": "^4.6.2", 47 | "response-time": "^2.3.2", 48 | "socket.io": "^4.6.2", 49 | "uuid": "^9.0.0" 50 | }, 51 | "devDependencies": { 52 | "nodemon": "^2.0.20" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /public/404.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 404! 8 | 9 | 10 | 11 | 12 |
13 |

The selected content couldn't be found on the server!

14 |

Try again later or make an issue on the GitHub Repo

15 |
16 | 19 | 49 | 50 | -------------------------------------------------------------------------------- /public/HLS_NOT_SUPPORTED.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wearrrrr/HaiKei/ab90f53b5ee085fa2d2fea4b6cfc7a0ee7da9a3f/public/HLS_NOT_SUPPORTED.mp4 -------------------------------------------------------------------------------- /public/assets/images/pick-home.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /public/assets/images/pick-movies.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 15 | 16 | -------------------------------------------------------------------------------- /public/assets/images/pick-popular.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 10 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /public/assets/images/pick-show.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 11 | 12 | -------------------------------------------------------------------------------- /public/assets/search.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/contribute.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | HaiKei needs YOU! 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | <%- include('./templates/navbar.ejs') %> 18 | <%- include("./templates/sidenav.ejs") %> 19 | 20 |

HaiKei needs developers!

21 | 22 |

Have development experience? Fork the repo and open a pull request Here!

23 | 24 |

Have an issue/bug with the website? Open a new issue Here!

25 | 26 | 27 |

\( ̄▽ ̄)/

28 | 29 | <%- include('./templates/new-footer.ejs') %> 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /public/css/404.css: -------------------------------------------------------------------------------- 1 | .text-wrapper { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | flex-direction: column; 6 | } 7 | .link-wrapper { 8 | display: flex; 9 | justify-content: center; 10 | align-items: center; 11 | flex-direction: column; 12 | } 13 | .main-text { 14 | font-family: 'Inter', sans-serif; 15 | color: white; 16 | text-align: center; 17 | } 18 | .sub-text { 19 | font-family: 'Inter', sans-serif; 20 | color: white; 21 | text-align: center; 22 | } 23 | .github-link { 24 | color: white; 25 | text-decoration: underline !important; 26 | } 27 | .github-link:hover { 28 | color: rgb(172, 172, 172); 29 | } 30 | .return-home-link { 31 | color: white; 32 | font-family: 'Inter', sans-serif; 33 | margin-top: 25px; 34 | border-radius: 15px; 35 | padding: 10px; 36 | background-color: #51D3FF; 37 | border: 3px solid white; 38 | transition: 250ms ease-in-out; 39 | } 40 | .return-home-link:hover { 41 | background-color: #7fddfc; 42 | } -------------------------------------------------------------------------------- /public/css/404.min.css: -------------------------------------------------------------------------------- 1 | .text-wrapper{display:flex;justify-content:center;align-items:center;flex-direction:column}.link-wrapper{display:flex;justify-content:center;align-items:center;flex-direction:column}.main-text{font-family:Inter,sans-serif;color:#fff;text-align:center}.sub-text{font-family:Inter,sans-serif;color:#fff;text-align:center}.github-link{color:#fff;text-decoration:underline!important}.github-link:hover{color:#acacac}.return-home-link{color:#fff;font-family:Inter,sans-serif;margin-top:25px;border-radius:15px;padding:10px;background-color:#51d3ff;border:3px solid #fff;transition:250ms ease-in-out}.return-home-link:hover{background-color:#7fddfc} -------------------------------------------------------------------------------- /public/css/base.css: -------------------------------------------------------------------------------- 1 | hr { 2 | margin: 20px 0; 3 | border: 0; 4 | border-top: 1px dashed #c5c5c5; 5 | border-bottom: 1px dashed #f7f7f7; 6 | } 7 | 8 | .learn a { 9 | font-weight: normal; 10 | text-decoration: none; 11 | color: #b83f45; 12 | } 13 | 14 | .learn a:hover { 15 | text-decoration: underline; 16 | color: #787e7e; 17 | } 18 | 19 | .info { 20 | display: flex; 21 | align-items: center; 22 | justify-content: center; 23 | flex-direction: column; 24 | } 25 | 26 | .progress-bar { 27 | border-radius: 4px; 28 | box-shadow: inset 0 0.5em 0.5em rgba(0,0,0,0.05); 29 | height: 20px; 30 | overflow: hidden; 31 | position: relative; 32 | transform: translateZ(0); 33 | width: 3%; 34 | transition: width 250ms ease-in-out; 35 | } 36 | 37 | .progress-bar--info { 38 | background-color: #5bc0de; 39 | } 40 | 41 | #togglePassword { 42 | margin-left: -30px; 43 | cursor: pointer; 44 | } 45 | 46 | .strength-wrapper { 47 | height: 20px; 48 | border-radius: 6px; 49 | border: 2px solid #ccc; 50 | margin-top: 10px; 51 | } 52 | 53 | .learn h3, 54 | .learn h4, 55 | .learn h5 { 56 | margin: 10px 0; 57 | font-weight: 500; 58 | line-height: 1.2; 59 | color: #000; 60 | } 61 | 62 | .learn h3 { 63 | font-size: 24px; 64 | } 65 | 66 | .learn h4 { 67 | font-size: 18px; 68 | } 69 | 70 | .learn h5 { 71 | margin-bottom: 0; 72 | font-size: 14px; 73 | } 74 | 75 | .learn ul { 76 | padding: 0; 77 | margin: 0 0 30px 25px; 78 | } 79 | 80 | .learn li { 81 | line-height: 20px; 82 | } 83 | 84 | .learn p { 85 | font-size: 15px; 86 | font-weight: 300; 87 | line-height: 1.3; 88 | margin-top: 0; 89 | margin-bottom: 0; 90 | } 91 | 92 | #issue-count { 93 | display: none; 94 | } 95 | 96 | .quote { 97 | border: none; 98 | margin: 20px 0 60px; 99 | } 100 | 101 | .quote p { 102 | font-style: italic; 103 | } 104 | 105 | .quote p:before { 106 | content: '“'; 107 | font-size: 50px; 108 | opacity: .15; 109 | position: absolute; 110 | top: -20px; 111 | left: 3px; 112 | } 113 | 114 | .quote p:after { 115 | content: '”'; 116 | font-size: 50px; 117 | opacity: .15; 118 | position: absolute; 119 | bottom: -42px; 120 | right: 3px; 121 | } 122 | 123 | .quote footer { 124 | position: absolute; 125 | bottom: -40px; 126 | right: 0; 127 | } 128 | 129 | .quote footer img { 130 | border-radius: 3px; 131 | } 132 | 133 | .quote footer a { 134 | margin-left: 5px; 135 | vertical-align: middle; 136 | } 137 | 138 | .speech-bubble { 139 | position: relative; 140 | padding: 10px; 141 | background: rgba(0, 0, 0, .04); 142 | border-radius: 5px; 143 | } 144 | 145 | .speech-bubble:after { 146 | content: ''; 147 | position: absolute; 148 | top: 100%; 149 | right: 30px; 150 | border: 13px solid transparent; 151 | border-top-color: rgba(0, 0, 0, .04); 152 | } 153 | 154 | .learn-bar > .learn { 155 | position: absolute; 156 | width: 272px; 157 | top: 8px; 158 | left: -300px; 159 | padding: 10px; 160 | border-radius: 5px; 161 | background-color: rgba(255, 255, 255, .6); 162 | transition-property: left; 163 | transition-duration: 500ms; 164 | } 165 | 166 | @media (min-width: 899px) { 167 | .learn-bar { 168 | width: auto; 169 | padding-left: 300px; 170 | } 171 | 172 | .learn-bar > .learn { 173 | left: 8px; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /public/css/donate.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Noto Sans", sans-serif; 3 | } 4 | .donate-header-container { 5 | display: flex; 6 | justify-content: center; 7 | align-items: center; 8 | flex-direction: column; 9 | margin-top: 20px; 10 | } 11 | .donation-reasons-container { 12 | display: flex; 13 | justify-content: center; 14 | align-items: center; 15 | flex-direction: column; 16 | } 17 | .donate-links-container { 18 | display: flex; 19 | justify-content: center; 20 | align-items: center; 21 | flex-direction: column; 22 | } 23 | .donate-links-container > .donate-item-wrapper > * { 24 | text-decoration: none; 25 | } 26 | .donate-item-wrapper { 27 | width: 75%; 28 | overflow-wrap: anywhere; 29 | text-align: center; 30 | } 31 | .donate-item-wrapper > h3 { 32 | transition: color 250ms ease-in; 33 | } 34 | .donate-item-wrapper > h3:hover { 35 | color: var(--light-accent-hover); 36 | } 37 | .donation-reasons-container > ul > li { 38 | color: var(--dark-accent); 39 | font-size: 24px; 40 | } 41 | h1 { 42 | color: var(--dark-accent); 43 | font-size: 48px; 44 | } 45 | h2 { 46 | color: var(--dark-accent); 47 | font-size: 36px; 48 | } 49 | h3 { 50 | color: var(--light-accent); 51 | font-size: 24px; 52 | text-decoration: underline; 53 | } 54 | h3 > a { 55 | color: var(--light-accent); 56 | } 57 | h4 { 58 | color: var(--dark-accent); 59 | margin: 0; 60 | margin-bottom: 10px; 61 | } 62 | .donate-header-container > svg { 63 | width: 10%; 64 | fill: var(--light-accent); 65 | } 66 | .donate-hr-separator { 67 | height: 2px; 68 | background-color: var(--dark-accent); 69 | border: none; 70 | width: 100%; 71 | } 72 | .donate-item > i { 73 | position: relative; 74 | top: -1px; 75 | } 76 | .platinum-donator { 77 | color: #A0B2C6; 78 | } 79 | .diamond-donator { 80 | color: var(--brand-color); 81 | } 82 | .gold-donator { 83 | color: #FFD700; 84 | } 85 | .silver-donator { 86 | color: #c0bfbf; 87 | } 88 | .bronze-donator { 89 | color: #bd752d; 90 | } -------------------------------------------------------------------------------- /public/css/genres.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap'); 2 | .genres { 3 | margin-top: 30px !important; 4 | width: 60%; 5 | max-width: 100%; 6 | margin: 0 auto; 7 | align-items: center; 8 | justify-content: center; 9 | display: flex; 10 | text-align: center; 11 | flex-wrap: wrap; 12 | cursor: pointer; 13 | 14 | } 15 | .genre-item { 16 | width: 150px; 17 | padding: 5px; 18 | border: 5px solid rgb(204, 204, 204); 19 | background-color: rgb(71, 71, 71); 20 | border-radius: 10px; 21 | margin: 10px; 22 | transition: 250ms ease-in-out; 23 | } 24 | @media only screen and (max-width: 716px) { 25 | .genre-item { 26 | margin-left: 40px; 27 | } 28 | } 29 | .genre-item:hover { 30 | background-color: rgb(95, 95, 95); 31 | } 32 | .grp-checkbx { 33 | display: none; 34 | } 35 | .genre-item > div > span { 36 | color: white; 37 | font-family: "Inter"; 38 | text-align: center; 39 | } 40 | 41 | .genre-item { 42 | list-style: none; 43 | } 44 | -------------------------------------------------------------------------------- /public/css/login.css: -------------------------------------------------------------------------------- 1 | .prompt { 2 | max-width: 400px; 3 | margin: 50px auto; 4 | padding: 25px; 5 | background: #141414; 6 | border: 1px solid #e6e6e6; 7 | border-radius: 8px; 8 | } 9 | 10 | button.enabled { 11 | display: block; 12 | padding: 10px; 13 | width: 100%; 14 | border-radius: 3px; 15 | background: #d83f45; 16 | font-size: 14px; 17 | font-weight: 700; 18 | color: white; 19 | cursor: pointer; 20 | } 21 | 22 | button.disabled { 23 | display: block; 24 | padding: 10px; 25 | width: 100%; 26 | border-radius: 3px; 27 | background: #f08b8f; 28 | font-size: 14px; 29 | font-weight: 700; 30 | color: white; 31 | } 32 | 33 | a.button { 34 | box-sizing: border-box; 35 | display: block; 36 | padding: 10px; 37 | width: 100%; 38 | border-radius: 3px; 39 | background: #000; 40 | font-size: 14px; 41 | font-weight: 700; 42 | text-align: center; 43 | text-decoration: none; 44 | color: white; 45 | } 46 | 47 | button.enabled:hover { 48 | background-color: #c83f45; 49 | } 50 | 51 | h1 { 52 | color: rgb(255, 255, 255); 53 | margin: 0 0 20px; 54 | padding: 0 0 5px; 55 | font-size: 24px; 56 | font-weight: 500; 57 | } 58 | 59 | h3 { 60 | margin-top: 0; 61 | font-size: 24px; 62 | font-weight: 300; 63 | text-align: center; 64 | color: #b83f45; 65 | } 66 | 67 | form section { 68 | margin: 0 0 20px; 69 | position: relative; /* for password toggle positioning */ 70 | } 71 | 72 | label { 73 | display: block; 74 | margin: 0 0 3px; 75 | font-size: 14px; 76 | font-weight: 500; 77 | color: rgb(255, 255, 255); 78 | } 79 | 80 | input { 81 | box-sizing: border-box; 82 | width: 100%; 83 | padding: 10px; 84 | font-size: 14px; 85 | border: 1px solid #d9d9d9; 86 | border-radius: 5px; 87 | } 88 | 89 | input[type=email]:not(:focus):invalid, 90 | input[type=password]:not(:focus):invalid { 91 | color: red; 92 | outline-color: red; 93 | } 94 | 95 | hr { 96 | border-top: 1px solid #d9d9d9; 97 | border-bottom: none; 98 | } 99 | 100 | p.instructions { 101 | font-weight: 400; 102 | } 103 | 104 | p.help { 105 | color: rgb(255, 255, 255); 106 | text-align: center; 107 | font-weight: 400; 108 | } 109 | 110 | .help > a { 111 | color: rgb(175 175 175); 112 | } 113 | 114 | /* background image by Cole Bemis */ 115 | .messages p { 116 | font-size: 14px; 117 | font-weight: 400; 118 | line-height: 1.3; 119 | color: #d83f45; 120 | padding-left: 20px; 121 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23d83f45' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-alert-circle'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E"); 122 | background-repeat: no-repeat; 123 | background-position: center left; 124 | } 125 | -------------------------------------------------------------------------------- /public/css/m.min.css: -------------------------------------------------------------------------------- 1 | a.twitter_bt{color:#fff;background:#1496e1;padding:7px;border-radius:3px;margin:14px;transition:all .3s}a.twitter_bt:hover{transition:all .3s;background:#087dc0}div.twitter_bt i.fa-twitter{transition:all .3s;font-size:30px;margin-left:12px;width:100%;overflow:hidden}div.twitter_bt i.fa-twitter:hover{transition:all .3s;color:#1496e1}#tora_d{transform:rotate(-90deg);margin-top:-26px;margin-right:24px}@media(min-width:1200px){.col-xl-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width: 25%;}}.col-xl-3{margin-top:2px}img{transition:all.3s}#action-button{z-index:0!important;} -------------------------------------------------------------------------------- /public/css/styles.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["styles.scss","styles.css"],"names":[],"mappings":"AAAQ,mHAAA;AACR;EACE,4BAAA;ACCF;;ADEA;EACE,sBAAA;ACCF;;ADEA;EACE,yCAAA;EACA,UAAA;EACA,yCAAA;EACQ,iCAAA;ACCV;;ADEA;EACE;IACE,UAAA;ECCF;EDCA;IACE,UAAA;ECCF;AACF;ADEA;EACE;IACE,UAAA;ECAF;EDEA;IACE,UAAA;ECAF;AACF;ADEA;EACE,gCAAA;EACA,cAAA;EACA,iBAAA;ACAF;;ADGA;EACE,kBAAA;ACAF;;ADGA;EACE,WAAA;EACA,kBAAA;EACA,UAAA;EACA,MAAA;EACA,SAAA;EACA,WAAA;EACA,gEAAA;ACAF;;ADGA;EACE,aAAA;EACA,kBAAA;EACA,YAAA;ACAF;;ADGA;EACE,kBAAA;EACA,yBAAA;EACA,YAAA;EACA,mBAAA;EACA,yBAAA;ACAF;;ADGA;EACE,yBAAA;EACA,gCAAA;ACAF;;ADGA;EACE,yBAAA;EACA,gCAAA;ACAF;;ADGA;EACE,yBAAA;EACA,gCAAA;ACAF;;ADGA;EACE,eAAA;EACA,YAAA;ACAF;;ADGA;EACE,aAAA;EACA,WAAA;EACA,eAAA;EACA,cAAA;EACA,kBAAA;EACA,MAAA;EACA,OAAA;EACA,QAAA;EACA,yBAAA;EACA,6BAAA;ACAF;;ADGA;EACE,iBAAA;ACAF;;ADGA;EACE,aAAA;EACA,mBAAA;EACA,6BAAA;EACA,eAAA;ACAF;;ADGA;EACE,mBAAA;ACAF;;ADGA;EACE,mBAAA;EACA,kBAAA;ACAF;;ADGA;EACE,eAAA;EACA,6BAAA;ACAF;;ADGA;EACE,qBAAA;EACA,oBAAA;EACA,qBAAA;EACA,cAAA;EACA,oBAAA;EACA,eAAA;EACA,iBAAA;ACAF;;ADGA;EACE,cAAA;ACAF;;ADGA;EACE;IACE,aAAA;ECAF;EDEA;IACE,4BAAA;ECAF;EDEA;IACE,4BAAA;ECAF;AACF;ADEA;EACE,6BAAA;EACA,yBAAA;ACAF;;ADGA;EACE;IACE,4BAAA;IACA,0BAAA;ECAF;AACF;ADEA;EACE,iCAAA;EACA,SAAA;EACA,kBAAA;EACA,WAAA;EACA,SAAA;EACA,eAAA;EACA,0BAAA;EACA,uBAAA;EACA,kBAAA;EACA,gBAAA;EACA,gBAAA;EACA,UAAA;EACA,mBAAA;EACA,kBAAA;EACA,sBAAA;EACA,gCAAA;EACA,iBAAA;EACA,kBAAA;EACA,QAAA;EACA,yBAAA;EACA,UAAA;EACA,oBAAA;EACA,gCAAA;ACAF;;ADGA;EACE,SAAA;EACA,SAAA;EACA,SAAA;EACA,WAAA;EACA,kBAAA;EACA,gBAAA;EACA,YAAA;EACA,YAAA;EACA,6CAAA;EACA,UAAA;ACAF;;ADGA;EACE;IACE,SAAA;ECAF;AACF;ADEA;EACE;IACE,SAAA;ECAF;AACF;ADEA;EACE;IACE,SAAA;ECAF;AACF;ADEA;EACE;IACE,SAAA;ECAF;AACF;ADEA;EACE;IACE,SAAA;ECAF;AACF;ADEA;EACE;IACE,SAAA;ECAF;AACF;ADEA;EACE;IACE,SAAA;IACA,QAAA;ECAF;AACF;ADEA;EACE,kBAAA;EACA,SAAA;EACA,QAAA;ACAF;;ADGA;EACE;IACE,cAAA;ECAF;EDEA;IACE,aAAA;ECAF;EDEA;IACE,4BAAA;ECAF;EDEA;IACE,4BAAA;ECAF;AACF;ADEA;EACE;IACE,8BAAA;ECAF;AACF;ADEA;EACE,iBAAA;EACA,6BAAA;ACAF;;ADGA;EACE,WAAA;EACA,WAAA;EACA,eAAA;EACA,aAAA;EACA,SAAA;EACA,UAAA;EACA,sBAAA;EACA,mBAAA;ACAF;;ADGA;EACE,cAAA;EACA,eAAA;ACAF;;ADGA;EACE,aAAA;EACA,+BAAA;EACA,8CAAA;EACA,SAAA;ACAF;;ADGA;EACE;IACE,sCAAA;ECAF;AACF;ADEA;EACE;IACE,kCAAA;ECAF;AACF;ADEA;EACE;IACE,8BAAA;ECAF;AACF;ADEA;EACE;IACE,0BAAA;ECAF;AACF;ADEA;EACE,aAAA;EACA,sBAAA;EACA,uBAAA;EACA,mBAAA;EACA,yBAAA;EACA,mBAAA;EACA,eAAA;ACAF;;ADGA;EACE,gBAAA;EACA,mBAAA;EACA,uBAAA;ACAF;;ADGA;EACE,gCAAA;EACA,cAAA;EACA,kBAAA;EACA,gBAAA;EACA,YAAA;EACA,gBAAA;EACA,eAAA;EACA,eAAA;ACAF;;ADGA;EACE,uBAAA;ACAF;;ADGA;EACE,gCAAA;EACA,cAAA;EACA,kBAAA;ACAF;;ADGA;EACE,gCAAA;EACA,cAAA;EACA,kBAAA;ACAF;;ADGA;EACE,aAAA;EACA,uBAAA;EACA,mBAAA;EACA,sBAAA;ACAF;;ADGA;EACE,aAAA;EACA,eAAA;EACA,gCAAA;EACA,YAAA;ACAF;;ADGA;EACE,YAAA;EACA,mBAAA;EACA,iCAAA;EACA,eAAA;EACA,uBAAA;EACA,YAAA;ACAF;;ADGA;EACE,uBAAA;EACA,YAAA;ACAF;;ADGA;EACE,uBAAA;EACA,YAAA;ACAF;;ADGA;EACE,cAAA;EACA,iBAAA;ACAF","file":"styles.css"} -------------------------------------------------------------------------------- /public/css/watchlist.css: -------------------------------------------------------------------------------- 1 | .watchlist-empty { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: center; 5 | align-items: center; 6 | } 7 | 8 | .splide__slide { 9 | position: relative; 10 | top: 250px; 11 | } -------------------------------------------------------------------------------- /public/dmca.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | DMCA - HaiKei 8 | 9 | 10 | 11 | 12 | 13 | <%- include("./templates/navbar.ejs") %> 14 | <%- include("./templates/sidenav.ejs") %> 15 |
16 |

DMCA Takedown Requests:

17 |

We take intellectual property rights very seriously and will respond quickly and effectively to valid DMCA takedown requests. The DMCA (Digital Millenium Copyright Act) Defines a set of strict standards for allowing copyright owners to keep their respective IPs as their own.

18 |
19 | 20 |
21 |

DMCA Report Requirements

22 |
    23 |
  • A description of the copyrighted work that you claim is being infringed
  • 24 |
  • A description of the material you claim is infringing and that you want removed or access to which you want disabled with a URL and proof you are the original owner or other location of that material
  • 25 |
  • Your name, title (if acting as an agent), address, telephone number, and email address
  • 26 |
  • The following statement: "I have a good faith belief that the use of the copyrighted material I am complaining of is not authorized by the copyright owner, its agent, or the law (e.g., as a fair use)"
  • 27 |
  • The following statement: "The information in this notice is accurate and, under penalty of perjury, I am the owner, or authorized to act on behalf of the owner, of the copyright or of an exclusive right that is allegedly infringed"
  • 28 |
  • The following statement: "I understand that I am subject to legal action upon submitting a DMCA request without solid proof."
  • 29 |
  • An electronic or physical signature of the owner of the copyright or a person authorized to act on the owner's behalf.
  • 30 |
31 |

You can get into contact with HaiKei through the following email address: dmca@haikei.xyz

32 |

Your DMCA Request will be reviewed and we will take swift action if we find your report to uphold to all of the following standards and be a valid DMCA takedown request.

33 |
34 | 35 | <%- include("./templates/user-modals.ejs") %> 36 | <%- include('./templates/new-footer.ejs') %> 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wearrrrr/HaiKei/ab90f53b5ee085fa2d2fea4b6cfc7a0ee7da9a3f/public/favicon.ico -------------------------------------------------------------------------------- /public/genres.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Genres - HaiKei 11 | 12 | 13 | 14 | 15 | 16 | 17 | <%- include("./templates/sidenav.ejs") %> 18 | <%- include("./templates/navbar.ejs") %> 19 | 20 |

Search By Genre:

21 |
22 | <%# card[String(Object.getOwnPropertyNames(card)[i])] %> 23 | <% genreData.genres.forEach((card) => { %> 24 | <% for (i = 0; i < Object.getOwnPropertyNames(card).length; i++) { %> 25 | 26 |
27 | 28 | 29 |
30 | <% } %> 31 | <% }) %> 32 |
33 | 34 | <%- include("./templates/user-modals.ejs") %> 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /public/img/36629.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wearrrrr/HaiKei/ab90f53b5ee085fa2d2fea4b6cfc7a0ee7da9a3f/public/img/36629.jpg -------------------------------------------------------------------------------- /public/img/HaiKeiBG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wearrrrr/HaiKei/ab90f53b5ee085fa2d2fea4b6cfc7a0ee7da9a3f/public/img/HaiKeiBG.png -------------------------------------------------------------------------------- /public/img/Untitled.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wearrrrr/HaiKei/ab90f53b5ee085fa2d2fea4b6cfc7a0ee7da9a3f/public/img/Untitled.pdn -------------------------------------------------------------------------------- /public/img/haikei_ad.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wearrrrr/HaiKei/ab90f53b5ee085fa2d2fea4b6cfc7a0ee7da9a3f/public/img/haikei_ad.pdn -------------------------------------------------------------------------------- /public/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wearrrrr/HaiKei/ab90f53b5ee085fa2d2fea4b6cfc7a0ee7da9a3f/public/img/logo.png -------------------------------------------------------------------------------- /public/img/logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wearrrrr/HaiKei/ab90f53b5ee085fa2d2fea4b6cfc7a0ee7da9a3f/public/img/logo.webp -------------------------------------------------------------------------------- /public/img/logoCircle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wearrrrr/HaiKei/ab90f53b5ee085fa2d2fea4b6cfc7a0ee7da9a3f/public/img/logoCircle.png -------------------------------------------------------------------------------- /public/img/logo_old.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wearrrrr/HaiKei/ab90f53b5ee085fa2d2fea4b6cfc7a0ee7da9a3f/public/img/logo_old.webp -------------------------------------------------------------------------------- /public/img/logo_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wearrrrr/HaiKei/ab90f53b5ee085fa2d2fea4b6cfc7a0ee7da9a3f/public/img/logo_small.png -------------------------------------------------------------------------------- /public/index_superold.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Home - HaiKei 8 | 9 | 10 | 11 | 12 |
13 |
14 |

15 | GenericAnimeWeb 16 |

17 | 25 |
26 |
27 |
28 |

New Releases

29 |
30 | <% recentReleases.results.forEach(card => { %> 31 |
  • 32 | <%- card.title %> 33 |

    <%- card.title %>

    34 |

    Episode: <%- card.episodeNumber %>

    35 |
  • 36 | <% }) %> 37 | <% recentReleases2.results.forEach(card => { %> 38 |
  • 39 | <%- card.title %> 40 |

    <%- card.title %>

    41 |

    Episode: <%- card.episodeNumber %>

    42 |
  • 43 | <% }) %> 44 | 45 |
    46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /public/js/af/random.js: -------------------------------------------------------------------------------- 1 | let randomData; 2 | async function randomAnime() { 3 | let randomIcn = document.getElementById('random-btn').className = "fa-regular fa-spinner-third" 4 | document.getElementById('random-btn').classList.add("spinning") 5 | let randomOutput = await fetch('https://cors.fl-anime.com/https://api.fl-anime.com/meta/anilist/random-anime'); 6 | randomData = await randomOutput.json() 7 | window.location.href = '/watch/' + randomData.title.romaji.replace(/ /g, '-').replace('.', '').replace(',', '').replace('/', '').replace('?', '').replace(/:/g, '').replace(/!/g, '').replace('∞', '').replace(/☆/g, '-').replace(/;/g, '').replace(/~/g, '').replace(/ä/g, 'a') + '-episode-1' 8 | } -------------------------------------------------------------------------------- /public/js/af/search_list.js: -------------------------------------------------------------------------------- 1 | // Generic Debouncing function :D 2 | function debounce(func, wait, immediate) { 3 | var timeout; 4 | return function() { 5 | var context = this, 6 | args = arguments; 7 | var callNow = immediate && !timeout; 8 | clearTimeout(timeout); 9 | timeout = setTimeout(function() { 10 | timeout = null; 11 | if (!immediate) { 12 | func.apply(context, args); 13 | } 14 | }, wait); 15 | if (callNow) func.apply(context, args); 16 | } 17 | } 18 | 19 | document.getElementById('livesearch').addEventListener('keyup', (e) => { 20 | if (e.keyCode == 27) return 21 | debouncedPost() 22 | }) 23 | 24 | function showResult() { 25 | var x = document.getElementById("livesearch"); 26 | $.post("https://cors.haikei.xyz/https://animefox.to/livesearch", { value: x.value }).done(function (data) { 27 | let list = DOMPurify.sanitize(document.getElementById("search-suggest").innerHTML = data) 28 | }); 29 | } 30 | 31 | // Define the debounced function 32 | var debouncedPost = debounce(showResult, 300); -------------------------------------------------------------------------------- /public/js/app.js: -------------------------------------------------------------------------------- 1 | // Universal Functions for Every HaiKei page 2 | 3 | function defaultConfig() { 4 | localStorage.setItem("_noads", false) 5 | localStorage.setItem("_firstload", false) 6 | localStorage.setItem("videoSource", "gogoanime") 7 | } 8 | if (localStorage.getItem("_firstload") == undefined) { 9 | defaultConfig() 10 | } -------------------------------------------------------------------------------- /public/js/donate.js: -------------------------------------------------------------------------------- 1 | let loadedFlag = false; 2 | function copyAddr(addr) { 3 | navigator.clipboard.writeText(addr) 4 | // dynamically load css only the first time 5 | if (loadedFlag == false) { 6 | const link = document.createElement("link"); 7 | link.rel = "stylesheet"; 8 | link.href = "https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css"; 9 | document.head.appendChild(link); 10 | loadedFlag = true; 11 | } 12 | 13 | Toastify({ 14 | text: "Copied to clipboard!", 15 | duration: 3000, 16 | style: { 17 | background: "var(--dark-accent)", 18 | boxShadow: "none" 19 | }, 20 | close: true, 21 | }).showToast(); 22 | 23 | } -------------------------------------------------------------------------------- /public/js/genres.js: -------------------------------------------------------------------------------- 1 | function toggle() { 2 | window.location.href = `/genre/${genre}` 3 | } -------------------------------------------------------------------------------- /public/js/index.js: -------------------------------------------------------------------------------- 1 | const swiper = new Swiper(".swiper", { 2 | // Optional parameters 3 | direction: "horizontal", 4 | loop: true, 5 | allowTouchMove: true, 6 | pagination: { 7 | el: ".swiper-pagination", 8 | }, 9 | autoplay: { 10 | delay: 6000, 11 | 12 | }, 13 | keyboard: { 14 | enabled: true, 15 | onlyInViewport: false, 16 | }, 17 | }); -------------------------------------------------------------------------------- /public/js/jquery_debounce.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery throttle / debounce - v1.1 - 3/7/2010 3 | * http://benalman.com/projects/jquery-throttle-debounce-plugin/ 4 | * 5 | * Copyright (c) 2010 "Cowboy" Ben Alman 6 | * Dual licensed under the MIT and GPL licenses. 7 | * http://benalman.com/about/license/ 8 | */ 9 | (function(b,c){var $=b.jQuery||b.Cowboy||(b.Cowboy={}),a;$.throttle=a=function(e,f,j,i){var h,d=0;if(typeof f!=="boolean"){i=j;j=f;f=c}function g(){var o=this,m=+new Date()-d,n=arguments;function l(){d=+new Date();j.apply(o,n)}function k(){h=c}if(i&&!h){l()}h&&clearTimeout(h);if(i===c&&m>e){l()}else{if(f!==true){h=setTimeout(i?k:l,i===c?e-m:e)}}}if($.guid){g.guid=j.guid=j.guid||$.guid++}return g};$.debounce=function(d,e,f){return f===c?a(d,e,false):a(d,f,e!==false)}})(this); -------------------------------------------------------------------------------- /public/js/kaomoji.js: -------------------------------------------------------------------------------- 1 | let joy = [ 2 | '(* ^ ω ^)', '(´ ∀ ` *)', '٩(◕‿◕。)۶', '☆*:.。.o(≧▽≦)o.。.:*☆', 3 | '(o^▽^o)', '(⌒▽⌒)☆', '<( ̄︶ ̄)>', "。.:☆*:・'(*⌒―⌒*)))", 4 | 'ヽ(・∀・)ノ', '(´。• ω •。`)', '( ̄ω ̄)', '`;:゛;`;・(°ε° )', 5 | '(o・ω・o)', '(@^◡^)', 'ヽ(*・ω・)ノ', '(o_ _)ノ彡☆', 6 | '(^人^)', '(o´▽`o)', '(*´▽`*)', '。゚( ゚^∀^゚)゚。', 7 | '( ´ ω ` )', '(((o(*°▽°*)o)))', '(≧◡≦)', '(o´∀`o)', 8 | '(´• ω •`)', '(^▽^)', '(⌒ω⌒)', '∑d(°∀°d)', 9 | '╰(▔∀▔)╯', '(─‿‿─)', '(*^‿^*)', 'ヽ(o^ ^o)ノ', 10 | '(✯◡✯)', '(◕‿◕)', '(*≧ω≦*)', '(☆▽☆)', 11 | '(⌒‿⌒)', '\(≧▽≦)/', 'ヽ(o^▽^o)ノ', "☆ ~('▽^人)", 12 | '(*°▽°*)', '٩(。•́‿•̀。)۶', '(✧ω✧)', 'ヽ(*⌒▽⌒*)ノ', 13 | '(´。• ᵕ •。`)', '( ´ ▽ ` )', '( ̄▽ ̄)', '╰(*´︶`*)╯', 14 | 'ヽ(>∀<☆)ノ', 'o(≧▽≦)o', '(☆ω☆)', '(っ˘ω˘ς )', 15 | '\( ̄▽ ̄)/', '(*¯︶¯*)', '\(^▽^)/', '٩(◕‿◕)۶', 16 | "('o˘◡˘o)", '\(★ω★)/', '\(^ヮ^)/', '(〃^▽^〃)', 17 | '(╯✧▽✧)╯', 'o(>ω<)o', 'o( ❛ᴗ❛ )o', '。゚(TヮT)゚。', 18 | '( ‾́ ◡ ‾́ )', '(ノ´ヮ`)ノ*: ・゚', '(b ᵔ▽ᵔ)b', '(๑˃ᴗ˂)ﻭ', 19 | '(๑˘︶˘๑)', '( ˙꒳​˙ )', '(*꒦ິ꒳꒦ີ)', '°˖✧◝(⁰▿⁰)◜✧˖°', 20 | '(´・ᴗ・ ` )', '(ノ◕ヮ◕)ノ*:・゚✧', '(„• ֊ •„)', '(.❛ ᴗ ❛.)', 21 | '(⁀ᗢ⁀)', '(¬‿¬ )', '(¬‿¬ )', '(* ̄▽ ̄)b', 22 | '( ˙▿˙ )', '(¯▿¯)', '( ◕▿◕ )', '\(٥⁀▽⁀ )/', 23 | '(„• ᴗ •„)', '(ᵔ◡ᵔ)', '( ´ ▿ ` )]' 24 | ] 25 | 26 | let sadness = [ 27 | '(ノ_<。)', '(-_-)', '(´-ω-`)', '.・゚゚・(/ω\)・゚゚・.', 28 | '(μ_μ)', '(ノД`)', '(-ω-、)', '。゜゜(´O`) ゜゜。', 29 | 'o(TヘTo)', '( ; ω ; )', '(。╯︵╰。)', '。・゚゚*(>д<)*゚゚・。', 30 | '( ゚,_ゝ`)', '(个_个)', '(╯︵╰,)', '。・゚(゚>_<゚)゚・。', 31 | '( ╥ω╥ )', '(╯_╰)', '(╥_╥)', '.。・゚゚・(>_<)・゚゚・。.', 32 | '(/ˍ・、)', '(ノ_<、)', '(╥﹏╥)', '。゚(。ノωヽ。)゚。', 33 | '(つω`。)', '(。T ω T。)', '(ノω・、)', '・゚・(。>ω<。)・゚・', 34 | '(T_T)', '(>_<)', '(っ˘̩╭╮˘̩)っ', '。゚・ (>﹏<) ・゚。', 35 | 'o(〒﹏〒)o', '(。•́︿•̀。)', '(ಥ﹏ಥ)', '(ಡ‸ಡ)', 36 | ] 37 | 38 | class kaomoji { 39 | 40 | joy() { 41 | return joy[Math.floor(Math.random()*joy.length)]; 42 | } 43 | 44 | sadness() { 45 | return sadness[Math.floor(Math.random()*sadness.length)]; 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /public/js/login.js: -------------------------------------------------------------------------------- 1 | var code = document.getElementById("new-password"); 2 | document.getElementById('complete_login').classList.add("enabled") 3 | 4 | const togglePassword = document.querySelector('#togglePassword'); 5 | const password = document.querySelector('#new-password'); 6 | 7 | togglePassword.addEventListener('click', function (e) { 8 | // toggle the type attribute 9 | const type = password.getAttribute('type') === 'password' ? 'text' : 'password'; 10 | password.setAttribute('type', type); 11 | // toggle the eye slash icon 12 | this.classList.toggle('fa-eye-slash'); 13 | this.classList.toggle('fa-eye') 14 | }); -------------------------------------------------------------------------------- /public/js/main.js: -------------------------------------------------------------------------------- 1 | // e.keyCode added for legacy support, if no one uses a legacy browser I will remove this in a later release 2 | // let searchBox = document.getElementById("searchanime") 3 | // searchBox.addEventListener('keyup', function(e) { 4 | // if (e.key == "Enter" || e.keyCode == 13) { 5 | // window.location.href = `/search?query=${searchBox.value}` 6 | // } 7 | // }) -------------------------------------------------------------------------------- /public/js/modals.js: -------------------------------------------------------------------------------- 1 | $("#login").iziModal(); 2 | $("#userInfo").iziModal(); 3 | $("#register").iziModal(); 4 | 5 | 6 | setTimeout(() => { 7 | const iziModalCSS = document.createElement("link"); 8 | iziModalCSS.rel = "stylesheet"; 9 | iziModalCSS.href = "https://cdnjs.cloudflare.com/ajax/libs/izimodal/1.6.1/css/iziModal.css"; 10 | document.head.appendChild(iziModalCSS); 11 | 12 | const fontAwesomeCSS = document.createElement("link"); 13 | fontAwesomeCSS.rel = "stylesheet"; 14 | fontAwesomeCSS.href = "https://site-assets.fontawesome.com/releases/v6.2.1/css/all.css"; 15 | document.head.appendChild(fontAwesomeCSS); 16 | }, 0); 17 | let navFlag = 0; // false 18 | 19 | window.addEventListener('keydown', function(e) { 20 | if (e.key === 'Escape' && navFlag === 1) { 21 | sidebarClose(); 22 | } 23 | }); 24 | 25 | function sidebarOpen() { 26 | if (window.innerWidth <= 585) { 27 | document.getElementById("sidebar-nav").style.width = "100%"; 28 | navFlag = 1; // true 29 | } else { 30 | document.getElementById("sidebar-nav").style.width = "360px"; 31 | document.getElementById("sidebar-nav").style.filter = "brightness(1.5)" 32 | document.getElementById('full-doc').style.filter = "brightness(0.5)" 33 | navFlag = 1; 34 | } 35 | 36 | } 37 | 38 | function sidebarClose() { 39 | document.getElementById("sidebar-nav").style.width = "0"; 40 | document.getElementById("sidebar-nav").style.filter = "brightness(0)" 41 | document.getElementById('full-doc').style.filter = "brightness(1)" 42 | navFlag = 0; 43 | } -------------------------------------------------------------------------------- /public/js/recentReleases.js: -------------------------------------------------------------------------------- 1 | const consumetURL = "http://localhost:8081/http://localhost:8080/" 2 | let recentReleases; 3 | let recentReleases2; 4 | async function loadRecentReleases() { 5 | fetch(`${consumetURL}anime/gogoanime/recent-episodes`) 6 | .then((response) => response.json()) 7 | .then((data) => recentReleases = data) 8 | .then(() => 9 | recentReleases.results.forEach((ep) => { 10 | 11 | })); 12 | fetch(`${consumetURL}anime/gogoanime/recent-episodes?page=2`) 13 | .then((response) => response.json()) 14 | .then((data) => recentReleases2 = data); 15 | } 16 | loadRecentReleases() 17 | -------------------------------------------------------------------------------- /public/js/signup.js: -------------------------------------------------------------------------------- 1 | var code = document.getElementById("new-password"); 2 | 3 | var strengthbar = document.getElementById("pw_strength"); 4 | document.getElementById('complete_signup').classList.add("disabled") 5 | code.addEventListener("keyup", function() { 6 | checkPassword(code.value); 7 | }); 8 | 9 | function checkPassword(password) { 10 | var strength = 0; 11 | if (password.match(/[a-z]+/)) { 12 | strength++; 13 | } 14 | if (password.match(/[A-Z]+/)) { 15 | strength++; 16 | } 17 | if (password.match(/[0-9]+/)) { 18 | strength++; 19 | } 20 | if (password.match(/[$@#&!]+/)) { 21 | strength++; 22 | if (password.length >= 8 ) { 23 | strength++ 24 | } 25 | console.log(strength) 26 | } 27 | 28 | // if (password.length < 6) { 29 | // display.innerHTML = "minimum number of characters is 6"; 30 | // } 31 | 32 | // if (password.length > 12) { 33 | // display.innerHTML = "maximum number of characters is 12"; 34 | // } 35 | 36 | switch (strength) { 37 | case 0: 38 | strengthbar.style.width = '3%'; 39 | document.getElementById('complete_signup').classList.add("disabled") 40 | document.getElementById('complete_signup').classList.remove("enabled"); 41 | break; 42 | 43 | case 1: 44 | strengthbar.style.width = '20%'; 45 | document.getElementById('complete_signup').classList.add("disabled") 46 | document.getElementById('complete_signup').classList.remove("enabled"); 47 | break; 48 | 49 | case 2: 50 | strengthbar.style.width = '40%'; 51 | document.getElementById('complete_signup').classList.add("disabled") 52 | document.getElementById('complete_signup').classList.remove("enabled"); 53 | break; 54 | 55 | case 3: 56 | strengthbar.style.width = '60%'; 57 | document.getElementById('complete_signup').classList.add("disabled") 58 | document.getElementById('complete_signup').classList.remove("enabled"); 59 | break; 60 | 61 | case 4: 62 | strengthbar.style.width = '80%'; 63 | document.getElementById('complete_signup').disabled = false; 64 | document.getElementById('complete_signup').classList.add("enabled"); 65 | document.getElementById('complete_signup').classList.remove("disabled"); 66 | break; 67 | case 5: 68 | strengthbar.style.width = '100%'; 69 | document.getElementById('complete_signup').disabled = false; 70 | document.getElementById('complete_signup').classList.add("enabled"); 71 | document.getElementById('complete_signup').classList.remove("disabled"); 72 | break; 73 | } 74 | } 75 | 76 | const togglePassword = document.querySelector('#togglePassword'); 77 | const password = document.querySelector('#new-password'); 78 | 79 | togglePassword.addEventListener('click', function (e) { 80 | // toggle the type attribute 81 | const type = password.getAttribute('type') === 'password' ? 'text' : 'password'; 82 | password.setAttribute('type', type); 83 | // toggle the eye slash icon 84 | this.classList.toggle('fa-eye-slash'); 85 | this.classList.toggle('fa-eye') 86 | }); -------------------------------------------------------------------------------- /public/js/splide__fn.js: -------------------------------------------------------------------------------- 1 | let perPage = 6 2 | 3 | function carouselInit() { 4 | carousel = new Splide('.splide', { 5 | type: "loop", 6 | perPage: perPage, 7 | gap: "10px", 8 | breakpoints: { 9 | 1800: { 10 | perPage: 8 11 | }, 12 | 1500: { 13 | perPage: 4, 14 | }, 15 | 730: { 16 | perPage: 3, 17 | }, 18 | 525: { 19 | perPage: 2, 20 | }, 21 | 350: { 22 | perPage: 1, 23 | } 24 | }, 25 | paginationKeyboard: true, 26 | height: '480px', 27 | }) // loads splide.js 28 | console.log("carousel.js loaded!") 29 | } -------------------------------------------------------------------------------- /public/js/trending.js: -------------------------------------------------------------------------------- 1 | let trending; 2 | async function getTrending() { 3 | 4 | 5 | const res = await fetch(window.location.protocol + "//" + window.location.hostname + ":8080" + "/consumet/anime/gogoanime/top-airing", { 6 | 7 | }) 8 | 9 | trending = await res.json(); 10 | 11 | const slideContainer = document.getElementById("splide__list") 12 | slideContainer.style = "display: flex; align-items: center; justify-content: center" 13 | 14 | for (let c in trending.results) { 15 | // create slides 16 | const card = document.createElement("li") 17 | card.className = "splide__slide" 18 | card.setAttribute("onclick","window.location.href=" + `"${window.location.origin}/watch/${trending['results'][c].id}-episode-1"`); 19 | slideContainer.appendChild(card) 20 | 21 | // add image to slide 22 | const slideImg = document.createElement("img") 23 | slideImg.style = "width: 164px; height: 231px; align-self: center" 24 | slideImg.src = trending['results'][c].image 25 | card.appendChild(slideImg) 26 | 27 | // add title to slide 28 | const animeTxt = document.createElement("p") 29 | animeTxt.textContent = trending['results'][c].title 30 | animeTxt.className = "splide__animeTitle" 31 | card.appendChild(animeTxt) 32 | 33 | // add episode number to slide 34 | const releaseTxt = document.createElement("p") 35 | releaseTxt.textContent = "Genres: " + trending.results[c].genres[0] + ', ' + trending.results[c].genres[1] 36 | releaseTxt.className = "splide__episodeNum" 37 | card.appendChild(releaseTxt) 38 | 39 | 40 | } 41 | 42 | 43 | 44 | carousel.mount(); 45 | 46 | } 47 | carouselInit() 48 | getTrending() -------------------------------------------------------------------------------- /public/login.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | HaiKei | Login 7 | 8 | 9 | 10 | 11 | 12 | 13 |
    14 |

    HaiKei

    15 |

    Sign in

    16 | <% if (message) { %> 17 |
    18 |

    <%= message %>

    19 |
    20 | <% } %> 21 |
    22 |
    23 | 24 | 25 |
    26 |
    27 | 28 | 29 |
    30 | 31 |
    32 |
    33 |

    Don't have an account? Sign up

    34 |
    35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / 3 | Crawl-delay: 2 4 | Sitemap: https://haikei.xyz/sitemap.xml -------------------------------------------------------------------------------- /public/search.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Search Results for <%= query %> 8 | 9 | 10 | 11 | 12 | 13 | 14 | <%- include("./templates/sidenav.ejs") %> 15 | <%- include("./templates/navbar.ejs") %> 16 |
    17 |

    Search Results for: "<%= query %>"

    18 |
    19 |
    20 | <% searchData.forEach((searchData) => { %> 21 |
  • 22 | 23 | <%- searchData.title.romaji %> 24 |

    <%- searchData.title.romaji %>

    25 |
  • 26 | <% }) %> 27 | 28 |
    29 |
    30 | <%- include("./templates/user-modals.ejs") %> 31 | 32 | 33 | 34 | 35 | 36 | 37 | 44 | <%- include('./templates/new-footer.ejs') %> 45 | 46 | -------------------------------------------------------------------------------- /public/signup.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | HaiKei | Signup 7 | 8 | 9 | 10 | 11 | 12 | 13 |
    14 |

    HaiKei

    15 |

    Sign up

    16 |
    17 |
    18 | 19 | 20 |
    21 |
    22 | 23 | 24 |
    25 |
    26 |
    27 |
    28 |
    29 |
    30 | 31 | 32 |
    33 | 34 |
    35 |
    36 |

    Already have an account? Sign in

    37 |
    38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /public/sitemap.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wearrrrr/HaiKei/ab90f53b5ee085fa2d2fea4b6cfc7a0ee7da9a3f/public/sitemap.xml -------------------------------------------------------------------------------- /public/status.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Genres - HaiKei 11 | 12 | 13 | 14 | 15 | 16 | 17 | <%- include("./templates/sidenav.ejs") %> 18 | <%- include("./templates/navbar.ejs") %> 19 | 20 |

    <%- status %>

    21 |
    22 | <% data.results.forEach((card) => { %> 23 | 24 |
    25 | 26 | 27 |
    28 | <% }) %> 29 |
    30 | 31 | <%- include("./templates/user-modals.ejs") %> 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /public/templates/analytics.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /public/templates/footer.ejs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/templates/header.ejs: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |
    Menu
    5 |
    6 |
    7 | 8 | 23 |
    24 |
    25 |
    26 |
    27 |
    -------------------------------------------------------------------------------- /public/templates/navbar.ejs: -------------------------------------------------------------------------------- 1 | 36 |
    37 | 41 |
    -------------------------------------------------------------------------------- /public/templates/new-footer.ejs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/templates/sidenav.ejs: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /public/templates/user-modals.ejs: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | 4 | 5 | 6 | 10 |
    11 | 12 | 13 |
    14 | 17 |
    18 | 19 |
    20 |
    21 |
    22 |
    23 |
    24 | 25 | 26 | 27 | 33 |
    34 | 35 |
    36 |
    37 |
    38 | 39 | 47 | 48 | <% if(loginState == true) { %> 49 |
    50 | 51 |
    52 |
    53 | 54 |
    55 | 56 |
    57 |
    58 | 59 | 63 |
    64 | 65 | 69 |
    70 |
    71 | <% } %> -------------------------------------------------------------------------------- /public/test.ejs: -------------------------------------------------------------------------------- 1 | 5 |
    6 | 7 | 8 |
    9 | -------------------------------------------------------------------------------- /public/trending.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Trending - HaiKei 8 | 9 | 10 | 11 | 12 | <%- include("./templates/sidenav.ejs") %> 13 | <%- include("./templates/navbar.ejs") %> 14 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /public/w2g.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Watch2Gether | HaiKei 8 | 9 | 10 | 11 | 12 | <%- include("./templates/sidenav.ejs") %> 13 | <%- include("./templates/navbar.ejs") %> 14 |
    15 | Create a New Room 16 | 17 |
    18 |
    19 |

    Public Rooms

    20 |
    21 | <% const parsedRoomData = JSON.parse(roomData) %> 22 | <% Object.keys(parsedRoomData).forEach(room => { %> 23 |
    24 |
    25 |

    <%- parsedRoomData[room].roomName %>

    26 |

    (<%- parsedRoomData[room].roomID %>)

    27 |

    <%- parsedRoomData[room].roomDescription %>

    28 |
    29 |
    30 | Join 31 |
    32 |
    33 | <% }) %> 34 |
    35 |
    36 | 37 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /routers/ajax.js: -------------------------------------------------------------------------------- 1 | // yes i still use ajax :) 2 | const express = require('express'); 3 | const app = express.Router(); 4 | const { META } = require('@consumet/extensions') 5 | const { ANIME } = require('@consumet/extensions') 6 | const anilist = new META.Anilist() 7 | const gogoanime = new ANIME.Gogoanime() 8 | const zoro = new ANIME.Zoro() 9 | const animepahe = new ANIME.AnimePahe() 10 | 11 | app.post("/search", (req, res) => { 12 | let search = req.body.search; 13 | try { 14 | anilist.search(search).then(data => { 15 | res.send(data) 16 | }) 17 | } catch (e) { 18 | res.status(500).send("Internal server error!") 19 | } 20 | }) 21 | 22 | const allowedPlatforms = ["gogoanime", "zoro", "animepahe"] 23 | 24 | app.post("/source", async (req, res) => { 25 | let sourceID = req.body.sourceID; 26 | let sourceName = req.body.sourceName; 27 | try { 28 | if (!allowedPlatforms.includes(sourceName)) { 29 | res.status(403).send("Access denied!") 30 | } 31 | if (sourceName == "gogoanime") { 32 | await gogoanime.fetchEpisodeSources(sourceID).then(data => { 33 | res.send(data) 34 | }) 35 | } else { 36 | 37 | } 38 | if (sourceName == "zoro") { 39 | await zoro.fetchAnimeInfo(sourceID).then(data => { 40 | res.send(data) 41 | }) 42 | } else { 43 | 44 | } 45 | if (sourceName == "animepahe") { 46 | await animepahe.fetchEpisodeSources(sourceID).then(data => { 47 | res.send(data) 48 | }) 49 | } else { 50 | 51 | } 52 | } catch (e) { 53 | console.log(e) 54 | res.status(500).send("Internal server error!") 55 | } 56 | }) 57 | 58 | module.exports = app; -------------------------------------------------------------------------------- /routers/api.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | var passport = require('passport'); 3 | var LocalStrategy = require('passport-local'); 4 | const app = express.Router(); 5 | var db = require('../db'); 6 | var crypto = require('crypto'); 7 | const { parse } = require('path'); 8 | const limit = require('express-limit').limit; 9 | 10 | 11 | app.post('/watchlist/add', (req, res) => { 12 | try { 13 | let data = db.get('SELECT * FROM users WHERE username = ?', [ req.user.username ], function(err, row) { 14 | let itemToPush = req.body.data 15 | let currentData = row.watchlist 16 | let parsed = JSON.parse(currentData) 17 | if (parsed.results.includes(itemToPush)) { 18 | return 19 | } else { 20 | parsed.results.push(itemToPush) 21 | let parsedText = JSON.stringify(parsed) 22 | db.run(`UPDATE "main"."users" SET "watchlist"=? WHERE "_rowid_"=?`, [ 23 | parsedText, 24 | row.id 25 | ]) 26 | } 27 | }); 28 | setTimeout(() => { 29 | let returnData = db.get(`SELECT * FROM users WHERE username = ?`, [req.user.username], function(err, row) { 30 | res.send("Success") 31 | }) 32 | }, 1); 33 | 34 | } catch { 35 | res.send("not logged in!") 36 | } 37 | }) 38 | 39 | app.post("/watchlist/remove", (req, res) => { 40 | try { 41 | let data = db.get('SELECT * FROM users WHERE username = ?', [req.user.username], function(err, row) { 42 | let parsedData = JSON.parse(row.watchlist) 43 | 44 | let idToRemove = req.body.id.toString(); 45 | 46 | let newResults = parsedData.results.filter(result => { 47 | let jsonResult = JSON.parse(result); 48 | return jsonResult.id !== idToRemove; 49 | }); 50 | 51 | if(newResults.length === parsedData.results.length){ 52 | res.send("Watchlist is empty already!") 53 | }else{ 54 | let newResponse = { 55 | "results": newResults 56 | }; 57 | let parsedText = JSON.stringify(newResponse) 58 | db.run(`UPDATE "main"."users" SET "watchlist"=? WHERE "_rowid_"=?`, [ 59 | parsedText, 60 | row.id 61 | ]) 62 | res.send("Success!") 63 | } 64 | }) 65 | } catch(err) { 66 | res.send("Error!") 67 | } 68 | }) 69 | 70 | module.exports = app -------------------------------------------------------------------------------- /routers/genre/genre.js: -------------------------------------------------------------------------------- 1 | const ex = require('express'); 2 | const app = ex.Router(); 3 | const axios = require('axios') 4 | const config = require('../../config.js') 5 | const consumetURL = config.app.api_url3 6 | 7 | app.get("/", async(req, res) => { 8 | const fullUrl = `${req.protocol}://${req.get("host")}${req.originalUrl}`; 9 | let loginState; 10 | let username; 11 | try { 12 | let genreData = req.query.sort 13 | genreString = genreData.toString() 14 | genreData = genreString[0].toUpperCase() + genreString.substring(1); 15 | if (genreData.includes('Slice of life')) { 16 | genreData = [ 'Slice of Life' ] 17 | } 18 | if (genreData.includes('Mahou shoujo')) { 19 | genreData = [ 'Mahou Shoujo' ] // 魔法少女 20 | } 21 | if (genreData.includes('Sci-fi')) { 22 | genreData = [ 'Sci-Fi' ] 23 | } 24 | let genreInfo = await axios(`${consumetURL}meta/anilist/advanced-search?genres=["${genreData}"]&sort=["POPULARITY_DESC"]`) 25 | let genreListing = await genreInfo.data; 26 | if (req.user == undefined) { 27 | loginState = false; 28 | } else { 29 | loginState = true; 30 | username = req.user.username 31 | } 32 | if (loginState == true) { 33 | return res.render('genre.ejs', {genre: genreData, listings: genreListing, username: username, loginState: loginState, url: fullUrl}); 34 | } else { 35 | return res.render('genre.ejs', {genre: genreData, listings: genreListing, loginState: loginState, url: fullUrl}); 36 | } 37 | 38 | } catch(e) { 39 | console.log(e); 40 | return res.status(404).render('error.ejs', {loginState: loginState, username: username, errCode: "Unknown Error has occured!"}) 41 | } 42 | }) 43 | 44 | module.exports = app; -------------------------------------------------------------------------------- /routers/genre/genres.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const app = express.Router(); 3 | const axios = require('axios') 4 | 5 | app.get('/', async (req, res) => { 6 | try { 7 | const fullUrl = `${req.originalUrl}`; 8 | if (req.user == undefined) { 9 | loginState = false; 10 | } else { 11 | loginState = true; 12 | username = req.user.username 13 | } 14 | let genreListData; 15 | try { 16 | let genreList = await axios.get(process.env.WEBSITE_URL + "/genres.json") 17 | genreListData = await genreList.data 18 | } catch(err) { 19 | try { 20 | let genreList = await axios.get("/genres.json") 21 | genreListData = await genreList.data 22 | } catch (err) { 23 | console.log(err) 24 | return res.status(500).send("Internal Server Error") 25 | } 26 | } 27 | 28 | if (loginState == true) { 29 | return res.render('genres.ejs', {loginState: loginState, url: fullUrl, username: username, genreData: genreListData}); 30 | } else { 31 | return res.render('genres.ejs', {loginState: loginState, url: fullUrl, genreData: genreListData}); 32 | } 33 | } catch(e) { 34 | console.log(e); 35 | return res.render('error.ejs', {errCode: "Server error!"}) 36 | } 37 | }) 38 | 39 | module.exports = app 40 | -------------------------------------------------------------------------------- /routers/releases.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const app = express.Router(); 3 | const axios = require('axios'); 4 | 5 | app.get('/', async (req, res) => { 6 | try { 7 | return res.render('releases.ejs'); 8 | } catch(e) { 9 | console.log(e); 10 | return res.render('404.ejs') 11 | } 12 | }) 13 | 14 | module.exports = app 15 | -------------------------------------------------------------------------------- /routers/search.js: -------------------------------------------------------------------------------- 1 | const ex = require('express'); 2 | const app = ex.Router(); 3 | const config = require('../config') 4 | 5 | const { META } = require('@consumet/extensions') 6 | 7 | const anilist = new META.Anilist() 8 | 9 | const consumetURL = config.app.api_url3 10 | app.get("/", async(req, res) => { 11 | const fullUrl = `${req.originalUrl}`; 12 | try { 13 | if (req.user == undefined) { 14 | loginState = false; 15 | } else { 16 | loginState = true; 17 | username = req.user.username 18 | } 19 | let searchQuery = req.query.query; 20 | let query = await anilist.search(searchQuery) 21 | let queryData = query.results 22 | if (loginState == true) { 23 | return res.render('search.ejs', {query: searchQuery, searchData: queryData, url: fullUrl, loginState: loginState, username: username}); 24 | } else { 25 | return res.render('search.ejs', {query: searchQuery, searchData: queryData, url: fullUrl, loginState: loginState}); 26 | } 27 | 28 | } catch(e) { 29 | console.log(e); 30 | return res.render('404.ejs') 31 | } 32 | }) 33 | 34 | module.exports = app; -------------------------------------------------------------------------------- /routers/status/status.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const app = express.Router(); 3 | const axios = require('axios'); 4 | const config = require('../../config.js'); 5 | const consumetURL = config.app.api_url3; 6 | 7 | app.get("/:status", async (req, res) => { 8 | let fullURL = req.protocol + '://' + req.get('host') + req.originalUrl; 9 | const allowedStatus = ["RELEASING", "NOT_YET_RELEASED", "FINISHED"] 10 | function rewriteStatusNameForClient() { 11 | switch (inputtedStatus) { 12 | case "RELEASING": 13 | return "Releasing Anime:"; 14 | case "NOT_YET_RELEASED": 15 | return "Not Yet Released Anime:"; 16 | case "FINISHED": 17 | return "Finished Anime:"; 18 | case "CANCELLED": 19 | return "Cancelled Anime:"; 20 | default: 21 | return "Unknown Status:"; 22 | } 23 | } 24 | const inputtedStatus = req.params.status.toUpperCase(); 25 | if (!allowedStatus.includes(inputtedStatus)) { 26 | return res.status(400).send("Invalid status!"); 27 | } 28 | try { 29 | let loginState; 30 | const loginCheck = req.user !== undefined ? loginState = true : loginState = false; 31 | const requestData = await axios.get(`${consumetURL}meta/anilist/advanced-search?status=${inputtedStatus}`) 32 | const responseData = await requestData.data; 33 | const properStatusName = rewriteStatusNameForClient(); 34 | if (loginCheck == true) { 35 | let username = req.user.username; 36 | return res.status(200).render("status.ejs", {data: responseData, status: properStatusName, url: fullURL, loginState: loginState, username: username}) 37 | } else { 38 | return res.status(200).render("status.ejs", {data: responseData, status: properStatusName, url: fullURL, loginState: loginState}) 39 | } 40 | 41 | } catch (err) { 42 | console.log(err) 43 | return res.status(500).send("Internal Server Error"); 44 | } 45 | 46 | }); 47 | 48 | module.exports = app; -------------------------------------------------------------------------------- /routers/trending.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const app = express.Router(); 3 | const META = require("@consumet/extensions").META; 4 | const anilist = new META.Anilist(); 5 | 6 | app.get("/", async (req, res) => { 7 | try { 8 | let trending = await anilist.fetchTrendingAnime("1"); 9 | let trendingData = await trending.results; 10 | let trending2 = await anilist.fetchTrendingAnime("2"); 11 | let trending2Data = await trending2.results; 12 | let loginState = req.user ? true : false; 13 | let username = req.user ? req.user.username : null; 14 | let url = req.originalUrl; 15 | 16 | res.render("trending.ejs", { 17 | trending: trendingData, 18 | trending2: trending2Data, 19 | loginState: loginState, 20 | username: username, 21 | url: url, 22 | }); 23 | } catch (e) { 24 | console.log(e); 25 | res.render("404.ejs"); 26 | } 27 | }); 28 | 29 | module.exports = app; 30 | -------------------------------------------------------------------------------- /routers/w2g.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express.Router() 3 | const makeID = require('../utils/makeID.js') 4 | const { getRoomData } = require('../server.js'); 5 | 6 | 7 | router.get("/", (req, res) => { 8 | try { 9 | const roomData = getRoomData(); 10 | const filteredRoomData = {} 11 | Object.keys(roomData).forEach((room) => { 12 | if (roomData[room].isPublic == true) { 13 | filteredRoomData[room] = roomData[room] 14 | } 15 | }) 16 | res.render("w2g.ejs", { 17 | loginState: req.user ? true : false, 18 | username: req.user ? req.user.username : null, 19 | url: req.originalUrl, 20 | roomData: JSON.stringify(filteredRoomData), 21 | }) 22 | } catch { 23 | res.status(500).send("Internal Server Error") 24 | } 25 | 26 | }) 27 | 28 | router.get("/create", (req, res) => { 29 | const roomName = makeID(5); 30 | res.redirect(`/w2g/room/${roomName}`) 31 | }) 32 | 33 | router.get("/room/:room", (req, res) => { 34 | res.render("w2g_room.ejs", { 35 | loginState: req.user ? true : false, 36 | username: req.user ? req.user.username : null, 37 | url: req.originalUrl, 38 | room: req.params.room, 39 | }) 40 | }) 41 | 42 | 43 | module.exports = router; -------------------------------------------------------------------------------- /routers/watchlist.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const app = express.Router(); 3 | var db = require('../db'); 4 | 5 | app.get('/', async (req, res) => { 6 | try { 7 | const fullUrl = `${req.originalUrl}`; 8 | let data = db.get(`SELECT watchlist FROM users WHERE username = ?`, [req.user.username], function(err, row) { 9 | let watchlistData = row.watchlist 10 | if (req.user == undefined) { 11 | loginState = false; 12 | } else { 13 | loginState = true; 14 | username = req.user.username 15 | user = req.user 16 | } 17 | if (loginState == true) { 18 | return res.render("watchlist.ejs", {watchlist: watchlistData, loginState: loginState, username: username, url: fullUrl}) 19 | } else { 20 | return res.render("watchlist.ejs", {watchlist: watchlistData, loginState: loginState, url: fullUrl}) 21 | } 22 | 23 | }) 24 | } catch(e) { 25 | let loginState; 26 | if (req.user == undefined) { 27 | loginState = false; 28 | } else { 29 | loginState = true; 30 | username = req.user.username 31 | user = req.user 32 | } 33 | return res.render('error.ejs', {loginState: loginState, errCode: "You must be logged in to access your watchlist!"}) 34 | } 35 | }) 36 | 37 | module.exports = app 38 | -------------------------------------------------------------------------------- /server_init.bat: -------------------------------------------------------------------------------- 1 | start consumet_start.bat 2 | 3 | start cors-anywhere_start.bat -------------------------------------------------------------------------------- /utils/getSources.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | const { META } = require('@consumet/extensions') 3 | const anilist = new META.Anilist(); 4 | async function getSources(ID) { 5 | try { 6 | let availableSources = []; 7 | let showIds = { 8 | sources: [] 9 | }; 10 | let id = ID 11 | 12 | let malSync = await axios.get(`https://api.malsync.moe/mal/anime/${id}`) 13 | let malSyncData = await malSync.data 14 | 15 | let zoroData = await malSyncData.Sites.Zoro 16 | let gogoData = await malSyncData.Sites.Gogoanime 17 | let marinData = await malSyncData.Sites.Marin 18 | let animepaheData = await malSyncData.Sites.animepahe 19 | let nineanimeData = await malSyncData.Sites["9anime"] // javascript moment 20 | if (gogoData !== undefined) { 21 | availableSources.push("Gogoanime") 22 | Object.getOwnPropertyNames(gogoData).forEach(function (val, idx, array) { 23 | if (gogoData[val].identifier.slice(-4) == "-dub") { 24 | showIds.sources.push({"data": gogoData[val].identifier, "source": "Gogoanime (Dub)"}) 25 | return 26 | } 27 | showIds.sources.push({"data": gogoData[val].identifier, "source": "Gogoanime"}) 28 | }); 29 | } 30 | if (zoroData !== undefined) { 31 | availableSources.push("Zoro") 32 | Object.getOwnPropertyNames(zoroData).forEach(function (val, idx, array) { 33 | showIds.sources.push({"data": zoroData[val].url.slice(16), "source": "Zoro"}) 34 | }); 35 | } 36 | if (marinData !== undefined) { 37 | // availableSources.push("Marin") 38 | // Object.getOwnPropertyNames(tenshiData).forEach(function (val, idx, array) { 39 | // showIds.sources.push({"data": tenshiData[val].identifier, "source": "Tenshi"}) 40 | // }); 41 | } 42 | if (animepaheData !== undefined) { 43 | availableSources.push("Animepahe") 44 | Object.getOwnPropertyNames(animepaheData).forEach(function (val, idx, array) { 45 | showIds.sources.push({"data": animepaheData[val].identifier, "source": "Animepahe"}) 46 | }); 47 | } 48 | // if (nineanimeData !== undefined) { 49 | // availableSources.push("9anime") // do nothing because 9anime cannot be used as a source quite yet. :( 50 | // } 51 | let sources = showIds 52 | 53 | return sources 54 | 55 | } catch(err) { 56 | console.log(err) 57 | } 58 | } 59 | 60 | async function getShowInfo(ID) { 61 | try { 62 | let showInfo = await axios.get(`https://api.fl-anime.com/meta/anilist/info/${ID}`) 63 | let showData = await showInfo.data 64 | return showData 65 | } catch(e) { 66 | console.log(e) 67 | } 68 | } 69 | 70 | async function getVideoSourcesGogoanime(videoID, videoEpisode) { 71 | try { 72 | let videoSources = await axios.get(`https://api.fl-anime.com/anime/gogoanime/watch/${videoID}`) 73 | let videoData = await videoSources.data 74 | return videoData 75 | } catch(e) { 76 | console.log(e) 77 | } 78 | } 79 | 80 | async function getVideoSourcesZoro(episodeID, episodeNumber) { 81 | try { 82 | let url = `https://api.fl-anime.com/anime/zoro/info?id=${episodeID}` 83 | console.log(url) 84 | let videoSources = await axios.get(url) 85 | let videoData = await videoSources.data 86 | let requestedEpisode = videoData.episodes[Number(episodeNumber) - 1] 87 | let requestURL = await axios.get("https://api.fl-anime.com/anime/zoro/watch?episodeId=" + requestedEpisode.id) 88 | await requestURL.data 89 | return requestURL.data 90 | } catch(e) { 91 | console.log(e) 92 | } 93 | } 94 | 95 | module.exports = { 96 | getSources, 97 | getShowInfo, 98 | getVideoSourcesGogoanime, 99 | getVideoSourcesZoro, 100 | } -------------------------------------------------------------------------------- /utils/makeID.js: -------------------------------------------------------------------------------- 1 | function makeID(length) { 2 | let result = ''; 3 | const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 4 | const charactersLength = characters.length; 5 | let counter = 0; 6 | while (counter < length) { 7 | result += characters.charAt(Math.floor(Math.random() * charactersLength)); 8 | counter += 1; 9 | } 10 | return result; 11 | } 12 | 13 | module.exports = makeID; -------------------------------------------------------------------------------- /utils/tokenSender.js: -------------------------------------------------------------------------------- 1 | const nodemailer = require('nodemailer'); 2 | const jwt = require('jsonwebtoken'); 3 | const env = require('dotenv').config(); 4 | const crypto = require('crypto'); 5 | const token = jwt.sign({ 6 | data: `${process.env.JWT_MSG} ${crypto.randomBytes(32)}` 7 | }, process.env.JWT_SECRET, { expiresIn: '1h' } 8 | ); 9 | 10 | async function sendMail(sender, username) { 11 | const transporter = await nodemailer.createTransport({ 12 | host: process.env.SMTP_PROVIDER, 13 | port: process.env.SMTP_PORT, 14 | auth: { 15 | user: process.env.MAIL_USERNAME, 16 | pass: process.env.MAIL_PASSWORD, 17 | }, 18 | }); 19 | 20 | let info = await transporter.sendMail({ 21 | from: '"no-reply" ', 22 | to: `${sender}`, 23 | subject: `Hello ${username}!`, 24 | text: `Hi There!, You recently signed up for HaiKei.xyz. Please verify your email by clicking the link below. (This link will expire in 1 hour!) 25 | ${process.env.WEBSITE_URL}/verify/${token} 26 | Thanks! 27 | wearr - HaiKei.xyz 28 | 29 | 30 | Not you? You can safely ignore this email! 31 | ` 32 | }, (error) => { 33 | if (error) throw Error(error); 34 | }); 35 | } 36 | 37 | 38 | 39 | if (!process.env.SMTP_PROVIDER) { 40 | console.log('SMTP Provider not set!'); 41 | } else { 42 | module.exports = sendMail; 43 | } 44 | 45 | -------------------------------------------------------------------------------- /webserver.bat: -------------------------------------------------------------------------------- 1 | node server.js -------------------------------------------------------------------------------- /webserver.sh: -------------------------------------------------------------------------------- 1 | node server.js --------------------------------------------------------------------------------