LOLastfm 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: []