lastfm-cli 0.1.2-universal-darwin-9
Sign up to get free protection for your applications and to get access to all the features.
- data/README +8 -0
- data/bin/lastfm-cli +4 -0
- data/lib/lastfm.rb +465 -0
- data/spec/lastfm_spec.rb +135 -0
- metadata +54 -0
data/README
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
lastfm-cli
|
2
|
+
|
3
|
+
An interactive command-line & telnet interface to a last.fm account and audio streams. lastfm-cli lets users choose radio stations, play songs, skip songs, "love" them or "ban" them. Multiple users can control one last.fm stream (playing through the office or home sound system, for instance) as long as the computer running lastfm-cli is accessible on the local network.
|
4
|
+
|
5
|
+
lastfm-cli requires an mp3 stream player. Currently, only iTunes is supported, but support for linux players such as MPG123 is under development.
|
6
|
+
|
7
|
+
author: Daniel Choi, dhchoi@gmail.com
|
8
|
+
license: MIT
|
data/bin/lastfm-cli
ADDED
data/lib/lastfm.rb
ADDED
@@ -0,0 +1,465 @@
|
|
1
|
+
# == Synopsis
|
2
|
+
#
|
3
|
+
# hello: greets user, demonstrates command line parsing
|
4
|
+
#
|
5
|
+
# == Usage
|
6
|
+
#
|
7
|
+
# hello [OPTION] ... DIR
|
8
|
+
#
|
9
|
+
# -h, --help:
|
10
|
+
# show help
|
11
|
+
#
|
12
|
+
# REQUIRED ARGUMENTS:
|
13
|
+
#
|
14
|
+
# --username, -u [lastfm username]:
|
15
|
+
# a valid last.fm username
|
16
|
+
#
|
17
|
+
# --password, -u [lastfm password]:
|
18
|
+
# a valid last.fm password
|
19
|
+
#
|
20
|
+
# OPTIONAL ARGUMENTS:
|
21
|
+
#
|
22
|
+
# --telnetport, -t [port]:
|
23
|
+
# the port to allow telnet access to the lastfm-cli on. 8004 by default.
|
24
|
+
#
|
25
|
+
|
26
|
+
#
|
27
|
+
# note this protocal version may be outdated soon
|
28
|
+
# protocol reference
|
29
|
+
# http://code.google.com/p/thelastripper/wiki/LastFM12UnofficialDocumentation
|
30
|
+
# http://www.audioscrobbler.net/development/protocol/
|
31
|
+
require 'digest/md5'
|
32
|
+
require 'uri'
|
33
|
+
require 'net/http'
|
34
|
+
require 'yaml'
|
35
|
+
class LastFM
|
36
|
+
Handshake = %Q[http://ws.audioscrobbler.com/radio/handshake.php?version=1.1.1&platform=linux&username=%s&passwordmd5=%s&debug=0&partner=]
|
37
|
+
Handshake2 = %Q[http://post.audioscrobbler.com/?hs=true&p=1.2&c=tst&v=1.0&u=%s&t=%s&a=%s]
|
38
|
+
Tuner = %Q[http://ws.audioscrobbler.com/radio/adjust.php?session=%s&url=lastfm://%s&lang=en]
|
39
|
+
CurrentSongInfo = %Q[http://ws.audioscrobbler.com/radio/np.php?session=%s&debug=0]
|
40
|
+
Command = %Q[http://ws.audioscrobbler.com/radio/control.php?session=%s&command=%s&debug=0]
|
41
|
+
attr_accessor :session, :stream_url, :username, :station_url
|
42
|
+
def initialize(options)
|
43
|
+
@username, @password = options[:username], options[:password]
|
44
|
+
@md5_password = Digest::MD5.hexdigest(@password)
|
45
|
+
handshake
|
46
|
+
end
|
47
|
+
|
48
|
+
def last_response
|
49
|
+
@response
|
50
|
+
end
|
51
|
+
|
52
|
+
# version 1.1 protocol
|
53
|
+
def handshake
|
54
|
+
response = send_request(Handshake % [@username, @md5_password])
|
55
|
+
@session = get_property("session", response.body)
|
56
|
+
@stream_url= get_property("stream_url", response.body)
|
57
|
+
end
|
58
|
+
|
59
|
+
# version 1.2 protocol
|
60
|
+
# Currently, we'll use the old protocol.
|
61
|
+
def handshake2
|
62
|
+
timestamp = Time.now.to_i
|
63
|
+
auth_token = Digest::MD5.hexdigest(@md5_password.to_s + timestamp.to_s)
|
64
|
+
response = send_request(Handshake2 % [@username, timestamp, auth_token])
|
65
|
+
puts response.body
|
66
|
+
@session = response.body.split("\n")[1]
|
67
|
+
response
|
68
|
+
end
|
69
|
+
|
70
|
+
# tuner stations:
|
71
|
+
# lastfm://user/<yourlastfmusername>/neighbours
|
72
|
+
# lastfm://artist/<Artist>/similarartists
|
73
|
+
# lastfm://globaltags/<Tag>
|
74
|
+
# For this method, just pass in the path part that varies, e.g.
|
75
|
+
# artist/u2/similarartists
|
76
|
+
def tune(path=nil)
|
77
|
+
@station_url = path || @station_url
|
78
|
+
@response = send_request(Tuner % [@session, @station_url])
|
79
|
+
return get_property("response", @response.body) == "OK" # return false if failed
|
80
|
+
end
|
81
|
+
|
82
|
+
# Info for the current song
|
83
|
+
def current_song
|
84
|
+
@response = response = send_request(CurrentSongInfo % [@session])
|
85
|
+
# puts response.body # for debugging
|
86
|
+
streaming = get_property("streaming", response.body) == "true"
|
87
|
+
return nil unless streaming
|
88
|
+
song = LastFM::Song.new
|
89
|
+
LastFM::Song::SongAttributes.each do |attribute|
|
90
|
+
value = get_property(attribute.to_s, response.body)
|
91
|
+
if value.nil?
|
92
|
+
# something went wrong, reset song
|
93
|
+
return nil
|
94
|
+
end
|
95
|
+
song.send(attribute.to_s + "=", value.strip)
|
96
|
+
end
|
97
|
+
song
|
98
|
+
rescue
|
99
|
+
nil
|
100
|
+
end
|
101
|
+
|
102
|
+
# NOTE skip doesn't work for some reason, so we will simply restart the stream
|
103
|
+
# from the telnet client
|
104
|
+
# skip, love, ban
|
105
|
+
def action(command)
|
106
|
+
request = Command % [@session, command]
|
107
|
+
puts request
|
108
|
+
@response = send_request(request)
|
109
|
+
puts @response.body
|
110
|
+
return get_property("response", @response.body) == "OK" # return false if failed
|
111
|
+
end
|
112
|
+
|
113
|
+
# convenience method for parsing the key/value response of the LastFM protocol
|
114
|
+
def get_property(key, responsebody)
|
115
|
+
re = /^#{key}=(.*)/
|
116
|
+
matchdata = re.match(responsebody)
|
117
|
+
return unless matchdata
|
118
|
+
matchdata[1]
|
119
|
+
end
|
120
|
+
|
121
|
+
def send_request(url)
|
122
|
+
uri = URI.parse(url)
|
123
|
+
Net::HTTP.start(uri.host, uri.port) {|http| http.get(uri.request_uri)}
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
class LastFM
|
128
|
+
class Song
|
129
|
+
SongAttributes = [:station, :artist, :artist_url, :track, :track_url, :album, :album_url, :albumcover_small, :albumcover_medium, :albumcover_large, :trackduration]
|
130
|
+
attr_accessor *SongAttributes
|
131
|
+
end
|
132
|
+
|
133
|
+
class Client
|
134
|
+
attr_accessor :current_song
|
135
|
+
def initialize(lastfm, audio_output)
|
136
|
+
@lastfm = lastfm
|
137
|
+
@audio_output = audio_output
|
138
|
+
@output_buffer = []
|
139
|
+
end
|
140
|
+
def start
|
141
|
+
display_current_song
|
142
|
+
end
|
143
|
+
def parse_command(command, *args)
|
144
|
+
if command.nil?
|
145
|
+
command = "current song info"
|
146
|
+
end
|
147
|
+
args = args.join(' ').strip
|
148
|
+
puts "request: #{command} #{args}"
|
149
|
+
|
150
|
+
case command
|
151
|
+
when /^h/
|
152
|
+
@output_buffer << help
|
153
|
+
when /^sk/ #skip
|
154
|
+
skip
|
155
|
+
@output_buffer << "skipping..."
|
156
|
+
when /^st/ #stop
|
157
|
+
stop
|
158
|
+
@output_buffer << "stopping..."
|
159
|
+
when /^pl/ #play
|
160
|
+
play
|
161
|
+
@output_buffer << "playing..."
|
162
|
+
when /^v/
|
163
|
+
if args != ''
|
164
|
+
@audio_output.volume = args.to_i
|
165
|
+
@output_buffer << "setting volume to #{args}"
|
166
|
+
else
|
167
|
+
@output_buffer << "current volume: #{@audio_output.volume}"
|
168
|
+
end
|
169
|
+
when /^l/ #"love"
|
170
|
+
@lastfm.action("love")
|
171
|
+
when /^b/ # "ban"
|
172
|
+
@lastfm.action("ban")
|
173
|
+
when /^q/ #"quit"
|
174
|
+
@audio_output.stop
|
175
|
+
exit
|
176
|
+
when /^a/ # "artist"
|
177
|
+
tune("artist/#{args}/similarartists")
|
178
|
+
when /^t/ #"tags" or tags
|
179
|
+
tune("globaltags/#{args}")
|
180
|
+
when /^r/ # "recommended"
|
181
|
+
tune("user/#{@lastfm.username}/recommended/100")
|
182
|
+
when /^n/ #"neighbors"
|
183
|
+
tune("user/#{@lastfm.username}/neighbours")
|
184
|
+
# when "tune"
|
185
|
+
# tune(args.first)
|
186
|
+
else
|
187
|
+
display_current_song
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def tune(path)
|
192
|
+
@lastfm.tune(path=URI.escape(path))
|
193
|
+
@output_buffer << "tuning to #{path}"
|
194
|
+
sleep 1
|
195
|
+
skip
|
196
|
+
end
|
197
|
+
|
198
|
+
def skip
|
199
|
+
@audio_output.skip
|
200
|
+
sleep 3 # so that current song has a chance to update
|
201
|
+
# The last fm skip command via the Command url doesn't work right now
|
202
|
+
#@lastfm.action("skip")
|
203
|
+
display_current_song
|
204
|
+
end
|
205
|
+
|
206
|
+
def play
|
207
|
+
skip
|
208
|
+
end
|
209
|
+
|
210
|
+
def stop
|
211
|
+
@audio_output.stop
|
212
|
+
end
|
213
|
+
|
214
|
+
def divider
|
215
|
+
"-" * 80
|
216
|
+
end
|
217
|
+
|
218
|
+
def display_current_song
|
219
|
+
song = @lastfm.current_song
|
220
|
+
if song.nil?
|
221
|
+
# use the previous current song
|
222
|
+
return "\nNo current song\n" unless @current_song
|
223
|
+
else
|
224
|
+
@current_song = song
|
225
|
+
end
|
226
|
+
@output_buffer << "Currently Playing:"
|
227
|
+
@output_buffer << divider
|
228
|
+
%W{ artist track album station }.each do |x|
|
229
|
+
unless song.respond_to?(x)
|
230
|
+
@output_buffer = "something is wrong with the last.fm connection. no song data"
|
231
|
+
break
|
232
|
+
end
|
233
|
+
@output_buffer << x + ": " + song.send(x)
|
234
|
+
end
|
235
|
+
@output_buffer << divider
|
236
|
+
@output_buffer
|
237
|
+
end
|
238
|
+
|
239
|
+
def flush_output_buffer
|
240
|
+
temp = @output_buffer.dup
|
241
|
+
@output_buffer = []
|
242
|
+
if temp.class == String
|
243
|
+
temp
|
244
|
+
else
|
245
|
+
temp.join("\n")
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def prompt
|
250
|
+
"Press h for help\nlastfm> "
|
251
|
+
end
|
252
|
+
|
253
|
+
def help
|
254
|
+
%Q[Commands: skip
|
255
|
+
love
|
256
|
+
ban
|
257
|
+
recommended
|
258
|
+
artist [artist]
|
259
|
+
tag [tag]
|
260
|
+
tags [tag+tag]
|
261
|
+
volume
|
262
|
+
volume [1-100]
|
263
|
+
stop
|
264
|
+
play
|
265
|
+
quit
|
266
|
+
[return]
|
267
|
+
|
268
|
+
You can truncate the commands to the fewest possible
|
269
|
+
unambiguous letters.
|
270
|
+
|
271
|
+
LastFM is sometimes unresponsive. In that case,
|
272
|
+
try "skip" every few seconds until it works again.
|
273
|
+
|
274
|
+
lastfm> ]
|
275
|
+
end
|
276
|
+
|
277
|
+
require 'socket'
|
278
|
+
class TelnetClient < LastFM::Client
|
279
|
+
def initialize(lastfm, audio_output, port)
|
280
|
+
@port = port
|
281
|
+
super(lastfm, audio_output)
|
282
|
+
end
|
283
|
+
def start
|
284
|
+
server = TCPServer.new('localhost', @port)
|
285
|
+
puts
|
286
|
+
puts "Starting last.fm telnet server on port #{@port}.
|
287
|
+
|
288
|
+
Other computers can log in and control the lastfm stream on this
|
289
|
+
computer simultaneously.
|
290
|
+
|
291
|
+
For a better experience, try logging in using rlwrap
|
292
|
+
(which provides readline support)."
|
293
|
+
display_current_song
|
294
|
+
while session=server.accept
|
295
|
+
puts "A telnet session has just started."
|
296
|
+
Thread.new(session) do |my_session|
|
297
|
+
my_session.print prompt
|
298
|
+
while req = my_session.gets
|
299
|
+
puts "\n"
|
300
|
+
puts divider
|
301
|
+
puts "TELNET REQUEST"
|
302
|
+
req = req.split(" ")
|
303
|
+
if req[0] =~ /^q/ # quit
|
304
|
+
my_session.close
|
305
|
+
break
|
306
|
+
end
|
307
|
+
command = req.shift
|
308
|
+
parse_command(command, req)
|
309
|
+
puts divider
|
310
|
+
print prompt
|
311
|
+
$stdout.flush
|
312
|
+
my_session.print flush_output_buffer
|
313
|
+
|
314
|
+
unless command =~ /^h/
|
315
|
+
my_session.print "\n"
|
316
|
+
my_session.print prompt
|
317
|
+
end
|
318
|
+
end
|
319
|
+
puts "A telnet session just ended."
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
class CommandLineClient < LastFM::Client
|
325
|
+
def initialize(lastfm, audio_output)
|
326
|
+
super
|
327
|
+
end
|
328
|
+
def start
|
329
|
+
puts
|
330
|
+
#puts "starting command line lastfm client"
|
331
|
+
print prompt
|
332
|
+
$stdout.flush
|
333
|
+
while req = gets
|
334
|
+
req = req.split(" ")
|
335
|
+
puts
|
336
|
+
command = req.shift
|
337
|
+
parse_command(command, req)
|
338
|
+
print flush_output_buffer
|
339
|
+
unless command =~ /^h/
|
340
|
+
puts "\n"
|
341
|
+
print prompt
|
342
|
+
end
|
343
|
+
$stdout.flush
|
344
|
+
end
|
345
|
+
#puts "command line session ended"
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
module AudioOutput
|
351
|
+
class RAOP
|
352
|
+
end
|
353
|
+
|
354
|
+
class ITunes
|
355
|
+
require 'osx/cocoa'
|
356
|
+
OSX.require_framework 'ScriptingBridge'
|
357
|
+
|
358
|
+
def initialize(stream_url)
|
359
|
+
@itunes = OSX::SBApplication.applicationWithBundleIdentifier("com.apple.iTunes")
|
360
|
+
@stream_url = stream_url
|
361
|
+
end
|
362
|
+
|
363
|
+
def quit
|
364
|
+
@itunes.stop
|
365
|
+
end
|
366
|
+
|
367
|
+
def play
|
368
|
+
skip
|
369
|
+
end
|
370
|
+
def stop
|
371
|
+
@itunes.stop
|
372
|
+
end
|
373
|
+
def skip
|
374
|
+
# simply restart the stream
|
375
|
+
fork do
|
376
|
+
%x{open -g -a iTunes.app #{@stream_url} &}
|
377
|
+
end
|
378
|
+
end
|
379
|
+
def start
|
380
|
+
fork do
|
381
|
+
%x{open -g -a iTunes.app #{@stream_url} &}
|
382
|
+
end
|
383
|
+
end
|
384
|
+
def volume
|
385
|
+
@itunes.soundVolume
|
386
|
+
end
|
387
|
+
def volume=(value)
|
388
|
+
@itunes.soundVolume = value
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
# Code Incomplete
|
393
|
+
class MPG123
|
394
|
+
def initialize(stream_url)
|
395
|
+
@stream_url = stream_url
|
396
|
+
end
|
397
|
+
def start
|
398
|
+
# TODO put this on a separate thread
|
399
|
+
command = "mpg123 #{@stream_url}"
|
400
|
+
puts command
|
401
|
+
@pipe = IO.popen(command)
|
402
|
+
@pid = @pipe.pid
|
403
|
+
puts "PID: #{@pid}"
|
404
|
+
end
|
405
|
+
# no volume control facility
|
406
|
+
end
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
def credits
|
411
|
+
message = "lastfm-cli : a last.fm ruby client. by daniel choi / betahouse.org / Apr 2008"
|
412
|
+
puts "-" * message.length
|
413
|
+
puts message
|
414
|
+
puts "-" * message.length
|
415
|
+
end
|
416
|
+
|
417
|
+
require 'getoptlong'
|
418
|
+
require 'rdoc/usage'
|
419
|
+
def parse_options
|
420
|
+
opts = GetoptLong.new(
|
421
|
+
[ "--username", "-u", GetoptLong::REQUIRED_ARGUMENT],
|
422
|
+
[ "--password", "-p", GetoptLong::REQUIRED_ARGUMENT],
|
423
|
+
[ "--help", "-h", GetoptLong::OPTIONAL_ARGUMENT],
|
424
|
+
[ "--telnetport", "-t", GetoptLong::OPTIONAL_ARGUMENT]
|
425
|
+
)
|
426
|
+
|
427
|
+
username, password, telnetport = nil, nil, 8004
|
428
|
+
print "LastFM Username: "
|
429
|
+
username = STDIN.gets.chomp
|
430
|
+
print "LastFM Password (will remain hidden): "
|
431
|
+
`stty -echo`
|
432
|
+
password = STDIN.gets.chomp ensure `stty echo`
|
433
|
+
puts
|
434
|
+
print "Telnet port (default is 8004): "
|
435
|
+
telnetport = STDIN.gets.chomp
|
436
|
+
telnetport = if telnetport == ""
|
437
|
+
8004
|
438
|
+
else
|
439
|
+
telnetport.to_i
|
440
|
+
end
|
441
|
+
|
442
|
+
if username.nil? || password.nil?
|
443
|
+
puts
|
444
|
+
puts "* MISSING SOME REQUIRED ARGUMENTS *"
|
445
|
+
|
446
|
+
exit
|
447
|
+
end
|
448
|
+
lastfm = LastFM.new(:username => username, :password => password)
|
449
|
+
audio_output = LastFM::AudioOutput::ITunes.new(lastfm.stream_url)
|
450
|
+
audio_output.start
|
451
|
+
puts "starting iTunes radio..."
|
452
|
+
puts "Please be patient.\nIt may take some time to connect to the lastfm stream."
|
453
|
+
Thread.new do
|
454
|
+
client = LastFM::Client::TelnetClient.new(lastfm, audio_output, telnetport)
|
455
|
+
#client = LastFM::Client::TelnetClient.new(lastfm, audio_output, 8004)
|
456
|
+
client.start
|
457
|
+
end
|
458
|
+
client2 = LastFM::Client::CommandLineClient.new(lastfm, audio_output)
|
459
|
+
client2.start
|
460
|
+
|
461
|
+
end
|
462
|
+
credits # print credits
|
463
|
+
parse_options
|
464
|
+
|
465
|
+
|
data/spec/lastfm_spec.rb
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
require '../lib/lastfm'
|
2
|
+
|
3
|
+
PASSWORD = "blah"
|
4
|
+
USERNAME = "blach"
|
5
|
+
describe "LastFM (handshaking)" do
|
6
|
+
before do
|
7
|
+
Net::HTTP.stub!(:start).and_return(mock_handshake_response)
|
8
|
+
@lastfm = LastFM.new(:username => USERNAME, :password => PASSWORD)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should expose a get_property convenience method to parse the response" do
|
12
|
+
@lastfm.get_property("session", mock_handshake_response.body).should ==
|
13
|
+
"6ad90a86a8b5b126e94d0c8850b8c769"
|
14
|
+
@lastfm.get_property("stream_url", mock_handshake_response.body).should ==
|
15
|
+
"http://87.117.229.85:80/last.mp3?Session=6ad90a86a8b5b126e94d0c8850b8c769"
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should set @session after performing a handshake" do
|
19
|
+
@lastfm.session.should == "6ad90a86a8b5b126e94d0c8850b8c769"
|
20
|
+
@lastfm.stream_url.should == "http://87.117.229.85:80/last.mp3?Session=6ad90a86a8b5b126e94d0c8850b8c769"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "LastFM (handshaking with protocol 1.2)" do
|
25
|
+
before do
|
26
|
+
pending
|
27
|
+
Net::HTTP.stub!(:start).and_return(mock_handshake2_response)
|
28
|
+
@lastfm = LastFM.new(:username => USERNAME, :password => PASSWORD)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should set @session after performing a handshake" do
|
32
|
+
pending("stick with 1.1 for now")
|
33
|
+
@lastfm.handshake2
|
34
|
+
@lastfm.session.should == "78c03e8103ae6906a180771f6dc65df6"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "LastFM (tuning)" do
|
39
|
+
before do
|
40
|
+
Net::HTTP.stub!(:start).and_return(mock_handshake_response)
|
41
|
+
@lastfm = LastFM.new(:username => USERNAME, :password => PASSWORD)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should tune the lastfm station" do
|
45
|
+
Net::HTTP.stub!(:start).and_return(mock_tuner_response)
|
46
|
+
@lastfm.tune("globaltags/indie")
|
47
|
+
@lastfm.get_property("response", @lastfm.last_response.body).should == "OK"
|
48
|
+
@lastfm.get_property("stationname", @lastfm.last_response.body).should == " indie Tag Radio"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "LastFM (skipping a song)" do
|
53
|
+
before do
|
54
|
+
Net::HTTP.stub!(:start).and_return(mock_handshake_response)
|
55
|
+
@lastfm = LastFM.new(:username => USERNAME, :password => PASSWORD)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should tune the lastfm station" do
|
59
|
+
Net::HTTP.stub!(:start).and_return(mock_tuner_response)
|
60
|
+
@lastfm.action("skip").should be_true
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "LastFM (getting current song info)" do
|
65
|
+
before do
|
66
|
+
Net::HTTP.stub!(:start).and_return(mock_handshake_response)
|
67
|
+
@lastfm = LastFM.new(:username => USERNAME, :password => PASSWORD)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should get info for the current song" do
|
71
|
+
Net::HTTP.stub!(:start).and_return(mock_current_song_info)
|
72
|
+
song = @lastfm.current_song
|
73
|
+
song.artist.should == "Metric"
|
74
|
+
song.station.should == "indie Tag Radio"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def mock_handshake_response
|
79
|
+
response = mock("response")
|
80
|
+
response.stub!(:body).and_return(<<-resp
|
81
|
+
session=6ad90a86a8b5b126e94d0c8850b8c769
|
82
|
+
stream_url=http://87.117.229.85:80/last.mp3?Session=6ad90a86a8b5b126e94d0c8850b8c769
|
83
|
+
subscriber=0
|
84
|
+
framehack=0..
|
85
|
+
base_url=ws.audioscrobbler.com
|
86
|
+
base_path=/radio
|
87
|
+
info_message=
|
88
|
+
fingerprint_upload_url=http://ws.audioscrobbler.com/fingerprint/upload.php
|
89
|
+
permit_bootstrap=0
|
90
|
+
resp
|
91
|
+
)
|
92
|
+
response
|
93
|
+
end
|
94
|
+
|
95
|
+
def mock_handshake2_response
|
96
|
+
response = mock("response")
|
97
|
+
response.stub!(:body).and_return "OK
|
98
|
+
78c03e8103ae6906a180771f6dc65df6
|
99
|
+
http://post.audioscrobbler.com:80/np_1.2
|
100
|
+
http://87.117.229.205:80/protocol_1.2"
|
101
|
+
response
|
102
|
+
end
|
103
|
+
|
104
|
+
def mock_tuner_response
|
105
|
+
response = mock("response")
|
106
|
+
response.stub!(:body).and_return "response=OK
|
107
|
+
url=lastfm://globaltags/indie
|
108
|
+
stationname= indie Tag Radio
|
109
|
+
discovery=true"
|
110
|
+
response
|
111
|
+
end
|
112
|
+
|
113
|
+
def mock_current_song_info
|
114
|
+
response = mock("response")
|
115
|
+
response.stub!(:body).and_return "price=
|
116
|
+
shopname=
|
117
|
+
clickthrulink=
|
118
|
+
streaming=true
|
119
|
+
discovery=0
|
120
|
+
station= indie Tag Radio
|
121
|
+
artist=Metric
|
122
|
+
artist_url=http://www.last.fm/music/Metric
|
123
|
+
track=Hustle Rose
|
124
|
+
track_url=http://www.last.fm/music/Metric/_/Hustle+Rose
|
125
|
+
album=Old World Underground, Where Are You Now
|
126
|
+
album_url=http://www.last.fm/music/Metric/Old+World+Underground%2C+Where+Are+You+Now
|
127
|
+
albumcover_small=http://cdn.last.fm/coverart/130x130/1428771.jpg
|
128
|
+
albumcover_medium=http://cdn.last.fm/coverart/130x130/1428771.jpg
|
129
|
+
albumcover_large=http://cdn.last.fm/coverart/130x130/1428771.jpg
|
130
|
+
trackduration=332
|
131
|
+
radiomode=1
|
132
|
+
recordtoprofile="
|
133
|
+
response
|
134
|
+
end
|
135
|
+
|
metadata
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lastfm-cli
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.2
|
5
|
+
platform: universal-darwin-9
|
6
|
+
authors:
|
7
|
+
- Daniel Choi
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-04-21 00:00:00 -04:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: lastfm-cli lets users choose radio stations, play songs, skip songs, "love" them or "ban" them. Multiple users can control one last.fm stream (playing through the office or home sound system, for instance) as long as the computer running lastfm-cli is accessible on the local network. lastfm-cli requires an mp3 stream player. Currently, only iTunes is supported, but support for linux players such as MPG123 is under development.
|
17
|
+
email: dhchoi@gmail.com
|
18
|
+
executables:
|
19
|
+
- lastfm-cli
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README
|
24
|
+
files:
|
25
|
+
- lib/lastfm.rb
|
26
|
+
- README
|
27
|
+
has_rdoc: true
|
28
|
+
homepage: http://cesareborgia.com/software/lastfm-cli/
|
29
|
+
post_install_message:
|
30
|
+
rdoc_options: []
|
31
|
+
|
32
|
+
require_paths:
|
33
|
+
- lib
|
34
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - ">="
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: "0"
|
39
|
+
version:
|
40
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: "0"
|
45
|
+
version:
|
46
|
+
requirements: []
|
47
|
+
|
48
|
+
rubyforge_project:
|
49
|
+
rubygems_version: 1.1.1
|
50
|
+
signing_key:
|
51
|
+
specification_version: 2
|
52
|
+
summary: An interactive command-line and telnet interface to a last.fm account and audio streams.
|
53
|
+
test_files:
|
54
|
+
- spec/lastfm_spec.rb
|