94859d743255b44fccc08b3d80ea8b02eeb527aa
[quotesite.git] / quotesite.pl
1 # autodisplay quotes from quotesites
2 #
3 # (c) 2007-2008 by Ralf Ertzinger <ralf@camperquake.de>
4 # licensed under GNU GPL v2
5
6
7 BEGIN {
8     # Get rid of a (possibly old) version of BettIrssi
9     # This is a hack to prevent having to reload irssi just
10     # because BettIrssi.pm changed
11
12     delete($INC{'BettIrssi.pm'});
13 }
14
15 use strict;
16 use Irssi 20020324 qw (command_bind command_runsub signal_add_first signal_add_last window_find_refnum);
17 use vars qw($VERSION %IRSSI);
18 use XML::Simple;
19 use Data::Dumper;
20 use File::Spec;
21 use BettIrssi 101 qw(_bcb _bcs);
22
23 my @grabbers;
24 my $conf;
25 my $conffile = File::Spec->catfile(Irssi::get_irssi_dir(), 'quotesite.xml');
26 my $plugindir = File::Spec->catfile(Irssi::get_irssi_dir(), 'scripts', 'quotesite');
27 my @outputstack = (undef);
28
29 my $PARAMS = {
30 };
31
32
33 # activate debug here
34 my $debug = 0;
35
36 # "message public", SERVER_REC, char *msg, char *nick, char *address, char *target
37 signal_add_last(_bcs("message public" => sub {check_for_link(@_)}));
38 # "message own_public", SERVER_REC, char *msg, char *target
39 signal_add_last(_bcs("message own_public" => sub {check_for_link(@_)}));
40
41 # "message private", SERVER_REC, char *msg, char *nick, char *address
42 signal_add_last(_bcs("message private" => sub {check_for_link(@_)}));
43 # "message own_private", SERVER_REC, char *msg, char *target, char *orig_target
44 signal_add_last(_bcs("message own_private" => sub {check_for_link(@_)}));
45
46 # "message irc action", SERVER_REC, char *msg, char *nick, char *address, char *target
47 signal_add_last(_bcs("message irc action" => sub {check_for_link(@_)}));
48 # "message irc own_action", SERVER_REC, char *msg, char *target
49 signal_add_last(_bcs("message irc own_action" => sub {check_for_link(@_)}));
50
51 sub push_output {
52     unshift(@outputstack, shift);
53 }
54
55 sub pop_output {
56     shift(@outputstack);
57
58     @outputstack = (undef) unless (@outputstack);
59 }
60
61 sub write_irssi {
62     my @text = @_;
63     my $output = $outputstack[0];
64
65     my $format = "%%mquotesite: %%n" . shift(@text);
66
67     # escape % in parameters for irssi
68     s/%/%%/g foreach @text;
69
70     if (defined($output)) {
71         $output->(sprintf($format, @text), MSGLEVEL_CLIENTCRAP);
72     } else {
73         Irssi::print(sprintf($format, @text));
74     }
75
76 }
77
78 sub write_debug {
79     if ($debug) {
80         write_irssi(@_);
81     }
82 }
83
84 sub check_for_link {
85     my $event = shift;
86     my $message = $event->message();
87     my $channel = $event->channel();
88     my $g;
89     my $m;
90     my $p;
91
92
93     # Look if we should ignore this line
94     if ($message =~ m,(?:\s|^)/nosave(?:\s|$),) {
95         return;
96     }
97
98     push_output($event->ewpf);
99
100     study($message);
101
102     # Offer the message to all Grabbers in turn
103     foreach $g (@grabbers) {
104         ($m, $p) = $g->get($message);
105         while (defined($m)) {
106             write_irssi('%%R>>> %%Y%s%%N %%G%s', $m->{'SOURCE'}, $m->{'ID'});
107
108             foreach (split(/[\n\r]+/, $m->{'CONTENT'})) {
109                 write_irssi('    %%g%s', $_);
110             }
111
112             # Remove the matched part from the message and try again (there may be
113             # more!)
114             $message =~ s/$p//;
115             study($message);
116
117             ($m, $p) = $g->get($message);
118         }
119     }
120
121     pop_output();
122 }
123
124 sub cmd_save {
125
126     eval {
127         open(CONF, '>'.$conffile) or die 'Could not open config file';
128         print CONF XML::Simple::XMLout($conf, KeepRoot => 1, KeyAttr => {'config' => 'module', 'option' => 'key'});
129         close(CONF);
130     };
131     if ($@) {
132         write_irssi('Could not save config to %s: %s', ($conffile, $@));
133     } else {
134         write_irssi('configuration saved to %s', $conffile);
135     }
136 }
137
138 sub cmd_set {
139     my $target = shift;
140     my $key = shift;
141     my $val = shift;
142     my $p;
143
144     foreach $p (@grabbers) {
145         if ($p->{'NAME'} eq $target) {
146             $p->setval($key, $val);
147             return;
148         }
149     }
150     write_irssi('No such module');
151 }
152
153
154 sub cmd_enable {
155     my $target = shift;
156     my $p;
157
158     foreach $p (@grabbers) {
159         if ($p->{'NAME'} eq $target) {
160             $p->enable();
161             return;
162         }
163     }
164     write_irssi('No such module');
165 }
166
167
168 sub cmd_disable {
169     my $target = shift;
170     my $p;
171
172     foreach $p (@grabbers) {
173         if ($p->{'NAME'} eq $target) {
174             $p->disable();
175             return;
176         }
177     }
178     write_irssi('No such module');
179 }
180
181
182 sub cmd_show {
183     my $target = shift;
184     my $p;
185     my $e;
186
187     if (defined($target)) {
188         foreach $p (@grabbers) {
189             if ($p->{'NAME'} eq $target) {
190                 write_irssi($p->getconfstr());
191                 return;
192             }
193         }
194         write_irssi('No such module');
195     } else {
196         write_irssi('Loaded grabbers (* denotes enabled modules):');
197         foreach $p (@grabbers) {
198             $e = $p->_getval('enabled');
199             write_irssi(' %s%s', $p->{'NAME'}, $e?'*':'');
200         };
201     }
202 }
203
204 sub cmd_help {
205     my $target = shift;
206     my $p;
207
208     if (defined($target)) {
209         foreach $p (@grabbers) {
210             if ($p->{'NAME'} eq $target) {
211                 write_irssi($p->gethelpstr());
212                 return;
213             }
214         }
215         write_irssi('No such module');
216     } else {
217         write_irssi(<<'EOT');
218 Supported commands:
219  save: Save the current configuration
220  help [modulename]: Display this help, or module specific help
221  show [modulename]: Show loaded modules, or the current parameters of a module
222  set modulename parameter value: set a module parameter to a new value
223  enable [modulename]: enable the usage of this module (grabbers only)
224  disable [modulename]: disable the usage of this module (grabbers only)
225  reload: reload all modules (this is somewhat experimental)
226  debug: enable debugging messages
227  nodebug: disable debugging messages
228 EOT
229     }
230 }
231
232 # save on unload
233 sub sig_command_script_unload {
234     my $script = shift;
235     if ($script =~ /(.*\/)?quotesite(\.pl)?$/) {
236         cmd_save();
237     }
238 }
239
240 sub ploader {
241
242     my $dir = shift;
243     my $pattern = shift;
244     my $type = shift;
245     my @list;
246     my $p;
247     my $g;
248     my @g = ();
249
250     opendir(D, $dir) || return ();
251     @list = grep {/$pattern/ && -f File::Spec->catfile($dir, $_) } readdir(D);
252     closedir(D);
253
254     foreach $p (@list) {
255         write_debug("Trying to load $p:");
256         $p =~ s/\.pm$//;
257         eval qq{ require quotesite::$p; };
258         if ($@) {
259             write_debug("Failed to load plugin: $@");
260             next;
261         }
262
263         $g = eval qq{ quotesite::$p->new(); };
264         if ($@) {
265             write_debug("Failed to instanciate: $@");
266             delete($INC{$p});
267             next;
268         }
269
270         write_debug("found $g->{'TYPE'} $g->{'NAME'}");
271         if ($type eq $g->{'TYPE'}) {
272             push(@g, $g);
273             $g->setio(\&write_irssi);
274         } else {
275             write_irssi('%s has wrong type (got %s, expected %s)', $p, $g->{'TYPE'}, $type);
276             delete($INC{$p});
277         }
278     }
279
280     write_debug("Loaded %d plugins", $#g+1);
281     
282     return @g;
283 }
284
285 sub _load_modules($) {
286
287     my $path = shift;
288
289     foreach (keys(%INC)) {
290         if ($INC{$_} =~ m|^$path|) {
291             write_debug("Removing %s from \$INC", $_);
292             delete($INC{$_});
293         }
294     }
295     @grabbers = ploader($path, '.*Grabber\.pm$', 'grabber');
296 }
297
298
299 sub init_quotesite {
300
301     my $bindings = shift;
302     my $p;
303
304     unless(-r $conffile && defined($conf = XML::Simple::XMLin($conffile, ForceArray => ['config', 'option'], KeepRoot => 1, KeyAttr => {'config' => 'module', 'option' => 'key'}))) {
305         # No config, start with an empty one
306         write_debug('No config found, using defaults');
307         $conf = { 'quotesite' => { }};
308     }
309     foreach (keys(%{$PARAMS})) {
310         unless (exists($conf->{'quotesite'}->{$_})) {
311             $conf->{'quotesite'}->{$_} = $PARAMS->{$_};
312         }
313     }
314
315     _load_modules($plugindir);
316
317     unless (defined(@grabbers)) {
318         write_irssi('No grabbers found, can not proceed.');
319         return;
320     }
321
322
323     # Loop through all plugins and load the config
324     foreach $p (@grabbers) {
325         $conf->{'quotesite'}->{'config'}->{$p->{'NAME'}} = $p->mergeconfig($conf->{'quotesite'}->{'config'}->{$p->{'NAME'}});
326     }
327
328     if ($bindings) {
329
330         Irssi::signal_add_first('command script load', 'sig_command_script_unload');
331         Irssi::signal_add_first('command script unload', 'sig_command_script_unload');
332         Irssi::signal_add('setup saved', 'cmd_save');
333
334
335         Irssi::command_bind(_bcb('quotesite' => \&cmdhandler));
336     }
337
338     write_irssi('quotesite initialized');
339 }
340
341 sub cmdhandler {
342     my $event = shift;
343     my @params = split(/\s+/, $event->message());
344
345     push_output($event->ewpf);
346
347     if ($params[0] eq 'save') {
348         cmd_save();
349     } elsif ($params[0] eq 'set') {
350         shift(@params);
351         cmd_set(@params);
352     } elsif ($params[0] eq 'show') {
353         shift(@params);
354         cmd_show(@params);
355     } elsif ($params[0] eq 'help') {
356         shift(@params);
357         cmd_help(@params);
358     } elsif ($params[0] eq 'enable') {
359         shift(@params);
360         cmd_enable(@params);
361     } elsif ($params[0] eq 'disable') {
362         shift(@params);
363         cmd_disable(@params);
364     } elsif ($params[0] eq 'reload') {
365         init_quotesite(0);
366     } elsif ($params[0] eq 'debug') {
367         $debug = 1;
368         foreach (@grabbers) {
369             $_->setdebug(1);
370         }
371         write_irssi('Enabled debugging');
372     } elsif ($params[0] eq 'nodebug') {
373         $debug = 0;
374         foreach (@grabbers) {
375             $_->setdebug(0);
376         }
377         write_irssi('Disabled debugging');
378     }
379
380     pop_output();
381 }
382
383 unshift(@INC, $plugindir);
384 init_quotesite(1);