diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..2eff99b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,68 @@ +# Python +__pycache__ +venv +.mypy_cache + +# VSCode +.VSCode +*.code-workspace + +# DetaSpace +.space + +# NextJS +## dependencies +standalone +node_modules +.pnp +.pnp.js +.yarn/install-state.gz + +## testing +coverage + +## next.js +.next +out + +## production +build + +## misc +.DS_Store +*.pem + +## debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +## local env files +.env*.local + +## vercel +.vercel + +## typescript +*.tsbuildinfo +next-env.d.ts + +# traefik +traefik/traefik + +old/ +#Trigger Vercel Prod Build + +# next-video +videos/* +!videos/*.json +!videos/*.js +!videos/*.ts +public/_next-video + +API-Trace/* +.env + +player-parsers +docs +.git \ No newline at end of file diff --git a/.env.sample b/.env.sample new file mode 100644 index 0000000..dcb7e8b --- /dev/null +++ b/.env.sample @@ -0,0 +1,5 @@ +# пример заполнения: https://example.com, http://0.0.0.0:80 +NEXT_PUBLIC_KODIK_PARSER_URL= # Домен парсера кодика, требуется для просмотра с данного источника +NEXT_PUBLIC_ANILIBRIA_PARSER_URL= # Домен парсера анилибрии, если не заполнено, используется официальное апи +NEXT_PUBLIC_SIBNET_PARSER_URL= # Домен парсера сибнет, требуется для просмотра с данного источника +# --- \ No newline at end of file diff --git a/.github/workflows/DeployPreviewToVercel.yml b/.github/workflows/DeployPreviewToVercel.yml deleted file mode 100644 index d9e97fa..0000000 --- a/.github/workflows/DeployPreviewToVercel.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: V3 Preview Deployment -env: - VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} -on: - push: - # Pattern matched against refs/tags - branches: - - 'V3' - paths-ignore: - - '**/README.md' - - '**/LICENSE' - - '**/TODO.md' - - '**/docs/**' - - '**/extension/**' -jobs: - Deploy-Preview: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Install Vercel CLI - run: npm install --global vercel@latest - - name: Pull Vercel Environment Information - run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }} - - name: Build Project Artifacts - run: vercel build --token=${{ secrets.VERCEL_TOKEN }} - - name: Deploy Project Artifacts to Vercel - run: vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/DeployTagToVercel.yml b/.github/workflows/DeployTagToVercel.yml deleted file mode 100644 index fc7ea21..0000000 --- a/.github/workflows/DeployTagToVercel.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Production Tag Deployment -env: - VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} -on: - push: - # Pattern matched against refs/tags - tags: - - '*' # Push events to every tag not containing / - paths-ignore: - - '**/README.md' - - '**/LICENSE' - - '**/TODO.md' - - '**/docs/**' - - '**/extension/**' -jobs: - Deploy-Production: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Install Vercel CLI - run: npm install --global vercel@latest - - name: Pull Vercel Environment Information - run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} - - name: Build Project Artifacts - run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }} - - name: Deploy Project Artifacts to Vercel - run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8595241..1775543 100644 --- a/.gitignore +++ b/.gitignore @@ -60,4 +60,5 @@ videos/* !videos/*.ts public/_next-video -API-Trace/* \ No newline at end of file +API-Trace/* +.env \ No newline at end of file diff --git a/DEPLOYMENT.RU.md b/DEPLOYMENT.RU.md new file mode 100644 index 0000000..78f43a3 --- /dev/null +++ b/DEPLOYMENT.RU.md @@ -0,0 +1,180 @@ +# Развёртывание приложения AniX + +## Vercel + +Требования: + +- аккаунт GitHub +- аккаунт Vercel + +1. Создайте форк репозитория + + ![fork button](./docs/deploy/fork.png) + +2. Войдите в аккаунт Vercel + +> [!IMPORTANT] +> Аккаунт Vercel должен быть связан с аккаунтом GitHub. +> +> Если у вас нет аккаунта Vercel, то создайте его через вход с помощью GitHub. + +3. Нажмите кнопку создать новый проект + + ![vercel new project button](./docs/deploy/vercel_new_project.png) + +4. Нажмите кнопку импортировать напротив названия репозитория + + ![vercel import button](./docs/deploy/vercel_import.png) + +5. (опционально) добавьте переменные для использования своего плеера: + + - NEXT_PUBLIC_KODIK_PARSER_URL + - NEXT_PUBLIC_ANILIBRIA_PARSER_URL + - NEXT_PUBLIC_SIBNET_PARSER_URL + + на те которые вы получили, если развёртывали [anix-player-parsers](./player-parsers/README.RU.md) + + ![vercel project settings](./docs/deploy/vercel_project.png) + +6. нажмите кнопку "Deploy" и ожидайте пока не появится подтверждение +7. нажмите кнопку "Continue to Dashboard" +8. клиент будет доступен по ссылке такого вида, нажмите на неё чтобы его открыть + ![vercel project url](./docs/deploy/vercel_url.png) + +## Netlify + +Требования: + +- аккаунт GitHub +- аккаунт Netlify + +1. Создайте форк репозитория + + ![fork button](./docs/deploy/fork.png) + +2. Войдите в аккаунт Netlify + +> [!IMPORTANT] +> Аккаунт Netlify должен быть связан с аккаунтом GitHub. +> +> Если у вас нет аккаунта Netlify, то создайте его через вход с помощью GitHub. + +3. Нажмите кнопку создать новый проект + + ![netlify new project button](./docs/deploy/netlify_new_project.png) + +4. Нажмите кнопку GitHub + + ![netlify provider choice](./docs/deploy/netlify_provider.png) + +5. Нажмите на название репозитория + + ![netlify import button](./docs/deploy/netlify_import.png) + +6. (опционально) заполните название проекта + + ![netlify project name](./docs/deploy/netlify_project_name.png) + +7. (опционально) добавьте переменные для использования своего плеера: + + - NEXT_PUBLIC_KODIK_PARSER_URL + - NEXT_PUBLIC_ANILIBRIA_PARSER_URL + - NEXT_PUBLIC_SIBNET_PARSER_URL + + на те которые вы получили, если развёртывали [anix-player-parsers](./player-parsers/README.RU.md) + + 1. ![alt text](./docs/deploy/netlify_env_1.png) + + 2. ![alt text](./docs/deploy/netlify_env_2.png) + +8. нажмите кнопку "Deploy" и ожидайте пока не появится подтверждение + +9. клиент будет доступен по ссылке такого вида, нажмите на неё чтобы его открыть + + ![netlify project url](./docs/deploy/netlify_url.png) + +## Docker + +Требования: + +- [docker](https://docs.docker.com/engine/install/) + +### Пре-билд + +1. выполните команду: + +`docker run -d --name anix -p 3000:3000 radiquum/anix:latest` + +### Ручной билд + +Доп. Требования: + +- [git](https://git-scm.com/) + +1. Клонируйте репозиторий `git clone https://github.com/Radiquum/AniX` +2. Переместитесь в директорию репозитория `cd AniX` +3. Выполните команду `docker build -t anix .` +4. После окончания, выполните команду: `docker run -d --restart always --name anix -p 3000:3000 anix` + +### docker/Обозначения + +- -d - запустить контейнер в фоне +- --restart always - всегда запускать после перезагрузки сервера +- --name - название контейнера +- -p - порт контейнера который будет доступен извне. ПОРТ:3000 + +> [!NOTE] +> для переменных которые вы получили, если развёртывали [anix-player-parsers](./player-parsers/README.RU.md), необходимо использовать `-e ПЕРЕМЕННАЯ=ЗНАЧЕНИЕ` до слова anix + +[команда docker run](https://docs.docker.com/reference/cli/docker/container/run/) + +### docker/После развёртывания + +Сервис будет доступен по адресу: `http://<ВАШ IP><:ВАШ ПОРТ>/` + +### docker/Примечание + +Для использования своего домена и поддержки протокола HTTPS, вы можете использовать Traefik или другой reverse-proxy, с сертификатом SSL. + +Полезные ссылки: + +- [Конвертер из команды docker run в синтакс для docker compose](https://it-tools.tech/docker-run-to-docker-compose-converter) +- [Как настроить Traefik + свой домен + SSL](https://letmegooglethat.com/?q=how+to+setup+traefik+with+custom+domain+and+ssl+certificate+from+lets+encrypt%3F) + +## pm2 + +Требования: + +- [git](https://git-scm.com/) +- [nodejs 23+ с npm](http://nodejs.org/) +- [pm2](https://pm2.keymetrics.io/) + +Инструкция: + +1. Клонируйте репозиторий `git clone https://github.com/Radiquum/AniX` +2. Переместитесь в директорию репозитория `cd AniX` +3. Выполните команду `npm install` +4. (опционально) скопируйте .env.sample как .env и заполните его переменными которые вы получили, если развёртывали [anix-player-parsers](./player-parsers/README.RU.md) +5. Выполните команду `npm run build` +6. создайте новую директорию (далее будем использовать `<имя_новой_директории>` как её имя) +7. переместите в созданную директорию (`<имя_новой_директории>`) + - директорию `public` в `<имя_новой_директории>/public` + - директорию `.next/static` в `<имя_новой_директории>/.next/static` + - файлы из `.next/standalone` в `<имя_новой_директории>` +8. Переместитесь в созданную директорию и выполните команду `pm2 start server.js -n anix` + +### pm2/Обозначения + +- -n - название сервиса в pm2 + +### pm2/После развёртывания + +Сервис будет доступен по адресу: `http://<ВАШ IP>:3000/` + +### pm2/Примечание + +Для автоматического запуска приложения, рекомендуется настроить pm2 на автозапуск, с помощью команды: `pm2 startup` + +Полезные ссылки: + +- [PM2: подходим к вопросу процесс-менеджмента с умом @ Habr](https://habr.com/ru/articles/480670/) diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000..f7ae21b --- /dev/null +++ b/DEPLOYMENT.md @@ -0,0 +1,180 @@ +# AniX Application Deployment + +## Vercel + +Requirements: + +- GitHub account +- Vercel account + +1. Fork the repository + + ![fork button](./docs/deploy/fork.png) + +2. Log in to your Vercel account + +> [!IMPORTANT] +> Your Vercel account must be linked with your GitHub account. +> +> If you don't have a Vercel account, create one by signing in with GitHub. + +3. Click the button to create a new project + + ![vercel new project button](./docs/deploy/vercel_new_project.png) + +4. Click the import button next to the repository name + + ![vercel import button](./docs/deploy/vercel_import.png) + +5. (optional) Add variables to use your own player: + + - NEXT_PUBLIC_KODIK_PARSER_URL + - NEXT_PUBLIC_ANILIBRIA_PARSER_URL + - NEXT_PUBLIC_SIBNET_PARSER_URL + + Use the ones you received if you deployed [anix-player-parsers](./player-parsers/README.md) + + ![vercel project settings](./docs/deploy/vercel_project.png) + +6. Click the "Deploy" button and wait until you see a confirmation +7. Click the "Continue to Dashboard" button +8. The client will be available at a link of this form, click it to open + ![vercel project url](./docs/deploy/vercel_url.png) + +## Netlify + +Requirements: + +- GitHub account +- Netlify account + +1. Fork the repository + + ![fork button](./docs/deploy/fork.png) + +2. Log in to your Netlify account + +> [!IMPORTANT] +> Your Netlify account must be linked with your GitHub account. +> +> If you don't have a Netlify account, create one by signing in with GitHub. + +3. Click the button to create a new project + + ![netlify new project button](./docs/deploy/netlify_new_project.png) + +4. Click the GitHub button + + ![netlify provider choice](./docs/deploy/netlify_provider.png) + +5. Click the repository name + + ![netlify import button](./docs/deploy/netlify_import.png) + +6. (optional) Fill in the project name + + ![netlify project name](./docs/deploy/netlify_project_name.png) + +7. (optional) Add variables to use your own player: + + - NEXT_PUBLIC_KODIK_PARSER_URL + - NEXT_PUBLIC_ANILIBRIA_PARSER_URL + - NEXT_PUBLIC_SIBNET_PARSER_URL + + Use the ones you received if you deployed [anix-player-parsers](./player-parsers/README.md) + + 1. ![alt text](./docs/deploy/netlify_env_1.png) + + 2. ![alt text](./docs/deploy/netlify_env_2.png) + +8. Click the "Deploy" button and wait until you see a confirmation + +9. The client will be available at a link of this form, click it to open + + ![netlify project url](./docs/deploy/netlify_url.png) + +## Docker + +Requirements: + +- [docker](https://docs.docker.com/engine/install/) + +### Pre-built + +1. Run the command: + +`docker run -d --name anix -p 3000:3000 radiquum/anix:latest` + +### Manual build + +Additional Requirements: + +- [git](https://git-scm.com/) + +1. Clone the repository `git clone https://github.com/Radiquum/AniX` +2. Navigate to the repository directory `cd AniX` +3. Run the command `docker build -t anix .` +4. Once finished, run the command: `docker run -d --restart always --name anix -p 3000:3000 anix` + +### docker/Flags + +- -d - run container in the background +- --restart always - always restart after server reboot +- --name - container name +- -p - container port to be exposed externally. PORT:3000 + +> [!NOTE] +> For variables you received if you deployed [anix-player-parsers](./player-parsers/README.md), you need to use `-e VARIABLE=VALUE` before the word anix + +[docker run command](https://docs.docker.com/reference/cli/docker/container/run/) + +### docker/After deployment + +The service will be available at: `http://<:YOUR PORT>/` + +### docker/Note + +To use your own domain and support HTTPS protocol, you can use Traefik or another reverse proxy with SSL certificate. + +Useful links: + +- [Converter from docker run command to docker compose syntax](https://it-tools.tech/docker-run-to-docker-compose-converter) +- [How to setup Traefik + custom domain + SSL](https://letmegooglethat.com/?q=how+to+setup+traefik+with+custom+domain+and+ssl+certificate+from+lets+encrypt%3F) + +## pm2 + +Requirements: + +- [git](https://git-scm.com/) +- [nodejs 23+ with npm](http://nodejs.org/) +- [pm2](https://pm2.keymetrics.io/) + +Instructions: + +1. Clone the repository `git clone https://github.com/Radiquum/AniX` +2. Navigate to the repository directory `cd AniX` +3. Run the command `npm install` +4. (optional) copy `.env.sample` as `.env` and fill it with variables you received if you deployed [anix-player-parsers](./player-parsers/README.md) +5. Run the command `npm run build` +6. Create a new directory (next we will be refer to its name as ``) +7. Move the following files into the new directory (``): + - move `public` directory to `/public` + - move `.next/static` directory to `/.next/static` + - move files from `.next/standalone` to `` +8. Move into the created directory () and run the command `pm2 start server.js -n anix` + +### pm2/Flags + +- -n - service name in pm2 + +### pm2/After deployment + +The service will be available at: `http://:3000/` + +### pm2/Note + +To enable automatic application startup, it is recommended to configure pm2 to start on boot using the command: `pm2 startup` + +Useful links: + +- [PM2: managing processes smartly @ Habr](https://habr.com/ru/articles/480670/) diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..87783ac --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +FROM node:23-alpine AS base + + +FROM base AS deps +RUN apk add --no-cache libc6-compat +WORKDIR /app +COPY package.json package-lock.json ./ +RUN npm ci + + +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . +RUN npm run build + + +FROM base AS runner +LABEL org.opencontainers.image.source=https://github.com/radiquum/anix +WORKDIR /app +ENV NODE_ENV=production +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs +COPY --from=builder --chown=nextjs:nodejs /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static +USER nextjs + +EXPOSE 3000 +ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" +CMD ["node", "server.js"] diff --git a/docs/REAME.RU.md b/README.RU.md similarity index 76% rename from docs/REAME.RU.md rename to README.RU.md index 9859ca1..70ca94e 100644 --- a/docs/REAME.RU.md +++ b/README.RU.md @@ -2,20 +2,20 @@ AniX - это неофициальный веб-клиент для Android-приложения Anixart. Он позволяет вам получать доступ к своей учетной записи Anixart и управлять ею из веб-браузера на вашем настольном компьютере или ноутбуке. -[Расширение для браузера](./extension/README.md) - -## Список изменений - -- [3.7.0](/public/changelog/3.7.0.md) -- [3.6.0](/public/changelog/3.6.0.md) -- [3.5.0](/public/changelog/3.5.0.md) - -[другие версии](/public/changelog) - ## Отказ от ответственности Пожалуйста, обратите внимание, что Anix является неофициальным проектом и не связан с разработчиками Anixart. Рекомендуется использовать официальное приложение Anixart для получения наиболее актуальных функций. +--- + +[[RU] ПРОЧТИ МЕНЯ](./README.RU.md) | [[EN] README](./README.md) + +[[RU] РАЗВЁРТЫВАНИЕ](./DEPLOYMENT.RU.md) | [[EN] DEPLOY](./DEPLOYMENT.md) + +[[RU] Списки изменений](./public/changelog) + +--- + ## Скриншоты
@@ -53,12 +53,6 @@ AniX - это неофициальный веб-клиент для Android-пр
-## Возможности - -1. Используйте свой существующий аккаунт на Anixart -2. Синхронизируйте списки, историю просмотров, коллекции и многое другое -3. используйте практически все функции приложения для Android - ## Внесение вклада Мы приветствуем вклад в этот проект! Если у вас есть какие-либо исправления ошибок, улучшения или новые функции, пожалуйста, не стесняйтесь отправлять запрос на обновление. diff --git a/README.md b/README.md index 65f6f84..0f965ed 100644 --- a/README.md +++ b/README.md @@ -2,20 +2,20 @@ AniX is an unofficial web client for the Android application Anixart. It allows you to access and manage your Anixart account from a web browser on your desktop or laptop computer. -[Readme [RU]](./docs/REAME.RU.md) | [Browser Extension [RU]](./extension/README.md) - -## Changelog [RU] - -- [3.7.0](./public/changelog/3.7.0.md) -- [3.6.0](./public/changelog/3.6.0.md) -- [3.5.0](./public/changelog/3.5.0.md) - -[other versions](./public/changelog) - ## Disclaimer Please note that AniX is an unofficial project and is not affiliated with the developers of Anixart. It is recommended to use the official Anixart app for the most up-to-date features and functionality. +--- + +[[RU] ПРОЧТИ МЕНЯ](./README.RU.md) | [[EN] README](./README.md) + +[[RU] РАЗВЁРТЫВАНИЕ](./DEPLOYMENT.RU.md) | [[EN] DEPLOY](./DEPLOYMENT.md) + +[[RU] Changelogs](./public/changelog) + +--- + ## Screenshots
@@ -53,12 +53,6 @@ Please note that AniX is an unofficial project and is not affiliated with the de
-## Features - -1. Use your existing Anixart account -2. sync lists, watch history, collections and more -3. use almost all features of an android app - ## Contributing -We welcome contributions to this project! If you have any bug fixes, improvements, or new features, please feel free to create a pull request. \ No newline at end of file +We welcome contributions to this project! If you have any bug fixes, improvements, or new features, please feel free to create a pull request. diff --git a/app/App.tsx b/app/App.tsx index 3733cd1..8cccbd0 100644 --- a/app/App.tsx +++ b/app/App.tsx @@ -4,10 +4,15 @@ import { usePreferencesStore } from "./store/preferences"; import { Navbar } from "./components/Navbar/NavbarUpdate"; import { Inter } from "next/font/google"; import { useEffect, useState } from "react"; -import { Button, Modal, ModalBody, ModalFooter, ModalHeader } from "flowbite-react"; +import { + Button, + Modal, + ModalBody, + ModalFooter, + ModalHeader, +} from "flowbite-react"; import { Spinner } from "./components/Spinner/Spinner"; import { ChangelogModal } from "#/components/ChangelogModal/ChangelogModal"; -import PlausibleProvider from "next-plausible"; import { Bounce, ToastContainer } from "react-toastify"; const inter = Inter({ subsets: ["latin"] }); @@ -104,14 +109,6 @@ export const App = (props) => { - {preferencesStore.flags.enableAnalytics && ( - - )} void ) => { // Fetch episode links via edge function + const NEXT_PUBLIC_KODIK_PARSER_URL = env("NEXT_PUBLIC_KODIK_PARSER_URL") + if (!NEXT_PUBLIC_KODIK_PARSER_URL) { + setPlayerError({ + message: "Источник не настроен", + detail: "переменная 'NEXT_PUBLIC_KODIK_PARSER_URL' не обнаружена", + }); + return { manifest: null, poster: null }; + } + const data = await _fetchPlayer( - `https://anix-player.wah.su/?url=${url}&player=kodik`, + `${NEXT_PUBLIC_KODIK_PARSER_URL}/?url=${url}&player=kodik`, setPlayerError ); if (data) { @@ -204,10 +214,17 @@ export const _fetchAnilibriaManifest = async ( const id = url.split("?id=")[1].split("&ep=")[0]; const epid = url.split("?id=")[1].split("&ep=")[1]; const _url = `https://api.anilibria.tv/v3/title?id=${id}`; - const data = await _fetchPlayer( - `https://anix-player.wah.su/?url=${_url}&player=libria`, - setPlayerError - ); + let data = null; + const NEXT_PUBLIC_ANILIBRIA_PARSER_URL = env("NEXT_PUBLIC_ANILIBRIA_PARSER_URL") + if (NEXT_PUBLIC_ANILIBRIA_PARSER_URL) { + data = await _fetchPlayer( + `${NEXT_PUBLIC_ANILIBRIA_PARSER_URL}/?url=${_url}&player=libria`, + setPlayerError + ); + } else { + data = await _fetchPlayer(_url, setPlayerError); + } + if (data) { const host = `https://${data.player.host}`; const ep = data.player.list[epid]; @@ -229,8 +246,16 @@ export const _fetchSibnetManifest = async ( setPlayerError: (state) => void ) => { // Fetch data via cloud endpoint + const NEXT_PUBLIC_SIBNET_PARSER_URL = env("NEXT_PUBLIC_SIBNET_PARSER_URL") + if (!NEXT_PUBLIC_SIBNET_PARSER_URL) { + setPlayerError({ + message: "Источник не настроен", + detail: "переменная 'NEXT_PUBLIC_SIBNET_PARSER_URL' не обнаружена", + }); + return { manifest: null, poster: null }; + } const data = await _fetchPlayer( - `https://sibnet.anix-player.wah.su/?url=${url}`, + `${NEXT_PUBLIC_SIBNET_PARSER_URL}/?url=${url}&player=sibnet`, setPlayerError ); if (data) { diff --git a/app/components/SettingsModal/SettingsModal.tsx b/app/components/SettingsModal/SettingsModal.tsx index daafeae..90ee3c3 100644 --- a/app/components/SettingsModal/SettingsModal.tsx +++ b/app/components/SettingsModal/SettingsModal.tsx @@ -267,23 +267,6 @@ export const SettingsModal = (props: { isOpen: boolean; setIsOpen: any }) => { checked={preferenceStore.flags.saveWatchHistory} /> -
-
-

Отправка аналитики

-

- Требуется перезагрузка для применения -

-
- - preferenceStore.setFlags({ - enableAnalytics: !preferenceStore.flags.enableAnalytics, - }) - } - checked={preferenceStore.flags.enableAnalytics} - /> -

diff --git a/app/layout.tsx b/app/layout.tsx index 7fe42a7..0c0415f 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,6 +1,7 @@ import "./globals.css"; import { App } from "./App"; import { ThemeModeScript } from "flowbite-react"; +import { PublicEnvScript } from 'next-runtime-env'; export const metadata = { metadataBase: new URL("https://anix.wah.su"), @@ -33,6 +34,7 @@ export default function RootLayout({ children }) { return ( + {children} diff --git a/app/pages/About.tsx b/app/pages/About.tsx index 7f6ce53..62e8486 100644 --- a/app/pages/About.tsx +++ b/app/pages/About.tsx @@ -45,8 +45,8 @@ export const AboutPage = () => { }); return ( -
- +
+
{

AniX - это неофициальный веб-клиент для Android-приложения Anixart. Он позволяет вам получать доступ к своей учетной записи - Anixart и управлять ею из веб-браузера. Так-же можно - синхронизировать и управлять списками и избранным. И самое главное - смотреть все доступные аниме из базы Anixart. + Anixart и управлять ею из веб-браузера компьютера или телефона. + В клиенте доступна синхронизация с аккаунтом и управление его списками и избранным. + А самое главное - это возможность смотреть все доступные аниме из базы Anixart даже недоступные на территории РФ.

- - -
- developer image -
-

Radiquum

-

- Разработчик -

-
-
-
-
@@ -114,7 +95,7 @@ export const AboutPage = () => {
- +

Список изменений

{current.changelog} diff --git a/app/store/preferences.ts b/app/store/preferences.ts index c1e4e74..1225c2d 100644 --- a/app/store/preferences.ts +++ b/app/store/preferences.ts @@ -9,7 +9,6 @@ interface preferencesState { // saveSearchHistory: boolean; saveWatchHistory?: boolean; showChangelog?: boolean; - enableAnalytics?: boolean; showNavbarTitles?: "always" | "links" | "selected" | "never"; showFifthButton?: null | 3 | 4 | 5; }; @@ -43,7 +42,6 @@ export const usePreferencesStore = create()( // saveSearchHistory: true, saveWatchHistory: true, showChangelog: true, - enableAnalytics: true, showNavbarTitles: "always", showFifthButton: null, }, diff --git a/docs/deploy/fork.png b/docs/deploy/fork.png new file mode 100644 index 0000000..c1d2c68 Binary files /dev/null and b/docs/deploy/fork.png differ diff --git a/docs/deploy/netlify_env_1.png b/docs/deploy/netlify_env_1.png new file mode 100644 index 0000000..48da6fd Binary files /dev/null and b/docs/deploy/netlify_env_1.png differ diff --git a/docs/deploy/netlify_env_2.png b/docs/deploy/netlify_env_2.png new file mode 100644 index 0000000..30e4cc6 Binary files /dev/null and b/docs/deploy/netlify_env_2.png differ diff --git a/docs/deploy/netlify_import.png b/docs/deploy/netlify_import.png new file mode 100644 index 0000000..e3b65e5 Binary files /dev/null and b/docs/deploy/netlify_import.png differ diff --git a/docs/deploy/netlify_new_project.png b/docs/deploy/netlify_new_project.png new file mode 100644 index 0000000..6ff5207 Binary files /dev/null and b/docs/deploy/netlify_new_project.png differ diff --git a/docs/deploy/netlify_project_name.png b/docs/deploy/netlify_project_name.png new file mode 100644 index 0000000..516169f Binary files /dev/null and b/docs/deploy/netlify_project_name.png differ diff --git a/docs/deploy/netlify_provider.png b/docs/deploy/netlify_provider.png new file mode 100644 index 0000000..8e6c922 Binary files /dev/null and b/docs/deploy/netlify_provider.png differ diff --git a/docs/deploy/netlify_url.png b/docs/deploy/netlify_url.png new file mode 100644 index 0000000..659f3af Binary files /dev/null and b/docs/deploy/netlify_url.png differ diff --git a/docs/deploy/vercel_import.png b/docs/deploy/vercel_import.png new file mode 100644 index 0000000..0cf60f7 Binary files /dev/null and b/docs/deploy/vercel_import.png differ diff --git a/docs/deploy/vercel_new_project.png b/docs/deploy/vercel_new_project.png new file mode 100644 index 0000000..ecfe0f1 Binary files /dev/null and b/docs/deploy/vercel_new_project.png differ diff --git a/docs/deploy/vercel_project.png b/docs/deploy/vercel_project.png new file mode 100644 index 0000000..28bfde4 Binary files /dev/null and b/docs/deploy/vercel_project.png differ diff --git a/docs/deploy/vercel_url.png b/docs/deploy/vercel_url.png new file mode 100644 index 0000000..a1da372 Binary files /dev/null and b/docs/deploy/vercel_url.png differ diff --git a/extension/README.md b/extension/README.md deleted file mode 100644 index e016101..0000000 --- a/extension/README.md +++ /dev/null @@ -1,20 +0,0 @@ -Это расширение для firefox и chrome для добавления кнопки Смотреть в Anix на сайт anixart.tv, а так-же найти в Anix на сайт кинопоиск, если обнаружен жанр аниме - -## Скачать - -Firefox: https://addons.mozilla.org/en-US/firefox/addon/watch-on-anix/ - -Chrome: https://github.com/Radiquum/anix/raw/V3/extension/chrome/watch-on-anix-chrome.zip - -## Установка - -Firefox: - -- Загрузите расширение из AMO - -Chrome: - -1. скачайте и распакуйте архив -2. зайдите в расширения браузера chrome://extensions/ -3. включите режим разработчика -4. нажмите "загрузить распакованное расширение" и выберите директорию куда вы распаковали архив diff --git a/extension/chrome/icon-16x16.png b/extension/chrome/icon-16x16.png deleted file mode 100644 index 7f26af4..0000000 Binary files a/extension/chrome/icon-16x16.png and /dev/null differ diff --git a/extension/chrome/icon-32x32.png b/extension/chrome/icon-32x32.png deleted file mode 100644 index cf7fe7e..0000000 Binary files a/extension/chrome/icon-32x32.png and /dev/null differ diff --git a/extension/chrome/icon-48x48.png b/extension/chrome/icon-48x48.png deleted file mode 100644 index 615849c..0000000 Binary files a/extension/chrome/icon-48x48.png and /dev/null differ diff --git a/extension/chrome/icon-72x72.png b/extension/chrome/icon-72x72.png deleted file mode 100644 index 370fdb9..0000000 Binary files a/extension/chrome/icon-72x72.png and /dev/null differ diff --git a/extension/chrome/icon-96x96.png b/extension/chrome/icon-96x96.png deleted file mode 100644 index 6d16c65..0000000 Binary files a/extension/chrome/icon-96x96.png and /dev/null differ diff --git a/extension/chrome/main.js b/extension/chrome/main.js deleted file mode 100644 index 8929809..0000000 --- a/extension/chrome/main.js +++ /dev/null @@ -1,116 +0,0 @@ -function determineHost() { - const url = new URL(window.location.href); - return { - host: url.host, - pathname: url.pathname, - }; -} - -function addButtonToAnixart(pathname) { - // find a container and an open in app link with button - const container = document.querySelector('div[style="text-align: center;"]'); - const openInAppLink = document.querySelector('a[href^="anixart"'); - const openInAppLinkButton = openInAppLink.querySelector("button"); - openInAppLinkButton.style = "margin-top: 0px !important;"; // disable default button margin - openInAppLinkButton.classList = "btn btn-secondary"; // change default button from primary to secondary - - // create a custom footer - const footer = document.createElement("div"); - footer.style = - "display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; align-items: center; margin-top: 20px;"; - - // create and set custom link - const link = document.createElement("a"); - const button = document.createElement("button"); - button.style = "margin-top: 0px !important;"; - button.classList = "btn btn-primary"; - button.textContent = "Открыть в Anix"; - - const url = new URL(window.location.href); - link.href = `https://anix.wah.su${pathname}?ref=anixart.tv&source=extension`; - link.appendChild(button); - - // append link and open in app link to footer - footer.appendChild(link); - footer.appendChild(openInAppLink); - - // append footer to container - container.appendChild(footer); -} - -function kinopoiskIsAnimeGenrePresent() { - const genre = document.querySelector('a[href^="/lists/movies/genre--anime"]'); - - if (genre) { - return true; - } - return false; -} - -function addButtonToKinopoisk() { - let isAnime = kinopoiskIsAnimeGenrePresent(); - if (!isAnime) { - console.log("genre not found"); - return; - } - let title = document.querySelector('h1[itemprop="name"]'); - if (!title) { - console.log("title not found"); - return; - } - title = title.textContent.split(" (")[0]; - - const buttonStyle = ` - display: inline-block; - font-weight: 400; - text-align: center; - vertical-align: middle; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - background-color: transparent; - border: 1px solid transparent; - font-size: 1.5rem; - line-height: 1.5; - border-radius: .25rem; - transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out; - padding: 1rem 2rem; - color: #fff; - background-color: #F04E4E; - border-color: #F04E4E; - `; - - const buttonHoverStyle = ` - color: #fff !important; - background-color: #E23D3D !important; - border-color: #E23D3D !important; - ` - - const link = document.createElement("a"); - const button = document.createElement("button"); - - link.style = - "text-decoration: none; position: fixed; bottom: 0; right: 0; margin: 1.5rem; z-index: 1000;"; - link.href = "https://anix.wah.su/search?q=" + title + "&ref=kinopoisk.ru&source=extension"; - link.appendChild(button); - button.style = buttonStyle; - button.onmouseover = function () { - button.style = buttonStyle + buttonHoverStyle - } - button.onmouseout = function () { - button.style = buttonStyle; - } - button.textContent = "Найти в Anix"; - - document.body.appendChild(link); -} - -const { host, pathname } = determineHost(); - -if (host == "anixart.tv") { - addButtonToAnixart(pathname); -} else if (host == "www.kinopoisk.ru") { - addButtonToKinopoisk(); -} diff --git a/extension/chrome/manifest.json b/extension/chrome/manifest.json deleted file mode 100644 index 9c33721..0000000 --- a/extension/chrome/manifest.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "manifest_version": 3, - "version": "1.2", - "name": "Watch on Anix", - "description": "Adds a button to watch on Anix.", - "content_scripts": [ - { - "matches": [ - "https://anixart.tv/release/*", - "https://anixart.tv/collection/*", - "https://anixart.tv/profile/*", - "https://www.kinopoisk.ru/film/*", - "https://www.kinopoisk.ru/series/*" - ], - "js": [ - "main.js" - ] - } - ], - "icons": { - "16": "icon-16x16.png", - "32": "icon-32x32.png", - "48": "icon-48x48.png", - "72": "icon-72x72.png", - "96": "icon-96x96.png" - } -} \ No newline at end of file diff --git a/extension/chrome/watch-on-anix-chrome.zip b/extension/chrome/watch-on-anix-chrome.zip deleted file mode 100644 index 72a3f70..0000000 Binary files a/extension/chrome/watch-on-anix-chrome.zip and /dev/null differ diff --git a/extension/firefox/icon-16x16.png b/extension/firefox/icon-16x16.png deleted file mode 100644 index 7f26af4..0000000 Binary files a/extension/firefox/icon-16x16.png and /dev/null differ diff --git a/extension/firefox/icon-32x32.png b/extension/firefox/icon-32x32.png deleted file mode 100644 index cf7fe7e..0000000 Binary files a/extension/firefox/icon-32x32.png and /dev/null differ diff --git a/extension/firefox/icon-48x48.png b/extension/firefox/icon-48x48.png deleted file mode 100644 index 615849c..0000000 Binary files a/extension/firefox/icon-48x48.png and /dev/null differ diff --git a/extension/firefox/icon-72x72.png b/extension/firefox/icon-72x72.png deleted file mode 100644 index 370fdb9..0000000 Binary files a/extension/firefox/icon-72x72.png and /dev/null differ diff --git a/extension/firefox/icon-96x96.png b/extension/firefox/icon-96x96.png deleted file mode 100644 index 6d16c65..0000000 Binary files a/extension/firefox/icon-96x96.png and /dev/null differ diff --git a/extension/firefox/main.js b/extension/firefox/main.js deleted file mode 100644 index 8929809..0000000 --- a/extension/firefox/main.js +++ /dev/null @@ -1,116 +0,0 @@ -function determineHost() { - const url = new URL(window.location.href); - return { - host: url.host, - pathname: url.pathname, - }; -} - -function addButtonToAnixart(pathname) { - // find a container and an open in app link with button - const container = document.querySelector('div[style="text-align: center;"]'); - const openInAppLink = document.querySelector('a[href^="anixart"'); - const openInAppLinkButton = openInAppLink.querySelector("button"); - openInAppLinkButton.style = "margin-top: 0px !important;"; // disable default button margin - openInAppLinkButton.classList = "btn btn-secondary"; // change default button from primary to secondary - - // create a custom footer - const footer = document.createElement("div"); - footer.style = - "display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; align-items: center; margin-top: 20px;"; - - // create and set custom link - const link = document.createElement("a"); - const button = document.createElement("button"); - button.style = "margin-top: 0px !important;"; - button.classList = "btn btn-primary"; - button.textContent = "Открыть в Anix"; - - const url = new URL(window.location.href); - link.href = `https://anix.wah.su${pathname}?ref=anixart.tv&source=extension`; - link.appendChild(button); - - // append link and open in app link to footer - footer.appendChild(link); - footer.appendChild(openInAppLink); - - // append footer to container - container.appendChild(footer); -} - -function kinopoiskIsAnimeGenrePresent() { - const genre = document.querySelector('a[href^="/lists/movies/genre--anime"]'); - - if (genre) { - return true; - } - return false; -} - -function addButtonToKinopoisk() { - let isAnime = kinopoiskIsAnimeGenrePresent(); - if (!isAnime) { - console.log("genre not found"); - return; - } - let title = document.querySelector('h1[itemprop="name"]'); - if (!title) { - console.log("title not found"); - return; - } - title = title.textContent.split(" (")[0]; - - const buttonStyle = ` - display: inline-block; - font-weight: 400; - text-align: center; - vertical-align: middle; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - background-color: transparent; - border: 1px solid transparent; - font-size: 1.5rem; - line-height: 1.5; - border-radius: .25rem; - transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out; - padding: 1rem 2rem; - color: #fff; - background-color: #F04E4E; - border-color: #F04E4E; - `; - - const buttonHoverStyle = ` - color: #fff !important; - background-color: #E23D3D !important; - border-color: #E23D3D !important; - ` - - const link = document.createElement("a"); - const button = document.createElement("button"); - - link.style = - "text-decoration: none; position: fixed; bottom: 0; right: 0; margin: 1.5rem; z-index: 1000;"; - link.href = "https://anix.wah.su/search?q=" + title + "&ref=kinopoisk.ru&source=extension"; - link.appendChild(button); - button.style = buttonStyle; - button.onmouseover = function () { - button.style = buttonStyle + buttonHoverStyle - } - button.onmouseout = function () { - button.style = buttonStyle; - } - button.textContent = "Найти в Anix"; - - document.body.appendChild(link); -} - -const { host, pathname } = determineHost(); - -if (host == "anixart.tv") { - addButtonToAnixart(pathname); -} else if (host == "www.kinopoisk.ru") { - addButtonToKinopoisk(); -} diff --git a/extension/firefox/manifest.json b/extension/firefox/manifest.json deleted file mode 100644 index 5a7cbb3..0000000 --- a/extension/firefox/manifest.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "manifest_version": 2, - "version": "1.2", - "name": "Watch on Anix", - "description": "Adds a button to watch on Anix.", - "browser_specific_settings": { - "gecko": { - "id": "{8c53d0c2-43ad-4498-b700-290bd2e1030f}" - } - }, - "content_scripts": [ - { - "matches": [ - "https://anixart.tv/release/*", - "https://anixart.tv/collection/*", - "https://anixart.tv/profile/*", - "https://www.kinopoisk.ru/film/*", - "https://www.kinopoisk.ru/series/*" - ], - "js": [ - "main.js" - ] - } - ], - "icons": { - "16": "icon-16x16.png", - "32": "icon-32x32.png", - "48": "icon-48x48.png", - "72": "icon-72x72.png", - "96": "icon-96x96.png" - } -} \ No newline at end of file diff --git a/extension/firefox/watch-on-anix-firefox.zip b/extension/firefox/watch-on-anix-firefox.zip deleted file mode 100644 index 1f286a2..0000000 Binary files a/extension/firefox/watch-on-anix-firefox.zip and /dev/null differ diff --git a/next.config.js b/next.config.js index bdba023..24aa345 100644 --- a/next.config.js +++ b/next.config.js @@ -1,11 +1,16 @@ -const { withPlausibleProxy } = require("next-plausible"); const withFlowbiteReact = require("flowbite-react/plugin/nextjs"); /** @type {import('next').NextConfig} */ const NextConfig = { + output: "standalone", reactStrictMode: false, images: { unoptimized: true, }, + env: { + NEXT_PUBLIC_KODIK_PARSER_URL: process.env.NEXT_PUBLIC_KODIK_PARSER_URL, + NEXT_PUBLIC_ANILIBRIA_PARSER_URL: process.env.NEXT_PUBLIC_ANILIBRIA_PARSER_URL, + NEXT_PUBLIC_SIBNET_PARSER_URL: process.env.NEXT_PUBLIC_SIBNET_PARSER_URL, + }, async headers() { return [ { @@ -75,8 +80,6 @@ const NextConfig = { }, }; -const config = withPlausibleProxy({ - customDomain: "https://analytics.wah.su", -})(withFlowbiteReact(NextConfig)); +const config = withFlowbiteReact(NextConfig); module.exports = config; diff --git a/package-lock.json b/package-lock.json index 42763d4..6309d00 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "markdown-to-jsx": "^7.4.7", "media-chrome": "^4.9.0", "next": "^14.2.26", - "next-plausible": "^3.12.1", + "next-runtime-env": "^3.3.0", "prettier": "^3.5.3", "react": "^18", "react-cropper": "^2.3.3", @@ -4956,18 +4956,18 @@ } } }, - "node_modules/next-plausible": { - "version": "3.12.4", - "resolved": "https://registry.npmjs.org/next-plausible/-/next-plausible-3.12.4.tgz", - "integrity": "sha512-cD3+ixJxf8yBYvsideTxqli3fvrB7R4BXcvsNJz8Sm2X1QN039WfiXjCyNWkub4h5++rRs6fHhchUMnOuJokcg==", + "node_modules/next-runtime-env": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/next-runtime-env/-/next-runtime-env-3.3.0.tgz", + "integrity": "sha512-JgKVnog9mNbjbjH9csVpMnz2tB2cT5sLF+7O47i6Ze/s/GoiKdV7dHhJHk1gwXpo6h5qPj5PTzryldtSjvrHuQ==", "license": "MIT", - "funding": { - "url": "https://github.com/4lejandrito/next-plausible?sponsor=1" + "dependencies": { + "next": "^14", + "react": "^18" }, "peerDependencies": { - "next": "^11.1.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 ", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + "next": "^14", + "react": "^18" } }, "node_modules/next/node_modules/postcss": { diff --git a/package.json b/package.json index cd174cc..ec1be21 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "markdown-to-jsx": "^7.4.7", "media-chrome": "^4.9.0", "next": "^14.2.26", - "next-plausible": "^3.12.1", + "next-runtime-env": "^3.3.0", "prettier": "^3.5.3", "react": "^18", "react-cropper": "^2.3.3", diff --git a/player-parsers/.dockerignore b/player-parsers/.dockerignore new file mode 100644 index 0000000..4400aea --- /dev/null +++ b/player-parsers/.dockerignore @@ -0,0 +1,2 @@ +node_modules +README.md \ No newline at end of file diff --git a/player-parsers/Dockerfile b/player-parsers/Dockerfile new file mode 100644 index 0000000..0963569 --- /dev/null +++ b/player-parsers/Dockerfile @@ -0,0 +1,15 @@ +FROM node:23-alpine + +LABEL org.opencontainers.image.source=https://github.com/radiquum/anix + +WORKDIR /app + +COPY package.json package-lock.json ./ +RUN npm ci + +COPY *.ts ./ + +EXPOSE 7000 +ENV PORT=7000 +ENV HOSTNAME="0.0.0.0" +CMD ["npm", "run", "serve"] \ No newline at end of file diff --git a/player-parsers/README.RU.md b/player-parsers/README.RU.md new file mode 100644 index 0000000..d206410 --- /dev/null +++ b/player-parsers/README.RU.md @@ -0,0 +1,114 @@ +# AniX - Player Parsers + +Данный под-проект позволяет получить прямые ссылки на видеофайлы с источников Sibnet, Kodik, Anilibria (источник: libria) + +Он может использоваться как для основного проекта AniX, так и как отдельный сервис. + +В основном проекте, парсеры используются для работы своего плеера, если вам не важна данная функция, вы можете не развёртывать данный суб-сервис. + +Лицензия: [MIT](../LICENSE) + +## Использование + +В строке веб-браузера необходимо ввести: + +`://<:port>/?url=&player=` + +где: + +- http|https - схема по которой будет осуществляться подключение к сервису +- ip|domain - IP адрес или домен на котором находится сервис +- :port - порт сервиса, опционально +- VIDEO_URL - ссылка на видео от источника +- PLAYER_SOURCE - источник, один из: kodik, sibnet, libria + +> [!NOTE] +> Если используется источник libria, ссылка должна быть ссылкой на API anilibria, а не на плеер + +Ответ: + +- 500|400: произошла ошибка, подробнее в строке `message` в теле ответа +- 200: запрос прошёл успешно + +## Развёртывание + +> [!IMPORTANT] +> В связи с спецификой источников, рекомендуется использовать виртуальный сервер в россии, т.к. они могут быть недоступны из других стран. +> +> Из-за данной специфики, парсеры невозможно развернуть на edge сервисах, таких как Cloudflare Workers или Deno, а только на отдельном сервере. + +### Docker + +Требования: + +- [docker](https://docs.docker.com/engine/install/) + +### Пре-билд + +1. выполните команду: + +`docker run -d --name anix-player -p 7000:7000 radiquum/anix-player-parser:latest` + +### Ручной билд + +Доп. Требования: + +- [git](https://git-scm.com/) + +1. Клонируйте репозиторий `git clone https://github.com/Radiquum/AniX` +2. Переместитесь в директорию репозитория `cd AniX` +3. Переместитесь в директорию парсеров `cd player-parsers` +4. Выполните команду `docker build -t anix-player-parser .` +5. После окончания, выполните команду: `docker run -d --restart always --name anix-player -p 7000:7000 anix-player-parser` + +### docker/Обозначения + +- -d - запустить контейнер в фоне +- --restart always - всегда запускать после перезагрузки сервера +- --name - название контейнера +- -p - порт контейнера который будет доступен извне. ПОРТ:7000 + +### docker/После развёртывания + +Сервис будет доступен по адресу: `http://<ВАШ IP><:ВАШ ПОРТ>/` + +### docker/Примечание + +Для использования своего домена и поддержки протокола HTTPS, вы можете использовать Traefik или другой reverse-proxy, с сертификатом SSL. + +Полезные ссылки: + +- [Конвертер из команды docker run в синтакс для docker compose](https://it-tools.tech/docker-run-to-docker-compose-converter) +- [Как настроить Traefik + свой домен + SSL](https://letmegooglethat.com/?q=how+to+setup+traefik+with+custom+domain+and+ssl+certificate+from+lets+encrypt%3F) + +### pm2 + +Требования: + +- [git](https://git-scm.com/) +- [nodejs 23+ с npm](http://nodejs.org/) +- [pm2](https://pm2.keymetrics.io/) + +Инструкция: + +1. Клонируйте репозиторий `git clone https://github.com/Radiquum/AniX` +2. Переместитесь в директорию репозитория `cd AniX` +3. Переместитесь в директорию парсеров `cd player-parsers` +4. Выполните команду `npm install` +5. После окончания и выполните команду `pm2 start index.ts -n anix-player-parser` + +### pm2/Обозначения + +- -n - название сервиса в pm2 + +### pm2/После развёртывания + +Сервис будет доступен по адресу: `http://<ВАШ IP>:7000/` + +### pm2/Примечание + +Для автоматического запуска приложения, рекомендуется настроить pm2 на автозапуск, с помощью команды: `pm2 startup` + +Полезные ссылки: + +- [PM2: подходим к вопросу процесс-менеджмента с умом @ Habr](https://habr.com/ru/articles/480670/) diff --git a/player-parsers/README.md b/player-parsers/README.md new file mode 100644 index 0000000..a0bc338 --- /dev/null +++ b/player-parsers/README.md @@ -0,0 +1,114 @@ +# AniX - Player Parsers + +This sub-project allows obtaining direct video file links from sources Sibnet, Kodik, Anilibria (source: libria) + +It can be used both for the main AniX project and as a standalone service. + +In the main project, the parsers are used to operate the internal player. If this function is not important to you, you may choose not to deploy this sub-service. + +License: [MIT](../LICENSE) + +## Usage + +In the web browser address bar, enter: + +`://<:port>/?url=&player=` + +where: + +- http|https - the scheme used to connect to the service +- ip|domain - IP address or domain where the service is hosted +- :port - service port, optional +- VIDEO_URL - the link to the video from the source +- PLAYER_SOURCE - the source, one of: kodik, sibnet, libria + +> [!NOTE] +> When using libria source, url should be the url to the anilibria api, not player directly + +Response: + +- 500|400: an error occurred, see the `message` field in the response body for details +- 200: request was successful + +## Deployment + +> [!IMPORTANT] +> Due to the nature of the sources, it is recommended to use a virtual server in Russia, as they may be inaccessible from other countries. +> +> Because of this specificity, the parsers cannot be deployed on edge services like Cloudflare Workers or Deno, only on a dedicated server. + +### Docker + +Requirements: + +- [docker](https://docs.docker.com/engine/install/) + +### Pre-built + +1. Run the command: + +`docker run -d --name anix-player -p 7000:7000 radiquum/anix-player-parser:latest` + +### Manual build + +Additional Requirements: + +- [git](https://git-scm.com/) + +1. Clone the repository `git clone https://github.com/Radiquum/AniX` +2. Navigate to the repository directory `cd AniX` +3. Navigate to the parsers directory `cd player-parsers` +4. Run the command `docker build -t anix-player-parser .` +5. Once finished, run the command: `docker run -d --restart always --name anix-player -p 7000:7000 anix-player-parser` + +### docker/Legend + +- -d - run container in the background +- --restart always - always restart after server reboot +- --name - container name +- -p - container port accessible externally. PORT:7000 + +### docker/After deployment + +The service will be available at: `http://<:YOUR PORT>/` + +### docker/Note + +To use your own domain and support the HTTPS protocol, you can use Traefik or another reverse-proxy with an SSL certificate. + +Useful links: + +- [Docker run to docker compose syntax converter](https://it-tools.tech/docker-run-to-docker-compose-converter) +- [How to setup Traefik + custom domain + SSL](https://letmegooglethat.com/?q=how+to+setup+traefik+with+custom+domain+and+ssl+certificate+from+lets+encrypt%3F) + +### pm2 + +Requirements: + +- [git](https://git-scm.com/) +- [nodejs 23+ with npm](http://nodejs.org/) +- [pm2](https://pm2.keymetrics.io/) + +Instructions: + +1. Clone the repository `git clone https://github.com/Radiquum/AniX` +2. Navigate to the repository directory `cd AniX` +3. Navigate to the parsers directory `cd player-parsers` +4. Run the command `npm install` +5. Once finished, run the command `pm2 start index.ts -n anix-player-parser` + +### pm2/Legend + +- -n - service name in pm2 + +### pm2/After deployment + +The service will be available at: `http://:7000/` + +### pm2/Note + +To enable automatic application start, it is recommended to configure pm2 to start on boot with the command: `pm2 startup` + +Useful links: + +- [PM2: smart approach to process management @ Habr](https://habr.com/ru/articles/480670/) diff --git a/player-parsers/index.ts b/player-parsers/index.ts new file mode 100644 index 0000000..e3b891f --- /dev/null +++ b/player-parsers/index.ts @@ -0,0 +1,46 @@ +import { asJSON } from "./shared"; +import { getAnilibriaURL } from "./libria"; +import { getSibnetURL } from "./sibnet"; +import { getKodikURL } from "./kodik"; + +import express from "express"; +const app = express(); + +const host = "0.0.0.0"; +const port = 7000; +const allowedPlayers = ["kodik", "libria", "sibnet"]; + +app.get("/", (req, res) => { + const urlParams = new URLSearchParams(req.query) + const url = urlParams.get("url"); + const player = urlParams.get("player"); + + if (!url) { + asJSON(res, { message: "no 'url' query provided" }, 400) + return + } + + if (!player) { + asJSON(res, { message: "no 'player' query provided" }, 400) + return + } + + switch (player) { + case "libria": + getAnilibriaURL(res, url) + return + case "sibnet": + getSibnetURL(res, url) + return + case "kodik": + getKodikURL(res, url) + return + default: + asJSON(res, { message: `player '${player}' is not supported. choose one of: ${allowedPlayers.join(", ")}` }, 400) + return + } +}); + +app.listen(port, host, function () { + console.log(`Server listens http://${host}:${port}`); +}); diff --git a/player-parsers/kodik.ts b/player-parsers/kodik.ts new file mode 100644 index 0000000..e28444d --- /dev/null +++ b/player-parsers/kodik.ts @@ -0,0 +1,95 @@ +import { asJSON, randomUA } from "./shared"; +const altDomains = ["kodik.info", "aniqit.com", "kodik.cc", "kodik.biz"]; + +export async function getKodikURL(res, url: string) { + const origDomain = url.replace("https://", "").split("/")[0]; + let domain = url.replace("https://", "").split("/")[0]; + + if (!altDomains.includes(domain)) { + asJSON(res, { message: "Wrong url provided for player kodik" }, 400); + return; + } + + let user_agent = randomUA(); + + let pageRes = await fetch(url, { + headers: { + "User-Agent": user_agent, + }, + }); + + if (!pageRes.ok) { + for (let i = 0; i < altDomains.length; i++) { + if (url.includes(altDomains[i])) { + continue; + } + + user_agent = randomUA(); + const altDomain = altDomains[i]; + const altUrl = url.replace( + `https://${origDomain}/`, + `https://${altDomain}/` + ); + + domain = altDomain; + pageRes = await fetch(altUrl, { + headers: { + "User-Agent": user_agent, + }, + }); + + if (pageRes.ok) { + break; + } + } + } + + if (!pageRes.ok) { + asJSON(res, { message: "KODIK: failed to load page" }, 500); + return; + } + + const pageData = await pageRes.text(); + const urlParamsRe = /var urlParams = .*;$/m; + const urlParamsMatch = urlParamsRe.exec(pageData); + + if (!urlParamsMatch || urlParamsMatch.length == 0) { + asJSON(res, { message: `KODIK: failed to find data to parse` }, 500); + return; + } + + const urlParamsStr = urlParamsMatch[0] + .replace("var urlParams = '", "") + .replace("';", ""); + + const urlStr = url.replace(`https://${origDomain}/`, ""); + const type = urlStr.split("/")[0]; + const id = urlStr.split("/")[1]; + const hash = urlStr.split("/")[2]; + + const urlParams = JSON.parse(urlParamsStr); + urlParams["type"] = type; + urlParams["id"] = id; + urlParams["hash"] = hash; + + const formData = new FormData(); + for (const [key, value] of Object.entries(urlParams)) { + formData.append(key, value as any); + } + + const linksRes = await fetch(`https://${domain}/ftor`, { + method: "POST", + body: formData, + headers: { + "User-Agent": user_agent, + }, + }); + + if (!linksRes.ok) { + asJSON(res, { message: `KODIK: failed to get links` }, 500); + return; + } + + asJSON(res, await linksRes.json(), 200); + return; +} diff --git a/player-parsers/libria.ts b/player-parsers/libria.ts new file mode 100644 index 0000000..a907074 --- /dev/null +++ b/player-parsers/libria.ts @@ -0,0 +1,17 @@ +import { asJSON } from "./shared"; + +export async function getAnilibriaURL(res, url: string) { + + if (!url.includes("anilibria")) { + asJSON(res, { message: "Wrong url provided for player libria" }, 400); + return + } + + let apiRes = await fetch(url); + if (!apiRes.ok) { + asJSON(res, { message: "LIBRIA: failed to get api response" }, 500); + return + } + asJSON(res, await apiRes.json(), 200); + return +} diff --git a/player-parsers/package-lock.json b/player-parsers/package-lock.json new file mode 100644 index 0000000..d628dca --- /dev/null +++ b/player-parsers/package-lock.json @@ -0,0 +1,1311 @@ +{ + "name": "player-parsers", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "player-parsers", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "express": "^5.1.0", + "tsx": "^4.19.4" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", + "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", + "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", + "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", + "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", + "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", + "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", + "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", + "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", + "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", + "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", + "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", + "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", + "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", + "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", + "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", + "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", + "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", + "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", + "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", + "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", + "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", + "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", + "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", + "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", + "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", + "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.5", + "@esbuild/android-arm": "0.25.5", + "@esbuild/android-arm64": "0.25.5", + "@esbuild/android-x64": "0.25.5", + "@esbuild/darwin-arm64": "0.25.5", + "@esbuild/darwin-x64": "0.25.5", + "@esbuild/freebsd-arm64": "0.25.5", + "@esbuild/freebsd-x64": "0.25.5", + "@esbuild/linux-arm": "0.25.5", + "@esbuild/linux-arm64": "0.25.5", + "@esbuild/linux-ia32": "0.25.5", + "@esbuild/linux-loong64": "0.25.5", + "@esbuild/linux-mips64el": "0.25.5", + "@esbuild/linux-ppc64": "0.25.5", + "@esbuild/linux-riscv64": "0.25.5", + "@esbuild/linux-s390x": "0.25.5", + "@esbuild/linux-x64": "0.25.5", + "@esbuild/netbsd-arm64": "0.25.5", + "@esbuild/netbsd-x64": "0.25.5", + "@esbuild/openbsd-arm64": "0.25.5", + "@esbuild/openbsd-x64": "0.25.5", + "@esbuild/sunos-x64": "0.25.5", + "@esbuild/win32-arm64": "0.25.5", + "@esbuild/win32-ia32": "0.25.5", + "@esbuild/win32-x64": "0.25.5" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-tsconfig": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tsx": { + "version": "4.19.4", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.4.tgz", + "integrity": "sha512-gK5GVzDkJK1SI1zwHf32Mqxf2tSJkNx+eYcNly5+nHvWqXUJYUkWBQtKauoESz3ymezAI++ZwT855x5p5eop+Q==", + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + } + } +} diff --git a/player-parsers/package.json b/player-parsers/package.json new file mode 100644 index 0000000..d0fb9e2 --- /dev/null +++ b/player-parsers/package.json @@ -0,0 +1,14 @@ +{ + "name": "player-parsers", + "version": "1.0.0", + "description": "Player Parsing for AniX", + "scripts": { + "serve": "npx tsx ./index.ts" + }, + "author": "", + "license": "MIT", + "dependencies": { + "express": "^5.1.0", + "tsx": "^4.19.4" + } +} diff --git a/player-parsers/shared.ts b/player-parsers/shared.ts new file mode 100644 index 0000000..fd3a955 --- /dev/null +++ b/player-parsers/shared.ts @@ -0,0 +1,48 @@ +export const corsHeaders = { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET,HEAD,POST,OPTIONS", + "Cache-Control": "no-cache", +}; + +export const resHeaders = { + ...corsHeaders, + "Content-Type": "application/json", +}; + +export const USERAGENTS = [ + "Mozilla/5.0 (Linux; Android 12.0; LG G8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.2.7124.71 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 11.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.5.1269.13 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/123.0 Firefox/123.0", + "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:94.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.8.4576.73 Safari/537.36", + "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:121.0) Gecko/121.0 Firefox/121.0", + "Mozilla/5.0 (Linux; Android 11.0; OnePlus 10T Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.8.1484.76 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.9.9841.32 Safari/537.36", + "Mozilla/5.0 (Windows NT 11.0; Win64; x64; rv:124.0) Gecko/124.0 Firefox/124.0", + "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:94.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.3457.25 Safari/537.36", + "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:124.0) Gecko/124.0 Firefox/124.0", + "Mozilla/5.0 (Linux; Android 13.0; Pixel 3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.3.1166.27 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.6.4126.27 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:130.0) Gecko/130.0 Firefox/130.0", + "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:94.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.3.4677.74 Safari/537.36", + "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:122.0) Gecko/122.0 Firefox/122.0", + "Mozilla/5.0 (Linux; Android 12.0; Xiaomi Redmi Note 9) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.6.3806.92 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 11.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.3.9963.85 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/122.0 Firefox/122.0", + "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:94.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.8.5618.48 Safari/537.36", + "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:126.0) Gecko/126.0 Firefox/126.0", + "Mozilla/5.0 (Linux; Android 12.0; Huawei Mate 40) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6740.69 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 11.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.9.2666.21 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/132.0 Firefox/132.0", + "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:94.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.4804.4 Safari/537.36", + "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:128.0) Gecko/128.0 Firefox/128.0", +]; + +export function asJSON(res, object: any, status: number) { + res.status(status).type("application/json"); + res.set(corsHeaders); + res.send(JSON.stringify(object)); +} + +export function randomUA() { + return USERAGENTS[Math.floor(Math.random() * USERAGENTS.length)]; +} diff --git a/player-parsers/sibnet.ts b/player-parsers/sibnet.ts new file mode 100644 index 0000000..5df85b8 --- /dev/null +++ b/player-parsers/sibnet.ts @@ -0,0 +1,59 @@ +import { asJSON, randomUA } from "./shared"; + +export async function getSibnetURL(res, url: string) { + + if (!url.includes("sibnet")) { + asJSON(res, { message: "Wrong url provided for player sibnet" }, 400); + return + } + + const user_agent = randomUA(); + + let pageRes = await fetch(url, { + headers: { + "User-Agent": user_agent, + }, + }); + if (!pageRes.ok) { + asJSON(res, { message: `SIBNET:${pageRes.status}: failed to load page` }, 500) + return + } + const pageData = await pageRes.text(); + const videoRe = /\/v\/.*?\.mp4/; + const videoMatch = videoRe.exec(pageData); + + if (!videoMatch || videoMatch.length == 0) { + asJSON(res, { message: `SIBNET: failed to find data to parse` }, 500) + return + } + + const posterRe = /\/upload\/cover\/.*?\.jpg/; + const posterMatch = posterRe.exec(pageData); + + const actualVideoRes = await fetch( + `https://video.sibnet.ru${videoMatch[0]}`, + { + headers: { + "User-Agent": user_agent, + Referer: url, + }, + redirect: "manual", + } + ); + + if (!actualVideoRes.headers.get("location")) { + asJSON(res, { message: `SIBNET: failed to get video link` }, 500) + return + } + + const video = actualVideoRes.headers.get("location"); + const poster = + posterMatch ? + posterMatch.length > 0 ? + `https://st.sibnet.ru${posterMatch[0]}` + : null + : null; + + asJSON(res, { video, poster }, 200) + return +} diff --git a/tsconfig.json b/tsconfig.json index f8a929b..8fdb696 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -33,5 +33,5 @@ "**/*.tsx", "next.config.js" ], - "exclude": ["node_modules"] + "exclude": ["node_modules", "player-parsers"] } diff --git a/vercel.json b/vercel.json index 4197322..78bf024 100644 --- a/vercel.json +++ b/vercel.json @@ -1,10 +1,4 @@ { - "git": { - "deploymentEnabled": { - "V3": false, - "V2": false - } - }, "headers": [ { "source": "/bookmarks/:slug*",