PageRenderTime 49ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/napster.PLS

https://github.com/gitpan/MP3-Napster
Perl | 555 lines | 516 code | 30 blank | 9 comment | 45 complexity | 50b1090eb142b5d67e4109cefafdc120 MD5 | raw file
  1. #!perl
  2. use Config;
  3. use File::Basename qw(&basename &dirname);
  4. use Cwd;
  5. $origdir = cwd;
  6. chdir dirname($0);
  7. $file = basename($0, '.PLS');
  8. $file .= $^O eq 'VMS' ? '.com' : '.pl';
  9. open OUT,">$file" or die "Can't create $file: $!";
  10. print "Extracting $file (with variable substitutions)\n";
  11. print OUT <<"!GROK!THIS!";
  12. $Config{startperl} -w
  13. !GROK!THIS!
  14. # In the following, perl variables are not expanded during extraction.
  15. print OUT <<'!NO!SUBS!';
  16. use strict;
  17. use lib './blib/lib';
  18. use vars qw(@SEARCH %UNLINK $COUNTER %OPT);
  19. BEGIN {
  20. use Getopt::Std;
  21. my $USAGE = <<USAGE;
  22. Usage: $0 [options] [address]
  23. Start a Napster session.
  24. Options:
  25. -r register as a new user
  26. -l <speed> link speed (see below)
  27. -d <path> download directory [./songs]
  28. -u <path> upload directory [same as download]
  29. -m <path> command to launch MP3 player from STDIN
  30. -p <port> port to listen on, use 0 with firewall
  31. -v <level> debug verbosity level
  32. -h help message
  33. [address] address of a Napster server in form aa.bb.cc.dd:port
  34. If not specified, will automatically choose the "best"
  35. server in the same way the Windows client does.
  36. <speed> one of: 14K 28K 33K 56K 64K 128K CABLE DSL T1 T3
  37. Defaults to "unknown".
  38. <port> If you provide -1 as the port (-p -1), then the client
  39. will choose an unused port at random. The standard
  40. Napster port is 6699 (-p 6699), but you can choose any
  41. port you like or let the script do it for you.
  42. USAGE
  43. getopts('rhl:u:d:m:p:v:',\%OPT) or die $USAGE;
  44. if ($OPT{h}) { print STDERR $USAGE; exit 0;}
  45. }
  46. use IO::File;
  47. use MP3::Napster;
  48. use Getopt::Std;
  49. use constant LINK_SPEED => LINK_UNKNOWN;
  50. use constant DOWNLOAD_PATH => './songs';
  51. use constant PORT => 6699;
  52. use constant PLAYER => 'mpg123 -';
  53. # note: test username/passwd = plugh1/xyzzy
  54. # defaults:
  55. $OPT{l} = LINK_SPEED unless defined $OPT{l}; # $OPT{l} is the link speed
  56. $OPT{p} = PORT unless defined $OPT{p}; # $OPT{p} is the port
  57. $OPT{d} ||= DOWNLOAD_PATH; # $OPT{d} is the download directory
  58. $OPT{u} ||= $OPT{d}; # $OPT{u} is the upload directory
  59. $OPT{m} ||= PLAYER; # $OPT{m} is the player application
  60. # process link speed a bit
  61. unless ($OPT{l} =~ /^\d+$/ && $OPT{l} <= LINK_T3) {
  62. $OPT{l} = "LINK_$OPT{l}" unless $OPT{l} =~ /^LINK_/i;
  63. $OPT{l} = eval "\U$OPT{l}\E" || 0;
  64. }
  65. $| = 1;
  66. warn "[ connecting... ]\n";
  67. my $addr = shift;
  68. my $nap = MP3::Napster->new($addr)
  69. || die "Couldn't connect: ",MP3::Napster->error,"\n";
  70. $nap->debug($OPT{v}) if defined $OPT{v};
  71. END {
  72. if (defined $nap) {
  73. print "[ logging out, wait... ]\n";
  74. }
  75. }
  76. # set up the download directory
  77. mkdir $OPT{d},0777 or die "Couldn't make directory $OPT{d}: $!\n"
  78. unless -d $OPT{d};
  79. $nap->download_dir($OPT{d});
  80. setup_callbacks($nap);
  81. login() || die "\n" unless $OPT{r};
  82. register() || die "Couldn't register\n" if $OPT{r};
  83. print "\n";
  84. # Update our link speed, if provided
  85. $nap->change_registration(link => $OPT{l}) if $OPT{l};
  86. # Share some files
  87. my $shared_songs = 0;
  88. $shared_songs += $nap->share_dir($OPT{u}) if $OPT{u};
  89. $nap->allow_setport(1);
  90. my $port = $nap->port;
  91. print "[ sharing $shared_songs songs ",$port?"on port $port":"(firewalled)"," ]\n";
  92. print "\n";
  93. $nap->run;
  94. print "Connection closed by server.\n";
  95. exit 0;
  96. sub commands {
  97. shift; # get rid of server object
  98. $_ = shift; # read command
  99. exit 0 unless defined $_;
  100. if (m!^/!) { # a command
  101. my ($command,$args) = m!^/(\w+)\s*(.*)!;
  102. $command = lc $command;
  103. $nap->channels,return if $command eq 'channels';
  104. join_channel($args),return if $command eq 'join';
  105. part_channel($args),return if $command eq 'part';
  106. search($args),return if $command eq 'search';
  107. msg($args),return if $command eq 'msg';
  108. browse($args),return if $command eq 'browse';
  109. download($args),return if $command eq 'download' or $command eq 'g';
  110. status($args),return if $command eq 'status';
  111. play($args),return if $command eq 'play';
  112. capture($args),return if $command eq 'capture';
  113. users($args),return if $command eq 'users';
  114. whois($args),return if $command eq 'whois' or $command eq 'finger';
  115. ping($args),return if $command eq 'ping';
  116. abort($args),return if $command eq 'abort';
  117. boot($args),return if $command eq 'boot';
  118. port($args),return if $command eq 'port';
  119. password($args),return if $command eq 'password';
  120. email($args),return if $command eq 'email';
  121. exit(0) if $command eq 'quit';
  122. help(),return;
  123. }
  124. speak($_) if /\S/;
  125. help() unless /\S/;
  126. }
  127. sub login {
  128. print "login: ";
  129. chomp (my $login = <>);
  130. print STDERR "password: ";
  131. system "stty -echo </dev/tty" unless $ENV{EMACS};
  132. chomp (my $pass = <>);
  133. system "stty echo </dev/tty" unless $ENV{EMACS};
  134. print STDERR "\n";
  135. warn "[ logging in... ]\n";
  136. if (my $email = $nap->login($login,$pass,$OPT{l},$OPT{p})) {
  137. print "[ Logged in with email $email ]\n";
  138. return 1;
  139. } else {
  140. print "[ Couldn't log in: ",$nap->error," ]\n";
  141. return undef;
  142. }
  143. }
  144. sub register {
  145. print "new nickname: ";
  146. chomp (my $login = <>);
  147. my $pass = get_password(1);
  148. print "link speed [14K 28K 33K 56K 64K 128K CABLE DSL T1 T3]: ";
  149. chomp (my $link_speed = <>);
  150. $link_speed = eval "\ULINK_$link_speed\E" || 0;
  151. warn "[ registering... ]\n";
  152. if (my $email = $nap->register($login,$pass,{link=>$link_speed,port=>$OPT{p}})) {
  153. print "[ Registered with email $email ]\n";
  154. return 1;
  155. } else {
  156. print "[ Couldn't register: ",$nap->error," ]\n";
  157. return undef;
  158. }
  159. }
  160. sub port {
  161. my $arg = shift;
  162. return unless $arg =~ /^\d+$/;
  163. print "* Changing port to $arg\n";
  164. $nap->process_event(SET_DATA_PORT,$arg);
  165. }
  166. sub password {
  167. return unless my $pass = get_password(1);
  168. print "* Changing password\n";
  169. $nap->change_registration(password => $pass);
  170. }
  171. sub email {
  172. return unless my $email = shift;
  173. print "* Changing email address\n";
  174. $nap->change_registration(email => $email);
  175. }
  176. sub get_password {
  177. my $new = $_[0] ? 'new ' : '';
  178. system "stty -echo </dev/tty" unless $ENV{EMACS};
  179. print "${new}password: ";
  180. chomp (my $pass1 = <>);
  181. print "\n";
  182. print "Re-enter ${new}password: ";
  183. chomp (my $pass2 = <>);
  184. print "\n";
  185. system "stty echo </dev/tty" unless $ENV{EMACS};
  186. unless ($pass1 eq $pass2) {
  187. print "they don't match\n";
  188. return;
  189. }
  190. return $pass1;
  191. }
  192. sub join_channel {
  193. my $chan = shift;
  194. foreach (split /\s+/,$chan) {
  195. print "[ Can't join $_ ",$nap->error," ]\n"
  196. unless $nap->join_channel($_);
  197. }
  198. my $channels = join(', ',$nap->enrolled_channels) || 'no channels';
  199. print "[ Enrolled in $channels ]\n";
  200. }
  201. sub part_channel {
  202. my $chan = shift;
  203. if ($nap->part_channel($chan)) {
  204. my $channels = join(', ',$nap->enrolled_channels) || 'no channels';
  205. print "[ Departing $chan, now a member of $channels ]\n";
  206. } else {
  207. print "[ Departure unsuccessful: ",$nap->error," ]\n";
  208. }
  209. }
  210. sub users {
  211. unless ($nap->channel) {
  212. print "[ no current channel ]\n";
  213. return;
  214. }
  215. foreach ($nap->users) {
  216. printf "\t[ %-20s sharing %4d files on a %-9s line ]\n",$_,$_->sharing,$_->link;
  217. }
  218. }
  219. sub speak {
  220. $nap->public_message(shift) || print "[ ERROR: no channel selected ]\n";
  221. }
  222. sub msg {
  223. my ($nick,$msg) = shift =~ /^(\S+)\s*(.*)/;
  224. $nap->private_message($nick,$msg);
  225. print "* you tell $nick: $msg\n";
  226. }
  227. sub print_search_results {
  228. my $counter=0;
  229. for my $song (@SEARCH) {
  230. (my $link = $song->link) =~ s/^LINK_//;
  231. printf "%3d. %-18s %-3dkbps %-3.1fM %-8s %-50s\n",
  232. ++$counter,$song->owner,$song->bitrate,$song->size/1E6,$link,$song->name;
  233. }
  234. }
  235. sub search {
  236. my $args = shift;
  237. undef @SEARCH;
  238. $COUNTER = 0;
  239. print "[ searching... ]\n";
  240. @SEARCH = sort {
  241. $a->link_code <=> $b->link_code
  242. || $a cmp $b
  243. } $nap->search($args);
  244. print_search_results;
  245. print "[ search done. ",scalar @SEARCH," songs found ]\n";
  246. }
  247. sub browse {
  248. my $args = shift;
  249. undef @SEARCH;
  250. $COUNTER = 0;
  251. print "[ browsing... ]\n";
  252. @SEARCH = sort {$a cmp $b} $nap->browse($args);
  253. print_search_results();
  254. print "[ browse done. ",scalar @SEARCH," songs found ]\n";
  255. }
  256. sub download {
  257. my $args = shift;
  258. my $fh = shift;
  259. my (@num) = $args =~ /(\d+)/g;
  260. unless (@num) {
  261. print "[ usage: download <song_no> <song_no> <song_no>.... ]\n";
  262. return;
  263. }
  264. foreach (@num) {
  265. my $index = $_-1;
  266. my $song = $SEARCH[$index];
  267. unless ($song) {
  268. print "[ $_: No such song identified on last search ]\n";
  269. return;
  270. }
  271. if (my $d = $song->download($fh)) {
  272. $d->interval(200000); # set reporting interval
  273. print "[ $song: starting download ]\n";
  274. } else {
  275. print "[ $song: ",$nap->error," ]\n";
  276. }
  277. }
  278. }
  279. sub play {
  280. my $args = shift;
  281. my $fh = IO::File->new('|' . PLAYER);
  282. print "[ Couldn't open player ".PLAYER.": $! ] \n" unless $fh;
  283. download($args,$fh);
  284. }
  285. sub capture {
  286. my $args = shift;
  287. my (@num) = $args =~ /(\d+)/g;
  288. my $song = $SEARCH[$num[0]-1];
  289. unless ($song) {
  290. print "[ No such song identified on last search/browse ]\n";
  291. return;
  292. }
  293. my $localfile = $nap->download_dir . '/' . quotemeta($song);
  294. my $player = PLAYER;
  295. my $fh = IO::File->new("| tee $localfile | $player");
  296. print "[ Couldn't open pipe to tee and $player: $! ] \n" unless $fh;
  297. $UNLINK{$song} = $nap->download_dir . '/' . $song;
  298. download($args,$fh);
  299. }
  300. # print download status
  301. sub status {
  302. my $p;
  303. for my $t ($nap->transfers) {
  304. $p++;
  305. my $song = $t->song;
  306. my $status = $t->status;
  307. my $label = $t->direction eq 'upload' ? 'uploading to '.$t->nickname
  308. : 'downloading from '.$song->owner;
  309. print "[ ($label) $song: $status, ",$t->transferred,"/",$t->size," bytes ]\n";
  310. }
  311. print "[ no uploads/downloads in progress ]\n" unless $p;
  312. }
  313. # abort
  314. sub abort {
  315. my $args = shift;
  316. $args ||= '.'; # by default, abort 'em all
  317. for my $d ($nap->downloads) {
  318. # my $song = $d->title;
  319. next unless $d =~ /$args/;
  320. $d->abort;
  321. }
  322. }
  323. # boot uploads
  324. sub boot {
  325. my $args = shift;
  326. $args ||= '.'; # by default, abort 'em all
  327. for my $d ($nap->uploads) {
  328. # my $song = $d->title;
  329. next unless $d =~ /$args/;
  330. $d->abort;
  331. }
  332. }
  333. sub whois {
  334. my $args = shift;
  335. foreach my $u (split /\s+/,$args) {
  336. $u = $SEARCH[$u-1]->owner
  337. if $u =~ /^\d+$/ && defined $SEARCH[$u-1];
  338. if (my $user = $nap->whois($u)) {
  339. my $profile = $user->profile;
  340. $profile =~ s/^/\t/gm;
  341. print $profile,"\n";
  342. } else {
  343. print "[ $u: ",$nap->error," ]\n";
  344. }
  345. }
  346. }
  347. sub ping {
  348. my $args = shift;
  349. return unless my @users = split /\s+/,$args;
  350. @users = map { lc(/^\d+$/ && defined $SEARCH[$_-1] ? $SEARCH[$_-1]->owner : $_)} @users;
  351. my $timing = $nap->ping(\@users,10);
  352. foreach (keys %$timing) {
  353. print " [ $_: $timing->{$_}s ]\n";
  354. }
  355. my %lc = map {lc($_)=>1} keys %$timing;
  356. print " [ $_: NOT pingable ]\n" foreach grep {!$lc{lc($_)}} @users;
  357. }
  358. sub help {
  359. print <<END;
  360. COMMANDS:
  361. /channels list channels
  362. /join <chan1> <chan2>... join channel(s)
  363. /users list users in current channel
  364. /msg <nickname> send private message to user
  365. /whois <nickname> get info on user
  366. /finger <nickname> same as /whois
  367. /browse <nickname> browse user's shared songs
  368. /search <keywords> search for a song
  369. /download <18> <20> <3> download songs 18, 20 and 3
  370. /g <18> <20> <3> same as /download
  371. /play <18> <20> <3> play songs 18, 20 and 3 (requires mpg123 installed)
  372. /capture <18> <20> <3> simultaneously play and capture songs 18, 20 and 3
  373. /abort <regexp> abort downloads matching regular expression (default all)
  374. /boot <regexp> abort uploads matching regular expression (default all)
  375. /status status of uploads/downloads
  376. /ping <nickname> ping a user
  377. /quit outta here
  378. END
  379. ;
  380. }
  381. ############ callbacks #################
  382. # callbacks
  383. sub setup_callbacks {
  384. my $nap = shift;
  385. $nap->command_processor(\&commands);
  386. my $user_speaks = sub {
  387. my ($nap,$ec,$message) = @_;
  388. my ($channel,$nickname,$mess) = $message =~/^(\S+) (\S+) (.*)/;
  389. print "[$channel] <$nickname> $mess\n";
  390. };
  391. my $private_msg = sub {
  392. my ($nap,$ec,$message) = @_;
  393. my ($nickname,$mess) = $message =~/^(\S+) (.*)/;
  394. print "$nickname tells you: $mess\n";
  395. };
  396. my $user_joins = sub {
  397. my ($nap,$ec,$user) = @_;
  398. my $channel = $user->current_channel;
  399. my $sharing = $user->sharing;
  400. my $link = $user->link;
  401. print "* $user joins $channel: sharing $sharing files on a $link line\n";
  402. };
  403. my $user_exits = sub {
  404. my ($nap,$ec,$user) = @_;
  405. my $channel = $user->current_channel;
  406. print "* $user has left $channel\n";
  407. };
  408. my $list_channel = sub {
  409. my ($nap,$ec,$channel) = @_;
  410. printf "[ %-15s %-40s %3d users ]\n",$channel,$channel->topic,$channel->user_count;
  411. };
  412. my $channel_topic = sub {
  413. my ($nap,$ec,$message) = @_;
  414. my ($channel,$banner) = $message =~ /^(\S+) (.*)/;
  415. print "[ \U$channel\E: $banner ]\n";
  416. };
  417. my $stats = sub {
  418. my ($users,$files,$gigs) = split /\s+/,$_[2];
  419. print "\t** SERVER STATS: $files files, $users users, ($gigs gigs) **\n";
  420. };
  421. my $transfer_started = sub {
  422. my ($nap,$ec,$transfer) = @_;
  423. return unless $transfer->direction eq 'upload';
  424. my $song = $transfer->song;
  425. my $nick = $transfer->remote_user;
  426. print "\t[ $nick has begun to download $song ]\n";
  427. };
  428. my $transfer_status = sub {
  429. my ($nap,$ec,$transfer) = @_;
  430. my $status = $transfer->status;
  431. print "\t[ $transfer: $status ]\n";
  432. };
  433. my $transfer_progress = sub {
  434. my ($nap,$ec,$transfer) = @_;
  435. my ($bytes,$expected) = ($transfer->transferred,$transfer->size);
  436. print "\t[ $transfer: $bytes / $expected bytes ]\n";
  437. };
  438. my $transfer_done = sub {
  439. my ($nap,$ec,$transfer) = @_;
  440. my $song = $transfer->song;
  441. my $file = $transfer->local_path || $UNLINK{$song};
  442. print "\t[ $song done: ",$transfer->status," ]\n";
  443. if ($transfer->direction eq 'download' &&
  444. $transfer->status ne 'transfer complete' &&
  445. $file) {
  446. print "\t[ $song incomplete: unlinking $file ]\n";
  447. unlink $file;
  448. delete $UNLINK{$song};
  449. }
  450. };
  451. my $error = sub {
  452. my ($nap,$code,$msg) = @_;
  453. print "* error: $msg\n";
  454. };
  455. $nap->callback(PUBLIC_MESSAGE, $user_speaks);
  456. $nap->callback(PRIVATE_MESSAGE, $private_msg);
  457. $nap->callback(USER_JOINS, $user_joins);
  458. $nap->callback(USER_DEPARTS, $user_exits);
  459. $nap->callback(INVALID_ENTITY, $error);
  460. $nap->callback(CHANNEL_ENTRY, $list_channel);
  461. $nap->callback(CHANNEL_TOPIC, $channel_topic);
  462. $nap->callback(SERVER_STATS, $stats);
  463. $nap->callback(TRANSFER_STARTED, $transfer_started);
  464. $nap->callback(TRANSFER_STATUS, $transfer_status);
  465. $nap->callback(TRANSFER_IN_PROGRESS,$transfer_progress);
  466. $nap->callback(TRANSFER_DONE, $transfer_done);
  467. $nap->callback(MOTD, sub { print defined $_[2] ? "* $_[2]\n" : "\n"; } );
  468. $nap->callback(PING, sub { print "* $_[2] is pinging us\n"; } );
  469. $nap->callback(PONG, sub { print "* Got a PONG from $_[2]\n"; } );
  470. $nap->callback(SET_DATA_PORT, sub { print "* Changing data port to $_[2]\n"; } );
  471. $nap->callback(DATA_PORT_ERROR, sub { print "* Server: data port is misconfigured\n"; } );
  472. my $s = sub {
  473. my($server,$ec,$message) = @_;
  474. my $event = $server->event;
  475. warn "$event: $message\n";
  476. };
  477. if ($OPT{v}) {
  478. $nap->callback($_,$s) for (0..900,2000..2007,9999);
  479. }
  480. }
  481. __END__
  482. !NO!SUBS!
  483. close OUT or die "Can't close $file: $!";
  484. chmod 0755, $file or die "Can't reset permissions for $file: $!\n";
  485. exec("$Config{'eunicefix'} $file") if $Config{'eunicefix'} ne ':';
  486. chdir $origdir;