| |
| |
| |
| |
|
|
| 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(); |
|
|
| |
| const asyncHandler = (fn) => (req, res, next) => { |
| Promise.resolve(fn(req, res, next)).catch(next); |
| }; |
|
|
| |
| |
| |
| |
| 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' |
| } |
| }); |
| }); |
|
|
| |
| |
| |
| |
| app.get('/api/home', asyncHandler(async (req, res) => { |
| const data = await scraper.getHomeComplete(); |
| res.json({ |
| success: true, |
| data |
| }); |
| })); |
|
|
| |
| |
| |
| |
| 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 |
| }); |
| })); |
|
|
| |
| |
| |
| |
| 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 |
| }); |
| })); |
|
|
| |
| |
| |
| |
| 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 |
| }); |
| })); |
|
|
| |
| |
| |
| |
| 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 |
| }); |
| })); |
|
|
| |
| |
| |
| |
| app.get('/api/anime/:slug', asyncHandler(async (req, res) => { |
| const data = await scraper.getAnimeDetail(req.params.slug); |
| res.json({ |
| success: true, |
| data |
| }); |
| })); |
|
|
| |
| |
| |
| |
| 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 |
| }); |
| })); |
|
|
| |
| |
| |
| |
| 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 |
| }); |
| })); |
|
|
| |
| |
| |
| |
| app.get('/api/schedule', asyncHandler(async (req, res) => { |
| const data = await scraper.getSchedule(); |
| res.json({ |
| success: true, |
| data |
| }); |
| })); |
|
|
| |
| |
| |
| |
| app.get('/api/genres', asyncHandler(async (req, res) => { |
| const data = await scraper.getGenres(); |
| res.json({ |
| success: true, |
| count: data.length, |
| data |
| }); |
| })); |
|
|
| |
| |
| |
| |
| 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 |
| }); |
| })); |
|
|
| |
| |
| |
| |
| 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 |
| }); |
| })); |
|
|
| |
| |
| |
| |
| app.get('/api/popular', asyncHandler(async (req, res) => { |
| const type = req.query.type || 'weekly'; |
| const data = await scraper.getPopular(type); |
| res.json({ |
| success: true, |
| type, |
| count: Array.isArray(data) ? data.length : 0, |
| data |
| }); |
| })); |
|
|
| |
| |
| |
| |
| app.get('/api/anime/:slug/batch', asyncHandler(async (req, res) => { |
| const data = await scraper.getBatchDownloads(req.params.slug); |
| res.json({ |
| success: true, |
| data |
| }); |
| })); |
|
|
| |
| |
| |
| |
| 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 |
| }); |
| })); |
|
|
| |
| |
| |
| |
| app.get('/api/list', asyncHandler(async (req, res) => { |
| const data = await scraper.getAnimeListAZ(); |
| res.json({ |
| success: true, |
| count: data.length, |
| data |
| }); |
| })); |
|
|
| |
| 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 |
| }); |
| }); |
|
|
| |
| 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 |
| `); |
| }); |
|
|