html/idonkey.pl


   1 # iDonkey for mldonkey
   2 #
   3 ## by Stefan Tomanek
   4 #
   5 
   6 use strict;
   7 
   8 use vars qw($VERSION %IRSSI);
   9 $VERSION = "2004051601";
  10 %IRSSI = (
  11     authors     => "Stefan 'tommie' Tomanek",
  12     contact     => "stefan\@pico.ruhr.de",
  13     name        => "iDonkey",
  14     description => "equips Irssi with an interface to mldonkey",
  15     license     => "GPLv2",
  16     changed     => "$VERSION",
  17     modules     => "IO::Socket::INET Data::Dumper LWP::UserAgent HTML::Entities",
  18     sbitems     => "idonkey",
  19     commands	=> "idonkey"
  20 );
  21 
  22 
  23 use Irssi 20020324;
  24 use Irssi::TextUI;
  25 use IO::Socket::INET;
  26 use LWP::UserAgent;
  27 use HTML::Entities;
  28 use Data::Dumper;
  29 use POSIX;
  30 use vars qw($forked $timer $timer2 $index %downloads $nresults $seen $credits $noul %edlinks $expected);
  31 
  32 sub show_help() {
  33     my $help = $IRSSI{name}." $VERSION
  34 /idonkey (downloads)
  35         List your current downloads
  36 /idonkey launch
  37         Start a new mldonkey process
  38 /idonkey quit
  39         Quit the mldonkey
  40 /idonkey servers (connected)
  41         List connected servers
  42 /idonkey servers all
  43         List all servers
  44 /idonkey servers connect <num>
  45         Connect to server with id <num>
  46         or connect more servers
  47 /idonkey servers disconnect <num>
  48         Disconnnect from server with id <num>
  49 /idonkey overnet (stats)
  50         Print OverNet statistics
  51 /idonkey dllink (force) <link>
  52         Download an ed2k-link
  53 /idonkey search <query>
  54         Query the donkey network for a file
  55 /idonkey results
  56         Display the results of the last query
  57 /idonkey get (force) <num1> <num2>
  58         Download the named files
  59 /idonkey pause <filename>
  60         Pause a download
  61 /idonkey resume <filename>
  62         Resume a download
  63 /idonkey cancel <filename>
  64         Cancel a download
  65 /idonkey commit
  66         Move downloaded files to the incoming directory
  67 /idonkey settings show
  68         Display the current mldonkey settings
  69 /idonkey settings change <key> <value>
  70         Change settings of mldonkey
  71 /idonkey shares reshare
  72         Check all shared files
  73 /idonkey shares close
  74         Close all open file descriptors
  75 /idonkey sharereactor (latest)
  76         Display the latest releases
  77 /idonkey sharereactor search <query>
  78         Search www.sharereactor.com
  79 /idonkey sharereactor download <release>
  80         Download all files of a release
  81 /idonkey bittorrent search <quer>
  82         Search torrents
  83 /idonkey noupload <min>
  84         Disable uploading for <min> minutes
  85 /idonkey client-stats
  86         Display detailed client statistics
  87 /idonkey forget
  88         Clear all searches
  89 ";
  90     my $text = '';
  91     foreach (split(/\n/, $help)) {
  92         $_ =~ s/^\/(.*)$/%9\/$1%9/;
  93         $text .= $_."\n";
  94     }
  95     print CLIENTCRAP &draw_box($IRSSI{name}, $text, "help", 1);
  96 }
  97 
  98 
  99 sub draw_box ($$$$) {
 100     my ($title, $text, $footer, $colour) = @_;
 101     my $box = ''; 
 102     $box .= '%R,--[%n%9%U'.$title.'%U%9%R]%n'."\n";
 103     foreach (split(/\n/, $text)) {
 104     	$box .= '%R|%n '.$_."\n";
 105     }
 106     $box .= '%R`--<%n'.$footer.'%R>->%n';
 107     unless ($colour) {
 108 	$box =~ s/%(.)/$1 eq '%'?$1:''/eg;
 109     }
 110     return $box;
 111 }   
 112 
 113 sub array2table {
 114     my (@array) = @_;
 115     my @width;
 116     foreach my $line (@array) {
 117         for (0..scalar(@$line)-1) {
 118 	    my $l = $line->[$_];
 119 	    $l =~ s/%[^%]//g;
 120 	    $l =~ s/%%/%/g;
 121             $width[$_] = length($l) if $width[$_]<length($l);
 122         }
 123     }   
 124     my $text;
 125     foreach my $line (@array) {
 126         for (0..scalar(@$line)-1) {
 127 	    my $l = $line->[$_];
 128             $text .= $line->[$_];
 129 	    $l =~ s/%[^%]//g;
 130 	    $l =~ s/%%/%/g;
 131             $text .= " "x($width[$_]-length($l)+1) unless ($_ == scalar(@$line)-1);
 132         }
 133         $text .= "\n";
 134     }
 135     return $text;
 136 }
 137 
 138 sub donkey_connect {
 139     my $host = Irssi::settings_get_str('idonkey_host');
 140     my $port = Irssi::settings_get_int('idonkey_port');
 141     my $password = Irssi::settings_get_str('idonkey_password');
 142     my $sock = IO::Socket::INET->new(PeerAddr => $host,
 143                                      PeerPort => $port,
 144                                      Proto    => 'tcp');
 145     return 0 unless $sock;
 146     my $password = Irssi::settings_get_str('idonkey_password');
 147     while ($_ = $sock->getline()) {
 148 	s/\e.*?m//g;
 149 	if (/Use \? for help/) {
 150 	    $sock->print("auth ".$password."\n");
 151 	} elsif (/Full access enabled/) {
 152 	    $sock->print("ansi false\n");
 153 	    foreach (1..3) {
 154 		$sock->getline();
 155 	    }
 156 	    return $sock;
 157 	} elsif (/Bad login\/password/) {
 158 	    $sock->close();
 159 	    return 0;
 160 	}
 161     }
 162 }
 163 
 164 sub bg_do ($) {
 165     my ($cmd) = @_;
 166     my ($rh, $wh);
 167     pipe($rh, $wh);
 168     return if $forked > 1;
 169     $forked++;
 170     my $pid = fork();
 171     if ($pid > 0) {
 172         close $wh;
 173         Irssi::pidwait_add($pid);
 174         my $pipetag;
 175         my @args = ($rh, \$pipetag);                                                    $pipetag = Irssi::input_add(fileno($rh), INPUT_READ, \&pipe_input, \@args); 
 176     } else {
 177 	eval {
 178 	    my $result;
 179 	    if ($cmd eq 'downloads') {
 180 		$result->{downloads} = get_downloads();
 181 	    } elsif ($cmd =~ /^(pause|cancel|resume) (.*)/) {
 182 		transfer_command($1, $2);
 183 	    } elsif ($cmd =~ /^results *(.*)/) {
 184 		$result->{results} = get_results($1);
 185 	    } elsif ($cmd eq 'servers') {
 186 		$result->{servers} = get_servers(0);
 187 	    } elsif ($cmd eq 'allservers') {
 188 		$result->{servers} = get_servers(1);
 189 	    } elsif ($cmd eq 'status') {
 190 		if (Irssi::settings_get_bool('idonkey_update_results')) {
 191 		    #$result->{nresults} = scalar @{ get_results('/.*/')->{results} };
 192 		}
 193 		$result->{status} = get_status_info();
 194 	    } elsif ($cmd eq 'ovstats') {
 195 		$result->{ovstats} = get_ovstats();
 196 	    } elsif ($cmd =~ /^settings (.*?)$/) {
 197 		my $regexp = $1;
 198 		$regexp = '.*' unless $regexp;
 199 		$result->{settings} = get_settings($regexp);
 200 	    } elsif ($cmd =~ /^set (.*?) (.*)$/) {
 201 		my ($key, $val) = ($1, $2);
 202 		$result->{change} = change_setting($key, $val);
 203 	    } elsif ($cmd eq 'reshare') {
 204 		$result->{reshare} = reshare();
 205 	    } elsif ($cmd eq 'close_fds') {
 206 		$result->{close_fds} = close_fds();
 207 	    } elsif ($cmd =~ /^sr-search (.*)$/) {
 208 		$result->{sr_search} = sharereactor_search($1);
 209 	    } elsif ($cmd eq "sr-latest") {
 210 		$result->{sr_latest} = sharereactor_latest();
 211 	    } elsif ($cmd =~ /^bt-search (.*)$/) {
 212 		$result->{bt_search} = bittorrent_search($1);
 213 	    } elsif ($cmd =~ /^noupload (.*)$/) {
 214 		$result->{noupload} = no_upload($1);
 215 	    } elsif ($cmd eq 'client-stats') {
 216 		$result->{client_stats} = get_client_stats();
 217 	    } elsif ($cmd eq 'forget') {
 218 		$result->{forget} = forget_searches();
 219 	    } elsif ($cmd =~ /^fake (.*)/) {
 220 		$result->{fake} = check_fake($1);
 221 	    }
 222 	    my $dumper = Data::Dumper->new([$result]);
 223 	    $dumper->Purity(1)->Deepcopy(1);
 224 	    my $text = $dumper->Dump;
 225 	    print($wh $text);
 226 	    #store_fd $result, $wh;
 227 	    close($wh);
 228 	};
 229 	POSIX::_exit(1);
 230     }
 231     
 232 }
 233 
 234 sub pipe_input {
 235     my ($rh, $pipetag) = @{$_[0]};
 236     $forked--;
 237     Irssi::input_remove($$pipetag);
 238     my $text;
 239     $text .= $_ foreach <$rh>;
 240     #print "RETURN";
 241     #print $text;
 242     no strict 'vars';
 243     my $result = eval "$text";
 244     return unless ref $result;
 245     %downloads = %{ $result->{downloads} } if ref $result->{downloads};
 246     $expected = $result->{results}->{waiting} if defined  $result->{results} && $result->{results}->{waiting};
 247     show_client_stats($result->{client_stats}) if ref $result->{client_stats};
 248     list_downloads($result->{downloads}) if ref $result->{downloads};
 249     list_results($result->{results}) if ref $result->{results};
 250     list_servers($result->{servers}) if ref $result->{servers};
 251     show_ovstats($result->{ovstats},0) if ref $result->{ovstats};
 252     show_settings($result->{settings}) if ref $result->{settings};
 253     show_change($result->{change}) if ref $result->{change};
 254     update_status($result->{status}) if ref $result->{status};
 255     show_sr_search($result->{sr_search}) if ref $result->{sr_search};
 256     show_sr_latest($result->{sr_latest}) if ref $result->{sr_latest};
 257     show_bt_search($result->{bt_search}) if ref $result->{bt_search};
 258     store_links($result->{sr_search}) if ref $result->{sr_search};
 259     store_links_bt($result->{bt_search}) if ref $result->{bt_search};
 260     $nresults = $result->{nresults} if $result->{nresults};
 261     
 262     show_fake($result->{fake}) if $result->{fake};
 263     print CLIENTCRAP "%B>>%n Forgot ".$result->{forget}." searche(s)" if exists $result->{forget};
 264     print CLIENTCRAP "%B>>%n Upload disabled for ".$result->{noupload}->[0]." minutes" if exists $result->{noupload};
 265     print CLIENTCRAP "%B>>%n Shares have been checked" if $result->{reshare};
 266     print CLIENTCRAP "%B>>%n Files have been closed" if $result->{close_fds};
 267 }
 268 
 269 sub show_fake ($) {
 270     my ($data) = @_;
 271     my $name = $data->{filename};
 272     my $hash = $data->{hash};
 273     my $text = "%B>>%n '".$name."' [".$hash."] is ";
 274     $text .= 'not ' unless $data->{fake};
 275     $text .= 'a fake';
 276     print CLIENTCRAP $text;
 277 }
 278 
 279 sub store_links ($) {
 280     my ($results) = @_;
 281     %edlinks = ();
 282     foreach (@$results) {
 283 	$edlinks{$_->{name}} = $_->{files};
 284     }
 285 }
 286 
 287 sub store_links_bt ($) {
 288     my ($results) = @_;
 289     %edlinks = ();
 290     foreach (@$results) {
 291         $edlinks{$_->{name}} = [ $_->{torrent} ];
 292     }
 293 }
 294 
 295 
 296 sub check_fake ($) {
 297     my ($num) = @_;
 298     my $results = get_results('/.*/');
 299     my $res;
 300     foreach (@{$results->{results}}) {
 301 	#print $num."  ".$_->{id};
 302 	$res = $_ if $_->{id} eq $num;
 303     }
 304     return undef unless $res;
 305     $res->{fake} = is_fake($res->{hash});
 306     return $res;
 307 }
 308 
 309 sub forget_searches {
 310     my $sock = donkey_connect();
 311     return undef unless $sock;
 312     my $i = 0;
 313     foreach (keys %{ get_queries() }) {
 314 	$sock->print('forget '.$_."\n");
 315 	$i++;
 316     }
 317     $sock->print("q\n");
 318     $sock->close();
 319     return $i;
 320 }
 321 
 322 sub show_client_stats ($) {
 323     my ($data) = @_;
 324     my @table;
 325     foreach (sort keys %$data) {
 326 	my $first = 1;
 327 	foreach my $item (sort keys %{ $data->{$_} }) {
 328 	    next if $item eq 'banned';
 329 	    next if $item eq 'seen';
 330 	    next if $data->{$_}{$item}{num} == 0;
 331 	    my @line;
 332 	    push @line, $first ? "%9".$_."%9" : '';
 333 	    $first = 0;
 334 	    push @line, uc substr($item, 0, 1);
 335 	    push @line, $data->{$_}{$item}{percent}."%";
 336 	    push @line, '#'x(80/100 * $data->{$_}{$item}{percent});
 337 	    push @table, \@line;
 338 	}
 339     }
 340     my $text = array2table(@table);
 341     print CLIENTCRAP &draw_box('iDonkey', $text, 'Client Stats', 1);
 342 }
 343 
 344 sub show_sr_latest ($) {
 345     my ($results) = @_;
 346     my @table;
 347     foreach (@$results) {
 348 	push @table, [$_->{date}, $_->{category}, "%9".$_->{title}."%9"];
 349 	#$text .= $_->{date}." %9".$_->{title}."%9 [".$_->{category}."]\n";
 350     }
 351     my $text = array2table(@table);
 352     print CLIENTCRAP &draw_box('iDonkey', $text, 'Sharereactor latest releases', 1);
 353 }
 354 
 355 sub show_sr_search ($) {
 356     my ($results) = @_;
 357     my $text;
 358     foreach (@$results) {
 359 	$text .= "%9".$_->{name}."%9 [".$_->{category}."]\n";
 360 	foreach (@{ $_->{files} }) {
 361 	    $text .= '-> '.$_."\n";
 362 	}
 363     }
 364     print CLIENTCRAP &draw_box('iDonkey', $text, 'ShareReactor', 1);
 365 }
 366 
 367 sub show_bt_search ($) {
 368     my ($results) = @_;
 369     my $text;
 370     foreach (@$results) {
 371 	$text .= "%9".$_->{name}."%9\n";
 372 	my $url = $_->{torrent};
 373 	$url =~ s/%/%%/g;
 374 	$text .= "-> $url\n";
 375     }
 376     print CLIENTCRAP &draw_box('iDonkey', $text, 'BitTorrent results', 1);
 377 }
 378 
 379 sub show_change (\%) {
 380     my ($change) = @_;
 381     print CLIENTCRAP "%B>>%n [iDonkey] Setting %9".$change->{key}."%9 changed from '".$change->{old}."' to '".$change->{new}."'";
 382 }
 383 
 384 sub show_settings (\%) {
 385     my ($settings) = @_;
 386     my @table;
 387     foreach (sort keys %$settings) {
 388 	push @table, ["%9".$_."%9", $settings->{$_}];
 389     }
 390     my $text = array2table(@table);
 391     print CLIENTCRAP &draw_box('iDonkey', $text, 'settings', 1);
 392 }
 393 
 394 sub show_ovstats (\%$) {
 395     my ($stats, $nodes) = @_;
 396     my $text;
 397     
 398     if ($nodes) {
 399 	$text .= "%9Connected nodes:%9\n";
 400 	$text .= '  '.$_."\n" foreach @{ $stats->{nodes} };
 401 	$text .= "\n";
 402     }
 403     #$text .= @{ $stats->{nodes} }." connected nodes\n\n";
 404     $text .= "%9Search hits:%9 ".$stats->{search_hits}."\n";
 405     $text .= "%9Source hits:%9 ".$stats->{source_hits};
 406     print CLIENTCRAP &draw_box('iDonkey', $text, 'OverNet stats', 1);
 407 }
 408 
 409 sub update_status (\%) {
 410     my ($data) = @_;
 411     return unless ref $data;
 412     $expected = $data->{waiting};
 413     $noul = $data->{noupload};
 414     $credits = $data->{credit};
 415     %downloads = %{ $data->{downloads} };
 416     Irssi::statusbar_items_redraw('idonkey');
 417 }
 418 
 419 sub no_upload ($) {
 420     my ($min) = @_;
 421     my $sock = donkey_connect();
 422     return undef unless $sock;
 423     my $noup;
 424     my $credit;
 425     $sock->print('nu '.$min."\n");
 426     $sock->flush();
 427     $sock->print("vu\n");
 428     while ($_ = $sock->getline()) {
 429 	if (/^Upload credits : (\d+) minutes$/) {
 430 	    $credit = $1;
 431 	} elsif (/^Upload disabled for (\d+)/) {
 432 	    $noup = $1;
 433 	    $sock->close();
 434 	}
 435     }
 436     return [$noup, $credit];
 437 }
 438 
 439 sub get_client_stats {
 440     my %stats;
 441     my $sock = donkey_connect();
 442     return \%stats unless $sock;
 443     $sock->print("client_stats\n");
 444     my $op;
 445     while ($_ = $sock->getline()) {
 446 	if (/Total seens:/) {
 447 	    $op = "seen";
 448 	} elsif (/Total filerequests received:/) {
 449 	    $op = "requests";
 450 	} elsif (/Total downloads:/) {
 451 	    $op = "downloads";
 452 	} elsif (/Total uploads:/) {
 453 	    $op = "uploads";
 454 	} elsif (/Total banneds:/) {
 455 	    $op = "banned";
 456 	} elsif (/^ *(.*?): *(\d+) \((\d+\.\d+) %\)/) {
 457 	    $stats{$1}{$op}{num} = $2;
 458 	    $stats{$1}{$op}{percent} = $3;
 459 	} elsif (/^$/) {
 460 	    $sock->close();
 461 	    last;
 462 	}
 463     }
 464     return \%stats;
 465 }
 466 
 467 sub get_downloads {
 468     my %downloads;
 469     my $sock = donkey_connect();
 470     return \%downloads unless $sock;
 471     $sock->print("vd\n");
 472     my $ready;
 473     my $nfiles;
 474     my @files;
 475     my $sent;
 476     while ($_ = $sock->getline()) {
 477 	my $line = $_;
 478 	#print $line foreach (1..100);
 479 	if (/^Downloaded (\d+)\/(\d+) files/) {
 480 	    $nfiles = $1+$2;
 481 	#} elsif (/^\[(.*?) *(\d+) *?\] +(?:.*?) +([-0-9.]+) +(?:-?\d+) +(?:\d+) +[\d-]+:([\d-]+) +([0-9.-]+|Paused|Queued)/) {
 482 	} elsif (/^\[(.*?) *(\d+) *?\] +(?:.*?) +([-0-9.]+) +(?:-?\d+) +(?:\d+) +(?:\d+) +[\d-]+:([\d-]+) +\d+\/\d+ +([0-9.-]+|Paused|Queued)/) {
 483 	    #print $_;
 484 	    my $id = $2;
 485 	    $downloads{$id}{percent} = $3;
 486 	    $downloads{$id}{available} = ($4 == 0) ? 1 : 0;
 487 	    $downloads{$id}{rate} = $5;
 488 	    push @files, $id;
 489 	} elsif (/^ *\[(.*?) *(\d+) *?\] +(.*?) +(\d+) +[0-9A-Z]{32}/) {
 490 	    my $id = $2;
 491 	    $downloads{$id}{net} = $1;
 492 	    $downloads{$id}{percent} = 100;
 493 	    $downloads{$id}{rate} = "Completed";
 494 	    $downloads{$id}{size} = $4;
 495 	    $downloads{$id}{downloaded} = $4;
 496 	    push @{ $downloads{$id}{names} }, $3;
 497 	    #$sock->print("vd ".$id."\n");
 498 	    push @files, $id;
 499 	} elsif (/\[(.*?) *(\d+) *?\] +(.*?) +(\d+) +(\d+)$/) {
 500 	    $downloads{$sent}{net} = $1;
 501 	    $downloads{$sent}{size} = $4;
 502 	    $downloads{$sent}{downloaded} = $5;
 503 	    push @{ $downloads{$sent}{names} }, $3;
 504 	} elsif (/^    \((.*?)\)$/) {
 505 	    push @{ $downloads{$sent}{names} }, $1;
 506 	} elsif (/^(\d+) sources:/) {
 507 	    $downloads{$sent}{sources} = $1;
 508 	    $sent = undef;
 509 	    #$downloads{$processing}{onlist} = 0;
 510 	} elsif (/^Chunks: \[(\d+)\]/ && not $downloads{$sent}{net} eq 'BitTorrent') {
 511 	    foreach (split(//, $1)) {
 512 		push @{ $downloads{$sent}{chunks} }, $_;
 513 		#print $processing if $processing eq '3';
 514 	    }
 515 	}
 516 	$ready = 1 if (@files == $nfiles);
 517 	#} elsif (/^ *(?:.*?) \(last_ok <(?:.*?)> lasttry <(?:.*?)> nexttry <(?:.*?)> onlist (true|false)\)$/) {
 518 	#    $downloads{$processing}{onlist}++ if $1 eq 'true';
 519 	#}
 520 	if (($nfiles == 0) || defined @files && @files == 0 && not defined $sent) {
 521 	    $sock->close();
 522 	    return \%downloads;
 523 	} else {
 524 	    if ($ready && not defined $sent) {
 525 		$sent = pop @files;
 526 		if (1) {
 527 		    $sock->close();
 528 		    $sock = donkey_connect();
 529 		    #$sock->print("id\n");
 530 		    $sock->print('vd '.$sent."\n");
 531 		    # What a hack :) FIXME in mldonkey
 532 		} else {
 533 		    $sent = undef;
 534 		}
 535 	    }
 536 	}
 537     }
 538 }
 539 
 540 sub transfer_command ($$) {
 541     my ($cmd, $transfer) = @_;
 542     my $sock = donkey_connect();
 543     return undef unless $sock;
 544     my $downloads = get_downloads();
 545     if ($downloads->{$transfer}) {
 546 	$sock->print($cmd." ".$transfer."\n");
 547     } else {
 548 	foreach (keys %$downloads) {
 549 	    foreach my $name (@{$downloads->{$_}{names}}) {
 550 		next unless $name eq $transfer;
 551 		$sock->print($cmd." ".$_."\n");
 552 	    }
 553 	}
 554     }
 555     $sock->close();
 556 }
 557 
 558 sub sharereactor_latest {
 559     my $ua = LWP::UserAgent->new(env_proxy => 1,
 560  				 keep_alive => 1,
 561                                  timeout => 30);
 562     my $response = $ua->get('http://www.sharereactor.com/');
 563     my @releases;
 564     foreach (split /\n/, $response->content() ) {
 565 	if (/^<a href="release\.php\?id=(\d+)">(\d+\.\d+\.\d+) - (.*?)<\/a> <a href="category\.php\?id=\d+">\((.*?)\)<\/a><br>/) {
 566 	    #print "FOO";
 567 	    my $new = { date => $2, id => $1, title => $3, category => $4 };
 568 	    push @releases, $new;
 569 	}
 570     }
 571     return \@releases;
 572 }
 573 
 574 sub sharereactor_search ($) {
 575     my ($query) = @_;
 576     my $enc_query = HTML::Entities::encode($query);
 577     my $ua = LWP::UserAgent->new(env_proxy => 1,
 578  				 keep_alive => 1,
 579                                  timeout => 30);
 580     my $response = $ua->get('http://www.sharereactor.com/search.php?search='.$enc_query.'&category=0');
 581     return unless $response->is_success();
 582     my @results;
 583     foreach (split /\n/, $response->content()) {
 584         if (/<a href="release\.php\?id=(\d+)">(.*?)<\/a>/) {
 585             push @results, { name => $2, id => $1 };
 586 
 587 	    my $ua2 = LWP::UserAgent->new(env_proxy => 1,
 588 			   		  keep_alive => 1,
 589 					  timeout => 30);
 590             my $response2 = $ua2->get('http://www.sharereactor.com/downloadrelease.php?id='.$1);
 591             foreach (split /\n/, $response2->content()) {
 592                 if (/"(ed2k:\/\/\|file\|.*?\|\d+\|.*?\|)";/) {
 593                     push @{ $results[-1]->{files} }, $1;
 594                 }
 595             }
 596         } elsif (/<a href="category\.php\?id=\d+">(.*?)<\/a>/) {
 597             $results[-1]->{category} = $1;
 598         }
 599     }
 600     #print $_->{name}." ".$_->{id}."\n" foreach @results;
 601     return \@results;
 602 }
 603 
 604 sub bittorrent_search ($) {
 605     my ($query) = @_;
 606     my $enc_query = HTML::Entities::encode($query);
 607     my $ua = LWP::UserAgent->new(env_proxy => 1,
 608                                  keep_alive => 1,
 609                                  timeout => 30);
 610     my $response = $ua->get('http://www.bytemonsoon.com/?search='.$enc_query.'&cat=0&incldead=0');
 611     return unless $response->is_success();
 612     my @results;
 613     foreach (split /\n/, $response->content()) {
 614 	if (/^<td><a href="details\.php\?id=(\d+)&amp;hit=1"><b>(.*?)<\/b><\/a><\/td>$/) {
 615 	    push @results, { name => $2, id => $1 };
 616 	} elsif (/^<td align="center"><a href="(.*?)">torrent<\/a><\/td>$/) {
 617 	    $results[-1]->{torrent} = "http://www.bytemonsoon.com/$1";
 618 	}
 619     }
 620     return \@results
 621 }
 622 
 623 sub get_ovstats {
 624     my $sock = donkey_connect();
 625     return unless $sock;
 626     $sock->print("ovstats\n");
 627     my $result; 
 628     $result->{nodes} = [];
 629     while ($_ = $sock->getline()) {
 630 	if (/^ +(\d+\.\d+\.\d+.\d+:\d+)$/) {
 631 	    push @{ $result->{nodes} }, $1;
 632 	} elsif (/^  Search hits: (\d+)$/) {
 633 	    $result->{search_hits} = $1;
 634 	} elsif (/^  Source hits: (\d+)$/) {
 635 	    $result->{source_hits} = $1;
 636 	} elsif (/^$/) {
 637 	    last;
 638 	}
 639     }
 640     return $result;
 641 }
 642 
 643 sub is_fake ($) {
 644     my ($hash) = @_;
 645     my $ua = LWP::UserAgent->new(env_proxy => 1,
 646  				 keep_alive => 1,
 647                                  timeout => 30);
 648     my $url = 'http://edonkeyfakes.ath.cx/fakecheck/update/fakecheck.php';
 649     my %form = ( hash => $hash );
 650     my $response = $ua->post($url, \%form);
 651     return unless $response->is_success();
 652     return not ($response->content() =~ /Your query didn't match anything in our fakedatabase\!/);
 653 }
 654 
 655 sub get_settings ($) {
 656     my ($regexp) = @_;
 657     my $sock = donkey_connect();
 658     return unless $sock;
 659     $sock->print("voo\n");
 660     my $result = {};
 661     while ($_ = $sock->getline()) {
 662 	if (/^(.*?) = (.*?)$/) {
 663 	    my ($key, $val) = ($1, $2);
 664 	    #print "<".$regexp.">";
 665 	    next unless ($key =~ /$regexp/i);
 666 	    $result->{$key} = $val;
 667 	} else {
 668 	    $sock->close();
 669 	    return $result;
 670 	}
 671     }
 672 }
 673 
 674 sub reshare {
 675     my $sock = donkey_connect();
 676     return 0 unless $sock;
 677     $sock->print("reshare\n");
 678     $sock->close();
 679     return 1;
 680 }
 681 
 682 sub close_fds {
 683     my $sock = donkey_connect();
 684     return 0 unless $sock;
 685     $sock->print("close_fds\n");
 686     $sock->close();
 687     return 1;
 688 }
 689 
 690 sub change_setting ($$) {
 691     my ($key, $val) = @_;
 692     my $result;
 693     $result->{key} = $key;
 694     $result->{old} = get_settings($key)->{$key};
 695     my $sock = donkey_connect();
 696     return unless $sock;
 697     $sock->print("set ".$key." ".$val."\n");
 698     $sock->close();
 699     #$result->{new} = $val;
 700     $result->{new} = get_settings($key)->{$key};
 701     return $result;
 702 }
 703 
 704 sub get_servers ($) {
 705     my ($all) = @_;
 706     my $sock = donkey_connect();
 707     return unless $sock;
 708     if ($all) {
 709 	$sock->print("vma\n");
 710     } else {
 711 	$sock->print("vm\n");
 712     }
 713     my $result;
 714     while ($_ = $sock->getline()) {
 715 	#if (/^\[(.*?) (\d+) *\] ([0-9.]+):(\d+) + (.*?) + (\d+) +(\d+) (Connected)?$/) {
 716 	if (/^\[(.*?) (\d+) *\] (.+):(\d+) + (.*?) + (\d+) +(\d+) (Connected)?$/) {
 717 	    my $server = { net     => $1,
 718 		           id      => $2,
 719 	                   ip      => $3,
 720 			   port    => $4,
 721 			   comment => $5,
 722 			   users   => $6,
 723 			   files   => $7
 724 			  };
 725 	    $result->{$2} = $server;
 726 	} elsif (/^ *$/) {
 727 	    $sock->close();
 728 	    return $result;
 729 	}
 730     }
 731 }
 732 
 733 sub search_file ($) {
 734     my ($query) = @_;
 735     my $sock = donkey_connect();
 736     return unless $sock;
 737     $sock->print("s ".$query."\n");
 738     while ($_ = $sock->getline()) {
 739 	if (/Query \d+ Sent to \d+/) {
 740 	    $sock->close;
 741 	    return 1;
 742 	} elsif (/exception/) {
 743 	    $sock->close;
 744 	    return 0;
 745 	}
 746     }
 747 }
 748 
 749 sub list_downloads ($) {
 750     my ($data) = @_;
 751     my $text = downloads2text($data, '/.*/'); #downloads_list($data, '/.*/');
 752     print CLIENTCRAP &draw_box('iDonkey', $text, 'Downloads', 1);
 753 }
 754 
 755 sub get_best_name ($) {
 756     my ($names) = @_;
 757     my $result;
 758     foreach (@$names) {
 759 	# It's a hash
 760 	$result = $_;
 761 	last unless /[A-Z0-9]{32}/;
 762     }
 763     return $result;
 764 }
 765 
 766 sub downloads2text ($$) {
 767     my ($downloads, $regexp) = @_;
 768     my $length = Irssi::settings_get_int('idonkey_max_filename_length');
 769     my $text;
 770     my @table;
 771     my @chunks;
 772     my @names;
 773     my ($speed, $downloaded, $size) = (0,0,0);
 774     foreach (sort {get_best_name($downloads->{$a}{names}) cmp get_best_name($downloads->{$b}{names})} keys %$downloads) {
 775 	my @line;
 776 	my $filename = get_best_name($downloads->{$_}{names});
 777 	my $name = shorten_filename($filename, $length);
 778 	$name =~ s/%/%%/g;
 779 	my $download;
 780 	# Color codes:
 781 	#  Yellow	Paused
 782 	#  Bold green	Completed
 783 	#  Green	Downloading & 100% available
 784 	#  Blue		Downloading, but not completly on network
 785 	if ($downloads->{$_}{rate} =~ /Paused|Queued/) {
 786 	    $download .= '%yo%n';
 787 	} elsif ($downloads->{$_}{rate} eq 'Completed') {
 788 	    $download .= '%Go%n';
 789 	} else {
 790 	    if ($downloads->{$_}{available}) {
 791 		$download .= '%go%n';
 792 	    } else {
 793 		$download .= '%bo%n';
 794 	    }
 795 	    $speed += $downloads->{$_}{rate};
 796 	}
 797 	$size += $downloads->{$_}{size};
 798 	$downloaded += $downloads->{$_}{downloaded};
 799 	$download .= ' %9'.$name.'%9 ('.$_.')';
 800 	push @names, $download;
 801 	#$text .= "\n" if 1;
 802 	push @line, round($downloads->{$_}{downloaded}, $downloads->{$_}{size});
 803 	#$text .= '     '.round($downloads->{$_}{downloaded}, $downloads->{$_}{size})."/";
 804 	push @line, round($downloads->{$_}{size},$downloads->{$_}{size});
 805 	push @line, '('.$downloads->{$_}{percent}.'%%)';
 806 	#$text .= round($downloads->{$_}{size},$downloads->{$_}{size})." (".$downloads->{$_}{percent}."%%)";
 807 	if ($downloads->{$_}{rate} =~ /^[0-9.]+$/) {
 808 	    push @line, $downloads->{$_}{rate}." kb/s";
 809 	} elsif ($downloads->{$_}{rate} eq '-') {
 810 	    push @line, "0 kb/s";
 811 	} else {
 812 	    push @line, $downloads->{$_}{rate};
 813 	}
 814 	#push @line, ' ['.$downloads->{$_}{sources}.'/'.$downloads->{$_}{onlist}.' @'.$downloads->{$_}{net}.']' if (defined $downloads->{$_}{sources});
 815 	my $netload = '[';
 816 	$netload .= $downloads->{$_}{sources}."@";
 817 	$netload .= $downloads->{$_}{net}.']';
 818 	push @line, $netload;
 819 	push @line, .$downloads->{$_}{tag};
 820 	#$text .= "\n";
 821 	if (1 || $downloads->{$_}{chunks}) {
 822 	    if (ref $downloads->{$_}{chunks} && @{$downloads->{$_}{chunks}} > 1) {
 823 		my $chunk;
 824 		$chunk .= '[';
 825 		foreach (@{$downloads->{$_}{chunks}}) {
 826 		    if ($_ > 1) {
 827 			$chunk .= '%g|%n';
 828 		    } elsif ($_ == 1) {
 829 			$chunk .= '%b:%n';
 830 		    } else {
 831 			$chunk .= '%r.%n';
 832 		    }
 833 		}
 834 		$chunk .= "]";
 835 		push @chunks, $chunk;
 836 	    } else {
 837 		push @chunks, "";
 838 	    }
 839 	}
 840 	push @table, \@line;
 841     }
 842     foreach (split /\n/, array2table(@table)) {
 843 	$text .= (shift @names)."\n";
 844 	$text .= "     ".$_."\n";
 845 	if (Irssi::settings_get_bool('idonkey_show_chunks')) {
 846 	    my $chunk = shift @chunks;
 847 	    $text .= "   ".$chunk."\n" if $chunk;
 848 	}
 849     }
 850     my $percent = $size > 0 ? ($downloaded / $size)*100 : 0; 
 851     $percent = $1 if ($percent =~ /(\d+\.\d{1}).*?/);
 852     if (keys %$downloads > 1) {
 853 	$text .= "".'%9Total:%9 ';
 854 	$text .= round($downloaded, $size).'/';
 855 	$text .= round($size, $size);
 856 	$text .= ' ('.$percent.'%%), '.$speed.' kb/s';
 857     }
 858     return $text;
 859 }
 860 
 861 sub round ($$) {
 862     return $_[0] unless Irssi::settings_get_bool('idonkey_round_filesize');
 863     if ($_[1] > 100000) {
 864 	return sprintf "%.2fMB", $_[0]/1024/1024;
 865     } else {
 866 	return sprintf "%.2fKB", $_[0]/1024;
 867     }
 868 }
 869 
 870 sub get_queries {
 871     my $sock = donkey_connect();
 872     # FIXME A real parser here?
 873     return undef unless $sock;
 874     $sock->print("vs\n");
 875     my %result;
 876     my $num;
 877     while ($_ = readline($sock)) {
 878 	chop;
 879 	if (/^Searching (\d+) queries$/) {
 880 	    $num = $1;
 881 	} elsif (/^\[(\d+) *\](.*) .*?$/) {
 882 	    my $id = $1;
 883 	    my $regexp = $2;
 884 	    my @token = $regexp =~ /CONTAINS\[(.*?)\]/g;
 885 	    $result{$id} = \@token;
 886 	    $num--;
 887 	}
 888 	last if (defined $num && $num == 0);
 889     }
 890     return \%result;
 891 }
 892 
 893 sub get_results ($) {
 894     my ($filter) = @_;
 895     my $sock = donkey_connect();
 896     my $net = '.*';
 897     if ($filter =~ /-net (.*?)(?: |$)/) {
 898 	$net = $1;
 899     }
 900     #my $regexp = '.*';
 901     my @filters;
 902     while  ($filter =~ /(\!?)\/(.*?)\//g) {
 903 	my %entry = ( "reverse" => $1 ? 1 : 0,
 904 	              "regexp"  => $2
 905 		      );
 906 	push @filters, \%entry;
 907     }
 908     my $result;
 909     my @results;
 910     my $waiting = 0;
 911     my $filtered = 0;
 912     return undef unless $sock;
 913     my $num = 0;
 914     $sock->print("vr\n");
 915     my @token;
 916     while ($_ = readline($sock)) {
 917 	chop;
 918 	if (/^Result of search (\d+)$/) {
 919 	    my $searches = get_queries();
 920 	    @token = @{ $searches->{$1} } if ref $searches->{$1};
 921 	} elsif (/^(\d+) results \((?:done|(-?\d+) waiting)\)$/) {
 922 	    $num = $1;
 923 	    $waiting = $2 if $2;
 924 	    unless ($num) {
 925 		$sock->close();
 926 		last();
 927 	    }
 928         } elsif (/^\[ *(\d+)\] (.*?(?: Napster)?) (.*)/) {
 929 	    # FIXME Find a better Solution for open Napster
 930 	    my %data = ( id=> $1, filename => $3, visible => 1, net => $2);
 931 	    
 932 	    $data{visible} = 1 unless @filters;
 933 	    foreach my $entry (@filters) {
 934 		next unless $data{visible};
 935 		my $regexp = $entry->{regexp};
 936 		my $reverse = $entry->{reverse};
 937 		if (not $reverse) {
 938 		    $data{visible} = 0 if not ($data{filename} =~ /$regexp/i);
 939 		} else {
 940 		    $data{visible} = 0 if ($data{filename} =~ /$regexp/i);
 941 		}
 942 	    }
 943 	    if (Irssi::settings_get_bool('idonkey_filter_search_results') && @token) {
 944 		foreach (@token) {
 945 	    	    $data{visible} = 0 unless $data{filename} =~ /$_/i;
 946 		    last unless $data{visible};
 947 		}
 948 		$data{visible} = 0 unless $data{net} =~ /$net/i;
 949 	    }
 950             if (Irssi::settings_get_bool('idonkey_filter_nameless_results')) {
 951 		    $data{visible} = 0 unless $data{filename};
 952             }
 953 	    push @results, \%data;
 954 	} elsif (/^ ALREADY DOWNLOADED$/) {
 955 	    $results[-1]->{downloaded} = 1;
 956         } elsif (/^ +(-?\d+) ([0-9A-Z]{32}) (\d+)?/) {
 957 	    $results[-1]->{size} = $1;
 958 	    $results[-1]->{hash} = $2;
 959 	    $results[-1]->{sources} = $3;
 960 	    unless ($results[-1]->{visible}) {
 961 		pop @results;
 962 		$filtered++;
 963 	    } else {
 964 		#$results[-1]->{fake} = is_fake($results[-1]->{hash});
 965 	    }
 966 	} elsif (/No search to print/ || /^$/ || /^exception/) {
 967 	    $sock->close();
 968 	    last();
 969 	}
 970     }
 971     my $sortby = Irssi::settings_get_str('idonkey_sort_results_by');
 972     @results = sort {uc($a->{$sortby}) <=> uc($b->{$sortby})} @results; 
 973     $result->{results} = \@results;
 974     $result->{waiting} = $waiting;
 975     $result->{filtered} = $filtered;
 976     return $result;
 977 }
 978 
 979 sub list_servers ($) {
 980     my ($data) = @_;
 981     my @text;
 982     foreach (sort { $data->{$a}{id} <=> $data->{$b}{id} } keys %$data) {
 983 	push @text, ["%9".$data->{$_}{id}."%9", $data->{$_}{net}, $data->{$_}{ip}.':'.$data->{$_}{port}, $data->{$_}{users}, $data->{$_}{files}, $data->{$_}{comment}];
 984     }
 985     unshift @text, ["%9ID%9", "%9net%9", "%9address%9", "%9users%9", "%9files%9", "%9comment%9"] if @text;
 986     print CLIENTCRAP &draw_box('iDonkey', array2table(@text), 'servers', 1);
 987 }
 988 
 989 sub list_results ($) {
 990     my ($data) = @_;
 991     my $results = $data->{results};
 992     my @text;
 993     $seen = $nresults;
 994     my $length = Irssi::settings_get_int('idonkey_max_filename_length');
 995     foreach (@$results) {
 996 	my @line;
 997 	next unless $_->{visible};
 998 	my $file = shorten_filename($_->{filename}, $length);
 999 	$file =~ s/%/%%/g;
1000 	push @line, '%9'.$_->{id}.'%9';
1001 	push @line, '%9'.$file.'%9';
1002 	push @line, $_->{fake} ? '%RF%n' : ''; 
1003 	push @line, $_->{downloaded} ? '%GD%n' : ''; 
1004 	push @line, $_->{net} if defined $_->{net};
1005 	push @line, '['.$_->{sources}.']';
1006 	push @line, round($_->{size}, $_->{size});
1007 	
1008 	push @text, \@line;
1009     }
1010     my $footer = 'Results';
1011     $footer .= ' ('.$data->{filtered}.' filtered)' if $data->{filtered} > 0;
1012     $footer .= ' ('.$data->{waiting}.' waiting)' if $data->{waiting} > 0;
1013     print CLIENTCRAP &draw_box('iDonkey', array2table(@text), $footer, 1);
1014 }
1015 
1016 sub get_file ($$) {
1017     my ($file, $force) = @_;
1018     my $sock = donkey_connect();
1019     return unless $sock;
1020     $sock->print("d ".$file."\n");
1021     while ($_ = $sock->getline()) {
1022 	if (/download started/) {
1023 	    $sock->close();
1024 	    return 1;
1025 	} elsif (/(File already downloaded|could not start download)/) {
1026 	    if ($force) {
1027 		$sock->print("force_download\n");
1028 		$sock->close();
1029 		return 1
1030 	    } else {
1031 		$sock->close();
1032 		return 0;
1033 	    }
1034 	}
1035     }
1036 }
1037 
1038 sub download_link ($$) {
1039     my ($url, $force) = @_;
1040     my $sock = donkey_connect();
1041     return unless $sock;
1042     $sock->print("dllink ".$url."\n");
1043     $sock->print("force_download\n") if $force;
1044     while ($_ = $sock->getline()) {
1045 	if (/download (started|forced)|Done/) {
1046 	    $sock->close();
1047 	    return 1;
1048 	} elsif (/Unable|bad syntax|exception/ && not $force) {
1049 	    $sock->close();
1050 	    return 0;
1051 	}
1052     }
1053 }
1054 
1055 sub connect_servers ($$) {
1056     my ($ids, $disconnect) = @_;
1057     my $sock = donkey_connect();
1058     return unless $sock;
1059     $sock->print("c\n") unless (@$ids);
1060     foreach (@$ids) {
1061 	if ($disconnect) {
1062 	    $sock->print("x ".$_."\n");
1063 	} else {
1064 	    $sock->print("c ".$_."\n");
1065 	}
1066     }
1067     $sock->close();
1068 }
1069 
1070 sub quit_donkey {
1071     my $sock = donkey_connect();
1072     return unless $sock;
1073     $sock->print("kill\n");
1074     $sock->close();
1075     return 1;
1076 }
1077 
1078 sub commit_downloads {
1079     my $sock = donkey_connect();
1080     return unless $sock;
1081     $sock->print("commit\n");
1082     $sock->close();
1083     until ($_ = $sock->getline()) {
1084 	#if (/commited/) {
1085 	    return 1;
1086 	#} else {
1087 	#    return 0;
1088 	#}
1089     }
1090 }
1091 
1092 sub get_status_info {
1093     my $result;
1094     $result->{downloads} = get_downloads();
1095     $result->{waiting} = get_results('/.*/')->{waiting};
1096     my $upload = no_upload(0);
1097     $result->{credit} = $upload->[1];
1098     $result->{noupload} = $upload->[0];
1099     return $result;
1100 }
1101 
1102 sub cmd_idonkey ($$$) {
1103     my ($args, $server, $witem) = @_;
1104     my @arg = split(/ /, $args);
1105     if (@arg == 0 || $arg[0] eq 'downloads') {
1106 	#list_downloads();
1107 	bg_do('downloads');
1108     } elsif ($arg[0] eq 'help') {
1109 	show_help();
1110     } elsif ($arg[0] =~ /pause|resume|cancel/ && defined $arg[1]) {
1111 	bg_do(join(' ', @arg));
1112     } elsif ($arg[0] =~ /results/) {
1113 	bg_do(join(' ', @arg));
1114     } elsif ($arg[0] eq 'search') {
1115 	shift @arg;
1116 	if (search_file(join(' ', @arg))) {
1117 	    $seen = 0;
1118 	    $nresults = 0;
1119 	    print CLIENTCRAP "%R>>%n Query '".join(' ', @arg)."' sent to network...";
1120 	}
1121     } elsif ($arg[0] eq 'get' && defined $arg[1]) {
1122 	shift @arg;
1123 	my $force = 0;
1124 	foreach my $id (@arg) {
1125 	    if ($id eq 'force') {
1126 		$force = 1;
1127 	    } elsif (get_file($id, $force)) {
1128 		print CLIENTCRAP "%R>>%n Download of file ".$id." started";
1129 	    } else {
1130 		print CLIENTCRAP "%R>>%n Download of file ".$id." failed";
1131 	    }
1132 	}
1133     } elsif ($arg[0] eq 'dllink' && defined $arg[1]) {
1134 	shift @arg;
1135 	join(' ', @arg) =~ /(force )?(.*)/;
1136 	my $force = defined $1 ? 1 : 0;
1137 	if (download_link($2, $1)) {
1138 	    print CLIENTCRAP "%R>>%n Download of ".$2." started";
1139 	} else {
1140 	    print CLIENTCRAP "%R>>%n Download of ".$2." failed";
1141 	}
1142     } elsif ($arg[0] eq 'commit') {
1143 	if (commit_downloads()) {
1144 	    print CLIENTCRAP "%R>>%n Completed downloads saved";
1145 	} else {
1146 	    print CLIENTCRAP "%R>>%n Saving completed downloads failed";
1147 	}
1148     } elsif ($arg[0] eq 'launch') {
1149 	my $cmd = Irssi::settings_get_str('idonkey_mldonkey_cmd');
1150 	system($cmd);
1151 	print CLIENTCRAP "%R>>%n MLDonkey launched";
1152     } elsif ($arg[0] eq 'quit') {
1153 	if ( quit_donkey() ) {
1154 	    print CLIENTCRAP "%R>>%n MLDonkey killed";
1155 	} else {
1156 	    print CLIENTCRAP "%R>>%n Unable to kill MLDonkey";
1157 	}
1158     } elsif ($arg[0] eq 'servers') {
1159 	shift @arg;
1160 	if ( (not @arg) || ($arg[0] eq 'connected') ) {
1161 	    bg_do('servers');
1162 	} elsif ($arg[0] eq 'disconnect') {
1163 	    shift @arg;
1164 	    connect_servers(\@arg, 1);
1165 	} elsif ($arg[0] eq 'connect') {
1166 	    shift @arg;
1167 	    connect_servers(\@arg, 0);
1168 	} elsif ($arg[0] eq 'all') {
1169 	    bg_do('allservers');
1170 	}
1171     } elsif ($arg[0] eq 'overnet') {
1172 	shift @arg;
1173 	if ( (not @arg) || ($arg[0] eq 'stats') ) {
1174 	    bg_do('ovstats');
1175 	} elsif ($arg[0] eq 'nodes') {
1176 	    bg_do('ovnodes');
1177 	}
1178     } elsif ($arg[0] eq 'settings') {
1179 	shift @arg;
1180 	if ( (not @arg) ) {
1181 	    # Do something
1182 	} elsif ($arg[0] eq 'show') {
1183 	    shift @arg;
1184 	    bg_do('settings '.join(' ',@arg));
1185 	} elsif ($arg[0] eq 'change') {
1186 	    shift @arg;
1187 	    return unless (defined $arg[0] && defined $arg[1]);
1188 	    my $key = shift @arg;
1189 	    my $val = join(' ', @arg);
1190 	    bg_do('set '.$key.' '.$val)
1191 	}
1192     } elsif ($arg[0] eq 'shares') {
1193 	shift @arg;
1194 	if ( (not @arg) ) {
1195 	    ## list shares?
1196 	} elsif ($arg[0] eq 'reshare') {
1197 	    bg_do('reshare');
1198 	} elsif ($arg[0] eq 'close') {
1199 	    bg_do('close_fds');
1200 	}
1201     } elsif ($arg[0] eq 'sharereactor') {
1202 	shift @arg;
1203 	if ( (not @arg) || $arg[0] eq 'latest') {
1204 	    print CLIENTCRAP "%B>>%n Retrieving latest releases...";
1205 	    bg_do('sr-latest');
1206 	} elsif ($arg[0] eq 'search') {
1207 	    shift @arg;
1208 	    bg_do('sr-search '.join(" ", @arg));
1209 	    print CLIENTCRAP "%B>>%n Searching ShareReactor for '".join(" ", @arg)."'";
1210 	} elsif ($arg[0] eq 'download' && defined $arg[1]) {
1211 	    shift @arg;
1212 	    download_sr(join(" ", @arg));
1213 	}
1214     } elsif ($arg[0] eq 'bittorrent') {
1215 	shift @arg;
1216 	if ($arg[0] eq 'search') {
1217 	    shift @arg;
1218 	    bg_do('bt-search '.join(" ", @arg));
1219 	    print CLIENTCRAP "%B>>%n Searching BitTorrent for '".join(" ", @arg)."'";
1220 	}
1221     } elsif ($arg[0] eq 'noupload') {
1222 	shift @arg;
1223 	if (@arg && $arg[0] =~ /-?\d+/) {
1224 	    bg_do('noupload '.$arg[0]);
1225 	}
1226     } elsif ($arg[0] eq 'client-stats') {
1227 	bg_do('client-stats');
1228     } elsif ($arg[0] eq 'forget') {
1229 	bg_do('forget');
1230     } elsif ($arg[0] eq 'fake' && defined $arg[1]) {
1231 	shift @arg;
1232 	foreach (@arg) {
1233 	    next unless /\d+/;
1234 	    bg_do('fake '.$_);
1235 	}
1236     }
1237 }
1238 
1239 sub download_sr ($) {
1240     my ($download) = @_;
1241     if (defined $edlinks{$download}) {
1242 	foreach my $link (@{ $edlinks{$download} }) {
1243 	    if (download_link($link,0)) {
1244 		print CLIENTCRAP "%R>>%n Download of ".$link." started";
1245 	    } else {
1246 		print CLIENTCRAP "%R>>%n Download of ".$link." failed";
1247 	    }
1248 	}
1249     } else {
1250 	print CLIENTCRAP "%B>>%n Unknown release, try searching for it.";
1251     }
1252 }
1253 
1254 
1255 sub shorten_filename ($$) {
1256     my ($file, $length) = @_;
1257     unless ($length == 0) {
1258 	my $post = 4;
1259 	my $pre = $length-5-$post;
1260 	$file =~ s/^(.{$pre}).*(.{$post})/$1\[\.\.\.\]$2/;
1261     }
1262     return $file;
1263 }
1264 
1265 sub filename_percent ($$) {
1266     my ($name, $percent) = @_;
1267     my $length = length($name);
1268     my $done = $length * ($percent/100);
1269     my $string = '%g%U'.substr($name, 0, $done).'%U%n%y'.substr($name, $done, $length).'%n';
1270     return $string;
1271 }
1272 
1273 sub sb_idonkey ($$) {
1274     my ($item, $get_size_only) = @_;
1275     my $line;
1276     $line .= $nresults."|" if ($seen != $nresults && defined $seen);
1277     $line .= '%F'.$expected."%F|" if $expected > 0;
1278     $line .= $noul."min|" if $noul > 0;
1279     #my $length = Irssi::settings_get_int('idonkey_max_filename_length');
1280     my $length = Irssi::settings_get_int('idonkey_statusbar_max_filename_length');
1281     my $i = 0;
1282     foreach (sort keys %downloads) {
1283 	$index = 0 if $index > (scalar keys %downloads)-1;
1284 	unless ($i == $index) {
1285 	    $i++;
1286 	    next;
1287 	}
1288 	unless (Irssi::settings_get_bool('idonkey_statusbar_show_paused')) {
1289 	    if ($downloads{$_}{rate} eq 'Paused') {
1290 		$index++;
1291 		next;
1292 	    }
1293 	}
1294 	my $filename = get_best_name($downloads{$_}{names});
1295 	my $file = shorten_filename($filename, $length);
1296 	$line .= filename_percent($file, $downloads{$_}{percent});
1297 	$line .= ' '.$downloads{$_}{percent}.'%% ';
1298 	unless ($downloads{$_}{rate} eq '-') {
1299 	    $line .= $downloads{$_}{rate};
1300 	    $line .= ' kb/s' if $downloads{$_}{rate} =~ /^[0-9.]+$/;
1301 	}
1302 	$line .= ' ';
1303 	$i++;
1304     }
1305     $line =~ s/ $//;
1306     my $format = "{sb ".$line."}";
1307     $item->{min_size} = $item->{max_size} = length($line);
1308     $item->default_handler($get_size_only, $format, 0, 1);
1309 }
1310 
1311 sub call_for_status {
1312     bg_do('status');
1313 }
1314 
1315 sub sig_complete_word ($$$$$) {
1316     my ($list, $window, $word, $linestart, $want_space) = @_;
1317     if ($linestart =~ /^.idonkey (pause|resume|cancel)/) {
1318 	foreach (sort {get_best_name($downloads{$a}{names}) cmp get_best_name($downloads{$b}{names})} keys %downloads) {
1319 	    my $name = get_best_name($downloads{$_}{names});
1320 	    if ( ($1 eq 'resume' && $downloads{$_}{rate} eq 'Paused') ||
1321 		($1 eq 'pause'  && not $downloads{$_}{rate} eq 'Paused') ||
1322 		($1 eq 'cancel') ) {
1323 		push @$list, $name if $name =~ /^(\Q$word\E.*)?$/i;
1324 	    }
1325 	}
1326 	Irssi::signal_stop();
1327     } elsif ($linestart =~ /^.idonkey search/) {
1328 	my @opts = ('minsize', 'maxsize', 'media', 'Video', 'Audio', 'format', 'title', 'album', 'artist', 'field', 'not', 'and', 'or');
1329 	foreach (@opts) {
1330 	    $_ = '-'.$_;
1331 	    push @$list, $_ if /^(\Q$word\E.*)?$/i;
1332 	}
1333 	Irssi::signal_stop();
1334     } elsif ($linestart =~ /^.idonkey sharereactor download/) {
1335 	foreach (sort keys %edlinks) {
1336 	    push @$list, $_ if /^(\Q$word\E.*)?$/i;
1337 	}
1338 	Irssi::signal_stop();
1339     } elsif ($linestart =~ /^.idonkey dllink/) {
1340 	foreach (sort keys %edlinks) {
1341 	    foreach my $link (@{ $edlinks{$_} }) {
1342 		push @$list, $link if $link =~ /^(\Q$word\E.*)?$/i;
1343 	    }
1344 	}
1345 	Irssi::signal_stop();
1346     } elsif ($linestart =~ /^.idonkey results/) {
1347 	my @opts = ('net');
1348 	foreach (@opts) {
1349 	    $_ = '-'.$_;
1350 	    push @$list, $_ if /^(\Q$word\E.*)?$/i;
1351 	}
1352     }
1353 }
1354 
1355 sub next_status {
1356     $index++;
1357     $index = 0 if $index > (scalar keys %downloads)-1;
1358     Irssi::statusbar_items_redraw('idonkey');
1359 }
1360 
1361 sub install_timer {
1362     return if defined $timer;
1363     my $timeout = Irssi::settings_get_int('idonkey_statusbar_interval');
1364     my $timeout2 = Irssi::settings_get_int('idonkey_update_interval');
1365     return unless $timeout && $timeout2;
1366     $timer = Irssi::timeout_add($timeout*1000, \&next_status, undef);
1367     $timer2 = Irssi::timeout_add($timeout2*1000, \&call_for_status, undef);
1368 }
1369 
1370 sub uninstall_timer {
1371     return unless defined $timer;
1372     Irssi::timeout_remove($timer);
1373     Irssi::timeout_remove($timer2);
1374     $timer = undef;
1375 }
1376 
1377 Irssi::command_bind('idonkey', \&cmd_idonkey);
1378 foreach my $cmd ('downloads', 'pause', 'resume', 'results', 'search', 'get', 'get force', 'cancel', 'help', 'commit', 'dllink', 'dllink force', 'launch', 'quit', 'servers', 'servers disconnect', 'servers connected', 'servers all', 'servers connect', 'overnet', 'overnet stats', 'overnet nodes', 'settings', 'settings show', 'settings change', 'shares', 'shares reshare', 'shares close', 'sharereactor', 'sharereactor search', 'sharereactor download', 'sharereactor latest', 'noupload', 'client-stats', 'forget', 'fake', 'bittorrent', 'bittorrent search') {
1379     Irssi::command_bind('idonkey '.$cmd => sub {
1380         cmd_idonkey("$cmd ".$_[0], $_[1], $_[2]); });
1381 }
1382 Irssi::signal_add_first('complete word', \&sig_complete_word);
1383 
1384 Irssi::settings_add_str($IRSSI{name}, 'idonkey_password', '');
1385 Irssi::settings_add_str($IRSSI{name}, 'idonkey_host', 'localhost');
1386 Irssi::settings_add_int($IRSSI{name}, 'idonkey_port', 4000);
1387 Irssi::settings_add_int($IRSSI{name}, 'idonkey_max_filename_length', 65);
1388 # sources, filename, id, size
1389 Irssi::settings_add_str($IRSSI{name}, 'idonkey_sort_results_by', "id");
1390 Irssi::settings_add_bool($IRSSI{name}, 'idonkey_round_filesize', 1);
1391 
1392 
1393 Irssi::settings_add_bool($IRSSI{name}, 'idonkey_filter_nameless_results', 1);
1394 Irssi::settings_add_bool($IRSSI{name}, 'idonkey_filter_search_results', 0);
1395 Irssi::settings_add_bool($IRSSI{name}, 'idonkey_show_chunks', 1);
1396 
1397 Irssi::settings_add_str($IRSSI{name}, 'idonkey_mldonkey_cmd', 'screen mldonkey');
1398 Irssi::settings_add_int($IRSSI{name}, 'idonkey_statusbar_interval', 0);
1399 Irssi::settings_add_bool($IRSSI{name}, 'idonkey_statusbar_show_paused', 1);
1400 Irssi::settings_add_int($IRSSI{name}, 'idonkey_update_interval', 0);
1401 Irssi::settings_add_bool($IRSSI{name}, 'idonkey_update_results', 1);
1402 Irssi::settings_add_int($IRSSI{name}, 'idonkey_statusbar_max_filename_length', 25);
1403 
1404 Irssi::statusbar_item_register('idonkey', 0, "sb_idonkey");
1405 
1406 install_timer();
1407 
1408 print CLIENTCRAP '%B>>%n '.$IRSSI{name}.' '.$VERSION.' loaded, /idonkey help';