# Blosxom Plugin: related-links # Author(s): Brian Del Vecchio # Version: 1.0 # This is for Blosxom 3.0 only. # http://www.hybernaut.com/bdv/related.html # Copyright 2004 Brian Del Vecchio # Released under the same License as Blosxom package Blosxom::Plugin::Related; use File::stat; use XML::LibXML; use LWP; use XML::Quote qw(:all); ### Begin user configuration section # change this to your del.icio.us credentials my $username = "username"; my $password = "password"; my @supported_flavours = ( 'html' ); my $cache_time = 7200; # seconds my $max_links = 10; my $backoff_secs = 1; # enable/disable the per-tag links after each post my $show_tag_links = 1; # strip the following tags from display # NOTE these are regexps my @private_tags = ( '^to:', '^\.'); # skip any links tagged with the following tag my $hide_link_tag = '.hide'; ### End configuration section my $version = "1.0"; my %allposts; my $taglinks = ''; my $rellinks = ''; # if we are throttled by the del.icio.us server, stop making requests. my $throttled = 0; sub run { my $self = shift; my $skip = 1; foreach my $flav (@supported_flavours) { if ($flav eq $self->{request}->{flavour}) { $skip = 0; } } return if ($skip); my $body_ref = \$self->{state}->{current_entry}->{body}; # permalink calculation (so we can exclude self-link from related) my $url = $self->{request}->{url}; my $path = $self->{state}->{current_entry}->{path}; my $file = $self->{state}->{current_entry}->{fn}; my $dflav = $self->{settings}->{default_flavour}; my $plink = $url . $path . $file . '.' . $dflav; $self->{state}->{current_entry}->{Plugin}->{Related}->{permalink} = $plink; my $cid = $self->{state}->{current_entry}->{Plugin}->{Meta}->{contentid}; my $tags = $self->{state}->{current_entry}->{Plugin}->{Meta}->{tags}; if ($tags) { # produce a list of related tags, linkified $taglinks = get_tag_links($self, $cid, $tags); $self->{state}->{current_entry}->{Plugin}->{Related}->{taglinks} = $taglinks; # produce a list of related links, tagified $rellinks = get_related_links($self, $cid, $tags, $plink); $self->{state}->{current_entry}->{Plugin}->{Related}->{rellinks} = $rellinks; } 1; } sub get_related_links { my ($self, $cid, $tags, $plink) = @_; my $cache_dir = $self->{settings}->{state_dir} . "/related_cache"; if (!-d $cache_dir) { mkdir $cache_dir; } %allposts = (); $ua = LWP::UserAgent->new; $ua->agent("Blosxom::Plugin::Related/$version"); my $parser = XML::LibXML->new(); $cidtag = ''; $cidtag = 'content-id:' . $cid unless ($cid eq ''); # support for multiple tags # right now del.icio.us doesn't have a union query, # so we perform multiple queries and merge the results foreach my $tag ( $cidtag, split ' ', $tags ) { next unless ($tag); my $cache = "$cache_dir/$tag.xml"; # use tricks from 'syndicated' plugin to cache result documents if (!$throttled && !stat($cache) || time - stat($cache)->mtime >= $cache_time) { my $url = "http://" . $username . ":" . $password . "\@del.icio.us/api/posts/recent?count=20&tag=" . $tag; my $req = HTTP::Request->new(GET => $url); my $resp = $ua->request($req); if (503 == $resp->code) { $throttled++; } if (200 == $resp->code) { open(FILE,'>',$cache); print FILE $resp->content; close FILE; } sleep $backoff_secs; } # parse the xml result for the current tag my $tree = $parser->parse_file($cache); my $root = $tree->getDocumentElement; my @posts = $root->getElementsByTagName('post'); # add the posts for this tag to allposts. # this is complicated, but has the effect of removing dupes POST: foreach my $post (@posts) { my $hash = $post->getAttribute('hash'); # exclude this entry's permalink, if given next if ($plink eq $post->getAttribute('href')); # suppress this entry, if it has one of these tags foreach my $ptag (split ' ', $post->getAttribute('tag')) { next POST if ($ptag eq $hide_link_tag); } if ($allposts{$hash}) { # rank up links with multiple tag matches $allposts{$hash}{rank}++; } else { $allposts{$hash} = { href => $post->getAttribute('href'), tags => $post->getAttribute('tag'), desc => $post->getAttribute('description'), extn => $post->getAttribute('extended'), time => $post->getAttribute('time'), hash => $post->getAttribute('hash'), rank => 1 }; } # rank up a match on the content-id if ($tag eq $cidtag) { $allposts{$hash}{rank}+= 5; } } } # Now walk through the list of posts and generate HTML my $html; # generated html my $count = 0; # sort the results by tag matches, then date (newest first) foreach $post ( sort sort_links keys %allposts ) { # take the newest $max_links last if ($count ++ >= $max_links); my $p = $allposts{$post}; $html .= ""; } return $html; 1; } # convert a space-separated list of tags into delicious tag links sub get_tag_links { my ($self, $cid, $tags) = @_; my $button = '/images/tags2.png'; my $class = 'delTagLink'; my $html = "\n surf these tags\n" ; foreach my $t ( sort split ' ', $tags ) { next if $t =~ /^content-id:/; next if tag_match($t, @private_tags); $html .= " " . $t . "\n"; } return $html; 1; } # sort by number of tag matches, then timestamp. # more tag matches means more relevant link. sub sort_links { $allposts{$b}{rank} <=> $allposts{$a}{rank} || $allposts{$b}{time} cmp $allposts{$a}{time} } # true if tag matched in tag_list sub tag_match { my ($tag, @taglist) = @_; foreach my $t (@taglist) { return 1 if ($tag =~ $t); } return 0; } 1; __END__ ChangeLog: * links tagged with .hide are not displayed * $show_tag_links controls generation of per-tag links to delicious * tags matching @private_tags (list of regexp) are not displayed * xml-escape description text used in titles * Only execute for explicitly supported flavours * Exclude permalink from related links. This is used to prevent the current story from listing.