#!/usr/bin/perl # autonuts v2.1 - automation of gnut using expect.pm # by steven fountain (cider@compulsion.org) # web: http://enraptured.compulsion.org/autonuts # web: http://enraptured.compulsion.org/code/ # # autonuts is designed to pillage and data-mine gnutella using the # command line gnutella client "gnut", using perl and Expect.pm # in a very hacked fashion. # # this tool succeeds at identifying unique file names # when given enough responses to chew through. # # in order to have it not grab files you already have, you must # change the $archive variable to point to where your mp3's are, # ie, /mp3 # then you must move the contents of a retrieval for "the fish" # into /mp3/the_fish # or "fish" as # /mp3/fish # # after identifying available unique songs by a band that you # dont already have, you can then queue them all.. # take the path of no return and jump into interactive mode # to watch the files download. # # usage: autonuts your simple text bandname here # ie: autonuts sneakerpimps & # autonuts the cure & # autonuts fugazi & # ;) # # requirements: # expect perl module w/stty/pty dependencies (deb users: libexpect-perl) # gnut (deb users: gnut) # some bandwidth # some harddrive space # # where your files are mostly stored.. for re-running $archive = "/ok/muse"; # change to /mp3 or something if i had drives? use Term::ANSIColor; # allways like to have this on my plate... use Expect; # gnut's kinda like ftp. *wink* use Text::Soundex; # for assistance in finding unique filenames $|++; # i like autoflushing too much. $*{STDERR} = \*{STDOUT}; # wouldnt want to miss anything now $SIG{INT} = sub { $nut -> hard_close(); die "exiting.\n"; }; # handle ctl-C $Expect::Debug=0; $Expect::Exp_Internals=0; $Expect::Log_Stdout=0; # turn this on for annoying debugging... $Expect::Multiline_Matching = 0; $Expect::Exp_Max_Accum = 0; $Expect::Manual_Stty = 1; # take a band name from command line. $band = join ' ', @ARGV; $band = lc($band); if ($band =~ /^\w/) { print "Good bandname found from commandline: $band\n"; } else { undef $band; } # coloring debugging output routines sub red { print color 'red'; } sub pred { $_ = shift; print color 'red'; print "$_"; print color 'reset'; print "\n"; } sub pgreen { $_ = shift; print color 'green'; print "$_"; print color 'reset'; print "\n"; } sub pbgreen { $_ = shift; print color 'bold green'; print "$_"; print color 'reset'; print "\n"; } sub white { print color 'bold white'; } sub creset { print color 'reset'; } sub pret { print "\n"; } sub wait_for_prompt { $quiet = @_; print "waiting for prompt.. "; unless($nut -> expect(15, "autonuts>")) { print "NOT ok.\n"; return 0; } print "ok.\n"; return 1; } sub waitfor { $waitfor = shift; $howlong = shift || 60; print "expecting \"$waitfor\" for ${howlong}s.. "; unless($nut -> expect($howlong, '-re', "$waitfor")) { print "NOT found.\n"; return 0; } print "ok.\n"; return 1; } sub start { print "spawning gnut process.. "; ($nut = Expect->spawn("gnut 2>&1")) || die "Couldn't spawn gnut: $!"; print "ok.\n"; print "waiting for gnut to start.. "; unless($nut -> expect(15, "at your service")) { die "gnut didn't start successfully.\n"; } print "ok.\n"; } @settings = ( "set prompt autonuts>\\n", # without this, wait_for_prompt would break "set response_format sz:{S} :: sp:{s} :: {f#} :: {N} ::", # needed for "resp" "set paginate 0", # gets rid of requests to more every 25 lines "set stats_format 1", # one line serverstats instead of two, i think "set max_downloads 100", "set max_incoming 100", "set sort_order p", # sort by speed, show the fastest ones at bottom ); sub settings { foreach $setting (@settings) { print "$setting.. "; $nut->send_slow(0, "$setting\r\n"); print "ok. "; wait_for_prompt(); } } sub sane { print "waiting for network to become ready..\n"; until($network_is_ready) { print "gathering... "; $nut->clear_accum(); $nut->send_slow(0, "info\r\n"); unless($nut -> expect(2, '-re', "HOST STATS:") ) { print "blah.\n"; } $host_stats = $nut->exp_after(); @host_stats = (); @host_stats = split /\n/, $host_stats; foreach (@host_stats) { if (/Unique GUIDs in memory: (\d+)/) { $users = $1; print "unique users: $users (not >500)"; $network_is_ready++ if ($users > 500); } } print " resting 20s" unless $network_is_ready; sleep 20 unless $network_is_ready; print ";\n"; } wait_for_prompt(quiet); $nut->send_slow(0, "info\r\n"); wait_for_prompt(quiet); pgreen "sane connectivity scenario reached."; } #until($cmd eq "stop"||$cmd eq "quit"||(!$nut)) { # print "autonuts: "; # chomp($cmd = ) unless $auto; # $auto=0 if $auto; # disable if previously auto... sub info { $nut->clear_accum(); $nut->send_slow(0, "info\r\n"); waitfor "HOST STATS:"; pgreen "playing back..."; $info = $nut->exp_after(); @info = (); @info = split /\n/, $info; foreach (@info) { chomp; $l++; pgreen "$l: $_"; } undef $l; wait_for_prompt(); } sub band { print "band presently set to: $band\n"; } sub bandset { $band = shift; $band = lc($band); band(); } sub search { pred("searching for stuff by ${band}... 75 second headstart... "); $nut->clear_accum(); $nut->send_slow(0, "search $band\r"); sleep 75; # the funny thing is that after 60s this picks up ~365, 495, etc. waitfor("responses received.", 120) ? $results=0 : $results=1 ; $nut->send_slow(0, "\r"); search() unless $results; resp() if $results; } sub resp { pred("showing responses..."); $nut->clear_accum(); $nut->send_slow(0, "resp\r"); $results = wait_for_prompt(); if($results) { $responses = $nut->exp_before(); @responses = (); @responses = split /\n/, $responses; $fmp3=0; %leech = (); %titles = (); %sounds = (); foreach (@responses) { chomp; $l++; pgreen "$l: $_"; if (/mp3/i) { $fmp3++; /sz:(.*?)\s+::\ssp:\s{0,}(\d+)\s::\s(\d+)\s::\s(.*?)\s::/; $song = $4; # and print "song: $song\n"; $number = $3; # and print "number: $number\n"; $speed = $2; # and print "speed: $speed\n"; $size = $1; # and print "size: $size\n"; $isize = int($size); next unless ($size =~ /m$/i); # should be in the meg ballpark next unless ($isize > 2); # should be bigger then two megs next unless ($isize < 10); # should be less than ten megs next unless ($speed > 200); # should get it faster than isdn $song = lc($song); # change the song to lowercase $song =~ s/_/ /g; # change underscores to spaces $song =~ s/\.mp3//gi; # remove the file extension $song =~ s/\s{0,}[\(\[].*?[\[\(].*?[\]\)].*?[\]\)]\s{0,}//g; # remove double variant comments eg: (remix(its phat)) $song =~ s/\s{0,}[\(\[].*?[\]\)]\s{0,}//g; # remove variant comments eg: (remix) $song =~ s/${band} - .* - (.*?)$/${band} - $1/g; # greedy attempt at removing album names next unless ($song =~ /$band - .*?/i); # should resemble band with a title $title = $1 if ($song =~ /${band} - (.*?)$/); $code = soundex $title; $already_have = 0; foreach (@already_have) { $ihave = $_; $ihavecode = $1 if ($ihave =~ /^(.*?) /); $uhave = "$code $title"; # if it sounds like something we already have, # we probably dont want it. if ($uhave =~ /^$ihavecode /) { $already_have = 1; print "passing by: $code $title\n"; } } if ($already_have eq 0) { print "marking: $code $title\n"; $sounds{$code}{title} = $title; $sounds{$code}{number} = $number; } }; } unless ($l > 20 && $number / 2 < $l) { print "not happy with these results, trying again.\n"; $auto=1; $resp_sucks++; if ($resp_sucks > 5) { undef $resp_sucks; print "hrm. you might want to try typing search again.\n"; print "searching again...\n"; search(); # ha figured it out heh } search(); } else { print "i'm happy with the results. you should now type list\n"; } undef $l; } $auto=1 unless $fmp3; } sub list { foreach $codes (sort keys %sounds) { $uniq++; print "$codes [$sounds{$codes}{number}]\t$sounds{$codes}{title}\n"; } print "$uniq uniq.\n"; search() unless ($uniq =~ /^\d+/); undef $uniq; } sub getuniq { print "i'll take"; foreach $codes (sort keys %sounds) { $num = $sounds{$codes}{number}; print " ${num},"; $nut->send_slow(0, "g $num\r\n"); } print " and that aughta do it.\n"; print "requested all unique files.\n"; } sub log { if ($log) { pred "turning log off."; $log=0; $Expect::Log_Stdout=0; } unless ($log) { pred "turning log on."; $log=1; $Expect::Log_Stdout=1; } } sub before { print $nut->exp_before(); } sub after { print $nut->exp_after(); } sub interact { print "please do not return to autonuts, i'm changing your prompt...\n"; print "setting prompt.. "; $nut->send_slow(0, "set prompt gnut+autonutted>\r\n"); print "ok.\n"; $nut->interact(); $cmd = "stop"; # eh who knows might work... } sub help { print "commands..\nband info search resp list getuniq log before after interact\nif you run them in no specific order your mileage will vary\n"; } sub check_existing_songs { $group = shift; $group =~ s/ /_/g; print "opening existing $group...\n"; opendir OFF, "$archive/$group" or return "no archive directory found.\n"; while($song = readdir OFF) { next if ($song =~ /^\.|\.\.$/); next unless ($song =~ /\.mp3$/i); $song = lc($song); # change the song to lowercase $song =~ s/_/ /g; # change underscores to spaces $song =~ s/\.mp3//gi; # remove the file extension $song =~ s/\s{0,}[\(\[].*?[\[\(].*?[\]\)].*?[\]\)]\s{0,}//g; # remove double variant comments eg: (remix(its phat)) $song =~ s/\s{0,}[\(\[].*?[\]\)]\s{0,}//g; # remove variant comments eg: (remix) $song =~ s/${band} - .* - (.*?)$/${band} - $1/g; # greedy attempt at removing album names next unless ($song =~ /$band - .*?/i); # should resemble band with a title $title = $1 if ($song =~ /${band} - (.*?)$/); $code = soundex $title; print "already have: $code $title\n"; push @already_have, "$code $title"; } closedir OFF; } # roadmap.. print "band: " unless(defined($band)); chomp($band = ) unless(defined($band)); start(); settings(); check_existing_songs($band); sane(); bandset($band) if(defined($band)); search(); list(); getuniq(); print "abandoning you now to the gnut program, press enter once inside.\n"; interact(); # lost into gnut... # shouldnt get here... print "aah, they got me.\n"; print "i'm dying.\n"; $nut->hard_close(); die "dead.\n";