#!/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 = ; 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 () { 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 () { 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 () { 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 ); }