# i7AWF.pm - $Id$ # # Fetches (free) artwork from iTMS, iTunes 7 style. # Copyright (c) 2006 Jesper Noehr # 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 Create a new i7AWF object. =item B Sets the artist to the given argument. =item B Sets the album to the given argument. =item B Will perform the actual request to the iTMS servers. Upon success, it will return the URL. =item B 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 =head1 COPYRIGHT =over 4 Copyright (c) 2006 Jesper Noehr. All rights reserved. Licensed under the Creative Commons share-alike license. =back =cut 1;