scrapescrobbler 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.
- data/.gitignore +5 -0
- data/CONTRIBUTORS +1 -0
- data/LICENSE +23 -0
- data/README.md +57 -0
- data/Rakefile +56 -0
- data/bin/dev_scrapes +4 -0
- data/bin/scrapes +11 -0
- data/lib/scrapescrobbler.rb +43 -0
- data/lib/scrapescrobbler/cli.rb +73 -0
- data/lib/scrapescrobbler/commands/authenticate.rb +10 -0
- data/lib/scrapescrobbler/commands/listen.rb +12 -0
- data/lib/scrapescrobbler/commands/stations.rb +7 -0
- data/lib/scrapescrobbler/config.rb +45 -0
- data/lib/scrapescrobbler/helpers.rb +13 -0
- data/lib/scrapescrobbler/lastfm.rb +3 -0
- data/lib/scrapescrobbler/listener.rb +55 -0
- data/lib/scrapescrobbler/models/song.rb +17 -0
- data/lib/scrapescrobbler/stations/base.rb +9 -0
- data/lib/scrapescrobbler/stations/wyep.rb +33 -0
- data/lib/scrapescrobbler/version.rb +3 -0
- data/scrapescrobbler.gemspec +34 -0
- data/spec/cli/list_spec.rb +24 -0
- data/spec/cli/listen_spec.rb +24 -0
- data/spec/listener_spec.rb +114 -0
- data/spec/models/song_spec.rb +35 -0
- data/spec/spec_helper.rb +20 -0
- metadata +213 -0
data/CONTRIBUTORS
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
* Christopher Peplin
|
data/LICENSE
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
http://www.opensource.org/licenses/mit-license.php
|
2
|
+
|
3
|
+
The MIT License
|
4
|
+
|
5
|
+
Copyright (c) 2010 Christopher Peplin
|
6
|
+
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
+
of this software and associated documentation files (the "Software"), to deal
|
9
|
+
in the Software without restriction, including without limitation the rights
|
10
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
copies of the Software, and to permit persons to whom the Software is
|
12
|
+
furnished to do so, subject to the following conditions:
|
13
|
+
|
14
|
+
The above copyright notice and this permission notice shall be included in
|
15
|
+
all copies or substantial portions of the Software.
|
16
|
+
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
23
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
scrapescrobbler -- A scrobbler for last.fm that scrapes radio station playlists
|
2
|
+
===============================================================================
|
3
|
+
|
4
|
+
## Description
|
5
|
+
|
6
|
+
Scrapescobbler is a tool to submit tracks you're listening to on an
|
7
|
+
old-fashioned radio station to last.fm.
|
8
|
+
|
9
|
+
Many radio stations offer almost real-time playlists online. For those of us who
|
10
|
+
still listen to them, scrapescrobbler will submit the tracks as they are publised
|
11
|
+
by the station to your last.fm feed. Start up scrapescrobbler when your radio is
|
12
|
+
on, and scrobble away.
|
13
|
+
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
RubyGems is the preferred method of installation:
|
18
|
+
|
19
|
+
$ [sudo] gem install scrapescrobbler
|
20
|
+
|
21
|
+
For the moment, this requires a forked version of the lastfm gem. Install it
|
22
|
+
like so:
|
23
|
+
|
24
|
+
$ git clone https://github.com/peplin/ruby-lastfm.git ruby-lastfm
|
25
|
+
$ cd ruby-lastfm
|
26
|
+
$ git checkout scrobblingv2
|
27
|
+
$ rake install
|
28
|
+
|
29
|
+
## Running
|
30
|
+
|
31
|
+
Whenever you're listening to the radio, run scrapescrobbler with the name of the
|
32
|
+
station:
|
33
|
+
|
34
|
+
$ ss listen wyep
|
35
|
+
|
36
|
+
The station must be in the list of supported stations. To see a list of
|
37
|
+
available stations, try this:
|
38
|
+
|
39
|
+
$ ss stations
|
40
|
+
|
41
|
+
## Configuration & Authentication
|
42
|
+
|
43
|
+
Before using scrapescrobbler, you must authenticate it with your Last.fm
|
44
|
+
account. Running the `listen` command will warn you that you are not
|
45
|
+
authenticated and spit out a URL - visit that and give scrapescrobbler the
|
46
|
+
go-ahead, and things should work fine. If you need to re-authenticate for any
|
47
|
+
reason, just run:
|
48
|
+
|
49
|
+
$ ss authenticate
|
50
|
+
|
51
|
+
## Contributing
|
52
|
+
|
53
|
+
Favorite station not in the list? Fork the repo on GitHub:
|
54
|
+
|
55
|
+
http://github.com/peplin/scrapescrobbler
|
56
|
+
|
57
|
+
Add a file in stations/ that provides the Station API and send a pull request.
|
data/Rakefile
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.unshift File.expand_path("../lib", __FILE__)
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'rubygems/specification'
|
6
|
+
require 'rake/gempackagetask'
|
7
|
+
require 'scrapescrobbler'
|
8
|
+
|
9
|
+
def gemspec
|
10
|
+
@gemspec ||= begin
|
11
|
+
file = File.expand_path('../scrapescrobbler.gemspec', __FILE__)
|
12
|
+
eval(File.read(file), binding, file)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
begin
|
17
|
+
require 'rspec/core/rake_task'
|
18
|
+
|
19
|
+
task :default => :spec
|
20
|
+
|
21
|
+
desc "Run specs"
|
22
|
+
RSpec::Core::RakeTask.new do |t|
|
23
|
+
t.rspec_opts = %w(-fs --color)
|
24
|
+
end
|
25
|
+
|
26
|
+
namespace :spec do
|
27
|
+
task :clean do
|
28
|
+
if sudo?
|
29
|
+
system "sudo rm -rf #{File.expand_path('../tmp', __FILE__)}"
|
30
|
+
else
|
31
|
+
rm_rf 'tmp'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
desc "Run the full spec suite"
|
36
|
+
task :full => ["clean", "spec"]
|
37
|
+
end
|
38
|
+
rescue LoadError
|
39
|
+
task :spec do
|
40
|
+
abort "Run `gem install rspec` to be able to run specs"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
Rake::GemPackageTask.new(gemspec) do |pkg|
|
45
|
+
pkg.gem_spec = gemspec
|
46
|
+
end
|
47
|
+
|
48
|
+
desc "install the gem locally"
|
49
|
+
task :install => [:package] do
|
50
|
+
sh %{gem install pkg/#{gemspec.name}-#{gemspec.version}}
|
51
|
+
end
|
52
|
+
|
53
|
+
desc "validate the gemspec"
|
54
|
+
task :gemspec do
|
55
|
+
gemspec.validate
|
56
|
+
end
|
data/bin/dev_scrapes
ADDED
data/bin/scrapes
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
begin
|
3
|
+
require 'scrapescrobbler'
|
4
|
+
rescue LoadError
|
5
|
+
if File.symlink? __FILE__
|
6
|
+
require File.dirname(File.readlink(__FILE__)) + '/../lib/scrapescrobbler'
|
7
|
+
else
|
8
|
+
require File.dirname(__FILE__) + '/../lib/scrapescrobbler'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
Scrapescrobbler::CLI.invoke
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'dm-core'
|
2
|
+
require 'dm-migrations'
|
3
|
+
require 'dm-validations'
|
4
|
+
require 'Getopt/Declare'
|
5
|
+
require 'active_support/all'
|
6
|
+
|
7
|
+
require File.join(File.dirname(__FILE__), 'scrapescrobbler', 'config')
|
8
|
+
require File.join(File.dirname(__FILE__), 'scrapescrobbler', 'helpers')
|
9
|
+
require File.join(File.dirname(__FILE__), 'scrapescrobbler', 'cli')
|
10
|
+
require File.join(File.dirname(__FILE__), 'scrapescrobbler', 'listener')
|
11
|
+
|
12
|
+
DB_NAME = 'sqlite://' + (defined?(TEST_MODE) ? '/tmp/db' : Scrapescrobbler::Config['database_file'])
|
13
|
+
DataMapper::Logger.new($stdout, :debug) if defined?(TEST_MODE)
|
14
|
+
DataMapper.setup(:default, DB_NAME)
|
15
|
+
|
16
|
+
Dir["#{File.dirname(__FILE__)}/scrapescrobbler/models/*.rb"].each do |path|
|
17
|
+
require path
|
18
|
+
end
|
19
|
+
|
20
|
+
Dir["#{File.dirname(__FILE__)}/scrapescrobbler/stations/*.rb"].each do |path|
|
21
|
+
require path
|
22
|
+
end
|
23
|
+
|
24
|
+
Dir["#{File.dirname(__FILE__)}/scrapescrobbler/commands/*.rb"].each do |path|
|
25
|
+
require path
|
26
|
+
end
|
27
|
+
|
28
|
+
DataMapper.finalize
|
29
|
+
DataMapper.auto_upgrade!
|
30
|
+
|
31
|
+
module Scrapescrobbler
|
32
|
+
extend self
|
33
|
+
|
34
|
+
class AlreadyRunning < StandardError
|
35
|
+
def message
|
36
|
+
"Scrapescrobbler is already running"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
CLI.args = Getopt::Declare.new(<<-EOF)
|
41
|
+
#{CLI::USAGE}
|
42
|
+
EOF
|
43
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Scrapescrobbler
|
2
|
+
module CLI
|
3
|
+
attr_accessor :args
|
4
|
+
extend self
|
5
|
+
|
6
|
+
USAGE = <<-EOF
|
7
|
+
|
8
|
+
Scrapescrobbler - Old-Fashioned Radio Station Scrobbling
|
9
|
+
|
10
|
+
Usage: #{File.basename $0} COMMAND [OPTIONS] [ARGS...]
|
11
|
+
|
12
|
+
where COMMAND is one of:
|
13
|
+
* listen - start scrobbling a radio station's playlist
|
14
|
+
usage: ss listen [STATION]
|
15
|
+
* stations - list all available stations
|
16
|
+
usage: ss stations
|
17
|
+
* authenticate - re-authenticate with last.fm
|
18
|
+
usage: ss authenticate
|
19
|
+
* configure - write out a config file. print path to config file.
|
20
|
+
|
21
|
+
OTHER OPTIONS
|
22
|
+
-h, --help Display this help
|
23
|
+
|
24
|
+
Submit bugs and feature requests to http://github.com/peplin/scrapescrobbler/issues
|
25
|
+
EOF
|
26
|
+
|
27
|
+
def parse arguments
|
28
|
+
args.parse arguments
|
29
|
+
end
|
30
|
+
|
31
|
+
def invoke
|
32
|
+
args['-h'] ? say(USAGE) : invoke_command_if_valid
|
33
|
+
end
|
34
|
+
|
35
|
+
def commands
|
36
|
+
Scrapescrobbler::CLI::USAGE.scan(/\* \w+/).map{|s| s.gsub(/\* /, '')}
|
37
|
+
end
|
38
|
+
|
39
|
+
def say *something
|
40
|
+
puts *something
|
41
|
+
end
|
42
|
+
|
43
|
+
def invoke_command_if_valid
|
44
|
+
command = args.unused.shift
|
45
|
+
set_global_options
|
46
|
+
case (valid = commands.select{|name| name =~ %r|^#{command}|}).size
|
47
|
+
when 0 then say "Invalid command: #{command}"
|
48
|
+
when 1 then send valid[0]
|
49
|
+
else
|
50
|
+
say "Ambiguous command: #{command}" if command
|
51
|
+
say(USAGE)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# currently just sets whether output should be rounded to 15 min intervals
|
56
|
+
def set_global_options
|
57
|
+
Scrapescrobbler::Entry.round = true if args['-r']
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def unused_args
|
63
|
+
args.unused.join(' ')
|
64
|
+
end
|
65
|
+
|
66
|
+
def ask_user question
|
67
|
+
return true if args['-y']
|
68
|
+
print question
|
69
|
+
$stdin.gets =~ /\Aye?s?\Z/i
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Scrapescrobbler
|
2
|
+
module CLI
|
3
|
+
def authenticate
|
4
|
+
lastfm = Lastfm.new Config['api_key'], Config['api_secret']
|
5
|
+
token = lastfm.auth.get_token
|
6
|
+
say "Go to http://www.last.fm/api/auth/?api_key=#{Config['api_key']}&token=#{token} to allow scrapescrobbler to access your last.fm account."
|
7
|
+
Config.update! 'token' => token
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Scrapescrobbler
|
2
|
+
module Config
|
3
|
+
extend self
|
4
|
+
PATH = ENV['SCRAPESCROBBLER_CONFIG_FILE'] || File.join(ENV['HOME'], '.scrapescrobbler.yml')
|
5
|
+
|
6
|
+
# Application defaults.
|
7
|
+
#
|
8
|
+
# These are written to a config file by invoking:
|
9
|
+
# <code>
|
10
|
+
# t configure
|
11
|
+
# </code>
|
12
|
+
def defaults
|
13
|
+
{
|
14
|
+
# Path to the sqlite db
|
15
|
+
'database_file' => "#{ENV['HOME']}/.scrapescrobbler.db",
|
16
|
+
'api_key' => "73af75e07cd58bfe66d13af9371b9504",
|
17
|
+
'api_secret' => "85b1b415e9c2b653e84bd1304d4dd9fa",
|
18
|
+
'token' => nil
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def [](key)
|
23
|
+
overrides = File.exist?(PATH) ? YAML.load(File.read(PATH)) : {}
|
24
|
+
defaults.merge(overrides)[key]
|
25
|
+
rescue => e
|
26
|
+
puts "invalid config file"
|
27
|
+
puts e.message
|
28
|
+
defaults[key]
|
29
|
+
end
|
30
|
+
|
31
|
+
def configure!
|
32
|
+
unless File.exist?(PATH)
|
33
|
+
File.open(PATH, 'w') do |fh|
|
34
|
+
fh.puts(defaults.to_yaml)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def update! values
|
40
|
+
File.open(PATH, 'w') do |fh|
|
41
|
+
fh.puts(defaults.update(values).to_yaml)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'lastfm'
|
2
|
+
|
3
|
+
module Scrapescrobbler
|
4
|
+
class Listener
|
5
|
+
attr_accessor :lastfm, :station, :started
|
6
|
+
|
7
|
+
def initialize(station)
|
8
|
+
@lastfm = Listener.authenticate
|
9
|
+
@station = ::Scrapescrobbler::Stations.const_get(station.classify)
|
10
|
+
end
|
11
|
+
|
12
|
+
def listen
|
13
|
+
raise if @started
|
14
|
+
@started = DateTime.now
|
15
|
+
end
|
16
|
+
|
17
|
+
def update
|
18
|
+
playlist = Stations::Wyep::playlist
|
19
|
+
if playlist and playlist.length > 0 then
|
20
|
+
@now_playing = playlist.first
|
21
|
+
playlist.each do |song|
|
22
|
+
saved = song.save
|
23
|
+
self.scrobble(song) if saved
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def scrobble song
|
29
|
+
puts "Scrobbling #{song} @ #{Time.now}"
|
30
|
+
if song.album then
|
31
|
+
@lastfm.track.scrobble song.artist, song.title, song.album, Time.now.utc.to_i
|
32
|
+
else
|
33
|
+
@lastfm.track.scrobble song.artist, song.title, Time.now.utc.to_i
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def now_playing
|
38
|
+
update if not @now_playing
|
39
|
+
@now_playing
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def self.authenticate
|
45
|
+
if not Config['token']
|
46
|
+
CLI.authenticate
|
47
|
+
exit
|
48
|
+
else
|
49
|
+
lastfm = Lastfm.new Config['api_key'], Config['api_secret']
|
50
|
+
lastfm.session = lastfm.auth.get_session(Config['token'])
|
51
|
+
lastfm
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Scrapescrobbler
|
2
|
+
class Song
|
3
|
+
include DataMapper::Resource
|
4
|
+
property :id, Serial
|
5
|
+
property :time, DateTime, :required => true
|
6
|
+
property :artist, String, :required => true
|
7
|
+
property :title, String, :required => true
|
8
|
+
property :album, String
|
9
|
+
property :station, String, :required => true
|
10
|
+
|
11
|
+
validates_uniqueness_of :time, :scope => :station
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
"#{artist} - #{title}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'hpricot'
|
2
|
+
require 'open-uri'
|
3
|
+
|
4
|
+
module Scrapescrobbler
|
5
|
+
module Stations
|
6
|
+
class Wyep < Station
|
7
|
+
NAME = "WYEP"
|
8
|
+
FREQUENCY = "91.3"
|
9
|
+
FM = true
|
10
|
+
PLAYLIST_URL = "http://wyep.org/playlist/"
|
11
|
+
|
12
|
+
def self.playlist
|
13
|
+
doc = open(PLAYLIST_URL) { |f| Hpricot(f) }
|
14
|
+
|
15
|
+
table = doc.at("div#center_right")
|
16
|
+
breaks = table/"tr"/"td"
|
17
|
+
songs = []
|
18
|
+
[table/"td.now_playing", table/"td.odd_row" - breaks, table/"td.even_row" - breaks].each do |row|
|
19
|
+
next unless row.length != 0
|
20
|
+
playlist_entries = row.collect do |cell|
|
21
|
+
cell.inner_html
|
22
|
+
end
|
23
|
+
songs = playlist_entries.chunk(5).collect do |entry|
|
24
|
+
time = entry[0] == "NOW PLAYING" ? Time.now : Time.parse(entry[0])
|
25
|
+
Scrapescrobbler::Song.new :time => time, :artist => entry[1],
|
26
|
+
:title => entry[2], :album => entry[3], :station => Wyep::NAME
|
27
|
+
end
|
28
|
+
end
|
29
|
+
songs
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib/', __FILE__)
|
3
|
+
$:.unshift lib unless $:.include?(lib)
|
4
|
+
|
5
|
+
require 'scrapescrobbler/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "scrapescrobbler"
|
9
|
+
s.version = Scrapescrobbler::VERSION
|
10
|
+
s.platform = Gem::Platform::RUBY
|
11
|
+
s.authors = ["Christopher Peplin"]
|
12
|
+
s.email = %q{chris.peplin@rhubarbtech.com}
|
13
|
+
s.homepage = "http://github.com/peplin/scrapescrobbler"
|
14
|
+
s.summary = %q{Radio station last.fm scrobbler}
|
15
|
+
s.description = %q{Scrapescobbler is a tool to submit tracks you're listening to on an old-fashioned radio station to last.fm.}
|
16
|
+
|
17
|
+
s.required_rubygems_version = ">= 1.3.6"
|
18
|
+
|
19
|
+
s.add_development_dependency "rspec"
|
20
|
+
s.add_development_dependency "mocha"
|
21
|
+
s.add_dependency "hpricot"
|
22
|
+
s.add_dependency "lastfm"
|
23
|
+
s.add_dependency "dm-core"
|
24
|
+
s.add_dependency "dm-sqlite-adapter"
|
25
|
+
s.add_dependency "dm-migrations"
|
26
|
+
s.add_dependency "dm-validations"
|
27
|
+
s.add_dependency "activesupport", ">= 3.0.0"
|
28
|
+
|
29
|
+
s.files = `git ls-files`.split("\n")
|
30
|
+
s.test_files = `git ls-files -- spec/*`.split("\n")
|
31
|
+
s.executables = `git ls-files bin/*`.split("\n").map{ |f| File.basename(f) }
|
32
|
+
s.default_executable = "scrapescrobble"
|
33
|
+
s.require_paths = ["lib"]
|
34
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'scrapescrobbler'
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
module Scrapescrobbler
|
5
|
+
describe CLI do
|
6
|
+
include HelperMethods
|
7
|
+
|
8
|
+
describe "list" do
|
9
|
+
before :each do
|
10
|
+
mockit
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should list all available stations" do
|
14
|
+
invoke 'stations'
|
15
|
+
$stdin.should include(Stations.constants.collect do |const|
|
16
|
+
station = Stations.const_get(const)
|
17
|
+
if station != Stations::Station then
|
18
|
+
station::NAME
|
19
|
+
end
|
20
|
+
end.compact)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'scrapescrobbler'
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
module Scrapescrobbler
|
5
|
+
describe CLI do
|
6
|
+
include HelperMethods
|
7
|
+
|
8
|
+
describe "listen" do
|
9
|
+
before :each do
|
10
|
+
mockit
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should complain without a station"
|
14
|
+
|
15
|
+
it "should complain with more than one station"
|
16
|
+
|
17
|
+
it "should start a last.fm session"
|
18
|
+
|
19
|
+
it "should not return"
|
20
|
+
|
21
|
+
it "should enable scrobbling for the station"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'scrapescrobbler'
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'lastfm'
|
4
|
+
|
5
|
+
module Scrapescrobbler
|
6
|
+
describe Listener do
|
7
|
+
include HelperMethods
|
8
|
+
|
9
|
+
describe "with an instance" do
|
10
|
+
before :each do
|
11
|
+
mockit
|
12
|
+
::Lastfm::MethodCategory::Auth.any_instance.stubs(
|
13
|
+
:get_session).returns("A session")
|
14
|
+
::Lastfm::MethodCategory::Auth.any_instance.stubs(
|
15
|
+
:get_token).returns("a token")
|
16
|
+
@scrobble_stub = ::Lastfm::MethodCategory::Track.any_instance.stubs(
|
17
|
+
:scrobble).returns("Good job!")
|
18
|
+
@station = "wyep"
|
19
|
+
@listener = Listener.new @station
|
20
|
+
@song = Song.new(:time => Time.now, :station => @station,
|
21
|
+
:title => "The Best Song Ever", :artist => 'Price')
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should have a station" do
|
25
|
+
@listener.station.should eq(Stations::Wyep)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should have a last.fm instance" do
|
29
|
+
@listener.lastfm.should be_an_instance_of(::Lastfm)
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "that is inactive" do
|
33
|
+
it "should not have a start time" do
|
34
|
+
@listener.started.should eq(nil)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should not have a last.fm session" do
|
38
|
+
@listener.lastfm.auth.should_not respond_to(:session)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "that is active" do
|
43
|
+
before :each do
|
44
|
+
@listener.listen
|
45
|
+
Stations::Wyep.stubs(:playlist).returns([@song])
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should have a start time" do
|
49
|
+
@listener.should respond_to(:started)
|
50
|
+
@listener.started.should be_an_instance_of(DateTime)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should have a last.fm session" do
|
54
|
+
@listener.lastfm.should respond_to(:auth)
|
55
|
+
@listener.lastfm.should respond_to(:session)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should have a current song" do
|
59
|
+
@listener.should respond_to(:now_playing)
|
60
|
+
@listener.now_playing.should be_an_instance_of(Song)
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "and when updated" do
|
64
|
+
it "should check the station's playlist" do
|
65
|
+
@listener.station.expects(:playlist)
|
66
|
+
@listener.update
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "and there is a new song to scrobble" do
|
70
|
+
|
71
|
+
it "should create a new Song" do
|
72
|
+
proc { @listener.update }.should change(Song, :count)
|
73
|
+
song = Song.first :time => @song.time, :station => @song.station
|
74
|
+
song.title.should eq(@song.title)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should scrobble the song" do
|
78
|
+
@scrobble_stub.expects(:scrobble).with(@song.artist, @song.title)
|
79
|
+
@listener.update
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should have a current song that matches the new song" do
|
83
|
+
@listener.update
|
84
|
+
@listener.now_playing.should eq(@song)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe "and there is not a new song to scrobble" do
|
89
|
+
before do
|
90
|
+
@song.save
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should not create a new Song" do
|
94
|
+
@listener.update
|
95
|
+
proc { @listener.update }.should_not change(Song, :count)
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should not scrobble the song" do
|
99
|
+
@scrobble_stub.expects(:scrobble).with(@song.artist,
|
100
|
+
@song.title).never
|
101
|
+
@listener.update
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should not update the current song" do
|
105
|
+
@listener.update
|
106
|
+
proc { @listener.update }.should_not change(@listener,
|
107
|
+
:now_playing)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'scrapescrobbler'
|
2
|
+
|
3
|
+
module Scrapescrobbler
|
4
|
+
describe Song do
|
5
|
+
before do
|
6
|
+
@song = Song.new :artist => "Prince", :title => "Purple Rain",
|
7
|
+
:time => Time.now
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should have a time" do
|
11
|
+
@song.should respond_to :time
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should have an artist" do
|
15
|
+
@song.should respond_to :artist
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should have a title" do
|
19
|
+
@song.should respond_to :title
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should have a station" do
|
23
|
+
@song.should respond_to :station
|
24
|
+
end
|
25
|
+
|
26
|
+
it "shouldn't save without a station" do
|
27
|
+
@song.save.should == false
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should require a station" do
|
31
|
+
@song.station = "wyep"
|
32
|
+
@song.save.should == true
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'scrapescrobbler'
|
2
|
+
|
3
|
+
module Scrapescrobbler
|
4
|
+
module HelperMethods
|
5
|
+
def invoke command
|
6
|
+
Scrapescrobbler::CLI.parse command
|
7
|
+
Scrapescrobbler::CLI.invoke
|
8
|
+
end
|
9
|
+
|
10
|
+
def mockit
|
11
|
+
$stdout = StringIO.new
|
12
|
+
$stdin = StringIO.new
|
13
|
+
DataMapper.auto_migrate!
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
RSpec.configure do |config|
|
19
|
+
config.mock_with :mocha
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,213 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: scrapescrobbler
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
version: "0.1"
|
9
|
+
platform: ruby
|
10
|
+
authors:
|
11
|
+
- Christopher Peplin
|
12
|
+
autorequire:
|
13
|
+
bindir: bin
|
14
|
+
cert_chain: []
|
15
|
+
|
16
|
+
date: 2010-12-06 00:00:00 -05:00
|
17
|
+
default_executable: scrapescrobble
|
18
|
+
dependencies:
|
19
|
+
- !ruby/object:Gem::Dependency
|
20
|
+
name: rspec
|
21
|
+
prerelease: false
|
22
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
23
|
+
none: false
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
version: "0"
|
30
|
+
type: :development
|
31
|
+
version_requirements: *id001
|
32
|
+
- !ruby/object:Gem::Dependency
|
33
|
+
name: mocha
|
34
|
+
prerelease: false
|
35
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
36
|
+
none: false
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
segments:
|
41
|
+
- 0
|
42
|
+
version: "0"
|
43
|
+
type: :development
|
44
|
+
version_requirements: *id002
|
45
|
+
- !ruby/object:Gem::Dependency
|
46
|
+
name: hpricot
|
47
|
+
prerelease: false
|
48
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
segments:
|
54
|
+
- 0
|
55
|
+
version: "0"
|
56
|
+
type: :runtime
|
57
|
+
version_requirements: *id003
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: lastfm
|
60
|
+
prerelease: false
|
61
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
segments:
|
67
|
+
- 0
|
68
|
+
version: "0"
|
69
|
+
type: :runtime
|
70
|
+
version_requirements: *id004
|
71
|
+
- !ruby/object:Gem::Dependency
|
72
|
+
name: dm-core
|
73
|
+
prerelease: false
|
74
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
segments:
|
80
|
+
- 0
|
81
|
+
version: "0"
|
82
|
+
type: :runtime
|
83
|
+
version_requirements: *id005
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: dm-sqlite-adapter
|
86
|
+
prerelease: false
|
87
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
segments:
|
93
|
+
- 0
|
94
|
+
version: "0"
|
95
|
+
type: :runtime
|
96
|
+
version_requirements: *id006
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: dm-migrations
|
99
|
+
prerelease: false
|
100
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
101
|
+
none: false
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
segments:
|
106
|
+
- 0
|
107
|
+
version: "0"
|
108
|
+
type: :runtime
|
109
|
+
version_requirements: *id007
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: dm-validations
|
112
|
+
prerelease: false
|
113
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
114
|
+
none: false
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
segments:
|
119
|
+
- 0
|
120
|
+
version: "0"
|
121
|
+
type: :runtime
|
122
|
+
version_requirements: *id008
|
123
|
+
- !ruby/object:Gem::Dependency
|
124
|
+
name: activesupport
|
125
|
+
prerelease: false
|
126
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
127
|
+
none: false
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
segments:
|
132
|
+
- 3
|
133
|
+
- 0
|
134
|
+
- 0
|
135
|
+
version: 3.0.0
|
136
|
+
type: :runtime
|
137
|
+
version_requirements: *id009
|
138
|
+
description: Scrapescobbler is a tool to submit tracks you're listening to on an old-fashioned radio station to last.fm.
|
139
|
+
email: chris.peplin@rhubarbtech.com
|
140
|
+
executables:
|
141
|
+
- dev_scrapes
|
142
|
+
- scrapes
|
143
|
+
extensions: []
|
144
|
+
|
145
|
+
extra_rdoc_files: []
|
146
|
+
|
147
|
+
files:
|
148
|
+
- .gitignore
|
149
|
+
- CONTRIBUTORS
|
150
|
+
- LICENSE
|
151
|
+
- README.md
|
152
|
+
- Rakefile
|
153
|
+
- bin/dev_scrapes
|
154
|
+
- bin/scrapes
|
155
|
+
- lib/scrapescrobbler.rb
|
156
|
+
- lib/scrapescrobbler/cli.rb
|
157
|
+
- lib/scrapescrobbler/commands/authenticate.rb
|
158
|
+
- lib/scrapescrobbler/commands/listen.rb
|
159
|
+
- lib/scrapescrobbler/commands/stations.rb
|
160
|
+
- lib/scrapescrobbler/config.rb
|
161
|
+
- lib/scrapescrobbler/helpers.rb
|
162
|
+
- lib/scrapescrobbler/lastfm.rb
|
163
|
+
- lib/scrapescrobbler/listener.rb
|
164
|
+
- lib/scrapescrobbler/models/song.rb
|
165
|
+
- lib/scrapescrobbler/stations/base.rb
|
166
|
+
- lib/scrapescrobbler/stations/wyep.rb
|
167
|
+
- lib/scrapescrobbler/version.rb
|
168
|
+
- scrapescrobbler.gemspec
|
169
|
+
- spec/cli/list_spec.rb
|
170
|
+
- spec/cli/listen_spec.rb
|
171
|
+
- spec/listener_spec.rb
|
172
|
+
- spec/models/song_spec.rb
|
173
|
+
- spec/spec_helper.rb
|
174
|
+
has_rdoc: true
|
175
|
+
homepage: http://github.com/peplin/scrapescrobbler
|
176
|
+
licenses: []
|
177
|
+
|
178
|
+
post_install_message:
|
179
|
+
rdoc_options: []
|
180
|
+
|
181
|
+
require_paths:
|
182
|
+
- lib
|
183
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
184
|
+
none: false
|
185
|
+
requirements:
|
186
|
+
- - ">="
|
187
|
+
- !ruby/object:Gem::Version
|
188
|
+
segments:
|
189
|
+
- 0
|
190
|
+
version: "0"
|
191
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
192
|
+
none: false
|
193
|
+
requirements:
|
194
|
+
- - ">="
|
195
|
+
- !ruby/object:Gem::Version
|
196
|
+
segments:
|
197
|
+
- 1
|
198
|
+
- 3
|
199
|
+
- 6
|
200
|
+
version: 1.3.6
|
201
|
+
requirements: []
|
202
|
+
|
203
|
+
rubyforge_project:
|
204
|
+
rubygems_version: 1.3.7
|
205
|
+
signing_key:
|
206
|
+
specification_version: 3
|
207
|
+
summary: Radio station last.fm scrobbler
|
208
|
+
test_files:
|
209
|
+
- spec/cli/list_spec.rb
|
210
|
+
- spec/cli/listen_spec.rb
|
211
|
+
- spec/listener_spec.rb
|
212
|
+
- spec/models/song_spec.rb
|
213
|
+
- spec/spec_helper.rb
|