#! /usr/bin/perl # mailquotes.pl # # mailquotes -- perl script to generate E-mail # autoresponder for stock quotes # mailquotes (C) 2005, pettingers.org, All Rights reserved under GPL # license: # http://www.gnu.org/licenses/gpl.txt # # # Full documentation for this program can be found at: # http://www.pettingers.org/code/stockchecker.html # use strict; use MIME::Lite; use Finance::Quote; use Finance::QuoteHist::Yahoo; ##################################### ####### User Defined Variables ###### # my $limit = 20000; # Max characters for outbound message minus header our $stocklist = "/var/www/html/users/stocks"; # Watchlist of stocks my $fromaddr = '"Mail Quotes" '; # Who the message will come from my $celladdr = '1235551234@mobile.att.com'; # Pager or cell address of admin/monitor my $notify = 1; # set to false (0) for no activity notification my $abuse = 'abuse@example.com'; # Abuse or admin E-mail for your server my $subj = 'Your Quotes!'; # Subject for the outbound message ##################################### ### End user Difined Variables ### # undef $/; my %hdrs; my @additional; my ($infrom, $insubject, $data, $message, $msg) = ''; $message = <>; $message =~ s/\n\s+/ /g; %hdrs = (UNIX_FROM => split /^(\S*?):\s*/m, $message); chop ($infrom = $hdrs{"From"}); # Find out who its from and chop the \n chop ($insubject = $hdrs{"Subject"}); # Get the subject and chop the \n # Be very picky about what is in the From: field. If it looks goofy, bail out. if ( $infrom =~ m/\<.+\@.+\>/ ) { if ($infrom =~ m/.*\<(.+)\>/ ) {$infrom = $1;} else {$infrom = '';} } else {exit;} $data = "This message is an automatically generated response to a request from $infrom\.\n"; $data .= "If you received this E-mail in error or did not request it, please contact us \n"; $data .= "immediately at: $abuse\n\n"; $insubject =~ tr/a-z/A-Z/; # Do sanity check on subject, then do more sanity checks! if (($insubject =~ m/^[\w, ]+$/) and ($insubject =~ m/^[A-Z]{1,5}[, ]*/)){ @additional = usercheck($insubject); # If we've got a good subject, do a custom lookup on the ticker symbol(s) $data .= displayone(@additional); } # Build up the message body with the watchlist $data .= displayall(); # Package up the message with a max length of $limit $data = substr($data, 0, $limit); # Format the message to send with MIME::Lite package $msg = MIME::Lite->new( From =>$fromaddr, To =>$infrom, Subject =>$subj, Data =>$data ); # Send it best way possible (usually sendmail) $msg->send; # # Build up message for admin/monitor. # set $notify to 0 above if you don't care to have this sent. # if ($notify) { $msg = MIME::Lite->new( From =>$fromaddr, To =>$celladdr, Subject =>"Quotes sent", Data =>"User-- $infrom" ); # Send it best way possible (usually sendmail) $msg->send; } exit; ######################################################### sub displayone { # # # Used to display quote information for one or more stocks # that were originally placed in the subject of the requesting E-mail. # use Finance::Quote; my $quoter = Finance::Quote->new; my ($stock, $build) = ''; my @lists = @_; $quoter->timeout(30); # Load custom, one-time quotes into %info. my %info = $quoter->fetch("usa", @lists); foreach $stock (@lists) { chomp ($stock); $stock =~ tr/a-z/A-Z/; # one last check, probably not needed. unless ($info{$stock, "success"}) { warn "Lookup of $stock failed - ".$info{$stock, "errormsg"}. "\n"; next; } $build .= "\n\n" . $info{$stock, "name"}. " \($stock\):\n"; $build .= '=====================' ."\n"; $build .= '52-wk Range: '. $info{$stock, "year_range"} . "\n"; # Call for historical quotes $build .= historical($stock,'3 months ago','3 Months Ago','2','1','0'); $build .= historical($stock,'2 months ago','2 Months Ago','2','1','0'); $build .= historical($stock,'2 days ago','2 Days Ago ','2','1','0'); $build .= '***Current: '. "Net Change: ". $info{$stock, "net"} . " \(". $info{$stock, "p_change"} . "\%\)". ' '. "Price: " . $info{$stock, "price"}. "\n"; $build .= '***Current: '. "Vol: ". $info{$stock, "volume"}. ' '. "Day Range: ". $info{$stock, "day_range"}. ' '. "Open: ". $info{$stock, "open"} . "\n"; } return $build; # Send the (partially) completed body back to the calling main } # End displayone #################################### ################################### sub displayall { # # Used to build the message body with quotes from the watchlist # use Finance::Quote; my $quoter = Finance::Quote->new; my ($stock, $build) = ''; my @lists; $quoter->timeout(30); open (USERIN, $stocklist); # @lists = ; Doesn't work??? $build = ; close (USERIN); @lists = split(/\n/, $build); #why do we need this? $build = ''; my %info = $quoter->fetch("usa", @lists); foreach $stock (@lists) { chomp ($stock); unless ($info{$stock, "success"}) { warn "Lookup of $stock failed - ".$info{$stock, "errormsg"}. "\n"; next; } $build .= "\n\n" . $info{$stock, "name"}. " \($stock\):\n"; $build .= '=====================' ."\n"; $build .= '52-wk Range: '. $info{$stock, "year_range"} . "\n"; # Call for historical quotes $build .= historical($stock,'3 weeks ago','3 Weeks Ago','2','1','0'); $build .= historical($stock,'2 Weeks ago','2 Weeks Ago','2','1','0'); $build .= historical($stock,'1 Week ago','1 Week Ago ','2','1','0'); $build .= '***Current: '. "Net Change: ". $info{$stock, "net"} . " \(". $info{$stock, "p_change"} . "\%\)". ' '. "Price: " . $info{$stock, "price"}. "\n"; $build .= '***Current: '. "Vol: ". $info{$stock, "volume"}. ' '. "Day Range: ". $info{$stock, "day_range"}. ' '. "Open: ". $info{$stock, "open"} . "\n"; } return $build; } # End displayall ################################################################## sub usercheck { # # Simple sanity checks for user entered lists # my ($entered) = @_; my @adds; for ($entered) { s/,/ /g; s/^\s+//; s/\s+$//; s/\s+/ /g; } $entered =~ tr/a-z/A-Z/; unless ($entered =~ m/^[\w ]+$/) { $entered = ''; } $entered = substr($entered,0,100); @adds = split(/ /,$entered); return @adds; } # End usercheck ############################################################### sub historical { # Used to provide historical quotes from Yahoo. # # Pass follwing: # # ticker # date of interest (Date::Manip format) # coloquial name for date (e.g. 2 weeks ago) # decimal place precision # attempt number time out for requests # full information (1) or just close price (0) # use Finance::QuoteHist::Yahoo; my ($stock, $datein, $datename, $prec, $attempt, $full) = @_; my ($buildh, $row, $symbol, $date, $open, $high, $low, $close, $volume) = ''; my $q = Finance::QuoteHist::Yahoo->new ( symbols => $stock, start_date => $datein, end_date => $datein, quote_precision => $prec, attempts => $attempt ); foreach $row ($q->quotes()) { ($symbol, $date, $open, $high, $low, $close, $volume) = @$row; if ($full) { $buildh .= "$datename: Open:$open High:$high Low:$low Close:$close Vol:$volume\n"; } else { $buildh .= "$datename: $close\n"; } } return $buildh; } # end historical # end