Compare commits
8 commits
743f756920
...
767121c77b
Author | SHA1 | Date | |
---|---|---|---|
767121c77b | |||
23da153e65 | |||
631936b022 | |||
a55a6c93dc | |||
8486e9eb82 | |||
9a2a547217 | |||
51a919e2b2 | |||
990b3c1736 |
|
@ -1,5 +1,5 @@
|
|||
# пример заполнения: example.com
|
||||
NEXT_PUBLIC_KODIK_PARSER_DOMAIN= # Домен парсера кодика, требуется для просмотра с данного источника
|
||||
NEXT_PUBLIC_ANILIBRIA_PARSER_DOMAIN= # Домен парсера анилибрии, если не заполнено, используется официальное апи
|
||||
NEXT_PUBLIC_SIBNET_PARSER_DOMAIN= # Домен парсера сибнет, требуется для просмотра с данного источника
|
||||
# пример заполнения: https://example.com, http://0.0.0.0:80
|
||||
NEXT_PUBLIC_KODIK_PARSER_URL= # Домен парсера кодика, требуется для просмотра с данного источника
|
||||
NEXT_PUBLIC_ANILIBRIA_PARSER_URL= # Домен парсера анилибрии, если не заполнено, используется официальное апи
|
||||
NEXT_PUBLIC_SIBNET_PARSER_URL= # Домен парсера сибнет, требуется для просмотра с данного источника
|
||||
# ---
|
|
@ -75,16 +75,16 @@ export const _fetchKodikManifest = async (
|
|||
setPlayerError: (state) => void
|
||||
) => {
|
||||
// Fetch episode links via edge function
|
||||
if (!process.env.NEXT_PUBLIC_KODIK_PARSER_DOMAIN) {
|
||||
if (!process.env.NEXT_PUBLIC_KODIK_PARSER_URL) {
|
||||
setPlayerError({
|
||||
message: "Источник не настроен",
|
||||
detail: "переменная 'NEXT_PUBLIC_KODIK_PARSER_DOMAIN' не обнаружена",
|
||||
detail: "переменная 'NEXT_PUBLIC_KODIK_PARSER_URL' не обнаружена",
|
||||
});
|
||||
return { manifest: null, poster: null };
|
||||
}
|
||||
|
||||
const data = await _fetchPlayer(
|
||||
`https://${process.env.NEXT_PUBLIC_KODIK_PARSER_DOMAIN}/?url=${url}&player=kodik`,
|
||||
`${process.env.NEXT_PUBLIC_KODIK_PARSER_URL}/?url=${url}&player=kodik`,
|
||||
setPlayerError
|
||||
);
|
||||
if (data) {
|
||||
|
@ -213,9 +213,9 @@ export const _fetchAnilibriaManifest = async (
|
|||
const epid = url.split("?id=")[1].split("&ep=")[1];
|
||||
const _url = `https://api.anilibria.tv/v3/title?id=${id}`;
|
||||
let data = null;
|
||||
if (process.env.NEXT_PUBLIC_ANILIBRIA_PARSER_DOMAIN) {
|
||||
if (process.env.NEXT_PUBLIC_ANILIBRIA_PARSER_URL) {
|
||||
data = await _fetchPlayer(
|
||||
`https://${process.env.NEXT_PUBLIC_ANILIBRIA_PARSER_DOMAIN}/?url=${_url}&player=libria`,
|
||||
`${process.env.NEXT_PUBLIC_ANILIBRIA_PARSER_URL}/?url=${_url}&player=libria`,
|
||||
setPlayerError
|
||||
);
|
||||
} else {
|
||||
|
@ -243,15 +243,15 @@ export const _fetchSibnetManifest = async (
|
|||
setPlayerError: (state) => void
|
||||
) => {
|
||||
// Fetch data via cloud endpoint
|
||||
if (!process.env.NEXT_PUBLIC_SIBNET_PARSER_DOMAIN) {
|
||||
if (!process.env.NEXT_PUBLIC_SIBNET_PARSER_URL) {
|
||||
setPlayerError({
|
||||
message: "Источник не настроен",
|
||||
detail: "переменная 'NEXT_PUBLIC_SIBNET_PARSER_DOMAIN' не обнаружена",
|
||||
detail: "переменная 'NEXT_PUBLIC_SIBNET_PARSER_URL' не обнаружена",
|
||||
});
|
||||
return { manifest: null, poster: null };
|
||||
}
|
||||
const data = await _fetchPlayer(
|
||||
`https://${process.env.NEXT_PUBLIC_SIBNET_PARSER_DOMAIN}/?url=${url}`,
|
||||
`${process.env.NEXT_PUBLIC_SIBNET_PARSER_URL}/?url=${url}&player=sibnet`,
|
||||
setPlayerError
|
||||
);
|
||||
if (data) {
|
||||
|
|
|
@ -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 Width: | Height: | Size: 875 B |
Before Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | 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 Width: | Height: | Size: 875 B |
Before Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | 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"
|
||||
}
|
||||
}
|
2
player-parsers/.dockerignore
Normal file
|
@ -0,0 +1,2 @@
|
|||
node_modules
|
||||
README.md
|
12
player-parsers/Dockerfile
Normal file
|
@ -0,0 +1,12 @@
|
|||
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 ./
|
||||
|
||||
CMD ["npm", "run", "serve"]
|
116
player-parsers/README.RU.md
Normal file
|
@ -0,0 +1,116 @@
|
|||
# AniX - Player Parsers
|
||||
|
||||
Данный под-проект позволяет получить прямые ссылки на видеофайлы с источников Sibnet, Kodik, 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
|
||||
|
||||
Ответ:
|
||||
|
||||
- 500|400: произошла ошибка, подробнее в строке `message` в теле ответа
|
||||
- 200: запрос прошёл успешно
|
||||
|
||||
## Развёртывание
|
||||
|
||||
> В связи с спецификой источников, рекомендуется использовать виртуальный сервер в россии или нидерландах, т.к. они могут быть недоступны из других стран.
|
||||
>
|
||||
> Из-за данной специфики, парсеры невозможно развернуть на edge сервисах, таких как CloudFlare Workers или Deno, а только на отдельном сервере.
|
||||
|
||||
<details>
|
||||
<summary>С помощью docker</summary>
|
||||
|
||||
Требования:
|
||||
|
||||
- [docker](https://docs.docker.com/engine/install/)
|
||||
|
||||
### Пре-билд
|
||||
|
||||
1. выполните комманду:
|
||||
|
||||
`docker run -d --name anix-player -p 7000:7000 ghcr.io/radiquum/anix-player-parser:29-05-2025`
|
||||
|
||||
### Ручной билд
|
||||
|
||||
Доп. Требования:
|
||||
|
||||
- [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`
|
||||
|
||||
### Обозначения
|
||||
|
||||
- -d - запустить контейнер в фоне
|
||||
- --restart always - всегда запускать после перезагрузки сервера
|
||||
- --name - название контейнера
|
||||
- -p - порт контейнера который будет доступен из вне. ПОРТ:7000
|
||||
|
||||
### После развёртывания
|
||||
|
||||
Сервис будет доступен по адресу: `http://<ВАШ IP><:ВАШ ПОРТ>/`
|
||||
|
||||
### Примечание
|
||||
|
||||
Для использования своего домена и поддержки протокола 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)
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>С помощью pm2</summary>
|
||||
|
||||
Требования:
|
||||
|
||||
- [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`
|
||||
|
||||
### Обозначения
|
||||
|
||||
- -n - название сервиса в pm2
|
||||
|
||||
### После развёртывания
|
||||
|
||||
Сервис будет доступен по адресу: `http://<ВАШ IP>:7000/`
|
||||
|
||||
### Примечание
|
||||
|
||||
Для автоматического запуска приложения, рекомендуется настроить pm2 на автозапуск, с помощью команды: `pm2 startup`
|
||||
|
||||
Полезные ссылки:
|
||||
|
||||
- [PM2: подходим к вопросу процесс-менеджмента с умом @ Habr](https://habr.com/ru/articles/480670/)
|
||||
|
||||
</details>
|
116
player-parsers/README.md
Normal file
|
@ -0,0 +1,116 @@
|
|||
# AniX - Player Parsers
|
||||
|
||||
This sub-project allows obtaining direct video file links from sources Sibnet, Kodik, 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
|
||||
|
||||
Response:
|
||||
|
||||
- 500|400: an error occurred, see the `message` field in the response body for details
|
||||
- 200: request was successful
|
||||
|
||||
## Deployment
|
||||
|
||||
> Due to the nature of the sources, it is recommended to use a virtual server in Russia or the Netherlands, 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.
|
||||
|
||||
<details>
|
||||
<summary>Using docker</summary>
|
||||
|
||||
Requirements:
|
||||
|
||||
- [docker](https://docs.docker.com/engine/install/)
|
||||
|
||||
### Pre-built
|
||||
|
||||
1. Run the command:
|
||||
|
||||
`docker run -d --name anix-player -p 7000:7000 ghcr.io/radiquum/anix-player-parser:29-05-2025`
|
||||
|
||||
### 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`
|
||||
|
||||
### Legend
|
||||
|
||||
- -d - run the container in background
|
||||
- --restart always - always restart after server reboot
|
||||
- --name - container name
|
||||
- -p - container port accessible externally. PORT:7000
|
||||
|
||||
### After deployment
|
||||
|
||||
The service will be available at: `http://<YOUR IP><:YOUR PORT>/`
|
||||
|
||||
### 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)
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Using pm2</summary>
|
||||
|
||||
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`
|
||||
|
||||
### Legend
|
||||
|
||||
- -n - service name in pm2
|
||||
|
||||
### After deployment
|
||||
|
||||
The service will be available at: `http://<YOUR IP>:7000/`
|
||||
|
||||
### 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/)
|
||||
|
||||
</details>
|
45
player-parsers/index.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
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 url = req.query.url;
|
||||
const player = req.query.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 - 1; 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"
|
||||
}
|
||||
}
|
33
player-parsers/shared.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
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 (Macintosh; Intel Mac OS X 10_15) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Safari/605.1.15",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Windows; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/603.3.8 (KHTML, like Gecko) Version/10.1.2 Safari/603.3.8",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Windows; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Safari/605.1.15",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Safari/605.1.15",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Windows; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36",
|
||||
];
|
||||
|
||||
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 - 1)]
|
||||
}
|
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",
|
||||
"next.config.js"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
"exclude": ["node_modules", "player-parsers"]
|
||||
}
|
||||
|
|