This file uses the del.icio.us API to backup a given account’s bookmarks. It also creates an index of tag -> URL mappings, to be used by an automatic redirection script. Gzipped raw source code is here.
#!/usr/bin/env perl # -*- coding: utf-8 -*- # # Written by Aidan Kehoe, Wed Aug 9 22:56:17 CEST 2006. Public domain. use strict; use warnings; use DB_File; use Date::Parse qw/str2time/; use Encode; use Fcntl; use File::Temp qw/tempfile tempdir/; use HTTP::Request; use LWP::UserAgent; use XML::Parser qw/new parserfile/; use constant { 'TAG_DB_FILE_NAME' => 'YOU NEED TO SET THIS', # Where the backup of your del.icio.us entries is stored. 'DELICIOUS_CURRENT_DUMP' => 'YOU NEED TO SET THIS TOO', 'DELICIOUS_USER_NAME' => 'AND THIS', 'DELICIOUS_PASSWORD' => 'AND THIS', }; # Use the del.icio.us API to check if a bookmark update has been made since # last backup; if it has, download it. sub backup_delicious { my ($buffer, $ua, $response, $old_update, $new_update, $tempfile_name, $tempdir, $request); $ua = LWP::UserAgent->new( 'cookie_jar' => {}, 'requests_redirectable' => [ 'GET', 'HEAD', 'POST' ], 'timeout' => 30, 'agent' => DELICIOUS_USER_NAME." del.icio.us backup script." ); if (open DEL_FILE, DELICIOUS_CURRENT_DUMP) { sysread DEL_FILE, $buffer, 1024; close DEL_FILE; $buffer =~ /<posts update="([^"]+)" user="/; $old_update = str2time $1; } else { print "backup-del.icio.us.pl: could not open the old dump\n"; $old_update = 0; } $request = new HTTP::Request GET => 'https://api.del.icio.us/v1/posts/update'; $request->authorization_basic(DELICIOUS_USER_NAME, DELICIOUS_PASSWORD); $response = $ua->request($request); unless ($response->is_success) { # Failed; oh well, we'll get it tomorrow. print "backup-del.icio.us.pl: failed checking for updates: ". $response->status_line."\n"; return undef; } $response->content =~ /<update time="([^"]+)" /; $new_update = str2time $1; if ($old_update >= $new_update) { return undef; } # Provide somewhere to download the posts to. $tempdir = tempdir( CLEANUP => 1); (undef, $tempfile_name) = tempfile( DIR => $tempdir); $request = new HTTP::Request GET => 'https://api.del.icio.us/v1/posts/all?'; $request->authorization_basic(DELICIOUS_USER_NAME, DELICIOUS_PASSWORD); $response = $ua->request($request, $tempfile_name); unless ($response->is_success) { # Failed; oh well, we'll get it tomorrow. print "backup-del.icio.us.pl: failed downloading posts: ". $response->status_line."\n"; return undef; } open(DEL_FILE, '>', DELICIOUS_CURRENT_DUMP) or print "backup-del.icio.us.pl: could not replace the old dump\n", return undef; open NEW_DEL_FILE, $tempfile_name or print "backup-del.icio.us.pl: could not open the new dump\n", return undef; print DEL_FILE $buffer while sysread NEW_DEL_FILE, $buffer, 8192; close NEW_DEL_FILE; close DEL_FILE; return 1; } sub rebuild_redirect_map { my %tag_hash; # Avoid duplicate entries in the hash. unlink TAG_DB_FILE_NAME; my $tag_db = tie %tag_hash, 'DB_File', TAG_DB_FILE_NAME, O_RDWR|O_CREAT, 0644 or die "Cannot open ".TAG_DB_FILE_NAME.": $!\n"; my $xml_parser = new XML::Parser('Style' => 'Tree'); my $tree = $xml_parser->parsefile(DELICIOUS_CURRENT_DUMP); my $expecting_posts_array = 0; my ($postinfo, $expecting_post_array, $postarr); for my $postsp (@{$tree}) { if ($postsp eq '0') { $expecting_posts_array = 0; next; } if ($postsp eq 'posts') { $expecting_posts_array = 1; next; } unless ($expecting_posts_array) { next; } $expecting_post_array = 0; for $postarr (@{$postsp}) { if ($postarr eq '0') { $expecting_post_array = 0; next; } if ($postarr eq 'post') { $expecting_post_array = 1; next; } unless ($expecting_post_array) { next; } $postinfo = shift @{$postarr}; # This is broken in the abstract, I shouldn't need to do this if # I turn on the appropriate modules above (that is, "use open," # and so on.). Bah, Perl. # # Of course, it does mean it shouldn't do Unicode lowercase # handling, just ASCII. Yay, Perl. $postinfo->{'href'} = encode('utf8', $postinfo->{'href'}); $postinfo->{'tag'} = encode('utf8', $postinfo->{'tag'}); my @tags = split / /, $postinfo->{'tag'}; for my $tag (@tags) { # We're not case sensitive. $tag = lc $tag; if (defined $tag_hash{$tag}) { # No automatic serialisation--this is Perl! $tag_hash{$tag} .= "\0".$postinfo->{'href'}; } else { $tag_hash{$tag} = $postinfo->{'href'}; } } } } undef $tag_db; untie %tag_hash; } sub main { my ($dump_mtime, $db_mtime); backup_delicious; $dump_mtime = (stat DELICIOUS_CURRENT_DUMP)[9]; $db_mtime = ((stat TAG_DB_FILE_NAME)[9] or 0); rebuild_redirect_map if $dump_mtime > $db_mtime; } main;