#!/usr/bin/perl
#
#  JLJ
#
#  Jerry's LiveJournal text-only perl script
#
#   Tue Feb  6 14:08:39 EST 2001
# Version 1.6
#
#   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);

$ver="1.6-utf8.2";

# 1.6-utf8.2
#      added tags entry (petya@nigilist.ru)
#
# 1.6-utf8
#      added utf8 support (petya@nigilist.ru)
#
# 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;

use Convert::Cyrillic;
use Unicode::MapUTF8 qw(to_utf8 from_utf8);


$|=1;

# form details:
#
#   webversion      full
#   subject     subject line
#   event     text. html ok.
#   security      'public' 'private' 'friends'
#   prop_opt_preformatted preformat (auto format?)
#   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, $taglist );
my( $autoformat );
my( $rawmood, $rawmusic, $rawpicture, $rawcommunity );
my( $rawmood_conv, $rawmusic_conv, $subject_conv, $body_conv);
my( %login_hash, %mood_hash );

my( %rcfile );

my $server = "";



######################################################################
######### 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{"server"})
    {
  $server = "www.livejournal.com";
    } else { $server = $rcfile{"server"};} 

    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 = "";

    if (defined $ENV{"EDITOR"})
    {
  $editor = $ENV{"EDITOR"};

  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 ($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{"autoformat"}) 
    {
        $val = get_user_input_line("Autoformat the text [y]/n");
  $val = lc substr $val,0,1;
    }
    $autoformat = ($val eq "y" || 
                   lc (substr $rcfile{"autoformat"},0,1) eq "y")
    ? "&prop_opt_preformatted=1" 
    : "&prop_opt_preformatted=0";
    

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

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

  $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 {

    if ( defined($rcfile{ "use-utf8" }) ) {
      my $rawmood_conv = to_utf8({ -string => $rawmood, -charset => $rcfile { "local-charset" } });
    }
    else {
      my $rawmood_conv = Convert::Cyrillic::cstocs('koi8', 'win', $rawmood);
    }

      $mood = "&prop_current_mood=".$rawmood_conv;
  }
    } 
    
    if (!defined $rcfile{"musicprompt"} || 
                 $rcfile{"musicprompt"} eq "yes")
    {
  $rawmusic = get_user_input_line("Current music");
  
  if ( defined($rcfile{ "use-utf8" }) ) {
    $rawmusic_conv = to_utf8({ -string => $rawmusic, -charset => $rcfile { "local-charset" } });
  }
  else {
    $rawmusic_conv = Convert::Cyrillic::cstocs('koi8', 'win', $rawmusic);
  }

  $music = "&prop_current_music=".$rawmusic_conv;
    } 

  if (!defined $rcfile{"tagsprompt"} || $rcfile{"tagsprompt"} eq "yes") {
    my $rawtags = get_user_input_line("Tags");
    if ( defined($rcfile{ "use-utf8" }) ) {
      $rawtags = to_utf8({ -string => $rawtags, -charset => $rcfile { "local-charset" } });
    }
    else {
      $rawtags = Convert::Cyrillic::cstocs('koi8', 'win', $rawtags);
    }
    $taglist = "&prop_taglist=" . webize($rawtags);
  }
  
    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);
    $body = shift;
    $retval = "";

    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 = "http://$server/interface/flat";
            $remotehost = $rcfile{"proxyhost"};
            if (defined($rcfile{"proxyport"}))
            {
                $remoteport = $rcfile{"proxyport"};
            }  else  {
                $remoteport = 80;
            }
        }    
    }   else   {
#        $webpath = "http://$server/interface/flat"; 
        $webpath = "/interface/flat"; 
        my $tmp=gethostbyname($server);
        $remotehost = inet_ntoa($tmp); 
        $remoteport = 80;
    }
#    print "server is $server, remotehost is $remotehost, webpath is $webpath, remoteport is $remoteport\n";
    $length = length $body;

    $fullbody=<<EOB;
POST $webpath HTTP/1.0
Host: $server
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);
    # 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 ( defined($rcfile{ "use-utf8" }) ) {
      $subject = to_utf8({ -string => $subject, -charset => $rcfile { "local-charset" } });
      $body = to_utf8({ -string => $body, -charset => $rcfile { "local-charset" } });
    }
    else {
      $subject = Convert::Cyrillic::cstocs('koi8', 'win', $subject);
      $body = Convert::Cyrillic::cstocs('koi8', 'win', $body);
    }

    $form= sprintf "mode=postevent&webversion=full&user=$user&password=$password$autoformat&year=$year&mon=%02d&day=%02d&hour=%02d&min=%02d&subject=%s&event=%s%s%s%s%s%s", 
    $month, $day, $hour, $min, webize($subject), webize($body),
    $taglist, $security, $mood, $music, $picture, $community;
    
#    print "\n\n:::" . $taglist . ":::\n";

#    print "\n\n" . $form . "\n";
#    exit 1;

    send_any_entry ($form);

    printf "Your entry ";
    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"
    , $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);
    &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  2001-Feb-06\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;
