rapgenius 0.1.0 → 1.0.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: cba91ddf9f2be805d1fdf8c884a3474b893ec741
4
+ data.tar.gz: 41f02951bd8769319f5ed6a4f4a28a84e38baac3
5
+ SHA512:
6
+ metadata.gz: 2632e2bac0a238c1d1f7989cbe3e3e72e34b7c8b7e4fb442cfa40473fc26472532220ad46b9007849ba630c1cf6cc94f765bcb967d4ac5d13256ff69f0314950
7
+ data.tar.gz: d4f89f5534f08a6ee69a6abefef724ba659f5994dbe766ab58af6bd64dc2be4bb941231ded1ced2d409ce211252711d5e94b451c4c14562dc3508671660207ac
data/.gitignore CHANGED
@@ -1,2 +1,4 @@
1
1
  Gemfile.lock
2
2
  spec/support/cassettes
3
+ .bundle/
4
+ pkg/
data/CHANGELOG.md CHANGED
@@ -15,4 +15,11 @@ __v0.0.3__ (22nd August 2013, *contributed by [tsigo](https://github.com/tsigo)*
15
15
 
16
16
  __v0.1.0__ (29th August 2013, *contributed by [tsigo](https://github.com/tsigo)*)
17
17
 
18
- * Adds support for searching for songs with `RapGenius::Song.search("Song, artist name or other query")`
18
+ * Adds support for searching for songs with `RapGenius::Song.search("Song, artist name or other query")`
19
+
20
+ __v1.0.0__ (27th January 2014)
21
+
22
+ * Switches to using the private REST API used by the soon to be released
23
+ [Genius iOS app](http://rapgenius.com/static/app).
24
+ * Vastly improves quality of data available on songs and their lyrics
25
+ * Provides access to media items and song artists
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2013 Tim Rogers
1
+ Copyright (c) 2013-2014 Tim Rogers
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,128 +1,151 @@
1
- # rapgenius
1
+ # rapgenius.rb
2
2
 
3
3
  ![Rap Genius logo](http://f.cl.ly/items/303W0c1i2r100j2u3Y0y/Screen%20Shot%202013-08-17%20at%2016.01.19.png)
4
4
 
5
5
  ## What does this do?
6
6
 
7
- It's a Ruby gem for accessing lyrics and explanations on
8
- [Rap Genius](http://rapgenius.com).
7
+ It's a Ruby gem for accessing lyrics, artists and explanations on
8
+ [Rap Genius](http://rapgenius.com).
9
9
 
10
- They very sadly [don't have an API](https://twitter.com/RapGenius/status/245057326321655808) so I decided to replicate one for myself
11
- with a nice bit of screen scraping with [Nokogiri](https://github.com/sparklemotion/nokogiri), much like my [amex](https://github.com/timrogers/amex), [ucas](https://github.com/timrogers/ucas) and [lloydstsb](https://github.com/timrogers/lloydstsb) gems.
10
+ In pre-1.0.0 versions, this gem used Nokogiri to scrape Rap Genius's pages,
11
+ but no more. The new [Genius iOS app](http://rapgenius.com/static/app) uses
12
+ a private API, which this gem makes use of.
12
13
 
13
14
  ## Installation
14
15
 
15
16
  Install the gem, and you're ready to go. Simply add the following to your
16
17
  Gemfile:
17
18
 
18
- `gem "rapgenius", "~> 0.1.0"`
19
+ `gem "rapgenius", "~> 1.0.0"`
19
20
 
20
21
  ## Usage
21
22
 
22
- Songs on Rap Genius don't have numeric identifiers as far as I can tell - they're identified by a URL slug featuring the artist and song name, for instance "Big-sean-control-lyrics". We use this to fetch a particular track, like so:
23
+ __The best way to get a decent idea of the attributes available on `Song` and
24
+ the other objects is by looking through the files in `lib/rapgenius`.__
25
+
26
+ ### Searching
27
+
28
+ You can search for songs by various fields. All of these
29
+ methods return an array of `Song` objects...:
30
+
31
+ ```ruby
32
+ RapGenius.search("Versace")
33
+ RapGenius.search_by_title("Versace")
34
+ RapGenius.search_by_artist("Migos")
35
+ RapGenius.search_by_lyrics("medusa")
36
+ ```
37
+
38
+ If more than 20 results are returned, you won't be able to get access to
39
+ records beyond the 20th. There doesn't appear to be any pagination support
40
+ in the API.
41
+
42
+ ### Songs
43
+
44
+ Songs on Rap Genius have unique identifiers. They're not especially
45
+ easy to find, but if you hover over the "pyong" button near the top of the page,
46
+ you'll see the song's ID in the URL. Once you have an ID, you can load a
47
+ song via the API:
23
48
 
24
49
  ```ruby
25
50
  require 'rapgenius'
26
- song = RapGenius::Song.find("Big-sean-control-lyrics")
51
+
52
+ song = RapGenius::Song.find(176872)
53
+ song.title # => "Versace"
54
+ song.artists.map(&:name) # => ["Migos", "Drake", "Zaytoven"]
27
55
  ```
28
56
 
29
- Once you've got the song, you can easily load details about it. This uses
30
- Nokogiri to fetch the song's page and then parse it:
57
+ Once you've found the song you're seeking, there's plenty of other useful
58
+ details you can access:
31
59
 
32
60
  ```ruby
33
61
  song.title
34
- # => "Control"
35
-
36
- song.artist
37
- # => "Big Sean"
62
+ # => "Versace"
38
63
 
39
- song.full_artist
40
- # => "Big Sean (Ft. Jay Electronica & Kendrick Lamar)"
64
+ song.url
65
+ # => "http://rapgenius.com/Migos-versace-lyrics""
41
66
 
42
- song.images
43
- # => ["http://s3.amazonaws.com/rapgenius/1376434983_jay-electronica.jpg", "http://s3.amazonaws.com/rapgenius/1375029260_Big%20Sean.png", "http://s3.amazonaws.com/rapgenius/Kendrick-Lamar-1024x680.jpg"]
67
+ song.pyongs
68
+ # => 22
44
69
 
45
70
  song.description
46
- # => "The non-album cut from Sean that basically blew up the Internet due to a world-beating verse by Kendrick Lamar...
71
+ # => "Released in June 2013, not only did they take the beat from Soulja Boy’s OMG part 2 but they absolutely killed it."
47
72
  ```
48
73
 
49
- The `#annotations` accessor on a Song returns an array of RapGenius::Annotation
50
- objects corresponding to different annotated lines of the song, identified by
51
- their `id`.
52
74
 
53
- You can look these up manually using `RapGenius::Annotation.find("id")`. You
54
- can grab the ID for a lyric from a RapGenius page by right clicking on an annotation, copying the shortcut and then finding the number after "http://rapgenius.com".
75
+ ### Lines
76
+
77
+ Once you've got a song, you can then go through its "lines", which includes
78
+ annotated and unannotated parts of the content.
55
79
 
56
80
  ```ruby
57
- song.annotations
58
- # => [<RapGenius::Annotation>, <RapGenius::Annotation>...]
81
+ line = song.lines[1]
59
82
 
60
- annotation = song.annotations[99]
83
+ line.lyric
84
+ # => Versace, Versace, Medusa head on me like I'm 'luminati
61
85
 
62
- annotation.lyric
63
- # => "And that goes for Jermaine Cole, Big KRIT, Wale\nPusha T, Meek Millz, A$AP Rocky, Drake\nBig Sean, Jay Electron', Tyler, Mac Miller"
86
+ line.annotations
87
+ # => ["Read about how this collaboration came to pass here..."]
64
88
 
65
- annotation.explanation
66
- # => "Kendrick calls out some of the biggest names in present day Hip-hop...""
89
+ line.song
90
+ # => #<RapGenius::Song:0x007fccdba50a50 @id=176872...
91
+ ```
67
92
 
68
- annotation.song == song # You can get back to the song from the annotation...
69
- # => true
93
+ ### Media
70
94
 
71
- annotation.id
72
- # => "2093001"
95
+ Rap Genius provides great access to media like MP3s on Soundcloud or videos
96
+ on YouTube direct from songs.
73
97
 
74
- annotation2 = RapGenius::Annotation.find("2093001") # Fetching directly...
98
+ ```ruby
99
+ media = song.media.first
75
100
 
76
- annotation == annotations2
77
- # => true
101
+ media.type
102
+ # => "audio"
103
+
104
+ media.provider
105
+ # => "soundcloud"
106
+
107
+ media.url
108
+ # => "https://soundcloud.com/mixtapemechaniks/migos-ft-drake-versace-remix"
78
109
  ```
79
110
 
80
- You can search for songs by artist and/or title.
111
+ ### Artist
112
+
113
+ You can navigate from a song to its artist, and then to other songs by that
114
+ artist. Magic, huh?!
81
115
 
82
116
  ```ruby
83
- results = RapGenius::Song.search("Big Sean Control")
84
- # => [#<RapGenius::Song:0x007fbe4b9195e0
85
- # @artist="Big Sean (Ft. Jay Electronica & Kendrick Lamar)",
86
- # @title="Control",
87
- # @url="http://rapgenius.com/Big-sean-control-lyrics">,
88
- # #<RapGenius::Song:0x007fbe4b920f70
89
- # @artist="Big Sean (Ft. Jay Electronica & Kendrick Lamar)",
90
- # @title="Control (French Version)",
91
- # @url="http://rapgenius.com/Big-sean-control-french-version-lyrics">,
92
- # #<RapGenius::Song:0x007fbe4b920958
93
- # @artist="Big Sean (Ft. Crobar, Jay Electronica & Kendrick Lamar)",
94
- # @title="Control (Remix) [Kendrick Diss]",
95
- # @url="http://rapgenius.com/Big-sean-control-remix-kendrick-diss-lyrics">,
96
- # #<RapGenius::Song:0x007fbe4b920250
97
- # @artist=
98
- # "Sa-roc (Ft. Big Sean - No I.D., Jay Electronica, Kendrick Lamar & Sa-roc)",
99
- # @title="CONTROL",
100
- # @url="http://rapgenius.com/Sa-roc-control-lyrics">,
101
- # #<RapGenius::Song:0x007fbe4b91ff30
102
- # @artist="C3",
103
- # @title=
104
- # "Control ( Disses Kendrick Lamar , Jay-Z, Tyler The Creator, Big Sean, Meek Mill & More )",
105
- # @url=
106
- # "http://rapgenius.com/C3-control-disses-kendrick-lamar-jay-z-tyler-the-creator-big-sean-meek-mill-and-more-lyrics">]
107
-
108
- results[0].description
109
- # => "The non-album cut from Sean that basically blew up the Internet due to a world-beating verse by Kendrick Lamar...
110
- ```
117
+ artist = song.artist # or song.artists, song.featured_artists or song.producer_artists
111
118
 
112
- ## Contributing
119
+ artist.name
120
+ # => "Migos"
121
+
122
+ artist.type
123
+ # => :primary
113
124
 
114
- After the last few contributions, there's one core thing I'd like to add to the gem:
125
+ artist.url
126
+ # => "http://rapgenius.com/artists/Migos"
115
127
 
116
- * __Support for *\*Genius*__ - Rap Genius also have other sites on subdomains like [News Genius](http://news.rapgenius.com) and [Poetry Genius](http://poetry.rapgenius.com). These could very easily be supported, since theyre identical in terms of markup.
128
+ artist.description
129
+ # => "Migos are an American hip-hop group from Atlanta, Georgia..."
130
+
131
+ artist.songs
132
+ # => [#<RapGenius::Song:0x007fccdb884398...]
133
+ ```
134
+
135
+ ## Contributing
117
136
 
118
137
  If you'd like to contribute anything else, go ahead or better still, make an issue and we can talk it over and spec it out! A few quick tips:
119
138
 
120
- * Don't update the version numbers before your pull request - I'll sort that part out for you!
139
+ * Don't update the version numbers before your pull request - I'll sort that part out for you.
121
140
  * Make sure you write specs, then run them with `$ bundle exec rake`
122
141
  * Update this README.md file so I, and users, know how your changes work
123
142
 
143
+ ## Copyright
144
+
145
+ Copyright (c) 2013-2014 Tim Rogers. See LICENSE for details.
146
+
124
147
  ## Get in touch
125
148
 
126
149
  [timrogers](https://github.com/timrogers) and [tsigo](https://github.com/tsigo) are the gem's primary contributors.
127
150
 
128
- Any questions, thoughts or comments? Email me at <me+rapgenius@timrogers.co.uk> or create an issue.
151
+ Any questions, thoughts or comments? Email me at <me@timrogers.co.uk> or create an issue.
data/lib/rapgenius.rb CHANGED
@@ -1,5 +1,42 @@
1
1
  require 'rapgenius/version'
2
- require 'rapgenius/scraper'
3
- require 'rapgenius/annotation'
2
+ require 'rapgenius/client'
3
+ require 'rapgenius/line'
4
4
  require 'rapgenius/song'
5
- require 'rapgenius/exceptions'
5
+ require 'rapgenius/artist'
6
+ require 'rapgenius/media'
7
+ require 'rapgenius/exceptions'
8
+
9
+ module RapGenius
10
+ extend RapGenius::Client
11
+
12
+ def self.search(query, options={})
13
+ response = Client::HTTPClient.get("/search", query: {q: query}.merge(options))
14
+
15
+ response["response"]["hits"].map do |song|
16
+ result = song["result"]
17
+
18
+ Song.new(
19
+ id: result["id"],
20
+ name: result["name"],
21
+ artist: Artist.new(
22
+ id: result["primary_artist"]["id"],
23
+ name: result["primary_artist"]["name"],
24
+ type: :primary
25
+ ),
26
+ title: result["title"]
27
+ )
28
+ end
29
+ end
30
+
31
+ def self.search_by_artist(query)
32
+ self.search(query, field: "primary_artist_name")
33
+ end
34
+
35
+ def self.search_by_title(query)
36
+ self.search(query, field: "title")
37
+ end
38
+
39
+ def self.search_by_lyrics(query)
40
+ self.search(query, field: "lyrics")
41
+ end
42
+ end
@@ -0,0 +1,54 @@
1
+ # encoding: utf-8
2
+ module RapGenius
3
+ class Artist
4
+ include RapGenius::Client
5
+
6
+ def self.find(id)
7
+ self.new(id: id).tap { |artist| artist.document }
8
+ end
9
+
10
+ def initialize(kwargs = {})
11
+ @id = kwargs.delete(:id)
12
+ @name = kwargs.delete(:name)
13
+ @type = kwargs.delete(:type)
14
+ self.url = "artists/#{@id}"
15
+ end
16
+
17
+ def response
18
+ document["response"]["artist"]
19
+ end
20
+
21
+ def name
22
+ @name ||= response["name"]
23
+ end
24
+
25
+ def image
26
+ @image ||= response["image_url"]
27
+ end
28
+
29
+ def url
30
+ response["url"]
31
+ end
32
+
33
+ def description
34
+ @description ||= response["description"]["dom"]["children"].map do |node|
35
+ parse_description(node)
36
+ end.flatten.join("")
37
+ end
38
+
39
+ def songs
40
+ @songs ||= fetch("/artists/#{@id}/songs")["response"]["songs"].map do |song|
41
+ Song.new(
42
+ artist: Artist.new(
43
+ name: song["primary_artist"]["name"],
44
+ id: song["primary_artist"]["id"],
45
+ type: :primary
46
+ ),
47
+ title: song["title"],
48
+ id: song["id"]
49
+ )
50
+ end
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,57 @@
1
+ require 'httparty'
2
+
3
+ module RapGenius
4
+ module Client
5
+ # HTTParty client
6
+ #
7
+ # Sets some useful defaults for all of our requests.
8
+ #
9
+ # See Scraper#fetch
10
+ class HTTPClient
11
+ include HTTParty
12
+
13
+ format :json
14
+ base_uri 'https://api.rapgenius.com'
15
+ headers 'User-Agent' => "rapgenius.rb v#{RapGenius::VERSION}"
16
+ end
17
+
18
+ BASE_URL = HTTPClient.base_uri + "/".freeze
19
+
20
+ def url=(url)
21
+ unless url =~ /^https?:\/\//
22
+ @url = BASE_URL + url.gsub(/^\//, '')
23
+ else
24
+ @url = url
25
+ end
26
+ end
27
+
28
+ def document
29
+ @document ||= fetch(@url)
30
+ end
31
+
32
+ def fetch(url)
33
+ response = HTTPClient.get(url)
34
+
35
+ if response.code != 200
36
+ raise RapGenius::Error, "Received a #{response.code} HTTP response"
37
+ end
38
+
39
+ response.parsed_response
40
+ end
41
+
42
+ # Descriptions are formatted in an irritating way, encapsulating the
43
+ # various kinds of HTML tag that can be included. This parses that
44
+ # into text, but some content may be lost.
45
+ def parse_description(node)
46
+ if node.is_a? String
47
+ node
48
+ elsif node.is_a? Array
49
+ node.map { |subnode| parse_description(subnode) }
50
+ elsif node.is_a? Hash
51
+ return unless node.key? "children"
52
+ parse_description(node["children"])
53
+ end
54
+ end
55
+
56
+ end
57
+ end
@@ -1,4 +1,4 @@
1
1
  module RapGenius
2
- class ScraperError < StandardError
2
+ class Error < StandardError
3
3
  end
4
4
  end
@@ -0,0 +1,59 @@
1
+ module RapGenius
2
+ class Line
3
+ include RapGenius::Client
4
+
5
+ attr_reader :id
6
+
7
+ def self.find(id)
8
+ self.new(id: id).tap { |line| line.document }
9
+ end
10
+
11
+ def initialize(kwargs)
12
+ @id = kwargs.delete(:id)
13
+ @song = kwargs.delete(:song)
14
+ @lyric = kwargs.delete(:lyric)
15
+ self.url = "referents/#{@id}" if @id
16
+ end
17
+
18
+ def response
19
+ return nil unless @id
20
+ document["response"]["referent"]
21
+ end
22
+
23
+ def lyric
24
+ if @id
25
+ @lyric ||= response["fragment"]
26
+ else
27
+ @lyric
28
+ end
29
+ end
30
+
31
+ def annotated?
32
+ !!@id
33
+ end
34
+
35
+ alias_method :explained?, :annotated?
36
+
37
+ # A line can have multiple annotations, usually if it has a community one
38
+ # and a verified one. Ideally, these would be encapsulated into an
39
+ # Annotation class, but I don't have time for now.
40
+ def explanations
41
+ return nil unless @id
42
+ @explanation ||= response["annotations"].map do |annotation|
43
+ annotation["body"]["dom"]["children"].map do |node|
44
+ parse_description(node)
45
+ end.join("")
46
+ end.flatten
47
+ end
48
+
49
+ alias_method :annotations, :explanations
50
+
51
+ def song
52
+ if @id
53
+ @song ||= Song.find(response['song_id'])
54
+ else
55
+ @song
56
+ end
57
+ end
58
+ end
59
+ end