#!/usr/bin/perl -w # defector - transfer Pyblosxom weblog entries to LiveJournal # version 0.1 # # (C) 2004 Joshua Kwan # # This code is in the public domain, but it would be cool if you sent me # any improvements. Thanks. use LiveJournal; use URI::Escape; use Term::ReadKey; use FileHandle; use Getopt::Std; use File::Basename; use Digest::MD5 qw/md5_hex/; sub help { print basename $0 . " [opts] [files]: transfer Pyblosxom entries to LiveJournal\n"; print "(C) 2004 Joshua Kwan \n\nOptions:\n"; print "-u username\tSpecify a username to log in to the LiveJournal server.\n"; print "-e\t\tDie immediately when there is an error during posting.\n"; print "-n\t\tDo not compare titles for duplicate entries.\n"; print "-p\t\tPost these entries as private.\n"; exit 0; } # Set some options first. my %opts; my ( $die_on_error, $security, $dupchecking ) = ( 0, "public", 1 ); getopts('hpu:en', \%opts); if (exists($opts{'h'}) && $opts{'h'} == 1) { help(); } if (exists($opts{'e'}) && $opts{'e'} == 1) { $die_on_error = 1; } if (exists($opts{'n'}) && $opts{'n'} == 1) { print "NOT checking for duplicate entries! You may get duplicates!\n"; $dupchecking = 0; } if (exists($opts{'p'})) { $security = "private"; print "Converted entries will be posted at security level: $security\n"; } if ($#ARGV == -1) { print "Nothing to do (specify some files.)\n"; help(); } # Get the user/pass. my ( $user, $password ) = ""x2; $/ = "\n"; if (exists($opts{'u'})) { $user = $opts{'u'}; } else { print "Username: "; $user = ; } STDOUT->autoflush(1); print "Passsword: "; STDOUT->autoflush(0); ReadMode('noecho'); $password = ; ReadMode('normal'); chomp $password; chomp $user; my %info = ( user => $user, hpassword => md5_hex($password), ); print "\nConnecting to LiveJournal...\n"; my $lj = LiveJournal->new(\%info); my $response = $lj->login(); $response->{success} eq "OK" or die "Could not connect to LiveJournal (" . $response->{success} . ")"; print "Connected.\n"; # Buffer journal entries so we can check to see that we don't re-upload # duplicates. Override with -n. print "Retrieving journal entry titles...\n"; my ( $count, $journal ) = (0, LiveJournal::Journal->new()); my %gethash = ( 'selecttype' => 'syncitems', ); my ( %items, %titles, %sums ) = %{$journal->get(\%gethash)}; while (my ($k, $v) = each (%items)) { if ($k =~ /events_\d+_subject/) { $k =~ s/subject/event/; my $sum = md5_hex($items{$k}); $sums{$sum} = $v; $titles{$v} = $items{$k}; $count++; } } print "Read $count journal entries.\n"; my $len = 0; foreach (@ARGV) { my ( $buf, $entry, $title, $ljtitle ) = ""x4; next if not -f $_; open ENTRY, $_; chomp ($real_title = ); # get title $title = uri_escape($real_title, "^A-Za-z0-9"); $ljtitle = uri_escape($real_title, '^A-Za-z0-9 /.:,!-&;'); while (read ENTRY, $buf, 4096) { # The most evil state machine ever. my ( $cl, $s, $p, $r, $e, $cr ) = ( 0, 0, 0, 0, 0, 0 ); my @chars = split(//, $buf); my $in_pre = 0; foreach (@chars) { if ($cl == 1 && $p == 1 && $r == 1 && $e == 1 && $cr == 1) { $in_pre = !$s; } if ($_ eq "\n" && $in_pre == 0) { $_ = " "; } elsif ($_ eq "<") { ( $cl, $s, $p, $r, $e, $cr ) = ( 0, 0, 0, 0, 0, 0 ); $cl = 1; } elsif ($_ eq "/" && $cl == 1 && $p == 0 && $r == 0 && $e == 0 && $cr == 0) { $s = 1; } elsif ($_ eq "p" && ($s == 1 || $cl == 1)) { $p = 1; } elsif ($_ eq "r" && $p == 1) { $r = 1; } elsif ($_ eq "e" && $r == 1) { $e = 1; } elsif ($_ eq ">" && $e == 1) { $cr = 1; } else { ( $cl, $s, $p, $r, $e, $cr ) = ( 0, 0, 0, 0, 0, 0 ); } $entry .= $_; $len++; } } my @s = stat($_); my $mtime = $s[9]; # Perform some surgery on the entry. $entry =~ s##\n#g; my $ljentry = uri_escape($entry, "^A-Za-z0-9 /\\\\.:,_-"); $entry = uri_escape($entry, "^A-Za-z0-9"); $entry =~ s/ /\+/g; $entry =~ s#%0A#%0D%0A#g; # This is so we can match against exactly what the server returns to us. $ljentry =~ s#%0A#%0D%0A#g; $ljentry =~ s#[[:space:]\n]+$##g; $ljentry =~ s#%0D%0A$##; $ljentry =~ s/ /+/g; $ljentry =~ s/\+$//; $ljentry =~ s/^\+//; # Make sure we don't post duplicates - intentionally, anyway. if (exists($sums{md5_hex($ljentry)}) && not exists($opts{'n'}) && $dupchecking == 1) { print "Skipping \"$real_title\": already in the journal.\n"; next; } else { if (exists($titles{$ljtitle})) { print "Skipping \"$real_title\": already in the journal (title match.)\n"; next; } } my %postinfo = ( subject => $title, event => $entry, date => $mtime, security => $security, ); # Set the backdate option if necessary. if ($mtime < time) { $postinfo{'prop_opt_backdated'} = 1; } print "Posting \"$real_title\" ($len bytes)...\n"; $response = $journal->post(\%postinfo); if ($response->{success} eq "OK") { print "Posted $_\n"; } else { if ($die_on_error) { die "Could not post new entry (" . $response->{success} . "): " . $response->{errmsg} . "\n"; } else { print "Could not post new entry (" . $response->{success} . "): " . $response->{errmsg} . "\n"; } } close ENTRY; $len = 0; }