277 lines
9.8 KiB
JavaScript
277 lines
9.8 KiB
JavaScript
const animeId = window.location.pathname.split('/').pop();
|
|
let player;
|
|
|
|
let totalEpisodes = 0;
|
|
let currentPage = 1;
|
|
const itemsPerPage = 12;
|
|
|
|
var tag = document.createElement('script');
|
|
tag.src = "https://www.youtube.com/iframe_api";
|
|
var firstScriptTag = document.getElementsByTagName('script')[0];
|
|
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
|
|
|
|
let extensionName;
|
|
|
|
async function loadAnime() {
|
|
try {
|
|
const path = window.location.pathname;
|
|
const parts = path.split("/").filter(Boolean);
|
|
let animeId;
|
|
|
|
if (parts.length === 3) {
|
|
extensionName = parts[1];
|
|
animeId = parts[2];
|
|
} else {
|
|
animeId = parts[1];
|
|
}
|
|
|
|
const fetchUrl = extensionName
|
|
? `/api/anime/${animeId.slice(0,40)}?ext=${extensionName}`
|
|
: `/api/anime/${animeId}`;
|
|
const res = await fetch(fetchUrl);
|
|
const data = await res.json();
|
|
|
|
if(data.error) {
|
|
document.getElementById('title').innerText = "Anime Not Found";
|
|
return;
|
|
}
|
|
|
|
const title = data.title?.english || data.title?.romaji || "Unknown Title";
|
|
document.title = `${title} | WaifuBoard`;
|
|
document.getElementById('title').innerText = title;
|
|
if (data.coverImage?.extraLarge) {
|
|
document.getElementById('poster').src = data.coverImage.extraLarge;
|
|
}
|
|
|
|
const rawDesc = data.description || "No description available.";
|
|
handleDescription(rawDesc);
|
|
|
|
document.getElementById('score').innerText = (data.averageScore || '?') + '% Score';
|
|
document.getElementById('year').innerText = data.seasonYear || data.startDate?.year || '????';
|
|
document.getElementById('genres').innerText = data.genres?.length > 0 ? data.genres.slice(0, 3).join(' • ') : '';
|
|
document.getElementById('format').innerText = data.format || 'TV';
|
|
document.getElementById('status').innerText = data.status || 'Unknown';
|
|
const extensionPill = document.getElementById('extension-pill');
|
|
if (extensionName && extensionPill) {
|
|
extensionPill.textContent = `${extensionName.charAt(0).toUpperCase() + extensionName.slice(1).toLowerCase()}`;
|
|
extensionPill.style.display = 'inline-flex';
|
|
} else if (extensionPill) {
|
|
extensionPill.style.display = 'none';
|
|
}
|
|
|
|
let seasonText = '';
|
|
if (data.season && data.seasonYear) {
|
|
seasonText = `${data.season} ${data.seasonYear}`;
|
|
} else if (data.startDate?.year) {
|
|
const months = ['', 'Winter', 'Winter', 'Spring', 'Spring', 'Spring', 'Summer', 'Summer', 'Summer', 'Fall', 'Fall', 'Fall', 'Winter'];
|
|
const month = data.startDate.month || 1;
|
|
const estimatedSeason = months[month] || '';
|
|
seasonText = `${estimatedSeason} ${data.startDate.year}`.trim();
|
|
}
|
|
document.getElementById('season').innerText = seasonText || 'Unknown';
|
|
|
|
let studioName = 'Unknown Studio';
|
|
if (data.studios?.nodes?.length > 0) {
|
|
studioName = data.studios.nodes[0].name;
|
|
} else if (data.studios?.edges?.length > 0) {
|
|
studioName = data.studios.edges[0]?.node?.name || 'Unknown Studio';
|
|
}
|
|
document.getElementById('studio').innerText = studioName;
|
|
|
|
const charContainer = document.getElementById('char-list');
|
|
charContainer.innerHTML = '';
|
|
|
|
let characters = [];
|
|
|
|
if (data.characters?.nodes?.length > 0) {
|
|
characters = data.characters.nodes.slice(0, 5);
|
|
}
|
|
else if (data.characters?.edges?.length > 0) {
|
|
characters = data.characters.edges
|
|
.filter(edge => edge?.node?.name?.full)
|
|
.slice(0, 5)
|
|
.map(edge => edge.node);
|
|
}
|
|
|
|
if (characters.length > 0) {
|
|
characters.forEach(char => {
|
|
if (char?.name?.full) {
|
|
charContainer.innerHTML += `
|
|
<div class="character-item">
|
|
<div class="char-dot"></div> ${char.name.full}
|
|
</div>`;
|
|
}
|
|
});
|
|
} else {
|
|
charContainer.innerHTML = `
|
|
<div class="character-item" style="color: #666;">
|
|
No character data available
|
|
</div>`;
|
|
}
|
|
|
|
document.getElementById('watch-btn').onclick = () => {
|
|
window.location.href = `/watch/${animeId}/1`;
|
|
};
|
|
|
|
if (data.trailer && data.trailer.site === 'youtube') {
|
|
window.onYouTubeIframeAPIReady = function() {
|
|
player = new YT.Player('player', {
|
|
height: '100%',
|
|
width: '100%',
|
|
videoId: data.trailer.id,
|
|
playerVars: {
|
|
'autoplay': 1, 'controls': 0, 'mute': 1,
|
|
'loop': 1, 'playlist': data.trailer.id,
|
|
'showinfo': 0, 'modestbranding': 1, 'disablekb': 1
|
|
},
|
|
events: { 'onReady': (e) => e.target.playVideo() }
|
|
});
|
|
};
|
|
} else {
|
|
const banner = data.bannerImage || data.coverImage?.extraLarge || '';
|
|
if (banner) {
|
|
document.querySelector('.video-background').innerHTML = `<img src="${banner}" style="width:100%; height:100%; object-fit:cover;">`;
|
|
}
|
|
}
|
|
|
|
let extensionEpisodes = [];
|
|
|
|
if (extensionName) {
|
|
extensionEpisodes = await loadExtensionEpisodes(animeId, extensionName);
|
|
|
|
if (extensionEpisodes.length > 0) {
|
|
totalEpisodes = extensionEpisodes.length;
|
|
} else {
|
|
totalEpisodes = 1;
|
|
}
|
|
} else {
|
|
// MODO NORMAL (AniList)
|
|
if (data.nextAiringEpisode?.episode) {
|
|
totalEpisodes = data.nextAiringEpisode.episode - 1;
|
|
} else if (data.episodes) {
|
|
totalEpisodes = data.episodes;
|
|
} else {
|
|
totalEpisodes = 12;
|
|
}
|
|
}
|
|
|
|
totalEpisodes = Math.min(Math.max(totalEpisodes, 1), 5000);
|
|
document.getElementById('episodes').innerText = totalEpisodes;
|
|
|
|
renderEpisodes();
|
|
|
|
} catch (err) {
|
|
console.error('Error loading anime:', err);
|
|
document.getElementById('title').innerText = "Error loading anime";
|
|
}
|
|
}
|
|
|
|
async function loadExtensionEpisodes(animeId, extName) {
|
|
try {
|
|
const url = `/api/anime/${animeId}/episodes?ext=${extName}`;
|
|
const res = await fetch(url);
|
|
const data = await res.json();
|
|
|
|
if (!Array.isArray(data)) return [];
|
|
|
|
return data.map(ep => ({
|
|
id: ep.id,
|
|
number: ep.number,
|
|
title: ep.title || `Episode ${ep.number}`,
|
|
url: ep.url
|
|
}));
|
|
} catch (err) {
|
|
console.error("Failed to fetch extension episodes:", err);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
|
|
function handleDescription(text) {
|
|
const tmp = document.createElement("DIV");
|
|
tmp.innerHTML = text;
|
|
const cleanText = tmp.textContent || tmp.innerText || "";
|
|
|
|
const sentences = cleanText.match(/[^\.!\?]+[\.!\?]+/g) || [cleanText];
|
|
|
|
document.getElementById('full-description').innerHTML = text;
|
|
|
|
if (sentences.length > 4) {
|
|
const shortText = sentences.slice(0, 4).join(' ');
|
|
document.getElementById('description-preview').innerText = shortText + '...';
|
|
document.getElementById('read-more-btn').style.display = 'inline-flex';
|
|
} else {
|
|
document.getElementById('description-preview').innerHTML = text;
|
|
document.getElementById('read-more-btn').style.display = 'none';
|
|
}
|
|
}
|
|
|
|
function openModal() {
|
|
document.getElementById('desc-modal').classList.add('active');
|
|
document.body.style.overflow = 'hidden';
|
|
}
|
|
|
|
function closeModal() {
|
|
document.getElementById('desc-modal').classList.remove('active');
|
|
document.body.style.overflow = '';
|
|
}
|
|
|
|
document.getElementById('desc-modal').addEventListener('click', (e) => {
|
|
if (e.target.id === 'desc-modal') closeModal();
|
|
});
|
|
|
|
function renderEpisodes() {
|
|
const grid = document.getElementById('episodes-grid');
|
|
grid.innerHTML = '';
|
|
|
|
const start = (currentPage - 1) * itemsPerPage + 1;
|
|
const end = Math.min(start + itemsPerPage - 1, totalEpisodes);
|
|
|
|
for(let i = start; i <= end; i++) {
|
|
createEpisodeButton(i, grid);
|
|
}
|
|
updatePaginationControls();
|
|
}
|
|
|
|
function createEpisodeButton(num, container) {
|
|
const btn = document.createElement('div');
|
|
btn.className = 'episode-btn';
|
|
btn.innerText = `Ep ${num}`;
|
|
btn.onclick = () =>
|
|
window.location.href = `/watch/${animeId}/${num}` + (extensionName ? `?${extensionName}` : "");
|
|
|
|
container.appendChild(btn);
|
|
}
|
|
|
|
function updatePaginationControls() {
|
|
const totalPages = Math.ceil(totalEpisodes / itemsPerPage);
|
|
document.getElementById('page-info').innerText = `Page ${currentPage} of ${totalPages}`;
|
|
document.getElementById('prev-page').disabled = currentPage === 1;
|
|
document.getElementById('next-page').disabled = currentPage === totalPages;
|
|
|
|
document.getElementById('pagination-controls').style.display = 'flex';
|
|
}
|
|
|
|
function changePage(delta) {
|
|
currentPage += delta;
|
|
renderEpisodes();
|
|
}
|
|
|
|
const searchInput = document.getElementById('ep-search');
|
|
searchInput.addEventListener('input', (e) => {
|
|
const val = parseInt(e.target.value);
|
|
const grid = document.getElementById('episodes-grid');
|
|
|
|
if (val > 0 && val <= totalEpisodes) {
|
|
grid.innerHTML = '';
|
|
createEpisodeButton(val, grid);
|
|
document.getElementById('pagination-controls').style.display = 'none';
|
|
} else if (!e.target.value) {
|
|
renderEpisodes();
|
|
} else {
|
|
grid.innerHTML = '<div style="color:#666; width:100%; text-align:center;">Episode not found</div>';
|
|
document.getElementById('pagination-controls').style.display = 'none';
|
|
}
|
|
});
|
|
|
|
loadAnime(); |