################################ # RDF Module # ################################ # this is really an RSS module, not an RDF module. # but oh well. package BotModules::RDF; use XML::RSS; use vars qw(@ISA); @ISA = qw(BotModules); 1; # RegisterConfig - Called when initialised, should call registerVariables sub RegisterConfig { my $self = shift; $self->SUPER::RegisterConfig(@_); $self->registerVariables( # [ name, save?, settable? ] ['sites', 1, 1, {}], ['updateDelay', 1, 1, 600], ['preferredLineLength', 1, 1, 80], ['maxInChannel', 1, 1, 5], ['maxLines', 1, 1, 20], ['trimTitles', 1, 1, '0'], ['data', 0, 0, {}], # data -> uri -> (title, link, last, items -> uri) ['mutes', 1, 1, {}], # uri -> "channel channel channel" ); } # Schedule - called when bot connects to a server, to install any schedulers # use $self->schedule($event, $delay, $times, $data) # where $times is 1 for a single event, -1 for recurring events, # and a +ve number for an event that occurs that many times. sub Schedule { my $self = shift; my ($event) = @_; $self->schedule($event, \$self->{'updateDelay'}, -1, 'rdf'); $self->SUPER::Schedule($event); } sub Help { my $self = shift; my ($event) = @_; my %commands; if ($self->isAdmin($event)) { $commands{''} = "The RDF module monitors various websites. Add new RDF channels to the 'sites' hash. Duplicates with different nicknames are fine. For example, \"vars $self->{'_name'} sites '+|slashdot|http://...'\" and \"vars $self->{'_name'} sites '+|/.|http://...'\" is fine. To remove a site from the RDF 'sites' hash, use this syntax \"vars $self->{_name} sites '-slashdot'"; $commands{'mute'} = 'Disable reporting of a site in a channel. (Only does something if the given site exists.) Syntax: mute in '; $commands{'unmute'} = 'Enable reporting of a site in a channel. By default, sites are reported in all channels that the module is active in. Syntax: unmute in '; } else { $commands{''} = 'The RDF module monitors various websites.'; } foreach my $site (keys(%{$self->{'sites'}})) { if ($self->{'data'}->{$self->{'sites'}->{$site}}) { $commands{$site} = "Reports the headlines listed in $self->{'data'}->{$self->{'sites'}->{$site}}->{'title'}"; # -- #mozilla was here -- # anyway, $self->{'data'}->{$self->{'sites'}->{$site}}->{'title'} is # another nice piece of perl (embedded in a quoted string in this case) # yeah, that's a bit more familiar # Oooh, nice one # Reminds me of Java, a bit :-) # Without all the casting about from Object to Hashtable # all this, BTW, is from the RDF module (the one that mozbot uses to # report changes in mozillazine and so on) # I still tend to comment these things a bit just for maintainability # by others who might not wish to do mental gymnastics :) # :-) } else { $commands{$site} = "Reports the headlines listed in $self->{'sites'}->{$site}"; } } return \%commands; } sub Told { my $self = shift; my ($event, $message) = @_; foreach my $site (keys(%{$self->{'sites'}})) { if ($message =~ /^\s*(\Q$site\E)\s*$/si) { $self->GetSite($event, $1, 'request'); return 0; # dealt with it... } } if ($self->isAdmin($event)) { if ($message =~ /^\s*mute\s+(\S+?)\s+in\s+(\S+?)\s*$/osi) { my $site = $1 eq 'RDF' ? '' : $self->{'sites'}->{$1}; my $siteName = $site eq '' ? 'all sites' : $site; if (defined($site)) { $self->{'mutes'}->{$site} .= " $2"; $self->saveConfig(); $self->say($event, "$event->{'from'}: RDF notifications for $siteName muted in channel $2."); } else { # can't say this, other modules might recognise it: $self->say($event, "$event->{'from'}: I don't know about any '$1' site..."); } } elsif ($message =~ /^\s*unmute\s+(\S+?)\s+in\s+(\S+?)\s*$/osi) { my $site = $1 eq 'RDF' ? '' : $self->{'sites'}->{$1}; my $siteName = $site eq '' ? 'all sites' : $site; if (defined($site)) { my %mutedChannels = map { lc($_) => 1 } split(/ /o, $self->{'mutes'}->{$site}); delete($mutedChannels{lc($2)}); # get rid of any mentions of that channel $self->{'mutes'}->{$site} = join(' ', keys(%mutedChannels)); $self->saveConfig(); $self->say($event, "$event->{'from'}: RDF notifications for $siteName resumed in channel $2."); } else { # can't say this, other modules might recognise it: $self->say($event, "$event->{'from'}: I don't know about any '$1' site..."); } } else { return $self->SUPER::Told(@_); } } else { return $self->SUPER::Told(@_); } return 0; } sub GetSite { my $self = shift; my ($event, $site, $intent) = @_; if (defined($self->{'sites'}->{$site})) { my $uri = $self->{'sites'}->{$site}; $self->getURI($event, $uri, $intent); } else { # XXX } } sub GotURI { my $self = shift; my ($event, $uri, $output, $intent) = @_; $self->{'data'}->{$uri}->{'ready'} = defined($self->{'data'}->{$uri}); if ($output) { # last update stamp my $last = $event->{'time'}; $self->{'data'}->{$uri}->{'last'} = $last; # Parse It my $rss = XML::RSS->new(); eval { $rss->parse($output) }; if ($@) { $self->debug("$uri is not a valid RSS file"); if ($intent eq 'request') { $self->say($event, "$event->{'from'}: Dude, the file is not valid RSS! ($uri)"); } return; } # Set Link and Title $self->{data}->{$uri}->{'link'} = $rss->{'channel'}->{'link'}; $self->{data}->{$uri}->{'title'} = $rss->{'channel'}->{'title'}; foreach my $item (@{$rss->{'items'}}) { unless (($item->{title} =~ /^last update/osi) || (defined($self->{'data'}->{$uri}->{'items'}->{$item->{'title'}}))) { $self->{'data'}->{$uri}->{'items'}->{$item->{'title'}} = $last; } } $self->ReportDiffs($event, $uri, $intent); if ($intent eq 'request') { $self->ReportAll($event, $uri); } } else { if ($intent eq 'request') { $self->say($event, "$event->{'from'}: Dude, the file was empty! ($uri)"); } } } sub Scheduled { my $self = shift; my ($event, @data) = @_; if ($data[0] eq 'rdf') { my %sites = map { $_ => 1 } values(%{$self->{'sites'}}); foreach (keys(%sites)) { $self->getURI($event, $_, 'update'); } } else { $self->SUPER::Scheduled($event, @data); } } sub ReportDiffs { my $self = shift; my ($event, $uri, $request) = @_; return unless $self->{'data'}->{$uri}->{'ready'}; my $last = $self->{'data'}->{$uri}->{'last'}; my @output; foreach (keys(%{$self->{'data'}->{$uri}->{'items'}})) { push(@output, $_) if ($self->{'data'}->{$uri}->{'items'}->{$_} == $last); } # -- #mrt was here -- # Friday's security advisories -- The first stable # Xen release -- Linux Gazette #95 # KDE Under The Microscope -- Additional OpenSSL info # wtf # Just appeared in jbisbee.com - # http://www.jbisbee.com/ : PoCo::RSS::Aggregator # why is it repeating the same thing over and over # PoCo::RSSAggregator & XML::RSS::Feed Uploaded to # CPAN -- More PoCo::RSSAggregator # mozbot: shutup please # Ok, threw away 2558 messages. # Ahem. So now we limit the diff reporting code to maxInChannel # items at a time... if (@output and @output < $self->{'maxInChannel'}) { my %mutedChannels = (); if (defined($self->{'mutes'}->{$uri})) { %mutedChannels = map { lc($_) => 1 } split(/\s+/os, $self->{'mutes'}->{$uri}); } if (defined($self->{'mutes'}->{''})) { %mutedChannels = (%mutedChannels, map { lc($_) => 1 } split(/\s+/os, $self->{'mutes'}->{''})); } if ($request eq 'request') { $mutedChannels{$event->{'channel'}} = 1; } foreach (@{$self->{'channels'}}) { unless ($mutedChannels{$_}) { local $event->{'target'} = $_; $self->say($event, "Just appeared in $self->{'data'}->{$uri}->{'title'} - $self->{'data'}->{$uri}->{'link'} :"); foreach (@output) { $self->say($event, " " . $_); } } } } } sub ReportAll { my $self = shift; my ($event, $uri) = @_; my @output; foreach (keys(%{$self->{'data'}->{$uri}->{'items'}})) { push(@output, $_); } @output = $self->prettyPrint($self->{'preferredLineLength'}, "Items in $self->{'data'}->{$uri}->{'title'} - $self->{'data'}->{$uri}->{'link'}: ", "$event->{'from'}: ", ' -- ', @output); if (@output > $self->{'maxLines'}) { splice(@output, $self->{'maxLines'} + 1); unshift(@output, "The list is longer than $self->{'maxLines'}" . " lines, only the first $self->{'maxLines'} will be shown."); } if (@output > $self->{'maxInChannel'}) { foreach (@output) { $self->directSay($event, $_); } $self->channelSay($event, "$event->{'from'}: /msg'ed"); } else { foreach (@output) { $self->say($event, $_); } } }