nct-downloader
Version:
Download nhaccuatui
266 lines (242 loc) • 8.98 kB
text/coffeescript
'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