#!/usr/bin/perl
#
# (C) Dave Hansen <dave@sr71.net> June 2005
#
# This program is licensed according to the GPL.
#
# usage: costco-upload.pl [username] [password] files...
#
# There are a lot of really good merchants out there that 
# print pictures quite cheaply.  Costco is one of the cheapest.
# But, they don't have any native Linux support.  You have to
# use the clumsy web interface to upload pictures.  After 
# hitting the browse button for the 19th time, it gets annoying.
#
# So, I wrote this.  It walks through the Costco web pages, and
# uploads files through the same web interface, but
# automatically.  I can do this all from the normal command-line
# in Linux.  This is handy because I keep my pictures on a web
# server, and I like to submit them directly from there sometimes.
#
# Note: Costco evidently licenses/outsources to Snapfish.  This
# code would probably work with relatively few modifications
# for that site as well.
#
# If they ever change very much stuff on their site, this will
# break.  It's fragile.
#

#
# This function takes a bunch of javascript, and returns only
# the portions that were quoted.
#
# foo = "bar" + "baz";
#
# would return "barbaz"
#
# There's a special case in here for extract() and to 
# escape the :'s into % notation
#
sub strip_js
{
        my $js = shift;
        my $doprint = 0;
        my $res;
        $js =~ s/escape\('(.*)'\)/"$1"/;
        $str = $1;
        ($str_exc = $str) =~ s/:/%3A/g;
        $js =~ s/$str/$str_exc/;
        for ($i=0; $i < length($js)-1; $i++) {
                $char = substr($js, $i, 1);
                if ($char =~ /"/) {
                        $doprint = !$doprint;
                        next;
                }
                if ($doprint) {
                        $res .= $char;
                }
        }
        return $res
}

# find a form on a page which has an "action" matching the pattern
sub get_form
{
        ( $form_name, $content, $base_page ) = @_;
        my @forms = HTML::Form->parse($content, $base_page);
        my $form;
        foreach $form (@forms) {
                if ( $form->action =~ /$form_name/ ) {
                        return $form;
                }
        }
}

use HTTP::Cookies;
use HTTP::Request;
use HTTP::Request::Common;
use URI::URL;
use LWP::UserAgent;
require HTML::LinkExtor;
use HTML::Form;
$ua = LWP::UserAgent->new;
push @{ $ua->requests_redirectable }, 'POST';
$cookie_pak = HTTP::Cookies->new;
$ua->cookie_jar($cookie_pak);

$username = shift @ARGV;
$password = shift @ARGV;

#
# Login, and get a cookie set
#
$url = 'http://www.costcophotocenter.com/loginsubmit/t_=0';
$res = $ua->request(POST $url,
	     [ emailaddress => $username,
	       password => $password,
	       savepassword => "ON",
	       "log in.x" => "21",
	       "log in.y" => "8",
	       ]);

#
# The "expressorder" link creates a new album with a name which
# matches the current date.
#
$url = "http://www.costcophotocenter.com/expressorder";
$res = $ua->request(GET $url);

# 
# This url is for a page which makes you agree that you're the copyright holder
# for the pictures.  Instead of parsing through it, I noticed that you can
# just skip right to the upload page by doing a simple substitution on the url.
#
$upload_to_end = substr($res->content, index($res->content, "/uploadcopyright"));
$upload_path = substr($upload_to_end, 0, index($upload_to_end, "\"")-1);
$upload_path =~ s/uploadcopyright/htmlupload/;

#
# The URL that the form submits to is generated at page-load time by the
# browser from a bunch of javascript.  It's pretty simple, so we
# replicate it here.  It basically looks like this:
#
# var url = null;
# url = "a" + "b" + "c" + extract('foo') + "d"...
# mForm.action = url;
#
sub extract_form_url_from_js
{
	my $webpage = shift;
	my $code = "";
	$print = 0;
	@lines = split("\n", $webpage);
	chomp @lines;
	foreach $line (@lines) {
		if ($print) {
			$code .= $line;
		}
		# the line of code preceeding the string creation
		if ($line =~ /var url = null;/) {
			$print = 1;
		}
		# the line after
		if ($line =~ /mForm.action = url;/) {
			$print = 0
		}
	}

	$url = strip_js($code);
}
$url = "http://www.costcophotocenter.com$upload_path";

#
# the "main loop".  We could do at least 12 pictures per form
# submission, but this is simpler to code, and just causes a 
# few extra pages to be fetched.
#
while ($file = shift @ARGV) {
	#print STDERR "upload form page url: '$url'\n";
	$res = $ua->request(GET $url);
	
	$url = extract_form_url_from_js($res->content);
	#print STDERR "upload form button url: '$url'\n";
	$res = $ua->request(POST $url,
			Content_Type => 'form-data',
			Content      => 
		     [  " upload photos .x" => 1,
			" upload photos .y" => 1,
			"file0" => [ $file ],
		       ]);
	print STDERR "upload result page length: ".length($res->content)."\n";

	#
	# After uploading the pictures, there's a "confirmation page".  Going
	# through this allows us to upload more pictures into the same album.
	#
	$upload_confirm_form = get_form("uploadsubmitconfirm", $res->content, $url);
	$request = $upload_confirm_form->click();
	$res = $ua->request($request);

	sub callback_upload_more {
		my($tag, %attr) = @_;
		return if $tag ne 'a';
		return if $attr{href} !~ /htmlupload/;
		$url = $attr{href};
	}
	# there's a redirection that goes on somewhere in here, and you
	# can't use the previous $url as the base for the next one
	$p = HTML::LinkExtor->new(\&callback_upload_more, "http://www.costcophotocenter.com/");
	$p->parse($res->content);
}

