#!/usr/local/bin/perl
#
#  JLJ
#
#  Jerry's LiveJournal text-only perl script
#
# 	Fri Jan 18 16:45:43 EST 2002
#	Version 2.1
#
#   Jerry's Homepage:     http://www.cis.rit.edu/~jerry/
#   Jerry's LiveJournal:  http://jerronimo.livejournal.com/
#
#  Use this freely.  There is no copyright or obligation to send me anything
#  for this script.  Feel free to do with it as you will.
#
#  No Warranty is either expressed or implied with this software.  Use it
#  at your own risk!

# you need to have a .livejournal.rc file in your $HOME directory.
# it needs at least "user: YourUsername" and "password: YourPassword"
# lines in it.  There should be a sample file included with this package.

my( $ver, $verd );

$ver="2.1";
$verd="2002-January-18";

# 2.1 2002-01-18
#	tweaked wiki formatting to handle things better, uses ^ instead of @
#	fixed the first-line-blank bug with jerry_format
#	bugfix: postponed messages not sending properly (or at all)
#
# 2.0.g
#	added in my jerryformat (it is easy enough to add in other formatters)
#	- WIKI style formatting, easy to modify in the file, search for
#	  jerry_format  for the methods. (at the bottom of this file)
#	  (this has issues.)
#	added the "delete" selection to the final question.
#
# 2.0.f
#	coded my mkdir -p in perl
#	switched the setup_dirs to use the above
#
# 2.0	2002-01-11
#	Added prompt for username/password if they are not found in the rc file
#	Finshed up prompting for all of the extra questions
#	Changed the .rc file a little to account for new options
#	Removed more unused code
#	Put the BBS style editor back in.
#	disabled editing of postponed messages if there's no editor.
#	I think it's at a good working state now...
#	** Russian language support is now non-functioning. **
#
# 2.0-a 2002-01-10
#	Full re-write of most of the code and flow
#	Edit queue files offline or online
#	Postpone entries to be edited later
#	Failed postings get stored in a queue directory 
#	Flushes of the directory can be forced.
#	re-arranged code, removed unused blocks, commented functions
#
# 1.11 Added in the checkfriends option
#
# 1.10 Added in David Lindes' error code message
#      David Lindes <lindes@daveltd.com>
#      Added in Alexey Bestciokov's russian code, and it seems to work fine.
#      Alexey Bestciokov <proforg@idbh.ru>
#
# 1.9  Added the =1 to the login string so that pictures work again
#      switched 'autoformat' to 'preformatted' to reduce confusion
#      (Change in the .livejournal.rc as well!)
#
# 1.8  We now check for $VISUAL along with $EDITOR
#      Fixed the way data is sent to the server.  
#      It should be \r\n rather than \n alone.  oops.
#      Thanks to the lj_dev crew for finding that one for me!  :D
#
# 1.7  Fixed security posting (everyone/all friends/private)
# 
# 1.6  Cleaned up leading and trailing whitespace on mood autoselection
#      Picture selector doesn't ask if you only have one picture/Icon
#      Post to community journals
#
# 1.5  Tweaks for it to work under linux... I rewrote submit_form_to_server()
#      mood id selector with automatic chooser now
#      picture selector
#      integrated in Adam T. Lindsay's proxy code
#	Adam T. Lindsay <atl@comp.lancs.ac.uk> 
#
# 1.4a Fixed some code so that backups now include Music and Mood properly
#
# 1.4  Added music & mood, re-wrote most other code.
#
# 1.3  Added backup, to keep a local copy of the journal.
#
# 1.2  Added the paragraph cleaner. (joins paragraphs together)
#
# 1.1  Added editor check, so you can edit with 'vi'.
#      It now submits the date/time of when you finish writing.
#
# 1.0  Basic functionality


###########################
# Todo list:
#
# - cache the mood list (less server traffic) 
#
# - cache the image and community list for offline posting
#
# - speeling checker (ispell?)
#
# - autoselect icon based on mood entered
#
# - store the itemid back into the file once it has been posted

######### Perl Behaviour
require 5.002;
use strict;
use Socket;

$|=1;

# form details:
#
#   webversion			full
#   subject			subject line
#   event			text. html ok.
#   security			'public' 'private' 'friends'
#   prop_opt_preformatted	preformat (was the code preformatted?)
#   prop_current_moodid		mood id number (automatic selector in effect)
#   prop_current_mood 		mood text box
#   prop_current_music		music text box
#   prop_picture_keyword 	select a picture to use (select via text)
#   use_journal			select a journal to post to


######################################################################
# Globals
######################################################################

my( %login_hash, %mood_hash );
my( $year, $month, $day, $hour, $min, $sec );
my( $offline, $postponedfile, $queuefilename );
my( $tempfilename, $quick );

my( $rawmood, $rawmusic, $rawpicture, $rawcommunity );
my( $basedir, $queuedir, $postponeddir, $sentdir );

my( %rcfile, $seperator );


$seperator = "--- Edit your event text below this line.  Do not edit this line. ---";


######################################################################
# Initialization functions
######################################################################

# init_date
#   setup the internally date variables
sub init_date
{
    my( $ddd, $mmm );
    ($sec,$min,$hour,$ddd,$mmm,$year)= localtime(time);
    $year += 1900;
    $month = $mmm+1;
    $day   = $ddd;

    return;

    printf "Date: %04d-%02d-%02d  %02d:%02d\n", 
		$year,$month,$day,
		$hour,$min;
}

# read_rc
#   read in the rc file into the %rcfile hash.
sub read_rc 
{
    my ( $value, $rcfiledata, $line, $key, $node );
    $rcfiledata = sprintf "%s/.livejournal.rc", $ENV{"HOME"};

    if (!-e $rcfiledata)
    {
	printf "$rcfiledata: file not found!\n";
	printf "create one!  ie:\n\n";
	printf "user: YourUserName\n";
	printf "password: YourPassword\n\n";
	exit(1);
    }

    open IF, "$rcfiledata";
    foreach $line (<IF>)
    {
	chomp $line;
	($key, $node) = split ":", $line;
	next if ($key eq "" || $node eq "");
	$key =~ s/\s//g;
	#$node =~ s/\s//g;
	$key = lc $key;
	$rcfile{$key} = (split " ", $node)[0];
    }

    if (!exists $rcfile{"user"})
    {
	$rcfile{"user"} = get_user_input_line("LJ Username");
    }
    if (!exists $rcfile{"password"})
    {
	$rcfile{"password"} = get_user_input_line("LJ Password");
    }

    close IF,
}


# setup_dirs
#   sets up the path variables
#   makes the directories if they don't exist
sub setup_dirs
{
    $basedir = $ENV{"HOME"} . "/.jlj";
    if (!-e $basedir)      { mkdir_p( $basedir ); }

    $queuedir = $basedir . "/queue";
    if (!-e $queuedir)     { mkdir_p( $queuedir ); }

    $postponeddir = $basedir . "/postponed";
    if (!-e $postponeddir) { mkdir_p( $postponeddir ); }

    $sentdir = $basedir . "/sent";
    if (!-e $sentdir)      { mkdir_p( $sentdir ); }
}


######################################################################
# User prompts and things
######################################################################

# get_user_input_line
#   display a prompt, and return what the user entered
sub get_user_input_line
{
    my ($text, $inline);
    $inline = "";
    $text = shift;

    printf "%s? ", $text;

    $inline = <STDIN>;
    chomp $inline;
    return $inline;
}

# get_user_input_fancy
#   a nifty automatic prompt method
#   param 1 is the default value on the following list =0 for first, etc
#   param 2..N are the list of parameters
sub get_user_input_fancy
{
    my ( @selections, $default, $prompt, $value, $cc, $tv, $dv, $done );
    $default = shift;

    $cc = 0;

    $tv = shift;
    while ( $tv ne "")
    {
	if ($cc == $default)
	{
	    $dv = $tv;
	    $prompt .= "/[$tv]";
	} else {
	    $prompt .= "/$tv";
	}
	push @selections, $tv;

	$cc++;
	$tv = shift;
    }
    
    $prompt =~ s/^\///g;

    $done = 0;
    while ( $done == 0 )
    {
	my ( $selection );

	$value = lc get_user_input_line( $prompt );
	if ( $value eq "" )
	{
	    return( $dv );
	}
	
	foreach $selection (@selections)
	{
	    if ((substr $selection, 0, 1) eq (substr $value, 0, 1))
	    {
		return( $selection );
	    }
	}
	printf "ERROR: Invalid input.  Try again.\n";
    }

    return( $value );
}


# get_user_input_picture
#   user selects which picture  
sub get_user_input_picture
{
    my ( $npics, $x, $inline, $pic_done );

    $pic_done = 0;
    $npics = int $login_hash{"pickw_count"};

    if ($npics <= 1)
    {
	# there's only one picture, no point in even asking.
	return;
    }
    
    while (!$pic_done)
    {
	printf "-----\n";
	for ($x=1 ; $x <= $npics ; $x++)
	{
	    printf "%3d  %s\n", $x, $login_hash{"pickw_".$x};
	}

	printf "-----\n";
	printf "Enter a number or hit enter for default. \n";
	printf "? ";

	$inline = <STDIN>;
	chomp $inline;

	if ($inline eq "")
	{
	    return "";
	}

	if (int $inline > $npics)
	{
	    printf "Value of 1 thru %d was expected, and not %d\n", 
		    int $login_hash{"pickw_count"},
		    int $inline;
	} else {
	    return $login_hash{"pickw_". int $inline};
	}
    }
}


# get_user_input_community
#   user selects which journal  
sub get_user_input_community
{
    my ( $ncommunities, $x, $inline, $community_done );

    $community_done = 0;
    $ncommunities = int $login_hash{"access_count"};

    if ($ncommunities == 0)
    {
	# no communities.  just return
	return;
    }
    
    while (!$community_done)
    {
	printf "-----\n";
	printf "%3d  %s\n", 0, "(your own journal)";

	for ($x=1 ; $x <= $ncommunities ; $x++)
	{
	    printf "%3d  %s\n", $x, $login_hash{"access_".$x};
	}

	printf "-----\n";
	printf "Enter a number or hit enter for your own journal. \n";
	printf "? ";

	$inline = <STDIN>;
	chomp $inline;

	if ($inline eq "")
	{
	    return "";
	}

	if (int $inline > $ncommunities)
	{
	    printf "Value of 0 thru %d was expected, and not %d\n", 
		    $ncommunities, int $inline;
	} else {
	    return $login_hash{"access_". int $inline};
	}
    }
}




######################################################################
# Web interaction functions
######################################################################

# webize
#   Convert special characters to be web form friendly
sub webize
{
    my ($text);
    $text = shift;

    $text =~ s/(\W)/ sprintf "%%%02lX", ord $1 /eg;
    
    return $text;
}

# recode
#   russian conversion
sub recode
{
    my( $text, $direction, @wk, @kw, $in, $i, $res, %encode);

    @wk = (128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,
	     144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,
	     160,161,162,163,164,165,166,167,179,169,170,171,172,173,174,175,
	     176,177,178,179,180,181,182,183,163,185,186,187,188,189,190,191,
	     225,226,247,231,228,229,246,250,233,234,235,236,237,238,239,240,
	     242,243,244,245,230,232,227,254,251,253,255,249,248,252,224,241,
	     193,194,215,199,196,197,214,218,201,202,203,204,205,206,207,208,
	     210,211,212,213,198,200,195,222,219,221,223,217,216,220,192,209);

    @kw = (128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,
	     144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,
	     160,161,162,184,164,165,166,167,168,169,170,171,172,173,174,175,
	     176,177,178,168,180,181,182,183,184,185,186,187,188,189,190,191,
	     254,224,225,246,228,229,244,227,245,232,233,234,235,236,237,238,
	     239,255,240,241,242,243,230,226,252,251,231,248,253,249,247,250,
	     222,192,193,214,196,197,212,195,213,200,201,202,203,204,205,206,
	     207,223,208,209,210,211,198,194,220,219,199,216,221,217,215,218);

    %encode = ( wk => \@wk, kw => \@kw);

	
    $text = shift;
    $direction = shift;

    if ($direction ne "wk" and $direction ne "kw")
    {
        return $text;
    };


    $res = "";
    
    for ($i=0; $i<length($text); $i++)
    {
        $in = ord(substr($text, $i, 1)) & 0377;
        
        if($in & 0200)
        {   
            $res .= chr($encode{$direction}[$in & 0177]);
        }
        else
        {
           $res .= substr($text, $i, 1);
        }
    }

    return $res;
}

########################################

# submit_form_to_server
#   takes the passed-in form and shuffles it off to the server
sub submit_form_to_server
{
    my ($in_addr, $addr, $proto, $remotehost, $remoteport, $length );
    my ($fullbody, $retval, $resp, $body, $webpath);
    my ($submiturl);

    $body = shift;
    $retval = "";

    #$submiturl = "/cgi-bin/log.cgi";
    $submiturl = "/interface/flat";

    if ($rcfile{"proxy"} eq "yes")
    {
        if (!defined ($rcfile{"proxyhost"}))
        {
            printf ".livejournal.rc file is incomplete!\n";
            printf "When using a proxy, the proxy host needs to be defined!\n";
            exit(1);
        }   else   {
            $webpath = sprintf "http://www.livejournal.com%s", $submiturl;
            $remotehost = $rcfile{"proxyhost"};
            if (defined($rcfile{"proxyport"}))
            {
                $remoteport = $rcfile{"proxyport"};
            }  else  {
                $remoteport = 80;
            }
        }    
    }   else   {
        $webpath = $submiturl;
        $remotehost = "www.livejournal.com";
        $remoteport = 80;
    }
     
    $length = length $body;

    $fullbody=<<EOB;
POST $webpath HTTP/1.0
Host: www.livejournal.com
User-Agent: JLJ $ver (Jerry's Live Journal Client)
Content-type: application/x-www-form-urlencoded
Content-length: $length

$body
EOB

    # Form the HTTP server address from the host name and port number
    $in_addr = (gethostbyname($remotehost))[4];
    $addr = sockaddr_in( $remoteport, $in_addr );
    $proto = getprotobyname( 'tcp' );

    # Create an Internet protocol socket.
    if (! socket( WEB, AF_INET, SOCK_STREAM, $proto ) )
    {
	printf "ERROR: Host unreachable!\n";
	return( "" );
    }

    # Connect our socket to the server socket.
    if (! connect( WEB, $addr ))
    {
	printf "ERROR: Could not connect to server!\n";
	return( "" );
    }

    # For fflush on socket file handle after every write.
    select(WEB); $| = 1; select(STDOUT);

    # clean up the body -- this is important.
    $fullbody =~ s/\n/\r\n/mg;

    # Send get request to server.
    print WEB "$fullbody\n";     

    while (read WEB, $resp, 10)
    {                          
        $retval .= $resp;
    }

    close (WEB);

    return $retval;
}


# parse_server_response
#   take the response from the above, and sift it out into an easy to use hash
sub parse_server_response
{
    my( $response, @lines, $li, $past_header, $x, $respkey );
    my( %sifted );
    $response = shift;

    @lines = split /[\n\r]/, $response;

    $x = 0;
    $respkey = "";

    foreach $li (@lines)
    {
	if (0 == length $li)
	{
	    $x++;
    	} 
        else 
        {
	    $x=0;
	    if ($past_header)
	
	    {
		if ($respkey eq "")
		{
		    $respkey = $li;
        		} 
                else 
                {
    		        $sifted{ $respkey } = &recode($li, "wk");
		    $respkey = "";
		}
	    }
	}
	if ($x > 2)
	{
	    $past_header = 1;
	}
    }

    return %sifted;
}


######################################################################
# Login etc
######################################################################

# figure_out_moods
#   a helper for the login method.
#   It generates the mood hash so that we can submit the proper mood id number
#   (this will get used when posting to the server)
sub figure_out_moods
{
    my( $keyid, $keyname, $mood, $x );

    $x = 1;
    while (1)
    {
	$keyid   = sprintf "mood_%d_id", $x;
	$keyname = sprintf "mood_%d_name", $x;

	$mood = lc $login_hash{$keyname};
	$mood =~ s/\s+/ /g;
	$mood =~ s/^\s//g;
	$mood =~ s/\s$//g;

	if ($mood ne "")
	{
	    $mood_hash{$mood} = int $login_hash{$keyid};
	} else {
	    return;
	}
	$x++;
    }
}


# login
#   connects to the server, tells who we are, gets community and icon info
sub login
{
    my( $response, $user, $password, $form, $success, $loginname );
    my( $clientversion );
    $user     = $rcfile{"user"};
    $password = $rcfile{"password"};

    # we really should cache the moods locally, to reduce the traffic...
    $form= sprintf "mode=login&user=$user&password=$password&clientversion=perl-JLJ/%s&getmoods=0&getpickws=1" , $ver;

    $response = submit_form_to_server ($form);

    %login_hash = parse_server_response($response);
    &figure_out_moods;

    $success   = $login_hash{"success"};
    if ($success ne "OK")
    {
	# thanks to David Lindes <lindes@daveltd.com>
	printf "Error Message returned:\n%s\n",
		(defined($login_hash{"errmsg"}) ?
		$login_hash{"errmsg"} :
		"[None, Sorry]");
	exit;
    }

    $loginname = $login_hash{"name"};
    printf "Welcome, %s.\n", $loginname;
}


# checkfriends
#   checks to see if someone on our friends list has made a post.
#   (standalone function)
sub checkfriends
{
    my( $response, $user, $password, $form, $success, $lastupdate );
    my( %checkfriends_hash, $key );
    $user     = $rcfile{"user"};
    $password = $rcfile{"password"};

    open LC, "$basedir/lastupdate";
    $lastupdate = <LC>;
    close LC;
    chomp $lastupdate;
    if ($lastupdate ne "")
    {
	$lastupdate = sprintf "&lastupdate=%s", $lastupdate;
    }

    $form= sprintf "mode=checkfriends&user=$user&password=$password%s",
		    $lastupdate;

    $response = submit_form_to_server ($form);

    %checkfriends_hash = parse_server_response($response);

    if ($checkfriends_hash{"success"} ne "OK")
    {
	printf "Error Message returned:\n%s\n",
		(defined($checkfriends_hash{"errmsg"}) ?
		$checkfriends_hash{"errmsg"} :
		"[None, Sorry]");
	exit;
    }

    if ($checkfriends_hash{"lastupdate"} ne "")
    {
	open LC, ">$basedir/lastupdate";
	printf LC "%s\n", $checkfriends_hash{"lastupdate"};
	close LC;
    }

    printf "new=%d\n", $checkfriends_hash{"new"};
    printf "interval=%d\n", $checkfriends_hash{"interval"};
}


######################################################################
# Entry interactions
######################################################################

# create_queuefile
#   makes the initial queue file to be edited and eventually posted.
sub create_queuefile
{
    my( $datefmt, $user );
    my( $subject, $community, $security, $mood, $music, $picture, $format );

    $user = shift;
    $community = shift;

    $subject = shift;
    $music = shift;
    $mood = shift;
    $security = shift;

    $picture = shift;
    $format = shift;

    # generate variables
    $datefmt = sprintf "%04d-%02d-%02d %02d:%02d:%02d",             
            $year, $month, $day, $hour, $min, $sec;


    # generate queuefilename
    $queuefilename = $queuedir . "/" . $tempfilename;

    open OF, ">$queuefilename";
    print OF <<EOB;
Server:    www.livejournal.com
User:      $user
Community: $community
Format:	   $format	(jerry/preformatted/none)
Security:  $security	(everyone/private/friends)
Music:     $music
Picture:   $picture
Mood:      $mood
Date:      $datefmt
Subject:   $subject

$seperator

EOB
    close OF;
}


# postponed_check
#   submenu and listing of postponed messages.
#   the initial menu
sub postponed_check
{
    my ( $done, @dlist, $prompt );

    # let the user edit a new file, or selct a postponed file
    $postponedfile = "";
    $done = 0;

    @dlist = get_dir_listing( $postponeddir );

    if ( (scalar @dlist == 0)
	|| (!defined$ENV{"EDITOR"} && !defined $ENV{"VISUAL"})
	)
    {
	if ($offline == 1)
	{
	    return;
	}
	$prompt = "[new]/offline";
    } else {
	if ($offline == 1)
	{
	    $prompt = "[offline]/list/<number>";
	} else {
	    $prompt = "[new]/offline/list/<number>";
	}
    }


    while ($done == 0)
    {
	my( $val );

	$val = lc get_user_input_line( $prompt );
	if( (($val eq "") && ($offline == 0))
		|| "n" eq substr $val, 0, 1)
	{
	    $postponedfile = "";
	    return;

	} elsif ( (($val eq "") && ($offline == 1))
		|| ("o" eq substr $val, 0, 1)) {
	    $offline = 1;
	    $postponedfile = "";
	    return;

	} elsif ( ("l" eq substr $val, 0, 1) && (scalar @dlist > 0) ) {
	    my( $ii, $cc );

	    printf "-----\n";
	    foreach $ii ( @dlist )
	    {
		my ($fdd, $fss, $fcc);
		$fdd = ""; $fss = ""; $fcc = "";

		open IF, "$postponeddir/$ii";
		foreach (<IF>)
		{
		    if ($_ eq "")
		    {
			close IF;
		    }
		    if ("Date:"     eq substr $_, 0, 5) { $fdd = substr $_, 6; }
		    if ("Subject:"  eq substr $_, 0, 8) { $fss = substr $_, 9; }
		    if ("Community:" eq substr $_, 0, 7) { $fcc = substr $_, 8; }
		}
		close IF;

		$fdd =~ s/[\s]+/ /g;  $fdd =~ s/^ //g;  $fdd =~ s/ $//g;
		$fss =~ s/[\s]+/ /g;  $fss =~ s/^ //g;  $fss =~ s/ $//g;
		$fcc =~ s/[\s]+/ /g;  $fcc =~ s/^ //g;  $fcc =~ s/ $//g;

		if ($fcc eq "")
		{
		    $fcc = $rcfile{"user"};
		}

		# date  journalname  subject 
		printf "%3d  %s  %s  %s\n", $cc++, $fdd, $fcc, $fss;
	    }
	    printf "-----\n";

	} else {
	    # assume it is a number
	    if( int $val == 0  && $val ne "0" )
	    {
		printf "Error: Invalid selection.\n\n";
	    } elsif ( int $val < scalar @dlist ) {
		printf "selected %s\n", $dlist[int $val];
		$postponedfile = $dlist[int $val];
		return;
	    } else {
		printf "Error: Invalid selection.\n\n";
	    }
	}
    }
}

# edit_entry
#   edit an entry, whether it be queue or postponed
sub edit_entry
{
    my( $filepath );
    my( $editor );
    $filepath = shift;

    #check for $VISUAL or $EDITOR
    if (defined $ENV{"EDITOR"})
    {
        $editor = $ENV{"EDITOR"};
    } elsif (defined $ENV{"VISUAL"})
    {
        $editor = $ENV{"VISUAL"};
    } else {
        $editor = "";
    }

    if ($editor eq "")
    {
	my( $line, $done );
        # old school bbs-style editor. ;) 
        printf "Enter body of text.  use a '.' on a line by itself to end.\n";
  
	open OF, ">>$filepath";
        $done = 0;
        while ($done == 0)
        {
            $line = <STDIN>;
            #chomp $line;
    
            if ($line eq ".\n")
            {
                $done = 1;
            } else { 
		print OF $line;
            }
        }
	close OF;
    } else {
	my ( $cmdline );

	$cmdline = sprintf "%s %s %s", 
			$editor, 
			$rcfile{"editoroffset"}, 
			$filepath;

	system( $cmdline );
    }
}


######################################################################
# Posting methods
######################################################################

# submit_file_to_server
#   parses in a file, converts it to a form, and sends it to the server
sub submit_file_to_server
{
    my( $fn, %header, $key, $value, $form, @rawbody, $part, $temp );

    $fn = shift;

    # parse out the values
    $part = 0;
    @rawbody = ();
    open IF, "$fn";
    while (<IF>)
    {
	if ($part == 0)
	{
	    chomp $_;
	    if ($_ eq $seperator)
	    {
		$part = 1;
	    } else {
		$_ != m/^(.+):\s(.+)$/;
		$key = lc $1;
		next if ($key eq "");

		$value = $2;
		$value =~ s/^\s+//g;
		$value =~ s/\s+$//g;

		$header{$key} = $value;
	    }
	} else {
	    push @rawbody, $_;
	}
    }
    close IF;

    # now parse out all of the information to be used...
    $temp = $header{"date"};
    $year  = substr $temp, 0, 4;
    $month = substr $temp, 5, 2;
    $day   = substr $temp, 8, 2;
    $hour  = substr $temp, 11, 2;
    $min   = substr $temp, 14, 2;
    $sec   = substr $temp, 17, 2;

    # construct the form
    my( $user, $password );
    my( $security, $mood, $music, $picture, $community, $format );
    my( %post_hash );
    my( $mood_id );

    $user     = $rcfile{"user"};
    $password = $rcfile{"password"};

    # MUSIC
    $music = "&prop_current_music=". webize($header{"music"});

    # MOOD
    $mood = $header{"mood"};
    $mood =~ s/\s+/ /g;
    $mood =~ s/^\s//g;
    $mood =~ s/\s$//g;
    $mood_id = $mood_hash{ lc $mood };
    if ($mood_id != 0)
    {
	$mood = "&prop_current_moodid=".$mood_id;
    } else {
	$mood = "&prop_current_mood=". webize($mood);
    }

    # FORMAT
    my( $formatname, $body );
    $formatname = (split " ", $header{"format"})[0];
    if( lc $formatname eq "jerry" )
    {
	$format = "&prop_opt_preformatted=1";
    } else {
	$format = ($formatname eq "preformatted")
                ? "&prop_opt_preformatted=1"
                : "&prop_opt_preformatted=0";
    }

    #reformat the body now..
    $body = "";
    if ( "jerry" eq lc $formatname )
    {
	# all formatters should use the same start/line/body methodology
	$body = $body . jerry_format_start();
	foreach( @rawbody )
	{
	    $body = $body . jerry_format_line($_);
	}
	$body = $body . jerry_format_end();
    } else {
	foreach( @rawbody )
	{
	    $body = $body . $_;
	}
    }

    # SECURITY
    $security = (split /\s/, $header{"security"})[0];

    if (lc $security eq "everyone")
    {
	$security = "&security=public";
    } elsif (lc $security eq "friends") {
	$security = "&security=usemask&allowmask=1";
    }  else {
	# if we encountered bad text, default to private to be sure.
	$security = "&security=private";
    }

    # COMMUNITY
    if ($header{"community"} eq "")
    {
	$community = "";
    } else { 
	$community = "&usejournal=".$header{"community"};
    }

    # PICTURE
    if ($header{"picture"} eq "")
    {
	$picture = "";
    } else {
	$picture = "&prop_picture_keyword=" . webize( $header{"picture"} );
    }

    $form = sprintf "mode=postevent&webversion=full&user=$user&password=$password%s&year=%04d&mon=%02d&day=%02d&hour=%02d&min=%02d&subject=%s&event=%s%s%s%s%s%s%s",
		$security,
                $year, $month, $day,
		$hour, $min, 
		webize($header{"subject"}), 
		webize($body), 
		$format, 
		$mood,
		$music,
		$community,
		$picture;

    # send it to the server
    my ( $svr_resp );

    %post_hash =  parse_server_response( submit_form_to_server( $form ));

    if ( !defined $post_hash{"success"} || ($post_hash{"success"} eq "FAIL") )
    {
	# if send failed, tell user, return, bail;
	printf "ERROR: Was not able to send message. (%s)\n", 
			$post_hash{"errmsg"};
	return;
    }

    #feedback to the user:
    printf "item %d posted: %s\n", $post_hash{"itemid"}, $header{"subject"};

    # copy it to the sent directory
    my( $dfn );
    $dfn = sprintf "$sentdir/%04d/%02d/%02d_%02d:%02d:%02d",
		$year, $month, $day, $hour, $min, $sec;
    file_copy( $fn, $dfn );

    # delete the source file
    unlink $fn;
}


######################################################################
# Utility functions
######################################################################

# get_dir_listing
#   Gets a directory listing and returns it (sans . and ..) as a list
sub get_dir_listing
{
    my( $fn, $dp, @dlist );

    $dp = shift;

    opendir ID, $dp;
    foreach $fn (readdir ID)
    {
	next if ( $fn eq "." );
	next if ( $fn eq ".." );
	push @dlist, $fn;
    }
    closedir ID;
    return( @dlist );
}

# mkdir_p
#   recursively makes a directory, disregarding the error codes
sub mkdir_p
{
    my( $path, @dirnames, $cd );
    $path = shift;

    @dirnames = split "/", $path;
    $path = "";

    foreach $cd ( @dirnames )
    {
	$path .= $cd;
	$path .= "/";

	mkdir $path, 0700;
    }
}

# file_copy
#   copies a file from the first param to the second param
sub file_copy
{
    my( $sf, $df, $pn, @pt );
    $sf = shift;
    $df = shift;

    @pt = split "/", $df;
    $pt[(scalar @pt)-1] = "";
    $pn = join "/", @pt;

    mkdir_p( $pn );

    open IF, "$sf";
    open OF, ">$df";
    while (<IF>)
    {
	print OF $_;
    }
    close OF;
    close IF;
}


######################################################################
# main
######################################################################

sub print_credits
{
    printf "\nJLJ version $ver  $verd\n";
    printf "    Scott \"Jerry\" Lawrence   jlj\@umlautllama.com\n";
    printf "    http://jerronimo.livejournal.com\n";
    printf "    http://www.cis.rit.edu/~jerry/Software/perl/#jlj\n\n";
}

sub print_usage
{
    printf "Usage: perl $0 [-c|-f|-h|-j|-o|-q]\n";
    printf "\t-c\t check friends\n";
    printf "\t-f\t flush the send queue\n";
    printf "\t-h\t display this help message\n";
    printf "\t-j\t display JerryWiki escapes\n";
    printf "\t-o\t work offline\n";
    printf "\t-q\t 'quick' mode\n";
}

my( $jerry_wiki );

sub main
{
    my ($process);

    &read_rc;
    &setup_dirs;

    if ($ARGV[0] eq "-h")
    {
	print_credits();
	print_usage();
	return;
    }

    if ($ARGV[0] eq "-j")
    {
	print $jerry_wiki;
	printf "\n";
	return;
    }

    if ($ARGV[0] eq "-c")
    {
	checkfriends();
	return;
    }

    if ($ARGV[0] eq "-f")
    {
	my( $fp, $cc );

	$cc = 0;
	opendir ID, "$queuedir";
	foreach $fp (readdir ID)
	{
	    next if( $fp eq ".");
	    next if( $fp eq "..");

	    if ($cc == 0)
	    {
		&login;
	    }
	    $cc++;
	    submit_file_to_server("$queuedir/$fp");
	}
	if ($cc == 0)
	{
	    printf "Queue is empty.\n";
	}
	closedir ID;

	exit -1;
    }

    if ($ARGV[0] eq "-q")
    {
	$quick = 1;
    }

    if ($ARGV[0] eq "-o")
    {
	$offline = 1;
    }

    &init_date;

    $tempfilename = sprintf "%04d%02d%02d_%02d:%02d:%02d",
	    $year, $month, $day, $hour, $min, $sec;

    if ($quick != 1)
    {
	&postponed_check; # check to see if we're going to unpostpone a post
    }
    
    if ($postponedfile ne "")
    {
	edit_entry( $postponeddir . "/" . $postponedfile );
    } else {
	my( $subject, $music, $mood, $security, $picture, $format, $community );
	if ($offline == 1)
	{
	    printf "NOTE: Working offline!\n";
	    #printf "Using cached lists of communities and images\n";
	    #&load_cached;
	} else {
	    &login;
	}

	# get the subject
	$subject = get_user_input_line("Subject");

	if ( $quick == 1 )
	{
	    $community = "";
	    $music = "";
	    $mood = "";
	    $picture = "";

	} else {
	    if (!defined $rcfile{"communityprompt"} || 
			 $rcfile{"communityprompt"} eq "yes")
	    {       
		$community = get_user_input_community();
	    }

	    if (!defined $rcfile{"musicprompt"} ||
			 $rcfile{"musicprompt"} eq "yes")
	    {
		$music = get_user_input_line("Current Music");
	    }

	    if (!defined $rcfile{"moodprompt"} ||
			 $rcfile{"moodprompt"} eq "yes")
	    {   
		$mood = get_user_input_line("Current Mood");
	    }

	    if (!defined $rcfile{"pictureprompt"} ||
			 $rcfile{"pictureprompt"} eq "yes")
	    {   
		$picture = get_user_input_picture();
	    }
	}

	if ((!defined $rcfile{"security"}) ||
	       ($rcfile{"security"} eq "prompt") )
	{
	    if( $quick == 1 )
	    {
		$security = "everyone";
	    } else {
		$security = get_user_input_fancy( 0, 
			    "everyone", "private", "friends" );
	    }
	} else {
	    $security = $rcfile{"security"};
	}

	if ((!defined $rcfile{"format"}) ||
	       ($rcfile{"format"} eq "prompt") )
	{
	    if( $quick == 1 )
	    {
		$format = "preformatted";
	    } else {
		$format = get_user_input_fancy( 0, 
				"preformatted", "none", "jerry" );
	    }
	} else {
	    $format = $rcfile{"format"};
	}

	&create_queuefile( $rcfile{"user"}, $community, $subject, 
				$music, $mood,
				$security, $picture, $format );

	&edit_entry($queuefilename); # get the body
    }

    if ($offline == 1)
    {
	$process = get_user_input_fancy( 1, "postpone", "queue", "delete" );
    } else {
	$process = get_user_input_fancy( 0, "send", "postpone", "queue", "delete");
    }

    if ($process eq "send")
    {
	my( $entryfile );

	# read in the queue file or postponed file
	if ($postponedfile eq "")
	{
	    $entryfile = $queuefilename;
	} else {
	    $entryfile = "$postponeddir/$tempfilename";

	    # copy it over to the queue directory
	    $queuefilename = $queuedir . "/" . $tempfilename;
	    file_copy( "$postponeddir/$postponedfile", $queuefilename );
	    unlink "$postponeddir/$postponedfile";
	}
	    
	# attempt to send it off to the server
	submit_file_to_server( $queuefilename );

	# that function will copy it to the sent directory, 
	# and delete it from the queue directory.
	# if it failed to connect, it will leave it in the queue directory

	my( @queuedir );
	@queuedir = get_dir_listing($queuedir);

	if (scalar @queuedir > 0)
	{
	    printf "\n====\n";
	    printf "NOTE: You have %d message%s in your queue folder.\n", 
			scalar @queuedir, (scalar @queuedir)>1?"s":"";
	    printf "      run jlj with the '-f' flag to flush the queue.\n";
	}

    } elsif ($process eq "postpone") {
	# if file was postponed already, bail
	printf "Postponing your message...\n";
	if ($postponedfile eq "") { 

	    # copy the queue file to the postponed directory
	    file_copy( $queuefilename, "$postponeddir/$tempfilename" );

	    # delete the queue file
	    unlink $queuefilename;
	}

    } elsif ($process eq "queue") {
	# if file was queued already, bail
	if ($postponedfile ne "")
	{
	    # copy the postponed file to the queue directory
	    file_copy(  "$postponeddir/$postponedfile", 
			"$queuedir/$postponedfile" );

	    # delete the postponed file
	    unlink "$postponeddir/$postponedfile";
	}
    } elsif ($process eq "delete") {
	my( $confirm );
	printf "It will be unrecoverable.  Are you sure you want to delete it?\n";
	$confirm = get_user_input_fancy( 1, "yes", "no" );

	if (lc $confirm eq "yes")
	{
	    if ($postponedfile ne "")
	    {
		unlink "$postponeddir/$postponedfile";
	    } else {
		unlink $queuefilename;
	    }
	}
    }

    print_credits();
}


################################################################################
#  Formatters...
################################################################################
#  these are of the form:
#	"name of formatter"_format_start	gets run first
#	"name of formatter"_format_line($line)	gets run on each line
#	"name of formatter"_format_end		gets run last
# all of these return a 'line of text'
####################

# this should only emit XHTML
#	http://www.w3.org/TR/xhtml1/

# - shortcuts for markup  (Wiki markup)   (format processor)
#	- line by line, if there's no close, it gets ignored.
#	- double character is an escape

my( $wf_nblank, $wf_verbatim );

$jerry_wiki =<<EOB;
JerryWiki formatting:
	type this	to yield this html code
	---------	-----------------------
	*bold*		<b>bold</b>
	**not bold**	*not bold*
	_underline_	<u>underline</u>
	~italic~	<i>italic</i>
	^blue text^	<font color="blue">text</font>

	fo & ba < xo	fo &amp; ba &lt; xo
	fo && ba << xo	fo & ba < xo

	\@-		<hr>  (one or more dashes)
	* foo		(list items)	(not supported)
	** bar	 		 	(not supported)

	^http... foo^	<a href="http...">foo</a>
	^ftp... foo^	<a href="ftp...">foo</a>
	^foo\@... foo^	<a href="mailto:foo\@...">foo</a>
	^img:http...^ 	<img src="http..."/>
	^IMG:http...^ 	<img src="http..."/><br clear="all"/>

	^user foo^	<lj user="foo">
	^frag^		<lj-cut text="frag">
EOB


sub jerry_format_start
{
    $wf_nblank = -1;  # fix the 'starting a post on the second line' bug
    $wf_verbatim = 0;
    return( "<p>" );
}

sub jerry_format_line
{
    my( $line, $pline, $nspline );
    $line = shift;
    chomp $line;   # make sure it's empty.

    # check for and set flags...
    $nspline = $line;
    $nspline =~ s/\s//g;
    if( $nspline eq "")
    {
	$wf_nblank++;
    } else {
	$wf_nblank = 0;
    }

    if(  ( $nspline eq "\@verbatim" )
       || ( $nspline eq "\@v" ) )
    {
	$wf_verbatim = 1;
	return( "<pre>\n" );

    } elsif(  ( $nspline eq "\@endverbatim" ) 
            ||( $nspline eq "\@ev" ) )
    {
	$wf_verbatim = 0;
	$wf_nblank = 0;
	return( "</pre>\n" );

    }

    # process the line...
    $pline = $line;

    #common bits (verbatim and non-verbatim)

    $pline =~ s/\&/\&amp;/g;       #  &  =>  &amp;
    $pline =~ s/\&amp;\&amp;/\&/g; # &&  =>  & 
    $pline =~ s/</\&lt;/g;         #  <  =>  &lt;
    $pline =~ s/\&lt;\&lt;/</g;    # <<  =>  < 
    $pline =~ s/>/\&gt;/g;         #  >  =>  &gt;
    $pline =~ s/\&gt;\&gt;/</g;    # >>  =>  > 

    if( $wf_verbatim )
    {
	# verbatim mode; anything looking like an HTML escape gets hacked
	$wf_nblank = 0;
	###JJJ###$pline .= "<br/>";          # tack on a line break (<pre>)
    } else {
	# non verbatim mode; 
	# == automatically add html tags

	# - check for newlines
	if ($wf_nblank == 1)
	{
	    $pline = "</p><p>";
	} elsif ( $wf_nblank > 1) {
	    $pline = "<br/>";
	}
	# == convert to html tags.

	#italics tag ~foo~  => <i>foo</i>
	$pline =~ s/~~/###TILDE#TILDE###/g;
	$pline =~ s/~(.+)~/<i>$1<\/i>/g;
	$pline =~ s/###TILDE#TILDE###/~/g;

	#bold tag *foo*  => <b>foo</b>
	$pline =~ s/\*\*/###SPLAT#SPLAT###/g;
	$pline =~ s/\*(.+)\*/<b>$1<\/b>/g;
	$pline =~ s/###SPLAT#SPLAT###/\*/g;

	#underline tag _foo_  => <u>foo</u>
	$pline =~ s/__/###UNDERSCORE#UNDERSCORE###/g;
	$pline =~ s/_(.+)_/<u>$1<\/u>/g;
	$pline =~ s/###UNDERSCORE#UNDERSCORE###/_/g;
    }

    if( $wf_verbatim == 0 )
    {
	# Jerry's quick markup stuff...

	# horizontal rule tag  @-  => <hr>
	$pline =~ s/\@-+/<hr><\/hr>/g;

	# ^http://this.is.the/url/  optional link text^
	# ^ftp://this.is.the/url/  optional link text^
	my( $url, $linktext );
	$pline =~ s/\^([h|f]t+p:\/\/\S+)(.*)\^/<a href="$1">###LINK#TEXT###<\/a>/g;
	$linktext = $2;
	$url = $1;
	$linktext =~ s/^\s+//g;
	$linktext =~ s/\s+$//g;
	if ($linktext eq "")
	{
	    $pline =~ s/###LINK#TEXT###/$url/g;
	} else {
	    $pline =~ s/###LINK#TEXT###/$linktext/g;
	}

	# ^foo@bar.com bob^
	my( $emailaddy, $name );
	$pline =~ s/\^(\S+)\@(\S+)(.*)\^/<a href="mailto:$1\@$2">###LINK#TEXT###<\/a>/g;
	$emailaddy = $1 . "\@" . $2;
	$name = $3;
	$name =~ s/^\s+//g;
	$name =~ s/\s+$//g;
	if ($name eq "")
	{
	    $pline =~ s/###LINK#TEXT###/$emailaddy/g;
	} else {
	    $pline =~ s/###LINK#TEXT###/$name/g;
	}

	# ^img:http://this.is.the/url/foo.gif^  image
	$pline =~ s/\^img:(\S+)\^/<img src="$1"\/>/g;
	$pline =~ s/\^IMG:(\S+)\^$/<img src="$1"\/><br clear="all"\/>/g;

	# ^user narf^
	$pline =~ s/\^user (.+)\^/<lj user="$1">/g;

	#color tag ^blue foo^  =>  <font color="blue">foo</font>
	$pline =~ s/\^\^/###CARAT#CARAT###/g;
	$pline =~ s/\^(\S+)\s(.+)*\^/<font color="$1">$2<\/font>/g;
	$pline =~ s/###CARAT#CARAT###/\^/g;

	my( $cuttext );
	# @cut narf
	$pline =~ s/\@(.*)$/<lj-cut ####LJ#CUT####>/g;
	$cuttext = $1;
	$cuttext =~ s/^\s+//g;
	$cuttext =~ s/\s+$//g;
	if( $cuttext eq "" )
	{
	    $pline =~ s/ ####LJ#CUT####//g;
	} else {
	    $pline =~ s/####LJ#CUT####/text="$cuttext"/g;
	}
    }

    $pline = $pline . "\n";
    return( $pline );
}

sub jerry_format_end
{
    if ($wf_verbatim)
    {
	return( "</pre>" );
    }
    return( "</p>" );
}


sub jerry_wiki_test
{
    my( $done, $line );
    $done = 0;
    printf "== %s\n", jerry_format_start();

    while ($done == 0)
    {
	$line = <STDIN>;
	chomp $line;

	if ($line eq ".")
	{
	    $done = 1;
	} else { 
	    printf "== %s", jerry_format_line($line);
	}
    }

    printf "== %s\n", jerry_format_end();
}

################################################################################
################################################################################
&main;
#&jerry_wiki_test;
