Permalink
Browse files

Initial commit of the script.

  • Loading branch information...
0 parents commit 4b3e1f18e71a47732ed9553584f3392a920302af Jonas Kramer committed May 10, 2008
Showing with 197 additions and 0 deletions.
  1. +197 −0 post-fm
197 post-fm
@@ -0,0 +1,197 @@
+#!/usr/bin/perl
+# Copyright (C) 2006-2008 by Jonas Kramer.
+# Published under the terms of the GNU General Public License (GPL).
+#
+# Bug reports and feedback are always welcome.
+# <jkramer at nex dot scrapping dot cc>
+
+use strict;
+use warnings;
+
+use POSIX qw/&strftime/;
+use Digest::MD5 qw/md5_hex/;
+use LWP::UserAgent;
+use IO::File;
+
+
+our %rc = (
+ user => "your-login",
+ pass => "your-password",
+ pid => "$ENV{HOME}/.cmus/last.fm-pid",
+ cache => "$ENV{HOME}/.cmus/last.fm-cache",
+ np => undef,
+ debug => 0,
+);
+
+our $track = "";
+
+sub cleanup {
+ unlink($rc{pid});
+ exit(0);
+};
+
+
+# Append a track to the scrobble cache.
+sub store {
+ my ($track) = @_;
+
+ return unless $track;
+
+ my $cache = new IO::File('>>' . $rc{cache});
+
+ if($cache) {
+ print $cache $track, "\n";
+ $cache->close;
+ }
+
+ else {
+ &debug("Failed to open cache. $!.");
+ }
+}
+
+
+# Append a message to the debug log.
+sub debug {
+ return unless $rc{debug};
+
+ my $log = new IO::File('>>' . $ENV{HOME} . '/post-log') or return;
+
+ print $log @_, "\n";
+ $log->close;
+}
+
+
+# Install signal handler for cleanup.
+$SIG{TERM} = \&cleanup;
+
+
+# If our predecessor is still alive, it's very likely that the song it's
+# going to submit was skipped, so we better kill the process or last.fm
+# will annoy us with pink messages.
+if(-e $rc{pid}) {
+ open(PIDFILE, $rc{pid});
+ my $pid = <PIDFILE>;
+ close(PIDFILE);
+
+ kill("TERM", $pid);
+ my $timeout = 2;
+ sleep(1) while($timeout-- && kill(0, $pid));
+ kill("KILL", $pid);
+ unlink($rc{pid}) if(-e $rc{pid});
+}
+
+# We fork and write the PID of our child into a PID file for the reasons
+# named above. Then we exit, so CMUS can start playing the next song.
+my $pid = fork;
+if($pid) {
+ open(PIDFILE, ">".$rc{pid});
+ print PIDFILE $pid, "\n";
+ close(PIDFILE);
+ exit(0);
+}
+
+# Read track data given from CMUS from @ARGV into a hash. Also get the
+# current time and store it (we will give this timestamp to last.fm later
+# as the time at which we started playing the current track). $timeout is
+# used for two things: 1) as timeout (in seconds) for our GET and POST
+# operations and 2) as the number of seconds we'll have to submit our
+# track data before our successor is going to kill us.
+my $timeout = 10;
+
+our $time = time;
+our %data = @ARGV;
+
+my @keys = qw(artist title album duration);
+foreach(@keys) { &cleanup unless exists($data{$_}) }
+
+$track = join("|", (@data{@keys}, $time));
+# $track = "$data{artist}|$data{title}|$data{album}|$data{duration}|$time";
+
+&debug("Called with arguments:\n\t", join("\n\t", map { "\"$_\"" } (@ARGV)));
+
+if(exists($rc{np}) && length($rc{np})) {
+ if(open(NP, ">".$rc{np})) {
+ print NP "Now playing \"$data{title}\" by $data{artist}.\n";
+ close(NP);
+ } else {
+ &debug("Failed to open $rc{np}. $!.");
+ }
+}
+
+my $agent = new LWP::UserAgent(timeout => $timeout);
+
+# Prepare $user and $password before sleeping; maybe we need to hurry
+# later.
+my $user = $rc{user};
+$user =~ s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/eg;
+$rc{pass} = md5_hex($rc{pass});
+
+# Sleep until we've reached the end of the song - $timeout - 2 seconds.
+sleep($data{duration} - $timeout - 2);
+
+my $url = "http://post.audioscrobbler.com/?hs=true&p=1.1&c=dcp&v=0.1&u=$user";
+my $response = $agent->get($url);
+
+&debug(
+ "Handshake response was:\n\t",
+ join("\n\t", split(/\r?\n/, $response->content))
+);
+
+if(!$response->is_success) {
+ &store;
+ &cleanup;
+}
+
+my @response = split(/[\r\n]+/, $response->content);
+&cleanup if(shift(@response) ne "UPTODATE");
+my ($challenge, $submitURL) = splice(@response, 0, 2);
+my $md5 = md5_hex($rc{pass}.$challenge);
+
+my @tracks = ();
+
+# Read older tracks from cache.
+if(-r $rc{cache}) {
+ open(CACHE, $rc{cache});
+ my @tracks = <CACHE>;
+ close(CACHE);
+}
+
+push(@tracks, $track);
+
+# Put the POST string together.
+my $query = "u=$user&s=$md5&";
+my $ntrack = 0;
+foreach(@tracks) {
+ my @splt = split(/\|/);
+ next if($splt[-2] < 30);
+ foreach(qw/a t b/) {
+ my $val = (shift(@splt) or "");
+ $val =~ s/(\W)/sprintf("%%%02X", ord($1))/egi;
+ $query .= $_."[$ntrack]=$val&";
+ }
+ $query .= "m[$ntrack]=&l[$ntrack]=".shift(@splt)."&i[$ntrack]=";
+ my $time = &strftime(q{%F %T}, gmtime(shift(@splt)));
+ $time =~ s/(\W)/sprintf("%%%02X", ord($1))/eg;
+ $query .= "$time&";
+ ++$ntrack;
+}
+
+&debug("Post data is: $query");
+
+# POST the data to last.fm.
+my $post = HTTP::Request->new("POST", $submitURL);
+$post->content_type("application/x-www-form-urlencoded; charset=\"UTF\"");
+$post->content($query);
+$response = $agent->request($post);
+
+&debug(
+ "Post response was:\n\t",
+ join("\n\t", split(/\r?\n/, $response->content))
+);
+
+# On success we can delete the cache, 'cause all the data in it has been
+# submitted. Otherwise we add the new track to the cache, so it can be
+# submitted later.
+$response->content =~ /^OK/ ? unlink($rc{cache}) : &store;
+
+&cleanup;

0 comments on commit 4b3e1f1

Please sign in to comment.