gmusic 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Tower He
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,28 @@
1
+ = GMusic
2
+
3
+ GMusic provides APIs to search songs from http://music.g.cn. There are three query conditions supported by GMusic, which are title, artist and album of a song. GMusic delivers query requests to Google and collects the search result returned from Google.
4
+
5
+ == SYNOPSIS:
6
+
7
+ require 'gmusic'
8
+
9
+ result = GMusic.search(:title => 'heal the world', :artist => 'michael jackson')
10
+
11
+ puts result.class
12
+ # => Array
13
+
14
+ result.first.keys
15
+ # => [:artist, :album, :format, :size, :url, :id, :lyrics, :title]
16
+
17
+ == REQUIREMENTS:
18
+
19
+ * jeweler (1.4.0) http://technicalpickles.github.com/jeweler
20
+ * hpricot (0.8.2) http://github.com/whymirror/hpricot
21
+
22
+ == INSTALL:
23
+
24
+ gem install gmusic
25
+
26
+ == COPYRIGHT:
27
+
28
+ Copyright (c) 2010 Tower He. See LICENSE for details.
@@ -0,0 +1,47 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "gmusic"
8
+ gem.summary = %Q{Search songs from http://music.g.cn}
9
+ gem.description = %Q{gmusic provides APIs to search songs from http://music.g.cn. There are three query conditions supported by gmusic, which are title, artist and album of a song. gmusic delivers query requests to Google and collects the search result returned from Google. }
10
+ gem.email = "towerhe@gmail.com"
11
+ gem.homepage = "http://github.com/towerhe/gmusic"
12
+ gem.authors = ["Tower He"]
13
+ gem.add_dependency "hpricot", ">=0.8.2"
14
+ gem.add_development_dependency "rspec", ">= 1.2.9"
15
+
16
+ gem.files = FileList['lib/**/*.rb', 'spec/**/*.rb', '[A-Z]*'].to_a
17
+ end
18
+ Jeweler::GemcutterTasks.new
19
+ rescue LoadError
20
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
21
+ end
22
+
23
+ require 'spec/rake/spectask'
24
+ Spec::Rake::SpecTask.new(:spec) do |spec|
25
+ spec.libs << 'lib' << 'spec'
26
+ spec.spec_files = FileList['spec/**/*_spec.rb']
27
+ end
28
+
29
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
30
+ spec.libs << 'lib' << 'spec'
31
+ spec.pattern = 'spec/**/*_spec.rb'
32
+ spec.rcov = true
33
+ end
34
+
35
+ task :spec => :check_dependencies
36
+
37
+ task :default => :spec
38
+
39
+ require 'rake/rdoctask'
40
+ Rake::RDocTask.new do |rdoc|
41
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
42
+
43
+ rdoc.rdoc_dir = 'rdoc'
44
+ rdoc.title = "gmusic #{version}"
45
+ rdoc.rdoc_files.include('README*')
46
+ rdoc.rdoc_files.include('lib/**/*.rb')
47
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,59 @@
1
+ require 'rubygems'
2
+ require 'hpricot'
3
+ require 'open-uri'
4
+
5
+ require 'gmusic/parser'
6
+
7
+ module GMusic
8
+ class << self
9
+ VALID_CONDITIONS = [ :title, :artist ]
10
+
11
+ SEARCH_URL_TEMPLATE = %Q(http://www.google.cn/music/search?q=%s&aq=f)
12
+ DOWNLOAD_INFO_URL_TEMPLATE = %Q(http://www.google.cn/music/top100/musicdownload?id=%s)
13
+ LYRICS_URL_TEMPLATE = %Q(http://www.google.cn/music/top100/lyrics?id=%s)
14
+
15
+
16
+ def search(conditions)
17
+ validate_conditions(conditions)
18
+ query = construct_query(conditions)
19
+ songs = SongListParser.parse(SEARCH_URL_TEMPLATE % query)
20
+
21
+ matched = []
22
+ songs.each do |song|
23
+ if matched?(song, conditions)
24
+ song.merge!(DownloadInfoParser.parse(DOWNLOAD_INFO_URL_TEMPLATE % song[:id]))
25
+ song.merge!(LyricsParser.parse(LYRICS_URL_TEMPLATE % song[:id]))
26
+ matched << song
27
+ end
28
+ end
29
+ matched
30
+ end
31
+
32
+ private
33
+ def validate_conditions(conditions)
34
+ raise_argument_error if conditions.empty? or (conditions.keys & VALID_CONDITIONS).empty?
35
+ end
36
+
37
+ def construct_query(conditions)
38
+ query = ''
39
+ VALID_CONDITIONS.each do |c|
40
+ query << (query.empty? ? '' : '+') << "\"#{conditions[c]}\"" if conditions.has_key? c
41
+ end
42
+ URI.escape query
43
+ end
44
+
45
+ def raise_argument_error
46
+ raise ArgumentError.new('must specify at least one condition')
47
+ end
48
+
49
+ def matched?(result, conditions)
50
+ if conditions.has_key?(:title) and not result[:title].downcase.eql?(conditions[:title].downcase)
51
+ return false
52
+ end
53
+ if conditions.has_key?(:artist) and not result[:artist].downcase.include?(conditions[:artist].downcase)
54
+ return false
55
+ end
56
+ return true
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,53 @@
1
+ module GMusic
2
+ module SongListParser
3
+ class << self
4
+ def parse url
5
+ songs = []
6
+ doc = Hpricot(open(url))
7
+ (doc/'#song_list > tr').each do |tr|
8
+ begin
9
+ song = {}
10
+ id = tr.attributes["id"].gsub(/row/, '')
11
+ next if id.include?('snippetRow')
12
+ song.merge!({ :id => id })
13
+ song.merge!({ :title => (tr/'td.Title > a > b').first.inner_html })
14
+ song.merge!({ :artist => (tr/'td.Artist > a').first.inner_html })
15
+ song.merge!({ :album => (tr/'td.Album > a').first.inner_html })
16
+ songs << song
17
+ rescue
18
+ end
19
+ end
20
+ songs
21
+ end
22
+ end
23
+ end
24
+
25
+ module LyricsParser
26
+ class << self
27
+ def parse url
28
+ doc = Hpricot(open(url))
29
+ begin
30
+ { :lyrics => (doc/'#lyrics').first.inner_html }
31
+ rescue
32
+ nil
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ module DownloadInfoParser
39
+ class << self
40
+ def parse url
41
+ result = {}
42
+ doc = Hpricot(open(url))
43
+ begin
44
+ result.merge!({ :size => (doc/'tr.meta-data-tr > td.td-size').first.inner_html })
45
+ result.merge!({ :format => (doc/'tr.meta-data-tr > td.td-format').first.inner_html })
46
+ result.merge!({ :url => (doc/'div.download > a').first.attributes['href'] })
47
+ rescue
48
+ nil
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,43 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Gmusic" do
4
+ it "should raise ArgumentError if the argument 'conditions' is empty" do
5
+ lambda { GMusic.search({}) }.should raise_error ArgumentError
6
+ end
7
+
8
+ it "should raise ArgumentError if there is no valid condition specified" do
9
+ lambda { GMusic.search(:invalid => 'test') }.should raise_error ArgumentError
10
+ end
11
+
12
+ it "should return an empty array if nothing is matched" do
13
+ GMusic::SongListParser.should_receive(:parse).and_return([])
14
+ GMusic.search(:title => 'inexistence').empty?.should be_true
15
+ end
16
+
17
+ it "should only return the full matched item" do
18
+ parsed = GMusic::SongListParser.parse path_of('g-search-alreadygone.html')
19
+ dowload_info = GMusic::DownloadInfoParser.parse path_of('g-download.html')
20
+ lyrics = GMusic::LyricsParser.parse path_of('g-lyrics.html')
21
+
22
+ GMusic::SongListParser.should_receive(:parse).and_return(parsed)
23
+ GMusic::DownloadInfoParser.should_receive(:parse).and_return(dowload_info)
24
+ GMusic::LyricsParser.should_receive(:parse).and_return(lyrics)
25
+ songs = GMusic.search(:title => 'already gone', :artist => 'kelly clarkson')
26
+
27
+ songs.size.should == 1
28
+ end
29
+
30
+ it "should return 5 full matched items" do
31
+ parsed = GMusic::SongListParser.parse path_of('g-search-thankyou.html')
32
+ dowload_info = GMusic::DownloadInfoParser.parse path_of('g-download.html')
33
+ lyrics = GMusic::LyricsParser.parse path_of('g-lyrics.html')
34
+
35
+ GMusic::SongListParser.should_receive(:parse).and_return(parsed)
36
+ GMusic::DownloadInfoParser.should_receive(:parse).exactly(5).times.and_return(dowload_info)
37
+ GMusic::LyricsParser.should_receive(:parse).exactly(5).times.and_return(lyrics)
38
+
39
+ songs = GMusic.search(:title => 'thank you', :artist => 'dido')
40
+
41
+ songs.size.should == 5
42
+ end
43
+ end
@@ -0,0 +1,67 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ module GMusic
4
+ describe SongListParser do
5
+ it "should return an empty array if no record has been found" do
6
+ songs = SongListParser.parse path_of('g-empty.html')
7
+ songs.empty?.should be_true
8
+ end
9
+
10
+ it "should parse all the songs with id, title, artist and album" do
11
+ songs = SongListParser.parse path_of('g-search-thankyou.html')
12
+
13
+ songs.should be_instance_of(Array)
14
+ songs.size.should == 20
15
+
16
+ songs.each do |s|
17
+ s.has_key?(:id).should be_true
18
+ s.has_key?(:title).should be_true
19
+ s.has_key?(:artist).should be_true
20
+ s.has_key?(:album).should be_true
21
+ end
22
+ end
23
+ end
24
+
25
+ describe LyricsParser do
26
+ it "should return nil if no record has been found" do
27
+ result = LyricsParser.parse path_of('g-empty.html')
28
+ result.should be_nil
29
+ end
30
+
31
+ it "should parse the lyrics" do
32
+ lyrics = <<-LYRICS
33
+ 歌手:
34
+ &#20975;&#33673; &#20811;&#33713;&#20811;&#26862;(Kelly Clarkson)
35
+ <br>
36
+ 专辑:&nbsp;
37
+ All I Ever Wanted
38
+ <br>
39
+ 公司:
40
+ RCA&#21809;&#29255;(&#32034;&#23612;BMG)
41
+ <br>
42
+ <br>
43
+ Remember all the things we wanted<br> Now all our memories, they&#39;re haunted<br> We were always meant to say goodbye<br> Even with our fists held high<br> It never would&#39;ve worked out right<br> We were never meant for do or die<br> I didn&#39;t want us to burn out<br> I didn&#39;t come here to hold you<br> Now I can&#39;t stop<br> I want you to know that it doesn&#39;t matter<br> Where we take this road<br> Someone&#39;s gotta go<br> And I want you to know<br> You couldn&#39;t have loved me better<br> But I want you to move on<br> So I&#39;m already gone<br> Looking at you makes it harder<br> But I know that you&#39;ll find another<br> That doesn&#39;t always make you want to cry<br> Started with a perfect kiss<br> Then we could feel the poison set in<br> Perfect couldn&#39;t keep this love alive<br> You know that I love you so<br> I love you enough to let you go<br> I want you to know that it doesn&#39;t matter<br> Where we take this road<br> Someone&#39;s gotta go<br> And I want you to know<br> You couldn&#39;t have loved me better<br> But I want you to move on<br> So I&#39;m already gone<br> I&#39;m already gone, already gone<br> You can&#39;t make it feel right<br> When you know that it&#39;s wrong<br> I&#39;m already gone, already gone<br> There&#39;s no moving on<br> So I&#39;m already gone<br> Remember all the things we wanted<br> Now all our memories, they&#39;re haunted<br> We were always meant to say goodbye<br> I want you to know that it doesn&#39;t matter<br> Where we take this road<br> Someone&#39;s gotta go<br> And I want you to know<br> You couldn&#39;t have loved me better<br> But I want you to move on<br> So I&#39;m already gone<br> I&#39;m already gone, already gone<br> You can&#39;t make it feel right<br> When you know that it&#39;s wrong<br> I&#39;m already gone, already gone<br> There&#39;s no moving on<br> So I&#39;m already gone<br>
44
+ LYRICS
45
+
46
+ result = LyricsParser.parse path_of('g-lyrics.html')
47
+
48
+ result[:lyrics].should == lyrics.strip.gsub(/<br>/, '<br />')
49
+ end
50
+ end
51
+
52
+ describe DownloadInfoParser do
53
+ it "should return nil if no record has been found" do
54
+ result = DownloadInfoParser.parse path_of('g-empty.html')
55
+ result.should be_nil
56
+ end
57
+
58
+ it "should parse the download link" do
59
+ result = DownloadInfoParser.parse path_of('g-download.html')
60
+
61
+ result[:size].should == '6.7&nbsp;MB'
62
+ result[:format].should == 'MP3'
63
+ # &amp; is decoded to &
64
+ result[:url].should == '/music/top100/url?q=http%3A%2F%2Ffile3.top100.cn%2F201002182021%2F6606E229A301A32E3EFEDD028337EA8C%2FSpecial_120398%2FAlready%2520Gone.mp3&ct=rdl&cad=dl&ei=uTB9S5CgOZjssQK5t5X8AQ&sig=9BD4F31BB70F31BAB6AD4AECA3C5200D'
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,14 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'gmusic'
4
+ require 'spec'
5
+ require 'spec/autorun'
6
+
7
+ Spec::Runner.configure do |config|
8
+
9
+ end
10
+
11
+ def path_of(file)
12
+ File.join(File.dirname(__FILE__), file)
13
+ end
14
+
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gmusic
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Tower He
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-02-22 00:00:00 +08:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: hpricot
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ - 8
30
+ - 2
31
+ version: 0.8.2
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: rspec
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 1
43
+ - 2
44
+ - 9
45
+ version: 1.2.9
46
+ type: :development
47
+ version_requirements: *id002
48
+ description: "gmusic provides APIs to search songs from http://music.g.cn. There are three query conditions supported by gmusic, which are title, artist and album of a song. gmusic delivers query requests to Google and collects the search result returned from Google. "
49
+ email: towerhe@gmail.com
50
+ executables: []
51
+
52
+ extensions: []
53
+
54
+ extra_rdoc_files:
55
+ - LICENSE
56
+ - README.rdoc
57
+ files:
58
+ - LICENSE
59
+ - README.rdoc
60
+ - Rakefile
61
+ - VERSION
62
+ - lib/gmusic.rb
63
+ - lib/gmusic/parser.rb
64
+ - spec/gmusic_spec.rb
65
+ - spec/parser_spec.rb
66
+ - spec/spec_helper.rb
67
+ has_rdoc: true
68
+ homepage: http://github.com/towerhe/gmusic
69
+ licenses: []
70
+
71
+ post_install_message:
72
+ rdoc_options:
73
+ - --charset=UTF-8
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ segments:
81
+ - 0
82
+ version: "0"
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ segments:
88
+ - 0
89
+ version: "0"
90
+ requirements: []
91
+
92
+ rubyforge_project:
93
+ rubygems_version: 1.3.6
94
+ signing_key:
95
+ specification_version: 3
96
+ summary: Search songs from http://music.g.cn
97
+ test_files:
98
+ - spec/gmusic_spec.rb
99
+ - spec/parser_spec.rb
100
+ - spec/spec_helper.rb