Compare commits
28 commits
Author | SHA1 | Date | |
---|---|---|---|
9931962a6b | |||
23eaa82993 | |||
be01230d0a | |||
038122fc2a | |||
3c5ec6c39f | |||
f249cb85ec | |||
e79d1e92b8 | |||
780c7b0f27 | |||
43da1862ca | |||
|
1f28580be7 | ||
2cfdfcfb28 | |||
97a70690bb | |||
79fb473998 | |||
a73c01e5bc | |||
9f7a7b8fd2 | |||
ed20227b1a | |||
767121c77b | |||
23da153e65 | |||
631936b022 | |||
a55a6c93dc | |||
8486e9eb82 | |||
9a2a547217 | |||
51a919e2b2 | |||
990b3c1736 | |||
743f756920 | |||
f5caefdc49 | |||
88064f05c4 | |||
c2fc453406 |
68
.dockerignore
Normal file
|
@ -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
|
5
.env.sample
Normal file
|
@ -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= # Домен парсера сибнет, требуется для просмотра с данного источника
|
||||||
|
# ---
|
28
.github/workflows/DeployPreviewToVercel.yml
vendored
|
@ -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 }}
|
|
28
.github/workflows/DeployTagToVercel.yml
vendored
|
@ -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 }}
|
|
1
.gitignore
vendored
|
@ -61,3 +61,4 @@ videos/*
|
||||||
public/_next-video
|
public/_next-video
|
||||||
|
|
||||||
API-Trace/*
|
API-Trace/*
|
||||||
|
.env
|
180
DEPLOYMENT.RU.md
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
# Развёртывание приложения AniX
|
||||||
|
|
||||||
|
## Vercel
|
||||||
|
|
||||||
|
Требования:
|
||||||
|
|
||||||
|
- аккаунт GitHub
|
||||||
|
- аккаунт Vercel
|
||||||
|
|
||||||
|
1. Создайте форк репозитория
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
2. Войдите в аккаунт Vercel
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> Аккаунт Vercel должен быть связан с аккаунтом GitHub.
|
||||||
|
>
|
||||||
|
> Если у вас нет аккаунта Vercel, то создайте его через вход с помощью GitHub.
|
||||||
|
|
||||||
|
3. Нажмите кнопку создать новый проект
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
4. Нажмите кнопку импортировать напротив названия репозитория
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
5. (опционально) добавьте переменные для использования своего плеера:
|
||||||
|
|
||||||
|
- NEXT_PUBLIC_KODIK_PARSER_URL
|
||||||
|
- NEXT_PUBLIC_ANILIBRIA_PARSER_URL
|
||||||
|
- NEXT_PUBLIC_SIBNET_PARSER_URL
|
||||||
|
|
||||||
|
на те которые вы получили, если развёртывали [anix-player-parsers](./player-parsers/README.RU.md)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
6. нажмите кнопку "Deploy" и ожидайте пока не появится подтверждение
|
||||||
|
7. нажмите кнопку "Continue to Dashboard"
|
||||||
|
8. клиент будет доступен по ссылке такого вида, нажмите на неё чтобы его открыть
|
||||||
|

|
||||||
|
|
||||||
|
## Netlify
|
||||||
|
|
||||||
|
Требования:
|
||||||
|
|
||||||
|
- аккаунт GitHub
|
||||||
|
- аккаунт Netlify
|
||||||
|
|
||||||
|
1. Создайте форк репозитория
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
2. Войдите в аккаунт Netlify
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> Аккаунт Netlify должен быть связан с аккаунтом GitHub.
|
||||||
|
>
|
||||||
|
> Если у вас нет аккаунта Netlify, то создайте его через вход с помощью GitHub.
|
||||||
|
|
||||||
|
3. Нажмите кнопку создать новый проект
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
4. Нажмите кнопку GitHub
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
5. Нажмите на название репозитория
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
6. (опционально) заполните название проекта
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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. 
|
||||||
|
|
||||||
|
2. 
|
||||||
|
|
||||||
|
8. нажмите кнопку "Deploy" и ожидайте пока не появится подтверждение
|
||||||
|
|
||||||
|
9. клиент будет доступен по ссылке такого вида, нажмите на неё чтобы его открыть
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 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/)
|
180
DEPLOYMENT.md
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
# AniX Application Deployment
|
||||||
|
|
||||||
|
## Vercel
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
|
||||||
|
- GitHub account
|
||||||
|
- Vercel account
|
||||||
|
|
||||||
|
1. Fork the repository
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
4. Click the import button next to the repository name
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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
|
||||||
|

|
||||||
|
|
||||||
|
## Netlify
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
|
||||||
|
- GitHub account
|
||||||
|
- Netlify account
|
||||||
|
|
||||||
|
1. Fork the repository
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
4. Click the GitHub button
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
5. Click the repository name
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
6. (optional) Fill in the project name
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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. 
|
||||||
|
|
||||||
|
2. 
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 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 IP><: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 `<new_dir>`)
|
||||||
|
7. Move the following files into the new directory (`<new_dir>`):
|
||||||
|
- move `public` directory to `<new_dir>/public`
|
||||||
|
- move `.next/static` directory to `<new_dir>/.next/static`
|
||||||
|
- move files from `.next/standalone` to `<new_dir>`
|
||||||
|
8. Move into the created directory (<new_dir>) 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://<YOUR IP>: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/)
|
32
Dockerfile
Normal file
|
@ -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"]
|
|
@ -2,20 +2,20 @@
|
||||||
|
|
||||||
AniX - это неофициальный веб-клиент для Android-приложения Anixart. Он позволяет вам получать доступ к своей учетной записи Anixart и управлять ею из веб-браузера на вашем настольном компьютере или ноутбуке.
|
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 для получения наиболее актуальных функций.
|
Пожалуйста, обратите внимание, что Anix является неофициальным проектом и не связан с разработчиками Anixart. Рекомендуется использовать официальное приложение Anixart для получения наиболее актуальных функций.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[[RU] ПРОЧТИ МЕНЯ](./README.RU.md) | [[EN] README](./README.md)
|
||||||
|
|
||||||
|
[[RU] РАЗВЁРТЫВАНИЕ](./DEPLOYMENT.RU.md) | [[EN] DEPLOY](./DEPLOYMENT.md)
|
||||||
|
|
||||||
|
[[RU] Списки изменений](./public/changelog)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Скриншоты
|
## Скриншоты
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
@ -53,12 +53,6 @@ AniX - это неофициальный веб-клиент для Android-пр
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## Возможности
|
|
||||||
|
|
||||||
1. Используйте свой существующий аккаунт на Anixart
|
|
||||||
2. Синхронизируйте списки, историю просмотров, коллекции и многое другое
|
|
||||||
3. используйте практически все функции приложения для Android
|
|
||||||
|
|
||||||
## Внесение вклада
|
## Внесение вклада
|
||||||
|
|
||||||
Мы приветствуем вклад в этот проект! Если у вас есть какие-либо исправления ошибок, улучшения или новые функции, пожалуйста, не стесняйтесь отправлять запрос на обновление.
|
Мы приветствуем вклад в этот проект! Если у вас есть какие-либо исправления ошибок, улучшения или новые функции, пожалуйста, не стесняйтесь отправлять запрос на обновление.
|
26
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.
|
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
|
## 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.
|
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
|
## Screenshots
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
@ -53,12 +53,6 @@ Please note that AniX is an unofficial project and is not affiliated with the de
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## 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
|
## 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.
|
We welcome contributions to this project! If you have any bug fixes, improvements, or new features, please feel free to create a pull request.
|
17
app/App.tsx
|
@ -4,10 +4,15 @@ import { usePreferencesStore } from "./store/preferences";
|
||||||
import { Navbar } from "./components/Navbar/NavbarUpdate";
|
import { Navbar } from "./components/Navbar/NavbarUpdate";
|
||||||
import { Inter } from "next/font/google";
|
import { Inter } from "next/font/google";
|
||||||
import { useEffect, useState } from "react";
|
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 { Spinner } from "./components/Spinner/Spinner";
|
||||||
import { ChangelogModal } from "#/components/ChangelogModal/ChangelogModal";
|
import { ChangelogModal } from "#/components/ChangelogModal/ChangelogModal";
|
||||||
import PlausibleProvider from "next-plausible";
|
|
||||||
import { Bounce, ToastContainer } from "react-toastify";
|
import { Bounce, ToastContainer } from "react-toastify";
|
||||||
|
|
||||||
const inter = Inter({ subsets: ["latin"] });
|
const inter = Inter({ subsets: ["latin"] });
|
||||||
|
@ -104,14 +109,6 @@ export const App = (props) => {
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</Modal>
|
</Modal>
|
||||||
{preferencesStore.flags.enableAnalytics && (
|
|
||||||
<PlausibleProvider
|
|
||||||
domain="anix.wah.su"
|
|
||||||
trackLocalhost={false}
|
|
||||||
selfHosted={true}
|
|
||||||
enabled={true}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<ToastContainer
|
<ToastContainer
|
||||||
className={"mx-2 mb-20 sm:mb-0"}
|
className={"mx-2 mb-20 sm:mb-0"}
|
||||||
position="bottom-center"
|
position="bottom-center"
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { tryCatchPlayer, tryCatchAPI } from "#/api/utils";
|
import { tryCatchPlayer, tryCatchAPI } from "#/api/utils";
|
||||||
|
import { env } from 'next-runtime-env';
|
||||||
|
|
||||||
export async function _fetchAPI(
|
export async function _fetchAPI(
|
||||||
url: string,
|
url: string,
|
||||||
|
@ -75,8 +76,17 @@ export const _fetchKodikManifest = async (
|
||||||
setPlayerError: (state) => void
|
setPlayerError: (state) => void
|
||||||
) => {
|
) => {
|
||||||
// Fetch episode links via edge function
|
// 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(
|
const data = await _fetchPlayer(
|
||||||
`https://anix-player.wah.su/?url=${url}&player=kodik`,
|
`${NEXT_PUBLIC_KODIK_PARSER_URL}/?url=${url}&player=kodik`,
|
||||||
setPlayerError
|
setPlayerError
|
||||||
);
|
);
|
||||||
if (data) {
|
if (data) {
|
||||||
|
@ -204,10 +214,17 @@ export const _fetchAnilibriaManifest = async (
|
||||||
const id = url.split("?id=")[1].split("&ep=")[0];
|
const id = url.split("?id=")[1].split("&ep=")[0];
|
||||||
const epid = url.split("?id=")[1].split("&ep=")[1];
|
const epid = url.split("?id=")[1].split("&ep=")[1];
|
||||||
const _url = `https://api.anilibria.tv/v3/title?id=${id}`;
|
const _url = `https://api.anilibria.tv/v3/title?id=${id}`;
|
||||||
const data = await _fetchPlayer(
|
let data = null;
|
||||||
`https://anix-player.wah.su/?url=${_url}&player=libria`,
|
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
|
setPlayerError
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
data = await _fetchPlayer(_url, setPlayerError);
|
||||||
|
}
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
const host = `https://${data.player.host}`;
|
const host = `https://${data.player.host}`;
|
||||||
const ep = data.player.list[epid];
|
const ep = data.player.list[epid];
|
||||||
|
@ -229,8 +246,16 @@ export const _fetchSibnetManifest = async (
|
||||||
setPlayerError: (state) => void
|
setPlayerError: (state) => void
|
||||||
) => {
|
) => {
|
||||||
// Fetch data via cloud endpoint
|
// 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(
|
const data = await _fetchPlayer(
|
||||||
`https://sibnet.anix-player.wah.su/?url=${url}`,
|
`${NEXT_PUBLIC_SIBNET_PARSER_URL}/?url=${url}&player=sibnet`,
|
||||||
setPlayerError
|
setPlayerError
|
||||||
);
|
);
|
||||||
if (data) {
|
if (data) {
|
||||||
|
|
|
@ -267,23 +267,6 @@ export const SettingsModal = (props: { isOpen: boolean; setIsOpen: any }) => {
|
||||||
checked={preferenceStore.flags.saveWatchHistory}
|
checked={preferenceStore.flags.saveWatchHistory}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<p className=" dark:text-white">Отправка аналитики</p>
|
|
||||||
<p className="text-gray-500 dark:text-gray-300">
|
|
||||||
Требуется перезагрузка для применения
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<ToggleSwitch
|
|
||||||
color="blue"
|
|
||||||
onChange={() =>
|
|
||||||
preferenceStore.setFlags({
|
|
||||||
enableAnalytics: !preferenceStore.flags.enableAnalytics,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
checked={preferenceStore.flags.enableAnalytics}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<HR className="my-4 dark:bg-slate-400" />
|
<HR className="my-4 dark:bg-slate-400" />
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="w-6 h-6 iconify material-symbols--experiment-outline"></span>
|
<span className="w-6 h-6 iconify material-symbols--experiment-outline"></span>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import { App } from "./App";
|
import { App } from "./App";
|
||||||
import { ThemeModeScript } from "flowbite-react";
|
import { ThemeModeScript } from "flowbite-react";
|
||||||
|
import { PublicEnvScript } from 'next-runtime-env';
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
metadataBase: new URL("https://anix.wah.su"),
|
metadataBase: new URL("https://anix.wah.su"),
|
||||||
|
@ -33,6 +34,7 @@ export default function RootLayout({ children }) {
|
||||||
return (
|
return (
|
||||||
<html lang="en" suppressHydrationWarning>
|
<html lang="en" suppressHydrationWarning>
|
||||||
<head>
|
<head>
|
||||||
|
<PublicEnvScript />
|
||||||
<ThemeModeScript />
|
<ThemeModeScript />
|
||||||
</head>
|
</head>
|
||||||
<App>{children}</App>
|
<App>{children}</App>
|
||||||
|
|
|
@ -45,8 +45,8 @@ export const AboutPage = () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 gap-2 md:grid-cols-2 lg:grid-cols-3">
|
<div className="grid grid-cols-1 gap-2 mb-4 md:grid-cols-2">
|
||||||
<Card className="md:col-span-2 lg:col-span-3">
|
<Card className="md:col-span-2">
|
||||||
<div className="flex flex-col items-center gap-4 md:flex-row">
|
<div className="flex flex-col items-center gap-4 md:flex-row">
|
||||||
<Image
|
<Image
|
||||||
src="/images/icons/icon-512x512.png"
|
src="/images/icons/icon-512x512.png"
|
||||||
|
@ -62,32 +62,13 @@ export const AboutPage = () => {
|
||||||
<p className="max-w-[900px]">
|
<p className="max-w-[900px]">
|
||||||
AniX - это неофициальный веб-клиент для Android-приложения
|
AniX - это неофициальный веб-клиент для Android-приложения
|
||||||
Anixart. Он позволяет вам получать доступ к своей учетной записи
|
Anixart. Он позволяет вам получать доступ к своей учетной записи
|
||||||
Anixart и управлять ею из веб-браузера. Так-же можно
|
Anixart и управлять ею из веб-браузера компьютера или телефона.
|
||||||
синхронизировать и управлять списками и избранным. И самое главное
|
В клиенте доступна синхронизация с аккаунтом и управление его списками и избранным.
|
||||||
смотреть все доступные аниме из базы Anixart.
|
А самое главное - это возможность смотреть все доступные аниме из базы Anixart даже недоступные на территории РФ.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<Link href={"https://wah.su/radiquum"} target="_blank">
|
|
||||||
<Card>
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<Image
|
|
||||||
src="https://radiquum.wah.su/static/avatar_512.jpg"
|
|
||||||
className="flex-shrink-0 w-16 h-16 rounded-full"
|
|
||||||
alt="developer image"
|
|
||||||
width={128}
|
|
||||||
height={128}
|
|
||||||
/>
|
|
||||||
<div>
|
|
||||||
<h1 className="text-xl font-bold">Radiquum</h1>
|
|
||||||
<p className="text-sm text-gray-500 dark:text-gray-200">
|
|
||||||
Разработчик
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</Link>
|
|
||||||
<Link href={"https://t.me/anix_web"} target="_blank">
|
<Link href={"https://t.me/anix_web"} target="_blank">
|
||||||
<Card>
|
<Card>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
|
@ -114,7 +95,7 @@ export const AboutPage = () => {
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</Link>
|
</Link>
|
||||||
<Card className="md:col-span-2 lg:col-span-3">
|
<Card className="md:col-span-2">
|
||||||
<h1 className="text-2xl font-bold">Список изменений</h1>
|
<h1 className="text-2xl font-bold">Список изменений</h1>
|
||||||
<Markdown className={Styles.markdown}>{current.changelog}</Markdown>
|
<Markdown className={Styles.markdown}>{current.changelog}</Markdown>
|
||||||
<Accordion collapseAll={true}>
|
<Accordion collapseAll={true}>
|
||||||
|
|
|
@ -9,7 +9,6 @@ interface preferencesState {
|
||||||
// saveSearchHistory: boolean;
|
// saveSearchHistory: boolean;
|
||||||
saveWatchHistory?: boolean;
|
saveWatchHistory?: boolean;
|
||||||
showChangelog?: boolean;
|
showChangelog?: boolean;
|
||||||
enableAnalytics?: boolean;
|
|
||||||
showNavbarTitles?: "always" | "links" | "selected" | "never";
|
showNavbarTitles?: "always" | "links" | "selected" | "never";
|
||||||
showFifthButton?: null | 3 | 4 | 5;
|
showFifthButton?: null | 3 | 4 | 5;
|
||||||
};
|
};
|
||||||
|
@ -43,7 +42,6 @@ export const usePreferencesStore = create<preferencesState>()(
|
||||||
// saveSearchHistory: true,
|
// saveSearchHistory: true,
|
||||||
saveWatchHistory: true,
|
saveWatchHistory: true,
|
||||||
showChangelog: true,
|
showChangelog: true,
|
||||||
enableAnalytics: true,
|
|
||||||
showNavbarTitles: "always",
|
showNavbarTitles: "always",
|
||||||
showFifthButton: null,
|
showFifthButton: null,
|
||||||
},
|
},
|
||||||
|
|
BIN
docs/deploy/fork.png
Normal file
After ![]() (image error) Size: 1.5 KiB |
BIN
docs/deploy/netlify_env_1.png
Normal file
After ![]() (image error) Size: 11 KiB |
BIN
docs/deploy/netlify_env_2.png
Normal file
After ![]() (image error) Size: 19 KiB |
BIN
docs/deploy/netlify_import.png
Normal file
After ![]() (image error) Size: 5.8 KiB |
BIN
docs/deploy/netlify_new_project.png
Normal file
After ![]() (image error) Size: 7 KiB |
BIN
docs/deploy/netlify_project_name.png
Normal file
After ![]() (image error) Size: 7 KiB |
BIN
docs/deploy/netlify_provider.png
Normal file
After ![]() (image error) Size: 19 KiB |
BIN
docs/deploy/netlify_url.png
Normal file
After ![]() (image error) Size: 39 KiB |
BIN
docs/deploy/vercel_import.png
Normal file
After ![]() (image error) Size: 13 KiB |
BIN
docs/deploy/vercel_new_project.png
Normal file
After ![]() (image error) Size: 9.2 KiB |
BIN
docs/deploy/vercel_project.png
Normal file
After ![]() (image error) Size: 49 KiB |
BIN
docs/deploy/vercel_url.png
Normal file
After ![]() (image error) Size: 70 KiB |
|
@ -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. нажмите "загрузить распакованное расширение" и выберите директорию куда вы распаковали архив
|
|
Before ![]() (image error) Size: 875 B |
Before ![]() (image error) Size: 2.6 KiB |
Before ![]() (image error) Size: 5.2 KiB |
Before ![]() (image error) Size: 10 KiB |
Before ![]() (image error) Size: 16 KiB |
|
@ -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();
|
|
||||||
}
|
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
Before ![]() (image error) Size: 875 B |
Before ![]() (image error) Size: 2.6 KiB |
Before ![]() (image error) Size: 5.2 KiB |
Before ![]() (image error) Size: 10 KiB |
Before ![]() (image error) Size: 16 KiB |
|
@ -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();
|
|
||||||
}
|
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +1,16 @@
|
||||||
const { withPlausibleProxy } = require("next-plausible");
|
|
||||||
const withFlowbiteReact = require("flowbite-react/plugin/nextjs");
|
const withFlowbiteReact = require("flowbite-react/plugin/nextjs");
|
||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const NextConfig = {
|
const NextConfig = {
|
||||||
|
output: "standalone",
|
||||||
reactStrictMode: false,
|
reactStrictMode: false,
|
||||||
images: {
|
images: {
|
||||||
unoptimized: true,
|
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() {
|
async headers() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
@ -75,8 +80,6 @@ const NextConfig = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const config = withPlausibleProxy({
|
const config = withFlowbiteReact(NextConfig);
|
||||||
customDomain: "https://analytics.wah.su",
|
|
||||||
})(withFlowbiteReact(NextConfig));
|
|
||||||
|
|
||||||
module.exports = config;
|
module.exports = config;
|
||||||
|
|
20
package-lock.json
generated
|
@ -17,7 +17,7 @@
|
||||||
"markdown-to-jsx": "^7.4.7",
|
"markdown-to-jsx": "^7.4.7",
|
||||||
"media-chrome": "^4.9.0",
|
"media-chrome": "^4.9.0",
|
||||||
"next": "^14.2.26",
|
"next": "^14.2.26",
|
||||||
"next-plausible": "^3.12.1",
|
"next-runtime-env": "^3.3.0",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-cropper": "^2.3.3",
|
"react-cropper": "^2.3.3",
|
||||||
|
@ -4956,18 +4956,18 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/next-plausible": {
|
"node_modules/next-runtime-env": {
|
||||||
"version": "3.12.4",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/next-plausible/-/next-plausible-3.12.4.tgz",
|
"resolved": "https://registry.npmjs.org/next-runtime-env/-/next-runtime-env-3.3.0.tgz",
|
||||||
"integrity": "sha512-cD3+ixJxf8yBYvsideTxqli3fvrB7R4BXcvsNJz8Sm2X1QN039WfiXjCyNWkub4h5++rRs6fHhchUMnOuJokcg==",
|
"integrity": "sha512-JgKVnog9mNbjbjH9csVpMnz2tB2cT5sLF+7O47i6Ze/s/GoiKdV7dHhJHk1gwXpo6h5qPj5PTzryldtSjvrHuQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"dependencies": {
|
||||||
"url": "https://github.com/4lejandrito/next-plausible?sponsor=1"
|
"next": "^14",
|
||||||
|
"react": "^18"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"next": "^11.1.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 ",
|
"next": "^14",
|
||||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
"react": "^18"
|
||||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/next/node_modules/postcss": {
|
"node_modules/next/node_modules/postcss": {
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
"markdown-to-jsx": "^7.4.7",
|
"markdown-to-jsx": "^7.4.7",
|
||||||
"media-chrome": "^4.9.0",
|
"media-chrome": "^4.9.0",
|
||||||
"next": "^14.2.26",
|
"next": "^14.2.26",
|
||||||
"next-plausible": "^3.12.1",
|
"next-runtime-env": "^3.3.0",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-cropper": "^2.3.3",
|
"react-cropper": "^2.3.3",
|
||||||
|
|
2
player-parsers/.dockerignore
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
node_modules
|
||||||
|
README.md
|
15
player-parsers/Dockerfile
Normal file
|
@ -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"]
|
114
player-parsers/README.RU.md
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
# AniX - Player Parsers
|
||||||
|
|
||||||
|
Данный под-проект позволяет получить прямые ссылки на видеофайлы с источников Sibnet, Kodik, Anilibria (источник: libria)
|
||||||
|
|
||||||
|
Он может использоваться как для основного проекта AniX, так и как отдельный сервис.
|
||||||
|
|
||||||
|
В основном проекте, парсеры используются для работы своего плеера, если вам не важна данная функция, вы можете не развёртывать данный суб-сервис.
|
||||||
|
|
||||||
|
Лицензия: [MIT](../LICENSE)
|
||||||
|
|
||||||
|
## Использование
|
||||||
|
|
||||||
|
В строке веб-браузера необходимо ввести:
|
||||||
|
|
||||||
|
`<http|https>://<ip|domain><:port>/?url=<VIDEO_URL>&player=<PLAYER_SOURCE>`
|
||||||
|
|
||||||
|
где:
|
||||||
|
|
||||||
|
- 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/)
|
114
player-parsers/README.md
Normal file
|
@ -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:
|
||||||
|
|
||||||
|
`<http|https>://<ip|domain><:port>/?url=<VIDEO_URL>&player=<PLAYER_SOURCE>`
|
||||||
|
|
||||||
|
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 IP><: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://<YOUR IP>: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/)
|
46
player-parsers/index.ts
Normal file
|
@ -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}`);
|
||||||
|
});
|
95
player-parsers/kodik.ts
Normal file
|
@ -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;
|
||||||
|
}
|
17
player-parsers/libria.ts
Normal file
|
@ -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
|
||||||
|
}
|
1311
player-parsers/package-lock.json
generated
Normal file
14
player-parsers/package.json
Normal file
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
48
player-parsers/shared.ts
Normal file
|
@ -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)];
|
||||||
|
}
|
59
player-parsers/sibnet.ts
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -33,5 +33,5 @@
|
||||||
"**/*.tsx",
|
"**/*.tsx",
|
||||||
"next.config.js"
|
"next.config.js"
|
||||||
],
|
],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules", "player-parsers"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,4 @@
|
||||||
{
|
{
|
||||||
"git": {
|
|
||||||
"deploymentEnabled": {
|
|
||||||
"V3": false,
|
|
||||||
"V2": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"headers": [
|
"headers": [
|
||||||
{
|
{
|
||||||
"source": "/bookmarks/:slug*",
|
"source": "/bookmarks/:slug*",
|
||||||
|
|