rmd 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,50 @@
1
+ require 'rmd/nct/getter/base'
2
+
3
+ module RMD
4
+ module NCT
5
+ module Getter
6
+ class KeyFromPage < Base
7
+
8
+ def fetch
9
+ if key && element
10
+ @data_link = element.text.strip
11
+ else
12
+ @errors = "The page does not contain the key."
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def uncached_key
19
+ page.search('script').each do |element|
20
+ if match_data = /NCTNowPlaying.intFlashPlayer\("flashPlayer", "song", "(.+)"\)\;/.match(element.text)
21
+ return match_data.to_a.last
22
+ end
23
+ end
24
+
25
+ nil
26
+ end
27
+
28
+ def new_link
29
+ "http://www.nhaccuatui.com/flash/xml?key1=#{key}"
30
+ end
31
+
32
+ def page
33
+ @page ||= agent.get(url)
34
+ end
35
+
36
+ def new_page
37
+ @new_page ||= agent.get(new_link)
38
+ end
39
+
40
+ def response
41
+ @response ||= Nokogiri::XML(new_page.body)
42
+ end
43
+
44
+ def element
45
+ @element ||= response.at_xpath('.//tracklist//location')
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,44 @@
1
+ require 'rmd/nct/getter/base'
2
+
3
+ module RMD
4
+ module NCT
5
+ module Getter
6
+ class KeyFromUrl < Base
7
+ ROOT_URL = 'http://www.nhaccuatui.com/download/song'
8
+
9
+ def fetch
10
+ if key
11
+ if response['error_message'] == 'Success'
12
+ @data_link = response['data']['stream_url']
13
+ else
14
+ @errors = "#{response['error_message']}: #{new_link}"
15
+ end
16
+ else
17
+ @errors = "The url does not contain the key."
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def response
24
+ @response ||= JSON.parse(page.body)
25
+ end
26
+
27
+ def uncached_key
28
+ uri = URI.parse(url)
29
+ if data = /\.(\S+)\./.match(uri.path)
30
+ data.to_a.last
31
+ end
32
+ end
33
+
34
+ def new_link
35
+ "#{ROOT_URL}/#{key}"
36
+ end
37
+
38
+ def page
39
+ @page ||= agent.get(new_link)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,63 @@
1
+ require 'mechanize'
2
+
3
+ module RMD
4
+ module NCT
5
+ class Playlist
6
+ attr_reader :errors, :link, :songs
7
+
8
+ def initialize(link)
9
+ @link = link
10
+ end
11
+
12
+ def fetch
13
+ if song_elements.count > 0
14
+ @errors = []
15
+ progress_bar = ProgressBar.create(
16
+ starting_at: 0,
17
+ total: song_elements.count,
18
+ format: '%c / %C Songs %B %p%%'
19
+ )
20
+
21
+ @songs = song_elements.inject([]) do |result, element|
22
+ link = element.attr('href')
23
+ song = RMD::NCT::Song.new(link)
24
+ song.fetch
25
+ if song.success?
26
+ result << song.data_link
27
+ else
28
+ @errors << song.errors
29
+ end
30
+
31
+ progress_bar.increment
32
+ result
33
+ end
34
+ else
35
+ @songs = []
36
+ @errors = ['Can not get song lists from this playlist page.']
37
+ end
38
+ end
39
+
40
+ def success?
41
+ !!songs && songs.count > 0
42
+ end
43
+
44
+ private
45
+
46
+ def song_css
47
+ '.item_content .name_song'
48
+ end
49
+
50
+ def song_elements
51
+ @song_element ||= page.search(song_css)
52
+ end
53
+
54
+ def page
55
+ @page ||= agent.get(link)
56
+ end
57
+
58
+ def agent
59
+ agent ||= Mechanize.new
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,36 @@
1
+ require 'rmd/nct/getter/key_from_page'
2
+ require 'rmd/nct/getter/key_from_url'
3
+
4
+ module RMD
5
+ module NCT
6
+ class Song
7
+ attr_reader :errors, :link, :data_link
8
+
9
+ def initialize(link)
10
+ @link = link
11
+ end
12
+
13
+ def fetch
14
+ getters.each do |getter|
15
+ getter.fetch
16
+ @data_link = getter.data_link
17
+ @errors = getter.errors
18
+ break unless @errors
19
+ end
20
+ end
21
+
22
+ def success?
23
+ !!data_link
24
+ end
25
+
26
+ private
27
+
28
+ def getters
29
+ [
30
+ RMD::NCT::Getter::KeyFromUrl.new(link),
31
+ RMD::NCT::Getter::KeyFromPage.new(link)
32
+ ]
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,44 @@
1
+ require 'rmd/downloader'
2
+ require 'rmd/factory'
3
+ require 'ruby-progressbar'
4
+
5
+ module RMD
6
+ class Processor
7
+ attr_reader :link
8
+
9
+ def initialize(link)
10
+ @link = link
11
+ end
12
+
13
+ def process
14
+ puts "Start processing #{link}...".green
15
+ playlist = RMD::Factory.build(link)
16
+ playlist.fetch
17
+
18
+ if playlist.success?
19
+ playlist.songs.each do |song|
20
+ download(song)
21
+ end
22
+ end
23
+
24
+ playlist.errors.each do |error|
25
+ puts error.red
26
+ end
27
+ end
28
+
29
+ def self.process(link)
30
+ begin
31
+ new(link).process
32
+ rescue => e
33
+ puts e.message.red
34
+ puts 'Errors! Can not continue!'.red
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def download(data_link)
41
+ RMD::Downloader.download(data_link)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,20 @@
1
+ module RMD
2
+ class SongPlaylistAdapter
3
+ extend Forwardable
4
+
5
+ attr_reader :song
6
+ def_delegators :song, :fetch, :success?
7
+
8
+ def initialize(song)
9
+ @song = song
10
+ end
11
+
12
+ def songs
13
+ [song.data_link].compact
14
+ end
15
+
16
+ def errors
17
+ [song.errors].compact
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ module Rmd
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rmd/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "rmd"
8
+ spec.version = Rmd::VERSION
9
+ spec.authors = ["Hieu Nguyen"]
10
+ spec.email = ["hieuk09@gmail.com"]
11
+ spec.summary = %q{RMD: Ruby Music Dowloader}
12
+ spec.description = %q{RMD: a music downloader written in pure ruby.}
13
+ spec.homepage = "https://github.com/hieuk09/rmd"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = 'rmd'
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency 'thor', '~> 0.19'
22
+ spec.add_dependency 'mechanize', '~> 2.7'
23
+ spec.add_dependency 'ruby-progressbar', '~> 1.7'
24
+ spec.add_dependency 'colorize', '~> 0.7'
25
+
26
+ spec.add_development_dependency "bundler", "~> 1.6"
27
+ spec.add_development_dependency "rspec", '~> 3.1'
28
+ spec.add_development_dependency "webmock", '~> 1.20'
29
+ spec.add_development_dependency "vcr", '~> 2.9'
30
+ spec.add_development_dependency "byebug", '~> 3.5'
31
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe RMD::Downloader do
4
+ describe '.download' do
5
+ let(:link) { 'www.example/xyz/abc.mp3' }
6
+ let(:downloader) { instance_double('RMD::Downloader') }
7
+
8
+ it 'downloads' do
9
+ expect(described_class).to receive(:new).with(link).and_return(downloader)
10
+ expect(downloader).to receive(:download)
11
+ described_class.download(link)
12
+ end
13
+ end
14
+
15
+ describe '#download' do
16
+ let(:downloader) { described_class.new(link) }
17
+ let(:link) { 'http://db.gamefaqs.com/portable/3ds/file/fire_emblem_awakening_shop.txt' }
18
+ let(:file_name) { 'fire_emblem_awakening_shop.txt' }
19
+ let(:content_length) { body.length }
20
+ let(:file_path) { File.expand_path("../../../#{file_name}", __FILE__) }
21
+ let(:result) { File.read(file_path) }
22
+
23
+ it 'downloads' do
24
+ VCR.use_cassette("song download") do
25
+ expect {
26
+ downloader.download
27
+ }.to output("\e[0;32;49m#{file_name}\e[0m\n").to_stdout
28
+ expect(File.exists?(file_path)).to eq true
29
+ end
30
+ File.delete(file_path)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe RMD::Factory do
4
+ describe '.build' do
5
+ let(:factory) { double('RMD::Factory') }
6
+ let(:link) { 'link' }
7
+
8
+ it 'builds adapter' do
9
+ expect(described_class).to receive(:new).with(link).and_return(factory)
10
+ expect(factory).to receive(:build)
11
+ described_class.build(link)
12
+ end
13
+ end
14
+
15
+ describe '#build' do
16
+ let(:factory) { described_class.new(link) }
17
+ subject { factory.build }
18
+
19
+ context 'when link belongs to nhaccuatui website' do
20
+ context 'when link is song link' do
21
+ let(:link) { 'www.nhaccuatui/bai-hat/song.mp3' }
22
+ it { is_expected.to be_a(RMD::SongPlaylistAdapter) }
23
+ end
24
+
25
+ context 'when link is playlist link' do
26
+ let(:link) { 'www.nhaccuatui/playlist/bang-kieu' }
27
+ it { is_expected.to be_a(RMD::NCT::Playlist) }
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,140 @@
1
+ require 'spec_helper'
2
+
3
+ describe RMD::NCT::Getter::KeyFromPage do
4
+ let(:url) { 'url' }
5
+ let(:getter) { described_class.new(url) }
6
+
7
+ describe '#fetch' do
8
+ let(:element) { double('Element', text: data_link) }
9
+ let(:data_link) { 'data_link' }
10
+ let(:key) { 'key' }
11
+ let(:errors) { 'The page does not contain the key.' }
12
+
13
+ before do
14
+ allow(getter).to receive(:key).and_return(key)
15
+ allow(getter).to receive(:element).and_return(element)
16
+ getter.fetch
17
+ end
18
+
19
+ context 'when key is not present' do
20
+ let(:key) { nil }
21
+
22
+ it 'returns errors' do
23
+ expect(getter.data_link).to eq nil
24
+ expect(getter.errors).to eq errors
25
+ end
26
+ end
27
+
28
+ context 'when element is not present' do
29
+ let(:element) { nil }
30
+
31
+ it 'returns errors' do
32
+ expect(getter.data_link).to eq nil
33
+ expect(getter.errors).to eq errors
34
+ end
35
+ end
36
+
37
+ context 'otherwise' do
38
+ it 'gets data' do
39
+ expect(getter.data_link).to eq data_link
40
+ expect(getter.errors).to eq nil
41
+ end
42
+ end
43
+ end
44
+
45
+ describe '#response' do
46
+ let(:body) { '<body>abc</body>' }
47
+ let(:new_page) { double('Page', body: body) }
48
+ let(:response) { getter.send(:response).child }
49
+
50
+ before do
51
+ expect(getter).to receive(:new_page).and_return(new_page)
52
+ end
53
+
54
+ it 'parse the response from the page' do
55
+ expect(response.name).to eq 'body'
56
+ expect(response.text).to eq 'abc'
57
+ end
58
+ end
59
+
60
+ describe '#key' do
61
+ let(:page) { double('Page') }
62
+ let(:elements) { [element] }
63
+ let(:element) { double('Element', text: text) }
64
+ subject { getter.send(:key) }
65
+
66
+ before do
67
+ expect(getter).to receive(:page).and_return(page)
68
+ expect(page).to receive(:search).with('script').and_return(elements)
69
+ end
70
+
71
+ context 'when match data exists' do
72
+ let(:text) { 'NCTNowPlaying.intFlashPlayer("flashPlayer", "song", "key");' }
73
+ it { is_expected.to eq 'key' }
74
+ end
75
+
76
+ context 'when match data is not exists' do
77
+ let(:text) { 'NCTNowPlaying.initFlashPlayer();' }
78
+ it { is_expected.to eq nil }
79
+ end
80
+ end
81
+
82
+ describe '#element' do
83
+ let(:response) { Nokogiri::XML(xml) }
84
+
85
+ before do
86
+ expect(getter).to receive(:response).and_return(response)
87
+ end
88
+
89
+ context 'when the path exists' do
90
+ let(:xml) { '<tracklist><location>abc</location></tracklist>' }
91
+ let(:subject) { getter.send(:element).text }
92
+ it { is_expected.to eq 'abc' }
93
+ end
94
+
95
+ context 'when the path is not exists' do
96
+ let(:xml) { '<location>abc</location>' }
97
+ subject { getter.send(:element) }
98
+ it { is_expected.to eq nil }
99
+ end
100
+ end
101
+
102
+ describe '#new_link' do
103
+ let(:key) { 'key' }
104
+ subject { getter.send(:new_link) }
105
+
106
+ before do
107
+ expect(getter).to receive(:key).and_return(key)
108
+ end
109
+
110
+ it { is_expected.to eq 'http://www.nhaccuatui.com/flash/xml?key1=key' }
111
+ end
112
+
113
+ describe '#page' do
114
+ let(:agent) { instance_double('Mechanize') }
115
+ let(:page) { double('Page') }
116
+ subject { getter.send(:page) }
117
+
118
+ before do
119
+ expect(getter).to receive(:agent).and_return(agent)
120
+ expect(agent).to receive(:get).with(url).and_return(page)
121
+ end
122
+
123
+ it { is_expected.to eq page }
124
+ end
125
+
126
+ describe '#new_page' do
127
+ let(:new_link) { 'new_link' }
128
+ let(:agent) { instance_double('Mechanize') }
129
+ let(:page) { double('Page') }
130
+ subject { getter.send(:new_page) }
131
+
132
+ before do
133
+ expect(getter).to receive(:new_link).and_return(new_link)
134
+ expect(getter).to receive(:agent).and_return(agent)
135
+ expect(agent).to receive(:get).with(new_link).and_return(page)
136
+ end
137
+
138
+ it { is_expected.to eq page }
139
+ end
140
+ end