The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl
my $APP = 'xkcd';
use vars qw($VERSION);
$VERSION = '0.082';

use strict;
use WWW::Mechanize;
use Getopt::Long;
use Pod::Usage;
use HTML::Entities qw(decode_entities);


my $m = WWW::Mechanize->new(
  onwarn  => undef,
  onerror => sub { print "Oops. Something went wrong on the server side :(\n"; },
);

$m->agent_alias('Linux Mozilla');

my $xkcd_base   = 'http://xkcd.com';
my $xkcd_img    = 'http://imgs.xkcd.com/comics';
my $xkcd_url    = undef;

our($opt_comic_no, $opt_alt) = (-1, 0);
our($opt_download, @opt_download_all);
our($opt_random);
our($opt_no_zoom);

GetOptions(
  'c|comic:i'    => \$opt_comic_no,
  'a|alt|text'   => \$opt_alt,
  'l|latest'     => sub { $opt_comic_no = -1; },
  'download=s'   => \$opt_download,
  'download-all:i' => \@opt_download_all, # Support range? 30 .. 42
  'r|random'     => \$opt_random,
  'no-zoom'      => \$opt_no_zoom,

  'h|help'       => sub { pod2usage(verbose => 1); },
  'm|man'        => sub { pod2usage(verbose => 3); },
  'v|version'    => sub { printf("%s v%s\n", $APP, $VERSION) and exit; },
);

$opt_comic_no = $ARGV[0] if $ARGV[0] =~ /^\d+$/;

if($opt_random) {
  $m->get("http://dynamic.xkcd.com/random/comic/");
  ($opt_comic_no) = $m->base =~ m|(\d+)/?$|;
}

$xkcd_url  = "$xkcd_base/$opt_comic_no/";

if(@opt_download_all) {
  my $latest_comic = latest($xkcd_base, 1);

  for(1 .. $latest_comic) {
    download_comic($_, $opt_download);
  }
  exit;
}


if($opt_download) {
  download_comic($opt_comic_no, $opt_download);
  exit;
}

if( ($opt_comic_no == -1) and not($opt_alt)) {
  latest($xkcd_base);
}

if($opt_alt) {
  if($opt_comic_no == -1) {
    $opt_comic_no = latest($xkcd_base, 1);
    $xkcd_url = $xkcd_base;
  }
  my(undef, $txt) = fetch($xkcd_url);
  $txt = _format($txt);
  print "\e[1mxkcd # $opt_comic_no\e[m:\n";
  print "$txt\n";
  exit;
}

display_img( fetch($xkcd_url) );


sub latest {
  my($url, $action) = @_;
  $m->get($url) or die($!);

  for($m->content) {
    if($_ =~ m/Permanent.+: (http:.+)</) {
      my $new_url = $1;
      ($opt_comic_no) = $new_url =~ m|(\d+)/?$|;

      if($action) {
        return $opt_comic_no;
      }
      display_img( fetch( $new_url ));
      exit;
    }
  }
  return;
}

sub _format {
  my $str = shift;
  $str = join(".\n", split(/(?:\.|!|\?) /, $str));
  $str =~ s/^/ / unless $str =~ /^\s+/;
  return $str;
}

sub download_comic {
  my($comic, $dest) = @_;
  defined $dest or $dest = './';

  $dest .= '/' if $dest !~ m;.*/$;;

  my $url;
  if($comic == -1) {
    $url = $xkcd_base;
  }
  elsif($comic !~ /^\d+$/) {
    print STDERR "'$comic' is not an integer!\n" and exit 1;
  }
  else {
    $url = "$xkcd_base/$comic/";
  }

  my ($img, undef) = fetch($url);
  $m->get($img);

  my($filename) = $img =~ m;.+/(.+\.(?:png|jpg))$;i;
  $filename = "$comic-$filename" unless $comic eq -1;
  $dest .= $filename;

  print "xkcd #$comic => $dest\n" if $m->save_content($dest) == 0;
  return;
}


sub fetch {
  my $url = shift;
  $m->get($url);

  my($img_url, $alt) = (undef) x 2;

  for($m->content) {
    if(m;<img src="($xkcd_img/.+\.(?:png|jpg))" title="(.+)" alt;) {
      $img_url = $1;
      $alt     = $2;
      last;
    }
  }
  return($img_url, decode_entities($alt));
}


sub display_img {
  my($url, $text) = @_;

  $text = _format($text);

  $m->get($url);

  if( x() ) {
    system(get_viewer(), $url)  == 0
      and printf("\e[1m%s\e[0m:\n%s\n",
      "xkcd # $opt_comic_no", $text,
    );
    return;
  }

  print "$url\n";
  print "Press any key to continue...\n";

  while(<STDIN>) {
    last;
  }

  printf("%s\n%s\n",
    $opt_comic_no, $text,
  );
}

sub x {
  exists($ENV{DISPLAY}) and return 1;
  return 0;
}

sub get_viewer {
  my $feh = system("which feh &> /dev/null");
  if( ($feh << 8) == 0 ) {
    my @feh = ('feh', qw(-F -Q -x -B black));
    push @feh, '-Z' unless $opt_no_zoom;
    return @feh;
  }
  my $display = system("which display &> /dev/null");
  if( ($display << 8) == 0 ) {
    return('display');
  }

  print STDERR "No viewer available.\n" and exit 1;
}

=pod

=head1 NAME

xkcd - xkcd in your console

=head1 USAGE

xkcd [OPTIONS]

=head1 DESCRIPTION

Command-line interface to xkcd. View, download and enjoy xkcd comics straight
from your shell.

=head1 OPTIONS

  -c,   --comic         comic number
  -l,   --latest        show the latest xkcd comic
  -r,   --random        operate on a random xkcd comic
  -a,   --alt           show the 'mouse over' text
  -d,   --download      download the comic to DESTINATION
        --download-all  download all comics to DESTINATION
        --no-zoom       don't zoom the comic

  -h,   --help      show the help and exit
  -v,   --version   show version info and exit
  -m,   --man       show the documentation and exit

=head1 PREREQUISITES

To view the xkcd comic in X, B<feh> or B<display> (from the ImageMagick suite)
needs to be installed.

=head1 AUTHOR

  Magnus Woldrich
  CPAN ID: WOLDRICH
  magnus@trapd00r.se
  http://japh.se

=head1 REPORTING BUGS

Report bugs on the issue tracker ( L<https://github.com/trapd00r/xkcd/issues> )
or by mail to <magnus@trapd00r.se>

=head1 COPYRIGHT

Copyright (C) 2010, 2011 Magnus Woldrich. All right reserved.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.

=cut