You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

230 lines
8.0 KiB

const mongoose = require('mongoose');
const mongoURI = 'mongodb://mongoadmin:something@192.168.71.137:27017';
mongoose.connect(mongoURI);
mongoose.connection.on('connected', ()=>{console.log('MongoDB - 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();
const BaseDir = './public/Saved'
var Queue = [];
var slots = 2;
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)
})
function removeFromQueue(id)
{
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()
{
const Scanlators = fs.readdirSync(BaseDir);
const Favs = await FavModel.find();
let Mangas = [];
for(let i =0; i<Scanlators.length; i++)
{
let mangas = fs.readdirSync(path.join(BaseDir, Scanlators[i]));
if(mangas.length < 0) continue
for(var j = 0; j<mangas.length; j++)
{
let chapters = fs.readdirSync(path.join(BaseDir, Scanlators[i], mangas[j]));
if(chapters.length==0) continue
chapters.sort((a, b) => { return parseFloat(a.split('_')[1]) - parseFloat(b.split('_')[1]) });
let latestDownloaded = parseFloat(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<FavedAndDownloaded.length; i++)
{
(async(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<manga.latestChap) addToQueue(manga, id, latestDownloaded);
})(i);
}
}
function addToQueue(manga, _id,startFrom=0)
{
const List = manga.List.filter(item => 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<auxList.length; i++)
{
const {link} = auxList[i];
const destPath = path.join(BaseDir, scanlator, title, 'CH_'+(i+1));
const chapterDownloaded = fs.existsSync(destPath);
if(!chapterDownloaded) continue;
const chapterDir = fs.readdirSync(destPath);
const chapter = await new Chapter(scanlator, link, title, i+1).get();
let auxChapterDir = chapterDir.map(item=>{return item.split('.')[0]})
var auxChapterList = chapter.List.map(item=>{return item.split('/')[item.split('/').length-1].split('.')[0]})
const missingImages = auxChapterList.filter(item=>!auxChapterDir.includes(item));
if(missingImages.length == 0) continue;
for(var j = 0; j<missingImages.length; j++)
{
const imageLink = chapter.List.filter(item=>item.split('/')[item.split('/').length-1].split('.')[0] == missingImages[j])[0]
const destination = `${destPath}/${imageLink.split('/')[(imageLink.split('/').length - 1)]}`
const download = await downloadImage(imageLink,destination, 5);
console.log('MissingImage:',scanlator, title, 'chapter',i+1, download.status)
}
}
}
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;
await downloadChapter(chapter);
}
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}`);
}
}
return {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;
while (retries < maxRetries) {
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}
}
}
}