#!/usr/bin/perl

# Announce incoming email using OS X's "Text to speech" facility.
# Copyright 2004 Mark T. Abbott
# Version 0.1.1, 2004-05-12
# Version 0.1.2, 2008-09-28

# Initialize.
use strict;
my ($d_home, $f_self) = ($0 =~ m#(.+)/([^/]+)$#);
my $f_lastrun	= "/tmp/$f_self.lastrun";
my $f_pid	= "/tmp/$f_self.pid";
my $f_log	= "/tmp/$f_self.log";

my $f_touch = '/usr/bin/touch';

# Some senders may not have the "human name" portion of their address
#  defined properly.  The optional file "lib/email_names.pl" defines
#  a hash mapping these email addresses to human names.
require "$d_home/lib/email_names.pl" if -e "$d_home/lib/email_names.pl";
our %email_name;


# NB: this script is launched by a recurring cron job.  The reason I
#  did it this way was I wanted to make it easy to modify the script
#  and have it restart automatically.  As a result, we first need to
#  check whether another instance is running, and if so, exit.  

# Check whether another instance is running.
open PIDFILE, $f_pid;
my $pid = <PIDFILE>;
chomp $pid;
close PIDFILE;
if ( $pid and kill(0, $pid) )
{
	# Another process is running.
	exit;
}
else
{
	# No process is running; write the pidfile and continue.
	open PIDFILE, ">$f_pid"	or	die $!;
	print PIDFILE "$$\n";
	close PIDFILE;
	&log('starting new instance');
}

# Make sure the timestamp file exists; otherwise create it.

if (!-e $f_lastrun)
{
	system($f_touch, $f_lastrun);
	&log('writing timestamp file');
}

while (1)
  {
    &say_message(&recent_email_senders) unless &after_hours;
    sleep(1*60);

    # Quit if this script has been modified.  
    last	if	-M($0)<0;
  }
&log('script modified since this instance began; exiting');
exit;


###  Primary routines.  ###

sub recent_email_senders
{
	# Get a stream of From lines on recent messages.

	my @froms = ();
	my %seen_from;
	open STREAM, "/usr/bin/find /Users/$ENV{'USER'}/Library/Mail -type f -newer $f_lastrun |"
		or	die $!;
	while (<STREAM>)
	{
		chomp;
		my $file = $_;

		# Skip my own email (sent and unsent).
		my $mbox = '\.(?:imap)?mbox';
		next	if	$file =~ m#Deleted Messages$mbox#;
		next	if	$file =~ m#Drafts$mbox#;
		next	if	$file =~ m#Outbox$mbox#;
		next	if	$file =~ m#Sent Messages$mbox#;

		open FILE, $file	or	die $!;
		LINE:
		my ($first_from, $last_from);
		while (<FILE>)
		{
			next	unless	m/From: (.+)/;
			my $from = $email_name{$1} || $1;
			if ( $from =~ m/^([^<>]+\S)\s*<\S+@\S+>$/ )
			{
				$from = $email_name{$1} || $1;
			}
			$first_from ||= $from;
			$last_from    = $from;
		}
		close FILE;

		&log("first and last froms differ ($first_from, $last_from) in [$file]")
			if $first_from ne $last_from;
		&log("$last_from in [$file]");
		next unless $last_from;
		next if length($last_from) > 30;
		push @froms, $last_from	unless	$seen_from{$last_from}++;
	}
	close STREAM;
	system($f_touch, $f_lastrun);

	&log("found " . scalar(@froms) . " froms: " . join(', ', @froms));

	$#froms = 3 if $#froms > 3;

	return @froms;
}

sub say_message
{
	my (@froms) = @_;
	@froms = grep { not m/mark\@bbott.org/ } @froms;
	return unless @froms;

	map { s/bloch/block/g } @froms;

	# Check whether iTunes is running.  
	my $itunes_running = 0;
	open ITCHECK, "/bin/ps -axww | /usr/bin/grep iTunes.app | /usr/bin/grep -vc grep |"
		or	die $!;
	while (<ITCHECK>)
	{
		chomp;
		$itunes_running = $_;
		last;
	}
	close ITCHECK;

	my $message = join '  ', map { "New mail from: $_." } @froms;
	$message =~ s/"//g;

	my @pause_script = (
		'set itunesPlaying to 0',
		'tell application "iTunes"', 
			'if player state is playing then',
				'set itunesPlaying to 1',
				'pause', 
			'end if',
		'end tell'
	);
	my @play_script =  (
		'if itunesPlaying is 1 then',
			'tell application "iTunes"', 
				'play',  
			'end tell',
		'end if',
	);
	my @say_script = "say \"$message\"";

	my @script = $itunes_running? 
		(@pause_script, @say_script, @play_script) : @say_script;

	my @e_script = ();
	for (@script)
	{
		push @e_script, '-e', $_;
	}

	&log(join '; ', @script);
	system('/usr/bin/osascript', @e_script);
}


###  Secondary routines.  ##

sub log
{
	open LOG, ">>$f_log";
	print LOG join("\t", scalar(localtime), @_), "\n";
	close LOG;
}

sub after_hours
  {
    my @lt = localtime;
    my $hour = $lt[2];
    return ( $hour < 7 or  20 < $hour );
  }
