const mongoose = require('mongoose'); const mongoURI = process.env.DATABASE_URL; mongoose.connect(mongoURI); mongoose.connection.on('connected', ()=>{console.log('MongoDB - Worker - Connected')}) .on('disconnected', ()=>{console.log('MongoDB - Disconnect')}) .on('error', (error)=>console.log('Mongoose Error:', error)); const path = require('path'); const fs = require('fs'); const { Stream } = require('stream'); const {Manga, Chapter} = require('./lib'); const FavModel = require('./models/favorite') const EventEmitter = require('events'); const Emitter = new EventEmitter(); let config; if(fs.existsSync('./config.json')) { config = require('./config.json') } const BaseDir = config?.saveFolder || './public/Saved'; var Queue = []; var slots = 2; var shouldKeepOnDownloading = true; // updateQueue(); // var queueUpdateTimer = setInterval(() => { // updateQueue(); // }, 5*60*1000); // process.on('message', m=> // { // if(m.action==='add') // { // clearInterval(queueUpdateTimer); // updateQueue(); // queueUpdateTimer = setInterval(() => { // updateQueue(); // }, 5*60*1000); // return // } // if(m.action==='remove') return removeFromQueue(m.id) // if(m.action==='pause') return shouldKeepOnDownloading=false; // }) function removeFromQueue(id) { const ItemDownloading = Queue.filter(manga => manga.inProgress == true)[0]; if(ItemDownloading) shouldKeepOnDownloading = false; Queue = Queue.filter(manga => manga._id !== id); } Emitter.on('queueUpdate', ()=> { if(!Queue[0]) return if(!Queue[0].inProgress) { Queue[0].inProgress = true; download(Queue[0]) } } ); async function updateQueue() { shouldKeepOnDownloading = true; const Scanlators = fs.readdirSync(BaseDir); const Favs = await FavModel.find(); let Mangas = []; for(let i =0; i { return parseInt(a.split('_')[1]) - parseInt(b.split('_')[1]) }); let latestDownloaded = parseInt(chapters[chapters.length-1].split('_')[1]); let manga = {scanlator:Scanlators[i], title:mangas[j], latestDownloaded} Mangas.push(manga) } } let FavedAndDownloaded = []; let FavedButNotDownloaded = null; for (let i = 0; i < Mangas.length; i++) { FavedAndDownloaded = FavedAndDownloaded.concat(Favs.filter(item => { return (item.scanlator === Mangas[i].scanlator && item.title === Mangas[i].title); }).map((matchedItem) =>{ return ({ ...matchedItem._doc, latestDownloaded: Mangas[i].latestDownloaded }) }) ); } FavedButNotDownloaded = Favs.filter(fav => { // return !FavedAndDownloaded.includes(fav) return !FavedAndDownloaded.some(fad => fad._id === fav._id); }); if(FavedButNotDownloaded) { for(let i = 0; i < FavedButNotDownloaded.length; i++) { (async(i)=> { const {title, link, scanlator, _id} = FavedButNotDownloaded[i]; const manga = await new Manga(scanlator, link, title).get(); let id = _id.toString(); addToQueue(manga, id); })(i) } } if(FavedAndDownloaded) { for(let i = 0; i { const {title, link, scanlator, latestDownloaded, _id} = FavedAndDownloaded[i]; const manga = await new Manga(scanlator, link, title).get(); let id = _id.toString(); checkForMissingFiles(manga) if(latestDownloaded item.num > startFrom); Queue.push({...manga, _id, List, startFrom}) Emitter.emit('queueUpdate', ''); } } async function checkForMissingFiles(manga) { const {scanlator, title, List} = manga; let auxList = List.sort((a,b)=>{return a.num - b.num}) for(var i = 0; i{return item.split('.')[0]}) if(!chapter?.List) continue; var auxChapterList = chapter.List.map(item=>{return item.split('/')[item.split('/').length-1].split('.')[0]}) var incompleteImages = []; for(var k = 0; k!auxChapterDir.includes(item))]; if(missingImages.length == 0) continue; for(var j = 0; jitem.split('/')[item.split('/').length-1].split('.')[0] == missingImages[j])[0] const destination = `${destPath}/${imageLink.split('/')[(imageLink.split('/').length - 1)]}` if(shouldKeepOnDownloading) { const download = await downloadImage(imageLink,destination, 5); } } } } /** * * @param {Path} filePath * @returns {Boolean} */ async function isImageComplete(filePath) { async function IsFileComplete(filePath) { try { await sharp(filePath, { sequentialRead: true }) .raw() .toBuffer({ resolveWithObject: true }); return true } catch (err) { return false; } } } async function download(manga) { if(slots <=0) return slots--; const { scanlator, title} = manga; const List = manga.List.sort((a, b) => { return a.num - b.num }); for (let i = 0; i < List.length; i++) { const ch = List[i]; if (isNaN(ch.num)) continue; const { num, link } = ch; const chapter = await new Chapter(scanlator, link, title, num).get(); // console.log(chapter) if (!chapter?.List?.length) continue; const dlChapter = await downloadChapter(chapter); if(dlChapter=='UserStoped') break; } Queue.shift(); slots++; Emitter.emit('queueUpdate', ''); } async function downloadChapter(chapter) { const {List, scanlator, title, chNum } = chapter; const images = List; for (let i = 0; i < images.length; i++) { const img = images[i]; const destination = `./public/Saved/${scanlator}/${title}/CH_${chNum}/${img.split('/')[(img.split('/').length - 1)]}`; try { const downloaded = await downloadImage(img, destination); console.log(`Downloaded: Scanlator:${scanlator}; Manga: ${title} Chapter:${chNum}; IMG:${img.split('/')[(img.split('/').length - 1)]} ${downloaded.status}`); } catch (error) { console.error(`Error downloading: ${img}: ${error}`); if(!shouldKeepOnDownloading) { ReturnError = error.Error; break; }; } } return !shouldKeepOnDownloading?'UserStoped':{chNum}; } /** * * @param {Link} url * @param {Path} destPath * @param {Number} maxRetries * @returns {Download} * @typedef {String} Link * @typedef {Object} Download * @property {String} status * @property {Path} file */ async function downloadImage(url, destPath, maxRetries = 3) { let retries = 0; if(!shouldKeepOnDownloading) throw new Error('UserStoped'); while (retries < maxRetries && shouldKeepOnDownloading) { try { const response = await fetch(url); const dir = path.dirname(destPath); if (!response.ok) throw new Error(`Failed to download image. Status Code: ${response.status}`); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } const fileStream = fs.createWriteStream(destPath); await new Promise((resolve, reject) => { const stream = Stream.Readable.fromWeb(response.body).pipe(fileStream); stream.on('error', reject); fileStream.on('finish', () => { fileStream.close(); resolve(); }); }); return {status:'Downloaded', file:destPath} } catch (error) { console.error(`Error downloading image: ${destPath}, Retry ${retries + 1}/${maxRetries}`);//, error); retries++; if(retries == maxRetries) return {status:'Failed', file:destPath} } } }