diff --git a/binding.gyp b/binding.gyp deleted file mode 100644 index 9f4d3b3..0000000 --- a/binding.gyp +++ /dev/null @@ -1,14 +0,0 @@ -{ - "targets": [ - { - "target_name": "anime_core", - "cflags!": [ "-fno-exceptions" ], - "cflags_cc!": [ "-fno-exceptions" ], - "sources": [ "./src/main.cpp" ], - "include_dirs": [ - " { - await loadList(); - setupEventListeners(); +document.addEventListener('DOMContentLoaded', () => { + SearchManager.init('#search-input', '#search-results', 'anime'); + ContinueWatchingManager.load('my-status', 'watching', 'ANIME'); + fetchContent(); + setInterval(() => fetchContent(true), 60000); }); -function getEntryLink(item) { - const isAnime = item.entry_type?.toUpperCase() === 'ANIME'; - const baseRoute = isAnime ? '/anime' : '/book'; - const source = item.source || 'anilist'; +document.addEventListener('click', (e) => { + if (!e.target.closest('.search-wrapper')) { + const searchResults = document.getElementById('search-results'); + const searchInput = document.getElementById('search-input'); + searchResults.classList.remove('active'); + searchInput.style.borderRadius = '99px'; + } +}); - if (source === 'anilist') { - return `${baseRoute}/${item.entry_id}`; - } else { - return `${baseRoute}/${source}/${item.entry_id}`; +function scrollCarousel(id, direction) { + const container = document.getElementById(id); + if(container) { + const scrollAmount = container.clientWidth * 0.75; + container.scrollBy({ left: direction * scrollAmount, behavior: 'smooth' }); } } -async function populateSourceFilter() { - const select = document.getElementById('source-filter'); - if (!select) return; +var tag = document.createElement('script'); +tag.src = "https://www.youtube.com/iframe_api"; +var firstScriptTag = document.getElementsByTagName('script')[0]; +firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); - select.innerHTML = ` - - - `; - - try { - const response = await fetch(`${API_BASE}/extensions`); - if (response.ok) { - const data = await response.json(); - const extensions = data.extensions || []; - - extensions.forEach(extName => { - if (extName.toLowerCase() !== 'anilist' && extName.toLowerCase() !== 'local') { - const option = document.createElement('option'); - option.value = extName; - option.textContent = extName.charAt(0).toUpperCase() + extName.slice(1); - select.appendChild(option); - } - }); - } - } catch (error) { - console.error('Error loading extensions:', error); - } -} - -function setupEventListeners() { - - document.querySelectorAll('.view-btn').forEach(btn => { - btn.addEventListener('click', () => { - document.querySelectorAll('.view-btn').forEach(b => b.classList.remove('active')); - btn.classList.add('active'); - const view = btn.dataset.view; - const container = document.getElementById('list-container'); - if (view === 'list') { - container.classList.add('list-view'); - } else { - container.classList.remove('list-view'); +function onYouTubeIframeAPIReady() { + player = new YT.Player('player', { + height: '100%', + width: '100%', + playerVars: { + 'autoplay': 1, + 'controls': 0, + 'mute': 1, + 'loop': 1, + 'showinfo': 0, + 'modestbranding': 1 + }, + events: { + 'onReady': (e) => { + e.target.mute(); + if(trendingAnimes.length) updateHeroVideo(trendingAnimes[currentHeroIndex]); } - }); - }); - - document.getElementById('status-filter').addEventListener('change', applyFilters); - document.getElementById('source-filter').addEventListener('change', applyFilters); - document.getElementById('type-filter').addEventListener('change', applyFilters); - document.getElementById('sort-filter').addEventListener('change', applyFilters); - - document.querySelector('.search-input').addEventListener('input', (e) => { - const query = e.target.value.toLowerCase(); - if (query) { - filteredList = currentList.filter(item => - item.title?.toLowerCase().includes(query) - ); - } else { - filteredList = [...currentList]; - } - applyFilters(); - }); - - document.getElementById('modal-save-btn')?.addEventListener('click', async () => { - - const entryToSave = window.ListModalManager.currentEntry || window.ListModalManager.currentData; - - if (!entryToSave) return; - - await window.ListModalManager.save(entryToSave.entry_id, entryToSave.source); - - await loadList(); - }); - - document.getElementById('modal-delete-btn')?.addEventListener('click', async () => { - const entryToDelete = window.ListModalManager.currentEntry || window.ListModalManager.currentData; - - if (!entryToDelete) return; - - await window.ListModalManager.delete(entryToDelete.entry_id, entryToDelete.source); - - await loadList(); - }); - - document.getElementById('add-list-modal')?.addEventListener('click', (e) => { - if (e.target.id === 'add-list-modal') { - window.ListModalManager.close(); } }); } -async function loadList() { - const loadingState = document.getElementById('loading-state'); - const emptyState = document.getElementById('empty-state'); - const container = document.getElementById('list-container'); - - await populateSourceFilter(); - +async function fetchContent(isUpdate = false) { try { - loadingState.style.display = 'flex'; - emptyState.style.display = 'none'; - container.innerHTML = ''; + const trendingRes = await fetch('/api/trending'); + const trendingData = await trendingRes.json(); - const response = await fetch(`${API_BASE}/list`, { - headers: window.AuthUtils.getSimpleAuthHeaders() - }); - - if (!response.ok) { - throw new Error('Failed to load list'); + if (trendingData.results && trendingData.results.length > 0) { + trendingAnimes = trendingData.results; + if (!isUpdate) { + updateHeroUI(trendingAnimes[0]); + startHeroCycle(); + } + renderList('trending', trendingAnimes); + } else if (!isUpdate) { + setTimeout(() => fetchContent(false), 2000); } - const data = await response.json(); - currentList = data.results || []; - filteredList = [...currentList]; - - loadingState.style.display = 'none'; - - if (currentList.length === 0) { - emptyState.style.display = 'flex'; - } else { - updateStats(); - applyFilters(); - } - } catch (error) { - console.error('Error loading list:', error); - loadingState.style.display = 'none'; - if (window.NotificationUtils) { - window.NotificationUtils.error('Failed to load your list. Please try again.'); - } else { - alert('Failed to load your list. Please try again.'); + const topRes = await fetch('/api/top-airing'); + const topData = await topRes.json(); + if (topData.results && topData.results.length > 0) { + renderList('top-airing', topData.results); } + } catch (e) { + console.error("Fetch Error:", e); + if(!isUpdate) setTimeout(() => fetchContent(false), 5000); } } -function updateStats() { - const total = currentList.length; - const watching = currentList.filter(item => item.status === 'WATCHING').length; - const completed = currentList.filter(item => item.status === 'COMPLETED').length; - const planning = currentList.filter(item => item.status === 'PLANNING').length; - - document.getElementById('total-count').textContent = total; - document.getElementById('watching-count').textContent = watching; - document.getElementById('completed-count').textContent = completed; - document.getElementById('planned-count').textContent = planning; +function startHeroCycle() { + if(heroInterval) clearInterval(heroInterval); + heroInterval = setInterval(() => { + if(trendingAnimes.length > 0) { + currentHeroIndex = (currentHeroIndex + 1) % trendingAnimes.length; + updateHeroUI(trendingAnimes[currentHeroIndex]); + } + }, 10000); } -function applyFilters() { - const statusFilter = document.getElementById('status-filter').value; - const sourceFilter = document.getElementById('source-filter').value; - const typeFilter = document.getElementById('type-filter').value; - const sortFilter = document.getElementById('sort-filter').value; +async function updateHeroUI(anime) { + if(!anime) return; - let filtered = [...filteredList]; + const title = anime.title.english || anime.title.romaji || "Unknown Title"; + const score = anime.averageScore ? anime.averageScore + '% Match' : 'N/A'; + const year = anime.seasonYear || ''; + const type = anime.format || 'TV'; + const desc = anime.description || 'No description available.'; + const poster = anime.coverImage ? anime.coverImage.extraLarge : ''; + const banner = anime.bannerImage || poster; - if (statusFilter !== 'all') { - filtered = filtered.filter(item => item.status === statusFilter); + document.getElementById('hero-title').innerText = title; + document.getElementById('hero-desc').innerHTML = desc; + document.getElementById('hero-score').innerText = score; + document.getElementById('hero-year').innerText = year; + document.getElementById('hero-type').innerText = type; + document.getElementById('hero-poster').src = poster; + + const watchBtn = document.getElementById('watch-btn'); + if(watchBtn) watchBtn.onclick = () => window.location.href = `/anime/${anime.id}`; + + const addToListBtn = document.querySelector('.hero-buttons .btn-blur'); + if(addToListBtn) { + ListModalManager.currentData = anime; + const entryType = ListModalManager.getEntryType(anime); + + await ListModalManager.checkIfInList(anime.id, 'anilist', entryType); + ListModalManager.updateButton(); + + addToListBtn.onclick = () => ListModalManager.open(anime, 'anilist'); } - if (sourceFilter !== 'all') { - filtered = filtered.filter(item => item.source === sourceFilter); - } + const bgImg = document.getElementById('hero-bg-media'); + if(bgImg && bgImg.src !== banner) bgImg.src = banner; - if (typeFilter !== 'all') { - filtered = filtered.filter(item => item.entry_type === typeFilter); - } + updateHeroVideo(anime); - switch (sortFilter) { - case 'title': - filtered.sort((a, b) => (a.title || '').localeCompare(b.title || '')); - break; - case 'score': - filtered.sort((a, b) => (b.score || 0) - (a.score || 0)); - break; - case 'progress': - filtered.sort((a, b) => (b.progress || 0) - (a.progress || 0)); - break; - case 'updated': - default: - filtered.sort((a, b) => new Date(b.updated_at) - new Date(a.updated_at)); - break; - } - - renderList(filtered); + document.getElementById('hero-loading-ui').style.display = 'none'; + document.getElementById('hero-real-ui').style.display = 'block'; } -function renderList(items) { - const container = document.getElementById('list-container'); - container.innerHTML = ''; +function updateHeroVideo(anime) { + if (!player || !player.loadVideoById) return; + const videoContainer = document.getElementById('player'); + if (anime.trailer && anime.trailer.site === 'youtube' && anime.trailer.id) { + if(player.getVideoData && player.getVideoData().video_id !== anime.trailer.id) { + player.loadVideoById(anime.trailer.id); + player.mute(); + } + videoContainer.style.opacity = "1"; + } else { + videoContainer.style.opacity = "0"; + player.stopVideo(); + } +} - if (items.length === 0) { - container.innerHTML = '

No entries match your filters

'; +function renderList(id, list) { + const container = document.getElementById(id); + const firstId = list.length > 0 ? list[0].id : null; + const currentFirstId = container.firstElementChild?.dataset?.id; + if (currentFirstId && parseInt(currentFirstId) === firstId && container.children.length === list.length) { return; } - items.forEach(item => { - const element = createListItem(item); - container.appendChild(element); + container.innerHTML = ''; + list.forEach(anime => { + const title = anime.title.english || anime.title.romaji || "Unknown Title"; + const cover = anime.coverImage ? anime.coverImage.large : ''; + const ep = anime.nextAiringEpisode ? 'Ep ' + anime.nextAiringEpisode.episode : (anime.episodes ? anime.episodes + ' Eps' : 'TV'); + const score = anime.averageScore || '--'; + + const el = document.createElement('div'); + el.className = 'card'; + el.dataset.id = anime.id; + + el.onclick = () => window.location.href = `/anime/${anime.id}`; + el.innerHTML = ` +
+
+

${title}

+

${score}% • ${ep}

+
+ `; + container.appendChild(el); }); } -function createListItem(item) { - const div = document.createElement('div'); - div.className = 'list-item'; +function saveToList() { + const animeId = ListModalManager.currentData ? ListModalManager.currentData.id : null; + if (!animeId) return; + ListModalManager.save(animeId, 'anilist'); +} - const itemLink = getEntryLink(item); +function deleteFromList() { + const animeId = ListModalManager.currentData ? ListModalManager.currentData.id : null; + if (!animeId) return; + ListModalManager.delete(animeId, 'anilist'); +} - const posterUrl = item.poster || '/public/assets/placeholder.png'; - const progress = item.progress || 0; - - const totalUnits = item.entry_type === 'ANIME' ? - item.total_episodes || 0 : - item.total_chapters || 0; - - const progressPercent = totalUnits > 0 ? (progress / totalUnits) * 100 : 0; - const score = item.score ? item.score.toFixed(1) : null; - const repeatCount = item.repeat_count || 0; - - const entryType = (item.entry_type).toUpperCase(); - let unitLabel = 'units'; - if (entryType === 'ANIME') { - unitLabel = 'episodes'; - } else if (entryType === 'MANGA') { - unitLabel = 'chapters'; - } else if (entryType === 'NOVEL') { - unitLabel = 'chapters/volumes'; - } - - const statusLabels = { - 'WATCHING': entryType === 'ANIME' ? 'Watching' : 'Reading', - 'COMPLETED': 'Completed', - 'PLANNING': 'Planning', - 'PAUSED': 'Paused', - 'DROPPED': 'Dropped' - }; - - const extraInfo = []; - if (repeatCount > 0) { - extraInfo.push(`🔁 ${repeatCount}`); - } - if (item.is_private) { - extraInfo.push('🔒 Private'); - } - - const entryDataString = JSON.stringify(item).replace(/'/g, '''); - - div.innerHTML = ` - - ${item.title || 'Entry'} - -
-
- -

${item.title || 'Unknown Title'}

-
-
- ${statusLabels[item.status] || item.status} - ${entryType} - ${item.source.toUpperCase()} - ${extraInfo.join('')} -
-
- -
-
-
-
-
- ${progress}${totalUnits > 0 ? ` / ${totalUnits}` : ''} ${unitLabel} ${score ? `⭐ ${score}` : ''} -
-
-
- - - `; - - const editBtn = div.querySelector('.edit-icon-btn'); - editBtn.addEventListener('click', (e) => { - try { - const entryData = JSON.parse(e.currentTarget.dataset.entry); - - window.ListModalManager.isInList = true; - window.ListModalManager.currentEntry = entryData; - window.ListModalManager.currentData = entryData; - - window.ListModalManager.open(entryData, entryData.source); - } catch (error) { - console.error('Error parsing entry data for modal:', error); - - if (window.NotificationUtils) { - window.NotificationUtils.error('Could not open modal. Check HTML form IDs.'); - } - } - }); - - return div; +function closeAddToListModal() { + ListModalManager.close(); } \ No newline at end of file diff --git a/src/shared/headless.js b/src/shared/headless.js index cc8dd20..0d1c1fe 100644 --- a/src/shared/headless.js +++ b/src/shared/headless.js @@ -70,7 +70,6 @@ async function scrape(url, handler, options = {}) { const type = req.resourceType(); if ( type === "font" || - type === "stylesheet" || type === "media" || type === "manifest" ) return route.abort();