lgaticaq/anime-dl

View on GitHub
src/index.js

Summary

Maintainability
B
6 hrs
Test Coverage
'use strict'

const http = require('http')
const https = require('https')
const url = require('url')
const cheerio = require('cheerio')
const querystring = require('querystring')
const Fuse = require('fuse.js')

const userAgent =
  'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36'

const requestOptions = uri => {
  return {
    headers: { 'User-Agent': userAgent },
    hostname: 'jkanime.net',
    port: 80,
    path: uri,
    method: 'GET'
  }
}

const testLink = uri => {
  return new Promise((resolve, reject) => {
    const myURL = url.parse(uri)
    const options = {
      headers: { 'User-Agent': userAgent },
      hostname: myURL.hostname,
      port: myURL.port,
      path: myURL.pathname,
      method: 'GET'
    }
    let request
    if (myURL.protocol === 'https:') {
      request = https.request
    } else {
      request = http.request
    }
    const req = request(options, res => {
      if (res.statusCode !== 200) {
        resolve(null)
      } else {
        resolve(uri)
      }
    })
    req.on('error', () => resolve(null))
    req.end()
  })
}

const getOriginalLinks = uri => {
  return new Promise((resolve, reject) => {
    const options = requestOptions(uri)
    const req = http.request(options, res => {
      resolve(res.headers.location)
    })
    req.on('error', err => reject(err))
    req.end()
  })
}

const getLinksByUrl = uri => {
  return new Promise((resolve, reject) => {
    const options = requestOptions(uri)
    const req = http.request(options, res => {
      if (res.statusCode !== 200) {
        reject(new Error(`Status Code: ${res.statusCode}`))
      } else {
        res.setEncoding('utf8')
        let rawData = ''
        res.on('data', chunk => {
          rawData += chunk
        })
        res.on('end', () => {
          try {
            const $ = cheerio.load(rawData)
            const validRegex = /\/([\w\d-_]+)\/(\d+)/
            const title = $('.vervideo')
              .text()
              .split(' - ')[0]
            const _exec = validRegex.exec(uri).slice(1, 3)
            const codeName = _exec[0]
            const chapter = _exec[1]
            const regex = /https:\/\/jkanime\.net\/jk\.php\?u=stream\/jkmedia\/([0-9a-f]{32}\/[0-9a-f]{32}\/\d+\/[0-9a-f]{32})\//
            const promises = $('.player_conte')
              .map(function () {
                return $(this).attr('src')
              })
              .get()
              .filter(x => regex.test(x))
              .map(x =>
                getOriginalLinks(`/stream/jkmedia/${regex.exec(x)[1]}/`).then(
                  testLink
                )
              )
            Promise.all(promises)
              .then(urls => {
                resolve({
                  title: title,
                  codeName: codeName,
                  chapter: chapter,
                  urls: urls.filter(uri => uri !== null)
                })
              })
              .catch(reject)
          } catch (err) {
            reject(err)
          }
        })
      }
    })
    req.on('error', err => reject(err))
    req.end()
  })
}

const getLastChapter = name => {
  return new Promise((resolve, reject) => {
    const options = requestOptions(`/${name}/`)
    const req = http.request(options, res => {
      if (res.statusCode !== 200) {
        reject(new Error(`Status Code: ${res.statusCode}`))
      } else {
        res.setEncoding('utf8')
        let rawData = ''
        res.on('data', chunk => {
          rawData += chunk
        })
        res.on('end', () => {
          try {
            const $ = cheerio.load(rawData)
            const text = $('.listnavi a')
              .last()
              .text()
            const result = parseInt(/\d+\s-\s(\d+)/.exec(text)[1], 10)
            resolve(result)
          } catch (err) {
            reject(err)
          }
        })
      }
    })
    req.on('error', err => reject(err))
    req.end()
  })
}

const searchAnime = keyword => {
  return new Promise((resolve, reject) => {
    const options = requestOptions(
      `/buscar/${querystring.escape(keyword).replace(/%20/g, '_')}/1/`
    )
    const req = http.request(options, res => {
      res.setEncoding('utf8')
      let rawData = ''
      res.on('data', chunk => {
        rawData += chunk
      })
      res.on('end', () => {
        try {
          const $ = cheerio.load(rawData)
          const result = $('.listpage .titl')
            .map(function () {
              return {
                codeName: /http:\/\/jkanime\.net\/([\w\d_-]+)\//.exec(
                  $(this).attr('href')
                )[1],
                name: $(this).text()
              }
            })
            .get()
          resolve(result)
        } catch (err) {
          reject(err)
        }
      })
    })
    req.on('error', err => reject(err))
    req.end()
  })
}

const fuseSearch = (data, keyword) => {
  const options = {
    shouldSort: true,
    threshold: 0.6,
    location: 0,
    distance: 100,
    maxPatternLength: 32,
    keys: ['name']
  }
  const fuse = new Fuse(data, options)
  return fuse.search(keyword)
}

const getName = keyword => {
  keyword = keyword.trim()
  return searchAnime(keyword).then(animes => {
    if (animes.length === 0) {
      throw new Error(`Not found anime with keyword "${keyword}"`)
    }
    const results = fuseSearch(animes, keyword)
    if (results.length === 0) throw new Error('Not found')
    return results[0].codeName
  })
}

const makeUrl = (keyword, chapter) => {
  let _name
  return getName(keyword)
    .then(name => {
      _name = name
      return getLastChapter(name)
    })
    .then(last => {
      if (!/\d+/.test(chapter)) throw new Error('Not a valid chapter')
      chapter = parseInt(chapter, 10)
      if (chapter > last) throw new Error(`Only chapters from 1 to ${last}`)
      return `/${_name}/${chapter}/`
    })
}

const getLinksByNameAndChapter = (name, chapter) => {
  return makeUrl(name, chapter).then(getLinksByUrl)
}

module.exports = {
  searchAnime: searchAnime,
  makeUrl: makeUrl,
  getLinksByUrl: getLinksByUrl,
  getLinksByNameAndChapter: getLinksByNameAndChapter,
  getOriginalLinks: getOriginalLinks,
  getLastChapter: getLastChapter
}