1 | const cheerio = require('cheerio');
|
2 | const CloudHttp = require('../libs/http');
|
3 | const debug = require('debug')('anime-scraper');
|
4 | const html = require('./html');
|
5 | const Promise = require('bluebird');
|
6 | const _ = require('lodash');
|
7 |
|
8 | const BASE_URL = 'https://gogoanime.io';
|
9 |
|
10 | const cHttp = new CloudHttp();
|
11 |
|
12 | class Page {
|
13 | static fromResponse(response) {
|
14 | return cheerio.load(response.body);
|
15 | }
|
16 |
|
17 | static fromUrl(url, options = {}) {
|
18 | return cHttp.request(url, options).then(Page.fromResponse);
|
19 | }
|
20 | }
|
21 |
|
22 | class Episode {
|
23 | constructor({
|
24 | name,
|
25 | url,
|
26 | videoLinks
|
27 | }) {
|
28 | this.name = name || null;
|
29 | this.url = url || null;
|
30 | this.videoLinks = videoLinks || null;
|
31 | }
|
32 |
|
33 | fetch() {
|
34 | return Page.fromUrl(this.url).then(html.parseVideo).then((videoLinks) => {
|
35 | this.videoLinks = videoLinks;
|
36 | }).then(() => this);
|
37 | }
|
38 | }
|
39 |
|
40 | class Anime {
|
41 | constructor({
|
42 | name,
|
43 | url,
|
44 | id,
|
45 | summary,
|
46 | genres,
|
47 | released,
|
48 | episodes
|
49 | }) {
|
50 | this.name = name || null;
|
51 | this.url = url || null;
|
52 | this.id = id || null;
|
53 | this.summary = summary || null;
|
54 | this.genres = genres || null;
|
55 | this.released = released || null;
|
56 | this.episodes = episodes || null;
|
57 | debug(`Anime created: ${this}`);
|
58 | }
|
59 |
|
60 | fetchEpisodes() {
|
61 | const url = BASE_URL + '/load-list-episode';
|
62 |
|
63 | const options = {
|
64 | query: {
|
65 | ep_start: 0,
|
66 | ep_end: 2000,
|
67 | id: this.id,
|
68 | }
|
69 | };
|
70 |
|
71 | return Page.fromUrl(url, options).then(html.parseEpisodeListing).then((episodes) => {
|
72 | this.episodes = episodes.map(x => new Episode(x));
|
73 | }).then(() => this);
|
74 | }
|
75 |
|
76 | fetchAllEpisodes() {
|
77 | debug(`Fetching all episodes for anime: ${this}`);
|
78 | return Promise.map(this.episodes, episode => episode.fetch(), {
|
79 | concurrency: 1
|
80 | })
|
81 | .then(() => this);
|
82 | }
|
83 |
|
84 | fetchInformation() {
|
85 | return Anime.fromUrl(this.url).then((anime) => {
|
86 | _.merge(this, anime);
|
87 | }).then(() => this);
|
88 | }
|
89 |
|
90 | toString() {
|
91 | return `${this.name} (${this.id}).`;
|
92 | }
|
93 |
|
94 | static fromName(name) {
|
95 |
|
96 | return Anime.search(name).then((results) => {
|
97 |
|
98 | return results[0]
|
99 | }).then(result => result.toAnime());
|
100 | }
|
101 |
|
102 | static fromPage($) {
|
103 | return new Anime(html.parseAnimePage($)).fetchEpisodes();
|
104 | }
|
105 |
|
106 | static fromUrl(url) {
|
107 | return Page.fromUrl(url).then(Anime.fromPage);
|
108 | }
|
109 |
|
110 | static fromSearchResult(result) {
|
111 | return Anime.fromUrl(result.url);
|
112 | }
|
113 |
|
114 | static search(query) {
|
115 | const url = `${BASE_URL}/search.html`;
|
116 |
|
117 | const options = {
|
118 | method: 'GET',
|
119 | query: {
|
120 | keyword: query
|
121 | },
|
122 | };
|
123 |
|
124 | debug(`Running search for ${query}`);
|
125 |
|
126 | return Page.fromUrl(url, options).then(html.parseSearchResults)
|
127 | .then(results => results.map(x => new SearchResult(x)));
|
128 | }
|
129 | }
|
130 |
|
131 | class SearchResult {
|
132 | constructor({
|
133 | name,
|
134 | url
|
135 | }) {
|
136 | this.name = name || null;
|
137 | this.url = url || null;
|
138 | debug(`Search result created: ${this.name}`);
|
139 | }
|
140 |
|
141 | toAnime() {
|
142 | return Anime.fromSearchResult(this);
|
143 | }
|
144 | }
|
145 |
|
146 |
|
147 |
|
148 | module.exports = {
|
149 | Page,
|
150 | Anime,
|
151 | Episode,
|
152 | SearchResult,
|
153 | };
|