#!/usr/bin/perl -w
###########################################
# pmwatcher - Gaim plugin to watch 
#             perlmonks.com
###########################################
use strict;
use Gaim;
use HTML::TreeBuilder;
use URI::URL;
use CGI qw(a);
use Cache::FileCache;
use POE qw(Component::Client::HTTP);
use HTTP::Request::Common;

our $FETCH_INTERVAL = 600;
our $FETCH_URL = "http://perlmonks.com/" .
                 "?node=Newest%20Nodes";

our $ACTIVE = 0;
    # Call plugins every second
our $UPDATE = 1;
our $USER   = "mikeschilli";
our $BUDDY  = undef;
our $PLUGIN = undef;

our %PLUGIN_INFO = (
  perl_api_version => 2,
  name        => "pmwatcher",
  summary     => "Perlmonks Watch Plugin",
  version     => "1.0",
  description => "Reports latest postings "
                 . "on perlmonks.com",
  author      => "Mike Schilli " .
                 "<m\@perlmeister.com>",
  load        => "plugin_load",
);

our $cache = new Cache::FileCache({
    namespace  => "pmwatcher",
});

###########################################
sub plugin_init {
###########################################
  return %PLUGIN_INFO;
}

###########################################
sub plugin_load {
###########################################
  my($plugin) = @_;

  Gaim::signal_connect(
    Gaim::BuddyList::handle(),
    "buddy-signed-on", $plugin, 
    \&buddy_signed_on_callback, 
  );

  Gaim::signal_connect(
    Gaim::BuddyList::handle(), 
    "buddy-signed-off", $plugin, 
    \&buddy_signed_off_callback, 
  );

  POE::Component::Client::HTTP->spawn(
      Alias     => "ua",
      Timeout   => 60,
  );

  POE::Session->create(
    inline_states => {
      _start     => sub {
        $poe_kernel->yield('http_start');
      },
      http_start => sub {
        Gaim::debug_info("pmwatcher", 
          "Fetching $FETCH_URL\n");
        $poe_kernel->post("ua", "request",
            "http_ready", GET $FETCH_URL);
        $poe_kernel->delay('http_start',
                          $FETCH_INTERVAL);
      },
      http_ready => sub {
        Gaim::debug_info("pmwatcher", 
          "http_ready $FETCH_URL\n");
        my $resp= $_[ARG1]->[0];
        if($resp->is_success()) {
          pm_update($resp->content());
        } else {
          Gaim::debug_info("pmwatcher", 
            "Can't fetch $FETCH_URL: " . 
            $resp->message());
        }
      },
    }
  );

  Gaim::timeout_add($plugin, $UPDATE, 
                    \&refresh);
  $PLUGIN = $plugin;
}

###########################################
sub buddy_signed_on_callback {
###########################################
  my ($buddy, $data) = @_;

  return if $buddy->get_alias ne $USER;
  $ACTIVE = 1;
  $BUDDY  = $buddy;
}

###########################################
sub buddy_signed_off_callback {
###########################################
  my ($buddy, $data) = @_;

  return if $buddy->get_alias ne $USER;
  $ACTIVE = 0;
  $BUDDY  = undef;
}

###########################################
sub refresh {
###########################################

  Gaim::debug_info("pmwatcher", 
             "Refresh (ACTIVE=$ACTIVE)\n");
  if($ACTIVE) {
      $poe_kernel->run_one_timeslice();
  }

  Gaim::timeout_add($PLUGIN, $UPDATE, 
                    \&refresh);
}

###########################################
sub pm_update {
###########################################
  my($html_text) = @_;

  if(my @nws = latest_news($html_text)) {
      my $c = Gaim::Conversation::IM::new(
                $BUDDY->get_account(), 
                $BUDDY->get_name());

      $c->send("$_\n") for @nws;
  }
}

###########################################
sub latest_news {
###########################################
  my($html_string) = @_;

  my $start_url = 
      URI::URL->new($FETCH_URL);

  my $max_node;
  
  my $saved = $cache->get("max-node");
  $saved = 0 unless defined $saved;

  my @aimtext = ();

  for my $entry (@{qparse($html_string)}) {
      my($text, $url) = @$entry;
  
      my($node) = $url =~ /(\d+)$/;
      if($node > $saved) {
          Gaim::debug_info("pmwatcher",
            "*** New node $text ($url)");
          $url = a({href => $url}, $url);
          push @aimtext, 
               "<b>$text</b>\n$url";
      }
  
      $max_node = $node if 
        !defined $max_node or 
         $max_node < $node;
  }

  $cache->set("max-node", $max_node) 
      if $saved < $max_node;

  return @aimtext;
}

###########################################
sub qparse {
###########################################
  my($html_string) = @_;

  my $start_url = 
    URI::URL->new($FETCH_URL);

  my @questions = ();

  my $parser = HTML::TreeBuilder->new();
  my $tree = $parser->parse($html_string);

  my($questions) = $tree->look_down(
    "_tag", "a",
    "name", "toc-Questions");

  if(! $questions) {
     Gaim::debug_info("pmwatcher", 
     "Couldn't find Questions section");
    return undef;
  }

  my $node = $questions->parent();
  while($node->tag() ne "table") {
    $node = $node->right();
  }

  for my $tr ($node->look_down(
                           "_tag", "tr")) {
    for my $a ($tr->look_down(
                            "_tag", "a")) {
      my $href = $a->attr('href');
      my $text = $a->as_text();
      my $url = URI::URL->new($href, 
                              $start_url);
    
      push @questions, 
           [$text, $url->abs()];
          # Process only the question 
          # node, not the author's node
      last;
    }
  }

  $tree->delete();
  return \@questions;
}
