From 14b42ec6a0104dca2c9d1366a9056c59ce82792e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9luchu?= Date: Sun, 31 May 2020 19:13:13 +0200 Subject: [PATCH] Added MoreInfo --- package-lock.json | 86 ++++++------- package.json | 3 +- src/api/api.js | 58 ++++++++- src/api/index.js | 9 +- src/api/routes/index.js | 15 +++ src/api/urls.js | 10 +- src/app.js | 2 - src/utils/index.js | 262 ++++++++++++++++++++++++++++++++++++++-- 8 files changed, 365 insertions(+), 80 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3936579..74d4856 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,14 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@babel/runtime": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.1.tgz", + "integrity": "sha512-nQbbCbQc9u/rpg1XCxoMYQTbSMVZjCDxErQ1ClCn9Pvcmv1lGads19ep0a2VsEiIJeHqjZley6EQGEC3Yo1xMA==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, "@types/debug": { "version": "0.0.31", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-0.0.31.tgz", @@ -34,6 +42,16 @@ "uri-js": "^4.2.2" } }, + "animeflv-scrapper": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/animeflv-scrapper/-/animeflv-scrapper-0.2.12.tgz", + "integrity": "sha512-EvASm0nj9tCtopOZ6L9JF5jX0c0Nmv1ROYEg4/wKcGlX2rINvtXNoW6jqM7SGWG4POrh4RI1AjkpbiaP/cMibA==", + "requires": { + "@babel/runtime": "^7.8.7", + "cloudscraper": "^4.6.0", + "node-html-parser": "^1.2.12" + } + }, "array-flatten": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", @@ -75,21 +93,6 @@ "follow-redirects": "1.5.10" } }, - "basic-auth": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", - "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", - "requires": { - "safe-buffer": "5.1.2" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -582,6 +585,11 @@ "har-schema": "^2.0.0" } }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=" + }, "helmet": { "version": "3.22.0", "resolved": "https://registry.npmjs.org/helmet/-/helmet-3.22.0.tgz", @@ -843,33 +851,6 @@ "mime-db": "1.43.0" } }, - "morgan": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", - "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", - "requires": { - "basic-auth": "~2.0.1", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-finished": "~2.3.0", - "on-headers": "~1.0.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - } - } - }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -899,10 +880,13 @@ "nan": "^2.13.2" } }, - "node-fetch": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", - "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + "node-html-parser": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-1.2.18.tgz", + "integrity": "sha512-DypklUSTuY9JDfYWz/NZYZA8mvgKmjRmHZe7at0H6O4KoXcs8QSpnH5mFk888gLsqXXRMVNmJGk/FdqaO9T1UQ==", + "requires": { + "he": "1.1.1" + } }, "npm": { "version": "6.14.5", @@ -4036,11 +4020,6 @@ "ee-first": "1.1.1" } }, - "on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" - }, "parse5": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", @@ -4119,6 +4098,11 @@ "resolved": "https://registry.npmjs.org/referrer-policy/-/referrer-policy-1.2.0.tgz", "integrity": "sha512-LgQJIuS6nAy1Jd88DCQRemyE3mS+ispwlqMk3b0yjZ257fI1v9c+/p6SD5gP5FGyXUIgrNOAfmyioHwZtYv2VA==" }, + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" + }, "request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", diff --git a/package.json b/package.json index 4679cc1..d13e6db 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "url": "https://github.com/aruppi/aruppi-api.git" }, "dependencies": { + "animeflv-scrapper": "^0.2.12", "body-parser": "^1.19.0", "cheerio": "^1.0.0-rc.3", "cheerio-tableparser": "^1.0.1", @@ -50,8 +51,6 @@ "express": "^4.16.4", "helmet": "^3.22.0", "image-to-base64": "^2.1.0", - "morgan": "^1.10.0", - "node-fetch": "^2.6.0", "npm": "^6.14.5", "request": "^2.88.0", "rss-to-json": "^1.1.1" diff --git a/src/api/api.js b/src/api/api.js index a3ca6b8..1429100 100644 --- a/src/api/api.js +++ b/src/api/api.js @@ -1,5 +1,15 @@ const rss = require('rss-to-json'); const cloudscraper = require('cloudscraper'); +const animeflv = require("animeflv-scrapper"); + +const { + animeflvInfo, + imageUrlToBase64, + getAnimeCharacters, + getAnimeVideoPromo, + animeExtraInfo +} = require('../utils/index'); + const { BASE_ANIMEFLV, BASE_ANIMEFLV_JELU, BASE_JIKAN, BASE_IVOOX, BASE_KUDASAI, BASE_PALOMITRON } = require('./urls'); @@ -233,7 +243,6 @@ const getOvas = async (type, page) =>{ }; - const getSpecials = async (type, page) =>{ const data = await cloudscraper.get(`${BASE_ANIMEFLV_JELU}Special/${type}/${page}`); @@ -284,6 +293,50 @@ const getTv = async (type, page) =>{ }; +const getMoreInfo = async (title) =>{ + + const promises = [] + let animeTitle ="" + let animeId = "" + + await animeflv.searchAnime(title).then(data => { + data.forEach(function (anime) { + if (anime.label === title) { + animeTitle = anime.label + animeId = anime.animeId + } + } + ) + }); + + try{ + promises.push(await animeflvInfo(animeId).then(async extra => ({ + title: animeTitle || null, + poster: await imageUrlToBase64(extra.animeExtraInfo[0].poster) || null, + synopsis: extra.animeExtraInfo[0].synopsis || null, + status: extra.animeExtraInfo[0].debut || null, + type: extra.animeExtraInfo[0].type || null, + rating: extra.animeExtraInfo[0].rating || null, + genres: extra.genres || null, + episodes: extra.listByEps || null, + moreInfo: await animeExtraInfo(title).then(info =>{ + return info || null + }), + promo: await getAnimeVideoPromo(title).then(promo =>{ + return promo || null + }), + characters: await getAnimeCharacters(animeTitle).then(characters =>{ + return characters || null + }) + }))); + + }catch(err){ + console.log(err) + } + + return Promise.all(promises); +}; + module.exports = { schedule, top, @@ -295,5 +348,6 @@ module.exports = { getMovies, getOvas, getSpecials, - getTv + getTv, + getMoreInfo }; diff --git a/src/api/index.js b/src/api/index.js index 1c45f26..a757782 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -18,10 +18,11 @@ router.get('/', (req, res) => { 'News': '/api/v2/news', 'Season': '/api/v2/season/:year/:type', 'LastEpisodes': '/api/v2/lastEpisodes', - 'Movies': '/api/v2/movies', - 'Ovas': '/api/v2/ovas', - 'Specials': '/api/v2/specials', - 'Tv': '/api/v2/tv' + 'Movies': '/api/v2/movies/:type/:page', + 'Ovas': '/api/v2/ovas/:type/:page', + 'Specials': '/api/v2/specials/:type/:page', + 'Tv': '/api/v2/tv/:type/:page', + 'MoreInfo': '/api/v2/moreInfo/:title' } ] }); diff --git a/src/api/routes/index.js b/src/api/routes/index.js index 6a8026a..4679e3e 100644 --- a/src/api/routes/index.js +++ b/src/api/routes/index.js @@ -166,4 +166,19 @@ router.get('/tv/:type/:page' , (req, res) =>{ }); +router.get('/moreInfo/:title' , (req, res) =>{ + + let title = req.params.title; + + api.getMoreInfo(title) + .then(info =>{ + res.status(200).json({ + info + }); + }).catch((err) =>{ + console.error(err); + }); + +}); + module.exports = router; diff --git a/src/api/urls.js b/src/api/urls.js index d20bdd7..e453d4b 100644 --- a/src/api/urls.js +++ b/src/api/urls.js @@ -1,14 +1,10 @@ module.exports = { BASE_ANIMEFLV: 'https://animeflv.net/', - BASE_ANIMEFLV_JELU: 'https://dev.aruppi.jeluchu.xyz/apis/animeflv/v1/', - BASE_JIKAN: 'https://dev.aruppi.jeluchu.xyz/apis/jikan/v3/', + BASE_ANIMEFLV_JELU: 'https://aruppi.jeluchu.xyz/apis/animeflv/v1/', + BASE_JIKAN: 'https://aruppi.jeluchu.xyz/apis/jikan/v3/', BASE_IVOOX: 'https://www.ivoox.com/podcast-anitakume_fg_f1660716_filtro_1.xml', BASE_KUDASAI: 'https://somoskudasai.com/feed/', BASE_PALOMITRON: 'https://elpalomitron.com/category/animemanga/feed/', - BROWSE_URL: 'https://animeflv.net/browse?', SEARCH_URL: 'https://animeflv.net/browse?q=', - ANIME_VIDEO_URL: 'https://animeflv.net/ver/', - BASE_EPISODE_IMG_URL: 'https://cdn.animeflv.net/screenshots/', - BASE_JIKA_URL: 'http://devel.jeluchu.xyz/apis/jikan/v3/search/anime?q=', - BASE_MYANIME_LIST_URL : 'https://myanimelist.net/character/' + BASE_EPISODE_IMG_URL: 'https://cdn.animeflv.net/screenshots/' }; \ No newline at end of file diff --git a/src/app.js b/src/app.js index b856769..a8f20b5 100644 --- a/src/app.js +++ b/src/app.js @@ -1,5 +1,4 @@ const express = require('express'); -const morgan = require('morgan'); const helmet = require('helmet'); const cors = require('cors'); const bodyParser = require('body-parser'); @@ -9,7 +8,6 @@ const api = require('./api'); const app = express(); -app.use(morgan('dev')); app.use(helmet()); app.use(cors()); app.use(bodyParser.json()); diff --git a/src/utils/index.js b/src/utils/index.js index 0a01dfd..b19134e 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -1,5 +1,250 @@ const cloudscraper = require('cloudscraper') const imageToBase64 = require("image-to-base64"); +const cheerio = require('cheerio'); + +const { + BASE_ANIMEFLV, BASE_JIKAN, BASE_EPISODE_IMG_URL +} = require('../api/urls'); + +const animeflvInfo = async(id) =>{ + try{ + const res = await cloudscraper(`${BASE_ANIMEFLV}anime/${id}`); + const body = await res; + const $ = cheerio.load(body); + const scripts = $('script'); + const anime_info_ids = []; + const anime_eps_data = []; + const animeExtraInfo = []; + const genres = []; + let listByEps; + + let animeTitle = $('body div.Wrapper div.Body div div.Ficha.fchlt div.Container h2.Title').text(); + let poster = `${BASE_ANIMEFLV}` + $('body div div div div div aside div.AnimeCover div.Image figure img').attr('src') + const banner = poster.replace('covers' , 'banners').trim(); + let synopsis = $('body div div div div div main section div.Description p').text().trim(); + let rating = $('body div div div.Ficha.fchlt div.Container div.vtshr div.Votes span#votes_prmd').text(); + const debut = $('body div.Wrapper div.Body div div.Container div.BX.Row.BFluid.Sp20 aside.SidebarA.BFixed p.AnmStts').text(); + const type = $('body div.Wrapper div.Body div div.Ficha.fchlt div.Container span.Type').text() + + + animeExtraInfo.push({ + title: animeTitle, + poster: poster, + banner: banner, + synopsis: synopsis, + rating: rating, + debut: debut, + type: type, + }) + + $('main.Main section.WdgtCn nav.Nvgnrs a').each((index , element) =>{ + const $element = $(element); + const genre = $element.attr('href').split('=')[1] || null; + genres.push(genre); + }); + + + Array.from({length: scripts.length} , (v , k) =>{ + const $script = $(scripts[k]); + const contents = $script.html(); + if((contents || '').includes('var anime_info = [')) { + let anime_info = contents.split('var anime_info = ')[1].split(';')[0]; + let dat_anime_info = JSON.parse(JSON.stringify(anime_info));//JSON.parse(anime_info); + anime_info_ids.push(dat_anime_info); + } + if((contents || '').includes('var episodes = [')) { + let episodes = contents.split('var episodes = ')[1].split(';')[0]; + let eps_data = JSON.parse(episodes) + anime_eps_data.push(eps_data); + } + }); + const AnimeThumbnailsId = anime_info_ids[0].split(',')[0].split('"')[1]; + const animeId = id; + let nextEpisodeDate = anime_info_ids[0][3] || null + const amimeTempList = []; + for(const [key , value] of Object.entries(anime_eps_data)){ + let episode = anime_eps_data[key].map(x => x[0]); + let episodeId = anime_eps_data[key].map(x => x[1]); + amimeTempList.push(episode , episodeId); + } + const animeListEps = [{nextEpisodeDate: nextEpisodeDate}]; + Array.from({length: amimeTempList[1].length} , (v , k) =>{ + let data = amimeTempList.map(x => x[k]); + let episode = data[0]; + let id = data[1]; + let imagePreview = `${BASE_EPISODE_IMG_URL}${AnimeThumbnailsId}/${episode}/th_3.jpg` + let link = `${id}/${animeId}-${episode}` + // @ts-ignore + animeListEps.push({ + episode: episode, + id: link, + imagePreview: imagePreview + }) + }) + + listByEps = animeListEps; + + return {listByEps , genres , animeExtraInfo}; + }catch(err){ + console.error(err) + } +}; + +const getAnimeCharacters = async(title) =>{ + const res = await cloudscraper(`${BASE_JIKAN}search/anime?q=${title}`); + const matchAnime = JSON.parse(res).results.filter(x => x.title === title); + const malId = matchAnime[0].mal_id; + + if(typeof matchAnime[0].mal_id === 'undefined') return null; + + const jikanCharactersURL = `${BASE_JIKAN}anime/${malId}/characters_staff`; + const data = await cloudscraper.get(jikanCharactersURL); + let body = JSON.parse(data).characters; + + if(typeof body === 'undefined') return null; + + const charactersId = body.map(doc =>{ + return doc.mal_id + }) + const charactersNames = body.map(doc => { + return doc.name; + }); + const charactersImages = body.map(doc =>{ + return doc.image_url + }); + + let characters = []; + Array.from({length: charactersNames.length} , (v , k) =>{ + const id = charactersId[k]; + let name = charactersNames[k]; + let characterImg = charactersImages[k]; + characters.push({ + character:{ + id: id, + name: name, + image: characterImg + } + }); + }); + + return Promise.all(characters); +}; + +const getAnimeVideoPromo = async(title) =>{ + const res = await cloudscraper(`${BASE_JIKAN}search/anime?q=${title}`); + const matchAnime = JSON.parse(res).results.filter(x => x.title === title); + const malId = matchAnime[0].mal_id; + + if(typeof matchAnime[0].mal_id === 'undefined') return null; + + const jikanCharactersURL = `${BASE_JIKAN}anime/${malId}/videos`; + const data = await cloudscraper.get(jikanCharactersURL); + const body = JSON.parse(data).promo; + const promises = []; + + body.map(doc =>{ + promises.push({ + title: doc.title, + previewImage: doc.image_url, + videoURL: doc.video_url + }); + }); + + return Promise.all(promises); +}; + +const animeExtraInfo = async(title) =>{ + const res = await cloudscraper(`${BASE_JIKAN}search/anime?q=${title}`); + const matchAnime = JSON.parse(res).results.filter(x => x.title === title); + const malId = matchAnime[0].mal_id; + + if(typeof matchAnime[0].mal_id === 'undefined') return null; + + const animeDetails = `${BASE_JIKAN}anime/${malId}`; + const data = await cloudscraper.get(animeDetails); + const body = Array(JSON.parse(data)); + const promises = []; + + body.map(doc =>{ + + let airDay + + switch (doc.broadcast.split('at')[0].replace(" ", "").toLowerCase()) { + case "mondays": + airDay = "Lunes"; + break; + case "monday": + airDay = "Lunes"; + break; + case "tuesdays": + airDay = "Martes"; + break; + case "tuesday": + airDay = "Martes"; + break; + case "wednesdays": + airDay = "Miércoles"; + break; + case "wednesday": + airDay = "Miércoles"; + break; + case "thursdays": + airDay = "Jueves"; + break; + case "thursday": + airDay = "Jueves"; + break; + case "fridays": + airDay = "Viernes"; + break; + case "friday": + airDay = "Viernes"; + break; + case "saturdays": + airDay = "Sábados"; + break; + case "saturday": + airDay = "Sábados"; + break; + case "sundays": + airDay = "Domingos"; + break; + case "sunday": + airDay = "Domingos"; + break; + default: + airDay = "Sin emisión"; + } + + promises.push({ + titleJapanese: doc.title_japanese, + source: doc.source, + totalEpisodes: doc.episodes, + aired:{ + from: doc.aired.from, + to: doc.aired.to + }, + duration: doc.duration.split('per')[0].replace(" ", ""), + rank: doc.rank, + broadcast: airDay, + producers: doc.producers.map(x => x.name) || null, + licensors: doc.licensors.map(x => x.name) || null, + studios: doc.studios.map(x => x.name) || null, + openingThemes: doc.opening_themes || null, + endingThemes: doc.ending_themes || null + }); + }); + return Promise.all(promises); +}; + +const imageUrlToBase64 = async(url) => { + + let base64image = "" + + await imageToBase64(url).then((response) => {base64image = response}) + + return base64image.toString("base64"); +}; const MergeRecursive = (obj1 , obj2) => { for(var p in obj2) { @@ -18,16 +263,6 @@ const MergeRecursive = (obj1 , obj2) => { return obj1; } -const imageUrlToBase64 = async(url) => { - let res = await cloudscraper({ - url, - method: "GET", - encoding: null - }); - - return Buffer.from(res).toString("base64"); -}; - const urlify = async(text) =>{ const urls = []; const urlRegex = /(https?:\/\/[^\s]+)/g; @@ -39,8 +274,11 @@ const urlify = async(text) =>{ module.exports = { - MergeRecursive, - millisecondsToDate, + animeflvInfo, + getAnimeCharacters, + getAnimeVideoPromo, + animeExtraInfo, imageUrlToBase64, + MergeRecursive, urlify } \ No newline at end of file