# i7AWF.pm - $Id$
#
# Fetches (free) artwork from iTMS, iTunes 7 style.
# Copyright (c) 2006 Jesper Noehr <jesper@noehr.org>
# Version 0.1 - Licensed under the Creative Commons share-alike license.
package i7AWF;
use strict;
use LWP::UserAgent;
use Digest::MD5 qw/md5_hex/;
use MIME::Base64;
use Compress::Zlib;
use XML::Simple;
# Define some constants. Notice the i7_STORE, which points to the local store. Change if you want to.
use constant {
i7_UA => "iTunes/7.0 (Macintosh; U; PPC Mac OS X 10.4.7)",
i7_URL => 'http://ax.phobos.apple.com.edgesuite.net/WebObjects/MZSearch.woa/wa/coverArtMatch?an=%s&pn=%s',
i7_STORE => 143457,
};
sub new {
my( $class ) = shift;
return bless { artist => undef, album => undef, }, $class;
}
# Setters
sub artist {
my( $self, $artist ) = @_;
$self->{ 'artist' } = $artist;
}
sub album {
my( $self, $album ) = @_;
$self->{ 'album' } = $album;
}
# Actual request. Creates new UA if not used before, and creates a new request object, per call.
# Caches result, and passes on to some nifty XML parsers. Notice content is GZIP'ed.
sub payload {
my( $self ) = @_;
if (!defined $self->{ 'UA' }) {
$self->{ 'UA' } = new LWP::UserAgent;
$self->{ 'UA' }->agent( i7_UA );
}
my $comped_url = sprintf( i7_URL, $self->{ 'artist' }, $self->{ 'album'} );
$comped_url =~ s/\s/%20/g;
my $req = HTTP::Request->new( GET => $comped_url );
$req->header
( "X-Apple-Tz" => "7200",
"X-Apple-Store-Front" => i7_STORE,
"Accept-Language","en-us, en;q=0.50",
"X-Apple-Validation" => $self->comp_seed( $comped_url, i7_UA ),
"Accept-Encoding" => "gzip, x-aes-cbc",
"Connection" => "close",
"Host" => "ax.phobos.apple.com.edgesuite.net",
);
$self->{ 'cached' } = $self->{ 'UA' }->request( $req );
if ( !$self->{ 'cached' }->is_success ) {
return $self->{ 'cached' }->status_line;
}
return $self->get_URL( Compress::Zlib::memGunzip( $self->{ 'cached' }->content ) );
}
# Returns the URL (if found), or undef on error. Payload calls this, but you can call it manually on the cached result.
sub get_URL {
my( $self, $result ) = @_;
# If this is called before payload, simply call it.
if ( !$self->{ 'cached' } ) {
return $self->payload;
}
my $xs = new XML::Simple;
my $xh = $xs->XMLin( $result );
my $xp = $xh->{ 'Protocol' }->{ 'plist' }->{ 'dict' };
return undef unless $xp->{ 'integer' } == 0;
return $xp->{ 'string' } [0];
}
# Some magic. Generates a seed we use for X-Apple-Validation. Adapted from LWP::UserAgent::iTMS_Client.
sub comp_seed {
my ( $self, $url, $user_agent ) = @_;
my $random = sprintf( "%04X%04X", rand(0x10000), rand(0x10000) );
my $static = decode_base64("ROkjAaKid4EUF5kGtTNn3Q==");
my $url_end = ( $url =~ m|.*/.*/.*(/.+)$| ) ? $1 : '?';
my $digest = md5_hex( $url_end, $user_agent, $static, $random );
return $random . '-' . uc $digest;
}
=head1 NAME
i7AWF - Fetches artwork from iTMS, iTunes 7 style
=head1 SYNOPSIS
use i7AWF;
my $i7 = new i7AWF;
$i7->artist("Dire Straits");
$i7->album("Sultans of Swing");
print $i7->payload;
=head1 DESCRIPTION
This perl module fetches missing artwork from iTMS, in the style of
how iTunes 7 does it. It will give you an URL, with the 600x600 album cover.
=head1 METHODS
=item B<new>
Create a new i7AWF object.
=item B<artist>
Sets the artist to the given argument.
=item B<album>
Sets the album to the given argument.
=item B<payload>
Will perform the actual request to the iTMS servers.
Upon success, it will return the URL.
=item B<get_URL>
This is what payload calls in the end. It uses the cached
result that came in the payload. This means that you can use this
continuously without re-fetching the request. If there is no cached result,
get_URL will call payload first.
=head1 AUTHOR
Jesper Noehr L<jesper@noehr.org>
=head1 COPYRIGHT
=over 4
Copyright (c) 2006 Jesper Noehr. All rights reserved.
Licensed under the Creative Commons share-alike license.
=back
=cut
1;