#!/usr/local/bin/perl
#
#  JLJ
#
#  Jerry's LiveJournal text-only perl script
#
# 	Sun Sep  2 23:05:38 EDT 2001
#	Version 1.9
#
#   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.
#
# For backups, you can also have a 'archive dir: /home/users/me' line in 
# the .rc file as well, to define where backups will get saved to.  If you
# don't, they will go to ~/.jljbackups

my ($ver,$verd);

$ver="1.9";
$verd="2001-September-02";

# 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 send_any_entry()
#      mood id selector with automatic chooser now
#      picture selector
#      integrated in Adam T. Lindsay's <atl@comp.lancs.ac.uk> proxy code
#
# 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
#
# - speeling checker 
#
# - what to do if the server is down?
#
# - dump everything into the file, and store it aside in a queue.
#   then try to submit it  (offline posting)  (some things get hairy then.)
#   (rewrite all of the input routine code?)


######### 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( $year, $month, $day, $hour, $min, $sec );
my( $subject, $body, $security, $mood, $music, $picture, $community );
my( $preformatted );
my( $itemid, $itemform );
my( $rawmood, $rawmusic, $rawpicture, $rawcommunity );
my( %login_hash, %mood_hash );

my( %rcfile );



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


######################################################################
# 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 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"} || !exists $rcfile{"password"})
    {
	printf "$rcfiledata: file incomplete!\n";
	printf "you need both 'user' and 'password' entries!\n";
	exit(1);
    }

    close IF,

    return;

    printf "Values read in:\n";
    foreach $key (keys %rcfile)
    {
	if ($key eq "password")
	{
	    $value = "******";
	} else {
	    $value = $rcfile{$key};
	}
	printf "   %15s  %s\n", "|".$key ."|", $value;
    }
}

######################################################################
######### Get Info from the user
######################################################################

sub get_user_input_line
{
    my ($text, $inline);
    $inline;
    $text = shift;

    printf "%s? ", $text;

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

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


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


######################################################################
# read in the body of the posting, or use the defined editor.
sub read_body
{
    my( $tempfilename, $done, $editor, $line );
    $tempfilename = shift;
    $done = 0;
    $body = "";

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

    if ($editor ne "")
    {
	system "$editor $tempfilename";

	if (!-s $tempfilename)
	{
	    printf "Body is empty.  Exiting.\n";
	    unlink $tempfilename;
	    exit(1);
	}

	$body = "";

	open IF, "$tempfilename";
	foreach (<IF>)
	{
	    #chomp $_;
	    $body .= "$_";
	}
	close IF;
	unlink $tempfilename;

    } else {
	# old school bbs-style editor. ;)
	printf "Enter body of text.  use a '.' on a line by itself to end.\n";

	$done = 0;
	while ($done == 0)
	{
	    $line = <STDIN>;
	    #chomp $line;
	    
	    if ($line eq ".\n")
	    {
		$done = 1;
	    } else {
		$body .= $line;
	    }
	}
    }
}

######################################################################
# save out a backup of the message if appropriate
sub save_backup
{
    my ( $yn, $backuppath, $filename, $junk );
    if (!defined $rcfile{"archive"}) 
    {
	$yn = get_user_input_line("Save a backup locally");
	$yn =~ s/[\W0-9_]//g;
    } else {
	if ("y" ne lc substr $rcfile{"archive"}, 0, 1)
	{
	    return;
	}
    }

    if ($yn eq "") { $yn = "y"; }

    if ("y" eq substr $yn, 0, 1)
    {
	if ($rcfile{"archivedir"} eq "")
	{
	    $backuppath = sprintf "%s/.jljbackups", $ENV{"HOME"};
	    $backuppath =~ s/\/+$//g;

	    printf "'archive dir' not defined in the .livejournal.rc file\n";
	} else {
	    $backuppath = sprintf $rcfile{"archivedir"}, $ENV{"HOME"};
	}

	# sort it a little bit...
	$backuppath = $backuppath . "/" . $year . "/" . sprintf "%02d", $month ;

	$junk = `mkdir -p $backuppath 2>&1`;
	$junk = `chmod $backuppath 700 2>&1`;

	$filename = sprintf "%s/%02d_%02d:%02d", 
				$backuppath, 
				$day, 
				int $hour,int $min;

	printf "Saving a backup copy in '$filename'\n";

	open OF, ">$filename";
	printf OF "Date:    %04d-%02d-%02d %02d%s%02d\n",
			$year,$month,$day, $hour,':',$min;

	if ($itemid ne "")
	{
	    printf OF "Reply to: %d\n", $itemid;
	}

	if ($rcfile{"moodprompt"} ne "no")
	{
	    printf OF "Mood:     %s\n", $rawmood;
	}

	if ($rcfile{"musicprompt"} ne "no")
	{
	    printf OF "Music:    %s\n", $rawmusic;
	}

	if ($rcfile{"pictureprompt"} ne "no")
	{
	    printf OF "Picture:  %s\n", $rawpicture;
	}

	if ($rcfile{"communityprompt"} ne "no")
	{
	    printf OF "Journal:  %s\n", $rawcommunity;
	}

	printf OF "Subject: %s\n\n", $subject;
	printf OF "%s", $body;
	close OF;
    }
}

######################################################################
# prompt about other things if we need to.
sub other_questions
{
    my ($val, $valid, $mood_id);
    if (!defined $rcfile{"preformatted"}) 
    {
        $val = get_user_input_line("Is the body preformatted? [y]/n");
	$val = lc substr $val,0,1;
    }
    $preformatted = ($val eq "y" || 
                   lc (substr $rcfile{"preformatted"},0,1) eq "y")
		? "&prop_opt_preformatted=1" 
		: "&prop_opt_preformatted=0";
    
    if ($itemid ne "")
    {
	# itemid is the base item we're replying to; the top of the tree
	# paretnttalkid  is what we're directly replying to

	$itemform = sprintf "&itemid=%d&parenttalkid=0", int $itemid;

	# replies don't need the following info.
	return;
    }

    # if security isn't defined, assume public, if it's 'prompt' then ask.
    if ($rcfile{"security"} eq "prompt")
    {
	$security = "";
	$val = "xxxyyyzzz";
	$valid = "public private friends";

	while (-1 eq index $valid, $val)
	{
	    $val = get_user_input_line("[public]/private/friends");
	    if ($val eq "")
	    {
		$val = "public";
	    }
	}

	# hack because the server is wierd
	if ($val eq "friends")
	{
	    $val = "usemask&allowmask=1";
	}

	$security = "&security=$val";
	
    } else {
	$security = "&security=public";
    }

    
    if (!defined $rcfile{"moodprompt"} || 
                 $rcfile{"moodprompt"} eq "yes")
    {
	$rawmood = get_user_input_line("Current mood");
	$rawmood =~ s/\s+/ /g;
	$rawmood =~ s/^\s//g;
	$rawmood =~ s/\s$//g;

	$mood_id = $mood_hash{lc $rawmood};
	if($mood_id != 0)
	{
	    $mood = "&prop_current_moodid=".$mood_id;
	} else {
	    $mood = "&prop_current_mood=".$rawmood;
	}
    } 
    
    if (!defined $rcfile{"musicprompt"} || 
                 $rcfile{"musicprompt"} eq "yes")
    {
	$rawmusic = get_user_input_line("Current music");
	$music = "&prop_current_music=".$rawmusic;
    } 


    if (!defined $rcfile{"communityprompt"} ||
                 $rcfile{"communityprompt"} eq "yes")
    {
	$rawcommunity = get_user_input_community();
	if ($rawcommunity ne "")
        {
            $community = "&usejournal=".$rawcommunity;
	} else {
            $community = "";
	}
    }
    
    if (!defined $rcfile{"pictureprompt"} || 
                 $rcfile{"pictureprompt"} eq "yes")
    {
	$rawpicture = get_user_input_picture();

	if ($rawpicture ne "")
	{
	    $picture = "&prop_picture_keyword=". webize($rawpicture);
	} else {
	    $picture = "";
	}
    } 
}

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

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

######################################################################
# send any entry

sub send_any_entry
{
    my ($in_addr, $addr, $proto, $remotehost, $remoteport, $length );
    my ($fullbody, $retval, $resp, $body, $webpath);
    my ($submiturl, $id );

    $body = shift;
    $id = shift;
    $retval = "";

    if ($id eq "")
    {
	$submiturl = "/cgi-bin/log.cgi";
    } else {
	$submiturl = "/talkpost_do.bml";
    }

    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.
    socket( WEB, AF_INET, SOCK_STREAM, $proto )
        or die "socket:$!";

    # Connect our socket to the server socket.
    connect( WEB, $addr )
        or die "connect:$!";

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


######################################################################
# Send off the entry
sub send_entry
{
    my( $user, $password, $form );
    $user     = $rcfile{"user"};
    $password = $rcfile{"password"};

    if ($itemid eq "")
    {
	$form= sprintf "mode=postevent&webversion=full&user=$user&password=$password&year=$year&mon=%02d&day=%02d&hour=%02d&min=%02d&subject=%s&event=%s%s%s%s%s%s%s", 
		$month, $day, $hour, $min, webize($subject), webize($body),
		$security, $mood, $music, $picture, $community, $preformatted;
    } else {
	$form= sprintf "usertype=user&userpost=%s&password=%s&subject=%s&body=%s&submitpost=%s%s%s", 
		$user, $password,
		webize($subject), webize($body),
		webize("Post Comment"), $picture, $itemform;
    }
    send_any_entry ($form, $itemid);


    printf "Your %s ", ($itemid eq "")? "entry" : "reply";
    if (1 < length $subject)
    {
	printf "about '%s' ", $subject;
    }
    printf "has been sent.\n";
}


sub parse_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 } = $li;
		    $respkey = "";
		}
	    }
	}
	if ($x > 2)
	{
	    $past_header = 1;
	}
    }

    return %sifted;
}


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



sub login
{
    my( $response, $user, $password, $form, $success, $loginname );
    my( $clientversion );
    $user     = $rcfile{"user"};
    $password = $rcfile{"password"};

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

    $response = send_any_entry ($form);

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

    $loginname = $login_hash{"name"};
    $success   = $login_hash{"success"};

    if ($success ne "OK")
    {
	printf "Error encountered with server: %s is NOT 'OK'\n", $success;
	exit;
    }

    printf "Welcome, %s.\n", $loginname;
}


######################################################################
# do EVERYTHING!  ;)
sub main
{
    my ($tempfilename);

if (0)
{
    print<<EOB;
   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

         This is an experimental version!  Use it at your own risk!

   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
EOB
}
    if ($ARGV[0] eq "-reply")
    {
	$itemid = $ARGV[1];
	if ($itemid eq "" || $itemid ne sprintf "%d", int $itemid)
	{
	    printf "ERROR: '$itemid' is not a valid message id number.\n";
	    exit 0;
	}
    }

    &read_rc;
    &login;

    &init_date;

    # edit the temp file in the home directory, or in /tmp if $HOME is undefined
    $tempfilename = sprintf "%s/.jlj_temp_%04d%02d%02d_%02d:%02d:%02d",
	    (defined $ENV{"HOME"})? $ENV{"HOME"} : "/tmp",
	    $year, $month, $day, $hour, $min, $sec;

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

    &read_body ($tempfilename);		# get the body
    &other_questions;	# ask about mood, music, and security
    &init_date;		# reset the date again
    &save_backup;	# save aside the entry, if we want to

    &send_entry;	# send it off...

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

&main;
