diff --git a/.dockerignore b/.dockerignore index 736d9d9..c5d68f2 100644 --- a/.dockerignore +++ b/.dockerignore @@ -11,6 +11,7 @@ dockerfiles # Ignore Git-related files and directories .git .gitignore +.github README.md # Ignore log files @@ -20,4 +21,5 @@ README.md *.tmp # Ignore any other unnecessary files or directories -wajik-anime-api \ No newline at end of file +wajik-anime-api +.idea \ No newline at end of file diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml index dd90a31..33d42ee 100644 --- a/.github/workflows/cd.yaml +++ b/.github/workflows/cd.yaml @@ -1,4 +1,4 @@ -name: Node.js CD +name: Auto Deploy To VPS on: push: @@ -45,7 +45,7 @@ jobs: passphrase: ${{ secrets.PASSPHRASE }} key: ${{ secrets.PRIVATE_KEY }} script: | - cd ~/.apps/zannime-dev + cd ~/.apps/zannime_dev git pull git status sudo docker compose -f docker-compose-dev.yaml build diff --git a/index.js b/index.js index dd4be5e..65ccd66 100644 --- a/index.js +++ b/index.js @@ -29,66 +29,71 @@ let allAnimeData = []; const fetchAllAnimeData = async () => { try { const response = await ky.get(`${process.env.API_URL}/otakudesu/anime`).json(); - const animeData = response.data; - allAnimeData = animeData.flatMap(item => item.anime); + allAnimeData = response.data.flatMap(item => item.anime); } catch (error) { console.error('Error fetching all anime data:', error); } }; // Fetch data on server start -fetchAllAnimeData(); +(async () => { + await fetchAllAnimeData(); +})(); + +// Periodically update all anime data (every 60 minutes) +setInterval(async () => { + await fetchAllAnimeData(); +}, 60 * 60 * 1000); // Home route to list content with pagination app.get('/', async (req, res) => { const page = req.query.page || 1; try { const response = await ky.get(`${process.env.API_URL}/otakudesu/ongoing?page=${page}`).json(); - const { data, pagination } = response; - res.render('index', { animes: data, pagination }); + res.render('index', { animes: response.data, pagination: response.pagination }); } catch (error) { console.error('Error fetching data:', error); res.status(500).send('Error fetching data'); } }); -// Search route with pagination -app.get('/search', (req, res) => { - const query = req.query.q; - const page = parseInt(req.query.page) || 1; - const limit = 20; // Number of results per page +// Search route with pagination parameters +app.get('/search-ajax', (req, res) => { + const { q: query, page = 1, limit = 20 } = req.query; + if (!query) return res.json({ results: [] }); - if (!query) { - return res.redirect('/'); - } + let filteredResults = allAnimeData.filter(anime => anime.judul.toLowerCase().includes(query.toLowerCase())); + const startIndex = (page - 1) * limit; + const endIndex = page * limit; + const results = filteredResults.slice(startIndex, endIndex); - const filteredResults = allAnimeData.filter(anime => - anime.judul.toLowerCase().includes(query.toLowerCase()) - ); + res.json({ + results: results, + currentPage: parseInt(page, 10), + totalPages: Math.ceil(filteredResults.length / limit) + }); +}); - const totalResults = filteredResults.length; - const totalPages = Math.ceil(totalResults / limit); - const startIndex = (page - 1) * limit; - const endIndex = startIndex + limit; - const paginatedResults = filteredResults.slice(startIndex, endIndex); +// All anime route with pagination parameters +app.get('/all-anime-ajax', (req, res) => { + const { page = 1, limit = 20 } = req.query; - const pagination = { - currentPage: page, - totalPages: totalPages, - prevPage: page > 1 ? page - 1 : null, - nextPage: page < totalPages ? page + 1 : null - }; + const startIndex = (page - 1) * limit; + const endIndex = page * limit; + const results = allAnimeData.slice(startIndex, endIndex); - res.render('search', { results: paginatedResults, query, pagination }); + res.json({ + results: results, + currentPage: parseInt(page, 10), + totalPages: Math.ceil(allAnimeData.length / limit) + }); }); // Anime detail route app.get('/anime/:slug', async (req, res) => { - const slug = req.params.slug; try { - const response = await ky.get(`${process.env.API_URL}/otakudesu/anime/${slug}`).json(); - const anime = response.data; - res.render('detail', { anime }); + const response = await ky.get(`${process.env.API_URL}/otakudesu/anime/${req.params.slug}`).json(); + res.render('detail', { anime: response.data }); } catch (error) { console.error('Error fetching anime details:', error); res.status(500).send('Error fetching anime details'); @@ -97,14 +102,11 @@ app.get('/anime/:slug', async (req, res) => { // Episode detail route app.get('/episode', async (req, res) => { - const slug = req.query.slug; - if (!slug) { - return res.redirect('/'); - } + if (!req.query.slug) return res.redirect('/'); + try { - const response = await ky.get(`${process.env.API_URL}/otakudesu/episode/${slug}`).json(); - const episode = response.data; - res.render('episode', { episode }); + const response = await ky.get(`${process.env.API_URL}/otakudesu/episode/${req.query.slug}`).json(); + res.render('episode', { episode: response.data }); } catch (error) { console.error('Error fetching episode details:', error); res.status(500).send('Error fetching episode details'); @@ -112,9 +114,9 @@ app.get('/episode', async (req, res) => { }); // All anime route -app.get('/all-anime', async (req, res) => { - const page = req.query.page || 1; - const limit = 20; // Number of anime per page +app.get('/all-anime', (req, res) => { + const { page = 1 } = req.query; + const limit = 20; const totalAnimes = allAnimeData.length; const totalPages = Math.ceil(totalAnimes / limit); @@ -124,7 +126,7 @@ app.get('/all-anime', async (req, res) => { const pagination = { currentPage: page, - totalPages: totalPages, + totalPages, prevPage: page > 1 ? page - 1 : null, nextPage: page < totalPages ? page + 1 : null }; @@ -136,8 +138,7 @@ app.get('/all-anime', async (req, res) => { app.get('/genres', async (req, res) => { try { const response = await ky.get(`${process.env.API_URL}/otakudesu/genres`).json(); - const genres = response.data; - res.render('genres', { genres }); + res.render('genres', { genres: response.data }); } catch (error) { console.error('Error fetching genres:', error); res.status(500).send('Error fetching genres'); @@ -146,13 +147,12 @@ app.get('/genres', async (req, res) => { // Anime by genre route app.get('/genres/:slug', async (req, res) => { - const slug = req.params.slug; - const page = req.query.page || 1; + const { slug } = req.params; + const { page = 1 } = req.query; try { const response = await ky.get(`${process.env.API_URL}/otakudesu/genres/${slug}?page=${page}`).json(); - const { data, pagination } = response; - res.render('genre-anime', { animes: data, pagination, genre: slug }); + res.render('genre-anime', { animes: response.data, pagination: response.pagination, genre: slug }); } catch (error) { console.error('Error fetching anime by genre:', error); res.status(500).send('Error fetching anime by genre'); @@ -166,35 +166,24 @@ const fetchShortlink = (url) => { const request = protocol.get(url, (response) => { if (response.statusCode >= 300 && response.statusCode < 400) { - const location = response.headers.location; - if (location) { - return resolve(location); - } + return resolve(response.headers.location); } resolve(null); }); - request.on('error', (error) => { - reject(error); - }); - + request.on('error', reject); request.end(); }); }; // Route to decode shortlink app.get('/decode-shortlink', async (req, res) => { - const shortlink = req.query.url; - - if (!shortlink) { - return res.status(400).send('URL parameter is required'); - } + const { url: shortlink } = req.query; + if (!shortlink) return res.status(400).send('URL parameter is required'); try { const redirectionUrl = await fetchShortlink(shortlink); - if (redirectionUrl) { - return res.send(redirectionUrl); - } + if (redirectionUrl) return res.send(redirectionUrl); res.status(404).send('No redirection found'); } catch (error) { console.error('Error decoding shortlink:', error); @@ -205,4 +194,4 @@ app.get('/decode-shortlink', async (req, res) => { // Start the server app.listen(port, () => { console.log(`Server is running on http://localhost:${port}`); -}); +}); \ No newline at end of file diff --git a/views/all-anime.ejs b/views/all-anime.ejs index 29e8720..f7262ee 100644 --- a/views/all-anime.ejs +++ b/views/all-anime.ejs @@ -6,63 +6,171 @@ All Anime - ZANNIME + <%- include('navbar') %> -
-

All Anime

-
-
- - +
+
+

All Anime

+ +
+ + + + + + + + + <% animes.slice(0, 20).forEach(anime => { %> + + + + + <% }) %> + +
JudulAction
<%= anime.judul %> + View +
- - - - - - - - - - <% animes.forEach(anime => { %> - - - - - <% }) %> - -
JudulAction
<%= anime.judul %> - View -
- - + +
+ <%- include('footer') %>
- <%- include('footer') %> + \ No newline at end of file diff --git a/views/episode.ejs b/views/episode.ejs index 5236041..f48ab49 100644 --- a/views/episode.ejs +++ b/views/episode.ejs @@ -22,7 +22,7 @@ <% episode.downloadUrl.forEach(quality => { %> <% if ((quality.kualitas === 'Mp4_720p') || (quality.kualitas === 'Mp4_1080p') || (quality.kualitas === 'MKV_1080p') || (quality.kualitas === '720p') || (quality.kualitas === '1080p')) { %> <% quality.urls.forEach(url => { %> - <% if ( ['Acefile', 'Pdrain', 'PDrain', 'KFiles', 'Kraken'].some(item => url.judul.toLowerCase().includes(item.toLowerCase())) ) { %> + <% if ( ['Acefile', 'Pdrain', 'PDrain', 'KFiles', 'Kraken', 'Mega'].some(item => url.judul.toLowerCase().includes(item.toLowerCase())) ) { %> <% } %> <% }) %> @@ -94,9 +94,15 @@ } else if (url.includes('acefile.co')) { const fileId = url.split('/f/')[1].split('/')[0]; return `https://acefile.co/player/${fileId}`; + } else if (url.includes('mega.nz')) { + const parts = url.split('#'); + if (parts.length !== 2) return url; + const fileId = parts[0].split('/').pop(); + const key = parts[1]; + return `https://mega.nz/embed/${fileId}#${key}`; } - return url; // Return the original URL if no specific format is found -} + return url; // Return the original URL if no specific format is found + } document.getElementById('streaming-source').addEventListener('change', async function() { const selectedUrl = this.value;