rmd 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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