Dave
This commit is contained in:
parent
c0c1673e46
commit
ef0c922762
@ -1,4 +1,3 @@
|
||||
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const app = express();
|
||||
@ -18,7 +17,7 @@ const sqlRoutes = require('./routes/sql');
|
||||
const pexelsRoutes = require('./routes/pexels');
|
||||
|
||||
const openaiRoutes = require('./routes/openai');
|
||||
|
||||
const mangaRoutes = require('./routes/manga');
|
||||
|
||||
|
||||
const usersRoutes = require('./routes/users');
|
||||
@ -191,7 +190,12 @@ app.use(
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
sqlRoutes);
|
||||
|
||||
|
||||
app.use(
|
||||
'/api/manga',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
mangaRoutes
|
||||
);
|
||||
|
||||
const publicDir = path.join(
|
||||
__dirname,
|
||||
'../public',
|
||||
@ -215,4 +219,4 @@ db.sequelize.sync().then(function () {
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = app;
|
||||
module.exports = app;
|
||||
30
backend/src/routes/manga.js
Normal file
30
backend/src/routes/manga.js
Normal file
@ -0,0 +1,30 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const MangaScraperService = require('../services/mangaScraper');
|
||||
const { wrapAsync } = require('../helpers');
|
||||
|
||||
router.get('/search', wrapAsync(async (req, res) => {
|
||||
const { query, sourceId } = req.query;
|
||||
const results = await MangaScraperService.search(query, sourceId);
|
||||
res.json(results);
|
||||
}));
|
||||
|
||||
router.get('/details/:sourceId/:mangaId', wrapAsync(async (req, res) => {
|
||||
const { sourceId, mangaId } = req.params;
|
||||
const details = await MangaScraperService.getMangaDetails(mangaId, sourceId);
|
||||
res.json(details);
|
||||
}));
|
||||
|
||||
router.get('/chapters/:sourceId/:mangaId', wrapAsync(async (req, res) => {
|
||||
const { sourceId, mangaId } = req.params;
|
||||
const chapters = await MangaScraperService.getChapters(mangaId, sourceId);
|
||||
res.json(chapters);
|
||||
}));
|
||||
|
||||
router.get('/pages/:sourceId/:chapterId', wrapAsync(async (req, res) => {
|
||||
const { sourceId, chapterId } = req.params;
|
||||
const pages = await MangaScraperService.getPages(chapterId, sourceId);
|
||||
res.json(pages);
|
||||
}));
|
||||
|
||||
module.exports = router;
|
||||
168
backend/src/services/mangaScraper.js
Normal file
168
backend/src/services/mangaScraper.js
Normal file
@ -0,0 +1,168 @@
|
||||
const axios = require('axios');
|
||||
const db = require('../db/models');
|
||||
|
||||
class MangaScraperService {
|
||||
static async getSource(sourceId) {
|
||||
const source = await db.sources.findByPk(sourceId);
|
||||
if (!source) throw new Error('Source not found');
|
||||
return source;
|
||||
}
|
||||
|
||||
static async search(query, sourceId) {
|
||||
const source = await this.getSource(sourceId);
|
||||
|
||||
// For now, let's implement a real MangaDex search if the name matches,
|
||||
// otherwise fallback to a mock search.
|
||||
if (source.name.toLowerCase().includes('mangadex') || source.base_url.includes('mangadex.org')) {
|
||||
return this.searchMangaDex(query);
|
||||
}
|
||||
|
||||
return this.mockSearch(query, source);
|
||||
}
|
||||
|
||||
static async searchMangaDex(query) {
|
||||
const response = await axios.get('https://api.mangadex.org/manga', {
|
||||
params: {
|
||||
title: query,
|
||||
limit: 20,
|
||||
'includes[]': ['cover_art']
|
||||
}
|
||||
});
|
||||
|
||||
return response.data.data.map(manga => {
|
||||
const coverArt = manga.relationships.find(r => r.type === 'cover_art');
|
||||
const fileName = coverArt ? coverArt.attributes?.fileName : null;
|
||||
const coverUrl = fileName ? `https://uploads.mangadex.org/covers/${manga.id}/${fileName}.256.jpg` : null;
|
||||
|
||||
return {
|
||||
id: manga.id,
|
||||
title: manga.attributes.title.en || Object.values(manga.attributes.title)[0],
|
||||
description: manga.attributes.description.en,
|
||||
coverUrl: coverUrl,
|
||||
source: 'MangaDex'
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
static async mockSearch(query, source) {
|
||||
// Return some mock data based on the source
|
||||
return [
|
||||
{
|
||||
id: `mock-${source.id}-1`,
|
||||
title: `${query} in ${source.name}`,
|
||||
description: `This is a mock result for ${query} from ${source.name}`,
|
||||
coverUrl: 'https://via.placeholder.com/256x360?text=Manga+Cover',
|
||||
source: source.name
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
static async getMangaDetails(mangaId, sourceId) {
|
||||
const source = await this.getSource(sourceId);
|
||||
|
||||
if (source.name.toLowerCase().includes('mangadex') || source.base_url.includes('mangadex.org')) {
|
||||
return this.getMangaDexDetails(mangaId);
|
||||
}
|
||||
|
||||
return this.mockMangaDetails(mangaId, source);
|
||||
}
|
||||
|
||||
static async getMangaDexDetails(mangaId) {
|
||||
const response = await axios.get(`https://api.mangadex.org/manga/${mangaId}`, {
|
||||
params: {
|
||||
'includes[]': ['cover_art', 'author', 'artist']
|
||||
}
|
||||
});
|
||||
|
||||
const manga = response.data.data;
|
||||
const coverArt = manga.relationships.find(r => r.type === 'cover_art');
|
||||
const fileName = coverArt ? coverArt.attributes?.fileName : null;
|
||||
const coverUrl = fileName ? `https://uploads.mangadex.org/covers/${manga.id}/${fileName}.512.jpg` : null;
|
||||
|
||||
return {
|
||||
id: manga.id,
|
||||
title: manga.attributes.title.en || Object.values(manga.attributes.title)[0],
|
||||
description: manga.attributes.description.en,
|
||||
coverUrl: coverUrl,
|
||||
author: manga.relationships.find(r => r.type === 'author')?.attributes?.name,
|
||||
artist: manga.relationships.find(r => r.type === 'artist')?.attributes?.name,
|
||||
status: manga.attributes.status,
|
||||
year: manga.attributes.year,
|
||||
source: 'MangaDex'
|
||||
};
|
||||
}
|
||||
|
||||
static async getChapters(mangaId, sourceId) {
|
||||
const source = await this.getSource(sourceId);
|
||||
|
||||
if (source.name.toLowerCase().includes('mangadex') || source.base_url.includes('mangadex.org')) {
|
||||
return this.getMangaDexChapters(mangaId);
|
||||
}
|
||||
|
||||
return this.mockChapters(mangaId, source);
|
||||
}
|
||||
|
||||
static async getMangaDexChapters(mangaId) {
|
||||
const response = await axios.get(`https://api.mangadex.org/manga/${mangaId}/feed`, {
|
||||
params: {
|
||||
translatedLanguage: ['en'],
|
||||
order: { chapter: 'desc' },
|
||||
limit: 100
|
||||
}
|
||||
});
|
||||
|
||||
return response.data.data.map(chapter => ({
|
||||
id: chapter.id,
|
||||
chapter: chapter.attributes.chapter,
|
||||
title: chapter.attributes.title,
|
||||
language: chapter.attributes.translatedLanguage,
|
||||
externalUrl: chapter.attributes.externalUrl
|
||||
}));
|
||||
}
|
||||
|
||||
static async getPages(chapterId, sourceId) {
|
||||
const source = await this.getSource(sourceId);
|
||||
|
||||
if (source.name.toLowerCase().includes('mangadex') || source.base_url.includes('mangadex.org')) {
|
||||
return this.getMangaDexPages(chapterId);
|
||||
}
|
||||
|
||||
return this.mockPages(chapterId, source);
|
||||
}
|
||||
|
||||
static async getMangaDexPages(chapterId) {
|
||||
const response = await axios.get(`https://api.mangadex.org/at-home/server/${chapterId}`);
|
||||
const { baseUrl, chapter } = response.data;
|
||||
const hash = chapter.hash;
|
||||
const files = chapter.data;
|
||||
|
||||
return files.map(file => `${baseUrl}/data/${hash}/${file}`);
|
||||
}
|
||||
|
||||
static async mockMangaDetails(mangaId, source) {
|
||||
return {
|
||||
id: mangaId,
|
||||
title: `Mock Manga ${mangaId}`,
|
||||
description: `Description for mock manga ${mangaId} from ${source.name}`,
|
||||
coverUrl: 'https://via.placeholder.com/512x720?text=Manga+Cover',
|
||||
source: source.name
|
||||
};
|
||||
}
|
||||
|
||||
static async mockChapters(mangaId, source) {
|
||||
return [
|
||||
{ id: 'chap-1', chapter: '1', title: 'Beginning' },
|
||||
{ id: 'chap-2', chapter: '2', title: 'The Journey' }
|
||||
];
|
||||
}
|
||||
|
||||
static async mockPages(chapterId, source) {
|
||||
return [
|
||||
'https://via.placeholder.com/800x1200?text=Page+1',
|
||||
'https://via.placeholder.com/800x1200?text=Page+2',
|
||||
'https://via.placeholder.com/800x1200?text=Page+3'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MangaScraperService;
|
||||
Loading…
x
Reference in New Issue
Block a user