mirror of
https://github.com/Radiquum/AniX.git
synced 2025-09-05 14:05:36 +05:00
rename player-parsers to player-parser for consistency with docker image
This commit is contained in:
parent
4701f6f62e
commit
1a1b548d39
15 changed files with 11 additions and 11 deletions
3
player-parser/.dockerignore
Normal file
3
player-parser/.dockerignore
Normal file
|
@ -0,0 +1,3 @@
|
|||
node_modules
|
||||
README.md
|
||||
README.RU.md
|
14
player-parser/Dockerfile
Normal file
14
player-parser/Dockerfile
Normal file
|
@ -0,0 +1,14 @@
|
|||
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
|
||||
|
||||
CMD ["npm", "run", "serve"]
|
111
player-parser/README.RU.md
Normal file
111
player-parser/README.RU.md
Normal file
|
@ -0,0 +1,111 @@
|
|||
# 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
|
||||
|
||||
Ответ:
|
||||
|
||||
- 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/)
|
111
player-parser/README.md
Normal file
111
player-parser/README.md
Normal file
|
@ -0,0 +1,111 @@
|
|||
# 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
|
||||
|
||||
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/)
|
62
player-parser/index.ts
Normal file
62
player-parser/index.ts
Normal file
|
@ -0,0 +1,62 @@
|
|||
import { asJSON } from "./shared";
|
||||
import { getAnilibriaURL } from "./libria";
|
||||
import { getSibnetURL } from "./sibnet";
|
||||
import { getKodikURL } from "./kodik";
|
||||
|
||||
import express from "express";
|
||||
const app = express();
|
||||
app.use(function (req, res, next) {
|
||||
res.header("Access-Control-Allow-Origin", req.headers.origin || "*");
|
||||
res.header(
|
||||
"Access-Control-Allow-Headers",
|
||||
"Origin, X-Requested-With, Content-Type, Accept"
|
||||
);
|
||||
res.header("Access-Control-Allow-Methods", "GET,HEAD,POST,OPTIONS");
|
||||
next();
|
||||
});
|
||||
|
||||
const HOST = process.env.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(req, res, { message: "no 'url' query provided" }, 400);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!player) {
|
||||
asJSON(req, res, { message: "no 'player' query provided" }, 400);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (player) {
|
||||
case "libria":
|
||||
getAnilibriaURL(req, res, url);
|
||||
return;
|
||||
case "sibnet":
|
||||
getSibnetURL(req, res, url);
|
||||
return;
|
||||
case "kodik":
|
||||
getKodikURL(req, res, url);
|
||||
return;
|
||||
default:
|
||||
asJSON(
|
||||
req,
|
||||
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}`);
|
||||
});
|
177
player-parser/kodik.ts
Normal file
177
player-parser/kodik.ts
Normal file
|
@ -0,0 +1,177 @@
|
|||
import { asJSON, randomUA } from "./shared";
|
||||
const altDomains = ["kodik.info", "aniqit.com", "kodik.cc", "kodik.biz"];
|
||||
|
||||
export async function getKodikURL(req, res, url: string) {
|
||||
const origDomain = url.replace("https://", "").split("/")[0];
|
||||
let domain = url.replace("https://", "").split("/")[0];
|
||||
|
||||
if (!altDomains.includes(domain)) {
|
||||
asJSON(req, res, { message: "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(req, res, { message: "KODIK: Не удалось загрузить страницу с плеером" }, 500);
|
||||
return;
|
||||
}
|
||||
|
||||
const pageData = await pageRes.text();
|
||||
const urlParamsRe = /var urlParams = .*;$/m;
|
||||
const urlParamsMatch = urlParamsRe.exec(pageData);
|
||||
|
||||
if (!urlParamsMatch || urlParamsMatch.length == 0) {
|
||||
asJSON(req, res, { message: `KODIK: Не удалось найти данные эпизода` }, 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(req, res, { message: `KODIK: Не удалось получить прямую ссылку` }, 500);
|
||||
return;
|
||||
}
|
||||
|
||||
let data = stripResponse(await linksRes.json());
|
||||
if (isEncrypted(data)) {
|
||||
for (const [key] of Object.entries(data.links)) {
|
||||
data.links[key][0].src = decryptSrc(data.links[key][0].src);
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasProto(data)) {
|
||||
for (const [key] of Object.entries(data.links)) {
|
||||
data.links[key][0].src = addProto(data.links[key][0].src);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isAnimeTvSeries(data)) {
|
||||
data["manifest"] = data.links[data.default][0].src.replace(
|
||||
`${data.default}.mp4:hls:`,
|
||||
""
|
||||
);
|
||||
} else {
|
||||
data["manifest"] = createManifest(data);
|
||||
}
|
||||
|
||||
data["poster"] = data.links[data.default][0].src.replace(
|
||||
`${data.default}.mp4:hls:manifest.m3u8`,
|
||||
"thumb001.jpg"
|
||||
);
|
||||
|
||||
asJSON(req, res, data, 200);
|
||||
return;
|
||||
}
|
||||
|
||||
function stripResponse(data) {
|
||||
return {
|
||||
default: data.default,
|
||||
links: data.links,
|
||||
};
|
||||
}
|
||||
|
||||
function isEncrypted(data) {
|
||||
return !data.links[data.default][0].src.includes("//");
|
||||
}
|
||||
|
||||
function decryptSrc(enc: string) {
|
||||
const decryptedBase64 = enc.replace(/[a-zA-Z]/g, (e: any) => {
|
||||
return String.fromCharCode(
|
||||
(e <= "Z" ? 90 : 122) >= (e = e.charCodeAt(0) + 18) ? e : e - 26
|
||||
);
|
||||
});
|
||||
return atob(decryptedBase64);
|
||||
}
|
||||
|
||||
function hasProto(data) {
|
||||
return data.links[data.default][0].src.startsWith("http");
|
||||
}
|
||||
|
||||
function addProto(string) {
|
||||
return `https:${string}`;
|
||||
}
|
||||
|
||||
function isAnimeTvSeries(data) {
|
||||
return (
|
||||
data.links[data.default][0].src.includes("animetvseries") ||
|
||||
data.links[data.default][0].src.includes("tvseries")
|
||||
);
|
||||
}
|
||||
|
||||
function createManifest(data) {
|
||||
const resolutions = {
|
||||
240: "427x240",
|
||||
360: "578x360",
|
||||
480: "854x480",
|
||||
720: "1280x720",
|
||||
1080: "1920x1080",
|
||||
};
|
||||
|
||||
const stringBuilder: string[] = [];
|
||||
|
||||
stringBuilder.push("#EXTM3U");
|
||||
for (const [key] of Object.entries(data.links)) {
|
||||
stringBuilder.push(`#EXT-X-STREAM-INF:RESOLUTION=${resolutions[key]}`);
|
||||
stringBuilder.push(data.links[key][0].src);
|
||||
}
|
||||
|
||||
return stringBuilder.join("\n");
|
||||
}
|
135
player-parser/libria.ts
Normal file
135
player-parser/libria.ts
Normal file
|
@ -0,0 +1,135 @@
|
|||
import { asJSON } from "./shared";
|
||||
|
||||
export interface APIStatusResponse {
|
||||
request: Request;
|
||||
is_alive: boolean;
|
||||
available_api_endpoints: string[];
|
||||
}
|
||||
|
||||
export interface Request {
|
||||
ip: string;
|
||||
country: string;
|
||||
iso_code: string;
|
||||
timezone: string;
|
||||
}
|
||||
|
||||
async function checkApiStatus(req, res) {
|
||||
const endpoints = ["https://anilibria.top", "https://anilibria.wtf"];
|
||||
let selectedEndpoint: string | null = null;
|
||||
|
||||
for (let i = 0; i < endpoints.length; i++) {
|
||||
const endpoint = endpoints[i];
|
||||
const apiRes = await fetch(`${endpoint}/api/v1/app/status`, {
|
||||
signal: AbortSignal.timeout(3000),
|
||||
});
|
||||
if (apiRes.ok) {
|
||||
const data: APIStatusResponse = await apiRes.json();
|
||||
if (data.is_alive != true) {
|
||||
asJSON(req, res, { message: "LIBRIA: API сервер не доступен" }, 500);
|
||||
return null;
|
||||
}
|
||||
selectedEndpoint = endpoint;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!selectedEndpoint) {
|
||||
asJSON(req, res, { message: "LIBRIA: Нет доступных эндпоинтов API" }, 500);
|
||||
return null;
|
||||
}
|
||||
|
||||
return selectedEndpoint;
|
||||
}
|
||||
|
||||
export async function getAnilibriaURL(req, res, url: string) {
|
||||
if (!url.includes("libria")) {
|
||||
asJSON(req, res, { message: "LIBRIA: Неправильная ссылка на плеер" }, 400);
|
||||
return;
|
||||
}
|
||||
|
||||
const apiEndpoint = await checkApiStatus(req, res);
|
||||
if (!apiEndpoint) {
|
||||
return;
|
||||
}
|
||||
|
||||
const decodedUrl = new URL(url);
|
||||
|
||||
const releaseId = decodedUrl.searchParams.get("id") || null;
|
||||
const releaseEp = decodedUrl.searchParams.get("ep") || null;
|
||||
|
||||
let apiRes = await fetch(`${apiEndpoint}/api/v1/anime/releases/${releaseId}`);
|
||||
if (!apiRes.ok) {
|
||||
if (apiRes.status == 404) {
|
||||
asJSON(req, res, { message: "LIBRIA: Релиз не найден" }, 404);
|
||||
return;
|
||||
}
|
||||
|
||||
asJSON(
|
||||
req,
|
||||
res,
|
||||
{ message: "LIBRIA: Ошибка получения ответа от API" },
|
||||
500
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let data = stripResponse(req, res, await apiRes.json(), releaseEp);
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (releaseEp) {
|
||||
data["manifest"] = createManifest(data);
|
||||
data["poster"] = getPoster(data);
|
||||
}
|
||||
|
||||
asJSON(req, res, data, 200);
|
||||
return;
|
||||
}
|
||||
|
||||
function stripResponse(req, res, data, releaseEp) {
|
||||
const resp = {};
|
||||
resp["posters"] = data.poster;
|
||||
resp["episodes"] = data.episodes;
|
||||
|
||||
if (releaseEp) {
|
||||
const episode = data.episodes.find((item) => item.ordinal == releaseEp);
|
||||
if (!episode) {
|
||||
asJSON(req, res, { message: "LIBRIA: Эпизод не найден" }, 404);
|
||||
return null;
|
||||
}
|
||||
resp["episodes"] = [episode];
|
||||
}
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
function createManifest(data) {
|
||||
const episode = data.episodes[0];
|
||||
const resolutions = {
|
||||
hls_480: "854x480",
|
||||
hls_720: "1280x720",
|
||||
hls_1080: "1920x1080",
|
||||
};
|
||||
|
||||
const stringBuilder: string[] = [];
|
||||
|
||||
stringBuilder.push("#EXTM3U");
|
||||
for (const [key, value] of Object.entries(resolutions)) {
|
||||
if (!episode[key]) continue;
|
||||
stringBuilder.push(`#EXT-X-STREAM-INF:RESOLUTION=${value}`);
|
||||
const url = new URL(episode[key]);
|
||||
url.search = "";
|
||||
stringBuilder.push(url.toString());
|
||||
}
|
||||
|
||||
return stringBuilder.join("\n");
|
||||
}
|
||||
|
||||
function getPoster(data) {
|
||||
const episode = data.episodes[0];
|
||||
|
||||
if (episode.preview && episode.preview.preview)
|
||||
return `https://anixart.libria.fun${episode.preview.preview}`;
|
||||
return `https://anilibria.top${data.poster.preview}`;
|
||||
}
|
1311
player-parser/package-lock.json
generated
Normal file
1311
player-parser/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
14
player-parser/package.json
Normal file
14
player-parser/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"
|
||||
}
|
||||
}
|
46
player-parser/shared.ts
Normal file
46
player-parser/shared.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
export const corsHeaders = {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept",
|
||||
"Access-Control-Allow-Methods": "GET,HEAD,POST,OPTIONS",
|
||||
"Cache-Control": "no-cache",
|
||||
};
|
||||
|
||||
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(req, res, object: any, status: number) {
|
||||
corsHeaders["Access-Control-Allow-Origin"] = req.headers.origin || "*";
|
||||
|
||||
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-parser/sibnet.ts
Normal file
59
player-parser/sibnet.ts
Normal file
|
@ -0,0 +1,59 @@
|
|||
import { asJSON, randomUA } from "./shared";
|
||||
|
||||
export async function getSibnetURL(req, res, url: string) {
|
||||
|
||||
if (!url.includes("sibnet")) {
|
||||
asJSON(req, res, { message: "SIBNET: Неправильная ссылка на плеер" }, 400);
|
||||
return
|
||||
}
|
||||
|
||||
const user_agent = randomUA();
|
||||
|
||||
let pageRes = await fetch(url, {
|
||||
headers: {
|
||||
"User-Agent": user_agent,
|
||||
},
|
||||
});
|
||||
if (!pageRes.ok) {
|
||||
asJSON(req, res, { message: `SIBNET: Не удалось загрузить страницу с плеером` }, 500)
|
||||
return
|
||||
}
|
||||
const pageData = await pageRes.text();
|
||||
const videoRe = /\/v\/.*?\.mp4/;
|
||||
const videoMatch = videoRe.exec(pageData);
|
||||
|
||||
if (!videoMatch || videoMatch.length == 0) {
|
||||
asJSON(req, res, { message: `SIBNET: Не удалось найти данные эпизода` }, 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(req, res, { message: `SIBNET: Не удалось получить прямую ссылку` }, 500)
|
||||
return
|
||||
}
|
||||
|
||||
const video = `https:${actualVideoRes.headers.get("location")}`;
|
||||
const poster =
|
||||
posterMatch ?
|
||||
posterMatch.length > 0 ?
|
||||
`https://st.sibnet.ru${posterMatch[0]}`
|
||||
: null
|
||||
: null;
|
||||
|
||||
asJSON(req, res, { manifest: video, poster }, 200)
|
||||
return
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue