#! /usr/bin/env perl -w ## Licence and stuff: # autoget.pl for irssi irc client. # # Copyright (C) 2002 by Sascha Lüdecke AKA Moxon # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. my $version = '0.7.22'; # $Date: 2002/05/04 12:43:51 $ # $Id: autoget.pl,v 1.14 2002/05/04 12:43:51 chat Exp $ # ---------------------------------------------------------------------- ## Quickstart (read '/autoget help' and Description below) # -1. Install recent irssi (CVS version) # 0. add autoget = { }; to your irssi config at statusbar->default->window->items # set dcc_autoget to ON and dcc_autoget_mask to *!*@* # check variables on top of autoget.pl (ag_prefs) # 1. /script load autoget.pl # 2. prepare a list to feed autoget with. # . Either a file with lines: # #homephotos !SmartGuy Visiting_the_Island_05.jpg # will result in autoget to try to get Visiting.... from SmartGuy in # #homephotos. Load the file with # /autoget load FILENAMEOFLIST # It can be deleted afterwards. # . or enter #homephotos in the autoget window and paste lines # !SmartGuy Visiting_the_Island_05.jpg # into the irssi window. Ther a single # to stop recording fora channel. # 3. /autoget on # 4. Watch autoget while it detects servers and starts to request/download # .... # 5. come back and enjoy whatever you waste HD space with # # NOTE: Donot connecttomore than one server, this will confuse autoget! ## Description # Helper for SPR jukeboxes/SDfind/BorgScript and alike # This script for irssi (http://irssi.org), a fine IRC console client, # eases the filesharing in channels where the SPR Jukebox and alike # are popular. That is: Filelists are requested with @nick and files # with !nick FILE. # autoget.pl will read prepared lists and tries to download the files # on after another (it catches your request from channel windows, and # youcan paste requests into the autoget window as well). It can be # used with more than one channel and more than one user at a time. # It will move finished downloads into a 'completed' directory if this # option is set. It will retry incomplete files until they are # completely downloaded. It will start requesting files as soon as it # detects that a user runs a fserv. Everything should work pretty # automagically, just add some lists and come back later ... thats # what filesharing should be like. # File format to read is (e.g.): # #homephotos !SmartGuy Visiting_the_Island_05.jpg # autoget.pl will store all scheduled files internally, one can delete # the input list after adding it. # One can search the list of files scheduled, delete from it and add # more lists. # Once loaded it must be enabled with '/autoget on'. # ---------------------------------------------------------------------- ## TODO # + caught requests from channel windows will be set to state requested instead of waiting # + reading existing requests no longer confuses ag_counter # + FIX: list catcher should only get lists once for each hostmask now # + last file incomplete ... won't get re-reuested # - dcc stuff still not 100% correct. Catch signal dcc-aborted! # - add on error handling (esp when transfer doesn't start) # - add stale transfer detection # - script shall enable autoresume and autoget + autoget mask # - disable/enable/delete certain nicks # - FIX: autoget windows is always refnum 2 # - /dcc close get does not respect counter # - /ag reset N should respect ongoing transfers # - incomplete last file might result in a request flood # ---------------------------------------------------------------------- use strict; no strict 'refs'; use Irssi; use Irssi::Irc; use Irssi::TextUI; my $prefix = "AutoGet"; my $autoget_window = (); my $autoget_window_name = $prefix; my $autoget_window_ref = -1; my $ag_win_notices = 'AutoGet NOTICES'; my %ag_prefs = ( enabled => 0, aggressive => 1, debug => 0, todofile => '~/.autoget.todo', donefile => '~/.autoget.done', completed_dir => '~/ftp/incoming/chatstuff/completed', capture_notices => 1, suppress_announcements => 1, message_fg => 0, message_bg => 4, lists_dir => '~/ftp/incoming/chatstuff/downloading/lists', listsfile => '~/.autoget.lists', catchlists => 0, catchlisttries => 1, dccallow_needed => 1 ); my $ag_catching = 0; my $ag_catching_time = 0; my $ag_input_channel = ''; my $ag_input_scheduled = 0; my %ag_counter = ( waiting => 0, requested => 0, incomplete => 0, receiving => 0, done => 0 ); my %ag_todolist; my %ag_urgent; my %ag_servers; my %ag_knownlists; ########################################################################### # # Signal Setup # ########################################################################### # managing list processing and dcc stuff Irssi::signal_add('dcc created', 'ag_sig_dcc_created'); Irssi::signal_add('dcc closed', 'ag_sig_dcc_closed'); Irssi::signal_add('nicklist changed', 'ag_sig_nick_changed'); # server list housekeeping Irssi::signal_add('message part', 'ag_sig_nick_parted'); Irssi::signal_add('message quit', 'ag_sig_nick_quit'); Irssi::signal_add('server disconnected', 'ag_sig_server_disconnected'); # server detection Irssi::signal_add_first('ctcp msg slots', 'ag_sig_ctcp_slots'); Irssi::signal_add_first('ctcp msg mp3', 'ag_sig_ctcp_slots'); Irssi::signal_add_first('ctcp msg wma', 'ag_sig_ctcp_slots'); Irssi::signal_add_first('ctcp msg sound', 'ag_sig_ctcp_slots'); Irssi::signal_add_first('message public', 'ag_sig_message_public'); # capture manual requests Irssi::signal_add('send text', 'ag_sig_send_text'); # filter notice replies from server Irssi::signal_add('message irc notice', 'ag_sig_irc_notice'); Irssi::command_bind('autoget', 'ag_main'); ########################################################################### # # Toolies # ########################################################################### sub ag_ensure_created_window { $autoget_window = Irssi::window_find_name($autoget_window_name); if (!$autoget_window) { $autoget_window = Irssi::Windowitem::window_create('', 1); $autoget_window->set_name($autoget_window_name); $autoget_window->set_refnum(2); $autoget_window_ref = 2; ## [FIXME] $autoget_window->{refnum}; } $autoget_window_ref = 2; ## [FIXME] $autoget_window->{refnum}; } sub ag_debug { &ag_ensure_created_window(); $autoget_window->print("$prefix DEBUG: @_", MSGLEVEL_CLIENTCRAP) if ($ag_prefs{debug}); } sub ag_print { &ag_ensure_created_window(); $autoget_window->print("$prefix @_", MSGLEVEL_CLIENTCRAP); } sub ag_message { &ag_ensure_created_window(); $autoget_window->print(sprintf("$prefix \003%02d,%02d@_", $ag_prefs{message_fg}, $ag_prefs{message_bg}), MSGLEVEL_CLIENTCRAP); } sub ag_status { my ($item, $get_size_only) = @_; my ($fwait, $freq, $finc, $frec, $fdone) = ($ag_counter{waiting}, $ag_counter{requested}, $ag_counter{incomplete}, $ag_counter{receiving}, $ag_counter{done}); my $string; if ($ag_prefs{enabled}) { $string = "{sb %_AG:%_ $fwait-$freq/$finc/$frec-$fdone"; } else { $string = "{sb --: $fwait-$freq|$finc|$frec-$fdone"; } if ($ag_input_channel) { $string .= " $ag_input_channel: $ag_input_scheduled"; } $string .= '}'; $item->default_handler($get_size_only, $string, undef, 1); } sub ag_maskhost { my $addr = lc(shift); if($addr =~ /@[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/) { $addr =~ s/(.+)@(.+)\.([^.]+)/\1@\2.*/; } else { $addr =~ s/(.+)@([^.]+)\.(.+)/\1@*.\3/; } return $addr; } ########################################################################### # # Entry management # ########################################################################### sub ag_entry_change_state ($$) { my ($entry, $state) = @_; $ag_counter{$entry->{state}} -= 1; $entry->{state} = $state; $ag_counter{$entry->{state}} += 1; } sub ag_entry_add ($$$) { my ($chan, $lnick, $request) = @_; my $nick = lc($lnick); $request =~ s/ / /g; # replace non-breakable space with space $request =~ s/ +::.+$//; my $filename = $request; $filename =~ s/_/ /g; my %entry = ( filename => $filename ); if (exists $ag_todolist{$filename}) { %entry = %{ $ag_todolist{$filename} }; } else { $ag_todolist{$filename} = \%entry; } $entry{channel} = $chan; $entry{nick} = $nick; $entry{request} = $request; $entry{filename} = $filename; ag_entry_change_state(\%entry, 'waiting'); unless ($ag_servers{$nick}) { %{ $ag_servers{$nick} } = (nick => $nick, channel => $chan); } return \%entry; } sub ag_file_get_entry { my ($filename) = @_; $filename =~ s/_/ /g; return \%{ $ag_todolist{$filename} }; } sub ag_file_has_entry { my ($filename) = @_; $filename =~ s/_/ /g; return exists $ag_todolist{$filename}; } sub ag_nick_start_leeching { my $nick = lc(shift); if ($ag_prefs{enabled}) { my $user = $ag_servers{$nick}; ##ag_print("Startleech from: $user->{leeching} :: $user->{serving} :: $nick") if $user; if ($user && $user->{serving} && !$user->{leeching} && ag_nick_get_next($nick)) { ag_message("$nick runs a server, starting requests."); $user->{leeching} = 1; ag_nick_request_next($nick); } } } sub ag_nick_get_next { my $nick = lc(shift); my $entry; foreach (sort keys %ag_urgent) { $entry = $ag_urgent{$_}; if ($entry->{nick} eq $nick && $entry->{state} =~ /(incomplete|wait)/) { return $entry; } } foreach (sort keys %ag_todolist) { $entry = $ag_todolist{$_}; if ($entry->{nick} eq $nick && $entry->{state} =~ /(incomplete|wait)/) { return $entry; } } } sub ag_nick_request_next { my $nick = lc(shift); my $entry; unless ($ag_prefs{enabled}) { ag_message("AutoGet disabled, won't get more files."); return; } unless ($ag_servers{$nick}) { ag_debug ("$nick doesn't run a server, skipping request."); return; } $entry = ag_nick_get_next($nick); if ($entry) { my $server = (Irssi::servers())[0]; my $channel = $server->channel_find($entry->{channel}) if $server; if ($channel && $channel->{joined}) { ag_print("Requesting from $entry->{nick} ($entry->{channel}): $entry->{request}"); $server->command("/msg $entry->{channel} !$entry->{nick} $entry->{request}"); ag_entry_change_state($entry, 'requested'); Irssi::statusbar_items_redraw('autoget'); } else { ag_print("Not in channel $entry->{channel}, skipping."); } } else { ag_message("No more files for $nick to get."); } } sub ag_entries_load { my @args = @_; my ($file, $line, %entry, $pat); my (%nicks, $curcount); $curcount = scalar keys %ag_todolist; foreach $pat (@args) { foreach $file (glob $pat){ ag_message("Loading list $file."); open INFILE, "<$file"; while ($line = ) { chop; if ($line =~ /^(\/msg +)?([^ ]+) +!([^ ]+) +(.+)/) { ag_entry_add($2, $3, $4); $nicks{$2} = 1; } } close INFILE; } } ag_message("Loaded " . ((scalar keys %ag_todolist) - $curcount) . " entries" . " (now " . (scalar keys %ag_todolist) . ") from " . (scalar keys %nicks) . " users."); if ($ag_prefs{enabled}) { foreach (keys %nicks) { ag_nick_start_leeching($_); } } Irssi::statusbar_items_redraw('autoget'); return; } sub ag_entries_save { my $quiet = shift @_; my ($file, $count); my $reallistfile = (glob($ag_prefs{todofile}))[0]; ag_message("Saving list ...") unless $quiet; if (-f $reallistfile) { rename "$reallistfile", "$reallistfile.bak"; } open LIST, ">$reallistfile"; $count = 0; foreach $file (sort keys %ag_todolist) { $file = $ag_todolist{$file}; if ($file->{state} ne 'done') { print LIST "/msg $file->{channel} !$file->{nick} $file->{request}\n"; $count += 1; } } close LIST; ag_message("Saved $count (omitting ". ((scalar keys %ag_todolist) - $count) ." finished) entries.") unless $quiet; return; } ########################################################################### # # Event handling (nickchange, fserv going online etc) # ########################################################################### sub ag_catch_next_list { if ($ag_prefs{catchlists} && $ag_prefs{enabled}) { foreach my $entry (values %ag_servers) { if ($entry->{hostmask} && !$entry->{gotlist} && (!$entry->{requestedlist} || $entry->{requestedlist} ne '-' && $entry->{requestedlist} < $ag_prefs{catchlisttries})) { my $server = (Irssi::servers())[0]; my @user_list = Irssi::Channel::nicks($server->channel_find($entry->{channel})); my $safenick = quotemeta $entry->{nick}; if (grep (\{ $_->{nick} =~ /^$safenick/i }, @user_list)) { $server->send_raw("dccallow +$entry->{nick}") if ($ag_prefs{dccallow_needed}); $server->command("/msg $entry->{channel} \@$entry->{nick}"); $entry->{requestedlist} = ($entry->{requestedlist} ? $entry->{requestedlist} + 1: 1); ag_message("Catching list from $entry->{nick} ($entry->{channel}, try no. $entry->{requestedlist})"); $ag_catching = 1; $ag_catching_time = time; last; } else { ag_message("$entry->{nick} no longer in $entry->{channel}, not catching list."); $entry->{requestedlist} = '-'; } } else { $ag_catching = 0; } } } else { $ag_catching = 0; } } sub ag_nick_server_detected { ## Makesureto rename if hostmask is found! my ($mcnick, $channel, $addr, $args) = @_; my $user; my $nick = lc($mcnick); $addr = ag_maskhost($addr); foreach (keys %ag_servers) { if ($_->{hostmask} eq $addr) { ag_nick_changed($_->{nick}, $nick); last; } } unless ($ag_servers{$nick}) { %{ $ag_servers{$nick} } = (nick => $mcnick, channel => $channel, leeching => 0, gotlist => 0); } $user = $ag_servers{$nick}; if ($args =~ /([0-9]+) ([0-9]+) (.+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)/) { my ($slots, $sused, $next, $qused, $queues, $maxspeed, $files) = ($1, $2, $3, $4, $5, $6, $7); $user->{slots} = $slots; $user->{sused} = $sused; $user->{nextslot} = $next; $user->{qused} = $qused; $user->{queues} = $queues; $user->{maxspeed} = $maxspeed; $user->{files} = $files; } $user->{hostmask} = $addr if ($addr ne ''); $user->{serving} = 1; ag_nick_start_leeching($nick); ag_catch_next_list() if (!$ag_catching || time - $ag_catching_time > 60 * 5); # 5 minutes timeout } sub ag_nick_changed { my ($oldnick, $newnick) = @_; return if $newnick =~ /^Guest/i; my $lcoldnick = lc($oldnick); my $lcnewnick = lc($newnick); my ($entry, $i); foreach (keys %ag_todolist) { $entry = $ag_todolist{$_}; if ($entry->{nick} eq $lcoldnick) { $entry->{nick} = $lcnewnick; $i++; } } if ($ag_servers{$lcoldnick}) { # save some values of the targetnick first if ($ag_servers{$lcnewnick}) { $ag_servers{$lcoldnick}->{serving} = $ag_servers{$lcnewnick}->{serving}; } $ag_servers{$lcnewnick} = $ag_servers{$lcoldnick}; $ag_servers{$lcnewnick}->{nick} = $newnick; delete $ag_servers{$lcoldnick}; ag_nick_start_leeching($newnick); } return $i; } sub ag_sig_nick_changed { my ($channel, $nickrec, $oldnick) = @_; ag_nick_changed($oldnick, $nickrec->{nick}); } sub ag_sig_nick_parted { my ($server, $channel, $nick, $addr, $reason) = @_; $nick = lc($nick); if ($ag_servers{$nick}) { ($ag_servers{$nick})->{serving} = 0; ($ag_servers{$nick})->{leeching} = 0; } } sub ag_sig_nick_quit { my ($server, $nick, $addr, $reason) = @_; $nick = lc($nick); if ($ag_servers{$nick}) { ($ag_servers{$nick})->{serving} = 0; ($ag_servers{$nick})->{leeching} = 0; } } sub ag_sig_server_disconnected { my $server = shift; foreach (keys %ag_servers) { $ag_servers{$_}->{serving} = 0; $ag_servers{$_}->{leeching} = 0; } foreach (keys %ag_todolist) { my $entry = $ag_todolist{$_}; if ($entry->{state} !~ /(done|receiving)/) { ag_entry_change_state($entry, 'waiting'); } } Irssi::statusbar_items_redraw('autoget'); } sub ag_sig_ctcp_slots { my ($server, $args, $nick, $addr, $target) = @_; ag_nick_server_detected($nick, $target, $addr, $args); Irssi::signal_stop() if ($ag_prefs{suppress_announcements}); } sub ag_sig_message_public # "message public", SERVER_REC, char *msg, char *nick, char *address, char *target { my ($server, $msg, $nick, $addr, $target) = @_; my $safenick = quotemeta $nick; if ($target =~ /^\#/ && $msg =~ /[@!]$safenick/i) { ag_nick_server_detected($nick, $target, $addr, ''); ag_debug("Caught server $nick ($safenick) from public message: $msg"); Irssi::signal_stop() if ($ag_prefs{suppress_announcements}); } } sub ag_sig_send_text # char *line, SERVER_REC, WI_ITEM_REC { my ($text, $serverrec, $windowrec) = @_; my %entry; if ($autoget_window_ref eq Irssi::active_win()->{refnum}) { &ag_main($text, $serverrec, $windowrec); } elsif ($text =~ /^!([^ ]+) +(.+) *$/ && $1 && $1 !~ /seen/i && $windowrec->{type} eq 'CHANNEL') { ag_entry_change_state(ag_entry_add($windowrec->{name}, $1, $2), 'requested'); } } # Capture notices from serving ppl we leech from to a separate window sub ag_sig_irc_notice # "message irc notice", SERVER_REC, char *msg, char *nick, char *address, char *target { my ($server, $msg, $nick, $address, $target) = @_; if ($server->{nick} eq $target) { ag_debug("NOTICE for us ($target): $nick ($address) = $msg"); if ($ag_servers{lc($nick)} && $ag_prefs{capture_notices}) { my $nwin = Irssi::window_find_name($ag_win_notices); # create window to catch DCC stuff if ($ag_prefs{capture_notices} && !$nwin) { $nwin = Irssi::Windowitem::window_create('', 1); $nwin->set_name($ag_win_notices); $nwin->set_refnum(3); } $nwin->print("$nick($address): $msg", MSGLEVEL_CLIENTCRAP); ag_debug("NOTICE captured"); Irssi::signal_stop(); } } else { ag_debug("NOTICE not for us ($target): $nick ($address) = $msg"); } } ########################################################################### # # DCC handlers # ########################################################################### sub ag_sig_dcc_generic { my ($dcc) = @_; my @dccs = Irssi::Irc::dccs(); my $i = 0; foreach (keys %{$dcc}) { ag_debug("$_ => $dcc->{$_} "); } ag_debug("dccs: @dccs"); foreach (@dccs) { $dcc = $dccs[$i]; ag_debug("DCC: $dcc"); foreach (keys %{$dcc}) { ag_debug("$_ => $dcc->{$_} "); } } } sub ag_sig_dcc_created { ag_debug("ag_sig_dcc_created entered: @_"); my ($dcc) = @_; my $entry; if ($dcc->{type} eq 'GET'){ if (ag_file_has_entry($dcc->{arg})) { $entry = ag_file_get_entry($dcc->{arg}); ag_print("Receiving from $entry->{nick} ($entry->{channel}): $entry->{request}"); ag_entry_change_state($entry, 'receiving'); Irssi::statusbar_items_redraw('autoget'); if ($ag_prefs{aggressive}) { ag_nick_request_next($entry->{nick}); } } else { ag_debug("File $dcc->{arg} not listed in autoget, irssi handles this."); } } } sub ag_sig_dcc_closed { ag_debug("ag_sig_dcc_closed entered: @_"); my ($dcc) = @_; my ($entry, $sourcename, $targetname); if ($dcc->{type} eq 'GET') { if (ag_file_has_entry($dcc->{arg})) { $entry = ag_file_get_entry($dcc->{arg}); if ($dcc->{size} eq $dcc->{transfd}) { ag_print("Completed from $entry->{nick} ($entry->{channel}): $entry->{request}"); $sourcename = $dcc->{file}; $targetname = $dcc->{file}; $targetname =~ s/_/ /g; if ($sourcename ne $targetname) { rename $sourcename, $targetname; } if (-d (glob($ag_prefs{completed_dir}))[0]) { $targetname =~ s/([\"\$\`\\])/\\\1/g; ag_debug("Moving $targetname to completed directory."); #ag_print("mv \"$targetname\" $ag_prefs{completed_dir}"); system "mv \"$targetname\" $ag_prefs{completed_dir}"; } ag_entry_change_state($entry, 'done'); Irssi::statusbar_items_redraw('autoget'); delete $ag_urgent{$entry->{filename}} if ($ag_urgent{$entry->{filename}}); ag_entries_save 1; # saving done my $rdfile = (glob($ag_prefs{donefile}))[0]; if (open DONEF, ">>$rdfile") { my $t = sprintf("%s %d %10d %15s !%-20s %s\n", time, $dcc->{starttime}, $dcc->{size}, $entry->{channel}, $entry->{nick}, $entry->{request}); print DONEF $t; close DONEF; } else { ag_message("Cannot open logfile for completed transfers $ag_prefs{donefile}!"); } } else { ag_print ("Incomplete from $entry->{nick} ($entry->{channel}): $entry->{request}"); ag_entry_change_state($entry, 'incomplete'); Irssi::statusbar_items_redraw('autoget'); $ag_urgent{$entry->{filename}} = $entry; # rerequest if last file. else the next should come automatically my $count = 0; foreach my $val (values %ag_todolist) { $count++ if ($entry->{state} !~ /(done|waiting)/ && $val->{nick} eq $entry->{nick}); } ag_nick_request_next($entry->{nick}) if $count == 1; } unless ($ag_prefs{aggressive}) { ag_nick_request_next($entry->{nick}); } } elsif (exists $ag_servers{lc($dcc->{nick})} && $dcc->{arg} =~ /\.(txt|zip)/) { my $nick = lc($dcc->{nick}); $ag_servers{$nick}->{requestedlist} = '-'; # too large number to be reasonable for retries if ($dcc->{size} eq $dcc->{transfd}) { my $ldfile = (glob($ag_prefs{listsfile}))[0]; my $listname = $dcc->{file}; $ag_servers{$nick}->{gotlist} = 1; $listname =~ s/([\"\$\`\\])/\\\1/g; #ag_print("mv \"$listname\" $ag_prefs{lists_dir}"); system "mv \"$listname\" $ag_prefs{lists_dir}"; if (open LISTF, ">>$ldfile") { print LISTF sprintf("%s %s %s\n", $ag_servers{$nick}->{channel}, $nick, $ag_servers{$nick}->{hostmask}); close LISTF; } else { ag_message("Cannot open logfile for retrieved lists $ag_prefs{listsfile}!"); } } my $server = (Irssi::servers())[0]; $server->send_raw("dccallow -$nick") if ($ag_prefs{dccallow_needed}) && $server; ag_catch_next_list; } else { ag_debug("File $dcc->{arg} not listed in autoget, irssi handles this."); } } } ########################################################################### # # User commands i.e. user interface i.e. good old CLI # ########################################################################### sub ag_main { my ($cmd_line, $server, $win_item) = @_; my @args = split(' ', $cmd_line); my $arg; unless (@args) { ag_message ("Give some command or try help!"); return; } while ($arg = shift @args) { $arg =~ /^help/ && do { ag_message("AutoGet is $version (C) by Moxon and understands the following commands:"); ag_print(" off - no file requests will follow successfull downloads."); ag_print(" on - the opposite + starts to request when servers are detected."); ag_print(" load FILE+ - loads all the following files (can be shell patters)"); ag_print(" search PAT - searches the list of files AutoGet will fetch for PAT. If PAT"); ag_print(" starts with @, ! or #, state, nicks or channels are searched."); ag_print(" delete PAT - deletes every filename from the filelist matching like search."); ag_print(" moveto #CHAN PAT - moves all file requests matching PAT to #CHAN."); ag_print(" save - saves the list of files to be fetched (saved automatically, too)"); ag_print(" stat [para] - shows some stats about file fetching stati if without parameter"); ag_print(" use 'urgent' for a list of urgent files."); ag_print(" server [PAT] - for a list of known active servers (add a regexp to restrict)."); ag_print(" user - prints a list of users we are interessted in. Ifany option is given,"); ag_print(" calls 'stat server' with every nick."); ag_print(" channel - prints a list of known channels we get from."); ag_print(" addserver N C - manually adds a server (some aren't detected) from Nick in Channel"); ag_print(" set [K [V]] - view and set configuration variables (set K to V)"); ag_print(" rename O N - renames all entries for nick O to nick N"); ag_print(" reset [N+] - flushes all lists and rereads the default listfile. When N specified,"); ag_print(" resets files from each N to waiting, and forgets that N is serving."); ag_print(" tidy - removes all done items from lists."); ag_print(" join - joins all channels we get files from (done with 'on', too)."); ag_print(" \#CHANNEL - Start recording lines like !nick FILE entered in AG win to be scheduled"); ag_print(" for CHANNEL. Recording stops, if If Channel is left blank."); ag_print(" where N+ - tells, in which channel the nick N was seen."); ag_print(" whois - makes a /whois on every nick we have files scheduled from"); return; }; $cmd_line =~ /^\#([^ ]*)( +(.+))?/ && do { my $chan = $1; my $req = $3; $req =~ s/^ +//g; $ag_input_scheduled = 0; if ($chan) { $ag_input_channel = "\#$chan"; ag_message("Started recording requests for channel $ag_input_channel."); if ($req) { &ag_main($req, $server, $win_item); } } else { ag_message("Stopped recording requests for channel $ag_input_channel."); $ag_input_channel = undef; } Irssi::statusbar_items_redraw('autoget'); return; }; $cmd_line =~ /^!([^ ]+) +(.+)/ && do { if ($ag_input_channel) { my ($nick, $file) = ($1, $2); ; ag_print("Saving for $ag_input_channel: !$nick " . ag_entry_add("$ag_input_channel", $nick, $file)->{filename}); $ag_input_scheduled++; Irssi::statusbar_items_redraw('autoget'); ag_nick_start_leeching($nick); } else { ag_message("Sorry, no input channel set, use #CHANNEL to set one."); } return; }; $arg =~ /^adds(erver)?/ && do { my ($nick, $channel) = @args; ag_message("Manually added server $nick" . ($channel ne '' ? " in $channel." : '')); ag_nick_server_detected($nick, $channel, '', ''); return; }; $arg =~ /^off/ && do { $ag_prefs{enabled} = 0; ag_message("AutoGet switched off, running transfers will continue."); foreach (values %ag_servers) { $_->{leeching} = 0; $_->{serving} = 0; }; Irssi::statusbar_items_redraw('autoget'); return; }; $arg =~ /^on/ && do { if ($ag_prefs{enabled}) { ag_message("AutoGet already active."); } else { ag_message("AutoGet switched on. Requests will start as soon as file servers are detected."); $ag_prefs{enabled} = 1; ag_main('join', $server, $win_item); foreach (keys %ag_servers) { ag_nick_start_leeching($_); } } Irssi::statusbar_items_redraw('autoget'); return; }; $arg =~ /^join/ && do { my %chans; foreach my $entry (values %ag_todolist) { if ($entry->{state} ne 'done') { if ($chans{$entry->{channel}}) { $chans{$entry->{channel}} += 1; } else { $chans{$entry->{channel}} = 1; } } } my @k = keys %chans; ag_message("Joining channels we leech in: @k"); my $server = (Irssi::servers())[0]; foreach (@k) { Irssi::Server::channels_join($server, $_, 'automatic'); } return; }; $arg =~ /^tidy/ && do { my $i = 0; foreach (values %ag_todolist) { if ($_->{state} eq 'done') { $i += 1; delete $ag_urgent{$_->{filename}} if ($ag_urgent{$_->{filename}}); delete $ag_todolist{$_->{filename}}; $ag_counter{done} -= 1; } } ag_message("Removed $i completed files from list."); if ($i) { Irssi::statusbar_items_redraw('autoget'); } return; }; $arg =~ /^load/ && do { ag_entries_load(@args); @args = (); return; }; $arg =~ /^reset/ && do { if (@args) { ag_message("Reseting file stati to 'waiting' for user(s) @args."); my ($user, %collect); foreach $user (@args) { my $lcuser = lc($user); foreach (values %ag_todolist) { if ($_->{nick} eq $lcuser && $_->{state} ne 'done') { ag_entry_change_state($_, 'waiting'); Irssi::statusbar_items_redraw('autoget'); $collect{$lcuser} = 1; } } } foreach (keys %collect) { $user = $ag_servers{$_}; if ($user) { ag_message("Forgetting that $_ runs a server."); $user->{leeching} = 0; $user->{serving} = 0; } } foreach $user (@args) { my $lcuser = lc($user); unless ($collect{$lcuser}) { ag_message("Sorry, no such user known: $user."); } } } else { ag_message("Flushing lists and reload $ag_prefs{todofile}..."); foreach (keys %ag_counter) { $ag_counter{$_} = 0; } %ag_urgent = (); %ag_todolist = (); foreach (keys %ag_servers) { $ag_servers{$_}->{serving} = 0; $ag_servers{$_}->{leeching} = 0; } $ag_catching = 0; ag_entries_load($ag_prefs{todofile}); ag_message("Reset complete."); } return; }; $arg =~ /^rename/ && do { my ($from, $to) = @args; my $amount = ag_nick_changed($from, $to); if ($amount) { ag_message("Transferred $amount entries for $from to $to."); } else { ag_message("No entries transferred."); } return; }; $arg =~ /^moveto/ && do { my ($to, $matcher) = @args; my ($i, $match, $entry); unless ($to || $matcher) { ag_message("Usage: moveto #channel PATTERN"); return 1; } $cmd_line =~ /^moveto +\#(.+?) +([@!\#])?(.+) *$/; $matcher = $3; $match = 'filename'; $match = 'state' if $2 eq '@';; $match = 'channel' if $2 eq '#'; $match = 'nick' if $2 eq '!'; foreach (keys %ag_todolist) { $entry = $ag_todolist{$_}; if ($entry->{$match} =~ /$matcher/i) { $entry->{channel} = $to; $i++; } } ag_message("Transferred $i entries to channel $to."); return; }; $arg =~ /^set/ && do { my $key = shift @args; my $value = shift @args; if ($value ne '') { if ($ag_prefs{$key} ne '') { $ag_prefs{$key} = $value; ag_message("$key set to $value."); } else { ag_message("No such key $key."); } } elsif ($ag_prefs{$key} ne '') { ag_message("AutoGet configuration:"); ag_print(" $key = $ag_prefs{$key}"); } else { ag_message("AutoGet configuration:"); foreach (sort keys %ag_prefs) { ag_print(" $_ = $ag_prefs{$_}"); } } return; }; $arg =~ /^ser(ver)?/ && do { my ($server, $i); my $matcher = lc($args[0]); if (scalar keys %ag_servers) { ag_message("Listing servers (" . (scalar keys %ag_servers) . " known)" . ($matcher ? " matching '$matcher'" : "") . ":" ); ag_print("No. Nick Channel rLSD Slots Queues Files Speed Next Hostmask"); ag_print("--- --------------- --------------- ---- -- -- --- --- ----- ------- -------- ---------------------"); $i = 0; foreach $server (sort keys %ag_servers) { if (!$matcher || $server =~ /$matcher/) { $server = $ag_servers{$server}; ag_print(sprintf("%3d %-15s %15s %1s%1s%1s%1s %2d %2d %3d %3d %5d %8s %-8s %-s", $i, $server->{nick}, $server->{channel}, ($server->{requestedlist} ? $server->{requestedlist} : ' '), ($server->{gotlist} ? '+' : ' '), ($server->{serving} ? '+' : ' '), ($server->{leeching} ? '+' : ' '), $server->{sused}, $server->{slots}, $server->{qused}, $server->{queues}, $server->{files}, $server->{maxspeed}, $server->{nextslot}, $server->{hostmask})); $i += 1; } } } else { ag_message("No servers detected yet."); } return; }; $arg =~/^se/ && do { my ($entry, $i); my ($match, $matcher); if (@args) { $cmd_line =~ /^se(arch)? +([@!\#])?(.+) *$/; $matcher = $3; $match = 'filename'; $match = 'state' if $2 eq '@';; $match = 'channel' if $2 eq '#'; $match = 'nick' if $2 eq '!'; } else { ag_message("Please specify search pattern."); return; ##SIDE EXIT } ag_message("Searching in $match for $matcher:"); $i = 0; foreach (sort keys %ag_todolist) { $entry = $ag_todolist{$_}; if ($entry->{$match} =~ /$matcher/i) { $i++; ag_print(sprintf("%4d %11s %15s %15s %s", $i, $entry->{state}, $entry->{channel}, "!$entry->{nick}", $entry->{request})); } } if ($i == 0) { ag_message ("Sorry, no match.") } return; }; $arg =~ /^del/ && do { my ($matcher, $match); my (@result, $entry); if (@args) { $cmd_line =~ /^del(ete)? +([@!\#])?(.+) *$/; $matcher = $3; $match = 'filename'; $match = 'state' if $2 eq '@';; $match = 'channel' if $2 eq '#'; $match = 'nick' if $2 eq '!'; } else { ag_message("Please specify search pattern."); return; ##SIDE EXIT } foreach (keys %ag_todolist) { $entry = $ag_todolist{$_}; if ($entry->{$match} =~ /$matcher/i) { push @result, $entry; } } if (scalar @result > 0) { foreach (@result) { $ag_counter{$_->{state}}--; delete $ag_todolist{$_->{filename}}; delete $ag_urgent{$_->{filename}} if $ag_urgent{$_->{filename}}; } ag_message("Deleted " . (scalar @result) . " entries."); Irssi::statusbar_items_redraw('autoget'); } else { ag_message("No match found."); } return; }; $arg =~ /^save/ && do { my $quiet = shift @args; ag_entries_save $quiet; return; }; $arg =~ /^user/ && do { my %chans; my %nicks; foreach my $entry (values %ag_todolist) { if ($entry->{state} ne 'done') { if ($nicks{$entry->{nick}}) { $nicks{$entry->{nick}} += 1; ($chans{$entry->{nick}})->{$entry->{channel}} = 1; } else { $nicks{$entry->{nick}} = 1; %{ $chans{$entry->{nick}} } = ($entry->{channel} => 1); } } } ag_message((scalar keys %ag_todolist) . " files scheduled from " . (scalar keys %nicks) . " users:"); if (scalar @args > 0) { my @k = keys %nicks; &ag_main("stat server @k", $server, $win_item); } else { ag_print("Nick Files rLSD Channels Hostmask"); ag_print("--------------- ----- ---- --------------- -------------------"); foreach my $entry (sort keys %nicks) { my @c = keys %{ $chans{$entry} }; my $serving = 'no'; $serving = 'yes' if ($ag_servers{$entry} && $ag_servers{$entry}->{serving}); ag_print(sprintf("%-15s %5d %1s%1s%1s%1s %15s %s", ($ag_servers{$entry}->{nick} ? $ag_servers{$entry}->{nick} : $entry), $nicks{$entry}, ($ag_servers{$entry}->{requestedlist} ? $ag_servers{$entry}->{requestedlist} : ' '), ($ag_servers{$entry}->{gotlist} ? '+' : ' '), ($ag_servers{$entry}->{serving} ? '+' : ' '), ($ag_servers{$entry}->{leeching} ? '+' : ' '), @c, $ag_servers{$entry}->{hostmask})); } } return; }; $arg =~ /^chan/ && do { my %nicks; foreach my $entry (values %ag_todolist) { if ($entry->{state} ne 'done') { if ($nicks{$entry->{channel}}) { $nicks{$entry->{channel}} += 1; } else { $nicks{$entry->{channel}} = 1; } } } ag_message((scalar keys %ag_todolist) . " files scheduled from " . (scalar keys %nicks) . " channels:"); ag_print("Channel Files"); ag_print("--------------- -----"); foreach my $entry (sort keys %nicks) { ag_print(sprintf("%-15s %5d", $entry, $nicks{$entry})); } return; }; $arg =~ /^stat/ && do { my (%chans, %nicks); my ($files, $fdone, $freq, $finc, $fwait, $frec) = (0, 0, 0, 0, 0, 0); my $entry; if ($args[0] =~ /^urgent/) { my $i = 0; if (scalar keys %ag_urgent == 0) { ag_message("Currently no urgent files."); } else { ag_message("Printing urgent files:"); foreach $entry (sort keys %ag_urgent) { $entry = $ag_urgent{$entry}; ag_print(sprintf("%4d %11s %s !%s %s", $i, $entry->{state}, $entry->{channel}, $entry->{nick}, $entry->{request})); $i++; } } } elsif (@args) { ag_message("Parameter @args for stat not recognized."); } else { foreach $entry (keys %ag_todolist) { $entry = $ag_todolist{$entry}; $chans{$entry->{channel}} = 1 if $entry->{state} ne "done"; $nicks{$entry->{nick}} = 1 if $entry->{state} ne "done"; $files++; if ($entry->{state} eq 'done') { $fdone += 1; } elsif ($entry->{state} eq 'requested') { $freq += 1; } elsif ($entry->{state} eq 'incomplete') { $finc += 1; } elsif ($entry->{state} eq 'waiting') { $fwait += 1; } elsif ($entry->{state} eq 'receiving') { $frec += 1; } } ag_message("Autoget Version $version is " . ($ag_prefs{enabled} ? 'enabled' : 'disabled')); ag_print(" Getting from " . (scalar keys %nicks) . " users (" . (scalar keys %ag_servers) . " active known) in " . (scalar keys %chans) . " different channels:"); ag_print(" -------------------"); ag_print(sprintf(" %4d waiting", $fwait)); ag_print(sprintf(" %4d requested", $freq)); ag_print(sprintf(" %4d incomplete", $finc)); ag_print(sprintf(" %4d receiving", $frec)); ag_print(sprintf(" %4d done", $fdone)); ag_print(sprintf(" (%4d urgent)", scalar keys %ag_urgent)); ag_print(" -------------------"); ag_print(sprintf(" %4d Total", $files)); ag_message("Currently " . ($ag_catching ? '' : 'not ') . "catching lists."); } return; }; $arg =~ /^where/ && do { if (@args) { ag_message("I've seen the following users from @args:"); while (my $ch = shift @args) { foreach my $entry (keys %ag_servers) { if ($entry =~ /$ch/i) { $entry = $ag_servers{$entry}; ag_print(sprintf("%-15s %-20s", $entry->{nick}, $entry->{channel})); } } } } else { ag_message("Specify users for seen request"); } return; }; $arg =~ /^whois/ && do { my $server = (Irssi::servers())[0]; my %nicks; ag_message("Whois query on nicks we scheduled files from:"); foreach my $entry (values %ag_todolist) { if ($entry->{state} ne 'done') { $nicks{$entry->{nick}} = 1; } } foreach my $nick (keys %nicks) { $server->command("/whois $nick $nick"); } return; }; # DEFAULT: ERROR ag_message("Sorry, don't know how to handle command $arg @args"); return; } } ########################################################################### # # Initialization code # ########################################################################### ag_message("AutoGet version $version starting up"); if (-f (glob($ag_prefs{todofile}))[0]) { ag_entries_load($ag_prefs{todofile}) } # set existing transfers to receiving my @foobar = Irssi::Irc::dccs(); my ($dx, $f); foreach $dx (@foobar) { if ($dx->{type} eq 'GET') { $f = $dx->{file}; $f =~ s#.+/##; if (ag_file_has_entry($f)) { ag_entry_change_state(ag_file_get_entry($f), 'receiving'); } Irssi::statusbar_items_redraw('autoget'); } } # create window to catch DCC stuff if (!Irssi::window_find_name('DCC messages')) { my $foowin = Irssi::Windowitem::window_create('', 1); $foowin->set_name('DCC messages'); $foowin->set_level(MSGLEVEL_DCC); $foowin->set_refnum(4); } # load list of known filelists if (open LISTF, (glob($ag_prefs{listsfile}))[0]) { while (my $line = ) { chomp $line; if ($line =~ /(.+) (.+) (.+)/) { my %entry = (nick => $2, channel => $1, hostmask => $3, gotlist => 1); %{ $ag_servers{lc($2)} } = %entry; } } close LISTF; } Irssi::statusbar_item_register('autoget', '{sb %_AG:%_ off}', 'ag_status'); ag_message("AutoGet version $version ready.");