UNPKG

nct-downloader

Version:
266 lines (242 loc) 8.98 kB
'use strict' http = require 'http' readline = require 'readline' chery = require 'cheerio' unirest = require 'unirest' sys = require 'sys' exec = require('child_process').exec() fs = require('fs') celeri = require('celeri') c = console path = require('path'); asset = path.resolve.bind(null, __dirname, '..'); events = require('events') util = require('util') multimeter = require('pm2-multimeter') class Url extends events.EventEmitter constructor: (@data) -> this.setMaxListeners 1000 url = @data.url url = "http://#{url}" if 'www' == url.substring(0,3) if 'http' == url.substring(0, 4) then @url = url else @url = "http://www.nhaccuatui.com/#{url}" container: '.list' item: '.button_download' crawler: (cb) -> t = this this.emit 'start', @url # Dunno how to get total length ? fake_progress = 1 http .get @url, (res) -> # @TODO Has no idea how to get content-length if the server did't return it ;(. maybe by chunk of transfer encoding... #total_size = parseInt(res.headers['content-length'], 10) res.setEncoding 'utf8' # @TODO Implement pipe html = '' res.on 'data', (chunk) -> fake_progress++ fake_progess = 84 if fake_progess == 100 html += chunk #fake_progess = parseInt(chunk.length * 100 / total_size) t.emit 'data', fake_progress return #bar.percent fake_progress # .on 'end', (data) -> #t.emit 'url' #bar.percent 100 t.emit 'data', 100 t.find_song_item html .on 'error', (e) -> console.log e.message() return this # TODO, follow next page find_song_item : (html) -> $ = chery.load html song_total = $("#{this.container} #{this.item}").length console.log "Found #{song_total} songs" # @TODO Imrpove this logic with a set of rule from external file song_list = [] $(@container) .find(@item) .each (index, elem) -> #console.log "Bài hát: #{$(elem).attr('reltitle')}" #console.log "Ca sĩ: #{$(elem).attr('singer')}" key = $(elem).attr 'key' if !key? return false title = $(elem).attr('title') title = title.split '-' singer = if title[1]? then title[1].trim() else '' song_list.push title: title[0].substring(16) #title: $(elem).attr('reltitle') singer: singer #rel: $(elem).attr('rel') key: key songurl: "http://www.nhaccuatui.com/download/song/#{key}" this.emit 'finish', song_list return class Playlist extends Url container: '.list_song_in_album' #container: '.list-song-plist, .list_song_in_album' #item: '.dl' item: '.button_download' class Search extends Url constructor: (@data) -> q = @data.q if 'http' == q.substring(0,4) then @url =q else @url = "http://www.nhaccuatui.com/tim-kiem?q=#{q}" container: '.search_returns_list' class Singer extends Url container: '.list_music' class Download constructor: () -> @multi = {} @bars = [] start : () -> #this.bar = new ProgressBar(':bar', { total: 10 }) done : (result = true) -> if result process.stdout.write("\nDone. Enjoy music.\n") else process.stdout.write("\nAn error occured. Check syntax and URL.\n") #this.spinner.done true process.exit() # Start to process a command process : (cmd, data) -> @multi = multimeter(process) @multi.on('^C', process.exit) @multi.charm.reset() switch cmd when "singer" then url = new Singer data when "search" then url = new Search data when "playlist" then url = new Playlist data when "page" then url = new Search data else console.log "Invalid command" process.exit(false) bar = {} this.start() url.on('finish', this.download.bind(this)) .on('start', ((url) -> @multi.write "Start to find songs.\n" @multi.write "Progress: \n" bar = @multi(13, 2, width: 20 solid: text : '|' foreground : 'yellow' background : 'red' empty: text : ' ' ) return ).bind(this)) .on('data', (data) -> bar.percent data ).crawler() # Download a list of URL pass from song download: (song_list) -> that = this @multi.charm.reset() this.done() if song_list.length == 0 number_of_downloaded = 0 download_file = (song_index, song_url, to) -> file = fs.createWriteStream to #console.log "\nStart to download ...#{p[p.length - 1]}" #that.spinner "Processing...", p[p.length - 1] * 100 / p.length file.on 'finish', () -> number_of_downloaded++ #celeri.progress('Finish..', number_of_downloaded * 100 / song_list.length) #console.log "Finish download file #{number_of_downloaded}/#{song_list.length}" that.done() if number_of_downloaded == song_list.length that.bars[song_index].percent 100 count_downloaded_size = 0 request = http.get song_url, (response) -> total_length = parseInt(response.headers['content-length'], 10) #console.log "Content length = " + total_length response.on 'data', (chunk) -> count_downloaded_size += chunk.length that.bars[song_index].percent parseFloat(count_downloaded_size * 100 / total_length) #console.log "Download #{count_downloaded_size/1024}KB of file #{p[p.length-1]}" #bar.percent response.pipe(file) for s, song_index in song_list title = "#{song_index+1}. #{s.title}: \n" @multi.write "#{title}" @bars[song_index] = @multi(title.length, song_index + 1, width: 20 solid: text : '|' foreground : 'yellow' background : 'red' empty: text : ' ' ) ((song_index) -> unirest .get(s.songurl) .end (res) -> #console.log s.songurl song = JSON.stringify res.body song = JSON.parse song if !song.data.stream_url? || song.error_code == 8 # StreamURL, notifiy it to get real URL unirest .post('http://www.nhaccuatui.com/ajax/stream') .send('type=playlist') .send("key=#{song_list[song_index].key}") .send("v=#{new Date().getTime()}") .end((res) -> song = JSON.parse res.body real_song_url = song.data.stream if song.error_code == 0 if real_song_url? p = real_song_url.split '/' download_file song_index, real_song_url, "#{process.cwd()}/#{p[p.length - 1]}" else number_of_downloaded++ return ) return false real_song_url = song.data.stream_url p = real_song_url.split '/' download_file song_index, real_song_url, "#{process.cwd()}/#{p[p.length - 1]}" )(song_index) #request.on return true exports.run = () -> pjson = require asset('package.json') console.log "\n NCT-Downloader #{pjson.version} \n \n .oo o .oPYo. \n .P 8 8 8 8 \n .P 8 `o o' .oPYo. .oPYo. o8P .oPYo. 8 .oPYo. ooYoYo. \n oPooo8 `bd' 8 ' 8 8 8 8 8 8 8 8 8' 8 8 \n .P 8 d'`b 8 . 8 8 8 8 8 8 8 8 8 8 8 8 \n .P 8 o' `o `YooP' `YooP' 8 `YooP' 88 `YooP' `YooP' 8 8 8 \n ..:::::....:::..:.....::.....:::..::.....:..::.....::.....:..:..:..\n :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::\n \n" celeri.usage 'ndown [command] keyword_in_quote or url or album..' if process.argv.length == 2 or ['help', 'casi', 'singer', 'timkiem', 'search', 'playlist', 'page', 'trang'].indexOf(process.argv[2]) == -1 console.log "Hey, type: ndown help.\n" process.exit() d = new Download celeri.option( command: 'casi :url OR singer :url', description: ' Download nhạc của 1 ca sĩ' , (data) -> d.process "singer", data) celeri.option( command: 'timkiem :q OR search :q', description: ' Download nhạc dựa theo từ khoá. Ví du: ' , (data) -> d.process "search", data) celeri.option( command: 'playlist :url', description: ' Download nhạc của 1 playlist' , (data) -> d.process "playlist", data) celeri.option( command: 'trang :url OR page :url', description: ' Download nhạc trên 1 trang cụ thể' , (data) -> d.process "page", data) # parse the command line args celeri.parse process.argv