diff --git a/.github/workflows/CI.yaml b/.github/workflows/CI.yaml index 12ef010..515027f 100644 --- a/.github/workflows/CI.yaml +++ b/.github/workflows/CI.yaml @@ -36,4 +36,11 @@ jobs: - name: Run linter run: npm run linter + - name: Run tests + run: npm run test + - name: Run format + run: npm run format + + - name: Run linter + run: npm run lint diff --git a/package.json b/package.json index 3969c72..c4dd7e2 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,9 @@ "format": "prettier --ignore-path .gitignore --write \"**/*.+(js|ts|json)\"", "linter": "npx eslint src/scraper/* src/routes/*", "build": "tsc", + "format": "prettier --ignore-path .gitignore --write \"**/*.+(js|ts|json)\"", "zoro": "ts-node src/scraper/sites/anime/zoro/Zoro.ts", + "lint": "eslint --ignore-path .eslintignore --ext .js,.ts .", "heroku-postbuild": "npm run build", "start:dev": "nodemon src/index.ts", "test": "jest --coverage" diff --git a/src/routes/v1/anime/wcostream/wcostreamRoutes.ts b/src/routes/v1/anime/wcostream/wcostreamRoutes.ts index d5ce365..a7825f0 100644 --- a/src/routes/v1/anime/wcostream/wcostreamRoutes.ts +++ b/src/routes/v1/anime/wcostream/wcostreamRoutes.ts @@ -3,20 +3,27 @@ import { WcoStream } from "../../../../scraper/sites/anime/wcostream/WcoStream"; const Anime = new WcoStream(); const router = Router(); - - router.get("/anime/wcostream/name/:name", async (req, res) => { let { name } = req.params let data = await Anime.GetAnimeInfo(name) - + res.send(data) }) router.get("/anime/wcostream/episode/:episode", async (req, res) => { let { episode } = req.params - let {id} =req.query - let data = await Anime.GetEpisodeServers(episode,id) + let { season } = req.query + let data = await Anime.GetEpisodeServers(episode, season) + res.send(data) }) +router.get("/anime/wcostream/filter", async (req, res) => { + let { search,page } = req.query + let data = await Anime.GetAnimeByFilter(search as string,page as unknown as number) + + res.send(data) +}) + + export default router \ No newline at end of file diff --git a/src/routes/v1/manga/comick/ComickRoutes.ts b/src/routes/v1/manga/comick/ComickRoutes.ts index 7787d99..334012d 100644 --- a/src/routes/v1/manga/comick/ComickRoutes.ts +++ b/src/routes/v1/manga/comick/ComickRoutes.ts @@ -23,12 +23,11 @@ router.get("/manga/comick/title/:manga", async (req, res) => { }); router.get("/manga/comick/chapter/:chapter", async (req, res) => { - let { chapter } = req.params let { lang } = req.query; let data = await Manga.GetChapterInfo(chapter, lang as string) - + res.send(data) }); export default router \ No newline at end of file diff --git a/src/scraper/sites/anime/animelatinohd/AnimeLatinoHD.ts b/src/scraper/sites/anime/animelatinohd/AnimeLatinoHD.ts index 1a08764..27e5353 100644 --- a/src/scraper/sites/anime/animelatinohd/AnimeLatinoHD.ts +++ b/src/scraper/sites/anime/animelatinohd/AnimeLatinoHD.ts @@ -1,8 +1,8 @@ import * as cheerio from "cheerio"; import axios from "axios"; -import { Anime } from "../../../../types/anime"; -import { Episode, EpisodeServer } from "../../../../types/episode"; -import { AnimeSearch, ResultSearch, IResultSearch, IAnimeSearch } from "../../../../types/search"; +import { Anime } from "@animetypes/anime"; +import { Episode, EpisodeServer } from "@animetypes/episode"; +import { AnimeSearch, ResultSearch, IResultSearch, IAnimeSearch } from "@animetypes/search"; export class AnimeLatinoHD { readonly url = "https://www.animelatinohd.com"; @@ -70,27 +70,33 @@ export class AnimeLatinoHD { let sel_lang = langType.filter((e) => e.lang == lang) let f_index = 0 - + if (sel_lang.length) { $("#languaje option").each((_i, e) => { if ($(e).text() == sel_lang[0].type) { f_index = Number($(e).val()) } - }) + }) + }else { + $("#languaje option").each((_i, e) => { + f_index = Number($(e).val()) + }) } await Promise.all(animeEpisodeParseObj.players[f_index].map(async (e: { server: { title: any; }; id: string; }) => { - let min = await axios.get("https://api.animelatinohd.com/stream/" + e.id, { headers: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.62", "Referer": "https://www.animelatinohd.com/" } }) - let dat = cheerio.load(min.data) + //let min = await axios.get("https://api.animelatinohd.com/stream/" + e.id, { headers: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.62", "Referer": "https://www.animelatinohd.com/" } }) + // let dat = cheerio.load(min.data) let Server: EpisodeServer = { name: e.server.title, url: "", } + Server.url = "https://api.animelatinohd.com/stream/" + e.id + Server.name = e.server.title //state 1 - if (e.server.title == "Beta") { + /*if (e.server.title == "Beta") { let sel = dat("script:contains('var foo_ui = function (event) {')") let sort = String(sel.html()) let domain = eval(sort.slice(sort.search("const url"), sort.search("const langDef")).replace("const url =", "").trim()) @@ -108,7 +114,7 @@ export class AnimeLatinoHD { let sortMORE = sort.slice(sort.lastIndexOf("master") + 7, sort.lastIndexOf("hls2") - 11) let id_file = sortMORE.replace("_x", "") Server.url = "https://filemoon.sx" + "/e/" + id_file - } + }*/ AnimeEpisodeInfo.servers.push(Server) })) @@ -151,7 +157,6 @@ export class AnimeLatinoHD { }) return animeSearch; } catch (error) { - console.log(error) } } diff --git a/src/scraper/sites/anime/wcostream/WcoStream.ts b/src/scraper/sites/anime/wcostream/WcoStream.ts index 1d6a5c0..be7ced4 100644 --- a/src/scraper/sites/anime/wcostream/WcoStream.ts +++ b/src/scraper/sites/anime/wcostream/WcoStream.ts @@ -1,155 +1,167 @@ import * as cheerio from "cheerio"; import axios from "axios"; -import { Anime } from "../../../../types/anime"; -import { Episode } from "../../../../types/episode"; -//import { AnimeSearch, ResultSearch, IResultSearch, IAnimeSearch } from "../../../../types/search"; -//https://m.wcostream.org/(phone v) +import { Anime } from "@animetypes/anime"; +import { Episode, EpisodeServer } from "@animetypes/episode"; +import { IResultSearch, IAnimeSearch, ResultSearch, AnimeSearch } from "@animetypes/search"; + +/** List of Domains + * https://m.wcostream.org (phone) + * + * https://wcopanel.cizgifilmlerizle.com + * https://neptun.cizgifilmlerizle.com + * + * https://ndisk[>1].cizgifilmlerizle.com + * https://neptun[>1].cizgifilmlerizle.com + * + * https://cdn.animationexplore.com + * https://animationexplore.com + * + * https://watchanimesub.net + * https://lb.watchanimesub.net + * + * https://www.wcopremium.tv +*/ + +//Default Set Axios Cookie +axios.defaults.withCredentials = true +axios.defaults.headers.common["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.55"; + export class WcoStream { readonly url = "https://www.wcostream.org"; - ///inc/animeuploads/embed.php?file=Dr.%20Slump%2FDr.%20Slump%20Episode%20001%20-%20Watch%20Dr.%20Slump%20Episode%20001%20online%20in%20high%20quality.flv&hd=1 async GetAnimeInfo(anime: string): Promise { try { - const { data } = await axios.get(`${this.url}/anime/${anime}`); + const { data } = await axios.get(`${this.url}/anime/${anime}`,{headers:{"User-Agent":"Mozilla/5.0 (Linux; Android 10; LM-K920) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Mobile Safari/537.36"}}); const $ = cheerio.load(data); let image = $("#category_description .ui-grid-solo .ui-block-a img").attr("src") let name = $(".main .ui-grid-solo.center .ui-block-a > .ui-bar.ui-bar-x").text().replace("Share On", "") let genre = $(".ui-grid-solo.left .ui-block-a").text().replace("Genre;", "").replace("Language; ", "") - + const AnimeInfo: Anime = { name: name, - url: `/anime/animelatinohd/name/${anime}`, - synopsis: $("#category_description .ui-grid-solo .ui-block-a div p").text().replace("Watch ", ""), + url: `/anime/wcostream/name/${anime}`, + synopsis: $("#category_description .ui-grid-solo .ui-block-a div p").text().replace("Watch ", "").replace(/\n/g, ""), image: { url: !image.includes("https://") ? image.replace("//", "https://") : image }, - genres: [...genre.replace(genre.includes("Dubbed") ? "Dubbed" : "Subbed", "").trim().split(",").map(v => v.trim())], - //type: no, - //status: no ? "En emisiĆ³n" : "Finalizado", - //date: no, + genres: [...genre.replace(genre.includes("Dubbed") ? "Dubbed" : "Subbed", "").trim().replace(/\n/g, "").replace(/\s+/g, "").replace("-", "").split(",").map(v => v.trim())], episodes: [] } $("ul.ui-listview-z li").map((_i, e) => { let data = $(e).find("a").text() - let episode = data.slice(data.search(" Episode ")).replace(data.includes("English Dubbed") ? "English Dubbed" : "English Subbed", "").replace("Episode", "").trim() - if (data) { + let episode = data.slice(data.search(" Episode ")).replace(data.includes("English Dubbed") ? "English Dubbed" : "English Subbed", "").replace("Episode", "").trim().replace(/[^0-9-.]/g, "") + let season = data.includes("Season") ? data.slice(data.search(" Season "), data.search(" Episode ")).replace("Season", "").trim() : "" + + if (data && !data.includes("Movie") && !data.includes("OVA")) { let AnimeEpisode: Episode = { name: data, number: episode, - image: "https://www.themoviedb.org/t/p/original", - url: `/anime/wcostream/episode/${$(e).find("a").attr("href").slice(1, $(e).find("a").attr("href").search("episode")) + episode}?id=${image.slice(image.lastIndexOf("/")+1,image.search(".jpg"))}`//"//cdn.animationexplore.com/catimg/3.jpg + image: `https://cdn.animationexplore.com/thumbs/${$(e).find("a").attr("href").replace("https://www.wcostream.org/", "").replace("/", "").replace(/[^a-zA-Z0-9 ]/g, " ").replace(/\s+/g, "-")}.jpg`, + url: `/anime/wcostream/episode/${anime.replace(/[^a-zA-Z0-9 ]/g, ' ').replace(/\s+/g, "-") + "-" + episode}${season ? "?season=" + season : ""}` } + AnimeInfo.episodes.push(AnimeEpisode); } }) - console.log(AnimeInfo) + return AnimeInfo; } catch (error) { } } - async GetEpisodeServers(episode: string,id:any) { + + // Global Apis https://www.wcostream.org/wp-json + // https://www.wcostream.org/wp-json/wp/v2/pages + + async GetEpisodeServers(episode: string, season: any) { try { - let number = episode.substring(episode.lastIndexOf("-") + 1) - //let anime = episode.substring(0, episode.lastIndexOf("-")) - //hacklegend-of-the-twilight-episode-12-english-dubbed-2 - let urlafter = await axios.get(`https://www.wcostream.org/hacklegend-of-the-twilight-episode-${number}-english-dubbed-2`); - let cl = await axios.get(`https://www.wcostream.org/playlist-cat/3/hacklegend-of-the-twilight-episode-12-english-dubbed-2`) - const fr = cheerio.load(cl.data) - - console.log(fr.html()) - //https://www.wcostream.org/playlist-cat-rss/3?rssh=4199af2f31c7c2346eba1fa8523a7a46&rsst=1689553671 - //https://www.wcostream.org/dr-slump-episode-1-english-subbed - - const $ = cheerio.load(urlafter.data); - - //console.log(cheerio.) - //console.log($("html").html()) - var scr = $(".ui-block-a script").get()[0].children[0].data - //https://lb.watchanimesub.net/getvid?evid=tuWPBjr1Sd-HjOm9c_-99yd40E-lu1tKg5CD8GvpB6T5av64CgYvd5mEbmHRDUqhAOoO8ROiy-_aHAE4CJWxSWGjt3uovFUmsRUp75i7K3wsvWOSVEaXK9NbI89TulBFVGG9-Ppwz7YjfFgBERLkCUam7bUAeT-Xaa99Z4m2WrGZ_Rcsc8winDJCcYe-7xbCcMb0vzbPDNjpccUGiu_HZiCLHq9Ie8zjB119fRTwOMMuJ8xHkeCNpFIgHsQoKgx5KZLDJuJk-GlvAdOJhULP6Df2bWyHTAxrRMbj-NEVmVQHzYWNK3F3sYudC44nyNGdiD8pndlLneH2_vBtnuBeBdlHqbVy7MExtALRHyR-wCqoXIWFFpD8aeGkOvNH8aUXyrjkgzCNQKj3FTS9hyAO9A - var enumList = ""; - var randomVar = eval(scr.slice(scr.search(' = "";') + 16, scr.search('"];') + 2).trim()) - var randomNumber = Number(scr.slice(scr.search("replace")+20,scr.lastIndexOf("); } );")).trim()) - randomVar.map((e: any, _i: any) => { - enumList += String.fromCharCode(parseInt(atob(e).replace(/\D/g,'')) - randomNumber); - }) + let NumEpisode = episode.substring(episode.lastIndexOf("-") + 1) + let anime = episode.substring(0, episode.lastIndexOf("-")) + + const { data } = await axios.get(`https://www.wcostream.org/playlist-cat/${anime}`) + const $ = cheerio.load(data); + + let mainUrl = $("script").get()[3].children[0].data + let mainOrigin = eval(mainUrl.trim().slice(mainUrl.search("playlist:") + 6, mainUrl.search('image: ') - 4).trim().replace(",", "")) + + const mainData = await axios.get(this.url + mainOrigin) + const $$ = cheerio.load(mainData.data.replaceAll(":image", " type='image'").replaceAll(":source", " type='video'").trim()) - console.log(eval(decodeURIComponent(escape(enumList)))) - var embed = new URL(enumList.slice(enumList.search('src="')+5,enumList.search('" frameborder="0"'))) - var t = embed.searchParams.get("t") - var h = embed.searchParams.get("h") - //var pid= embed.searchParams.get("pid") // id episode - //let {data} = await axios.get(); - console.log(`https://www.wcostream.org/playlist-cat-rss/${id}?rssh=${h}&rsst=${t}`) - //await fetch(`https://www.wcostream.org/playlist-cat-rss/${id}?rssh=${h}&rsst=${t}`).then((e) => e.json()).then((e) => console.log(e)) - //const $q = cheerio.load(data) - //console.log(t,h,id) - //console.log($q("html").html()) - // after - const AnimeEpisodeInfo: Episode = { name: "", - url: `/anime/animelatinohd/episode/${episode}`, - number: number, + url: `/anime/wcostream/episode/${episode}${season ? "?season=" + season : ""}`, + number: NumEpisode, image: "", servers: [] } - /* $("#languaje option").each((_i, el) => { - let v = Number($(el).val()); - animeEpisodeParseObj.players[v].map((e: { server: { title: any; }; id: any; }) => { - let Server: EpisodeServer = { - name: "", - url: String(e), - } - AnimeEpisodeInfo.servers.push(Server); - }) - })*/ - + + $$("item").each((_i, e) => { + let title = $$(e).find("title").text() + + if (title.includes("Episode " + NumEpisode + " ") && !season && !title.includes("Season")) { + AnimeEpisodeInfo.name = title.replace("", "").trim() + AnimeEpisodeInfo.image = $$(e).find("jwplayer[type='image']").text() + let Server: EpisodeServer = { + name: "JWplayer - " + $$(e).find("jwplayer[type='video']").attr("label"), + url: $$(e).find("jwplayer[type='video']").attr("file"), + } + + AnimeEpisodeInfo.servers.push(Server); + } else if (title.includes("Episode " + NumEpisode + " ") && season && title.includes("Season " + season)) { + AnimeEpisodeInfo.name = title.replace("", "").trim() + AnimeEpisodeInfo.image = $$(e).find("jwplayer[type='image']").text() + let Server: EpisodeServer = { + name: "JWplayer - " + $$(e).find("jwplayer[type='video']").attr("label"), + url: $$(e).find("jwplayer[type='video']").attr("file"), + } + + AnimeEpisodeInfo.servers.push(Server); + } + }) return AnimeEpisodeInfo; } catch (error) { console.log(error) } } - /*async GetAnimeByFilter(search?: string, type?: number, page?: number, year?: string, genre?: string): Promise> { + async GetAnimeByFilter(search?: string, page?: any): Promise> { try { - const { data } = await axios.get(`${this.api}/api/anime/list`, { - params: { - search: search, - type: type, - year: year, - genre: genre, - page: page - } - }); + const formdata = new FormData(); + formdata.append("catara", search); + formdata.append("konuara", "series"); - let animeSearchParseObj = data + const { data } = await axios.post(`${this.url}/search`, formdata); + const $ = cheerio.load(data) const animeSearch: ResultSearch = { nav: { - count: animeSearchParseObj.data.length, - current: animeSearchParseObj.current_page, - next: animeSearchParseObj.data.length < 28 ? 0 : animeSearchParseObj.current_page + 1, - hasNext: animeSearchParseObj.data.length < 28 ? false : true + count: $("#blog .cerceve").length, + current: Number(page ? page : 1), + next: $("#blog .cerceve").length < 28 ? 0 : page ? Number(page) + 1 : 2, + hasNext: $("#blog .cerceve").length < 28 * page ? false : true }, results: [] } - animeSearchParseObj.data.map(e => { - const animeSearchData: AnimeSearch = { - name: e.name, - image: "https://www.themoviedb.org/t/p/original" + e.poster + "?&w=53&q=95", - url: `/anime/animelatinohd/name/${e.slug}`, - type: "" + + $("#blog .cerceve").each((i, e) => { + if ((animeSearch.nav.current > 1 ? i - 1 : i) >= 28 * (animeSearch.nav.current - 1) && (animeSearch.nav.current > 1 ? i + 1 : i) <= 28 * animeSearch.nav.current) { + const animeSearchData: AnimeSearch = { + name: $(e).find(".iccerceve a").attr("title"), + image: $(e).find(".iccerceve a img").attr("src"), + url: `/anime/wcostream/name/${$(e).find(".iccerceve a").attr("href").replace("/anime/", "")}`, + type: "anime" + } + animeSearch.results.push(animeSearchData) } - animeSearch.results.push(animeSearchData) }) return animeSearch; } catch (error) { + console.log(error) } - }*/ + } } diff --git a/src/scraper/sites/manga/comick/Comick.ts b/src/scraper/sites/manga/comick/Comick.ts index 91d274e..34b4c35 100644 --- a/src/scraper/sites/manga/comick/Comick.ts +++ b/src/scraper/sites/manga/comick/Comick.ts @@ -1,6 +1,6 @@ import * as cheerio from "cheerio"; import axios from "axios"; -import { Manga, MangaChapter, IMangaResult } from "../../../../types/manga"; +import { Manga, MangaChapter, IMangaResult } from "@animetypes/manga"; import { IResultSearch } from "@animetypes/search"; //Default Set Axios Cookie @@ -46,17 +46,11 @@ export class Comick { async GetMangaInfo(manga: string, lang: string): Promise { try { const { data } = await axios.get(`${this.api}/comic/${manga}`); - const currentLang = lang ? `?lang=${lang}` : "" - const mangaInfoParseObj = data - - - //https://comick.app/_next/data/Ut35IGvJuQU3g2jzrGDk-/comic/naruto-rocket-doujinshi.json?slug=naruto-rocket-doujinshi const dataApi = await axios.get(`${this.api}/comic/${mangaInfoParseObj.comic.hid}/chapters${currentLang}`); - const MangaInfo: Manga = { id: mangaInfoParseObj.comic.id, title: mangaInfoParseObj.comic.title, @@ -85,12 +79,12 @@ export class Comick { images: null, cover: null, date: { - year: mindate.getFullYear(), - month: null, - day: null + year: mindate.getFullYear() ? mindate.getFullYear() : null, + month: mindate.getMonth() ? mindate.getMonth() : null, + day: mindate.getDay() ? mindate.getDay() : null } } - return MangaInfo.chapters.push(MangaInfoChapter) + return MangaInfo.chapters.push(!langChapter.includes("?lang=id") ? MangaInfoChapter : null) }) return MangaInfo @@ -101,11 +95,11 @@ export class Comick { async GetChapterInfo(manga: string, lang: string) { try { - const currentLang = lang ? "-" + lang : "-en"; - const hid = manga.substring(0, manga.indexOf("-")); - const idTitle = manga.substring(manga.indexOf("-") + 1); - var idNumber = idTitle.substring(idTitle.lastIndexOf("-") + 1); - const title = idTitle.substring(0, idTitle.lastIndexOf("-")); + let currentLang = lang ? "-" + lang : "-en"; + let hid = manga.substring(0, manga.indexOf("-")); + let idTitle = manga.substring(manga.indexOf("-") + 1); + let idNumber = idTitle.substring(idTitle.lastIndexOf("-") + 1); + let title = idTitle.substring(0, idTitle.lastIndexOf("-")); let urlchange = "" @@ -117,7 +111,7 @@ export class Comick { const { data } = await axios.get(`${this.url}/comic/${title}/${urlchange}`); const $ = cheerio.load(data); - console.log(JSON.parse($("#__NEXT_DATA__").html()).isFallback) + if (JSON.parse($("#__NEXT_DATA__").html()).isFallback = false) { const mangaChapterInfoParseObj = JSON.parse($("#__NEXT_DATA__").html()).props.pageProps const mindate = new Date(mangaChapterInfoParseObj.chapter.created_at); @@ -138,18 +132,18 @@ export class Comick { cover: "https://meo.comick.pictures/" + mangaChapterInfoParseObj.chapter.md_comics.md_covers[0].b2key, date: { year: mindate.getFullYear() ? mindate.getFullYear() : null, - month: null, - day: null + month: mindate.getMonth() ? mindate.getMonth() : null, + day: mindate.getDay() ? mindate.getDay() : null } } return MangaChapterInfoChapter; } else { let buildid = JSON.parse($("#__NEXT_DATA__").html()).buildId - let currentUrl = idNumber == "err" ? `${title}/${hid}.json?slug=${title}&chapter=${hid}`:`${title}/${hid}-chapter-${idNumber}${currentLang}.json?slug=${title}&chapter=${hid}-chapter-${idNumber}${currentLang}` + let currentUrl = idNumber == "err" ? `${title}/${hid}.json?slug=${title}&chapter=${hid}` : `${title}/${hid}-chapter-${idNumber}${currentLang}.json?slug=${title}&chapter=${hid}-chapter-${idNumber}${currentLang}` let dataBuild = await axios.get(`${this.url}/_next/data/${buildid}/comic/${currentUrl}`); - console.log(dataBuild) - const mindate = new Date(dataBuild.data.pageProps.chapter.created_at); + + let mindate = new Date(dataBuild.data.pageProps.chapter.created_at); const MangaChapterInfoChapter: MangaChapter = { id: dataBuild.data.pageProps.chapter.id, @@ -167,13 +161,13 @@ export class Comick { cover: "https://meo.comick.pictures/" + dataBuild.data.pageProps.chapter.md_comics.md_covers[0].b2key, date: { year: mindate.getFullYear() ? mindate.getFullYear() : null, - month: null, - day: null + month: mindate.getMonth() ? mindate.getMonth() : null, + day: mindate.getDay() ? mindate.getDay() : null } } return MangaChapterInfoChapter; - + } } catch (error) { }