#!/usr/bin/perl -w # friends_rss.pl - generates an RSS feed on stdout of a user's friends page, # with authentication # # (C) 2005 Joshua Kwan # # This code is in the public domain, but it would be cool if you sent me # any improvements. Thanks. use LiveJournal; use Digest::MD5 'md5_hex'; use RPC::XML; use RPC::XML::Client; use XML::RSS; use LWP::UserAgent; use HTTP::Cookies; use Data::Dumper; use Text::Wrapper; use Time::Piece; use strict; use constant AUTH_FILE => "~%s/.ljrc"; use constant MAX_ENTRIES => 15; our $client; sub get_challenge_with_password($) { my $resp = $client->send_request('LJ.XMLRPC.getchallenge'); my $pwhash = md5_hex(${$resp->{'challenge'}} . md5_hex(shift)); return (${$resp->{'challenge'}}, $pwhash); } my $user = (getpwuid($<))[7]; my $auth_file = sprintf "%s/.ljrc", $user; # Load the username and password from a mode 600 dotfile. if (! -e $auth_file) { die "Before running this, .ljrc should exist with two lines:\nusername = your_username\npassword = your_password\n\nand set to permission 600."; } elsif (((stat($auth_file))[2] & 0777) != 0600) { die "Please chmod 600 $auth_file first.\n"; } my %data; open LJRC, '<', $auth_file; while () { chomp; my ($key, $val) = split / = /; die "Error parsing file" if (not defined $key or not defined $val); $data{$key} = $val; } close LJRC; die "Missing either username or password field in .ljrc." if (not defined $data{"username"} or not defined $data{"password"}); # Now we know we have a password, hash it $client = RPC::XML::Client->new("http://www.livejournal.com/interface/xmlrpc"); my $pwsave = $data{password}; delete $data{password}; $data{auth_method} = "challenge"; ($data{auth_challenge}, $data{auth_response}) = get_challenge_with_password($pwsave); print STDERR "Generating session cookie\n"; my $req = RPC::XML::request->new('LJ.XMLRPC.sessiongenerate', RPC::XML::struct->new(%data)); my $resp = $client->send_request($req); if (defined $resp->{'faultString'}) { die "Failed to authenticate!"; } my $ua = LWP::UserAgent->new; $ua->default_headers->push_header('Cookie' => "ljsession=${$resp->{'ljsession'}}"); # need another challenge ($data{auth_challenge}, $data{auth_response}) = get_challenge_with_password($pwsave); print STDERR "Getting friends list\n"; $req = RPC::XML::request->new('LJ.XMLRPC.getfriends', RPC::XML::struct->new(%data)); $resp = $client->send_request($req); if (defined $resp->{'faultString'}) { die "Failed to authenticate!"; } my @friends; my $rss = new XML::RSS (version => '2.0'); my $wrapper = Text::Wrapper->new(columns => 72, body_start => ' '); my %allitems; my @errorusers; my $g = 0; foreach my $friend (@{$resp->{'friends'}}) { last if ($g == 5); # XXX next if defined $friend->{'type'}; # community or syndicated my $user = ${$friend->{'username'}}; print STDERR "Parsing RSS feed for $user...\n"; $resp = $ua->get("http://www.livejournal.com/users/$user/rss"); if ($resp->content) { $rss->parse($resp->content); foreach my $item (@{$rss->{'items'}}) { my $t = Time::Piece->strptime($item->{'pubDate'}, "%a, %d %b %Y %T GMT"); $item->{'title'} .= " - by $user"; $allitems{$t->epoch} = $item; } } else { push @errorusers, $user; } $g++; #XXX } my $rssfile = new XML::RSS (version => '2.0'); $rssfile->channel( 'title' => $data{username} . "'s Friends", 'link' => "http://www.livejournal.com/users/" . $data{username} . "/friends", 'description' => "You have no life already by writing your own entries, why not make it worse by reading other people's?", 'language' => 'en' ); my $n = 0; foreach my $post (sort {$b cmp $a} keys %allitems) { last if $n == MAX_ENTRIES; push @{$rssfile->{'items'}}, $allitems{$post}; $n++; } print $rssfile->as_string;