#!/usr/bin/perl

=head1 NAME

ticker - A stock monitoring client written for Peep: The Network
Auralizer.

Peep takes virtual events and turns them into unobtrusive sounds like
birds chirping, rain falling, crickets doing whatever crickets do,
etc.  It is mostly used for network and systems monitoring because it
is well suited for that task.

Since there are some stocks I follow I decided to make my first custom
client a stock ticker.  Thanks to Finance::Quote, it took me about a
half-hour to write :-) A rooster crow or doorbell chime tells me that
something is happening with my stocks.  And at the end of the day, it
e-mails me a report on any questionable activity.

There are quite a few comments, but the code itself is quite short.

=head1 USAGE

  ./ticker --help

  ./ticker --noautodiscovery --server=localhost --port=2001 --nodaemon

  ./ticker --output=/var/log/peep/ticker.log

If you have any problems, try turning on debugging output with
something like --debug=9.

=head1 CONFIGURATION

To use this client, include a section like the following in peep.conf:

  client ticker
    class home
    port 1999
    config
      # Stock symbol  Stock exchange    Event             Max      Min  
      RHAT            nasdaq            red-hat           4.0      3.6
      SUNW            nasdaq            sun-microsystems  9.0      8.0
    end config
    notification
      notification-hosts localhost
      notification-recipients bogus.user@bogusdomain.com
      notification-level warn
    end notification
  end client ticker

and another section in the events block with something like

  events
  #Event Type      |          Path to Sound File           | # of sounds to load
  ...
  red-hat            /usr/local/share/peep/sounds/misc/events/rooster.*        1
  sun-microsystems   /usr/local/share/peep/sounds/misc/events/doorbell.*       1
  end events

=head1 AUTHOR

Collin Starkweather <collin.starkweather@colorado.edu> Copyright (C) 2001

=head1 SEE ALSO 

perl(1), peepd(1), Net::Peep, Net::Peep::Client, Net::Peep::BC,
Net::Peep::Notifier, Net::Peep::Notification, Finance::Quote

http://peep.sourceforge.net

=cut

# Always use strict :-)
use strict;
use Net::Peep::BC;
use Net::Peep::Log;
use Net::Peep::Client;
use Net::Peep::Notifier;
use Net::Peep::Notification;
use Finance::Quote;
use vars qw{ %config $logger $client $quoter $conf };

# The Net::Peep::Log object will allow us to print out some things in
# a nicely formatted way so we can tell ourselves what we're doing ...
$logger = new Net::Peep::Log;

# Instantiate a Peep client object.  The client object handles most of
# the dirty work for us so we don't have to worry about things such as
# forking in daemon mode or parsing the command-line options or the
# Peep configuration file.  For more information, perldoc
# Net::Peep::Client
$client = new Net::Peep::Client;
$quoter = new Finance::Quote;

# First we have to give the client a name
$client->name('ticker');

# Now we initialize the client.  If the initialization returns a false
# value, we display documentation for the script.
$client->initialize() || $client->pods();

# Now we assign a parser that will parse the 'ticker' section of the
# Peep configuration file
$client->parser( \&parse );

# Now we tell the client to get the information from the configuration
# file.  It returns a  Net::Peep::Conf, the Peep configuration object,
# which contains information from the configuration file.
$conf = $client->configure();

# Register a callback which will be executed every 60 seconds.  The
# callback is simply a function which checks the price of the stock
# and peeps every time it exceeds the maximum or minimum price that
# has been set.
$client->callback( \&loop );

$SIG{INT} = $SIG{TERM} = sub { $client->shutdown(); exit 0; };

# Start looping.  The callback will be executed every 60 seconds ...
$client->MainLoop(60);

sub parse {

    # Parse the config section for the ticker client in the Peep
    # configuration file

    # We'll use a regular expression to extract the ticker information
    # and stuff it into a data structure (the global variable %config)

    # This subroutine will be used to parse lines from peep.conf such
    # as the following and store the information in %config:
    #   RHAT            nasdaq            red-hat           4.0      3.6
    #   SUNW            nasdaq            sun-microsystems  9.0      8.0
    for my $line (@_) {
	if ($line =~ /^\s*([A-Z]+)\s+(\w+)\s+([\w\-]+)\s+([\d\.]+)\s+([\d\.]+)/) {
	    my ($symbol,$exchange,$event,$max,$min) = ($1,$2,$3,$4,$5,$6);
	    $config{$symbol} = { event=>$event, exchange=>$exchange, max=>$max, min=>$min };
	}
    }

} # end sub parse

sub loop {

    # The %config hash is built in the parse() subroutine from
    # information gleaned from the Peep configuration file
    for my $key (sort keys %config) {
	$logger->log("Checking the price of [$key] ...");
	# Fetch some information about the stock including the price
	my %results = $quoter->fetch($config{$key}->{'exchange'},$key);
	my $price = $results{$key,'price'};
	$logger->log("\tThe price of [$key] is [$price].");
	if ($price ne '') {
	    if ($price > $config{$key}->{'max'} or $price < $config{$key}->{'min'}) {
	        $logger->log("\tThe price is out of bounds!  Sending notification ....");
	        # The price is out of bounds!  We'll start peeping ...
	        my $broadcast = Net::Peep::BC->new('ticker',$conf);
	        $broadcast->send('ticker',
	    		     type=>0, 
	    		     sound=>$config{$key}->{'event'},
	    		     location=>128, 
	    		     priority=>0, 
	    		     volume=>255);
	        # In case we're away from our desk, we'll also send out an
	        # e-mail notification.  Don't want to miss the action!
	        my $notifier = new Net::Peep::Notifier;
	        my $notification = new Net::Peep::Notification;
	        $notification->client('ticker');
	        $notification->status('crit');
	        $notification->datetime(time());
	        $notification->message("The price of $key is $price!");
	        $notifier->notify($notification);
	    }
	}
    }

} # end sub loop

__END__

