165 lines
4.9 KiB
TypeScript
165 lines
4.9 KiB
TypeScript
import { serve } from "https://deno.land/std@0.140.0/http/server.ts";
|
||
|
||
const baseHeaders = {
|
||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
|
||
"Accept": "application/json",
|
||
};
|
||
|
||
// Взвешенное среднее из scores_stats
|
||
function calculateWeightedScore(stats) {
|
||
if (!stats?.length) return null;
|
||
|
||
let total = 0;
|
||
let count = 0;
|
||
|
||
for (const item of stats) {
|
||
const score = parseInt(item.name);
|
||
const votes = item.value;
|
||
total += score * votes;
|
||
count += votes;
|
||
}
|
||
|
||
return count ? (total / count).toFixed(2) : null;
|
||
}
|
||
|
||
// Получение данных с Shikimori
|
||
async function getShikimoriData(search, year) {
|
||
if (!search) {
|
||
return { score: "N/A", weightedScore: "N/A", characters: [] };
|
||
}
|
||
|
||
const animeUrl = `https://shikimori.one/api/animes?search=${encodeURIComponent(search)}&limit=1${year ? `&year=${year}` : ""}`;
|
||
|
||
try {
|
||
const animeRes = await fetch(animeUrl, { headers: baseHeaders });
|
||
const animeData = await animeRes.json();
|
||
const anime = animeData?.[0];
|
||
|
||
if (!anime || !anime.id) {
|
||
return { score: "N/A", weightedScore: "N/A", characters: [] };
|
||
}
|
||
|
||
// Взвешенный рейтинг из scores_stats
|
||
const statsUrl = `https://shikimori.one/api/animes/${anime.id}`;
|
||
const statsRes = await fetch(statsUrl, { headers: baseHeaders });
|
||
const statsData = await statsRes.json();
|
||
const weightedScore = calculateWeightedScore(statsData.scores_stats) || "N/A";
|
||
|
||
// Главные персонажи
|
||
const rolesUrl = `https://shikimori.one/api/animes/${anime.id}/roles`;
|
||
const rolesRes = await fetch(rolesUrl, { headers: baseHeaders });
|
||
const roles = await rolesRes.json();
|
||
|
||
const mainCharacters = roles
|
||
.filter((c) => c.roles.includes("Main"))
|
||
.slice(0, 5)
|
||
.map((c) => ({
|
||
name: c.character.russian,
|
||
url: `https://shikimori.one${c.character.url}`,
|
||
}));
|
||
|
||
return {
|
||
score: anime.score || "N/A",
|
||
weightedScore,
|
||
characters: mainCharacters,
|
||
};
|
||
} catch {
|
||
return { score: "N/A", weightedScore: "N/A", characters: [] };
|
||
}
|
||
}
|
||
|
||
// Рейтинг с MyAnimeList через Jikan
|
||
async function getMALScore(title) {
|
||
const url = `https://api.jikan.moe/v4/anime?q=${encodeURIComponent(title)}&limit=1`;
|
||
|
||
try {
|
||
const response = await fetch(url);
|
||
const data = await response.json();
|
||
const anime = data?.data?.[0];
|
||
|
||
if (!anime || !anime.score) {
|
||
return "N/A";
|
||
}
|
||
|
||
return `${anime.score}★`;
|
||
} catch {
|
||
return "N/A";
|
||
}
|
||
}
|
||
|
||
// Получение и сборка данных о релизе
|
||
async function getReleaseFromAnixart(releaseId, token = "") {
|
||
const url = `https://api.anixart.tv/release/${releaseId}${token ? `?token=${token}` : ""}`;
|
||
|
||
try {
|
||
const response = await fetch(url, { headers: baseHeaders });
|
||
const data = await response.json();
|
||
const release = data?.release;
|
||
|
||
if (!release) {
|
||
return { code: 2, release: null };
|
||
}
|
||
|
||
const title = release.title_original || release.title_ru || "";
|
||
const year = release.year || "";
|
||
let noteExtra = "";
|
||
|
||
try {
|
||
const shikiData = await getShikimoriData(title, year);
|
||
const shikiScore = shikiData.score || "N/A";
|
||
const shikiWeighted = shikiData.weightedScore || "N/A";
|
||
const characters = shikiData.characters.length
|
||
? shikiData.characters.map((c) => `<a href="${c.url}">${c.name}</a>`).join(", ")
|
||
: "N/A";
|
||
|
||
const malScore = await getMALScore(title);
|
||
|
||
noteExtra =
|
||
`<b>Shikimori (официальный):</b> ${shikiScore}<br>` +
|
||
`<b>Shikimori (взвешенный):</b> ${shikiWeighted}<br>` +
|
||
`<b>MyAnimeList:</b> ${malScore}<br>` +
|
||
`<b>Главные персонажи:</b> ${characters}<br>`;
|
||
} catch {
|
||
noteExtra =
|
||
`<b>Shikimori (официальный):</b> N/A<br>` +
|
||
`<b>Shikimori (взвешенный):</b> N/A<br>` +
|
||
`<b>MyAnimeList:</b> N/A<br>` +
|
||
`<b>Главные персонажи:</b> N/A<br>`;
|
||
}
|
||
|
||
const originalNote = release.note?.trim();
|
||
let finalNote = noteExtra;
|
||
if (originalNote) {
|
||
finalNote += `<br><b>Примечание от Anixart:</b><br>${originalNote}`;
|
||
}
|
||
|
||
release.note = finalNote;
|
||
|
||
return { code: 0, release };
|
||
} catch {
|
||
return { code: 2, release: null };
|
||
}
|
||
}
|
||
|
||
// Серверный роутинг
|
||
serve(async (req) => {
|
||
const url = new URL(req.url);
|
||
const path = url.pathname;
|
||
const token = url.searchParams.get("token");
|
||
|
||
// /api/release/{releaseId}
|
||
const releaseMatch = path.match(/^\/api\/release\/([^\/]+)/);
|
||
if (releaseMatch) {
|
||
const releaseId = releaseMatch[1];
|
||
const result = await getReleaseFromAnixart(releaseId, token);
|
||
return new Response(JSON.stringify(result), {
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
"Access-Control-Allow-Origin": "*",
|
||
},
|
||
});
|
||
}
|
||
|
||
return new Response("Invalid endpoint", { status: 404 });
|
||
});
|