889312ac2cdcb543fbda439f8eb50ec80836e2ca
[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 my $quotesite_commands = {
62     'save' => sub {
63         cmd_save();
64     },
65
66
67     'set' => sub {
68         cmd_set(@_);
69     },
70
71     'show' => sub {
72         cmd_show(@_);
73     },
74
75     'help' => sub {
76         cmd_help(@_);
77     },
78
79     'enable' => sub {
80         cmd_enable(@_);
81     },
82
83     'disable' => sub {
84         cmd_disable(@_);
85     },
86
87     'reload' => sub {
88         init_quotesite(0);
89     },
90
91     'debug' => sub {
92         $debug = 1;
93         foreach (@grabbers) {
94             $_->setdebug(1);
95         }
96         write_irssi('Enabled debugging');
97     },
98
99     'nodebug' => sub {
100         $debug = 0;
101         foreach (@grabbers) {
102             $_->setdebug(0);
103         }
104         write_irssi('Disabled debugging');
105     },
106 };
107
108
109 sub write_irssi {
110     my @text = @_;
111     my $output = $outputstack[0];
112
113     my $format = "%%mquotesite: %%n" . shift(@text);
114
115     # escape % in parameters for irssi
116     s/%/%%/g foreach @text;
117
118     if (defined($output)) {
119         $output->(sprintf($format, @text), MSGLEVEL_CLIENTCRAP);
120     } else {
121         Irssi::print(sprintf($format, @text));
122     }
123
124 }
125
126 sub write_debug {
127     if ($debug) {
128         write_irssi(@_);
129     }
130 }
131
132 sub check_for_link {
133     my $event = shift;
134     my $message = $event->message();
135     my $channel = $event->channel();
136     my $g;
137     my $m;
138     my $p;
139
140
141     # Look if we should ignore this line
142     if ($message =~ m,(?:\s|^)/nosave(?:\s|$),) {
143         return;
144     }
145
146     push_output($event->ewpf);
147
148     study($message);
149
150     # Offer the message to all Grabbers in turn
151     foreach $g (@grabbers) {
152         ($m, $p) = $g->get($message);
153         while (defined($m)) {
154             write_irssi('%%R>>> %%Y%s%%N %%G%s', $m->{'SOURCE'}, $m->{'ID'});
155
156             foreach (split(/[\n\r]+/, $m->{'CONTENT'})) {
157                 write_irssi('    %%g%s', $_);
158             }
159
160             # Remove the matched part from the message and try again (there may be
161             # more!)
162             $message =~ s/$p//;
163             study($message);
164
165             ($m, $p) = $g->get($message);
166         }
167     }
168
169     pop_output();
170 }
171
172 sub cmd_save {
173
174     eval {
175         open(CONF, '>'.$conffile) or die 'Could not open config file';
176         print CONF XML::Simple::XMLout($conf, KeepRoot => 1, KeyAttr => {'config' => 'module', 'option' => 'key'});
177         close(CONF);
178     };
179     if ($@) {
180         write_irssi('Could not save config to %s: %s', ($conffile, $@));
181     } else {
182         write_irssi('configuration saved to %s', $conffile);
183     }
184 }
185
186 sub cmd_set {
187     my $target = shift;
188     my $key = shift;
189     my $val = shift;
190     my $p;
191
192     foreach $p (@grabbers) {
193         if ($p->{'NAME'} eq $target) {
194             $p->setval($key, $val);
195             return;
196         }
197     }
198     write_irssi('No such module');
199 }
200
201
202 sub cmd_enable {
203     my $target = shift;
204     my $p;
205
206     foreach $p (@grabbers) {
207         if ($p->{'NAME'} eq $target) {
208             $p->enable();
209             return;
210         }
211     }
212     write_irssi('No such module');
213 }
214
215
216 sub cmd_disable {
217     my $target = shift;
218     my $p;
219
220     foreach $p (@grabbers) {
221         if ($p->{'NAME'} eq $target) {
222             $p->disable();
223             return;
224         }
225     }
226     write_irssi('No such module');
227 }
228
229
230 sub cmd_show {
231     my $target = shift;
232     my $p;
233     my $e;
234
235     if (defined($target)) {
236         foreach $p (@grabbers) {
237             if ($p->{'NAME'} eq $target) {
238                 write_irssi($p->getconfstr());
239                 return;
240             }
241         }
242         write_irssi('No such module');
243     } else {
244         write_irssi('Loaded grabbers (* denotes enabled modules):');
245         foreach $p (@grabbers) {
246             $e = $p->_getval('enabled');
247             write_irssi(' %s%s', $p->{'NAME'}, $e?'*':'');
248         };
249     }
250 }
251
252 sub cmd_help {
253     my $target = shift;
254     my $p;
255
256     if (defined($target)) {
257         foreach $p (@grabbers) {
258             if ($p->{'NAME'} eq $target) {
259                 write_irssi($p->gethelpstr());
260                 return;
261             }
262         }
263         write_irssi('No such module');
264     } else {
265         write_irssi(<<'EOT');
266 Supported commands:
267  save: Save the current configuration
268  help [modulename]: Display this help, or module specific help
269  show [modulename]: Show loaded modules, or the current parameters of a module
270  set modulename parameter value: set a module parameter to a new value
271  enable [modulename]: enable the usage of this module (grabbers only)
272  disable [modulename]: disable the usage of this module (grabbers only)
273  reload: reload all modules (this is somewhat experimental)
274  debug: enable debugging messages
275  nodebug: disable debugging messages
276 EOT
277     }
278 }
279
280 # save on unload
281 sub sig_command_script_unload {
282     my $script = shift;
283     if ($script =~ /(.*\/)?quotesite(\.pl)?$/) {
284         cmd_save();
285     }
286 }
287
288 sub ploader {
289
290     my $dir = shift;
291     my $pattern = shift;
292     my $type = shift;
293     my @list;
294     my $p;
295     my $g;
296     my @g = ();
297
298     opendir(D, $dir) || return ();
299     @list = grep {/$pattern/ && -f File::Spec->catfile($dir, $_) } readdir(D);
300     closedir(D);
301
302     foreach $p (@list) {
303         write_debug("Trying to load $p:");
304         $p =~ s/\.pm$//;
305         eval qq{ require quotesite::$p; };
306         if ($@) {
307             write_debug("Failed to load plugin: $@");
308             next;
309         }
310
311         $g = eval qq{ quotesite::$p->new(); };
312         if ($@) {
313             write_debug("Failed to instanciate: $@");
314             delete($INC{$p});
315             next;
316         }
317
318         write_debug("found $g->{'TYPE'} $g->{'NAME'}");
319         if ($type eq $g->{'TYPE'}) {
320             push(@g, $g);
321             $g->setio(\&write_irssi);
322         } else {
323             write_irssi('%s has wrong type (got %s, expected %s)', $p, $g->{'TYPE'}, $type);
324             delete($INC{$p});
325         }
326     }
327
328     write_debug("Loaded %d plugins", $#g+1);
329     
330     return @g;
331 }
332
333 sub _load_modules($) {
334
335     my $path = shift;
336
337     foreach (keys(%INC)) {
338         if ($INC{$_} =~ m|^$path|) {
339             write_debug("Removing %s from \$INC", $_);
340             delete($INC{$_});
341         }
342     }
343     @grabbers = ploader($path, '.*Grabber\.pm$', 'grabber');
344 }
345
346
347 sub init_quotesite {
348
349     my $bindings = shift;
350     my $p;
351
352     unless(-r $conffile && defined($conf = XML::Simple::XMLin($conffile, ForceArray => ['config', 'option'], KeepRoot => 1, KeyAttr => {'config' => 'module', 'option' => 'key'}))) {
353         # No config, start with an empty one
354         write_debug('No config found, using defaults');
355         $conf = { 'quotesite' => { }};
356     }
357     foreach (keys(%{$PARAMS})) {
358         unless (exists($conf->{'quotesite'}->{$_})) {
359             $conf->{'quotesite'}->{$_} = $PARAMS->{$_};
360         }
361     }
362
363     _load_modules($plugindir);
364
365     unless (defined(@grabbers)) {
366         write_irssi('No grabbers found, can not proceed.');
367         return;
368     }
369
370
371     # Loop through all plugins and load the config
372     foreach $p (@grabbers) {
373         $conf->{'quotesite'}->{'config'}->{$p->{'NAME'}} = $p->mergeconfig($conf->{'quotesite'}->{'config'}->{$p->{'NAME'}});
374     }
375
376     if ($bindings) {
377
378         Irssi::signal_add_first('command script load', 'sig_command_script_unload');
379         Irssi::signal_add_first('command script unload', 'sig_command_script_unload');
380         Irssi::signal_add('setup saved', 'cmd_save');
381
382
383         Irssi::command_bind(_bcb('quotesite' => \&cmdhandler));
384     }
385
386     write_irssi('quotesite initialized');
387 }
388
389 sub cmdhandler {
390     my $event = shift;
391     my ($cmd, @params) = split(/\s+/, $event->message());
392
393     push_output($event->ewpf);
394
395     if (exists($quotesite_commands->{$cmd})) {
396         $quotesite_commands->{$cmd}->(@params);
397     }
398
399     pop_output();
400 }
401
402 unshift(@INC, $plugindir);
403 init_quotesite(1);