]> git.somenet.org - irc/bugbot.git/blob - BotModules/RDF.bm
GITOLITE.txt
[irc/bugbot.git] / BotModules / RDF.bm
1 ################################
2 # RDF Module                   #
3 ################################
4 # this is really an RSS module, not an RDF module.
5 # but oh well.
6
7 package BotModules::RDF;
8 use XML::RSS;
9 use vars qw(@ISA);
10 @ISA = qw(BotModules);
11 1;
12
13 # RegisterConfig - Called when initialised, should call registerVariables
14 sub RegisterConfig {
15     my $self = shift;
16     $self->SUPER::RegisterConfig(@_);
17     $self->registerVariables(
18       # [ name, save?, settable? ]
19         ['sites', 1, 1, {}], 
20         ['updateDelay', 1, 1, 600],
21         ['preferredLineLength', 1, 1, 80],
22         ['maxInChannel', 1, 1, 5],
23         ['maxLines', 1, 1, 20],
24         ['trimTitles', 1, 1, '0'],
25         ['data', 0, 0, {}],  # data -> uri -> (title, link, last, items -> uri)
26         ['mutes', 1, 1, {}],  # uri -> "channel channel channel"
27     );
28 }
29
30 # Schedule - called when bot connects to a server, to install any schedulers
31 # use $self->schedule($event, $delay, $times, $data)
32 # where $times is 1 for a single event, -1 for recurring events,
33 # and a +ve number for an event that occurs that many times.
34 sub Schedule {
35     my $self = shift;
36     my ($event) = @_;
37     $self->schedule($event, \$self->{'updateDelay'}, -1, 'rdf');
38     $self->SUPER::Schedule($event);
39 }
40
41 sub Help {
42     my $self = shift;
43     my ($event) = @_;
44     my %commands; 
45     if ($self->isAdmin($event)) {
46         $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'";
47         $commands{'mute'} = 'Disable reporting of a site in a channel. (Only does something if the given site exists.) Syntax: mute <site> in <channel>';
48         $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 <site> in <channel>';
49     } else {
50         $commands{''} = 'The RDF module monitors various websites.';
51     }
52     foreach my $site (keys(%{$self->{'sites'}})) {
53         if ($self->{'data'}->{$self->{'sites'}->{$site}}) {
54             $commands{$site} = "Reports the headlines listed in $self->{'data'}->{$self->{'sites'}->{$site}}->{'title'}";
55
56             # -- #mozilla was here --
57             #      <Hixie> anyway, $self->{'data'}->{$self->{'sites'}->{$site}}->{'title'} is 
58             #              another nice piece of perl (embedded in a quoted string in this case)
59             #     <moogle> yeah, that's a bit more familiar
60             #        <jag> Oooh, nice one
61             #        <jag> Reminds me of Java, a bit :-)
62             #        <jag> Without all the casting about from Object to Hashtable
63             #      <Hixie> all this, BTW, is from the RDF module (the one that mozbot uses to 
64             #              report changes in mozillazine and so on)
65             #     <moogle> I still tend to comment these things a bit just for maintainability 
66             #              by others who might not wish to do mental gymnastics :)
67             #      <Hixie> :-) 
68
69         } else {
70             $commands{$site} = "Reports the headlines listed in $self->{'sites'}->{$site}";
71         }
72
73     }
74     return \%commands;
75 }
76
77 sub Told {
78     my $self = shift;
79     my ($event, $message) = @_;
80     foreach my $site (keys(%{$self->{'sites'}})) {
81         if ($message =~ /^\s*(\Q$site\E)\s*$/si) {
82             $self->GetSite($event, $1, 'request');
83             return 0; # dealt with it... 
84         }
85     }
86     if ($self->isAdmin($event)) {
87         if ($message =~ /^\s*mute\s+(\S+?)\s+in\s+(\S+?)\s*$/osi) {
88             my $site = $1 eq 'RDF' ? '' : $self->{'sites'}->{$1};
89             my $siteName = $site eq '' ? 'all sites' : $site;
90             if (defined($site)) {
91                 $self->{'mutes'}->{$site} .= " $2";
92                 $self->saveConfig();
93                 $self->say($event, "$event->{'from'}: RDF notifications for $siteName muted in channel $2.");
94             } else {
95                 # can't say this, other modules might recognise it: $self->say($event, "$event->{'from'}: I don't know about any '$1' site...");
96             }
97         } elsif ($message =~ /^\s*unmute\s+(\S+?)\s+in\s+(\S+?)\s*$/osi) {
98             my $site = $1 eq 'RDF' ? '' : $self->{'sites'}->{$1};
99             my $siteName = $site eq '' ? 'all sites' : $site;
100             if (defined($site)) {
101                 my %mutedChannels = map { lc($_) => 1 } split(/ /o, $self->{'mutes'}->{$site});
102                 delete($mutedChannels{lc($2)}); # get rid of any mentions of that channel
103                 $self->{'mutes'}->{$site} = join(' ', keys(%mutedChannels));
104                 $self->saveConfig();
105                 $self->say($event, "$event->{'from'}: RDF notifications for $siteName resumed in channel $2.");
106             } else {
107                 # can't say this, other modules might recognise it: $self->say($event, "$event->{'from'}: I don't know about any '$1' site...");
108             }
109         } else {
110             return $self->SUPER::Told(@_);
111         }
112     } else {
113         return $self->SUPER::Told(@_);
114     }
115     return 0;
116 }
117
118 sub GetSite {
119     my $self = shift;
120     my ($event, $site, $intent) = @_;
121     if (defined($self->{'sites'}->{$site})) {
122         my $uri = $self->{'sites'}->{$site};
123         $self->getURI($event, $uri, $intent);
124     } else {
125         # XXX
126     }
127 }
128
129 sub GotURI {
130     my $self = shift;
131     my ($event, $uri, $output, $intent) = @_;
132
133     $self->{'data'}->{$uri}->{'ready'} = defined($self->{'data'}->{$uri});
134
135     if ($output) {
136
137         # last update stamp
138         my $last = $event->{'time'};
139         $self->{'data'}->{$uri}->{'last'} = $last;
140
141         # Parse It
142         my $rss = XML::RSS->new();
143         eval { $rss->parse($output) };
144         if ($@) {
145             $self->debug("$uri is not a valid RSS file");
146             if ($intent eq 'request') {
147                 $self->say($event, "$event->{'from'}: Dude, the file is not valid RSS! ($uri)");
148             }
149             return;
150         }
151
152         # Set Link and Title
153         $self->{data}->{$uri}->{'link'} = $rss->{'channel'}->{'link'};
154         $self->{data}->{$uri}->{'title'} = $rss->{'channel'}->{'title'};
155
156         foreach my $item (@{$rss->{'items'}}) {
157             unless (($item->{title} =~ /^last update/osi) || 
158                     (defined($self->{'data'}->{$uri}->{'items'}->{$item->{'title'}}))) {
159                 $self->{'data'}->{$uri}->{'items'}->{$item->{'title'}} = $last;
160             }
161         }
162
163         $self->ReportDiffs($event, $uri, $intent);
164         if ($intent eq 'request') {
165             $self->ReportAll($event, $uri);
166         }
167
168     } else {
169
170         if ($intent eq 'request') {
171             $self->say($event, "$event->{'from'}: Dude, the file was empty! ($uri)");
172         }
173
174     }
175
176 }
177
178 sub Scheduled {
179     my $self = shift;
180     my ($event, @data) = @_;
181     if ($data[0] eq 'rdf') {
182         my %sites = map { $_ => 1 } values(%{$self->{'sites'}});
183         foreach (keys(%sites)) {
184             $self->getURI($event, $_, 'update');
185         }
186     } else {
187         $self->SUPER::Scheduled($event, @data);
188     }
189 }
190
191 sub ReportDiffs {
192     my $self = shift;
193     my ($event, $uri, $request) = @_;
194     return unless $self->{'data'}->{$uri}->{'ready'};
195     my $last = $self->{'data'}->{$uri}->{'last'};
196     my @output;
197     foreach (keys(%{$self->{'data'}->{$uri}->{'items'}})) {
198         push(@output, $_) if ($self->{'data'}->{$uri}->{'items'}->{$_} == $last);
199     }
200
201     # -- #mrt was here --
202     #     <mozbot> Friday's security advisories -- The first stable
203     #              Xen release -- Linux Gazette #95
204     #     <mozbot> KDE Under The Microscope -- Additional OpenSSL info
205     #      <Hixie> wtf
206     #     <mozbot> Just appeared in jbisbee.com -
207     #              http://www.jbisbee.com/ : PoCo::RSS::Aggregator
208     #      <Hixie> why is it repeating the same thing over and over
209     #     <mozbot> PoCo::RSSAggregator & XML::RSS::Feed Uploaded to
210     #              CPAN -- More PoCo::RSSAggregator
211     #      <Hixie> mozbot: shutup please
212     #     <mozbot> Ok, threw away 2558 messages.
213
214     # Ahem. So now we limit the diff reporting code to maxInChannel
215     # items at a time...
216
217     if (@output and @output < $self->{'maxInChannel'}) {
218         my %mutedChannels = ();
219         if (defined($self->{'mutes'}->{$uri})) {
220             %mutedChannels = map { lc($_) => 1 } split(/\s+/os, $self->{'mutes'}->{$uri});
221         }
222         if (defined($self->{'mutes'}->{''})) {
223             %mutedChannels = (%mutedChannels, map { lc($_) => 1 } split(/\s+/os, $self->{'mutes'}->{''}));
224         }
225         if ($request eq 'request') {
226             $mutedChannels{$event->{'channel'}} = 1;
227         }
228         foreach (@{$self->{'channels'}}) {
229             unless ($mutedChannels{$_}) {
230                 local $event->{'target'} = $_;
231                 $self->say($event, "Just appeared in $self->{'data'}->{$uri}->{'title'} - $self->{'data'}->{$uri}->{'link'} :");
232                 foreach (@output) {
233                     $self->say($event, "   " . $_);
234                 }
235             }
236         }
237     }
238 }
239
240 sub ReportAll {
241     my $self = shift;
242     my ($event, $uri) = @_;
243     my @output;
244     foreach (keys(%{$self->{'data'}->{$uri}->{'items'}})) {
245         push(@output, $_);
246     }
247
248     @output = $self->prettyPrint($self->{'preferredLineLength'}, 
249                                  "Items in $self->{'data'}->{$uri}->{'title'} - $self->{'data'}->{$uri}->{'link'}: ",
250                                  "$event->{'from'}: ", ' -- ', @output);
251
252     if (@output > $self->{'maxLines'}) {
253         splice(@output, $self->{'maxLines'} + 1);
254         unshift(@output, "The list is longer than $self->{'maxLines'}"
255             . " lines, only the first $self->{'maxLines'} will be shown.");
256     }
257
258     if (@output > $self->{'maxInChannel'}) {
259         foreach (@output) {
260             $self->directSay($event, $_);
261         }
262         $self->channelSay($event, "$event->{'from'}: /msg'ed");
263     } else {
264         foreach (@output) {
265             $self->say($event, $_);
266         }
267     }
268 }