LOLastfm 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/LOLastfm.gemspec CHANGED
@@ -16,5 +16,4 @@ Gem::Specification.new {|s|
16
16
 
17
17
  s.add_dependency 'lastfm'
18
18
  s.add_dependency 'taglib-ruby'
19
- s.add_dependency 'call-me'
20
19
  }
data/README.md CHANGED
@@ -1,2 +1,51 @@
1
- LOLastfm - blablablabal
2
- =======================
1
+ LOLastfm - LOL a scrobbler
2
+ ==========================
3
+ LOLastfm is a modular and extensible last.fm scrobbler.
4
+
5
+ It supports a virtually infinite array of music players and has a cool server-client
6
+ interface that lets you manage music related features seamlessly.
7
+
8
+ For instance, you can ask it the current playing song without having to relay on client specific
9
+ shims, or by including the glyr extension you can ask it to give you the lyrics of the currently
10
+ playing song.
11
+
12
+ How to configure
13
+ ----------------
14
+ Before being able to use LOLastfm you have to allow access to it on last.fm, just run `LOLastfm -a`
15
+ and follow what it says, then create a file at `~/.LOLastfm/config` and put the session id it gave you
16
+ back.
17
+
18
+ You have to then tell LOLastfm what music player you want to be used, you can do it dynamically with
19
+ `LOLastfm-do --use player:path'.
20
+
21
+ ```ruby
22
+ session 'heregoesthesession'
23
+
24
+ use :moc
25
+ ```
26
+
27
+ The example above is if you're using moc.
28
+
29
+ You can also hook to certain events to, for example, change the attributes of a song before it's sent.
30
+
31
+ This allows for some neat features, like easily scrobbling from a radio, the following example shows
32
+ how to scrobble songs from trance.fm.
33
+
34
+ ```ruby
35
+ %w(listened love now_playing).each {|name|
36
+ on name do |song|
37
+ next if song.artist || !song.stream?
38
+
39
+ if song.comment && song.comment.include?('trance.fm')
40
+ next :stop if song.title.include?('trance.fm') || song.title.start_with?('Trance - FM-')
41
+
42
+ song.artist, song.title = song.title.split(/ - /, 2)
43
+ song.length = 60 * 1337
44
+
45
+ next
46
+ end
47
+
48
+ :stop
49
+ end
50
+ }
51
+ ```
data/bin/LOLastfm CHANGED
@@ -24,7 +24,7 @@ OptionParser.new do |o|
24
24
  end.parse!
25
25
 
26
26
  EM.run {
27
- d = LOLastfm.load(ARGV.first || '~/.LOLastfm.rc')
27
+ d = LOLastfm.load(ARGV.first || '~/.LOLastfm/config')
28
28
  d.start
29
29
 
30
30
  %w[INT KILL].each {|sig|
data/bin/LOLastfm-cmus CHANGED
@@ -4,7 +4,7 @@ require 'json'
4
4
 
5
5
  args = Hash[*ARGV]
6
6
 
7
- UNIXSocket.new(File.expand_path('~/.LOLastfm.socket')).tap {|socket|
7
+ UNIXSocket.new(File.expand_path('~/.LOLastfm/socket')).tap {|socket|
8
8
  if args['url']
9
9
  socket.puts [:hint, [:stream, args['title'], args['comment']]].to_json
10
10
  end
@@ -0,0 +1,22 @@
1
+ #! /usr/bin/env ruby
2
+ require 'json'
3
+ require 'socket'
4
+
5
+ socket = UNIXSocket.new(File.expand_path(ARGV.empty? ? '~/.LOLastfm/socket' : ARGV.first))
6
+ socket.puts [:commands, :glyr].to_json
7
+
8
+ if STDOUT.tty?
9
+ require 'ncursesw'
10
+
11
+ abort 'ncurses UI not implemented yet, redirect the output to something'
12
+ else
13
+ socket.puts [:lyrics?].to_json
14
+
15
+ response = JSON.parse(socket.readline)
16
+
17
+ if response.first
18
+ puts response.first['data']
19
+ else
20
+ exit 1
21
+ end
22
+ end
@@ -0,0 +1,14 @@
1
+ #! /usr/bin/env ruby
2
+ require 'json'
3
+ require 'socket'
4
+
5
+ socket = UNIXSocket.new(File.expand_path(ARGV.empty? ? '~/.LOLastfm/socket' : ARGV.first))
6
+ socket.puts [:now_playing?].to_json
7
+
8
+ response = JSON.parse(?[ + socket.readline + ?]).first
9
+
10
+ exit 1 if response.nil?
11
+
12
+ %w[track title artist album].each {|name|
13
+ puts "#{name} #{response[name]}" if response[name]
14
+ }
@@ -6,7 +6,7 @@ require 'json'
6
6
  options = {}
7
7
 
8
8
  OptionParser.new do |o|
9
- options[:socket] = '~/.LOLastfm.socket'
9
+ options[:socket] = '~/.LOLastfm/socket'
10
10
  options[:song] = {}
11
11
 
12
12
  o.on '--listened', 'enable listened sending' do
@@ -48,6 +48,14 @@ OptionParser.new do |o|
48
48
  o.on '-i', '--id ID', 'the MusicBrainz id of the song' do |value|
49
49
  options[:song][:id] = value
50
50
  end
51
+
52
+ o.on '-u', '--use USE', 'the string for the checker' do |value|
53
+ options[:use] = value
54
+ end
55
+
56
+ o.on '-u', '--commands COMMANDS', 'the string for the command library' do |value|
57
+ options[:commands] = value
58
+ end
51
59
  end.parse!
52
60
 
53
61
  if ARGV.first
@@ -56,12 +64,18 @@ end
56
64
 
57
65
  socket = UNIXSocket.new(File.expand_path(options[:socket]))
58
66
 
59
- if options[:now_playing]
60
- socket.puts [:now_playing, options[:song]].to_json
61
- elsif options[:love]
62
- socket.puts [:love, options[:song].empty? ? nil : options[:song]].to_json
67
+ if options[:use]
68
+ socket.puts [:use, options[:use].split(':')].to_json
69
+ elsif options[:commands]
70
+ socket.puts [:commands, options[:commands].split(':')].to_json
63
71
  else
64
- socket.puts [:listened, options[:song]].to_json
72
+ if options[:now_playing]
73
+ socket.puts [:now_playing, options[:song]].to_json
74
+ elsif options[:love]
75
+ socket.puts [:love, options[:song].empty? ? nil : options[:song]].to_json
76
+ else
77
+ socket.puts [:listened, options[:song]].to_json
78
+ end
65
79
  end
66
80
 
67
81
  socket.flush
data/lib/LOLastfm.rb CHANGED
@@ -10,7 +10,6 @@
10
10
 
11
11
  require 'eventmachine'
12
12
  require 'lastfm'
13
- require 'call-me/memoize'
14
13
 
15
14
  class LOLastfm
16
15
  def self.load (path)
@@ -33,31 +32,45 @@ class LOLastfm
33
32
  commands[name.to_sym.downcase] = block
34
33
  end
35
34
 
36
- attr_reader :cache
35
+ attr_reader :path, :host, :port, :cache
37
36
 
38
37
  def initialize
39
38
  @session = Lastfm.new('5f7b134ba19b20536a5e29bc86ae64c9', '3b50e74d989795c3f4b3667c5a1c8e67')
40
39
  @cache = Cache.new(self)
41
40
  @events = Hash.new { |h, k| h[k] = [] }
42
41
 
43
- cache_at '~/.LOLastfm.cache'
42
+ cache_at '~/.LOLastfm/cache'
44
43
  end
45
44
 
46
45
  def load (path)
47
46
  instance_eval File.read(File.expand_path(path)), path
48
47
  end
49
48
 
49
+ def started?; !!@started; end
50
+
50
51
  def start
51
- @server = EM.start_unix_domain_server(@socket || File.expand_path('~/.LOLastfm.socket'), LOLastfm::Connection) {|conn|
52
- conn.fm = self
53
- }
52
+ return if started?
53
+
54
+ @server = if @host && @port
55
+ EM.start_server(host, port, LOLastfm::Connection) {|conn|
56
+ conn.fm = self
57
+ }
58
+ else
59
+ EM.start_unix_domain_server(path || File.expand_path('~/.LOLastfm/socket'), LOLastfm::Connection) {|conn|
60
+ conn.fm = self
61
+ }
62
+ end
54
63
 
55
64
  @timer = EM.add_periodic_timer 360 do
56
65
  save
57
66
  end
67
+
68
+ @checker.start
58
69
  end
59
70
 
60
71
  def stop
72
+ return unless started?
73
+
61
74
  EM.stop_server @server
62
75
  EM.cancel_timer @timer
63
76
 
@@ -82,8 +95,13 @@ class LOLastfm
82
95
  end
83
96
  end
84
97
 
85
- def socket (path)
86
- @socket = path
98
+ def server_at (host, port = nil)
99
+ if port
100
+ @host = host
101
+ @port = port
102
+ else
103
+ @path = host
104
+ end
87
105
  end
88
106
 
89
107
  def session (key)
@@ -96,6 +114,7 @@ class LOLastfm
96
114
 
97
115
  def now_playing (song)
98
116
  song = Song.new(song) unless song.is_a?(Song)
117
+ song = song.dup
99
118
 
100
119
  return false unless fire :now_playing, song
101
120
 
@@ -112,6 +131,7 @@ class LOLastfm
112
131
 
113
132
  def listened (song)
114
133
  song = Song.new(song) unless song.is_a?(Song)
134
+ song = song.dup
115
135
 
116
136
  return false unless fire :listened, song
117
137
 
@@ -138,6 +158,7 @@ class LOLastfm
138
158
  def love (song = nil)
139
159
  song = @last_played or return unless song
140
160
  song = Song.new(song) unless song.is_a? Song
161
+ song = song.dup
141
162
 
142
163
  return false unless fire :love, song
143
164
 
@@ -168,16 +189,26 @@ class LOLastfm
168
189
  @last_played
169
190
  end
170
191
 
171
- def use (*args, &block)
172
- if args.first.is_a?(String)
173
- return require args.first
192
+ def commands (name)
193
+ begin
194
+ require "LOLastfm/commands/#{name}"
195
+ rescue LoadError
196
+ require name.to_s
174
197
  end
198
+ end
175
199
 
176
- unless block
200
+ def use (*args, &block)
201
+ return if args.empty? && !block
202
+
203
+ unless args.first.respond_to?(:to_hash) || block
177
204
  name = args.shift.to_sym
178
205
 
179
- if args.first.is_a?(String)
180
- require args.shift
206
+ if args.first.is_a? String
207
+ require args.pop
208
+ elsif !self.class.checkers[name]
209
+ begin
210
+ require "LOLastfm/checkers/#{name}"
211
+ rescue LoadError; end
181
212
  end
182
213
 
183
214
  block = self.class.checkers[name]
@@ -187,9 +218,9 @@ class LOLastfm
187
218
  @checker.stop
188
219
  end
189
220
 
190
- @checker = Checker.new(self, name, args.shift, &block)
191
- @checker.start
192
- @checker
221
+ @checker = Checker.new(self, name, args.shift, &block).tap {|c|
222
+ c.start if started?
223
+ }
193
224
  end
194
225
 
195
226
  def hint (*args)
@@ -62,17 +62,21 @@ class Checker
62
62
  end
63
63
 
64
64
  def stop (&block)
65
- @timers.each {|timer|
66
- clear_timeout(timer)
67
- }
68
-
69
65
  if block
70
66
  @stop_block = block
71
67
  else
68
+ @stopped = true
69
+
70
+ @timers.each {|timer|
71
+ clear_timeout(timer)
72
+ }
73
+
72
74
  @stop_block.call if @stop_block
73
75
  end
74
76
  end
75
77
 
78
+ def stopped?; !!@stopped; end
79
+
76
80
  def hint (*args, &block)
77
81
  if block
78
82
  @hint_block = block
@@ -11,7 +11,6 @@
11
11
  require 'cmus'
12
12
 
13
13
  class Cmus::Controller::Status
14
- memoize
15
14
  def to_song
16
15
  return unless song
17
16
 
@@ -33,26 +32,37 @@ LOLastfm.define_checker :cmus do
33
32
  settings.default[:every] = 5
34
33
  settings.default[:timeout] = 0.005
35
34
 
36
- @cmus = Cmus::Controller.new(settings[:socket], settings[:timeout])
37
- @last = @cmus.status
35
+ if @cmus = Cmus::Controller.new(settings[:socket], settings[:timeout]) rescue false
36
+ @last = @cmus.status
38
37
 
39
- if @last == :play
40
- now_playing @last.to_song
38
+ if @last == :play
39
+ now_playing @last.to_song
40
+ end
41
41
  end
42
42
 
43
43
  set_interval settings[:every] do
44
+ unless @cmus
45
+ @cmus = Cmus::Controller.new(settings[:socket], settings[:timeout]) rescue next
46
+ @last = @cmus.status
47
+ end
48
+
49
+ unless status = @cmus.status rescue nil
50
+ @cmus = nil
51
+ next
52
+ end
53
+
44
54
  status = @cmus.status
45
55
 
46
56
  if status == :stop
47
- if @last != :stop && LOLastfm::Song.is_scrobblable?(@last.song.position, @last.song.duration)
57
+ if @last != :stop && (@last.to_song.stream? || LOLastfm::Song.is_scrobblable?(@last.song.position, @last.song.duration))
48
58
  listened @last.to_song
49
- else
50
- stopped_playing!
51
59
  end
60
+
61
+ stopped_playing!
52
62
  elsif status == :pause
53
63
  stopped_playing!
54
64
  else
55
- if @last != :stop && (@last.to_song != status.to_song || @last.song.position > status.song.position) && LOLastfm::Song.is_scrobblable?(@last.song.position, @last.song.duration)
65
+ if @last != :stop && (@last.to_song != status.to_song || @last.song.position > status.song.position + 30) && (@last.to_song.stream? || LOLastfm::Song.is_scrobblable?(@last.song.position, @last.song.duration))
56
66
  listened @last.to_song
57
67
  end
58
68
 
@@ -10,23 +10,25 @@
10
10
 
11
11
  require 'moc'
12
12
 
13
- class Moc::Controller::Status
14
- memoize
13
+ class Moc::Controller::Status::Live
15
14
  def to_song
16
15
  return unless song
17
16
 
18
- if song.file.start_with?('http://') || song.file.start_with?('ftp://')
19
- comment = song.file
17
+ file = song.file
18
+ tags = song.tags
19
+
20
+ if file.start_with?('http://') || file.start_with?('ftp://')
21
+ comment = file
20
22
  else
21
- path = song.file
23
+ path = file
22
24
  end
23
25
 
24
26
  LOLastfm::Song.new(true,
25
- track: song.track,
26
- title: song.title,
27
- artist: song.artist,
28
- album: song.album,
29
- length: song.duration,
27
+ track: tags.track,
28
+ title: tags.title,
29
+ artist: tags.artist,
30
+ album: tags.album,
31
+ length: tags.time,
30
32
  comment: comment,
31
33
  path: path,
32
34
  stream: !!comment
@@ -38,34 +40,58 @@ LOLastfm.define_checker :moc do
38
40
  settings.default[:socket] = '~/.moc/socket2'
39
41
  settings.default[:every] = 5
40
42
 
41
- @moc = Moc::Controller.new(settings[:socket])
42
- @last = @moc.status
43
+ create = proc {
44
+ unless moc = Moc::Controller.new(settings[:socket]) rescue false
45
+ set_timeout settings[:every], &create unless stopped?
46
+ end
43
47
 
44
- if @last == :play
45
- now_playing @last.to_song
46
- end
48
+ Thread.new {
49
+ song, position = nil
47
50
 
48
- set_interval settings[:every] do
49
- status = @moc.status
51
+ begin
52
+ moc.loop {|e|
53
+ if e == :audio_stop
54
+ next unless song
50
55
 
51
- if status == :stop
52
- if @last != :stop && LOLastfm::Song.is_scrobblable?(@last.song.position, @last.song.duration)
53
- listened @last.to_song
54
- else
55
- stopped_playing!
56
- end
57
- elsif status == :pause
58
- stopped_playing!
59
- else
60
- if @last != :stop && (@last.to_song != status.to_song || @last.song.position > status.song.position) && LOLastfm::Song.is_scrobblable?(@last.song.position, @last.song.duration)
61
- listened @last.to_song
62
- end
56
+ if song.stream?
57
+ listened song
58
+ else
59
+ if LOLastfm::Song.is_scrobblable?(position, song.length)
60
+ listened song
61
+ else
62
+ stopped_playing!
63
+ end
64
+ end
65
+
66
+ song = nil
67
+ elsif e == :tags
68
+ elsif e == :audio_start
69
+ now_playing song = moc.status(true).to_song
70
+ elsif e == :state && moc.status(true) == :paused
71
+ stopped_playing!
72
+ elsif e == :ctime
73
+ unless song
74
+ now_playing song = moc.status(true).to_song
75
+ end
63
76
 
64
- if @last != :play || @last.to_song != status.to_song
65
- now_playing status.to_song
77
+ if song.stream?
78
+ if moc.status(true).song.title != song.title
79
+ listened song
80
+ now_playing song = moc.status(true).to_song
81
+ end
82
+ else
83
+ position = moc.status(true).song.position
84
+ end
85
+ end
86
+ }
87
+ rescue Exception => e
88
+ $stderr.puts e.message
89
+ $stderr.puts e.backtrace
66
90
  end
67
- end
68
91
 
69
- @last = status
70
- end
92
+ set_timeout settings[:every], &create unless stopped?
93
+ }
94
+ }
95
+
96
+ create.call
71
97
  end
@@ -9,19 +9,34 @@
9
9
  #++
10
10
 
11
11
  require 'glyr'
12
+ require 'tmpdir'
13
+ require 'fileutils'
12
14
 
13
- class Glyr::Result::Data
14
- def to_json
15
- { source: source, provider: provider, data: data }.to_json
15
+ module Glyr
16
+ class Source
17
+ def to_hash
18
+ { name: name, quality: quality, speed: speed, language_aware: language_aware? }
19
+ end
16
20
  end
21
+
22
+ class Result
23
+ def to_hash
24
+ { source: source, url: url, data: data }
25
+ end
26
+ end
27
+
28
+ File.join(Dir.tmpdir, rand.to_s).tap {|path|
29
+ FileUtils.mkpath(path)
30
+ cache_at path
31
+ }
17
32
  end
18
33
 
19
34
  LOLastfm.define_command :lyrics? do
20
35
  song = now_playing?
21
36
 
22
37
  EM.defer -> {
23
- Glyr.query.title(song.title).artist(song.artist).album(song.album).lyrics
24
- }, -> result {
25
- send_line result.first.to_str.to_json
38
+ Glyr.query(title: song.title, artist: song.artist, album: song.album).lyrics
39
+ }, -> results {
40
+ send_line results.map(&:to_hash).to_json
26
41
  }
27
42
  end
@@ -16,6 +16,10 @@ define_command :use do |*args|
16
16
  use *args
17
17
  end
18
18
 
19
+ define_command :commands do |*args|
20
+ commands *args
21
+ end
22
+
19
23
  define_command :listened do |song|
20
24
  listened song
21
25
  end
@@ -55,7 +59,7 @@ class Connection < EventMachine::Protocols::LineAndTextProtocol
55
59
  command, arguments = JSON.parse(line)
56
60
 
57
61
  if block = LOLastfm.commands[command.to_sym.downcase]
58
- instance_eval *arguments, &block
62
+ instance_exec *arguments, &block
59
63
  end
60
64
  rescue => e
61
65
  $stderr.puts e.to_s
data/lib/LOLastfm/song.rb CHANGED
@@ -17,6 +17,8 @@ class LOLastfm
17
17
 
18
18
  class Song
19
19
  def self.is_scrobblable? (position, duration)
20
+ return false unless position && duration
21
+
20
22
  return false if duration < 30
21
23
 
22
24
  return true if position > 240
@@ -10,6 +10,6 @@
10
10
 
11
11
  class LOLastfm
12
12
  def self.version
13
- '0.0.1'
13
+ '0.0.2'
14
14
  end
15
15
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: LOLastfm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-06-23 00:00:00.000000000 Z
12
+ date: 2012-06-26 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: lastfm
@@ -43,28 +43,14 @@ dependencies:
43
43
  - - ! '>='
44
44
  - !ruby/object:Gem::Version
45
45
  version: '0'
46
- - !ruby/object:Gem::Dependency
47
- name: call-me
48
- requirement: !ruby/object:Gem::Requirement
49
- none: false
50
- requirements:
51
- - - ! '>='
52
- - !ruby/object:Gem::Version
53
- version: '0'
54
- type: :runtime
55
- prerelease: false
56
- version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
- requirements:
59
- - - ! '>='
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
46
  description:
63
47
  email: meh@paranoici.org
64
48
  executables:
65
49
  - LOLastfm
66
50
  - LOLastfm-cmus
67
- - LOLastfm-do
51
+ - LOLastfm-lyrics
52
+ - LOLastfm-now-playing
53
+ - LOLastfm-send
68
54
  extensions: []
69
55
  extra_rdoc_files: []
70
56
  files:
@@ -72,7 +58,9 @@ files:
72
58
  - README.md
73
59
  - bin/LOLastfm
74
60
  - bin/LOLastfm-cmus
75
- - bin/LOLastfm-do
61
+ - bin/LOLastfm-lyrics
62
+ - bin/LOLastfm-now-playing
63
+ - bin/LOLastfm-send
76
64
  - lib/LOLastfm.rb
77
65
  - lib/LOLastfm/cache.rb
78
66
  - lib/LOLastfm/checker.rb