Originally written on 2005-06-04.
I thought it would be cool to have link digests posted in my journal once in a while automatically. It would leave a record of places I’ve been and things I’ve been interested in, but haven’t had the time/itch to write extensively about.
There are quite a few requirements:
- must be called periodically (ie. via cron)
- a functioning mail setup must be present
- paid LJ account for email posting
- a bunch of Perl modules that your host may not have (mine didn’t have XML::Simple). I’m lazy, though, and just wanted to use the tools at my disposal.
Note: If you’re going to use this, you may need to append “&count=n” to the API url (where n is an int between 1 and 100) to get the appropriate number of recent posts. I don’t use del.icio.us extensively, so the default 15 is good for me.
I hope I got the LWP stuff right. The script will back-off if yelled at, but it’s quite persistent otherwise (which may not be a good thing). Maybe I’ll tune down the number of repeated requests before giving up.
So, now, if I post any new links with a ljpost tag, it should automatically update every Friday and lj-cut the entry if there are too many links.
Source: Perl file
#!/usr/bin/perl -w
# Copyright Dmitri Vassilenko (dv [at] glyphy [dot] com)
# Personal use is OK. Keep the ownership info.
# Needs to be called from cron (or whatever you have) once in a while.
# Upon each successive call with no arguments, it will query del.icio.us
# for new posts under a certain tag and store the fetched data on disk along
# with the last post time. Naturally, you shouldn't run this in a tight loop.
# I run this a couple of times a day. You may get throttled by del.icio.us if
# you poll too often.
# To force a post, use the -u cmd-line option.
# If you post a lot, you may wish to append "&count=n" to $DEL_API, where n
# is the number of posts you'd like to fetch. The default is 15 and max is 100.
### Requirements
use strict;
use Getopt::Std;
use Data::Dumper;
use LWP::UserAgent;
use XML::Simple;
use Storable;
use DateTime::Format::W3CDTF;
use Mail::Send;
########## Define constants ##########
use constant LJ_USERNAME => 'YourLJUsername';
use constant LJ_PIN => 'YourLJEmailPostingPin';
my $DEL_USERNAME = 'YourDeliciousUsername';
my $DEL_PASSWORD = 'YourDeliciousPassword';
my $DEL_TAG = 'postingTag';
# changing the following is optional
my $DEL_API = "https://api.del.icio.us/v1/posts/recent?tag=$DEL_TAG";
use constant TIME_FILE => '/home/you/last'; # stores last update time
use constant POSTS_FILE => '/home/you/posts'; # stores queued items for posting
use constant MAX_DOC_SIZE => 100000; # in bytes
use constant TIMEOUT => 10;
use constant MAX_POSTS => 7; # after this # of new items, an lj-cut is used
use constant MIN_POSTS => 3; # don't post less than this # of items
######################################
sub usage {
print "Usage: ljdelpost [-u] [-s] [-t n]
Fetches the last few posts from del.icio.us and stores them on disk.
-u updates and forces a post
-s shows the latest posts currently stored on disk without posting them
-t n trims the posts list to n last posts\n";
}
# Parse commands
my $cmds = getopts('usht:');
our($opt_h, $opt_s, $opt_u, $opt_t);
if (defined $opt_h) { usage(); exit 0 }
########## Fetch stored data from disk ##########
if (! -e TIME_FILE) {
my $initial_time = 0;
store(\$initial_time, TIME_FILE);
}
my $last = retrieve(TIME_FILE); # Last update time from epoch
if (! -e POSTS_FILE) {
my @initial_posts;
store(\@initial_posts, POSTS_FILE);
}
my $sposts = retrieve(POSTS_FILE); # Posts stored on disk
#################################################
if (defined $opt_t) {
splice(@{$sposts}, $opt_t);
store($sposts, POSTS_FILE) or die "Can't store %a in " . POSTS_FILE . "\n";
$opt_s = 1;
}
if (defined $opt_s) {
print "Stored posts:\n";
print Dumper($sposts);
exit 0;
}
my $ua = new LWP::UserAgent;
my $content;
$ua->max_size(MAX_DOC_SIZE);
$ua->timeout(TIMEOUT);
$ua->agent("LJDelPost (http://www.livejournal.com/users/troworld/162341.html)");
my $req = new HTTP::Request GET => $DEL_API;
$req->authorization_basic($DEL_USERNAME, $DEL_PASSWORD);
my $res;
while ($res = $ua->request($req)) {
if ($res->is_success) {
$content = $res->content;
last;
} else {
warn "Error: " . $res->code . ' ' . $res->message;
exit 1 if ($res->code == 503 || $res->code == 401); # Back off
sleep 60;
}
}
my $posts = XMLin($content, ForceArray => 1)->{post};
my $f = DateTime::Format::W3CDTF->new;
my $dt; # Date of $i'th post
my @newposts; # will be copied to disk when complete
my $newtime = $f->parse_datetime($posts->[0]->{time}); # date of last new post
foreach my $i (@{$posts}) {
$dt = $f->parse_datetime($i->{time});
last unless ($dt->epoch() > ${$last}); # Posts are in descending order sorted
# by time
push @newposts, $i;
}
# Copy new posts to disk
if ($#newposts >= 0) {
unshift @{$sposts}, @newposts;
store($sposts, POSTS_FILE) or die "Can't store %a in " . POSTS_FILE . "\n";
store(\$newtime->epoch(), TIME_FILE) or die "Can't store %a in " . TIME_FILE . "\n"; # update last post time
}
if (defined $opt_u) {
if ($#{$sposts} < 0 || $#{$sposts} < MIN_POSTS) {
warn 'Not enough new posts. Not sending any messages.';
exit 0;
}
my $msg = new Mail::Send;
$msg->to(LJ_USERNAME . '+' . LJ_PIN . '@post.livejournal.com');
#$msg->cc('your@email.com'); # CC yourself
$msg->subject('Links for ' . $newtime->day . ' ' . $newtime->month_name . ' ' . $newtime->year);
my $mailer = $msg->open;
#open(my $mailer, '>-'); # for debugging purposes
print $mailer "lj-security: public\nlj-tags: links\nlj-userpic: default\n\n";
print $mailer "<lj-cut text=\"" . @{$sposts} . " links\">" if ($#{$sposts}+1 > MAX_POSTS);
print $mailer "<lj-raw>\n<ul class='delList'>\n";
foreach my $i (reverse @{$sposts}) {
my $posted_time = $f->parse_datetime($i->{time});
print $mailer ' <li class="delPost">
<a href="' . $i->{href} .
'">' . $i->{description} . '</a> <small>(Posted on ' . $posted_time->day . ' ' . $posted_time->month_name . ' ' . $posted_time->year . ' @ ' . $posted_time->hms . ' ' . $posted_time->time_zone_short_name . ")</small>\n";
print $mailer ' <div class="delExt">' . $i->{extended} . "</div>\n" if (exists $i->{extended});
my $tagline = $i->{tag};
$tagline =~ s|$DEL_TAG||; # kill the lj tag
if (length $tagline > 0) {
my @tags = split(' ', $tagline);
print $mailer ' <div class="delTags">Tags: ';
foreach my $j (@tags) {
print $mailer '<a href="http://del.icio.us/' . $DEL_USERNAME . "/$j\">$j</a> ";
}
print $mailer "</div>\n";
}
print $mailer " </li>\n";
}
print $mailer "</ul>\n";
print $mailer '</lj-raw>';
print $mailer "</lj-cut>\n" if ($#{$sposts}+1 > MAX_POSTS);
$mailer->close; # Send the message
my @cleanposts;
store(\@cleanposts, POSTS_FILE) or die "Can't store %a in " . POSTS_FILE . "\n";
}