simple_scrobbler 0.1.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.
data/README.md ADDED
@@ -0,0 +1,54 @@
1
+ SimpleScrobbler
2
+ ===============
3
+
4
+ Scrobble tracks to Last.fm without wanting to gnaw your own arm off.
5
+
6
+ Because, probably, all you want to do is just scrobble some tracks. I couldn't
7
+ find any Ruby libraries/gems that actually worked, so I took some code from
8
+ James Darling and Chris Mear's [Captor](http://github.com/james/captor) hack
9
+ and tidied it up into a self-contained package.
10
+
11
+ Usage
12
+ -----
13
+
14
+ Authorise.
15
+
16
+ require "simple_scrobbler"
17
+ ss = SimpleScrobbler.new(api_key, secret, user)
18
+ ss.fetch_session_key do |url|
19
+ puts "Go and visit #{url}"
20
+ gets
21
+ end
22
+
23
+ Tell Last.fm what you're listening to.
24
+
25
+ > This is used for realtime display of a user's currently playing track,
26
+ > and does not affect a user's musical profile.
27
+
28
+ ss.now_playing("Sex Pistols", "Anarchy in the UK")
29
+
30
+ Scrobble it!
31
+
32
+ ss.submit("Sex Pistols", "Anarchy in the UK", :length => 211)
33
+
34
+ Store the session_key for next time.
35
+
36
+ session_key = ss.session_key
37
+
38
+ And use it:
39
+
40
+ ss = SimpleScrobbler.new(api_key, secret, user, session_key)
41
+
42
+ You can pass in other information to `submit` and `now_playing`:
43
+
44
+ * `:time` (for `submit` only): Time at which the track started playing. Defaults to now
45
+ * `:length`: Length of the track in seconds (required for `submit` if the source is `P`, the default)
46
+ * `:album`: Album title
47
+ * `:track_number`: Track number
48
+ * `:mb_trackid`: MusicBrainz Track ID
49
+
50
+ If you're listening to a radio station, [you'll want to set the source](http://www.last.fm/api/submissions):
51
+
52
+ ss.source = "R" # Non-personalised broadcast (e.g. Shoutcast, BBC Radio 1).
53
+
54
+ This will also free you from needing to specify the track length on `submit`.
@@ -0,0 +1,3 @@
1
+ class SimpleScrobbler
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,218 @@
1
+ require "net/http"
2
+ require "digest/md5"
3
+ require "uri"
4
+ require "cgi"
5
+ require "rexml/document"
6
+ require "simple_scrobbler/version"
7
+
8
+ class SimpleScrobbler
9
+ CLIENT_ID = "tst"
10
+ CLIENT_VERSION = "1.0"
11
+
12
+ HandshakeError = Class.new(RuntimeError)
13
+ SubmissionError = Class.new(RuntimeError)
14
+ DataError = Class.new(RuntimeError)
15
+ SessionError = Class.new(RuntimeError)
16
+
17
+ # Instantiate a new SimpleScrobbler instance. If the session key is not
18
+ # supplied, it must be fetched using fetch_session_key before scrobbling is
19
+ # attempted.
20
+ #
21
+ # Your own API key and secret can be obtained from
22
+ # http://www.last.fm/api/account
23
+ #
24
+ def initialize(api_key, secret, user, session_key=nil)
25
+ @api_key = api_key
26
+ @secret = secret
27
+ @user = user
28
+ @session_key = session_key
29
+ @source = "P"
30
+ end
31
+
32
+ attr_reader :user, :api_key, :secret
33
+ private :api_key, :secret
34
+
35
+ def session_key
36
+ @session_key or raise SessionError, "The session key must be set or fetched"
37
+ end
38
+
39
+ # The source of the track. Required, must be one of the following codes:
40
+ # P :: Chosen by the user.
41
+ # R :: Non-personalised broadcast (e.g. Shoutcast, BBC Radio 1).
42
+ # E :: Personalised recommendation except Last.fm (e.g. Pandora, Launchcast).
43
+ # L :: Last.fm (any mode).
44
+ #
45
+ def source=(a)
46
+ unless %w[ P R E L ].include?(a)
47
+ raise DataError, "source must be one of P, R, E, L (see http://www.last.fm/api/submissions)"
48
+ end
49
+ @source = a
50
+ end
51
+
52
+ # Fetch the auth key needed for the application. This can be stored and
53
+ # supplied in the constructor on future occasions.
54
+ #
55
+ # Yields a URL which the user must visit. The block should not return until
56
+ # this is done.
57
+ #
58
+ def fetch_session_key(&blk)
59
+ doc = last_fm_web_service("method" => "auth.gettoken")
60
+ request_token = doc.value_at("//token")
61
+
62
+ yield "http://www.last.fm/api/auth/?api_key=#{api_key}&token=#{request_token}"
63
+
64
+ doc = last_fm_web_service("method" => "auth.getsession", "token" => request_token)
65
+
66
+ @session_key = doc.value_at("//key")
67
+ @user = doc.value_at("//name")
68
+
69
+ @session_key
70
+ end
71
+
72
+ # Scrobble a track.
73
+ #
74
+ # The artist and track are required parameters. Other parameters can be added
75
+ # as options:
76
+ #
77
+ # :time :: Time at which the track started playing. Defaults to now
78
+ # :length :: Length of the track in seconds (required if the source is "P", the default)
79
+ # :album :: Album title
80
+ # :track_number :: Track number
81
+ # :mb_trackid :: MusicBrainz Track ID
82
+ #
83
+ def submit(artist, track, options={})
84
+ if @source == "P" && !options[:length]
85
+ raise DataError, "Track length must be specified if source is P"
86
+ end
87
+ handshake
88
+ parameters =
89
+ generate_scrobbling_parameters(true,
90
+ options.merge(:artist => artist,
91
+ :track => track,
92
+ :source => @source,
93
+ :time => (options[:time] || Time.now).utc.to_i))
94
+ status, = split_plain_text_response(post(@submission_url, parameters))
95
+ raise SubmissionError, status unless status == "OK"
96
+ end
97
+ alias_method :scrobble, :submit
98
+
99
+ # "The Now-Playing notification is a lightweight mechanism for notifying
100
+ # Last.fm that a track has started playing. This is used for realtime display
101
+ # of a user's currently playing track, and does not affect a user's musical
102
+ # profile."
103
+ #
104
+ # The artist and track are required parameters. Other parameters can be added
105
+ # as options:
106
+ #
107
+ # :length :: Length of the track in seconds
108
+ # :album :: Album title
109
+ # :track_number :: Track number
110
+ # :mb_trackid :: MusicBrainz Track ID
111
+ #
112
+ def now_playing(artist, track, options={})
113
+ handshake
114
+ parameters =
115
+ generate_scrobbling_parameters(false,
116
+ options.merge(:artist => artist, :track => track))
117
+ status, = split_plain_text_response(post(@now_playing_url, parameters))
118
+ raise SubmissionError, status unless status == "OK"
119
+ end
120
+
121
+ # Perform handshake with the API.
122
+ #
123
+ # There is usually no need to call this, as it will be called
124
+ # automatically the first time a track is scrobbled.
125
+ #
126
+ def handshake
127
+ return if @scrobble_session_id
128
+ timestamp = Time.now.utc.to_i.to_s
129
+ authentication_token = md5(secret + timestamp)
130
+ parameters = {
131
+ "hs" => "true",
132
+ "p" => "1.2.1",
133
+ "c" => CLIENT_ID,
134
+ "v" => CLIENT_VERSION,
135
+ "u" => user,
136
+ "t" => timestamp,
137
+ "a" => authentication_token,
138
+ "api_key" => api_key,
139
+ "sk" => session_key }
140
+ body = get("http://post.audioscrobbler.com/", parameters)
141
+ status,
142
+ @scrobble_session_id,
143
+ @now_playing_url,
144
+ @submission_url, =
145
+ split_plain_text_response(body)
146
+ raise HandshakeError, status unless status == "OK"
147
+ end
148
+
149
+ private
150
+ module DocHelper
151
+ def value_at(xpath)
152
+ elements[xpath].first.to_s.strip
153
+ end
154
+ end
155
+
156
+ def last_fm_web_service(parameters={})
157
+ xml = get("http://ws.audioscrobbler.com/2.0/",
158
+ signed_parameters(parameters.merge("api_key" => api_key)))
159
+ REXML::Document.new(xml).extend(DocHelper)
160
+ end
161
+
162
+ def get(url, parameters)
163
+ query_string = sort_parameters(parameters).
164
+ map{ |k, v| "#{k}=#{CGI.escape(v)}" }.
165
+ join("&")
166
+ Net::HTTP.get_response(
167
+ URI.parse(url + "?" + query_string)
168
+ ).body
169
+ end
170
+
171
+ def post(url, parameters)
172
+ Net::HTTP.post_form(
173
+ URI.parse(url),
174
+ parameters
175
+ ).body
176
+ end
177
+
178
+ def md5(s)
179
+ Digest::MD5.hexdigest(s)
180
+ end
181
+
182
+ def signed_parameters(parameters)
183
+ sorted = sort_parameters(parameters)
184
+ signature = md5(sorted.flatten.join + secret)
185
+ parameters.merge("api_sig" => signature)
186
+ end
187
+
188
+ def split_plain_text_response(body)
189
+ body.split(/\n/)
190
+ end
191
+
192
+ def sort_parameters(parameters)
193
+ parameters.map{ |k, v| [k.to_s, v.to_s] }.sort
194
+ end
195
+
196
+ def generate_scrobbling_parameters(for_scrobble, details)
197
+ mapping = {
198
+ :artist => "a",
199
+ :track => "t",
200
+ :length => "l",
201
+ :album => "b",
202
+ :track_number => "n",
203
+ :mb_trackid => "m" }
204
+ if for_scrobble
205
+ mapping.merge!(
206
+ :source => "o",
207
+ :time => "i",
208
+ :rating => "r" )
209
+ mapping.keys.each do |k|
210
+ mapping[k] << "[0]"
211
+ end
212
+ end
213
+ mapping.inject({"s" => @scrobble_session_id}){ |hash, (key, param)|
214
+ hash[param] = details[key].to_s
215
+ hash
216
+ }
217
+ end
218
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: simple_scrobbler
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Paul Battley
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-07-10 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: mocha
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: fakeweb
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ description: Scrobble tracks to Last.fm without wanting to gnaw your own arm off.
36
+ email:
37
+ - pbattley@gmail.com
38
+ executables: []
39
+
40
+ extensions: []
41
+
42
+ extra_rdoc_files: []
43
+
44
+ files:
45
+ - lib/simple_scrobbler.rb
46
+ - lib/simple_scrobbler/version.rb
47
+ - README.md
48
+ has_rdoc: true
49
+ homepage: http://github.com/threedaymonk/simple_scrobbler
50
+ licenses: []
51
+
52
+ post_install_message:
53
+ rdoc_options: []
54
+
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: "0"
62
+ version:
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ version:
69
+ requirements: []
70
+
71
+ rubyforge_project:
72
+ rubygems_version: 1.3.5
73
+ signing_key:
74
+ specification_version: 3
75
+ summary: A simple Last.fm scrobbler that works
76
+ test_files: []
77
+