sunnymh-scraper/src/downloaders/MangaDownloader.js
2025-05-14 16:20:56 +08:00

135 lines
5.0 KiB
JavaScript

const fs = require('fs').promises;
const path = require('path');
const helpers = require('../utils/helpers');
const ColaMangaScraper = require('../scrapers/ColaMangaScraper');
const MangaPoster = require('../posters/MangaPoster');
const config = require('../config/config');
/**
* Main class for downloading manga
*/
class MangaDownloader {
constructor() {
this.outputDir = config.baseDir;
this.maxConcurrentManga = config.getDownloadSetting('maxConcurrentDownloads');
this.mangaPoster = new MangaPoster();
}
/**
* Initialize the downloader
* @returns {Promise<void>}
*/
async init_colamanga_scraper() {
this.mangaUrls = await helpers.readJsonFile(config.mangaUrlsPath) || {};
this.scraper = new ColaMangaScraper();
await this.scraper.init();
}
/**
* Download a single manga with memory management
* @param {string} mangaName - Manga name
* @param {string} mangaUrl - Manga URL
* @returns {Promise<void>}
*/
async downloadManga(mangaName, mangaUrl) {
console.log(`Getting Manga: ${mangaName} info`);
try {
const mangaDetails = await this.scraper.getMangaInfo(mangaUrl);
// // Save manga info
// const infoPath = path.join(this.outputDir, 'info.json');
// await fs.writeFile(infoPath, JSON.stringify(mangaDetails, null, 2));
// Load manga info
// const mangaDetails = await helpers.readJsonFile(path.join(this.outputDir, 'info.json'));
const mangaDir = path.join(this.outputDir, mangaName);
await helpers.ensureDirectory(mangaDir);
// Save cover image
if (mangaDetails.coverPic && await helpers.fileExists(path.join(mangaDir, 'cover.jpg')) == false) {
const coverPath = path.join(mangaDir, 'cover.jpg');
await fs.writeFile(coverPath, mangaDetails.coverPic);
mangaDetails.coverPic = null;
}
const { chapters, coverPic, genres, ...mangaInfo } = mangaDetails;
let response = await this.mangaPoster.getMangaId(mangaDetails.mangaName);
if (response.success == true) {
mangaDetails.mangaId = response.mangaId;
}
else {
mangaDetails.mangaId = helpers.generateId();
this.mangaPoster.insertMangaInfo({mangaId: mangaDetails.mangaId, ...mangaInfo});
this.mangaPoster.insertMangaGenres(mangaDetails.mangaId, genres);
}
// Download chapters in sequence to manage memory
for (const chapter of chapters) {
if (await helpers.isDirectoryNotEmpty(path.join(mangaDir, chapter.chapterName))) {
console.log(`Skipping Manga: ${mangaDetails.mangaName}, Chapter: ${chapter.order} - ${chapter.chapterName} as it already exists`);
continue;
}
console.log(`Downloading Manga: ${mangaDetails.mangaName}, Chapter: ${chapter.order} - ${chapter.chapterName}`);
const chapterDir = path.join(mangaDir, chapter.chapterName);
await helpers.ensureDirectory(chapterDir);
await this.scraper.downloadChapterPics(chapter, chapterDir);
await this.mangaPoster.insertMangaChapter({
mangaId: mangaDetails.mangaId,
chapterName: chapter.chapterName,
chapterOrder: chapter.order
});
await this.scraper.cleanup();
// Add smart delay between chapter downloads
const delay = helpers.getSmartDelay();
console.log(`Waiting ${helpers.formatDelay(delay)} before next chapter...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
console.log(`Completed downloading: ${mangaName}`);
} catch (error) {
throw new Error(`Failed to download manga ${mangaName}: ${error.message}`);
}
}
/**
* Run the downloader with memory management
* @returns {Promise<Object>} Download results
*/
async run() {
const results = {
successful: [],
failed: []
};
try {
// Process manga sequentially to manage memory
for (const [mangaName, mangaUrl] of Object.entries(this.mangaUrls)) {
try {
await this.downloadManga(mangaName, mangaUrl);
results.successful.push(mangaName);
// Clean up after each manga
await this.scraper.cleanup();
// Force garbage collection
if (global.gc) {
global.gc();
}
} catch (error) {
results.failed.push({ name: mangaName, error: error.message });
}
}
} finally {
await this.scraper.close();
}
return results;
}
}
module.exports = MangaDownloader;