Last active
September 26, 2015 20:38
-
-
Save youpy/1156159 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
# -*- coding: utf-8 -*- | |
# | |
# for OSX only | |
# | |
# $ gem install launch-agent | |
# $ launchagent --daemon --env "RBENV_VERSION=2.0.0-p0" -- zsh -c "ruby /path/to/scrobbler.rb" | |
require 'rubygems' | |
require 'lastfm' | |
require 'soundcloud' | |
require 'pit' | |
require 'eventmachine-itunes' | |
require 'logger' | |
class VLogger < Logger | |
def initialize(*args) | |
super(*args) | |
Thread.start do | |
loop do | |
@logdev.dev.puts('') | |
sleep 5 | |
end | |
end | |
end | |
end | |
$stdout.sync = true | |
$logger = | |
# VLogger. | |
Logger. | |
new(STDOUT) | |
class Faver < EventMachine::ITunesWatch | |
def on_play(info) | |
description = info['Description'] | |
rating = info['Rating'].to_i | |
if rating > 0 | |
if description && matched = description.match(/^http:\/\/soundcloud[^ ]+/) | |
favorite(matched[0]) | |
end | |
end | |
end | |
def favorite(url) | |
$logger.info("(%s) favorite" % url) | |
soundcloud.put(api_base_url + '/me/favorites/%i' % track(url).id) | |
end | |
def track(url) | |
soundcloud.get(api_base_url + '/resolve.json?url=%s&client_id=%s' % [url, soundcloud.client_id]) | |
end | |
def api_base_url | |
'http://api.soundcloud.com' | |
end | |
def config | |
@config ||= Pit.get("soundcloud.com", :require => { | |
"client_id" => "your client ID in soundcloud.com", | |
"client_secret" => "your client secret in soundcloud.com", | |
"username" => "your username in soundcloud.com", | |
"password" => "your password in soundcloud.com" | |
} | |
) | |
end | |
def soundcloud | |
@soundcloud ||= | |
Soundcloud.new( | |
:client_id => config['client_id'], | |
:client_secret => config['client_secret'], | |
:username => config['username'], | |
:password => config['password'] | |
) | |
end | |
end | |
class Scrobbler < EventMachine::ITunesWatch | |
def cancel_timer | |
@scrobble_timer.cancel if @scrobble_timer | |
$logger.info 'cancel timer' | |
end | |
def on_pause(info) | |
clear_now_playing | |
cancel_timer | |
end | |
def on_stop(info) | |
clear_now_playing | |
cancel_timer | |
end | |
def on_play(info) | |
name = info['Name'] | |
artist = info['Artist'] | |
rating = info['Rating'].to_i | |
duration = info['Total Time'] / 1000 | |
description = info['Description'] | |
tag = nil | |
if name && artist | |
if start_playing?(artist, name) | |
cancel_timer | |
update_now_playing(artist, name, duration) | |
set_timer_if_needed(duration) do | |
scrobble(artist, name, duration) | |
end | |
end | |
remove_rating_tag(artist, name) | |
if rating > 0 | |
love(artist, name, rating) | |
tag = (rating / 20).to_s | |
add_tag(artist, name, tag) | |
end | |
end | |
end | |
def scrobble(artist, name, duration) | |
lastfm.track.scrobble( | |
:artist => artist, | |
:track => name, | |
:timestamp => Time.now.to_i, | |
:duration => duration) | |
$logger.info("(%s / %s) scrobble" % [name, artist]) | |
end | |
def clear_now_playing | |
@now_playing = nil | |
$logger.info 'clear now playing' | |
end | |
def update_now_playing(artist, name, duration) | |
lastfm.track.update_now_playing( | |
:artist => artist, | |
:track => name, | |
:duration => duration) | |
@now_playing = [artist, name].join('&&') | |
$logger.info("(%s / %s) update_now_playing" % [name, artist]) | |
end | |
def start_playing?(artist, name) | |
@now_playing != [artist, name].join('&&') | |
end | |
def love(artist, name, rating) | |
if rating > 80 | |
lastfm.track.love( | |
:artist => artist, | |
:track => name) | |
$logger.info("(%s / %s) love" % [name, artist]) | |
else | |
lastfm.track.unlove( | |
:artist => artist, | |
:track => name) | |
$logger.info("(%s / %s) unlove" % [name, artist]) | |
end | |
end | |
def add_tag(artist, name, tag) | |
lastfm.track.add_tags( | |
:artist => artist, | |
:track => name, | |
:tags => tag) | |
$logger.info("(%s / %s) add tag: %s" % [name, artist, tag]) | |
end | |
def remove_rating_tag(artist, name) | |
tags = lastfm.track.get_tags( | |
:artist => artist, | |
:track => name) || [] | |
tags.each do |t| | |
if t['name'] =~ /^\d+$/ | |
lastfm.track.remove_tag( | |
:artist => artist, | |
:track => name, | |
:tag => t['name']) | |
$logger.info("(%s / %s) remove tag: %s" % [name, artist, t['name']]) | |
end | |
end | |
end | |
# http://www.lastfm.jp/api/scrobbling#when-is-a-scrobble-a-scrobble | |
# | |
# The track must be longer than 30 seconds. | |
# And the track has been played for at least half its duration, or for 4 minutes (whichever occurs earlier.) | |
# | |
def set_timer_if_needed(duration, &callback) | |
return unless duration > 30 | |
half_duration = duration / 2 | |
four_mins = 60 * 4 | |
timer_sec = half_duration > four_mins ? four_mins : half_duration | |
$logger.info 'add timer (fired after %i seconds)' % timer_sec | |
@scrobble_timer = EventMachine::Timer.new(timer_sec) do | |
callback.call | |
end | |
end | |
def lastfm | |
return @lastfm if @lastfm | |
config = Pit.get("last.fm", :require => { | |
"api_key" => "your api key in last.fm", | |
"api_secret" => "your api secret in last.fm" | |
}) | |
lastfm = Lastfm.new( | |
config['api_key'], | |
config['api_secret']) | |
unless session = config['session'] | |
token = lastfm.auth.get_token | |
auth('http://www.last.fm/api/auth/?api_key=' + config['api_key'] + '&token=' + token) | |
session = lastfm.auth.get_session(:token => token)['key'] | |
Pit.set('last.fm', :data => { | |
"session" => session | |
}.merge(config)) | |
puts "Session key was generated." | |
end | |
lastfm.session = session | |
@lastfm = lastfm | |
end | |
def auth(auth_url) | |
unless system('open', auth_url) | |
print 'open ' + auth_url + ' in your browser and ' | |
end | |
print 'after authorization, push any key:' | |
result = STDIN.gets.chomp | |
puts | |
result | |
end | |
end | |
def main | |
EM.run do | |
[Scrobbler, Faver].each do |klass| | |
EM.watch_itunes(klass) | |
end | |
end | |
end | |
main |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment