#!/opt/local/bin/perl 
#
#  JLJ
#
#  Jerry's LiveJournal text-only perl script
#
#	Mon Jan 30 18:12:34 EST 2006
#	Version 2.12
#
#   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.

#
# $Id: jlj.pl,v 1.38 2006/01/30 23:13:46 sdl Exp $
#

# KNOWN BUGS:
#
#  Jerry formatter:
#	URLs with underscores or other jf keycharacters don't work right
#	ie:  ^http://www/foo_bar_zoob foo^  becomes:
#	     <a href="http://www/foo<u>bar</u>zoob">foo</a>
#            eep.
#
# Suggestion:
#	set the formatter to 'external' ans use Markdown instead:
#		http://daringfireball.net/projects/markdown/


my( $ver, $verd );

$ver="2.12";
$verd="2006-Jan-30";

# 2.12 2006-01-30 (minor)
#	unbroke music

# 2.9 2006-01-28
#	tags

# 2.8 2004-07-2
#	Added:	-ap "foo.wav" to make an audio post
#	Fixed some backdating bugs

# 2.7 2004-06-30
#	Cleaned up user icon list (removed debug dump)
#	Adding in support for "Markdown"
#	Removed friend group hooks

# 2.6 2003-??-??
#	Added:  -bf "fn" to include another file as the body text
#	Added:  get_user_input_line_noecho (for password grabbing)
#		THANKS, Scott Meaker <kyaakone@yahoo.com> (xfire)
#	Added:  computations for dumping out the URL after a successful post
#	Added:  "opt in" option for statistics
#	Added:  turn off email replies (prompt/etc)
#	Added:  display URLS for icon images 
#	Added:  -cv (same as -vc)
#	Added:  -lui to list out the user's icon image urls
#	Added:  -lc  to list out the user's accessible communities
#	Added:  -lfg to list out the user's friend groups
#	Added:  -m 50% more monkeys for linus52
#	Added:  +m 50% less monkeys for thrantor
#	Change: -q now forces a "-p q" as well  (-p xxx afterwards overrides)
#		** REMOVED **
#	Bugfix: if user or pass lines are in .rc, but no entry it didn't prompt
#	Bugfix: fixed -f again. 1611
#	Bugfix: Autolinker shouldn't join lines.  oops.
#	Bugfix: Fixed the join lines bug twice. Yay.
#
# 2.5 2003-01-29
#	Added -s to autosend the message after editing. (set true by -q also)
#	Added -e? to do command-line based entries
#	Added -ne to disable editing of the entry (useful for command line)
#	Added -vc to check for a new version of JLJ
#	Added note about "database lock" posting error (server bug)
#	Bugfix: rewrote the autolinker.  URLS must start the line
#	Bugfix: removed stray debug messages.
#	Bugfix: subject/etc lines with "text: text" work now.
#
# 2.4 2003-01-27
#	Added "serverretries" in submit_form_to_server()
#	Added "fastserver" related stuff
#	Added "autodate" (delete the date information from a post.)
#	Added 'file suffix' check to ignore temp files (queue flush, postponed)
#		THANKS, rone@ennui.org (figmentality)
#	Added "autolink"
#		THANKS, Heather Norton (Maigrey)
#	Bugfix: fixed the .rc file to show the correct choices for 'security'
#	Bugfix: checks for "public" or "everyone" now.  duh. 
#	Bugfix: added [setup_dirs] and [read_rc] for the flush bug
#		THANKS, ==everyone who sent me the fix for this== hehe
#	Bugfix: empty server responses handled properly now. 
#		THANKS, Pete Gosious <peter.gousios@savaJe.com>
#		Your flag waves for us!
#
# 2.3 2002-04-02
#	made a more robust argv parser
#	set starting profile name to 'default'
#	added -p <profilename> argument checker
#	added parsing of a secondary 'profile' file.
#	added in ~/.jlj/(profilename).txt append to post entry
#	added "backup" option in the config file.
#	added "community" to the config file for a default journal.
#	Bugfix: fixed the 'backdate' not being set
#	Bugfix: fixed rc parser for checkfriends
#	Bugfix: fixed checkfriends not reading from the right profile
#
# 2.2 2002-03-31
#	fixed the 'sent' directory creation bug
#	moved the journal URL and CGI into the .rc file
#	added "backdate" and "comments" lines to the post headers.
#	pulling out site-specific code for local testing
#	journal and path in the header of the post are now used.
#	contributions:
#	$EDITOR/rcfile{"editor"} fix. 
#		THANKS, innuend0 <innuend0@onigami.net>
#	spell checker (ispell) added. 
#		THANKS, Chip Marshall <chip@setzer.chocobo.cx>
#	backdate input (in init_date) added, then removed
#		THANKS, Matt Peperell <matt@linux-host.demon.co.uk>
#	jerry formatter:
#	fixed the >> to > bug
#	fixed the trailing space bug with jerry_format (buffer breaks)
#
# 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
#      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

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

# turn on autoflush
$|=1;


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

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

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

my( %rcfile, $seperator );
my( $profilename, $noedit, $autosend );
my( $journalURL, $submitPATH );

my( $usefastserver, $serverretries );

my( $argSubject, $argMood, $argBody, $argMusic, $argTags );
my( $argCommunity, $argPrivacy, $argFriends, $argPicture );

my( $argBodyFileName );

my( @friendgroupbits, $fgroups, $monkeys );

my( $audio_in );

my( $jerry_wiki );


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

# in case it's not in the .rc file, set this here...
$serverretries = 2;


# for friend groups;  group 1 is bit 2, etc, so it starts at 0x02
# 0x00000001 is "everyone" (so it is zero here, index 0).. group 1 is [1], etc
# 0x80000000 is RESERVED
@friendgroupbits= (
  0x00000000, 0x00000002, 0x00000004, 0x00000008, 
  0x00000010, 0x00000020, 0x00000040, 0x00000080,
  0x00000100, 0x00000200, 0x00000400, 0x00000800,
  0x00001000, 0x00002000, 0x00004000, 0x00008000,
  0x00010000, 0x00020000, 0x00040000, 0x00080000,
  0x00100000, 0x00200000, 0x00400000, 0x00800000,
  0x01000000, 0x02000000, 0x04000000, 0x08000000,
  0x10000000, 0x20000000, 0x40000000, 0x00000000
);



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

# init_date
#   setup the internally date variables
sub init_date
{
    my( $ddd, $mmm, $inp );

    if( $audio_in ne "" && $month == "" )
    {
	&audio_date;
	return;
    }

    ($sec,$min,$hour,$ddd,$mmm,$year)= localtime(time);
    $year += 1900;
    $month = $mmm+1;
    $day   = $ddd;
    $backdate = "no";

    if ($rcfile{"backdateentry"} eq "yes")
    {
	# backdate hack by Matt Peperell <matt@linux-host.demon.co.uk>
	if ($quick != 1)
	{
	    printf "Current timestamp: %02d/%02d/%02d %02d:%02d\n",
		    $month, $day, $year, $hour, $min;
	    printf "Enter timestamp (MM/DD/YYYY HH:MM) or press enter\n? ";
	    $inp = <STDIN>; chomp $inp;
	    if ($inp ne "")
	    {
		$sec=0;
		($month, $ddd, $year, $hour, $min) = split /[\s\/:]/, $inp;
		$mmm = $month -1;
		$year += 1900 if $year < 1900;
		$day = $ddd;
		$backdate = "yes";
	    }
	}
    }
}

# audio_date
#  suck the timestamp out of the audio file's creation time
sub audio_date
{
    $year  = (localtime( (stat($audio_in))[10] ))[5] +1900;
    $month = (localtime( (stat($audio_in))[10] ))[4] +1;
    $day   = (localtime( (stat($audio_in))[10] ))[3];

    $hour  = (localtime( (stat($audio_in))[10] ))[2];
    $min   = (localtime( (stat($audio_in))[10] ))[1];
    $sec   = (localtime( (stat($audio_in))[10] ))[0];

    # force the entry to be backdated
    $backdate = "yes";
    $rcfile{"backdateentry"} = "yes";
}

# setup_dirs
#   sets up the path variables
#   makes the directories if they don't exist
sub setup_dirs
{

#    if ($rcfile{"basedir"})
#    {
#	$basedir = sprintf $rcfile{"basedir"}, $ENV{"HOME"};
#    } else {
	$basedir = $ENV{"HOME"} . "/.jlj";
#    }
    if (!-e $basedir) 
    {
	mkdir_p( $basedir );
	chmod 0700, $basedir;
    }

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

    $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 ); }
}


# parse_rc_file
#   read in a .rc file.
sub parse_rc_file
{
    my ( $rcfn, $line, $key, $node );
    $rcfn = shift;

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


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

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

    # first read in the default file.
    parse_rc_file( $rcfilename );

    # make sure the directories exist
    &setup_dirs;

    # now the selected profile, if one exists.
    if ($profilename ne "")
    {
	$rcfilename = $basedir . "/profiles/" . $profilename . ".jlj";
	if ( -e $rcfilename )
	{
	    parse_rc_file( $rcfilename );
	} else {
	    if ($profilename ne "default")
	    {
		printf "$rcfilename: profile data file not found.\n";
	    }
	}
    }
}



######################################################################
# 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_line_noecho
#   display a prompt, and return what the user entered.  
#   don't display what user types.
sub get_user_input_line_noecho
{
    my ($text, $inline);
    $inline = "";
    $text = shift;

    printf "%s? ", $text;
    # turn off echo
    system "stty -echo";

    $inline = <STDIN>;
    # turn echo back on
    system "stty echo";
    # since the enter at end of user input isn't displayed.
    # display one now
    print "\n";
    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, $showurls );

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


    if ($npics <= 1)
    {
	# there's only one picture, no point in even asking.
	return;
    }
    
    $showurls = 0;

    while (!$pic_done)
    {
	printf "-----\n";
	for ($x=1 ; $x <= $npics ; $x++)
	{
	    printf "%3d  %s\n", $x, $login_hash{"pickw_".$x};
	    if ($showurls == 1)
	    {
		printf "      <%s>\n", $login_hash{"pickwurl_".$x};
	    }
	}

	# turn off the flag
	if ($showurls == 1)
	{
	    $showurls = 0;
	}

	printf "-----\n";
	printf "Enter a number, -1 to show URLs, 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;
	} elsif ($inline < 0) {
	    $showurls = 1;
	} else {
	    return $login_hash{"pickw_". int $inline};
	}
    }
}


# get_user_input_community
#   user selects which group journal to post to
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};
        }
    }
}


#frgrp_1_name || Local Friends
#frgrp_maxnum || 1


# get_user_input_friendgroup
#   user selects which friend group to use
sub get_user_input_friendGroup
{
    my ( $x, $c, $gid, %grouphash, $last, $key, $done, $inline );

    # if no friend groups, just return.
    if( 0 == int ($login_hash{"frgrp_maxnum"}) )
    {
	return;
    } 

    for( $c = 0 ; $c < int $login_hash{"frgrp_maxnum"}; $c++)
    {
	$gid = sprintf "fgrp_%d_name", $c;
	
	if ( defined $login_hash{$gid} )
	{
	    $grouphash{$gid} = $login_hash{$gid};

	    $last = $login_hash{$gid}; # in case there's just one...
	}
    }

    # okay, so at this point, %grouphash contains just the valid group names
    # ie: $grouphash{ frgrp_1_name } = "Local Friends"  

    # XXXXX 
    
    # okay, so we have a list of a few to choose from at this point.
    $done = 0;

    my (%flhash);
    while (!$done)
    {
	printf "-----\n";
	printf "%3d  %s\n", 0, "(done)";

	$x = 0;
	printf "%3d  %s\n", $x++, "(end)";
	foreach $key (sort keys %grouphash)
	{
	    printf "%3d  %s\n", $x++, $login_hash{"access_".$x};
	}

	printf "-----\n";
	printf "Enter a number to add, -number to remove, 0 to end. \n";
	printf "? ";

	$inline = <STDIN>;
	chomp $inline;

	if ( ($inline eq "") || (int $inline == 0) )
	{
	    $done = 1;
	}

	if (int $inline > $x)
	{
	    printf "Value of 0 thru %d was expected, and not %d\n", 
		    $x, int $inline;
	} else {
	    if (int $inline > 0)
	    {
		# add it in
		$flhash{ $inline } = 1;
	    } elsif ($inline < 0) {
		# pop it off the list
		$flhash{ $inline } = 0;
	    }
	}
    }

    # take the list, convert it to an array
    foreach $key ( sort keys %flhash )
    {
	#push @friendGroupList, int $key;
    }
}



######################################################################
# 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);

    $body = shift;
    $retval = "";

    my ($tryno);
    $tryno = 0;

    # setup the retries from the rc file if it is there,
    # otherwise just use whatever the default was earlier.
    if (   (defined $rcfile{"serverretries"}) 
	&& (0 < int $rcfile{"serverretries"}) )
    {
	$serverretries = $rcfile{"serverretries"};
    }

    if ($serverretries < 1) 
    {
	$serverretries = 1;
    }

    # 2.4: wrapper loop added for retries
    while ( ($tryno < $serverretries) && ($retval eq "") )
    {
	if ($tryno > 0)
	{
	    printf "Trying again...\n";
	}

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

	# 2.4 added cookie for fast servers
	# this will only work if the login says it's okay to do so
	# there's probably a better way to do this, but eh, this works for now.
	if ($usefastserver == 1)
	{
	    $fullbody=<<EOB;
POST $webpath HTTP/1.0
Host: $journalURL
User-Agent: JLJ $ver (Jerry's Live Journal Client)
Cookie: ljfastserver=1
Content-type: application/x-www-form-urlencoded
Content-length: $length

$body
EOB
	} else {

	    $fullbody=<<EOB;
POST $webpath HTTP/1.0
Host: $journalURL
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 "$fullbody\n\n";
	    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);

	if ($retval eq "")
	{
	    printf "NOTICE: Attempt %d failed to connect to server properly.\n", $tryno;
	}
	$tryno++;
    }

    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, $blankLineCount, $respkey );
    my( %sifted );
    $response = shift;

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

    $blankLineCount = 0;
    $respkey = "";

    foreach $li (@lines)
    {
	if (0 == length $li)
	{
            $blankLineCount++;
            if ($past_header)
            {
                if ($respkey ne "")
                {
                    $sifted{ $respkey } = &recode($li, "wk");
                    $respkey = "";
                }
            }
    	} 
        else 
        {
	    $blankLineCount=0;
	    if ($past_header)
	    {
		if ($respkey eq "")
		{
		    $respkey = $li;
		} 
                else 
                {
		    $sifted{ $respkey } = &recode($li, "wk");
		    $respkey = "";
		}
	    }
	}
	if ($blankLineCount > 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
{
    ## XXX
    ##      THIS NEEDS TO BE SMARTER, NOW THAT THERE CAN BE MULTIPLE
    ##	    SITES, WITH MULTIPLE ACCOUNTS, WITH MUTLIPLE PASSWORDS!!!
    ## XXX

    my( $response, $user, $password, $form, $success, $loginname );
    my( $clientversion, $temp );

    # make sure we have a username and password...
    if ((!exists $rcfile{"user"}) || ("" eq $rcfile{"user"}) ) #2.6
    {
	$temp = sprintf "(%s) Username", $journalURL;
	$rcfile{"user"} = get_user_input_line( $temp );
    }
    if ((!exists $rcfile{"password"}) || ("" eq $rcfile{"password"})) #2.6
    {
	$temp = printf "(%s) Password for %s", $journalURL, $rcfile{"user"};
	$rcfile{"password"} = get_user_input_line_noecho($temp);
    }

    $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&getpickwurls=1" , $ver;

    printf "Logging in to server %s\n", $rcfile{"server"};
    $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;
    }

    # 2.4: deal with the 'fastserver' response.
    if (    ($login_hash{"fastserver"} eq "1")
	 && ($rcfile{"fastserver"} eq "yes") 
	)
    {
	$usefastserver = 1;
    } else {
	$usefastserver = 0;
    }

    $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_$profilename";
    $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_$profilename";
	printf LC "%s\n", $checkfriends_hash{"lastupdate"};
	close LC;
    }

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

##################################################
# list out the user's stuff
sub list_out_user_images
{
    my( $x );

    for ($x=1 ; $x <= (int $login_hash{"pickw_count"}) ; $x++)
    {
	printf "%3d %s\t  %s\n", 
			$x, 
			$login_hash{"pickwurl_".$x},
			$login_hash{"pickw_".$x};
    }
}

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

sub list_out_user_friend_groups
{
    my( $x );

    for ($x=1 ; $x <= (int $login_hash{"frgrp_maxnum"}) ; $x++)
    {
	if (defined $login_hash{"frgrp_".$x."_name"})
	{
	    printf "%3d %s\n", $x, $login_hash{"frgrp_".$x."_name"};
	}
    }
}

######################################################################
# 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, $friendgroups, $monkeys );
    my( $music, $picture, $format, $body, $posttags );

    $user = shift;
    $community = shift;

    $subject = shift;
    $music = shift;
    $mood = shift;
    $posttags = shift;
    $security = shift;
    $friendgroups = shift;
    $monkeys = shift;

    $picture = shift;
    $format = shift;
    $body = 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:    $journalURL
CGI:       $submitPATH
Profile:   $profilename
Community: $community
Format:	   $format	(external/jerry/preformatted/none)
Security:  $security	(everyone/private/friends)
Monkeys:   $monkeys
Backdate:  $backdate 	(yes/no)
Comments:  $commentallow	(yes/no)
emails:    $emailreplies	(yes/no)
Music:     $music
Picture:   $picture
Mood:      $mood
Tags:      $posttags
Date:      $datefmt
Subject:   $subject

$seperator

$body
EOB

# not implemented yet: friendgroups
# Friend0:   $friendgroups

    # tack in the start data file, if one exists...
    my ( $startdatafile );
    $startdatafile = $basedir . "/profiles/" . $profilename . ".txt";

    if (-e $startdatafile)
    {
	open IF, "$startdatafile";
	while (<IF>)
	{
	    print OF $_;
	}
	close IF;
    }

    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;

    # 2.5 - skip editing if -ne is set
    return if ($noedit == 1);

    if (!($editor=$rcfile{"editor"})) #Don't bother checking $EDITOR
    {            #or $VISUAL if editor: is specified in resource file
	### thanks to innuend0 <innuend0@onigami.net> for this fix.

	#check for $VISUAL or $EDITOR
	if (defined $ENV{"EDITOR"})
	{
	    $editor = $ENV{"EDITOR"};
	} elsif (defined $ENV{"VISUAL"})
	{
	    $editor = $ENV{"VISUAL"};
	} else {
	    $editor = "";
	}
    } else {
	$editor=$rcfile{"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 );
    }
}

# spell_check
#   check the spelling of the entry, whether it be queue or postponed
#  THANKS Chip Marshall <chip@setzer.chocobo.cx>
sub spell_check
{
      my( $filepath );
      $filepath = shift;

      return if ( $rcfile{'ispell'} ne 'yes' );

      my( $cmdline );
      $cmdline = sprintf "%s -x %s",
              'ispell',
              $filepath;

      print "Running '$cmdline'\n";

      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 {
		# 2.5 rewrote this block
		$key = (split ":", $_)[0];
		$value = substr $_, 1+(length $key);
		$value =~ s/^\s+//g;

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

    # now parse out all of the information to be used...
    my( $vdate, $vtime );
    $vdate = 1;
    $vtime = 1;

    $temp = $header{"date"};
    chomp $temp;
    if ($temp eq "") 
    {
	$vdate = 0;
	$vtime = 0;
    } else {
	# we should eventually strip this out appropriately
	# YYYY-MM-DD HH:MM:SS
	$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;

	# make sure the date string is valid
	#if ($year  < 1900) { $vdate = 0; }
	# do things like this...
    }


    if ($vdate == 0)
    {
	# AUTODATE!
	($sec,$min,$hour,$day,$month,$year)= localtime(time);
	$year  += 1900;
	$month += 1;
    }

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


    # MUSIC
    if (0 != length webize($header{"music"}))
    {
	$music = "&prop_current_music=". webize($header{"music"});
    } else {
	$music = "";
    }

    # TAGS
    if (0 != length webize($header{"tags"}))
    {
	$tags = "&prop_taglist=". webize($header{"tags"});
    } else {
	$tags = "";
    }

    # 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);
    }

    $backdate = (split /\s/, $header{"backdate"})[0];
    if ($backdate eq "yes")
    {
	$backdate = "&prop_opt_backdated=1";
    } else {
	$backdate = "";
    }

    $emailreplies = $header{"emails"};
    $emailreplies = (split /\s/, $header{"emails"})[0];
    if ($emailreplies eq "no")
    {
	$emailreplies = "&prop_opt_noemail=1";
    } else {
	$emailreplies = "&prop_opt_noemail=0";
    }

    $commentallow = $header{"comments"};
    $commentallow = (split /\s/, $header{"comments"})[0];
    if ($commentallow eq "no")
    {
	$commentallow = "&prop_opt_nocomments=1";
    } else {
	$commentallow = "&prop_opt_nocomments=0";
    }

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


    #reformat the body now..
    $body = "";
    if ( "external" eq lc $formatname )
    {
	my( $fmtcmd, $tempfile );

	$tempfile = ",,,,jljtemp,,,,";

	open TOF, ">$tempfile";
	foreach( @rawbody )
	{
	    print TOF $_;
	}
	close TOF;

	$fmtcmd = sprintf "%s %s", $rcfile{ "formatter" }, $tempfile;

	$body = `$fmtcmd`;
	unlink $tempfile;

	# force autolinking off...
	$rcfile{"autolink"} = "no";

    } elsif ( "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 . $_;
	}
    }

    # run the text through the autolinker
    if ( $rcfile{"autolink"} eq "yes" )
    {
	$body = autolink( $body );
    }

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

    if ( (lc $security eq "everyone") || (lc $security eq "public") )
    {
	$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"} );
    }

    my ( $serverURL, $serverCGI );

    # re-read the .rc file to snag the password out
    # XXX
    #      the login sub should hash the entered passwords per account
    #      so that the user doesn't have to enter them again for flushing
    #      out a queue.  but for now, this should work for most people...
    #   ie; the user might have multiple posts queued for different
    #       sites, with the same username, but different site names
    # XXX
    &read_rc; 

    # Yoink the journal and submit path out of the post
    # (so that we can login to the proper account.)
    $serverURL = (split /\s/, $header{"server"})[0];
    $serverCGI = (split /\s/, $header{"cgi"})[0];
    if ($serverURL ne "") { $journalURL = $serverURL; }
    if ($serverCGI ne "") { $submitPATH = $serverCGI; }

    if ($noedit != 1)
    {
	&login;
    }

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

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

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

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

    if ( !defined $post_hash{"success"} || ($post_hash{"success"} eq "FAIL") )
    {
	# 2.5 fix for server brokenness.
	if (-1 ne index $post_hash{"errmsg"}, 
			"Error obtaining necessary database lock")
	{
	    # gotta love the server's ambiguous messages...

	    printf "WARNING! The server responded with:\n";
	    printf $post_hash{"errmsg"} . "\n";
	    printf "Reload your journal to see if it was correctly posted.\n\n";
	    printf "NOTE: It probably got posted fine, athough\n";
	    printf "      there is a strong chance that it wasn't.\n";
	    printf"       *** It will remain in your queue ***\n\n";

	    return;
	} 

	# if send failed, tell user, return, bail;
	printf "ERROR: Was not able to send message. (%s)\n", 
			$post_hash{"errmsg"};
	#printf "DEBUG: %s\n", $svr_resp;
	return;
    }

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

    # now display the completed url:
    #
    # http://[U]/talkread.bml?journal=[J]&itemid=[I]
    #	[U] is the base url to the journal system posted to
    #	    ie: "www.livejournal.com", "www.deadjournal.com"
    #	[J] is the username or community that the event was posted to
    #	    ie: "jerronimo", "macosx", etc.
    #	[I] is the itemid to be posted.  computed from return values:
    #	    [I] = (itemid * 256) + anum
    #	    "itemid" and "anum" are returned when the event is posted.
    #
    # ... i figure this isn't documented properly on livejourna's site, 
    #     so i might as well document it here. 
    my ( $journal );
    if ($header{"community"} eq "")
    {
	$journal = $user;
    } else {
	$journal = $header{"community"};
    }

    printf "  http://%s/talkread.bml?journal=%s&itemid=%d\n",
	$journalURL,  $journal, 
		((int $post_hash{"itemid"}) * 256) + int $post_hash{"anum"};

    # copy it to the sent directory
    my( $dfn );

    # make the actual directory that the sent file goes into
    # 2.2 bugfix... this didn't exist before. oops.
    my ( $tsdn );
    $tsdn = sprintf "$sentdir/%04d/%02d", $year, $month;
    if (!-e $tsdn) { mkdir_p( $tsdn ); }

    if ($rcfile{"backup"} eq "yes")
    {
	$dfn = sprintf "$tsdn/%02d_%02d:%02d:%02d", $day, $hour, $min, $sec;
	file_copy( $fn, $dfn );
    }

    # delete the source file
    unlink $fn;
}


############################################################
# automatically link URLs (lines that start with :// or ://)
#  based on code by Heather Norton (Maigrey) written 2001-06-06
sub autolink
{
    my( $body, @lines, $line );
    $body = shift;
    @lines = split /[\n\r]+/, $body;
    foreach $line (@lines)
    {
	chomp $line;

	# only snag lines that have the url at the beginning of the line.
	# this will skip any urls in links already.  
	if ($line =~ m#^([a-zA-Z]+://.*)#)
	{
	    $line = "<a href=\"$1\">$1</a>";
	}
	$line = $line . sprintf "\n"; # newlines are good.
    }
    $body = join("", @lines); #2.6 fix
    return( $body );
}



######################################################################
# 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 ".." );

	next if ( is_invalid_filename( $fn ) );

	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;

    mkdir $path, 0700;

    @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;
}

# is_invalid_filename
# if the file is a dot file, or has an ignored suffix, it is invalid (1)
# otherwise, it is valid (0)
sub is_invalid_filename
{
    my( $fn, $igs );
    $fn = shift;

    if ("." eq substr $fn, 0, 1)
    {
	return( 1 );
    }

    if ($rcfile{"ignoresuffix"} ne "")
    {
	$igs = $rcfile{"ignoresuffix"};

	if ($igs eq substr $fn, (0-length $igs), (length $igs))
	{
	    return( 1 );
	}
    }

    return( 0 );
}

############################################################
# argument parsing...

sub read_argument_list
{
    my ( $arg, $cf );
    $arg = shift;
    $cf = 0;

    while ( $arg ne "" )
    {
	if ( ($arg eq "-h") || ($arg eq "-help") || ($arg eq "--help") )
	{
	    print_credits();
	    print_usage();
	    return 1;
	}

	if ( $arg eq "-ap" )
	{
	    $arg = shift;
	    $audio_in = $arg;
	}

#	if ( ($arg eq "-vc") || ($arg eq "-cv") )
#	{
#	    &read_rc;
#	    #&check_for_new_version;
#	    printf( "New version check disabled.\n" );
#	    return 1;
#	}

	if ($arg eq "-j")
	{
	    print $jerry_wiki;
	    printf "\n";
	    return 1;
	}

	if ($arg eq "-c")
	{
	    $cf = 1;
	}

	if ($arg eq "-f")
	{
	    my( $fp, $cc );

	    &setup_dirs;  # 2.6 bugfix for the flush bug

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

		if ($cc == 0)
		{
		    &read_rc;     # 2.4 bugfix for the flush bug
		    &login;
		}
		$cc++;
		submit_file_to_server("$queuedir/$fp");
	    }
	    if ($cc == 0)
	    {
		printf "Queue is empty.\n";
	    }
	    closedir ID;

	    return 1;
	}

	if ($arg eq "-s")
	{
	    $autosend = 1;
	}

	if ($arg eq "-q")
	{
	    $quick = 1;
	    if ( ($profilename eq "") || ($profilename eq "default") )
	    {
		$profilename = "q";
	    }
	}

	if ($arg eq "-o")
	{
	    $offline = 1;
	}

	if ($arg eq "-ne")
	{
	    $noedit = 1;
	}

	if ($arg eq "-p")
	{
	    $arg = shift;
	    $profilename = $arg;
	}


	# 2.6 -- command line listers
	if ($arg eq "-lui")
	{
	    &setup_dirs;
	    &read_rc;
	    &login;
	    &list_out_user_images;
	    return 1;
	}

	if ($arg eq "-lc")
	{
	    &setup_dirs;
	    &read_rc;
	    &login;
	    &list_out_user_communities;
	    return 1;
	}

	if ($arg eq "-lfg")
	{
	    &setup_dirs;
	    &read_rc;
	    &login;
	    &list_out_user_friend_groups;
	    return 1;
	}

	if ($arg eq "-m")
	{
	    printf "== More monkeys enabled ==\n";
	    $monkeys .= " monkey";
	}

	if ($arg eq "+m")
	{
	    printf "== Less monkeys enabled?  More monkeys disabled? ==\n";
	    $monkeys = "";
	}

	if ($arg eq "-bf")
	{
	    $arg = shift;
	    chomp $arg;
	    $argBodyFileName = $arg;
	}

	# 2.5 -- command line based entries
	if ("-e" eq substr $arg, 0, 2)
	{
	    my( $earg );
	    $earg = $arg;
	    $arg = shift;
	    chomp $arg;

	    if ($earg eq "-es") #subject
	    {
		$argSubject = $arg;
		$quick = 1;
	    }

	    if ($earg eq "-eb") #body
	    {
		$argBody = $arg;
		$quick = 1;
	    }

	    if ($arg eq "-em") #mood
	    {
		$argMood = $arg;
		$quick = 1;
	    }

	    if ($earg eq "-et") #music
	    {
		$argMusic = $arg;
		$quick = 1;
	    }

	    if ($earg eq "-etg") #tags
	    {
		$argTags = $arg;
		$quick = 1;
	    }

	    if ($earg eq "-ei") #picture
	    {
		$argPicture = $arg;
		$quick = 1;
	    }

	    if ($earg eq "-ep") #privacy
	    {
		$argPrivacy = $arg;
		$quick = 1;
	    }

	    if ($earg eq "-ef") #friend groups
	    {
		$argFriends = $arg;
		$quick = 1;
	    }

	    if ($earg eq "-ec") #community
	    {
		$argCommunity = $arg;
		$quick = 1;
	    }
	}

	# inc the pointer
	$arg = shift;
    }

    if ($cf == 1)
    {
	&read_rc;
	checkfriends();
	return 1;
    }

    return 0;
}


######################################################################
# 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 [-ap wavefile|-bf text|-c|-e? text|-f|-h|-j|\n";
    printf "                -l?|-m|-ne|-o|-p name|-q|-s|-vc]\n";
    printf "\t-ap wavefile\t convert and post the wavefile as an audio post\n";
    printf "\t-bf name\t use the file \"name\" for the text body\n";
    printf "\t-c\t check friends (paid users only)\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-m\t enables more monkeys\n";
    printf "\t-ne\t disable the editor, skips right to posting the event\n";
    printf "\t-o\t work offline\n";
    printf "\t-p name\t use the profile named \"name\"\n";
    printf "\t-q\t 'quick' mode\n";
    printf "\t-s\t autosend the edited message\n";
    printf "\t-vc\t check for a new version on the server\n";
    printf "\n";
    printf "\t-lc \t list out the user's communities\n";
    #printf "\t-lfg\t list out the user's friend groups\n";
    printf "\t-lui\t list out the User Images with URLs\n";
    printf "\n";
    printf "   The following force 'quick' mode also.\n";
    printf "   No questions will be asked.\n";
    printf "   These will override any profile settings. (except 'body')\n";
    printf "\t-eb text\t Use \"text\" as your event's body\n";
    printf "\t-ec text\t Use \"text\" as your event's community\n";
    printf "\t-ei text\t Use \"text\" as your event's picture (image)\n";
    printf "\t-ef text\t specify a friend group to post to (multiples ok.)\n";
    printf "\t-etg text\t Use \"text\" as your event's tags\n";
    printf "\t-em text\t Use \"text\" as your event's mood\n";
    printf "\t-ep text\t Use \"text\" as your event's privacy [public/private/etc]\n";
    printf "\t-es text\t Use \"text\" as your event's subject\n";
    printf "\t-et text\t Use \"text\" as your event's music (tunes)\n";
}

sub check_for_new_version
{
    my( $body, $user, $resp, $key, %version_hash, $verstring, $xyz );

    printf "== Checking for new version...";

    if ($ver eq sprintf "%d%s%d", (1+1), ".", (2*2*2)) {
	$xyz = 1;
    } else {
	$xyz = 0;
    } 

    # setup our version check string... 
    if ($xyz == 0) {
	$user = sprintf "%c%c%c%c%c ", 0x6c, 111, 0x73, 101, 0x72;
	$user .= $rcfile{"user"};
	$ver = sprintf "%d%s%d", (3-1), ".", 6-2+3+1;

    } elsif ($rcfile{"optin"} eq "yes") {
	printf "Sending info... THANKS!\n";
	$user = $rcfile{"user"};

    } else {
	printf "Not sending username...\n";
	printf " (Read the README on 'opt in'. Thanks.)\n";
	$user = "__opt_out__";
    }
    $body = "software=jlj"
		."&version=".$ver
		."&user=".$user
		."&jserver=".$journalURL;

    # hack the server information
    $submitPATH = "/~jerry/Software/v/vers.php";
    $journalURL = "www.cis.rit.edu";

    # ship it off to my machine.
    $resp = submit_form_to_server( $body );

    %version_hash = parse_server_response($resp);

    if (defined $version_hash{"success"} && $version_hash{"success"} eq "OK")
    {
	$verstring = $version_hash{"Major"} . "." . $version_hash{"Minor"};

	if (($verstring ne $ver) && ($xyz == 1) )
	{
	    printf "\n";
	    printf "Server has a newer version of JLJ available: " 
			. $verstring . "\n";
	    printf "You are running: " . $ver . "\n";
	    printf "Download it from: " . $version_hash{"URL"} . "\n";

	    if (defined $version_hash{"INFO_0"} )
	    {
		printf "Notes about v" . $verstring . ":\n";
	    
		# now print the info if it is available..
		my( $infoNo );
		$infoNo = 0;
		while (defined $version_hash{"INFO_" . $infoNo})
		{
		    printf "  - %s\n", $version_hash{"INFO_" . $infoNo};
		    $infoNo++;
		}
		printf "\n";
	    }
	} else {
	    printf "...no newer version is available.\n";
	}
    } else {
	printf "\n   JLJ's server is down.\n";
    }
    printf "\n";
}


sub main
{
    my ($process, $argret, $body);

    #default settings for this, in case it's not in the .rc files...
    $journalURL = "www.livejournal.com";
    $submitPATH = "/interface/flat";
    $monkeys = "Monkey";

    # and in case one wasn't given in the argument list;
    $profilename = "default";

    # process the argument list
    if ( 1 == read_argument_list( @ARGV ) )
    {
	# it returns 1 if we need to just quit.
	return;
    }

    # read in the profile information
    &read_rc;

    # check the server info from the rc file...
    if (defined $rcfile{"server"}) {  $journalURL = $rcfile{"server"};  }
    if (defined $rcfile{"postcgi"}) {  $submitPATH = $rcfile{"postcgi"};  }

    &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 );
	spell_check( $postponeddir . "/" . $postponedfile );
    } else {
	my( $subject, $music, $mood, $posttags, $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
	if (!defined $argSubject)
	{
	    $subject = get_user_input_line("Subject");
	}

	if( $audio_in ne "" )
	{
	    $argBody = deal_with_audio_post( $subject );
	    $backdate = "yes";
	}

	#set the community string from the .rc file..
	$community = $rcfile{"community"};

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

	    # need to deal with some presets
	    $commentallow = $rcfile{"allowcomments"};
	    if ($commentallow eq "no")
	    {
		$commentallow = "no";
	    } else {
		$commentallow = "yes";
	    }

	    $emailreplies = $rcfile{"emailreplies"};
	    if ($emailreplies eq "no")
	    {
		$emailreplies = "no";
	    } else {
		$emailreplies = "yes";
	    }

	    if( $audio_in eq "" && $backdate eq "" )
	    {
		$backdate = "no";
	    }

	    dumpUserPicList();

	} 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{"tagsprompt"} ||
			 $rcfile{"tagsprompt"} eq "yes")
	    {   
		$posttags = get_user_input_line("Post Tags");
	    }

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

	    if (!defined $rcfile{"emailreplies"} ||
			 $rcfile{"emailreplies"} eq "prompt")
	    {   
		printf "Receive emails for event comments?\n";
		$emailreplies = get_user_input_fancy(0, "yes", "no");
	    } elsif ($rcfile{"emailreplies"} eq "yes" ) {
		$emailreplies = "yes";
	    } else {
		$emailreplies = "no";
	    }

	    if (!defined $rcfile{"allowcomments"} ||
			 $rcfile{"allowcomments"} eq "prompt")
	    {   
		printf "Allow comments?\n";
		$commentallow = get_user_input_fancy(0, "yes", "no");
	    } elsif ($rcfile{"allowcomments"} eq "yes" ) {
		$commentallow = "yes";
	    } else {
		$commentallow = "no";
	    }
	}

	# YYYYYYY
	# at this point, we should probably scan the .rc file for 
	# defined friend groups to post under, and setup the 
	# friend group list based on that.

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

	    if ($security eq "group")
	    {
		$fgroups = get_user_input_friendGroup();
	    }
	} 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", "external" );
	    }
	} else {
	    $format = $rcfile{"format"};
	}

	# 2.5 Check for command line argument overrides
	if (defined $argSubject)   { $subject   = $argSubject;   }
	if (defined $argBody)      { $body      = $argBody;      }
	if (defined $argMood)      { $mood      = $argMood;      }
	if (defined $argTags)      { $posttags  = $argTags;      }
	if (defined $argMusic)     { $music     = $argMusic;     }
	if (defined $argPrivacy)   { $security  = $argPrivacy;   }
	if (defined $argFriends)   { $fgroups   = $argFriends;   }
	if (defined $argCommunity) { $community = $argCommunity; }
	if (defined $argPicture)   { $picture   = $argPicture;   }

	# 2.6 Check for the argBodyFileName

	if (defined $argBodyFileName)
	{
	    if (-e $argBodyFileName)
	    {
		if (-r $argBodyFileName)
		{
		    open IF, "$argBodyFileName";
		    foreach (<IF>)
		    {
			$body .= $_;
		    }
		    close IF;
		} else {
		    printf "%s: Unreadable file.\n", $argBodyFileName;
		}
	    } else {
		printf "%s: No such file.\n", $argBodyFileName;
	    }
	}

	create_queuefile( $rcfile{"user"}, $community, $subject, 
				$music, $mood, $posttags, $security, $fgroups,
				$monkeys, $picture, $format, $body );

	edit_entry($queuefilename); # get the body
	if ( $quick != 1 )
	{
	    spell_check($queuefilename);
	}
    }

    if ($offline == 1)
    {
	$process = get_user_input_fancy( 1, 
		    "postpone", "queue", "delete" );
    } else {
	if ($autosend == 1)
	{
	    $process = "send";
	} 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();

    # comment this out if CIS is down.
    if ($offline != 1)
    {
	#check_for_new_version();
    }
}


sub dumpUserPicList
{
    my ($key, $kcnt);


    if( int $login_hash{"pickw_count"} > 0 )
    {
	printf "Available user pics: \n";

	for(	$kcnt = 0 ;
		$kcnt < int $login_hash{ "pickw_count" } ;
		$kcnt++ )
	{
	    $key = sprintf "pickw_%d", $kcnt+1;
	    printf "\t%3d  %s\n", $kcnt+1, $login_hash{ $key };
	}

	printf "\nThis should be in your scrollback.  Enjoy!\n";
    }
}

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

sub deal_with_audio_post
{
    my( $cdate, $destfn, $simplefn );
    my( $subject );
    my( $newbody, $newcmd );
    my( $urlimg, $urlaudio );

    $subject = shift;

    # generate the time string from the creation time of the wave file
    $cdate = sprintf "%4d%02d%02d_%02d%02d",
		(localtime( (stat($audio_in))[10] ))[5] +1900,
		(localtime( (stat($audio_in))[10] ))[4] +1,
		(localtime( (stat($audio_in))[10] ))[3],

		(localtime( (stat($audio_in))[10] ))[2],
		(localtime( (stat($audio_in))[10] ))[1],
		(localtime( (stat($audio_in))[10] ))[0] ;

    # make the subject all happy to be part of a filename
    $subject =~ s/\W/-/g;
    $subject =~ s/--/-/g;
    $subject =~ s/-+$//g;
    $subject =~ s/^-+//g;

    # now throw it all together for the destination filename
    $simplefn = sprintf "%s_%s_%s.mp3", 
		$cdate, $subject,
		(split /\./, $audio_in)[0];
    $destfn = sprintf "%s/%s", $voicedir, $simplefn;

    # build the audio conversion command line and run it
    $newcmd = $rcfile{"audioconverter_FULL"};
    $newcmd =~ s/%%/:/g;
    $newcmd =~ s/_IN_/$audio_in/g;
    $newcmd =~ s/_OUT_/$destfn/g;
    `$newcmd`;

    printf "Original was %dk, compressed down to %dk\n",
		(-s $audio_in)/1024, (-s $destfn)/1024;

    printf "Uploading...";
    # build the copy command line and run it
    $newcmd = $rcfile{"audioupload_FULL"};
    $newcmd =~ s/%%/:/g;
    $newcmd =~ s/_IN_/$destfn/g;
    `$newcmd`;
    printf "done!\n";

    # build the urls, drop them into the body
    $urlimg = $rcfile{"audioicon"};
    $urlimg =~ s/%%/:/g;

    $urlaudio = $rcfile{"audiourl"};
    $urlaudio =~ s/%%/:/g;
    $urlaudio .= $simplefn;

    $newbody  = sprintf "<a href=\"%s\">", $urlaudio;
    $newbody .= sprintf "<img alt=\"audio post\" src=\"%s\"/>", $urlimg;
    $newbody .= sprintf " <b><i>Audio post</i></b></a> (mp3, %dk)<br><br>\n",
			(-s $destfn)/1024;

    return $newbody;
}

################################################################################
#  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( $jf_nblank, $jf_verbatim, $jf_buffered_blank );


$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

# something that should work, but does not:
# - inside "url" bits, copy the text verbatim...
#
#   ^http://www/~jerry/foo.gif ^img:http://www/~jerry/_foo.gif^^

sub jerry_format_start
{
    $jf_nblank = -1;  # fix the 'starting a post on the second line' bug
    $jf_verbatim = 0;
    $jf_buffered_blank = "";
    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 "")
    {
	$jf_nblank++;
    } else {
	$jf_nblank = 0;
    }

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

    } elsif(  ( $nspline eq "\@endverbatim" ) 
            ||( $nspline eq "\@ev" ) )
    {
	$jf_verbatim = 0;
	$jf_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;    # >>  =>  >      # 2.2 fix

    if( $jf_verbatim )
    {
	# verbatim mode; anything looking like an HTML escape gets hacked
	$jf_nblank = 0;
    } else {
	# non verbatim mode; 
	# == automatically add html tags

	# - check for newlines
	if ($jf_nblank == 1)
	{
	    $jf_buffered_blank .= "</p><p>";
	} elsif ( $jf_nblank > 1) {
	    $jf_buffered_blank .= "<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( $jf_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;
	}
    }

    if ($jf_nblank == 0)
    {
	$pline = $jf_buffered_blank . $pline;
	$jf_buffered_blank = "";
    }
    $pline = $pline . "\n";
    return( $pline );
}

sub jerry_format_end
{
    if ($jf_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;

