/** * SOKUJA API Server * REST API for the Sokuja Scraper */ const express = require('express'); const cors = require('cors'); const SokujaScraper = require('./index'); const app = express(); const PORT = process.env.PORT || 7860; app.use(cors()); app.use(express.json()); const scraper = new SokujaScraper(); // Middleware for error handling const asyncHandler = (fn) => (req, res, next) => { Promise.resolve(fn(req, res, next)).catch(next); }; /** * @route GET / * @description API Info */ app.get('/', (req, res) => { res.json({ name: 'SOKUJA API', version: '1.0.0', description: 'Unofficial API for SOKUJA anime streaming', baseUrl: 'https://x5.sokuja.uk', endpoints: { home: '/api/home', latest: '/api/latest', ongoing: '/api/ongoing?page=1', completed: '/api/completed?page=1', movies: '/api/movies?page=1', anime: '/api/anime/:slug', episodes: '/api/anime/:slug/episodes', episode: '/api/anime/:slug/episode/:number', schedule: '/api/schedule', genres: '/api/genres', genreAnime: '/api/genre/:slug?page=1', search: '/api/search?q=keyword', popular: '/api/popular?type=weekly', batch: '/api/anime/:slug/batch', stream: '/api/stream?url=xPhpUrl' } }); }); /** * @route GET /api/home * @description Get home page data (featured, latest, popular) */ app.get('/api/home', asyncHandler(async (req, res) => { const data = await scraper.getHomeComplete(); res.json({ success: true, data }); })); /** * @route GET /api/latest * @description Get latest episode updates */ app.get('/api/latest', asyncHandler(async (req, res) => { const page = parseInt(req.query.page) || 1; const data = await scraper.getLatestUpdates(page); res.json({ success: true, page, data }); })); /** * @route GET /api/ongoing * @description Get ongoing anime series */ app.get('/api/ongoing', asyncHandler(async (req, res) => { const page = parseInt(req.query.page) || 1; const data = await scraper.getOngoing(page); res.json({ success: true, page, total: data.total, count: data.anime.length, hasNext: data.hasNextPage, data: data.anime }); })); /** * @route GET /api/completed * @description Get completed anime series */ app.get('/api/completed', asyncHandler(async (req, res) => { const page = parseInt(req.query.page) || 1; const data = await scraper.getCompleted(page); res.json({ success: true, page, total: data.total, count: data.anime.length, hasNext: data.hasNextPage, data: data.anime }); })); /** * @route GET /api/movies * @description Get anime movies */ app.get('/api/movies', asyncHandler(async (req, res) => { const page = parseInt(req.query.page) || 1; const data = await scraper.getMovies(page); res.json({ success: true, page, total: data.total, count: data.anime.length, hasNext: data.hasNextPage, data: data.anime }); })); /** * @route GET /api/anime/:slug * @description Get anime detail with episodes */ app.get('/api/anime/:slug', asyncHandler(async (req, res) => { const data = await scraper.getAnimeDetail(req.params.slug); res.json({ success: true, data }); })); /** * @route GET /api/anime/:slug/episodes * @description Get all episodes for an anime */ app.get('/api/anime/:slug/episodes', asyncHandler(async (req, res) => { const anime = await scraper.getAnimeDetail(req.params.slug); res.json({ success: true, anime: { title: anime.title, slug: req.params.slug }, count: anime.episodes.length, episodes: anime.episodes }); })); /** * @route GET /api/anime/:slug/episode/:number * @description Get specific episode with stream/download links */ app.get('/api/anime/:slug/episode/:number', asyncHandler(async (req, res) => { const data = await scraper.getEpisodeDetail(req.params.slug, req.params.number); res.json({ success: true, data }); })); /** * @route GET /api/schedule * @description Get anime release schedule */ app.get('/api/schedule', asyncHandler(async (req, res) => { const data = await scraper.getSchedule(); res.json({ success: true, data }); })); /** * @route GET /api/genres * @description Get all available genres */ app.get('/api/genres', asyncHandler(async (req, res) => { const data = await scraper.getGenres(); res.json({ success: true, count: data.length, data }); })); /** * @route GET /api/genre/:slug * @description Get anime by genre */ app.get('/api/genre/:slug', asyncHandler(async (req, res) => { const page = parseInt(req.query.page) || 1; const data = await scraper.getByGenre(req.params.slug, page); res.json({ success: true, genre: req.params.slug, page, total: data.total, count: data.anime.length, hasNext: data.hasNextPage, data: data.anime }); })); /** * @route GET /api/search * @description Search anime by keyword */ app.get('/api/search', asyncHandler(async (req, res) => { const { q } = req.query; if (!q) { return res.status(400).json({ success: false, message: 'Query parameter "q" is required' }); } const data = await scraper.searchAnime(q); res.json({ success: true, query: q, found: data.results.length, data: data.results }); })); /** * @route GET /api/popular * @description Get popular anime */ app.get('/api/popular', asyncHandler(async (req, res) => { const type = req.query.type || 'weekly'; // weekly, monthly, alltime const data = await scraper.getPopular(type); res.json({ success: true, type, count: Array.isArray(data) ? data.length : 0, data }); })); /** * @route GET /api/anime/:slug/batch * @description Get batch download links for anime */ app.get('/api/anime/:slug/batch', asyncHandler(async (req, res) => { const data = await scraper.getBatchDownloads(req.params.slug); res.json({ success: true, data }); })); /** * @route GET /api/stream * @description Resolve stream URL from x.php link */ app.get('/api/stream', asyncHandler(async (req, res) => { const { url } = req.query; if (!url) { return res.status(400).json({ success: false, message: 'URL parameter is required' }); } const data = await scraper.getStreamUrl(url); res.json({ success: true, data }); })); /** * @route GET /api/list * @description Get complete anime list A-Z */ app.get('/api/list', asyncHandler(async (req, res) => { const data = await scraper.getAnimeListAZ(); res.json({ success: true, count: data.length, data }); })); // Error handler app.use((err, req, res, next) => { console.error(err.stack); res.status(500).json({ success: false, message: err.message || 'Internal Server Error', error: process.env.NODE_ENV === 'development' ? err.stack : undefined }); }); // 404 handler app.use((req, res) => { res.status(404).json({ success: false, message: 'Endpoint not found', available: '/' }); }); app.listen(PORT, () => { console.log(` ╔════════════════════════════════════════╗ ║ SOKUJA API Server v1.0.0 ║ ║ Port: ${PORT} ║ ╚════════════════════════════════════════╝ Available endpoints: - GET / → API info - GET /api/home → Home data - GET /api/ongoing → Ongoing series - GET /api/completed → Completed series - GET /api/anime/:slug → Anime detail - GET /api/search?q= → Search anime - GET /api/schedule → Release schedule `); });