NunoNunes.org

Loading
Entries by year
2012
Months
JanFeb Mar Apr
May Jun Jul Aug
Sep Oct Nov Dec
Entries by month
January
Sun Mon Tue Wed Thu Fri Sat
10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31        
Powered by Blosxom
Creative Commons License

Blosxom plugin for Disqus feedback system

This is a plugin that I concocted for enabling the usage of the Disqus on a Blosxom site.

It is perfectly possible to add this functionality just by tweaking the flavour templates, of course, but this makes it easier to use and also makes it possible to port existing sites to Blosxom and easily maintain the comments that may already exist.

It also allows for the individual turning on or off of feedback on a per-story basis.

I’ll probably add a zipped file with the plugin somewhere on the site, but for now the code is displayed right here on this page.

# Blosxom Plugin: disqus
# Author: Nuno Nunes <http://nunonunes.org/>
# Version: v0.01  2010-01-05
# Documentation: See the relevant sections of this file or type: perldoc disqus

package disqus;

# --- Configurable variables -------------------
#
# What is your username on Disqus (the one that should appear
# on the javascript section of the HTML pages)?
$disqus_username = 'nunonunes' unless defined $disqus_username;
# 
# Message to display if feedback (comments and trackbacks) is not allowed 
# on a story.
$feedback_not_allowed_message = 'Feedback has been disabled for this page'
   unless defined $feedback_not_allowed_message;
#
# ---------------------------------------------

# Use $disqus::comments and $disqus::comment_count in your story template.
# Use $disqus::footer in your footer template.

use vars qw{ $footer $comments $comment_count };

sub start {
  1;
}

sub story {
  my ($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_;

  my $feedback_allowed = 1;
  $feedback_allowed = 0
    if (defined $meta::allowfeedback && $meta::allowfeedback =~ /^n/i);

  if ($pagetype::pagetype eq 'story') {
    $comment_count ="";
    if ($feedback_allowed) {
      $comments = <<COMMENTS_THREAD;
<div id="disqus_thread"></div>
<script type="text/javascript" src="http://disqus.com/forums/$disqus_username/embed.js"></script>
<noscript><a href="http://disqus.com/forums/$disqus_username/?url=ref">View the discussion thread.</a></noscript>
COMMENTS_THREAD
    }
    else {
      $comments = $feedback_not_allowed_message;
    }
  }
  else {
    $comments = "";
    $comment_count = "";
    if ($feedback_allowed) {
      $comment_count = "$permalink::story#disqus_thread";
    }
  };

  1;
}

sub foot {
  my ($pkg, $dir, $head_ref) = @_;

  $footer = <<FOOTER_END;
<script type="text/javascript">
//<![CDATA[
(function() {
    var links = document.getElementsByTagName('a');
    var query = '?';
    for(var i = 0; i < links.length; i++) {
    if(links[i].href.indexOf('#disqus_thread') >= 0) {
        query += 'url' + i + '=' + encodeURIComponent(links[i].href) + '&';
    }
    }
    document.write('<script charset="utf-8" type="text/javascript" src="http://disqus.com/forums/$disqus_username/get_num_replies.js' + query + '"></' + 'script>');
})();
//]]>
</script>
FOOTER_END

  if ($pagetype::pagetype eq 'story') {
    $footer = "";
  }

  1;
}


1;

=head1 NAME

Blosxom Plug-in: disqus


=head1 SYNOPSIS

Integrates the comment and trackback service provided by Disqus
(http://disqus.com/) into blosxom.

Requires the permalink and pagetype plugins and knows how to work with the meta 
    plugin if it is installed.

=head1 INSTALLATION

Fill in the configurable variables according to the instructions and
put the file in your plugin directory (just like any other plugin).

If you whish to use the meta plugin together with this one make sure
that the it is run before this one (renaming meta to 00meta and this
one to 99disqus does the trick --read the bloxsom plugin documentation
if you require further assistance).

You need to have the permalink plugin installed for this one to work and it must 
run before this one does. You can use the same scheme as defined for the meta 
plugin to that effect.

    You also need to have the pagetype plugin installed and have it run before
    this one, just like the permalink plugin above.

=head1 CONFIGURATION

Fill in the variables in the Configurable varables section of this file 
according with the descriptions (which should hopefully be self-explanatory) 
and if you have any doubts check out the usage section bellow.

=head1 USAGE

After having set up an account on Disqus and filled in all the variables to 
be configured at the top of this plugin, just put the $disqus::footer in your 
foot flavour template, near the end.

Then, on your story flavour template, drop the $disqus::comment_count and 
$disqus::comments variables wherever you want them to appear and that's it!

Feedback (both comments and trackbacks) is enabled by default for all stories.
Should you wish to control this behaviour, you can do so on a story-by-story
basis using the meta plugin and assigning the meta-variable
meta-allowfeedback as exemplified below:

----------
My great story's name
meta-allowfeedback: no

This is a really interesting story...
----------

Any value you assign to this variable starting with the letter n turns it off 
and anything else (including not setting the variable at all) turns it on.

=head1 BUGS

Bug reports and comments may be sent to nuno@nunonunes.org.

=head1 CHANGELOG

2011-03-21 - Fixed some typos and bugs that were helpfully pointed out by 
             Christian G. Warden.

2010-01-05 - First implementation.

=head1 AUTHOR

Nuno Nunes <http://nunonunes.org/>

=head1 LICENSE

This Disqus Plug-in
Copyright 2010, Nuno Nunes

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

About this entry

Originally written on Jan 05, 2010 @ 22:33
Read article on it's own page (permalink)

Wazup

Note: This is just an idea at this stage, I don’t know if/when I’ll ever get around to implementing it. Knowing me, it will probably never happen, so don’t hold your breath if you find this to be interesting.

What is it?

Wazup is an idea of mine for a service that boils down to a site which lets me know, at a glance, what is happening now, near me. For a given definition of now. And of near.

Introduction and motivation

This project was born for a number of reasons, of which I highlight a few to help me keep on track if I ever get to developing it:

  1. I wanted to learn Python and there is no better way to learn a new language than to have a real, useful project to build using it;
  2. I wanted to try out the Google App Engine;
  3. I wanted to build a new application and code it from the ground up (feeling nostalgic from the days when I did good old honest-to-God coding);
  4. I also wanted it to be a web application, since I’ve never really written one of these;
  5. I actually find myself sometimes in the late afternoon or early evening feeling like going out to do something and having to trawl through a number of sites and feeds that I keep with “interesting stuff” that is happening.

From the above list it should be apparent that even if I do know that there are probably a dozen such sites out there, I really want to indulge in thinking about (and maybe even prototyping) one.
As such, comments along the lines of “Yeah, but site X.com does it this way” or “Well, site Y.org does it better” are totally irrelevant so don’t bother.
On the other hand, comments which point me to nifty feature ideas or implementation details or something along these lines are most welcome (even if they may well end up not being considered for the exercise).

Intended features

First of all a warning: this is not a road-map for the app’s development. I’m not actually sure I’ll even ever get to build the thing. This is just a list of stuff I’d like to eventually have working, if I do go ahead and do it.

The numbers in front of each item are a crude idea of the importance of each idea. A ‘1’ means that it is important to have ASAP, a ‘3’ means that it can wait (and possibly will never get done at all).

And now, for the list:

  • Handle multiple users and their respective data feeds and preferences (3);
  • Handle multiple data sources, with fetchers and parsers built specifically for each one of them (1);
  • Have an easy plug-in system to build such fetchers and parsers (1);
  • Be able to fetch the data in real-time (unless I can figure out if the Google App Engine environment allows me to call a function regularly with some kind of scheduled job or something);
  • Allow me to define what time-frame I’m interested in seeing (2);
  • Allow me to pick and choose events to look at more closely (3);
  • Allow me to choose how I want the results to be ordered (by location, by time, …) (2);
  • Be aware of where I am (or allow me to tell it) and automatically adjust to show me the relevant events only (over-rideable, and using some of the many geo-location services available out there —so web2.0!) (3).

About this entry

Originally written on May 07, 2008 @ 22:47
Read article on it's own page (permalink)

Blosxom plugin for Haloscan feedback system

This is a plugin that I concocted for enabling the usage of the Haloscan on a Blosxom site.

It is perfectly possible to add this functionality just by tweaking the flavour templates, of course, but this makes it easier to use and also makes it possible to port existing sites to Blosxom and easily maintain the comments that may already exist.

It also allows for the individual turning on or off of both comments and trackbacks on a per-story basis.

I’ll probably add a zipped file with the plugin somewhere on the site, but for now the code is displayed right here on this page.

# Blosxom Plugin: haloscan
# Author: Nuno Nunes (nuno@nunonunes.org)
# Version: v0.02  2006-09-15
# Documentation: See the relevant sections of this file or type: perldoc haloscan

package haloscan;

# --- Configurable varables -------------------
#
# What is your account name on haloscan (the one that should appear
# on the javascript in the 'head' section of the HTML page)?
$haloscan_id = 'nunonunes' unless defined $haloscan_id;
#
# Message to display if comments are not allowed on a story.
$comments_closed_message = 'Comments are closed for this page'
  unless defined $comments_closed_message;
#
# Message to display if trackbacks are not allowed on a story.
$trackbacks_closed_message = 'Trackbacks are closed for this page'
  unless defined $trackbacks_closed_message;
#
# ---------------------------------------------

# Use $haloscan::head in your head template.
# Use $haloscan::comments and $haloscan::trackbakcs in your story template.

#use strict;
use vars qw{ $header $comments $trackbacks };

sub start {
  1;
}

sub head {
  my ($pkg, $dir, $head_ref) = @_;

  $header = qq{<script type="text/javascript" src="http://www.haloscan.com/load/$haloscan_id"> </script>};

  1;
}

sub story {
  my ($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_;

  my $story_id = $permalink::story;
  $story_id =~ s/$blosxom::url//;

  $story_id = $meta::haloscanstoryid
    if defined $meta::haloscanstoryid;

  my $comments_allowed = 1;
  $comments_allowed = 0
    if (defined $meta::haloscancomments && $meta::haloscancomments =~ /^n/i);
  my $trackbacks_allowed = 1;
  $trackbacks_allowed = 0 
    if (defined $meta::haloscantrackbacks && $meta::haloscantrackbacks =~ /^n/i);

  if ($comments_allowed) {
    $comments = qq{<a href="javascript:HaloScan('$story_id');"><script type="text/javascript">postCount('$story_id');</script></a>};
  }
  else {
    $comments = $comments_closed_message;
  };

  if ($trackbacks_allowed) {
    $trackbacks = qq{<a href="javascript:HaloScanTB('$story_id');"><script type="text/javascript">postCountTB('$story_id');</script></a>};
  }
  else {
    $trackbacks = $trackbacks_closed_message;
  };



  1;
}

1;

=head1 NAME

Blosxom Plug-in: haloscan


=head1 SYNOPSIS

Integrates the comment and trackback service provided by Haloscan 
(http://haloscan.com/) into blosxom.

Requires the permalink plugin and knows how to work with the meta plugin
if it is installed.

=head1 INSTALLATION

Fill in the configurable variables according to the instructions and
put the file in your plugin directory (just like any other plugin).

If you whish to use the meta plugin together with this one make sure
that the it is run before this one (renaming meta to 00meta and this
one to 99haloscan does the trick --read the bloxsom plugin documentation
if you need further assistance).

You need to have the pemalink plugin installed for this one to work and
it must run before this one does. You can use the same scheme as defined
for the meta plugin to that effect.

=head1 CONFIGURATION

Fill in the variables in the Configurable varables section of this file
according with the descriptions (which should hopefully be self-explanatory)
and if you have any doubts check out the usage section bellow.

=head1 USAGE

After having set up an account on Haloscan and filled in all the variables to
be configured just put the $haloscan::header in your head flavour template
anywhere inside the HEAD HTML element.

Then, on your story flavour template, drop the $haloscan::comments and
$haloscan::trackbacks variables wherever you want them to appear and that's it!

Both comments and trackbacks are enabled by default for all stories.
Should you wish to control this behaviour, you can do so on a story-by-story
basis using the meta plugin and assigning the meta-variables
meta-haloscancomments and meta-haloscantrackbacks variables as exemplified
below:

----------
My great story's name
meta-haloscancomments: no
meta-haloscantrackbacks: yes
meta-haloscanstoryid: 1115

This is a really interesting story...
----------

For any one of the meta-haloscancomments and meta-haloscantrackbacks variables,
any value starting with the letter n turns it off and anything else (including
not setting the vriable at all) turns it on.

The meta-haloscanstoryid variable allows you to retain story IDs for specific 
stories in case you're porting from a different blogging system or if you want 
your Haloscan story IDs to be generated some other way.

These three variables (meta-haloscancomments, meta-haloscantrackbacks and 
meta-haloscanstoryid) are optional.

=head1 BUGS

Bug reports and comments may be sent to blosxom_haloscan.feedback@nunonunes.org.

=head1 CHANGELOG

2006-09-16 - Added the ability to import storyIDs from a meta-variable.
2006-09-15 - First implementation.


=head1 LICENSE

This Blosxom Plug-in
Copyright 2006, Nuno Nunes

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

About this entry

Originally written on Sep 16, 2006 @ 17:46
Read article on it's own page (permalink)

Flickerize My Photolog

What is This then?

This page contains a script for getting all the photos from a given flickr photoset and some HTMl and CSS to display them in a nice way.

Introduction and Motivation

This script was born out of my desire to use Flickr as a “storage engine” for my photolog.

I wanted to take a specific photoset from my Flickr account and display those photos as a photolog, on my site, with my own design.

The best way to do it (OK, it may or may not be the best, but it sure is the most fun) was via it’s API, specifically, the Flickr::API Perl’s API.

At the time of this writing the script goes out and fetches the information on the photos but doesn’t do anything intelligent with it. It is a work in progress and it is getting late…

Source Code

This version of the source code still has it’s own functions to parse the response from the Flickr::API::Response object. This job will be moved to a new set of modules and will be removed from here but until then here it is.

One more note: this script outputs something which is very specific to my own test site and it expects to have a combination of pages which include the files it outputs and also a CSS style-sheet to make it all look good.

In this page you will also find the companion files for my test setup.

#!/usr/bin/perl -w

use strict;
use Flickr::API;
use Data::Dumper;

my $DEBUG=1;
my $flickr_api_key='<YOUR API KEY>';
my $flickr_username='<YOUR FLICKR USERNAME>';
my $desired_photoset='<YOUR PHOTOSET NAME>';
my $api = new Flickr::API({'key' => $flickr_api_key});
my $local_photos_path='<LOCAL PATH FOR THE SITE>';
my $local_index_file=$local_photos_path.'/photolistbody.html';
my $local_individual_photos_path=$local_photos_path.'/photos';


# Get the nsid from the username
my $findUser = $api->execute_method('flickr.people.findByUsername',
           {
         'username' => $flickr_username
           });
testError($findUser);

my $goodies = $findUser->{tree}{children}[1]{attributes};
my $nsid = $goodies->{nsid};
print STDERR "Got nsid: $nsid\n" if $DEBUG;

# Now get the required photoset's ID
my $getList = $api->execute_method('flickr.photosets.getList', {
           'user_id' => $nsid
           });
testError($getList);

my $photoset_id;
foreach $goodies (@{$getList->{tree}{children}[1]{children}}) {
  next unless (defined($goodies->{name})
           and ($goodies->{name} eq 'photoset'));
  my $found_photoset=0;
  foreach my $this_photoset (@{$goodies->{children}}) {
    next unless (defined($this_photoset->{name})
             and ($this_photoset->{name} eq 'title'));
    if ($this_photoset->{children}[0]{content} eq $desired_photoset)
    {
    $found_photoset = 1;
    last;
    }
  }
  if ($found_photoset) {
    $photoset_id = $goodies->{attributes}{id};
    last;
  }
}
print STDERR "Got the ID for the '$desired_photoset' photoset: $photoset_id\n"
  if $DEBUG;

# Let's finally get the pictures from the photoset
my $getPhotos = $api->execute_method('flickr.photosets.getPhotos', {
           'photoset_id' => $photoset_id
           });
testError($getPhotos);

my @photos;
foreach $goodies (@{$getPhotos->{tree}{children}[1]{children}}) {
  next unless (defined($goodies->{name}) and ($goodies->{name} eq 'photo'));
  unshift @photos, {title => $goodies->{attributes}{title},
         flickrurl => photoFlickrURL($goodies->{attributes}),
         localurl => photoLocalURL($goodies->{attributes}),
         flickrthumb => photoFlickrURL($goodies->{attributes},'s'),
         id => $goodies->{attributes}{id},
         secret => $goodies->{attributes}{secret}};
}

foreach my $photo (@photos) {
  my $getInfo = $api->execute_method('flickr.photos.getInfo', {
        photo_id => $photo->{id},
        secret => $photo->{secret},
        });
  testError($getInfo);

  # Get all the relevant data for each photo
  my $info_data = readInfo($getInfo);
  foreach my $data_item (keys(%$info_data)) {
    $photo->{$data_item} = $info_data->{$data_item};
  } 

  my $getExif = $api->execute_method('flickr.photos.getExif', {
        photo_id => $photo->{id},
        secret => $photo->{secret},
        });
  testError($getExif);

  # Get all the EXIF data for each photo
  $photo->{exif} = readExif ($getExif);
}

# Finaly write the index file and the individual photo files
open INDEX, ">$local_index_file" or die "Could not open index file for writing: $!";
print INDEX "<ul id=\"thumbList\">\n";
my $index=0;
foreach my $photo (@photos) {
  open PHOTOFILE, ">$local_individual_photos_path/".$photo->{id}.".html" or die "Could not open photo file for writing: $!";
  print INDEX "<li><a href=\"".$photo->{localurl}."\"><span class=\"thumbNail\"><img src=\"".$photo->{flickrthumb}."\" alt=\"".$photo->{title}."\" /></span><span class=\"thumbNailTitle\">".$photo->{title}."</span></a></li>\n";
  print PHOTOFILE "<ul class=\"photoNav\">\n";
  if (exists $photos[$index+1]) {
    print PHOTOFILE "<li><a href=\"".$photos[$index+1]->{localurl}."\"><--</a></li>\n";
  } else {
    print PHOTOFILE "<li>|--</li>\n";
  }
  print PHOTOFILE "<li><a href=\"ispy.html\">index</a></li>\n";
  if ($index) {
    print PHOTOFILE "<li><a href=\"".$photos[$index-1]->{localurl}."\">--></a></li>\n";
  } else {
    print PHOTOFILE "<li>--|</a></li>\n";
  }
  print PHOTOFILE "</ul>\n";
  print PHOTOFILE "<h1 id=\"photoTitle\">".$photo->{title}."</h1>\n";
  print PHOTOFILE "<div id=\"photoBig\"><img src=\"".$photo->{flickrurl}."\" alt=\"".$photo->{title}."\" /></div>\n";
  print PHOTOFILE "<p id=\"photoDescription\">".$photo->{description}."</p>\n"
    if defined $photo->{description};
  print PHOTOFILE "<div id=\"photoData\">\n";
  print PHOTOFILE "<p id=\"photoDate\">Photographed on: ".$photo->{date_taken}."</p>\n"
    if defined $photo->{date_taken};
  if (exists $photo->{exif}) {
    print PHOTOFILE "<ul id=\"EXIFData\">\n";
    foreach my $exifItemName (keys(%{$photo->{exif}})) {
      if ($exifItemName eq 'cameramodel') {
    print PHOTOFILE "<li>Camera model: ".$photo->{exif}{$exifItemName}."</li>\n";
      } elsif ($exifItemName eq 'shutterspeed') {
    print PHOTOFILE "<li>Shutter speed: 1/".eval($photo->{exif}{$exifItemName})."s</li>\n";
      } elsif ($exifItemName eq 'aperture') {
    print PHOTOFILE "<li>Lens aperture: f/".eval($photo->{exif}{$exifItemName})."</li>\n";
      } elsif ($exifItemName eq 'ISOspeed') {
    print PHOTOFILE "<li>ISO speed: ".$photo->{exif}{$exifItemName}."</li>\n";
      } elsif ($exifItemName eq 'focallength') {
    print PHOTOFILE "<li>Focal length: ".eval($photo->{exif}{$exifItemName})." mm (35mm equivalent: ".eval($photo->{exif}{$exifItemName})*1.6."mm)</li>\n";
      } elsif ($exifItemName eq 'exposurebias') {
    print PHOTOFILE "<li>Exposure bias: ".eval($photo->{exif}{$exifItemName})."EV</li>\n";
      }
    }
    print PHOTOFILE "</ul>\n";
  }
  print PHOTOFILE "</div>\n";
  close PHOTOFILE;
  $index++;
}
print INDEX "</ul>\n";
close INDEX;



# print STDERR Dumper(\@photos) if $DEBUG;

print STDERR "Got ".scalar(@photos)." photos\n" if $DEBUG;





sub testError {
  my $response = shift;

  die "Error getting data from flickr: got error ".
    $response->{error_code}.
    " (".
    $response->{error_message}
    .")" if (!$response->{success});
}





sub photoFlickrURL {
  my $attributes = shift;
  my $size = shift || 'o';

  # Check out http://www.flickr.com/services/api/misc.urls.html for
  # the URL translation algorithm. At this time it takes this form:
  # http://photos{server-id}.flickr.com/{id}_{secret}_[mstb].jpg
  # 
  die "Error getting photo attributes" unless $attributes;
  my $photo_id;
  if (defined($attributes->{id})) {
    $photo_id .= $attributes->{id};
  } else {
    die "Didn't get a photo ID at all!";
  }

  my $server_id;
  if (defined($attributes->{server})) {
    $server_id .= $attributes->{server};
  } else {
    die "Didn't get the server_id from the photo with ID $photo_id";
  } 

  my $secret;
  if (defined($attributes->{secret})) {
    $secret .= $attributes->{secret};
  } else {
    die "Didn't get the secret from the photo with ID $photo_id";
  } 

  my $url = "http://photos$server_id.flickr.com/$photo_id\_$secret\_$size.jpg";
  return $url;
}





sub photoLocalURL {
  my $attributes = shift;

  die "Error getting photo attributes" unless $attributes;

  my $photo_id;
  if (defined($attributes->{id})) {
    $photo_id .= $attributes->{id};
  } else {
    die "Didn't get a photo ID at all!";
  }

  my $photo_title;
  if (defined($attributes->{title})) {
    $photo_title .= $attributes->{title};
  }

  my $url = "photo.html?phototitle=$photo_title&photoid=$photo_id";
  return $url;
}





sub readInfo {
  my $infoData = shift;

  die "Didn't get the photo info!" unless $infoData;

  my $photo_info;
  foreach my $photo_data_item (@{$infoData->{tree}{children}[1]{children}}) {
    my $a = $photo_data_item->{name};
    if ((defined $a) and ($a)) {
      if ($a eq 'description') {
    $photo_info->{description} = $photo_data_item->{children}[0]{content};
      }
      if ($a eq 'visibility') {
    $photo_info->{public} = $photo_data_item->{attributes}{ispublic};
      }
      if ($a eq 'dates') {
    $photo_info->{date_taken} = $photo_data_item->{attributes}{taken};
    $photo_info->{date_posted} = $photo_data_item->{attributes}{posted};
      }

    }
  }

  return $photo_info;
}





sub readExif {
  my $exifData = shift;

  die "Didn't get the EXIF data!" unless $exifData;

  my $photo_exif;
  foreach my $photo_exif_item (@{$exifData->{tree}{children}[1]{children}}) {
    my $a = $photo_exif_item->{name};
    if ((defined $a) and ($a eq 'exif')) {
      my $label = $photo_exif_item->{attributes}{label};
      my $content = $photo_exif_item->{children}[1]{children}[0]{content};
      if ($label eq 'Model') {
    $photo_exif->{cameramodel} = $content;
      } elsif ($label eq 'ISO Speed') {
    $photo_exif->{ISOspeed} = $content;
      } elsif ($label eq 'Shutter Speed') {
    $photo_exif->{shutterspeed} = $content;
      } elsif ($label eq 'Aperture') {
    $photo_exif->{aperture} = $content;
      } elsif ($label eq 'Exposure Bias') {
    $photo_exif->{exposurebias} = $content;
      } elsif ($label eq 'Focal Length') {
    $photo_exif->{focallength} = $content;
      } 
    }
  }

  return $photo_exif;
}

The main index file

This file is called ispy.html and it is the main entrance page.
Mostly it just includes the main page generated by the script.

And a quick note: Yes, I do use PHP for this. It is just so right for this task… ;-)

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>iSpy</title>
<link rel="stylesheet" href="style.css" type="text/css" />
</head>
<body>
<? include "photolistbody.html" ?>
</body>
</html>

The individual photo page

This is the file which includes each individual photo (whose content is generated by the script).

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<?
  $phototitle = $_GET[phototitle];
  $photoid = $_GET[photoid];
?>

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>iSpy - <? print $phototitle ?></title>
<link rel="stylesheet" href="style.css" type="text/css" />
</head>
<body id="photoDisplay">
<? include "photos/$photoid.html" ?>
</body>
</html>

The Style-sheet

This is the style.css file. I’m no CSS wizard, so this is essentially a test of my own.

#thumbList {
  list-style-type: none;
  width: 100px;
  /*margin: 0 auto 0 auto;*/
}

#thumbList li {
  margin-top: 20px;
}

#thumbList li a {
  text-decoration: none;
}

#thumbList li a:hover {
  text-decoration: underline;
  color: #ab5518;
}

.thumbNail {
  display: block;
  text-align: center;
}

.thumbNail img {
  border: none;
}

.thumbNailTitle {
  font-family: verdana, arial; 
  font-size: 80%;
  display: block;
  text-align: center;
}

#photoDisplay {
  width: 800px;
  margin: 0 auto 0 auto;
  font-family: verdana, arial;
  color: #999999;
  background-color: #333333;
}

#photoDisplay h1 {
  text-align: center;
}

.photoNav {
  list-style-type: none;
  text-align: center;
  margin: 20px auto 0 auto;
  /*border: 1px solid red;*/
}

.photoNav li {
  display: inline;
  /*border: 1px solid yellow;*/
}

.photoNav li a {
  text-decoration: none;
  color: #999999;
}

.photoNav li a:hover {
  text-decoration: underline;
  color: #ab5518;
}

#photoBig {
  margin: 0 auto 0 auto;
  text-align: center;
}

#photoBig img {
  border: 35px solid #222222;
}

#photoDescription {
  width: 600px;
  margin: 20px auto 0 auto;
  padding: 15px;
  border: 2px solid #222222;
  background-color: #2f2f2f;
  font-family: verdana, arial;
  text-align: left;
  font-size: 100%;
}

#photoData {
  width: 600px;
  margin: 20px auto 50px auto;
  padding: 5px;
  border: 2px solid #222222;
  background-color: #2f2f2f;
  text-align: left;
  font-size: 75%;
  font-family: verdana, arial;
}

#EXIFData {
  list-style-type: none;
}

About this entry

Originally written on May 06, 2005 @ 02:08
Read article on it's own page (permalink)

Feed Joiner

What is it?

Feed Joiner is a script that joins several RSS or ATOM feeds into a single ATOM feed.

Introduction and motivation

I’ve had the need to take all the feeds that I create from the various instances of my on-line presence and clump it together into a master feed for some time now.

Well, one day I just decided to get to work and just do it.

This is the result. So far it is very experimental and it is kind of ugly.

Whishlist and To-Dos

This is what I hope to improve in the near future:

  • Make it auto-detect whether a feed is ATOM or some kind of RSS and treat it accordingly;
  • Sort all the feed items on the master feed (the date on each individual item is good enough for a good reader to sort it, but it would be more elegant to pre-sort them in the feed itself);
  • Handle failings in some sort of sensible way (which I still have to figure out what it actually means: do I drop the items from a source feed that has a problem? Do I use the previous version of the source feed (I’d have to cache it somewhere for that)? Do I just fail to create the master feed?);
  • Make it easier to configure. Especially take out all those hammered in strings and options;
  • Make it all around more robust.

The source code

Anyway, here is the current source code for your perusal (and no, I never claimed it was pretty, but it sure gets the job done!):

#!/usr/bin/perl -w

use strict;

use LWP::Simple;
use XML::RAI;
use XML::Atom::Syndication;
use XML::Atom::SimpleFeed;

my %urls = (
  'http://nowhereland.nunonunes.org/atom.xml' => {
    type => 'atom',
    name => 'Nowhereland',
  },
  'http://www.flickr.com/services/feeds/photos_public.gne?id=92591068@N00&tags=nunonunesispy&format=atom_03' => {
    type => 'atom',
    name => 'iSpy',
  },
  'http://del.icio.us/rss/nunonunes' => {
    type => 'rss',
    name => 'Links',
  },
);

my @newsitems;

# Let's get all the data from all the sourcess
print STDERR "Starting the master feed refresh...\n";

foreach my $url (keys %urls) {

  my $content = get($url);
  my $type = $urls{$url}{type};
  my $feed_title = $urls{$url}{name};

  die "Error getting the feed from $feed_title: $!" unless $content;

  if ($type eq 'atom') {
    my $atomic = XML::Atom::Syndication->instance;

    my $doc = $atomic->parse($content);
    foreach ($doc->query('//entry')) {
    my $newsitem = undef;

    $newsitem->{title} = "[$feed_title]";
    $newsitem->{title} .= " ".$_->query('title')->text_value
      if $_->query('title');

    $newsitem->{content} =  $newsitem->{content}->text_value
      if $newsitem->{content};

    $newsitem->{link} = $_->query('link/@href')
      if $_->query('link/@href');

    $newsitem->{modified} = $_->query('modified')->text_value
      if $_->query('modified');

    $newsitem->{summary} = $_->query('summary')->text_value
      if $_->query('summary');

    $newsitem->{content} = $_->query('content')->text_value
      if $_->query('content');

    $newsitem->{issued} = $_->query('issued')->text_value
      if $_->query('issued');

    $newsitem->{id} = $_->query('id')->text_value
      if $_->query('id');

    $newsitem->{created} = $_->query('created')->text_value
      if $_->query('created');

    push @newsitems, $newsitem;
    }
  }
  elsif ($type eq 'rss') {
    my $rai = XML::RAI->parse($content);

    foreach my $item ( @{$rai->items} ) {
      my $newsitem = undef;

      $newsitem->{title} =  "[$feed_title] ".$item->title;

      $newsitem->{link} = $item->link if $item->link;

      $newsitem->{content} = $item->content if $item->content;

      $newsitem->{issued} = $item->issued if $item->issued;

      push @newsitems, $newsitem;
    }

  }

}

# Now let's build the aggregate feed...

my $atomfeed = XML::Atom::SimpleFeed->new(
  title => "Planet Nuno Nunes",
  link => "http://nunonunes.org/",
  tagline => "All things regarding Nuno Nunes - A collection of all the relevant feeds.",
  author => {
    name => "Nuno Nunes",
    url => "http://nunonunes.org/",
  }
)
or die "Error creating the aggregate feed: $!";

foreach (@newsitems) {
  $atomfeed->add_entry(
    %$_,
    author => {
      name => "Nuno Nunes",
      url => "http://nunonunes.org/",
    }
  )
  or die "Error adding item (\"".$_->{title}."\") to aggregate feed: $!";
}

$atomfeed->print;

print STDERR "Done refreshing the master feed.\n";

1;

About this entry

Originally written on May 03, 2005 @ 13:50
Read article on it's own page (permalink)

The content of this site is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 License.