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.

675 lines
26 KiB

const {Search, Chapter, Manga,SearchByTag, isValidID, Modules, Baker, Crypto, cookieParser} = require('../lib.js')
const mongoose = require('mongoose')
const FavoriteModel = require('../models/favorite.js');
const ChapterModel = require('../models/readChapter.js');
const fs = require('fs');
const path = require('path');
exports.home = async (req,res)=>
{
const cookieStr = req.headers.cookie;
if(!cookieStr) return res.render('home.ejs',{data:null});
const cookie = cookieParser(cookieStr, 'hash');
res.render('home.ejs', {data:cookie});
}
exports.searchBar = (req, res)=>
{
res.render('searchBar.ejs');
}
exports.search = async (req, res)=>
{
if(!req.body?.searchstring) return res.sendStatus(404)
var data = await new Search(req.body.searchstring).searchExtraInfo();
if(!Array.isArray(data)) return res.sendStatus(404);
const favorites = await FavoriteModel.find()
const favoriteScanlators = [...new Set(favorites.map(favorite => favorite.scanlator))];
for(let i = 0; i<data.length; i++)
{
for(let j= 0; j < data[i].Results.length; j++)
{
const scanlator = data[i].scanlator;
if(!favoriteScanlators.includes(scanlator)) continue
let item = data[i].Results[j];
const matchingFavorite = favorites.find(fav=>
{
return item.title === fav.title && scanlator === fav.scanlator;
})
if(matchingFavorite) data[i].Results[j].favorite = matchingFavorite._id;
}
}
data = data.filter(item => item.Results.length > 0)
data.sort((a,b)=>
{
const scanlatorA = a.scanlator.toLowerCase();
const scanlatorB = b.scanlator.toLowerCase();
return scanlatorA.localeCompare(scanlatorB);
})
res.render('searchResults.ejs', {data})
}
exports.bookmark = (worker)=>
{
return async (req, res)=>
{
if (!req.params || typeof req.params !== 'object' || !('scanlator' in req.params) || !('title' in req.params)) return res.sendStatus(404);
const {scanlator, title, idorLink} = req.params;
const {cookie} = req.headers;
if(!cookie) return res.sendStatus(404);
const config = require('../config.json');
if(cookieParser(cookie, 'hash')!=config.hash) return res.sendStatus(404);
if(isValidID(idorLink))
{
worker.send({ action:'remove', idorLink})
return await FavoriteModel.findOneAndDelete({_id:idorLink}).then((deleted)=>
{
return res.render('bookmark.ejs', {scanlator, title, link:encodeURIComponent(deleted.link)})
})
}
const scanlatorExists = await new Modules(scanlator).exists();
if(!scanlatorExists) return res.sendStatus(404);
const scanlatorHasTitle = await new Modules(scanlator).titleExists(title);
if(!scanlatorHasTitle) return res.sendStatus(404);
const fav = new FavoriteModel()
fav.scanlator = scanlator;
fav.title = title;
fav.link = idorLink;
await fav.save();
worker.send({action:'add', id:fav._id})
res.render('bookmarked.ejs', {scanlator, title, link:idorLink, id:fav._id});
let manga;
try {
manga = await new Manga(scanlator, idorLink, title).get();
} catch (error) {
return FavoriteModel.findByIdAndDelete(fav._id)
}
const {tags} = manga;
FavoriteModel.findByIdAndUpdate(fav._id,{tags}, { new: true})
.then((doc, err) =>
{
if (err) console.error('test',err);
});
}
}
exports.manga = async (req, res)=>
{
const {scanlator, link, title} = req.params;
const scanlatorExists = await new Modules(scanlator).exists();
if(!scanlatorExists) return res.sendStatus(404);
const scanlatorHasTitle = await new Modules(scanlator).titleExists(title);
if(!scanlatorHasTitle) return res.sendStatus(404);
let manga;
let chaptersRead;
try
{
manga = await new Manga(scanlator,link, title).get();
chaptersRead = await ChapterModel.find({scanlator,title});
for(let i = 0; i<manga.List.length; i++)
{
for(let j = 0; j<chaptersRead.length; j++)
{
if(manga.List[i].num==chaptersRead[j].chapterNum)
{
manga.List[i]['completely'] = chaptersRead[j].completely
manga.List[i]['Read'] = true;
manga.List[i]['_id'] = chaptersRead[j]._id;
}
}
}
const filteredArray = chaptersRead.length>0?chaptersRead.filter(item => item.completely == true):chaptersRead;
filteredArray.sort((a, b) => a.chapterNum - b.chapterNum);
const lastCompletelyRead = filteredArray.pop();
const sortedChapters = chaptersRead.sort((a, b) => a.chapterNum - b.chapterNum);
const sortedAndFilteredChapters = sortedChapters.filter(item=> item.lastImageRead && !item.completely);
const lastChapterWithReadImages=sortedAndFilteredChapters.shift();
let chapterNumToContinue = getChapterNumToContinue(lastCompletelyRead, lastChapterWithReadImages, manga.List);
if(!manga.List.some(item=> item.num==chapterNumToContinue)) chapterNumToContinue = Math.trunc(chapterNumToContinue);
if(lastCompletelyRead || lastChapterWithReadImages)
{
let index = manga.List.indexOf(manga.List.filter(item=>
{
return item.num == chapterNumToContinue
})[0]);
manga.List[index]['continue'] = true;
}
}
catch (error)
{
console.log('There was an error:', error);
}
res.render('mangaPage.ejs', {data:{...manga, scanlator}});
}
/**
*
* @param {Chapter} lastCompletelyRead
* @param {Chapter} lastChapterWithReadImages
* @param {ChapterList} List
* @returns {Nyumber}
*/
function getChapterNumToContinue(lastCompletelyRead, lastChapterWithReadImages, List)
{
if(!lastCompletelyRead && !lastChapterWithReadImages) return List[0].num;
const firstIndex = List.indexOf(List.filter(item=>item.num == lastCompletelyRead.chapterNum)[0]);
if(lastCompletelyRead)
{
if(lastCompletelyRead.chapterNum < lastChapterWithReadImages.chapterNum)
{
return lastChapterWithReadImages.chapterNum;
}
const chNum = lastCompletelyRead.chapterNum+1;
const secondIndex = List.indexOf(List.filter(item=>item.num == chNum)[0]);
if(secondIndex - firstIndex > 1) return List[firstIndex + 1].num;
return chNum
}
return lastChapterWithReadImages.chapterNum;
}
exports.chapter = async (req, res)=>
{
const {scanlator, title, chapter, link} = req.params;
//IF its already there even with wrong things its there just show it
const fileExists = fs.existsSync(`./public/Saved/${scanlator}/${title}/CH_${chapter}`);
if(fileExists)
{
const imgs =fs.readdirSync(`./public/Saved/${scanlator}/${title}/CH_${chapter}`);
const latestChap = fs.readdirSync(`./public/Saved/${scanlator}/${title}`).length
const List = imgs.map(filename => {return `./Saved/${scanlator}/${title}/CH_${chapter}/${filename}`});
const mangaLink = await new Chapter(scanlator, link, title, chapter).getMangaLink();
return res.render('chapterPage.ejs', {data:{title,latestChap,scanlator, chapterNum:parseFloat(chapter), mangaLink, List}});
}
//If it isn't make sure there is no bad params being passed
const scanlatorExists = await new Modules(scanlator).exists();
if(!scanlatorExists) return res.sendStatus(404);
const scanlatorHasTitle = await new Modules(scanlator).titleExists(title);
if(!scanlatorHasTitle) return res.sendStatus(404);
let manga;
try
{
manga = await new Chapter(scanlator, link, title,chapter).get();
}
catch (error)
{
return res.sendStatus(404);
}
if(!manga.List)
{
return res.redirect('/')
}
res.render('chapterPage.ejs', {data:{...manga, scanlator, chapterNum:parseFloat(chapter)}});
}
exports.favorites = async (req, res)=>
{
const favs = await FavoriteModel.find();
var mangas = [];
if(favs.length == 0) return res.render('favorites.ejs', {isEmpty:true, mangas})
await Promise.all(Object.keys(favs).map(async (key) =>
{
const manga = favs[key];
var aux = await new Manga(manga.scanlator, manga.link, manga.title).get();
if(aux)
{
aux.favorite = manga._id;
mangas.push(aux);
}
}));
function getUniqueScanlators(items)
{
const scanlators = {};
items.forEach(item => {
scanlators[item.scanlator] = true;
});
return Object.keys(scanlators);
}
const uniqueScanlators = getUniqueScanlators(mangas);
const scanlatorsArray = uniqueScanlators.map(scanlator =>
{
var scanlatorItems = mangas.filter(manga => manga.scanlator === scanlator);
if(!scanlatorItems) return res.sendStatus(500);
scanlatorItems.sort((a, b) =>
{
if (a.title.toUpperCase() < b.title.toUpperCase()) return -1;
else return 1;
});
return {
scanlator,
Results: scanlatorItems
};
});
scanlatorsArray.sort((a,b)=>
{
const scanlatorA = a.scanlator.toLowerCase();
const scanlatorB = b.scanlator.toLowerCase();
return scanlatorA.localeCompare(scanlatorB);
})
res.render('favorites.ejs', {isEmpty:false, mangas:scanlatorsArray})
}
exports.recommended = async (req,res)=>
{
const favorites = await FavoriteModel.find();
const favoriteScanlators = favorites.map(favorite => favorite.scanlator);
let favTags = [];
for(var i = 0; i<favorites.length; i++)
{
favTags.push(...favorites[i].tags)
}
const counted = favTags.reduce((acc, curr)=>
{
acc[curr] = (acc[curr]||0)+1;
return acc;
}, {});
const mostCommonTags = [...new Set(favTags)].sort((a,b)=>
{
if(counted[b] !== counted[a])
{
return counted[b]-counted[a];
}
else
{
return favTags.indexOf(a)-favTags.indexOf(b);
}
})
const firstTwoTags =favoriteScanlators.length <=0 ? ['action', 'shounen'] : mostCommonTags.slice(0,2);
let recommended = [];
for(var i =0; i<2; i++)
{
const results = await new SearchByTag(firstTwoTags[i]).search();
recommended.push(results);
}
const groupedByScanlator = recommended.reduce((acc, currentArray) =>
{
currentArray.forEach(({ Results, scanlator }) =>
{
if (!acc[scanlator])
{
acc[scanlator] = { Results: [], scanlator };
}
const uniqueResults = Results.filter(result =>
!acc[scanlator].Results.some(existingResult => existingResult.title === result.title)
);
acc[scanlator].Results = acc[scanlator].Results.concat(uniqueResults);
});
return acc;
}, {});
recommended = Object.values(groupedByScanlator);
recommended.forEach((item, index) =>
{
if (favoriteScanlators.includes(item.scanlator))
{
item.Results.find(result =>
{
const matchingFavorite = favorites.find(favorite =>
{
return result.title === favorite.title && item.scanlator === favorite.scanlator;
});
if (matchingFavorite)
{
result.favorite = matchingFavorite._id;
return true;
}
return false;
});
}
});
recommended.sort((a,b)=>
{
const scanlatorA = a.scanlator.toLowerCase();
const scanlatorB = b.scanlator.toLowerCase();
return scanlatorA.localeCompare(scanlatorB);
})
res.render('searchResults.ejs', {data:recommended});
}
exports.chapterNavInfo = async (req, res)=>
{
const {scanlator, title, mangaLink} = req.params;
const scanlatorExists = await new Modules(scanlator).exists();
if(!scanlatorExists) return res.sendStatus(404);
const scanlatorHasTitle = await new Modules(scanlator).titleExists(title);
if(!scanlatorHasTitle) return res.sendStatus(404);
let chapter = parseFloat(req.params.chapter);
let manga;
try
{
manga = await new Manga(scanlator, mangaLink, title).get();
}
catch (error)
{
return res.sendStatus(404);
}
const {latestChap, List} = manga;
List.sort((a,b)=> {return a.num - b.num});
let nextChapterLink = chapter>= latestChap? '' : List[chapter].link;
let prevChapterLink = chapter<=0? '' : List[chapter-1].link;
res.render('chapterNav.ejs', {data:{title,latestChap,scanlator, chapterNum:chapter,mangaLink, nextChapterLink, prevChapterLink}})
}
exports.hasConfig = (req, res, next) =>
{
const exists = fs.existsSync('./config.json');
if(!exists) return this.config(req,res)
next();
}
exports.config = async (req, res)=>
{
function getSubDirectories(dir)
{
let protectedSubDirs = ['node_modules', '.git', 'controllers', 'models', 'Modules', 'css','images', 'js', 'routes', 'views'];
const map = new Map();
const dirs = fs.readdirSync(dir)
.filter(file => fs.statSync(path.join(dir, file)).isDirectory())
.filter(file => !protectedSubDirs.includes(file))
.filter(file=> !file.includes('scans'));
for(let i =0; i<dirs.length; i++)
{
map.set(dirs[i], getSubDirectories(path.join(dir, dirs[i])));
}
return map.size==0?'':map;
}
function getPaths(map, prefix='.')
{
const paths = [];
for (const [key, value] of map.entries())
{
const fullPath = `${prefix}/${key}`;
if (value instanceof Map)
{
paths.push(...getPaths(value, fullPath));
}
else
{
paths.push(fullPath);
}
}
return paths;
}
const subDirs = await getSubDirectories('./');
const data = getPaths(subDirs)
res.render('config.ejs', {data});
}
exports.configPost = async (req, res)=>
{
const {username, password, password2, saveFolder} = req.body;
const {body} = req;
if(!username || !password || !password2) return res.sendStatus(404);
if(username.length < 4) return res.sendStatus(404);
if(password !== password2) return res.sendStatus(404);
if(!checkValidPath(saveFolder)) return res.sendStatus(404);
if(!saveFolder) body.saveFolder = './public/Saved'
body['hash'] = new Crypto(username, password).Hash;
await fs.writeFileSync('./config.json', JSON.stringify(body));
res.redirect('/');
}
/**
* @param {String} dir
* @returns {Boolean}
*/
function checkValidPath(dir)
{
if(!dir) return true; //by pass when there is no savePath (use default);
dir = dir;
return !path.isAbsolute(dir)
}
exports.loginPage = async (req, res)=>
{
res.render('login.ejs');
}
exports.login = async (req, res)=>
{
const {username, password} = req.body;
if(!username || !password ) return res.sendStatus(404);
const config = require('../config.json');
if(username != config.username || password != config.password) return res.sendStatus(404);
await Baker(res, 'hash', config.hash, 24)
//TODO: change this to dashboard
res.redirect('/');
}
exports.logout = async (req, res)=>
{
const config = require('../config.json');
const cookieStr = req.headers.cookie;
if(!cookieStr) return res.redirect('/');
const hash = cookieParser(cookieStr, 'hash');
if(hash != config.hash) return res.redirect('/');
await Baker(res, 'hash', '')
res.render('dashboard.ejs');
}
exports.errorPage = (req, res)=>
{
res.render('error.ejs')
}
exports.chapterRead = async (req, res)=>
{
const cookieStr = req.headers.cookie;
if(!cookieStr) return res.sendStatus(404);
const {scanlator, title, chapter, link} = req.params;
const scanlatorExists = await new Modules(scanlator).exists();
if(!scanlatorExists) return res.sendStatus(404);
const scanlatorHasTitle = await new Modules(scanlator).titleExists(title);
if(!scanlatorHasTitle) return res.sendStatus(404);
const manga = await new Manga(scanlator, link, title).get();
const {latestChap, List} = manga;
if(chapter > latestChap || chapter < 0) return res.sendStatus(404);
const chap = new ChapterModel();
let chapterNum = chapter;
let chapFromList = List.filter(item=>item.num == chapterNum)[0];
let chapterLink = chapFromList.link;
let chapterDate = chapFromList.date;
if(!chapterLink) return res.sendStatus(404);
const _chapter = await new Chapter(scanlator, chapterLink,title, chapterNum).get();
let lastImageRead = _chapter.List[_chapter.List.length-1];
chap.lastImageRead = lastImageRead;
const chaps = await ChapterModel.find({scanlator, title, link, chapterNum});
if(chaps.length>0) updateChapter(chaps[0], lastImageRead, true);
_chapter['num'] = _chapter.chNum;
_chapter['date'] = chapterDate;
_chapter['Read'] = true;
_chapter['_id'] = chap._id;
_chapter['completely'] = true;
res.render('chapterButton.ejs', {chapter:_chapter, scanlator, title, link, shortCut:false});
chap.scanlator = scanlator;
chap.title = title;
chap.link = link;
chap.chapterNum = chapter;
chap.lastImageRead = lastImageRead;
chap.completely = true;
chap.save()
}
async function updateChapter(chap,lastImageRead, completely=false,res)
{
await ChapterModel.findOneAndUpdate(chap._id,{completely, lastImageRead:lastImageRead}, {new:true});
if(res) return res.sendStatus(200)
}
exports.chapterIncompleteRead = async (req,res)=>
{
const {scanlator, title, chapter, link, img} = req.params;
const scanlatorExists = await new Modules(scanlator).exists();
if(!scanlatorExists) return res.sendStatus(404);
const scanlatorHasTitle = await new Modules(scanlator).titleExists(title);
if(!scanlatorHasTitle) return res.sendStatus(404);
let chapterNum = chapter;
const manga = await new Manga(scanlator, link, title).get();
const {latestChap, List} = manga;
let chapterLink = List.filter(item=> item.num == chapterNum)[0].link;
if(chapter > latestChap || chapter < 0) return res.sendStatus(404);
const chap = new ChapterModel();
const _chapter = await new Chapter(scanlator, chapterLink,title, chapterNum).get();
let lastImageRead = _chapter.List.filter(item=>
{
let parsedImageURL = item.split('.')[item.split('.').length-2].split('/')[item.split('.')[item.split('.').length-2].split('/').length-1]
return(parsedImageURL==img)
})[0];
const chaps = await ChapterModel.find({scanlator, title, link, chapterNum});
if(chaps.length>0) return updateChapter(chaps[0],res, lastImageRead)
chap.scanlator = scanlator;
chap.title = title;
chap.link = link;
chap.chapterNum = chapter;
chap.lastImageRead = lastImageRead;
chap.completely = false;
chap.save()
res.sendStatus(200);
}
exports.chapterLastRead = async (req, res)=>
{
const {scanlator, title, chapter, link} = req.params;
const scanlatorExists = await new Modules(scanlator).exists();
if(!scanlatorExists) return res.sendStatus(404);
const scanlatorHasTitle = await new Modules(scanlator).titleExists(title);
if(!scanlatorHasTitle) return res.sendStatus(404);
let chapterNum = chapter;
const chaps = await ChapterModel.find({scanlator, title, link, chapterNum});
if(chaps.length == 0) return res.sendStatus(404);
let data = chaps[0].lastImageRead;
res.render('data.ejs', {data});
}
exports.continue = async (req, res)=>
{
async function getChaptersToContinue()
{
const favs = await FavoriteModel.find();
const chaps = await ChapterModel.find();
const favMap = new Map(favs.map(fav => [`${fav.title}_${fav.scanlator}`, {latestComplete: null, incomplete: []}]));
const favoritesNotYetStarted = [];
// Filter chapters directly into their respective categories
chaps.forEach(chap =>
{
const key = `${chap.title}_${chap.scanlator}`;
if (favMap.has(key))
{
const group = favMap.get(key);
if (chap.completely) {
if (!group.latestComplete || chap.chapterNum > group.latestComplete.chapterNum) group.latestComplete = chap;
}
else
{
group.incomplete.push(chap);
}
}
});
favs.forEach((element) =>
{
const {title, scanlator} = element;
if (!chaps.some(chap => chap.title == title && chap.scanlator == scanlator)) {
favoritesNotYetStarted.push(element);
}
});
// Compile the results
let results = Array.from(favMap.values()).flatMap(group =>
group.latestComplete ? [group.latestComplete, ...group.incomplete] : group.incomplete
);
results = await Promise.all(results.map(async (chap) =>
{
if (!chap.completely) return chap;
let next = await getNextChapter(chap);
return next ? next : null;
}));
results = results.filter(chap => chap !== null);
// Filter to find unique chapters that are not completed
const uniqueContinuationChapters = chaps.filter(chap =>
{
return !chap.completely && !results.some(f => f.scanlator === chap.scanlator && f.title === chap.title)
});
const chaptersToStart = [];
for(var i = 0; i< favoritesNotYetStarted.length; i++)
{
const {title, link, scanlator} = favoritesNotYetStarted[i];
const {List, img} = await new Manga(scanlator, link, title).get();
let aux = {byPass:true, chapterNum:0};
const chap = await new Chapter(scanlator, List[0].link, title, List[0].num).get();
const imgReadOutOfTotal = 0+'/'+(chap.List.length-1)
aux = {...aux, ...chap, mangaCoverImg:img, imgReadOutOfTotal,lastImageRead:chap.List[0]}
chaptersToStart.push(aux);
}
return [...results, ...uniqueContinuationChapters, ...chaptersToStart];
}
const chaptersToContinue = await getChaptersToContinue();
chaptersToContinue.sort((a, b) => {
if (a.chapterNum === 0) return 1;
if (b.chapterNum === 0) return -1;
return a.chapterNum - b.chapterNum;
});
for(let i = 0; i<chaptersToContinue.length; i++)
{
if(chaptersToContinue[i].byPass) continue
if(!chaptersToContinue[i].mangaLink)
{
const {scanlator, link, title, chapterNum} = chaptersToContinue[i];
const {List, img} = await new Manga(scanlator, link, title).get();
if(!List.some(item=>item.num == chapterNum)) return;
let chapter = await new Chapter(scanlator,List[chapterNum-1].link, title,chapterNum).get();
chaptersToContinue[i].link = chapter.link;
let index = chapter.List.indexOf(chaptersToContinue[i].lastImageRead)
const imgReadOutOfTotal = (index !=-1 ? index:0)+'/'+(chapter.List.length-1)
let aux = {...chaptersToContinue[i]._doc, mangaCoverImg:img, imgReadOutOfTotal}
chaptersToContinue[i] = aux;
}
}
res.render('chaptersToContinue.ejs', {data:chaptersToContinue});
}
async function getNextChapter (chapter)
{
const {scanlator, link, title, _id } = chapter;
let chapterNum = chapter.chapterNum;
const manga = await new Manga(scanlator, link, title).get();
if((chapterNum+1)>manga.latestChap) return false;
if(!manga.List.some(item=>item.num == (chapterNum+1))) chapterNum = Math.trunc(chapterNum);
const chapterFromList = manga.List.filter(item=>item.num == (chapterNum+1))[0]
const {date, num} = chapterFromList;
const nextChapter = await new Chapter(scanlator, chapterFromList.link, title, (chapterNum+1)).get();
let index = chapter.lastImageRead?nextChapter.List.indexOf(chapter.lastImageRead):-1;
const imgReadOutOfTotal = (index !=-1 ? index:0)+'/'+(nextChapter.List.length-1)
return {
...nextChapter,
num,
date,
_id,
mangaCoverImg:manga.img,
chapterNum:nextChapter.chNum,
lastImageRead:nextChapter.List[0],
completely:false,
imgReadOutOfTotal,
byPass:true
};
}
exports.chapterToContinue = async (req, res)=>
{
const {scanlator, mangaLink, title} = req.params;
const chaptersRead = await ChapterModel.find({scanlator, title}).then(res=>{return res});
let sorted = chaptersRead.sort((a,b)=>{return b.chapterNum-a.chapterNum});
const newChapter = await getNextChapter({scanlator, link:mangaLink, title,chapterNum:parseFloat(sorted[0].chapterNum), _id:null});
res.render('chapterButton.ejs',{chapter:newChapter, scanlator, title,shortCut:true})
}
exports.removeChapter = async (req, res)=>
{
const cookieStr = req.headers.cookie;
if(!cookieStr) return res.sendStatus(404);
if(!isValidID(req.params.id)) return res.sendStatus(404);
const {link, scanlator, title, chapterNum} = await ChapterModel.findOneAndDelete({_id:req.params.id}).then(deleted=>{return deleted});
const {List} = await new Manga(scanlator, link, title).get();
const chapter = List.filter(item=>item.num == chapterNum)[0];
res.render('chapterButton.ejs', {chapter, scanlator, title, shortCut:false});
}
exports.dashboard = async (req, res)=>
{
let data = 0;
res.render('dashboard.ejs', {data});
}