LOLastfm 0.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/LOLastfm.gemspec ADDED
@@ -0,0 +1,20 @@
1
+ Kernel.load 'lib/LOLastfm/version.rb'
2
+
3
+ Gem::Specification.new {|s|
4
+ s.name = 'LOLastfm'
5
+ s.version = LOLastfm.version
6
+ s.author = 'meh.'
7
+ s.email = 'meh@paranoici.org'
8
+ s.homepage = 'http://github.com/meh/LOLastfm'
9
+ s.platform = Gem::Platform::RUBY
10
+ s.summary = 'LOL a scrobbler.'
11
+
12
+ s.files = `git ls-files`.split("\n")
13
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
14
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
+ s.require_paths = ['lib']
16
+
17
+ s.add_dependency 'lastfm'
18
+ s.add_dependency 'taglib-ruby'
19
+ s.add_dependency 'call-me'
20
+ }
data/README.md ADDED
@@ -0,0 +1,2 @@
1
+ LOLastfm - blablablabal
2
+ =======================
data/bin/LOLastfm ADDED
@@ -0,0 +1,39 @@
1
+ #! /usr/bin/env ruby
2
+ require 'optparse'
3
+ require 'LOLastfm'
4
+
5
+ OptionParser.new do |o|
6
+ o.on '-a', '--auth', 'authorize LOLastfm to work' do
7
+ lastfm = Lastfm.new('5f7b134ba19b20536a5e29bc86ae64c9', '3b50e74d989795c3f4b3667c5a1c8e67')
8
+ token = lastfm.auth.get_token
9
+
10
+ puts "Go to the following URL, allow LOLastfm and press Enter to continue..."
11
+ puts ""
12
+ puts " http://www.last.fm/api/auth/?api_key=5f7b134ba19b20536a5e29bc86ae64c9&token=#{token}"
13
+
14
+ gets
15
+
16
+ puts "Use #{lastfm.auth.get_session(token)['key']} as session in your LOLastfm configuration."
17
+ exit
18
+ end
19
+
20
+ o.on '-v', '--version', 'show version and exit' do
21
+ puts "LOLastfm #{LOLastfm.version}"
22
+ exit
23
+ end
24
+ end.parse!
25
+
26
+ EM.run {
27
+ d = LOLastfm.load(ARGV.first || '~/.LOLastfm.rc')
28
+ d.start
29
+
30
+ %w[INT KILL].each {|sig|
31
+ trap sig do
32
+ puts 'LOLastfm stopping...'
33
+
34
+ d.stop
35
+
36
+ EM.stop_event_loop
37
+ end
38
+ }
39
+ }
data/bin/LOLastfm-cmus ADDED
@@ -0,0 +1,14 @@
1
+ #! /usr/bin/env ruby
2
+ require 'socket'
3
+ require 'json'
4
+
5
+ args = Hash[*ARGV]
6
+
7
+ UNIXSocket.new(File.expand_path('~/.LOLastfm.socket')).tap {|socket|
8
+ if args['url']
9
+ socket.puts [:hint, [:stream, args['title'], args['comment']]].to_json
10
+ end
11
+
12
+ socket.flush
13
+ socket.close
14
+ }
data/bin/LOLastfm-do ADDED
@@ -0,0 +1,68 @@
1
+ #! /usr/bin/env ruby
2
+ require 'optparse'
3
+ require 'socket'
4
+ require 'json'
5
+
6
+ options = {}
7
+
8
+ OptionParser.new do |o|
9
+ options[:socket] = '~/.LOLastfm.socket'
10
+ options[:song] = {}
11
+
12
+ o.on '--listened', 'enable listened sending' do
13
+ options[:listened] = true
14
+ end
15
+
16
+ o.on '--now-playing', 'enable now playing sending' do
17
+ options[:now_playing] = true
18
+ end
19
+
20
+ o.on '--love', 'enable love sending' do
21
+ options[:love] = true
22
+ end
23
+
24
+ o.on '-t', '--title TITLE', 'title of the song' do |value|
25
+ options[:song][:title] = value
26
+ end
27
+
28
+ o.on '-a', '--artist ARTIST', 'the artist of the song' do |value|
29
+ options[:song][:artist] = value
30
+ end
31
+
32
+ o.on '-A', '--album ALBUM', 'the album of the song' do |value|
33
+ options[:song][:album] = value
34
+ end
35
+
36
+ o.on '-l', '--length LENGTH', Integer, 'the length of the song in seconds' do |value|
37
+ options[:song][:length] = value
38
+ end
39
+
40
+ o.on '-L', '--listened-at DATE', 'the time when the song has started playing' do |value|
41
+ options[:song][:listened_at] = value
42
+ end
43
+
44
+ o.on '-p', '--path PATH', 'the path of the song' do |value|
45
+ options[:song][:path] = value
46
+ end
47
+
48
+ o.on '-i', '--id ID', 'the MusicBrainz id of the song' do |value|
49
+ options[:song][:id] = value
50
+ end
51
+ end.parse!
52
+
53
+ if ARGV.first
54
+ options[:socket] = ARGV.first
55
+ end
56
+
57
+ socket = UNIXSocket.new(File.expand_path(options[:socket]))
58
+
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
63
+ else
64
+ socket.puts [:listened, options[:song]].to_json
65
+ end
66
+
67
+ socket.flush
68
+ socket.close
data/lib/LOLastfm.rb ADDED
@@ -0,0 +1,232 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ require 'eventmachine'
12
+ require 'lastfm'
13
+ require 'call-me/memoize'
14
+
15
+ class LOLastfm
16
+ def self.load (path)
17
+ new.tap { |o| o.load(path) }
18
+ end
19
+
20
+ def self.checkers
21
+ @checkers ||= {}
22
+ end
23
+
24
+ def self.define_checker (name, &block)
25
+ checkers[name.to_sym.downcase] = block
26
+ end
27
+
28
+ def self.commands
29
+ @commands ||= {}
30
+ end
31
+
32
+ def self.define_command (name, &block)
33
+ commands[name.to_sym.downcase] = block
34
+ end
35
+
36
+ attr_reader :cache
37
+
38
+ def initialize
39
+ @session = Lastfm.new('5f7b134ba19b20536a5e29bc86ae64c9', '3b50e74d989795c3f4b3667c5a1c8e67')
40
+ @cache = Cache.new(self)
41
+ @events = Hash.new { |h, k| h[k] = [] }
42
+
43
+ cache_at '~/.LOLastfm.cache'
44
+ end
45
+
46
+ def load (path)
47
+ instance_eval File.read(File.expand_path(path)), path
48
+ end
49
+
50
+ def start
51
+ @server = EM.start_unix_domain_server(@socket || File.expand_path('~/.LOLastfm.socket'), LOLastfm::Connection) {|conn|
52
+ conn.fm = self
53
+ }
54
+
55
+ @timer = EM.add_periodic_timer 360 do
56
+ save
57
+ end
58
+ end
59
+
60
+ def stop
61
+ EM.stop_server @server
62
+ EM.cancel_timer @timer
63
+
64
+ @checker.stop if @checker
65
+ ensure
66
+ save
67
+ end
68
+
69
+ def cache_at (path)
70
+ @cache_at = File.expand_path(path)
71
+
72
+ if File.exists? @cache_at
73
+ @cache.load(@cache_at)
74
+ end
75
+ end
76
+
77
+ def save
78
+ if @cache_at
79
+ File.open(@cache_at, 'w') {|f|
80
+ f.write(@cache.to_yaml)
81
+ }
82
+ end
83
+ end
84
+
85
+ def socket (path)
86
+ @socket = path
87
+ end
88
+
89
+ def session (key)
90
+ @session.session = key
91
+ end
92
+
93
+ def is_authenticated?
94
+ !!@session.session
95
+ end
96
+
97
+ def now_playing (song)
98
+ song = Song.new(song) unless song.is_a?(Song)
99
+
100
+ return false unless fire :now_playing, song
101
+
102
+ @now_playing = nil
103
+
104
+ return false if song.nil?
105
+
106
+ @now_playing = song
107
+
108
+ @session.track.update_now_playing(song.artist, song.title, song.album, song.track, song.id, song.length)
109
+ rescue
110
+ false
111
+ end
112
+
113
+ def listened (song)
114
+ song = Song.new(song) unless song.is_a?(Song)
115
+
116
+ return false unless fire :listened, song
117
+
118
+ @cache.flush!
119
+ @now_playing = nil
120
+
121
+ return false if song.nil?
122
+
123
+ unless listened! song
124
+ @cache.listened(song)
125
+ end
126
+
127
+ true
128
+ end
129
+
130
+ def listened! (song)
131
+ @session.track.scrobble(song.artist, song.title, song.listened_at.to_time.to_i, song.album, song.track, song.id, song.length).tap {
132
+ @last_played = song
133
+ }
134
+ rescue
135
+ false
136
+ end
137
+
138
+ def love (song = nil)
139
+ song = @last_played or return unless song
140
+ song = Song.new(song) unless song.is_a? Song
141
+
142
+ return false unless fire :love, song
143
+
144
+ return false if song.nil?
145
+
146
+ unless love! song
147
+ @cache.love(song)
148
+ end
149
+
150
+ true
151
+ end
152
+
153
+ def love! (song)
154
+ @session.track.love(song.artist, song.title)
155
+ rescue
156
+ false
157
+ end
158
+
159
+ def stopped_playing!
160
+ @now_playing = nil
161
+ end
162
+
163
+ def now_playing?
164
+ @now_playing
165
+ end
166
+
167
+ def last_played?
168
+ @last_played
169
+ end
170
+
171
+ def use (*args, &block)
172
+ if args.first.is_a?(String)
173
+ return require args.first
174
+ end
175
+
176
+ unless block
177
+ name = args.shift.to_sym
178
+
179
+ if args.first.is_a?(String)
180
+ require args.shift
181
+ end
182
+
183
+ block = self.class.checkers[name]
184
+ end
185
+
186
+ if @checker
187
+ @checker.stop
188
+ end
189
+
190
+ @checker = Checker.new(self, name, args.shift, &block)
191
+ @checker.start
192
+ @checker
193
+ end
194
+
195
+ def hint (*args)
196
+ return unless @checker
197
+
198
+ @checker.hint(*args)
199
+ end
200
+
201
+ def on (event, &block)
202
+ @events[event.to_sym] << block
203
+ end
204
+
205
+ def fire (event, *args)
206
+ delete = []
207
+ stopped = false
208
+
209
+ @events[event.to_sym].each {|block|
210
+ result = block.call(*args)
211
+
212
+ case result
213
+ when :delete
214
+ delete << event
215
+
216
+ when :stop
217
+ stopped = true
218
+ break
219
+ end
220
+ }
221
+
222
+ @events[event.to_sym] -= delete
223
+
224
+ return !stopped
225
+ end
226
+ end
227
+
228
+ require 'LOLastfm/version'
229
+ require 'LOLastfm/cache'
230
+ require 'LOLastfm/connection'
231
+ require 'LOLastfm/song'
232
+ require 'LOLastfm/checker'
@@ -0,0 +1,72 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ require 'yaml'
12
+
13
+ class LOLastfm
14
+
15
+ class Cache
16
+ attr_reader :fm
17
+
18
+ def initialize (fm)
19
+ @fm = fm
20
+
21
+ @listened = []
22
+ @loved = []
23
+ end
24
+
25
+ def listened (song)
26
+ return if @loved.member? song
27
+
28
+ @listened << song
29
+ end
30
+
31
+ def love (song)
32
+ return if @loved.member? song
33
+
34
+ @loved << song
35
+ end
36
+
37
+ def empty?
38
+ @listened.empty? && @loved.empty?
39
+ end
40
+
41
+ def flush!
42
+ until @listened.empty?
43
+ break unless fm.listened! @listened.first
44
+
45
+ @listened.shift
46
+ end
47
+
48
+ until @loved.empty?
49
+ break unless fm.love! @loved.first
50
+
51
+ @loved.shift
52
+ end
53
+ end
54
+
55
+ def load (path)
56
+ data = YAML.parse_file(path).transform
57
+
58
+ data['listened'].each {|song|
59
+ listened(Song.new(song))
60
+ }
61
+
62
+ data['loved'].each {|song|
63
+ love(Song.new(song))
64
+ }
65
+ end
66
+
67
+ def to_yaml
68
+ { 'listened' => @listened.map(&:to_hash), 'loved' => @loved.map(&:to_hash) }.to_yaml
69
+ end
70
+ end
71
+
72
+ end
@@ -0,0 +1,103 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ class LOLastfm
12
+
13
+ class Checker
14
+ class Settings
15
+ attr_reader :default
16
+
17
+ def initialize (checker, data)
18
+ @data = data.to_hash
19
+ @default = {}
20
+ end
21
+
22
+ def respond_to_missing? (id)
23
+ @data.respond_to?(id)
24
+ end
25
+
26
+ def method_missing (id, *args, &block)
27
+ if @default.respond_to? id
28
+ return @default.merge(@data).__send__ id, *args, &block
29
+ end
30
+
31
+ super
32
+ end
33
+ end
34
+
35
+ attr_reader :fm, :name, :settings
36
+
37
+ def initialize (fm, name, settings = nil, &block)
38
+ raise LocalJumpError, 'no block given' unless block
39
+
40
+ @fm = fm
41
+ @name = name
42
+ @settings = Settings.new(self, settings || {})
43
+ @block = block
44
+
45
+ @timers = []
46
+ end
47
+
48
+ def respond_to_missing? (id)
49
+ @fm.respond_to?(id)
50
+ end
51
+
52
+ def method_missing (id, *args, &block)
53
+ if @fm.respond_to? id
54
+ return @fm.__send__ id, *args, &block
55
+ end
56
+
57
+ super
58
+ end
59
+
60
+ def start
61
+ instance_eval &@block
62
+ end
63
+
64
+ def stop (&block)
65
+ @timers.each {|timer|
66
+ clear_timeout(timer)
67
+ }
68
+
69
+ if block
70
+ @stop_block = block
71
+ else
72
+ @stop_block.call if @stop_block
73
+ end
74
+ end
75
+
76
+ def hint (*args, &block)
77
+ if block
78
+ @hint_block = block
79
+ else
80
+ @hint_block.call(*args) if @hint_block
81
+ end
82
+ end
83
+
84
+ def set_timeout (*args, &block)
85
+ EM.schedule {
86
+ @timers << EM.add_timer(*args, &block)
87
+ }
88
+ end
89
+
90
+ def set_interval (*args, &block)
91
+ EM.schedule {
92
+ @timers << EM.add_periodic_timer(*args, &block)
93
+ }
94
+ end
95
+
96
+ def clear_timeout (what)
97
+ EM.schedule {
98
+ EM.cancel_timer(what)
99
+ }
100
+ end
101
+ end
102
+
103
+ end
@@ -0,0 +1,80 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ require 'cmus'
12
+
13
+ class Cmus::Controller::Status
14
+ memoize
15
+ def to_song
16
+ return unless song
17
+
18
+ LOLastfm::Song.new(true,
19
+ track: song.track,
20
+ title: song.title,
21
+ artist: song.artist,
22
+ album: song.album,
23
+ length: song.duration,
24
+ comment: song.tags.comment,
25
+ path: song.file,
26
+ stream: !song.file
27
+ )
28
+ end
29
+ end
30
+
31
+ LOLastfm.define_checker :cmus do
32
+ settings.default[:socket] = '~/.cmus/socket'
33
+ settings.default[:every] = 5
34
+ settings.default[:timeout] = 0.005
35
+
36
+ @cmus = Cmus::Controller.new(settings[:socket], settings[:timeout])
37
+ @last = @cmus.status
38
+
39
+ if @last == :play
40
+ now_playing @last.to_song
41
+ end
42
+
43
+ set_interval settings[:every] do
44
+ status = @cmus.status
45
+
46
+ if status == :stop
47
+ if @last != :stop && LOLastfm::Song.is_scrobblable?(@last.song.position, @last.song.duration)
48
+ listened @last.to_song
49
+ else
50
+ stopped_playing!
51
+ end
52
+ elsif status == :pause
53
+ stopped_playing!
54
+ 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)
56
+ listened @last.to_song
57
+ end
58
+
59
+ if @last != :play || @last.to_song != status.to_song
60
+ now_playing status.to_song
61
+ end
62
+ end
63
+
64
+ @last = status
65
+ end
66
+
67
+ hint do |type, *args|
68
+ if type == 'stream'
69
+ title, comment = args
70
+
71
+ if @hint && title != @hint
72
+ next unless listened title: @hint, comment: comment || @cmus.status.song.comment, stream: true
73
+ end
74
+
75
+ next unless now_playing title: title, comment: comment || @cmus.status.song.comment, stream: true
76
+
77
+ @hint = title
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,71 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ require 'moc'
12
+
13
+ class Moc::Controller::Status
14
+ memoize
15
+ def to_song
16
+ return unless song
17
+
18
+ if song.file.start_with?('http://') || song.file.start_with?('ftp://')
19
+ comment = song.file
20
+ else
21
+ path = song.file
22
+ end
23
+
24
+ LOLastfm::Song.new(true,
25
+ track: song.track,
26
+ title: song.title,
27
+ artist: song.artist,
28
+ album: song.album,
29
+ length: song.duration,
30
+ comment: comment,
31
+ path: path,
32
+ stream: !!comment
33
+ )
34
+ end
35
+ end
36
+
37
+ LOLastfm.define_checker :moc do
38
+ settings.default[:socket] = '~/.moc/socket2'
39
+ settings.default[:every] = 5
40
+
41
+ @moc = Moc::Controller.new(settings[:socket])
42
+ @last = @moc.status
43
+
44
+ if @last == :play
45
+ now_playing @last.to_song
46
+ end
47
+
48
+ set_interval settings[:every] do
49
+ status = @moc.status
50
+
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
63
+
64
+ if @last != :play || @last.to_song != status.to_song
65
+ now_playing status.to_song
66
+ end
67
+ end
68
+
69
+ @last = status
70
+ end
71
+ end
@@ -0,0 +1,34 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ LOLastfm.define_checker :process do
12
+ settings.default[:every] = 5
13
+
14
+ set_interval settings[:every] do
15
+ if status == :stopped
16
+ if @last == :playing
17
+
18
+ end
19
+ else
20
+ end
21
+
22
+ @last = status
23
+ end
24
+
25
+ hint do |path|
26
+ if @hint && path != @hint
27
+ next unless listened path: path
28
+ end
29
+
30
+ next unless now_playing path: path
31
+
32
+ @hint = path
33
+ end
34
+ end
@@ -0,0 +1,27 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ require 'glyr'
12
+
13
+ class Glyr::Result::Data
14
+ def to_json
15
+ { source: source, provider: provider, data: data }.to_json
16
+ end
17
+ end
18
+
19
+ LOLastfm.define_command :lyrics? do
20
+ song = now_playing?
21
+
22
+ 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
26
+ }
27
+ end
@@ -0,0 +1,84 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ require 'json'
12
+
13
+ class LOLastfm
14
+
15
+ define_command :use do |*args|
16
+ use *args
17
+ end
18
+
19
+ define_command :listened do |song|
20
+ listened song
21
+ end
22
+
23
+ define_command :now_playing do |song|
24
+ now_playing song
25
+ end
26
+
27
+ define_command :stopped_playing do
28
+ stopped_playing!
29
+ end
30
+
31
+ define_command :love do |song|
32
+ love song
33
+ end
34
+
35
+ define_command :hint do |*args|
36
+ hint *args
37
+ end
38
+
39
+ define_command :now_playing? do
40
+ send_line now_playing?.to_json
41
+ end
42
+
43
+ define_command :next? do
44
+ on :now_playing do |song|
45
+ send_line song.to_json
46
+
47
+ :delete
48
+ end
49
+ end
50
+
51
+ class Connection < EventMachine::Protocols::LineAndTextProtocol
52
+ attr_writer :fm
53
+
54
+ def receive_line (line)
55
+ command, arguments = JSON.parse(line)
56
+
57
+ if block = LOLastfm.commands[command.to_sym.downcase]
58
+ instance_eval *arguments, &block
59
+ end
60
+ rescue => e
61
+ $stderr.puts e.to_s
62
+ $stderr.puts e.backtrace
63
+ end
64
+
65
+ def send_line (line)
66
+ raise ArgumentError, 'the line already has a newline character' if line.include? "\n"
67
+
68
+ send_data "#{line}\r\n"
69
+ end
70
+
71
+ def respond_to_missing? (id)
72
+ @fm.respond_to?(id)
73
+ end
74
+
75
+ def method_missing (id, *args, &block)
76
+ if @fm.respond_to? id
77
+ return @fm.__send__ id, *args, &block
78
+ end
79
+
80
+ super
81
+ end
82
+ end
83
+
84
+ end
@@ -0,0 +1,143 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ require 'taglib'
12
+ require 'json'
13
+ require 'yaml'
14
+ require 'date'
15
+
16
+ class LOLastfm
17
+
18
+ class Song
19
+ def self.is_scrobblable? (position, duration)
20
+ return false if duration < 30
21
+
22
+ return true if position > 240
23
+
24
+ return true if position >= duration / 2
25
+
26
+ false
27
+ end
28
+
29
+ attr_accessor :track, :title, :album, :artist, :comment, :length, :listened_at, :path, :id
30
+
31
+ def initialize (no_fill = false, data)
32
+ data = Hash[data.map { |key, value| [key.to_sym, value] }]
33
+
34
+ @track = data[:track] && data[:track].to_i
35
+ @title = data[:title]
36
+ @album = data[:album]
37
+ @artist = data[:artist]
38
+ @length = data[:length] && data[:length].to_i
39
+ @comment = data[:comment]
40
+ @listened_at = data[:listened_at]
41
+ @path = data[:path]
42
+ @id = data[:id]
43
+
44
+ fill! unless no_fill
45
+
46
+ if data[:stream] || (@length && @length < 0)
47
+ stream!
48
+
49
+ @length = nil
50
+ end
51
+
52
+ if @listened_at
53
+ @listened_at = @listened_at.is_a?(String) ? DateTime.parse(@listened_at) : @listened_at.to_datetime
54
+ elsif @length
55
+ @listened_at = (Time.now - @length).to_datetime
56
+ else
57
+ @listened_at = DateTime.now
58
+ end
59
+ end
60
+
61
+ def fill!
62
+ return unless @path
63
+
64
+ return if @track && @title && @album && @artist && @comment && @length && @id
65
+
66
+ TagLib::FileRef.open(@path) {|f|
67
+ if f.tag
68
+ @track = f.tag.track && f.tag.track.to_i unless @track
69
+ @title = f.tag.title unless @title
70
+ @album = f.tag.album unless @album
71
+ @artist = f.tag.artist unless @artist
72
+ @comment = f.tag.comment unless @comment
73
+ end
74
+
75
+ if f.audio_properties
76
+ @length = f.audio_properties.length.to_i unless @length
77
+ end
78
+ }
79
+
80
+ unless @id
81
+ TagLib::MPEG::File.new(@path).tap {|file|
82
+ next unless tag = file.id3v2_tag
83
+
84
+ if ufid = tag.frame_list('UFID').find { |u| u.owner == 'http://musicbrainz.org' }
85
+ @id = ufid.identifier
86
+ end
87
+ }
88
+ end
89
+
90
+ unless @id
91
+ TagLib::FLAC::File.new(@path).tap {|file|
92
+ next unless tag = file.xiph_comment
93
+
94
+ @id = tag.field_list_map['MUSICBRAINZ_TRACKID']
95
+ }
96
+ end
97
+
98
+ unless @id
99
+ TagLib::Ogg::Vorbis::File.new(@path).tap {|file|
100
+ next unless tag = file.tag
101
+
102
+ @id = tag.field_list_map['MUSICBRAINZ_TRACKID']
103
+ }
104
+ end
105
+ end
106
+
107
+ def stream?; !!@stream; end
108
+ def stream!; @stream = true; end
109
+
110
+ def nil?
111
+ title.nil? || artist.nil?
112
+ end
113
+
114
+ def hash
115
+ [title, artist].hash
116
+ end
117
+
118
+ def == (other)
119
+ return true if super
120
+
121
+ title == other.title && artist == other.artist
122
+ end
123
+
124
+ alias eql? ==
125
+
126
+ def to_hash
127
+ {
128
+ track: track, title: title, album: album, artist: artist, comment: comment,
129
+ length: length, listened_at: listened_at, path: path, id: id,
130
+ stream: stream?
131
+ }
132
+ end
133
+
134
+ def to_json
135
+ to_hash.to_json
136
+ end
137
+
138
+ def to_yaml
139
+ to_hash.to_yaml
140
+ end
141
+ end
142
+
143
+ end
@@ -0,0 +1,15 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ class LOLastfm
12
+ def self.version
13
+ '0.0.1'
14
+ end
15
+ end
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: LOLastfm
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - meh.
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-06-23 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: lastfm
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: taglib-ruby
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
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
+ description:
63
+ email: meh@paranoici.org
64
+ executables:
65
+ - LOLastfm
66
+ - LOLastfm-cmus
67
+ - LOLastfm-do
68
+ extensions: []
69
+ extra_rdoc_files: []
70
+ files:
71
+ - LOLastfm.gemspec
72
+ - README.md
73
+ - bin/LOLastfm
74
+ - bin/LOLastfm-cmus
75
+ - bin/LOLastfm-do
76
+ - lib/LOLastfm.rb
77
+ - lib/LOLastfm/cache.rb
78
+ - lib/LOLastfm/checker.rb
79
+ - lib/LOLastfm/checkers/cmus.rb
80
+ - lib/LOLastfm/checkers/moc.rb
81
+ - lib/LOLastfm/checkers/process.rb
82
+ - lib/LOLastfm/commands/glyr.rb
83
+ - lib/LOLastfm/connection.rb
84
+ - lib/LOLastfm/song.rb
85
+ - lib/LOLastfm/version.rb
86
+ homepage: http://github.com/meh/LOLastfm
87
+ licenses: []
88
+ post_install_message:
89
+ rdoc_options: []
90
+ require_paths:
91
+ - lib
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ! '>='
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ! '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ requirements: []
105
+ rubyforge_project:
106
+ rubygems_version: 1.8.24
107
+ signing_key:
108
+ specification_version: 3
109
+ summary: LOL a scrobbler.
110
+ test_files: []