#!/usr/bin/perl
##
## stock quote notifier, written by torh@bogus.net
##
## v0.12 07/01/2000 - initial release
## v0.13 08/01/2000 - bugfix, didn't read in thresholds correctly from file
##                  - also didn't like blank lines very much
##                  - elvis' birthday
##
## v0.14 02/02/2000 - added proxy support for those firewalled people
##                  - changed from using LWP::Simple to LWP::UserAgent
##                  - added "useragent" support
##
## v0.15 04/02/2000 - added support for HP printer display
##                  - thanks to W. Mansfield (mansfieldw@globalsportsinc.com)
##                  - for this idea. this code contains a perl re-write of
##                  - silicosis's (sili@l0pht.com) "HP Printer Hack" of '97.
##
## v0.16 22/03/2001 - fixes for making it run with latest version of perl
##
## v0.17 08/10/2001 - fool (bless 'em) changed their quote page a while 
##                  - back; this is a fix to make it working again.
##
## This program comes with NO WARRANTIES whatsoever. If you bankrupt yourself
## because you trusted the output of this program, it's not my problem.
##
$verno="0.17";
$version="QuoteNotifier v$verno 08/10/2001 (c) torh\@bogus.net";

$user=$ENV{USER};
chop($hostname=`hostname`);

use Getopt::Long;
use LWP::UserAgent;
use Mail::Sendmail;
use Socket;

## get some options from the command line
##
GetOptions("s=s" => \$symbol,"t=s" => \$threshold,"mh=s" => \$mailhost,
	   "m=s" => \$email,"f=s" => \$symbolfile,"h","v","x","e",
	   "ph=s" => \$proxyhost,"pp=s" => \$proxyport,
	   "ua=s" => $useragent,"p=s" => \$printer,"phf=s" => \$printhostfile);

## new user agent
##
$ua=new LWP::UserAgent;

## hewlett-packard laser printer support! kinda cool, huh?
##
$pc=0;
$printport=9100;

if($printer) {
    $printhost=gethostbyname($printer);
    if(!$printhost) {
	print STDERR "FATAL: gethostbyname('$printer') failed\n";
	exit(1);
    }
    @printers[$pc]=$printhost;
    $pc=1;
}

## support for multiple printers; format is
##
## host\n
## host\n
## ..
##
## lines beginning with ';' are ignored; too bad if there are
## any hosts out there with these characters in them.
##
if($printhostfile) {
    open(PRINTERS,"<$printhostfile") || die "ERROR: unable to open printhost file: $!\n";
    while(<PRINTERS>) {
	if(/^;.*/) {next;}
	chop($_);
	($input)=/^\s*(\S+)\s*/;
	$printhost=gethostbyname($input);
	if(!$printhost) {
	    print STDERR "WARNING: gethostbyname('$input') failed (skipping)\n";
	} else {
	    @printers[$pc++]=$printhost;
	}
    }
    close(PRINTERS);
    if(!$pc) {
	print STDERR "FATAL: no suitable printers found in hostfile\n";
	exit(1);
    }
}

## the added proxy support
##
if($proxyhost) {
    if(!$proxyport) {
	$proxyport = 8080; ## default port for most proxies
    }
    $ua->proxy("http","http://$proxyhost:$proxyport/");
}

## configure useragent
##
if($useragent) {
    $ua->agent("QuoteNotifier/$verno");
} else {
    $ua->agent($useragent);
}

## if no mailhost, use localhost
##
if(!$mailhost) {
    $mail{smtp}="localhost";
} else {
    $mail{smtp}=$mailhost;
}

## we are assuming SMTP (tcp port 25); if you have a screwed up
## system, you are welcome to _manually_ edit the code here.
##
$mail{port}=25;

## where is the xmessage binary?
##
if($opt_x) {
    $xmessage=`which xmessage`;
    if(!$xmessage) {
	print STDERR "WARNING: cannot locate xmessage binary, disabling xmessage popup.\n";
    } else {
	chop($xmessage);
    }
}

## output some help
##
if($opt_h) {
    print "$version\n";
    print "usage: quotenotifier [-h|-v] [-m <address>] [-f <file>]\n";

    print "                     [-ph <proxyhost> [-pp <proxyport>]]\n";
    print "                     [-p <address> | -phf <file> ] [-e]\n";
    print "                     -s <symbol> -t <value>[+]|-\n";
    print "       -h : this help\n";
    print "       -v : print version string\n";
    print "       -s : ticker symbol (e.g. CIEN)\n";
    print "       -f : file containing ticker symbols and thresholds (optional)\n";
    print "       -t : stock threshold value, decimal (e.g. 76.2[+] or 67-)\n";
    print "       -m : email notification (optional)\n";
    print "      -mh : mail host (default: localhost)\n";
    print "      -ph : proxy host (optional)\n";
    print "      -pp : proxy port (optional, default: 8080)\n";
    print "      -ua : user agent string (optional, defaults to \'QuoteNotifier/$verno\'\n";
    print "       -p : HP printer address (optional)\n";
    print "     -phf : file containing IP adresses of HP printers (optional)\n";
    print "       -e : make sure we also echo to STDOUT (optional)\n";
    print "\ne.g. quotenotifier -m torh\@bogus.net -s sunw -ph localhost -pp 3128\n\n";
    exit(0);
}

if($opt_v) {
    print "$version\n";
    exit(0);
}

if(!$symbol && !$symbolfile) {
    print STDERR "FATAL: can't perform without ticker symbol or ticker symbol file!\n";
    exit(1);
}

if($symbol && $symbolfile) {
    print STDERR "FATAL: don't know how to use -s and -f options together!\n";
    exit(1);
}

uc($symbol);

if($symbol) {

    $request= new HTTP::Request('GET',"http:\/\/quote.fool.com\/news/symbolnews.asp?symbols=$symbol");
    $response = $ua->request($request);

    $response=$response->content;

    $quote_start='.*mw_news_last><NOBR>';
    $response=~/$quote_start(\d+\.\d+).*/s;
    $currentvalue=$1;

    if(!$currentvalue) {
	$response=~/$quote_start(\d+).*/s;
	$currentvalue=$1;
	if(!$currentvalue) {
	    ReportPageError();
	    exit(1);
	}
    }

    ## print/email some action
    ##

    if($threshold=~/.*\d+-/) {
	if($currentvalue<$threshold) {
	    if($email) {
		SendEmail($email,$symbol,"NEGATIVE");
	    } else {
		PrintAlert("NEGATIVE");
	    }
	}
    } else {
	if($currentvalue>$threshold) {
	    if($email) {
		SendEmail($email,$symbol,"POSITIVE");
	    } else {
		PrintAlert("POSITIVE");
	    }
	}
    }

} else {

    ## file must be in format <symbol><whitespace><threshold>\n
    ## threshold is a DECIMAL or INTEGER value, no fractions!
    ## i.e. :
    ##
    ## SUNW 78.00+ ; maybe sell sun if above 78 bucks?
    ## CIEN 24-    ; perhaps buy ciena if below 24 bucks
    ## WIND 34.2   ; perhaps sell wind river if above 34.2 dollars
    ##

    open(SYMBOLS,"<$symbolfile") || die "ERROR: Couldn't open \'$symbolfile\': $!\n";

    while(<SYMBOLS>) {
	($symbol,$threshold)=/\s*(\S+)\s+(\d+\.\d+\S*)\s*.*/;

	## if no symbol or threshold found, try a little harder
	##

	if(!$symbol || !$threshold) {
	    ($symbol,$threshold)=/\s*(\S+)\s+(\d+\S*)\s*.*/;
	}

	## skip lines we can't parse
	##

	next if (!$symbol || !$threshold);

	$request= new HTTP::Request('GET',"http:\/\/quote.fool.com\/simple.asp?symbols=$symbol&Format=Decimal&print=true");
	$response = $ua->request($request);

	$response=$response->content;

	$quote_start='.* nowrap .* geneva\S+<b>';
	$response=~/$quote_start(\d+\.\d+).*/s;
	$currentvalue=$1;
	if(!$currentvalue) {
	    $response=~/$quote_start(\d+).*/s;
	    $currentvalue=$1;
	    if(!$currentvalue) {
		ReportPageError();
		exit(1);
	    }
	}

	if($threshold=~/.*\d+-/) {
	    if($currentvalue<$threshold) {
		if($email) {
		    SendEmail($email,$symbol,"NEGATIVE");
		} else {
		    PrintAlert("NEGATIVE");
		}
	    }
	} else {
	    if($currentvalue>$threshold) {
		if($email) {
		    SendEmail($email,$symbol,"POSITIVE");
		} else {
		    PrintAlert("POSITIVE");
		}
	    }

	}
    }
    close(SYMBOLS);
}

exit(0);

## subroutines
##

sub SendEmail {
    my ($address,$ticker,$updown)=@_;
    if(!$threshold) {$threshold=0};
    %mail = (To=>$address,
	     From=>"QuoteWatcher <$user\@$hostname>",
	     Subject=>"[QuoteWatch] $ticker now $updown at $currentvalue (t=$threshold)",
	     Body=>' ');
    sendmail(%mail) or die $Mail::Sendmail::error;
}

## another one!
##

sub PrintAlert {
    my ($updown)=@_;
    if(!$threshold) {$threshold=0};

    ## xmessage popup
    ##
    if($xmessage) {
	system("$xmessage -fg gold -bg navy -nearmouse -timeout 180 -buttons OK:0 \"$symbol is $updown at $currentvalue (t=$threshold)\">/dev/null&");
	if($opt_e) {PrintTTY($updown);}
	return();
    }

    ## send some data to the printer
    ##
    if($printer || $printhostfile) {
	
	## now we try to communicate with the printer ...
	##

	$sockaddr = 'S n a4 x8';
	($name,$aliases,$proto) = getprotobyname('tcp');
	($name,$aliases,$port) = getservbyname($printport,'tcp');
	($name,$aliases,$type,$len,$thisaddr) = gethostbyname($hostname);

	foreach $printer (@printers) {
	    ($name,$aliases,$type,$len,$thataddr) = gethostbyname($printer);
	    $this=pack($sockaddr,AF_INET,0,$thisaddr);
	    $that=pack($sockaddr,AF_INET,$printerport,$thataddr);

	    ## create socket, give it an address, then call up the printer
	    ##

	    if(!socket(S,AF_INET,SOCK_STREAM,$proto)) {die "FATAL: socket() failed: $!\n";}
	    if(!bind(S,$this)) {die "FATAL: bind() failed: $!\n";}
	    if(!connect(S,$that)) {die "FATAL: connect() failed: $1!\n";}

	    ## command buffered socket; probably not needed.
	    ##

	    select(S);$|=1;select(STDOUT);

	    print S "\033%-12345X\@PJL RDYMSG DISPLAY = \"$symbol \@ $currentvalue\"\r\n\033%-12345X\r\n"; 

	}
	
	if($opt_e) {PrintTTY($updown);}
	return();
    }

    ## echo to TTY
    ##
    PrintTTY($updown);
}

sub PrintTTY {
    my($updown)=@_;
    print "ALERT: $symbol is now $updown at $currentvalue (t=$threshold)\n";
}

## and more
##

sub ReportPageError {
    print STDERR "FATAL: failed to find current stock value for '$symbol'!\n";
}







