const {parse} = require('node-html-parser'); const {decodeHTML} = require('../lib') module.exports = class ReaperModule { /** * @property {Scanlator} scanlator - Scanlator name * @typedef {String} Scanlator * @pattern /^[\w-]+-scans$/ */ constructor() { this.scanlator = 'Reaper-scans'; this.BaseLink = 'https://reaper-scans.com/'; this.timeout = 5000; } /** * * @param {String} query * @returns {Array<Manga>} * @typedef {Object} Manga * @property {Link} link - Manga Link * @property {String} title - Manga Title * @property {String} Status - Manga Status * @property {Link} img - Image Link * @property {Number} latestChap - Latest Chapter Number * @typedef {String} Link */ async Search(query) { return await fetch(this.BaseLink+'?s='+query, {timeout:this.timeout}) .then(handleResponse) .then(handleData) .catch(handleError); function handleResponse(response) { return response.text(); } function handleError(error) { return error; } function handleData(data) { data = data.split('<div class="listupd">')[1].split('<div class="pagination">')[0] const document = parse(data); let payload = []; for(var result of document.querySelectorAll('a')) { let aux = { link:result.rawAttrs.split('"')[1], title:result.rawAttrs.split('"')[3], img:result.querySelectorAll('.limit > img')[0].rawAttrs.split('"')[1], status:result.querySelector('.status')?.rawText||'Ongoing', latestChap:parseInt(result.childNodes[1].childNodes[1].childNodes[0].rawText.split(" ")[1]), }; payload.push(aux); } return payload } } /** * * @param {String} query * @returns {Array<Manga>} * @typedef {Object} Manga * @property {Link} link - Manga Link * @property {String} title - Manga Title * @property {Link} img - Image Link * @property {Number} latestChap - Latest Chapter Number * @typedef {String} Link */ async SearchByTag(query, ammount=5) { return await fetch(this.BaseLink+'genres/'+query, {timeout:this.timeout}) .then(handleResponse) .then(handleData) .catch(handleError); function handleResponse(response) { return response.text(); } function handleError(error) { return error; } function handleData(data) { data = data.split('<div class="listupd">')[1].split('<div class="pagination">')[0] const document = parse(data); var payload = []; var results = document.querySelectorAll('a') var limit = results.length<ammount?results.length:ammount; for(var i =0;i<limit; i++) { const result = results[i]; var aux = { link:result.rawAttrs.split('"')[1], title:decodeHTML(result.rawAttrs.split('"')[3]), img:result.querySelectorAll('.limit > img')[0].rawAttrs.split('data-src="')[1].split('"')[0], status:result.querySelector('.status')?.rawText||'Ongoing', latestChap:parseInt(result.childNodes[1].childNodes[1].childNodes[0].rawText.split(" ")[1]), }; payload.push(aux); } return payload } } /** * * @param {String} name Manga/Manwha Name * @param {Link} link Manga url ( * @returns {Array<Chapter>} - Chapters Array * @typedef {Object} Chapter * @property {Number} num - Chapter Number * @property {String} link - Chapter Link * @property {String} date - Chapter Release Date * @typedef {String} Link * @pattern ^(https?:\/\/)?([\w\d.-]+)\.([a-z]{2,})(:[0-9]+)?(\/\S*)?$ */ async ChapterList(link, name) { return await fetch(link, {timeout:this.timeout}) .then(handleResponse) .then(handleData) .catch(handleError); function handleResponse(response) { return response.text(); } function handleError(error) { return error; } function handleData(data) { const document = parse(data); const chapterlist = document.querySelectorAll('ul')[2]; const chapters = parse(chapterlist).querySelectorAll('li'); var payload = []; try { for(var chapter of chapters) { var aux = { num:parseInt(chapter.rawAttrs.split('"')[1]), link:chapter.childNodes[0].childNodes[0].childNodes[1].rawAttrs.split('"')[1], date:chapter.childNodes[0].childNodes[0].childNodes[1].childNodes[3].rawText, }; payload.push(aux); } } catch (error) { throw new Error('This manga should be called by url', error); } return payload } } /** * * @param {Link} Link Chapter Link * @returns {ChapterList} * @typedef {Obejct} ChapterList * @property {Array<Image>} List * @property {Link} mangaLink - Manga Link * @typedef {String} Image - Image link * @typedef {String} Link * @pattern ^(https?:\/\/)?([\w\d.-]+)\.([a-z]{2,})(:[0-9]+)?(\/\S*)?$ */ async Chapter(Link) { return await fetch(Link, {timeout:this.timeout}) .then(handleResponse) .then(handleData) .catch(handleError); function handleResponse(response) { return response.text(); } function handleError(error) { return error; } function handleData(data) { const document = parse(data); var List = []; let mangaLink = document.querySelectorAll('.allc')[0].querySelector('a')._rawAttrs.href; for(var image of document.querySelectorAll('p')[1].childNodes) { if(image.rawAttrs) { let index = image.rawAttrs.split('"').findIndex(el => el.includes('http')); List.push(image.rawAttrs.split('"')[index].replace(/\n/g, '')); } } return {List, mangaLink} } } /** * * @param {String} Title * @returns {Manga} * @typedef {Object} Manga * @property {Link} Link - Manga Link * @property {Link} img - Image Link * @property {String} description - Latest Chapter Number * @property {String} Type - Manga Type * @property {String} Released - Year of Release * @property {String} Author - Author's name * @property {String} Artist - Artist Team * @property {String} Serialization - Serialization Company * @property {Number} latestChap - Latest Chap number * @property {Array<String>} tags - Manga Tags * @typedef {String} Link */ async GetManga(Link, title) { const auxTitle = title; return await fetch(Link, {timeout:this.timeout}) .then(handleResponse) .then(handleData) .catch(handleError); function handleResponse(response) { return response.text(); } function handleError(error) { return error; } function handleData(data) { const parsed = parse(data) let description = parsed.querySelectorAll('.entry-content-single')[0].innerText description = decodeHTML(description); if(description.split('/>').length>1) description = description.split('/>')[1] const img = parsed.querySelectorAll('img')[1].rawAttrs.split('src="')[1].split('"')[0].includes('data:')? parsed.querySelectorAll('img')[1].rawAttrs.split('data-src="')[1].split('"')[0]:parsed.querySelectorAll('img')[1].rawAttrs.split('src="')[1].split('"')[0]; let table = parsed.querySelectorAll('.infotable')[0].childNodes[0].structuredText.split('\n'); let tags = parsed.querySelectorAll('.seriestugenre')[0].structuredText.split('\n')[0].split(' '); let latestChap = parseInt(parsed.querySelectorAll('.epcurlast')[0].rawText.split(' ')[1]) table.splice(-6); const aux = {}; for (let i = 0; i < table.length; i += 2) { const key = table[i].trim(); aux[key] = table[i + 1].trim(); } return {description, img, ...aux, tags, title:auxTitle, latestChap} } } /** * * @param {String} title Manga Title * @returns {Boolean} */ async hasTitle(title) { const Search = await this.Search(title); if(!Array.isArray(Search)) return false return Search.filter(Manga => {return Manga.title === title})[0]; } }