Added SILC Thread Queue API
[crypto.git] / apps / irssi / scripts / scriptassist.pl
1 # by Stefan "tommie" Tomanek
2 #
3 # scriptassist.pl
4
5
6 use strict;
7
8 use vars qw($VERSION %IRSSI);
9 $VERSION = '2003020803';
10 %IRSSI = (
11     authors     => 'Stefan \'tommie\' Tomanek',
12     contact     => 'stefan@pico.ruhr.de',
13     name        => 'scriptassist',
14     description => 'keeps your scripts on the cutting edge',
15     license     => 'GPLv2',
16     url         => 'http://irssi.org/scripts/',
17     changed     => $VERSION,
18     modules     => 'Data::Dumper LWP::UserAgent (GnuPG)',
19     commands    => "scriptassist"
20 );
21
22 use vars qw($forked %remote_db $have_gpg);
23
24 use Irssi 20020324;
25 use Data::Dumper;
26 use LWP::UserAgent;
27 use POSIX;
28
29 # GnuPG is not always needed
30 use vars qw($have_gpg @complist);
31 $have_gpg = 0;
32 eval "use GnuPG qw(:algo :trust);";
33 $have_gpg = 1 if not ($@);
34
35 sub show_help() {
36     my $help = "scriptassist $VERSION
37 /scriptassist check
38     Check all loaded scripts for new available versions
39 /scriptassist update <script|all>
40     Update the selected or all script to the newest version
41 /scriptassist search <query>
42     Search the script database
43 /scriptassist info <scripts>
44     Display information about <scripts>
45 /scriptassist ratings <scripts>
46     Retrieve the average ratings of the the scripts
47 /scriptassist top <num>
48     Retrieve the first <num> top rated scripts
49 /scriptassist new <num>
50     Display the newest <num> scripts
51 /scriptassist rate <script> <stars>
52     Rate the script with a number of stars ranging from 0-5
53 /scriptassist contact <script>
54     Write an email to the author of the script
55     (Requires OpenURL)
56 /scriptassist cpan <module>
57     Visit CPAN to look for missing Perl modules
58     (Requires OpenURL)
59 /scriptassist install <script>
60     Retrieve and load the script
61 /scriptassist autorun <script>
62     Toggles automatic loading of <script>
63 ";  
64     my $text='';
65     foreach (split(/\n/, $help)) {
66         $_ =~ s/^\/(.*)$/%9\/$1%9/;
67         $text .= $_."\n";
68     }
69     print CLIENTCRAP &draw_box("ScriptAssist", $text, "scriptassist help", 1);
70     #theme_box("ScriptAssist", $text, "scriptassist help", 1);
71 }
72
73 sub theme_box ($$$$) {
74     my ($title, $text, $footer, $colour) = @_;
75     Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'box_header', $title);
76     foreach (split(/\n/, $text)) {
77         Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'box_inside', $_);
78     }
79     Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'box_footer', $footer);
80 }
81
82 sub draw_box ($$$$) {
83     my ($title, $text, $footer, $colour) = @_;
84     my $box = '';
85     $box .= '%R,--[%n%9%U'.$title.'%U%9%R]%n'."\n";
86     foreach (split(/\n/, $text)) {
87         $box .= '%R|%n '.$_."\n";
88     }                                                                               $box .= '%R`--<%n'.$footer.'%R>->%n';
89     $box =~ s/%.//g unless $colour;
90     return $box;
91 }
92
93 sub call_openurl ($) {
94     my ($url) = @_;
95     no strict "refs";
96     # check for a loaded openurl
97     if (defined %{ "Irssi::Script::openurl::" }) {
98         &{ "Irssi::Script::openurl::launch_url" }($url);
99     } else {
100         print CLIENTCRAP "%R>>%n Please install openurl.pl";
101     }
102     use strict;
103 }
104
105 sub bg_do ($) {
106     my ($func) = @_; 
107     my ($rh, $wh);
108     pipe($rh, $wh);
109     if ($forked) {
110         print CLIENTCRAP "%R>>%n Please wait until your earlier request has been finished.";
111         return;
112     }
113     my $pid = fork();
114     $forked = 1;
115     if ($pid > 0) {
116         print CLIENTCRAP "%R>>%n Please wait...";
117         close $wh;
118         Irssi::pidwait_add($pid);
119         my $pipetag;
120         my @args = ($rh, \$pipetag, $func);
121         $pipetag = Irssi::input_add(fileno($rh), INPUT_READ, \&pipe_input, \@args);
122     } else {
123         eval {
124             my @items = split(/ /, $func);
125             my %result;
126             my $ts1 = $remote_db{timestamp};
127             my $xml = get_scripts();
128             my $ts2 = $remote_db{timestamp};
129             if (not($ts1 eq $ts2) && Irssi::settings_get_bool('scriptassist_cache_sources')) {
130                 $result{db} = $remote_db{db};
131                 $result{timestamp} = $remote_db{timestamp};
132             }
133             if ($items[0] eq 'check') {
134                 $result{data}{check} = check_scripts($xml);
135             } elsif ($items[0] eq 'update') {
136                 shift(@items);
137                 $result{data}{update} = update_scripts(\@items, $xml);
138             } elsif ($items[0] eq 'search') {
139                 shift(@items);
140                 #$result{data}{search}{-foo} = 0;
141                 foreach (@items) {
142                     $result{data}{search}{$_} = search_scripts($_, $xml);
143                 }
144             } elsif ($items[0] eq 'install') {
145                 shift(@items);
146                 $result{data}{install} = install_scripts(\@items, $xml);
147             } elsif ($items[0] eq 'debug') {
148                 shift(@items);
149                 $result{data}{debug} = debug_scripts(\@items);
150             } elsif ($items[0] eq 'ratings') {
151                 shift(@items);
152                 @items = @{ loaded_scripts() } if $items[0] eq "all";
153                 #$result{data}{rating}{-foo} = 1;
154                 my %ratings = %{ get_ratings(\@items, '') };
155                 foreach (keys %ratings) {
156                     $result{data}{rating}{$_}{rating} = $ratings{$_}->[0];
157                     $result{data}{rating}{$_}{votes} = $ratings{$_}->[1];
158                 }
159             } elsif ($items[0] eq 'rate') {
160                 #$result{data}{rate}{-foo} = 1;
161                 $result{data}{rate}{$items[1]} = rate_script($items[1], $items[2]);
162             } elsif ($items[0] eq 'info') {
163                 shift(@items);
164                 $result{data}{info} = script_info(\@items);
165             } elsif ($items[0] eq 'echo') {
166                 $result{data}{echo} = 1;
167             } elsif ($items[0] eq 'top') {
168                 my %ratings = %{ get_ratings([], $items[1]) };
169                 foreach (keys %ratings) {
170                     $result{data}{rating}{$_}{rating} = $ratings{$_}->[0];
171                     $result{data}{rating}{$_}{votes} = $ratings{$_}->[1];
172                 }
173             } elsif ($items[0] eq 'new') {
174                 my $new = get_new($items[1]);
175                 $result{data}{new} = $new;
176             } elsif ($items[0] eq 'unknown') {
177                 my $cmd = $items[1];
178                 $result{data}{unknown}{$cmd} = get_unknown($cmd, $xml);
179             }
180             my $dumper = Data::Dumper->new([\%result]);
181             $dumper->Purity(1)->Deepcopy(1)->Indent(0);
182             my $data = $dumper->Dump;
183             print($wh $data);
184         };
185         close($wh);
186         POSIX::_exit(1);
187     }
188 }
189
190 sub get_unknown ($$) {
191     my ($cmd, $db) = @_;
192     foreach (keys %$db) {
193         next unless defined $db->{$_}{commands};
194         foreach my $item (split / /, $db->{$_}{commands}) {
195             return { $_ => $db->{$_} } if ($item =~ /^$cmd$/i);
196         }
197     }
198     return undef;
199 }
200
201 sub script_info ($) {
202     my ($scripts) = @_;
203     no strict "refs";
204     my %result;
205     my $xml = get_scripts();
206     foreach (@{$scripts}) {
207         next unless (defined $xml->{$_.".pl"} || (defined %{ 'Irssi::Script::'.$_.'::' } && defined %{ 'Irssi::Script::'.$_.'::IRSSI' }));
208         $result{$_}{version} = get_remote_version($_, $xml);
209         my @headers = ('authors', 'contact', 'description', 'license', 'source');
210         foreach my $entry (@headers) {
211             $result{$_}{$entry} = ${ 'Irssi::Script::'.$_.'::IRSSI' }{$entry};
212             if (defined $xml->{$_.".pl"}{$entry}) {
213                 $result{$_}{$entry} = $xml->{$_.".pl"}{$entry};
214             }
215         }
216         if ($xml->{$_.".pl"}{signature_available}) {
217             $result{$_}{signature_available} = 1;
218         }
219         if (defined $xml->{$_.".pl"}{modules}) {
220             my $modules = $xml->{$_.".pl"}{modules};
221             #$result{$_}{modules}{-foo} = 1;
222             foreach my $mod (split(/ /, $modules)) {
223                 my $opt = ($mod =~ /\((.*)\)/)? 1 : 0;
224                 $mod = $1 if $1;
225                 $result{$_}{modules}{$mod}{optional} = $opt;
226                 $result{$_}{modules}{$mod}{installed} = module_exist($mod);
227             }
228         } elsif (defined ${ 'Irssi::Script::'.$_.'::IRSSI' }{modules}) {
229             my $modules = ${ 'Irssi::Script::'.$_.'::IRSSI' }{modules};
230             foreach my $mod (split(/ /, $modules)) {
231                 my $opt = ($mod =~ /\((.*)\)/)? 1 : 0;
232                 $mod = $1 if $1;
233                 $result{$_}{modules}{$mod}{optional} = $opt;
234                 $result{$_}{modules}{$mod}{installed} = module_exist($mod);
235             }
236         }
237         if (defined $xml->{$_.".pl"}{depends}) {
238             my $depends = $xml->{$_.".pl"}{depends};
239             foreach my $dep (split(/ /, $depends)) {
240                 $result{$_}{depends}{$dep}{installed} = 1; #(defined ${ 'Irssi::Script::'.$dep }); 
241             }
242         }
243     }
244     return \%result;
245 }
246
247 sub rate_script ($$) {
248     my ($script, $stars) = @_;
249     my $ua = LWP::UserAgent->new(env_proxy=>1, keep_alive=>1, timeout=>30);
250     $ua->agent('ScriptAssist/'.$VERSION);
251     my $request = HTTP::Request->new('GET', 'http://ratings.irssi.de/irssirate.pl?&stars='.$stars.'&mode=rate&script='.$script);
252     my $response = $ua->request($request);
253     unless ($response->is_success() && $response->content() =~ /You already rated this script/) {
254         return 1;
255     } else {
256         return 0;
257     }
258 }
259
260 sub get_ratings ($$) {
261     my ($scripts, $limit) = @_;
262     my $ua = LWP::UserAgent->new(env_proxy=>1, keep_alive=>1, timeout=>30);
263     $ua->agent('ScriptAssist/'.$VERSION);
264     my $script = join(',', @{$scripts});
265     my $request = HTTP::Request->new('GET', 'http://ratings.irssi.de/irssirate.pl?script='.$script.'&sort=rating&limit='.$limit);
266     my $response = $ua->request($request);
267     my %result;
268     if ($response->is_success()) {
269         foreach (split /\n/, $response->content()) {
270             if (/<tr><td><a href=".*?">(.*?)<\/a>/) {
271                 my $entry = $1;
272                 if (/"><\/td><td>([0-9.]+)<\/td><td>(.*?)<\/td><td>/) {
273                     $result{$entry} = [$1, $2];
274                 }
275             }
276         }
277     }
278     return \%result;
279 }
280
281 sub get_new ($) {
282     my ($num) = @_;
283     my $result;
284     my $xml = get_scripts();
285     foreach (sort {$xml->{$b}{last_modified} cmp $xml->{$a}{last_modified}} keys %$xml) {
286         my %entry = %{ $xml->{$_} };
287         $result->{$_} = \%entry;
288         $num--;
289         last unless $num;
290     }
291     return $result;
292 }
293 sub module_exist ($) {
294     my ($module) = @_;
295     $module =~ s/::/\//g;
296     foreach (@INC) {
297         return 1 if (-e $_."/".$module.".pm");
298     }
299     return 0;
300 }
301
302 sub debug_scripts ($) {
303     my ($scripts) = @_;
304     my %result;
305     foreach (@{$scripts}) {
306         my $xml = get_scripts();
307         if (defined $xml->{$_.".pl"}{modules}) {
308             my $modules = $xml->{$_.".pl"}{modules};
309             foreach my $mod (split(/ /, $modules)) {
310                 my $opt = ($mod =~ /\((.*)\)/)? 1 : 0;
311                 $mod = $1 if $1;
312                 $result{$_}{$mod}{optional} = $opt;
313                 $result{$_}{$mod}{installed} = module_exist($mod);
314             }
315         }
316     }
317     return(\%result);
318 }
319
320 sub install_scripts ($$) {
321     my ($scripts, $xml) = @_;
322     my %success;
323     #$success{-foo} = 1;
324     my $dir = Irssi::get_irssi_dir()."/scripts/";
325     foreach (@{$scripts}) {
326         if (get_local_version($_) && (-e $dir.$_.".pl")) {
327             $success{$_}{installed} = -2;
328         } else {
329             $success{$_} = download_script($_, $xml);
330         }
331     }
332     return \%success;
333 }
334
335 sub update_scripts ($$) {
336     my ($list, $database) = @_;
337     $list = loaded_scripts() if ($list->[0] eq "all" || scalar(@$list) == 0);
338     my %status;
339     #$status{-foo} = 1;
340     foreach (@{$list}) {
341         my $local = get_local_version($_);
342         my $remote = get_remote_version($_, $database);
343         next if $local eq '' || $remote eq '';
344         if (compare_versions($local, $remote) eq "older") {
345             $status{$_} = download_script($_, $database);
346         } else {
347             $status{$_}{installed} = -2;
348         }
349         $status{$_}{remote} = $remote;
350         $status{$_}{local} = $local;
351     }
352     return \%status;
353 }
354
355 sub search_scripts ($$) {
356     my ($query, $database) = @_;
357     my %result;
358     #$result{-foo} = " ";
359     foreach (sort keys %{$database}) {
360         my %entry = %{$database->{$_}};
361         my $string = $_." ";
362         $string .= $entry{description} if defined $entry{description};
363         if ($string =~ /$query/i) {
364             my $name = $_;
365             $name =~ s/\.pl$//;
366             if (defined $entry{description}) {
367                 $result{$name}{desc} = $entry{description};
368             } else {
369                 $result{$name}{desc} = "";
370             }
371             if (defined $entry{authors}) {
372                 $result{$name}{authors} = $entry{authors};
373             } else {
374                 $result{$name}{authors} = "";
375             }
376             if (get_local_version($name)) {
377                 $result{$name}{installed} = 1;
378             } else {
379                 $result{$name}{installed} = 0;
380             }
381         }
382     }
383     return \%result;
384 }
385
386 sub pipe_input {
387     my ($rh, $pipetag) = @{$_[0]};
388     my @lines = <$rh>;
389     close($rh);
390     Irssi::input_remove($$pipetag);
391     $forked = 0;
392     my $text = join("", @lines);
393     unless ($text) {
394         print CLIENTCRAP "%R<<%n Something weird happend";
395         return();
396     }
397     no strict "vars";
398     my $incoming = eval("$text");
399     if ($incoming->{db} && $incoming->{timestamp}) {
400         $remote_db{db} = $incoming->{db};
401         $remote_db{timestamp} = $incoming->{timestamp};
402     }
403     unless (defined $incoming->{data}) {
404         print CLIENTCRAP "%R<<%n Something weird happend";
405         return;
406     }
407     my %result = %{ $incoming->{data} };
408     @complist = ();
409     if (defined $result{new}) {
410         print_new($result{new});
411         push @complist, $_ foreach keys %{ $result{new} };
412     }
413     if (defined $result{check}) {
414         print_check(%{$result{check}});
415         push @complist, $_ foreach keys %{ $result{check} };
416     }
417     if (defined $result{update}) {
418         print_update(%{ $result{update} });
419         push @complist, $_ foreach keys %{ $result{update} };
420     }
421     if (defined $result{search}) {
422         foreach (keys %{$result{search}}) {
423             print_search($_, %{$result{search}{$_}});
424             push @complist, keys(%{$result{search}{$_}});
425         }
426     }
427     if (defined $result{install}) {
428         print_install(%{ $result{install} });
429         push @complist, $_ foreach keys %{ $result{install} };
430     }
431     if (defined $result{debug}) {
432         print_debug(%{ $result{debug} });
433     }
434     if (defined $result{rating}) {
435         print_ratings(%{ $result{rating} });
436         push @complist, $_ foreach keys %{ $result{rating} };
437     }
438     if (defined $result{rate}) {
439         print_rate(%{ $result{rate} });
440     }
441     if (defined $result{info}) {
442         print_info(%{ $result{info} });
443     }
444     if (defined $result{echo}) {
445         Irssi::print "ECHO";
446     }
447     if ($result{unknown}) {
448         print_unknown($result{unknown});
449     }
450
451 }
452
453 sub print_unknown ($) {
454     my ($data) = @_;
455     foreach my $cmd (keys %$data) {
456         print CLIENTCRAP "%R<<%n No script provides '/$cmd'" unless $data->{$cmd};
457         foreach (keys %{ $data->{$cmd} }) {
458             my $text .= "The command '/".$cmd."' is provided by the script '".$data->{$cmd}{$_}{name}."'.\n";
459             $text .= "This script is currently not installed on your system.\n";
460             $text .= "If you want to install the script, enter\n";
461             my ($name) = /(.*?)\.pl$/;
462             $text .= "  %U/script install ".$name."%U ";
463             my $output = draw_box("ScriptAssist", $text, "'".$_."' missing", 1);
464             print CLIENTCRAP $output;
465         }
466     }
467 }
468
469 sub check_autorun ($) {
470     my ($script) = @_;
471     my $dir = Irssi::get_irssi_dir()."/scripts/";
472     if (-e $dir."/autorun/".$script.".pl") {
473         if (readlink($dir."/autorun/".$script.".pl") eq "../".$script.".pl") {
474             return 1;
475         }
476     }
477     return 0;
478 }
479
480 sub array2table {
481     my (@array) = @_;
482     my @width;
483     foreach my $line (@array) {
484         for (0..scalar(@$line)-1) {
485             my $l = $line->[$_];
486             $l =~ s/%[^%]//g;
487             $l =~ s/%%/%/g;
488             $width[$_] = length($l) if $width[$_]<length($l);
489         }
490     }   
491     my $text;
492     foreach my $line (@array) {
493         for (0..scalar(@$line)-1) {
494             my $l = $line->[$_];
495             $text .= $line->[$_];
496             $l =~ s/%[^%]//g;
497             $l =~ s/%%/%/g;
498             $text .= " "x($width[$_]-length($l)+1) unless ($_ == scalar(@$line)-1);
499         }
500         $text .= "\n";
501     }
502     return $text;
503 }
504
505
506 sub print_info (%) {
507     my (%data) = @_;
508     my $line;
509     foreach my $script (sort keys(%data)) {
510         my ($local, $autorun);
511         if (get_local_version($script)) {
512             $line .= "%go%n ";
513             $local = get_local_version($script);
514         } else {
515             $line .= "%ro%n ";
516             $local = undef;
517         }
518         if (defined $local || check_autorun($script)) {
519             $autorun = "no";
520             $autorun = "yes" if check_autorun($script);
521         } else {
522             $autorun = undef;
523         }
524         $line .= "%9".$script."%9\n";
525         $line .= "  Version    : ".$data{$script}{version}."\n";
526         $line .= "  Source     : ".$data{$script}{source}."\n";
527         $line .= "  Installed  : ".$local."\n" if defined $local;
528         $line .= "  Autorun    : ".$autorun."\n" if defined $autorun;
529         $line .= "  Authors    : ".$data{$script}{authors};
530         $line .= " %Go-m signed%n" if $data{$script}{signature_available};
531         $line .= "\n";
532         $line .= "  Contact    : ".$data{$script}{contact}."\n";
533         $line .= "  Description: ".$data{$script}{description}."\n";
534         $line .= "\n" if $data{$script}{modules};
535         $line .= "  Needed Perl modules:\n" if $data{$script}{modules};
536
537         foreach (sort keys %{$data{$script}{modules}}) {
538             if ( $data{$script}{modules}{$_}{installed} == 1 ) {
539                 $line .= "  %g->%n ".$_." (found)";
540             } else {
541                 $line .= "  %r->%n ".$_." (not found)";
542             }
543             $line .= " <optional>" if $data{$script}{modules}{$_}{optional};
544             $line .= "\n";
545         }
546         #$line .= "  Needed Irssi scripts:\n";
547         $line .= "  Needed Irssi Scripts:\n" if $data{$script}{depends};
548         foreach (sort keys %{$data{$script}{depends}}) {
549             if ( $data{$script}{depends}{$_}{installed} == 1 ) {
550                 $line .= "  %g->%n ".$_." (loaded)";
551             } else {
552                 $line .= "  %r->%n ".$_." (not loaded)";
553             }
554             #$line .= " <optional>" if $data{$script}{depends}{$_}{optional};
555             $line .= "\n";
556         }
557     }
558     print CLIENTCRAP draw_box('ScriptAssist', $line, 'info', 1) ;
559 }
560
561 sub print_rate (%) {
562     my (%data) = @_;
563     my $line;
564     foreach my $script (sort keys(%data)) {
565         if ($data{$script}) {
566             $line .= "%go%n %9".$script."%9 has been rated";
567         } else {
568             $line .= "%ro%n %9".$script."%9 : Already rated this script";
569         }
570     }
571     print CLIENTCRAP draw_box('ScriptAssist', $line, 'rating', 1) ;
572 }
573
574 sub print_ratings (%) {
575     my (%data) = @_;
576     my @table;
577     foreach my $script (sort {$data{$b}{rating}<=>$data{$a}{rating}} keys(%data)) {
578         my @line;
579         if (get_local_version($script)) {
580             push @line, "%go%n";
581         } else {
582             push @line, "%yo%n";
583         }
584         push @line, "%9".$script."%9";
585         push @line, $data{$script}{rating};
586         push @line, "[".$data{$script}{votes}." votes]";
587         push @table, \@line;
588     }
589     print CLIENTCRAP draw_box('ScriptAssist', array2table(@table), 'ratings', 1) ;
590 }
591
592 sub print_new ($) {
593     my ($list) = @_;
594     my @table;
595     foreach (sort {$list->{$b}{last_modified} cmp $list->{$a}{last_modified}} keys %$list) {
596         my @line;
597         my ($name) = /^(.*?)\.pl$/;
598         if (get_local_version($name)) {
599             push @line, "%go%n";
600         } else {
601             push @line, "%yo%n";
602         }
603         push @line, "%9".$name."%9";
604         push @line, $list->{$_}{last_modified};
605         push @table, \@line;
606     }
607     print CLIENTCRAP draw_box('ScriptAssist', array2table(@table), 'new scripts', 1) ;
608 }
609
610 sub print_debug (%) {
611     my (%data) = @_;
612     my $line;
613     foreach my $script (sort keys %data) {
614         $line .= "%ro%n %9".$script."%9 failed to load\n";
615         $line .= "  Make sure you have the following perl modules installed:\n";
616         foreach (sort keys %{$data{$script}}) {
617             if ( $data{$script}{$_}{installed} == 1 ) {
618                 $line .= "  %g->%n ".$_." (found)";
619             } else {
620                 $line .= "  %r->%n ".$_." (not found)\n";
621                 $line .= "     [This module is optional]\n" if $data{$script}{$_}{optional};
622                 $line .= "     [Try /scriptassist cpan ".$_."]";
623             }
624             $line .= "\n";
625         }
626         print CLIENTCRAP draw_box('ScriptAssist', $line, 'debug', 1) ;
627     }
628 }
629
630 sub load_script ($) {
631     my ($script) = @_;
632     Irssi::command('script load '.$script);
633 }
634
635 sub print_install (%) {
636     my (%data) = @_;
637     my $text;
638     my ($crashed, @installed);
639     foreach my $script (sort keys %data) {
640         my $line;
641         if ($data{$script}{installed} == 1) {
642             my $hacked;
643             if ($have_gpg && Irssi::settings_get_bool('scriptassist_use_gpg')) {
644                 if ($data{$script}{signed} >= 0) {
645                     load_script($script) unless (lc($script) eq lc($IRSSI{name}));
646                 } else {
647                     $hacked = 1;
648                 }
649             } else {
650                 load_script($script) unless (lc($script) eq lc($IRSSI{name}));
651             }
652             if (get_local_version($script) && not lc($script) eq lc($IRSSI{name})) {
653                 $line .= "%go%n %9".$script."%9 installed\n";
654                 push @installed, $script;
655             } elsif (lc($script) eq lc($IRSSI{name})) {
656                 $line .= "%yo%n %9".$script."%9 installed, please reload manually\n";
657             } else {
658                 $line .= "%Ro%n %9".$script."%9 fetched, but unable to load\n";
659                 $crashed .= $script." " unless $hacked;
660             }
661             if ($have_gpg && Irssi::settings_get_bool('scriptassist_use_gpg')) {
662                 foreach (split /\n/, check_sig($data{$script})) {
663                     $line .= "  ".$_."\n";
664                 }
665             }
666         } elsif ($data{$script}{installed} == -2) {
667             $line .= "%ro%n %9".$script."%9 already loaded, please try \"update\"\n";
668         } elsif ($data{$script}{installed} <= 0) {
669             $line .= "%ro%n %9".$script."%9 not installed\n";
670             foreach (split /\n/, check_sig($data{$script})) {
671                 $line .= "  ".$_."\n";
672             }
673         } else {
674             $line .= "%Ro%n %9".$script."%9 not found on server\n";
675         }
676         $text .= $line;
677     }
678     # Inspect crashed scripts
679     bg_do("debug ".$crashed) if $crashed;
680     print CLIENTCRAP draw_box('ScriptAssist', $text, 'install', 1);
681     list_sbitems(\@installed);
682 }
683
684 sub list_sbitems ($) {
685     my ($scripts) = @_;
686     my $text;
687     foreach (@$scripts) {
688         no strict 'refs';
689         next unless defined %{ "Irssi::Script::${_}::" };
690         next unless defined %{ "Irssi::Script::${_}::IRSSI" };
691         my %header = %{ "Irssi::Script::${_}::IRSSI" };
692         next unless $header{sbitems};
693         $text .= '%9"'.$_.'"%9 provides the following statusbar item(s):'."\n";
694         $text .= '  ->'.$_."\n" foreach (split / /, $header{sbitems});
695     }
696     return unless $text;
697     $text .= "\n";
698     $text .= "Enter '/statusbar window add <item>' to add an item.";
699     print CLIENTCRAP draw_box('ScriptAssist', $text, 'sbitems', 1);
700 }
701
702 sub check_sig ($) {
703     my ($sig) = @_;
704     my $line;
705     my %trust = ( -1 => 'undefined',
706                    0 => 'never',
707                    1 => 'marginal',
708                    2 => 'fully',
709                    3 => 'ultimate'
710                  );
711     if ($sig->{signed} == 1) {
712         $line .= "Signature found from ".$sig->{sig}{user}."\n";
713         $line .= "Timestamp  : ".$sig->{sig}{date}."\n";
714         $line .= "Fingerprint: ".$sig->{sig}{fingerprint}."\n";
715         $line .= "KeyID      : ".$sig->{sig}{keyid}."\n";
716         $line .= "Trust      : ".$trust{$sig->{sig}{trust}}."\n";
717     } elsif ($sig->{signed} == -1) {
718         $line .= "%1Warning, unable to verify signature%n\n";
719     } elsif ($sig->{signed} == 0) {
720         $line .= "%1No signature found%n\n" unless Irssi::settings_get_bool('scriptassist_install_unsigned_scripts');
721     }
722     return $line;
723 }
724
725 sub print_search ($%) {
726     my ($query, %data) = @_;
727     my $text;
728     foreach (sort keys %data) {
729         my $line;
730         $line .= "%go%n" if $data{$_}{installed};
731         $line .= "%yo%n" if not $data{$_}{installed};
732         $line .= " %9".$_."%9 ";
733         $line .= $data{$_}{desc};
734         $line =~ s/($query)/%U$1%U/gi;
735         $line .= ' ('.$data{$_}{authors}.')';
736         $text .= $line." \n";
737     }
738     print CLIENTCRAP draw_box('ScriptAssist', $text, 'search: '.$query, 1) ;
739 }
740
741 sub print_update (%) { 
742     my (%data) = @_;
743     my $text;
744     my @table;
745     my $verbose = Irssi::settings_get_bool('scriptassist_update_verbose');
746     foreach (sort keys %data) {
747         my $signed = 0;
748         if ($data{$_}{installed} == 1) {
749             my $local = $data{$_}{local};
750             my $remote = $data{$_}{remote};
751             push @table, ['%yo%n', '%9'.$_.'%9', 'upgraded ('.$local.'->'.$remote.')'];
752             foreach (split /\n/, check_sig($data{$_})) {
753                 push @table, ['', '', $_];
754             }
755             if (lc($_) eq lc($IRSSI{name})) {
756                 push @table, ['', '', "%R%9Please reload manually%9%n"];
757             } else {
758                 load_script($_);
759             }
760         } elsif ($data{$_}{installed} == 0 || $data{$_}{installed} == -1) {
761             push @table, ['%yo%n', '%9'.$_.'%9', 'not upgraded'];
762             foreach (split /\n/, check_sig($data{$_})) {
763                 push @table, ['', '', $_];
764             } 
765         } elsif ($data{$_}{installed} == -2 && $verbose) {
766             my $local = $data{$_}{local};
767             push @table, ['%go%n', '%9'.$_.'%9', 'already at the latest version ('.$local.')'];
768         }
769     }
770     $text = array2table(@table);
771     print CLIENTCRAP draw_box('ScriptAssist', $text, 'update', 1) ;
772 }
773
774 sub contact_author ($) {
775     my ($script) = @_;
776     no strict 'refs';
777     return unless defined %{ "Irssi::Script::${script}::" };
778     my %header = %{ "Irssi::Script::${script}::IRSSI" };
779     if (defined $header{contact}) {
780         my @ads = split(/ |,/, $header{contact});
781         my $address = $ads[0];
782         $address .= '?subject='.$script;
783         $address .= '_'.get_local_version($script) if defined get_local_version($script);
784         call_openurl($address);
785     }
786 }
787
788 sub get_scripts {
789     my $ua = LWP::UserAgent->new(env_proxy=>1, keep_alive=>1, timeout=>30);
790     $ua->agent('ScriptAssist/'.$VERSION);
791     $ua->env_proxy();
792     my @mirrors = split(/ /, Irssi::settings_get_str('scriptassist_script_sources'));
793     my %sites_db;
794     my $fetched = 0;
795     my @sources;
796     foreach my $site (@mirrors) {
797         my $request = HTTP::Request->new('GET', $site);
798         if ($remote_db{timestamp}) {
799             $request->if_modified_since($remote_db{timestamp});
800         }
801         my $response = $ua->request($request);
802         next unless $response->is_success;
803         $fetched = 1;
804         my $data = $response->content();
805         my ($src, $type);
806         if ($site =~ /(.*\/).+\.(.+)/) {
807             $src = $1;
808             $type = $2;
809         }
810         push @sources, $src;
811         #my @header = ('name', 'contact', 'authors', 'description', 'version', 'modules', 'last_modified');
812         if ($type eq 'dmp') {
813             no strict 'vars';
814             my $new_db = eval "$data";
815             foreach (keys %$new_db) {
816                 if (defined $sites_db{script}{$_}) {
817                     my $old = $sites_db{$_}{version};
818                     my $new = $new_db->{$_}{version};
819                     next if (compare_versions($old, $new) eq 'newer');
820                 }
821                 #foreach my $key (@header) {
822                 foreach my $key (keys %{ $new_db->{$_} }) {
823                     next unless defined $new_db->{$_}{$key};
824                     $sites_db{$_}{$key} = $new_db->{$_}{$key};
825                 }
826                 $sites_db{$_}{source} = $src;
827             }
828         } else {
829             ## FIXME Panic?!
830         }
831         
832     }
833     if ($fetched) {
834         # Clean database
835         foreach (keys %{$remote_db{db}}) {
836             foreach my $site (@sources) {
837                 if ($remote_db{db}{$_}{source} eq $site) {
838                     delete $remote_db{db}{$_};
839                     last;
840                 }
841             }
842         }
843         $remote_db{db}{$_} = $sites_db{$_} foreach (keys %sites_db);
844         $remote_db{timestamp} = time();
845     }
846     return $remote_db{db};
847 }
848
849 sub get_remote_version ($$) {
850     my ($script, $database) = @_;
851     return $database->{$script.".pl"}{version};
852 }
853
854 sub get_local_version ($) {
855     my ($script) = @_;
856     no strict 'refs';
857     return unless defined %{ "Irssi::Script::${script}::" };
858     my $version = ${ "Irssi::Script::${script}::VERSION" };
859     return $version;
860 }
861
862 sub compare_versions ($$) {
863     my ($ver1, $ver2) = @_;
864     my @ver1 = split /\./, $ver1;
865     my @ver2 = split /\./, $ver2;
866     #if (scalar(@ver2) != scalar(@ver1)) {
867     #    return 0;
868     #}       
869     my $cmp = 0;
870     ### Special thanks to Clemens Heidinger
871     $cmp ||= $ver1[$_] <=> $ver2[$_] || $ver1[$_] cmp $ver2[$_] for 0..scalar(@ver2);
872     return 'newer' if $cmp == 1;
873     return 'older' if $cmp == -1;
874     return 'equal';
875 }
876
877 sub loaded_scripts {
878     no strict 'refs';
879     my @modules;
880     foreach (sort grep(s/::$//, keys %Irssi::Script::)) {
881         #my $name    = ${ "Irssi::Script::${_}::IRSSI" }{name};
882         #my $version = ${ "Irssi::Script::${_}::VERSION" };
883         push @modules, $_;# if $name && $version;
884     }
885     return \@modules;
886
887 }
888
889 sub check_scripts {
890     my ($data) = @_;
891     my %versions;
892     #$versions{-foo} = 1;
893     foreach (@{loaded_scripts()}) {
894         my $remote = get_remote_version($_, $data);
895         my $local =  get_local_version($_);
896         my $state;
897         if ($local && $remote) {
898             $state = compare_versions($local, $remote);
899         } elsif ($local) {
900             $state = 'noversion';
901             $remote = '/';
902         } else {
903             $state = 'noheader';
904             $local = '/';
905             $remote = '/';
906         }
907         if ($state) {
908             $versions{$_}{state} = $state;
909             $versions{$_}{remote} = $remote;
910             $versions{$_}{local} = $local;
911         }
912     }
913     return \%versions;
914 }
915
916 sub download_script ($$) {
917     my ($script, $xml) = @_;
918     my %result;
919     my $site = $xml->{$script.".pl"}{source};
920     $result{installed} = 0;
921     $result{signed} = 0;
922     my $dir = Irssi::get_irssi_dir();
923     my $ua = LWP::UserAgent->new(env_proxy => 1,keep_alive => 1,timeout => 30);
924     $ua->agent('ScriptAssist/'.$VERSION);
925     my $request = HTTP::Request->new('GET', $site.'/scripts/'.$script.'.pl');
926     my $response = $ua->request($request);
927     if ($response->is_success()) {
928         my $file = $response->content();
929         mkdir $dir.'/scripts/' unless (-e $dir.'/scripts/');
930         local *F;
931         open(F, '>'.$dir.'/scripts/'.$script.'.pl.new');
932         print F $file;
933         close(F);
934         if ($have_gpg && Irssi::settings_get_bool('scriptassist_use_gpg')) {
935             my $ua2 = LWP::UserAgent->new(env_proxy => 1,keep_alive => 1,timeout => 30);
936             $ua->agent('ScriptAssist/'.$VERSION);
937             my $request2 = HTTP::Request->new('GET', $site.'/signatures/'.$script.'.pl.asc');
938             my $response2 = $ua->request($request2);
939             if ($response2->is_success()) {
940                 local *S;
941                 my $sig_dir = $dir.'/scripts/signatures/';
942                 mkdir $sig_dir unless (-e $sig_dir);
943                 open(S, '>'.$sig_dir.$script.'.pl.asc');
944                 my $file2 = $response2->content();
945                 print S $file2;
946                 close(S);
947                 my $sig;
948                 foreach (1..2) {
949                     # FIXME gpg needs two rounds to load the key
950                     my $gpg = new GnuPG();
951                     eval {
952                         $sig = $gpg->verify( file => $dir.'/scripts/'.$script.'.pl.new', signature => $sig_dir.$script.'.pl.asc' );
953                     };
954                 }
955                 if (defined $sig->{user}) {
956                     $result{installed} = 1;
957                     $result{signed} = 1;
958                     $result{sig}{$_} = $sig->{$_} foreach (keys %{$sig});
959                 } else {
960                     # Signature broken?
961                     $result{installed} = 0;
962                     $result{signed} = -1;
963                 }
964             } else {
965                 $result{signed} = 0;
966                 $result{installed} = -1;
967                 $result{installed} = 1 if Irssi::settings_get_bool('scriptassist_install_unsigned_scripts');
968             }
969         } else {
970             $result{signed} = 0;
971             $result{installed} = -1;
972             $result{installed} = 1 if Irssi::settings_get_bool('scriptassist_install_unsigned_scripts');
973         }
974     }
975     if ($result{installed}) {
976         my $old_dir = "$dir/scripts/old/";
977         mkdir $old_dir unless (-e $old_dir);
978         rename "$dir/scripts/$script.pl", "$old_dir/$script.pl.old" if -e "$dir/scripts/$script.pl";
979         rename "$dir/scripts/$script.pl.new", "$dir/scripts/$script.pl";
980     }
981     return \%result;
982 }
983
984 sub print_check (%) {
985     my (%data) = @_;
986     my $text;
987     my @table;
988     foreach (sort keys %data) {
989         my $state = $data{$_}{state};
990         my $remote = $data{$_}{remote};
991         my $local = $data{$_}{local};
992         if (Irssi::settings_get_bool('scriptassist_check_verbose')) {
993             push @table, ['%go%n', '%9'.$_.'%9', 'Up to date. ('.$local.')'] if $state eq 'equal';
994         }
995         push @table, ['%mo%n', '%9'.$_.'%9', "No version information available on network."] if $state eq "noversion";
996         push @table, ['%mo%n', '%9'.$_.'%9', 'No header in script.'] if $state eq "noheader";
997         push @table, ['%bo%n', '%9'.$_.'%9', "Your version is newer (".$local."->".$remote.")"] if $state eq "newer";
998         push @table, ['%ro%n', '%9'.$_.'%9', "A new version is available (".$local."->".$remote.")"] if $state eq "older";;
999     }
1000     $text = array2table(@table);
1001     print CLIENTCRAP draw_box('ScriptAssist', $text, 'check', 1) ;
1002 }
1003
1004 sub toggle_autorun ($) {
1005     my ($script) = @_;
1006     my $dir = Irssi::get_irssi_dir()."/scripts/";
1007     mkdir $dir."autorun/" unless (-e $dir."autorun/");
1008     return unless (-e $dir.$script.".pl");
1009     if (check_autorun($script)) {
1010         if (readlink($dir."/autorun/".$script.".pl") eq "../".$script.".pl") {
1011             if (unlink($dir."/autorun/".$script.".pl")) {
1012                 print CLIENTCRAP "%R>>%n Autorun of ".$script." disabled";
1013             } else {
1014                 print CLIENTCRAP "%R>>%n Unable to delete link";
1015             }
1016         } else {
1017             print CLIENTCRAP "%R>>%n ".$dir."/autorun/".$script.".pl is not a correct link";
1018         }
1019     } else {
1020         symlink("../".$script.".pl", $dir."/autorun/".$script.".pl");
1021         print CLIENTCRAP "%R>>%n Autorun of ".$script." enabled";
1022     }
1023 }
1024
1025 sub sig_script_error ($$) {
1026     my ($script, $msg) = @_;
1027     return unless Irssi::settings_get_bool('scriptassist_catch_script_errors');
1028     if ($msg =~ /Can't locate (.*?)\.pm in \@INC \(\@INC contains:(.*?) at/) {
1029         my $module = $1;
1030         $module =~ s/\//::/g;
1031         missing_module($module);
1032     }
1033 }
1034
1035 sub missing_module ($$) {
1036     my ($module) = @_;
1037     my $text;
1038     $text .= "The perl module %9".$module."%9 is missing on your system.\n";
1039     $text .= "Please ask your administrator about it.\n";
1040     $text .= "You can also check CPAN via '/scriptassist cpan ".$module."'.\n";
1041     print CLIENTCRAP &draw_box('ScriptAssist', $text, $module, 1);
1042 }
1043
1044 sub cmd_scripassist ($$$) {
1045     my ($arg, $server, $witem) = @_;
1046     my @args = split(/ /, $arg);
1047     if ($args[0] eq 'help' || $args[0] eq '-h') {
1048         show_help();
1049     } elsif ($args[0] eq 'check') {
1050         bg_do("check");
1051     } elsif ($args[0] eq 'update') {
1052         shift @args;
1053         bg_do("update ".join(' ', @args));
1054     } elsif ($args[0] eq 'search' && defined $args[1]) {
1055         shift @args;
1056         bg_do("search ".join(" ", @args));
1057     } elsif ($args[0] eq 'install' && defined $args[1]) {
1058         shift @args;
1059         bg_do("install ".join(' ', @args));
1060     } elsif ($args[0] eq 'contact' && defined $args[1]) {
1061         contact_author($args[1]);
1062     } elsif ($args[0] eq 'ratings' && defined $args[1]) {
1063         shift @args;
1064         bg_do("ratings ".join(' ', @args));
1065     } elsif ($args[0] eq 'rate' && defined $args[1] && defined $args[2]) {
1066         shift @args;
1067         bg_do("rate ".join(' ', @args)) if ($args[2] >= 0 && $args[2] < 6);
1068     } elsif ($args[0] eq 'info' && defined $args[1]) {
1069         shift @args;
1070         bg_do("info ".join(' ', @args));
1071     } elsif ($args[0] eq 'echo') {
1072         bg_do("echo");
1073     } elsif ($args[0] eq 'top') {
1074         my $number = defined $args[1] ? $args[1] : 10;
1075         bg_do("top ".$number);
1076     } elsif ($args[0] eq 'cpan' && defined $args[1]) {
1077         call_openurl('http://search.cpan.org/search?mode=module&query='.$args[1]);
1078     } elsif ($args[0] eq 'autorun' && defined $args[1]) {
1079         toggle_autorun($args[1]);
1080     } elsif ($args[0] eq 'new') {
1081         my $number = defined $args[1] ? $args[1] : 5;
1082         bg_do("new ".$number);
1083     }
1084 }
1085
1086 sub sig_command_script_load ($$$) {
1087     my ($script, $server, $witem) = @_;
1088     no strict;
1089     $script = $2 if $script =~ /(.*\/)?(.*?)\.pl$/;
1090     if (defined %{ "Irssi::Script::${script}::" }) {
1091         if (defined &{ "Irssi::Script::${script}::pre_unload" }) {
1092             print CLIENTCRAP "%R>>%n Triggering pre_unload function of $script...";
1093             &{ "Irssi::Script::${script}::pre_unload" }();
1094         }
1095     }
1096 }
1097
1098 sub sig_default_command ($$) {
1099     my ($cmd, $server) = @_;
1100     return unless Irssi::settings_get_bool("scriptassist_check_unknown_commands");
1101     bg_do('unknown '.$cmd);
1102 }
1103
1104 sub sig_complete ($$$$$) {
1105     my ($list, $window, $word, $linestart, $want_space) = @_;
1106     return unless $linestart =~ /^.script(assist)? (install|rate|ratings|update|check|contact|info|autorun)/;
1107     my @newlist;
1108     my $str = $word;
1109     foreach (@complist) {
1110         if ($_ =~ /^(\Q$str\E.*)?$/) {
1111             push @newlist, $_;
1112         }
1113     }
1114     foreach (@{loaded_scripts()}) {
1115         push @newlist, $_ if /^(\Q$str\E.*)?$/;
1116     }
1117     $want_space = 0;
1118     push @$list, $_ foreach @newlist;
1119     Irssi::signal_stop();
1120 }
1121
1122
1123 Irssi::settings_add_str($IRSSI{name}, 'scriptassist_script_sources', 'http://scripts.irssi.org/scripts.dmp');
1124 Irssi::settings_add_bool($IRSSI{name}, 'scriptassist_cache_sources', 1);
1125 Irssi::settings_add_bool($IRSSI{name}, 'scriptassist_update_verbose', 1);
1126 Irssi::settings_add_bool($IRSSI{name}, 'scriptassist_check_verbose', 1);
1127 Irssi::settings_add_bool($IRSSI{name}, 'scriptassist_catch_script_errors', 1);
1128
1129 Irssi::settings_add_bool($IRSSI{name}, 'scriptassist_install_unsigned_scripts', 1);
1130 Irssi::settings_add_bool($IRSSI{name}, 'scriptassist_use_gpg', 1);
1131 Irssi::settings_add_bool($IRSSI{name}, 'scriptassist_integrate', 1);
1132 Irssi::settings_add_bool($IRSSI{name}, 'scriptassist_check_unknown_commands', 1);
1133
1134 Irssi::signal_add_first("default command", \&sig_default_command);
1135 Irssi::signal_add_first('complete word', \&sig_complete);
1136 Irssi::signal_add_first('command script load', \&sig_command_script_load);
1137 Irssi::signal_add_first('command script unload', \&sig_command_script_load);
1138
1139 if (defined &Irssi::signal_register) {
1140     Irssi::signal_register({ 'script error' => [ 'Irssi::Script', 'string' ] });
1141     Irssi::signal_add_last('script error', \&sig_script_error);
1142 }
1143
1144 Irssi::command_bind('scriptassist', \&cmd_scripassist);
1145
1146 Irssi::theme_register(['box_header', '%R,--[%n$*%R]%n',
1147 'box_inside', '%R|%n $*',
1148 'box_footer', '%R`--<%n$*%R>->%n',
1149 ]);
1150
1151 foreach my $cmd ( ( 'check', 'install', 'update', 'contact', 'search', '-h', 'help', 'ratings', 'rate', 'info', 'echo', 'top', 'cpan', 'autorun', 'new') ) {
1152     Irssi::command_bind('scriptassist '.$cmd => sub {
1153                         cmd_scripassist("$cmd ".$_[0], $_[1], $_[2]); });
1154     if (Irssi::settings_get_bool('scriptassist_integrate')) {
1155         Irssi::command_bind('script '.$cmd => sub {
1156                             cmd_scripassist("$cmd ".$_[0], $_[1], $_[2]); });
1157     }
1158 }
1159
1160 print CLIENTCRAP '%B>>%n '.$IRSSI{name}.' '.$VERSION.' loaded: /scriptassist help for help';