Changed announceEMail to stop polling and use a callback
authorAndrew DeFaria <Andrew@DeFaria.com>
Tue, 7 Jul 2020 20:50:30 +0000 (13:50 -0700)
committerAndrew DeFaria <Andrew@DeFaria.com>
Tue, 7 Jul 2020 20:50:30 +0000 (13:50 -0700)
This was done by switching to using Mail::IMAPTalk. No IMAPTalk does not
do the speaking but it "talks" to IMAP and it allows me to register a
callback. Much more efficient and timely.

Maybe someday I'll implement a mechanism of looking for other
announceEmail's on the LAN and have it inform the others that this
announceEmail will handle the announcement. This is because I run this
on both my desktop and my MacBook so while they're both in the same room
I hear the announcement twice. One solution would be to poll the LAN
and see if there are any more announceEmail processes and then use
IPC to communicate between them. Of course, if they are physically
very distant we probably want the announcement being a true broadcast.

bin/announceEmail.pl

index 0fae494..7e840e1 100755 (executable)
@@ -17,7 +17,7 @@ Andrew DeFaria <Andrew@DeFaria.com>
 
 =item Revision
 
-$Revision: 1.0 $
+$Revision: 1.1 $
 
 =item Created:
 
@@ -44,7 +44,6 @@ $Date: 2019/04/04 13:40:10 $
    -use|rname: User name to log in with (Default: $USER)
    -p|assword: Password to use (Default: prompted)
    -i|map:     IMAP server to talk to (Default: defaria.com)
-   -s|leep:    Number of minutes to sleep inbetween checking mail (Default: 1)
 
 =head1 DESCRIPTION
 
@@ -63,8 +62,7 @@ use warnings;
 use FindBin;
 use Getopt::Long;
 use Pod::Usage;
-use Net::IMAP::Simple;
-use Email::Simple;
+use Mail::IMAPTalk;
 use MIME::Base64;
 
 use lib "$FindBin::Bin/../lib";
@@ -74,7 +72,6 @@ use Logger;
 use Utils;
 
 my $defaultIMAPServer = 'defaria.com';
-my $defaultSleeptime  = 1;
 my $IMAP;
 my %unseen;
 my $log;
@@ -88,7 +85,6 @@ my %opts = (
   username => $ENV{USER},
   password => $ENV{PASSWORD},
   imap     => $defaultIMAPServer,
-  sleep    => $defaultSleeptime,
 );
 
 sub interrupted {
@@ -114,33 +110,25 @@ sub debugit($) {
 } # logit
 
 sub unseenMsgs() {
-  my %unseenMsgs;
+  $IMAP->select('inbox') or
+    $log->err("Unable to select inbox: " . get_last_error(), 1);
 
-  for (my $i = 1; $i <= $IMAP->status; $i++) {
-    $unseenMsgs{$i} = 0 unless $IMAP->seen($i);
-  } # for
-
-  return %unseenMsgs;
+  return map { $_=> 0 } @{$IMAP->search('not', 'seen')};
 } # unseenMsgs 
 
 sub Connect2IMAP() {
   $log->msg("Connecting to $opts{imap} as $opts{username}...", 1);
 
-  $IMAP = Net::IMAP::Simple->new($opts{imap}) ||
-    $log->err("Unable to connect to IMAP server $opts{imap}: " . $Net::IMAP::Simple::errstr, 1);
+  $IMAP = Mail::IMAPTalk->new(
+    Server   => $opts{imap},
+    Username => $opts{username},
+    Password => $opts{password},
+  ) or $log->err("Unable to connect to IMAP server $opts{imap}: $@", 1);
 
   $log->msg(' connected');
 
-  $log->msg("Logging onto $opts{imap} as $opts{username}...", 1);
-
-  unless ($IMAP->login($opts{username}, $opts{password})) {
-    $log->err("Login to $opts{imap} as $opts{username} failed: " . $IMAP->errstr, 1);
-  } # unless
-
-  $log->msg(' logged on');
-
   # Focus on INBOX only
-  $IMAP->select('INBOX');
+  $IMAP->select('inbox');
 
   # Setup %unseen to have each unseen message index set to 0 meaning not read
   # aloud yet
@@ -150,14 +138,6 @@ sub Connect2IMAP() {
 } # Connect2IMAP
 
 sub MonitorMail() {
-  my $msg = "Now monitoring email for $opts{username}\@$opts{imap}";
-
-  $log->msg($msg);
-
-  my $cmd = "/usr/local/bin/gt \"$msg\"";
-
-  my ($status, @output) = Execute $cmd;
-
   while () {
     # First close and reselect the INBOX to get its current status
     debugit "Reconnecting to INBOX";
@@ -185,22 +165,16 @@ sub MonitorMail() {
     for (keys %newUnseen) {
       next if $unseen{$_};
 
-      my @msglines = $IMAP->top($_);
-
-      # What happens at INBOX 0? Does top return empty array?
-      $log->err("Unable to get top for $_ - " . $IMAP->errstr(), 1) unless @msglines;
-
-      my $email = Email::Simple->new(join '', @msglines);
-
-      my $from = $email->header('From');
+      my $envelope = $IMAP->fetch($_, '(envelope)');
+      my $from     = $envelope->{$_}{envelope}{From};
+      my $subject  = $envelope->{$_}{envelope}{Subject};
+         $subject //= 'Unknown subject';
 
       # Extract the name only when the email is of the format "name <email>"
       if ($from =~ /^"?(.*?)"?\s*\<(\S*)>/) {
         $from = $1 if $1 ne '';
       } # if
 
-      my $subject = $email->header('Subject');
-
       if ($subject =~ /=?\S+?(Q|B)\?(.+)\?=/) {
         $subject = decode_base64($2);
       } # if
@@ -212,19 +186,22 @@ sub MonitorMail() {
       debugit "Speaking message from $from";
       my $logmsg = "From $from $subject";
 
-      $msg = "Message from $from... " . quotemeta $subject;
-      $msg =~ s/\"/\\"/g;
+      my $msg = "Message from $from... " . quotemeta $subject;
+         $msg =~ s/\"/\\"/g;
+
+      # Log it
+      $log->msg($logmsg);
 
       debugit $logmsg;
 
-      $cmd = "/usr/local/bin/gt \"$msg\"";
+      my $cmd = "/usr/local/bin/gt \"$msg\"";
 
       my $hour = (localtime)[2];
 
-      # Only announce if after 6 Am. Not this will announce up until
+      # Only announce if after 6 Am. Note this will announce up until
       # midnight but that's ok. I want midnight to 6 Am as silent time.
       if ($hour > 6) {
-        ($status, @output) = Execute $cmd;
+        my ($status, @output) = Execute $cmd;
 
         if ($status) {
           $log->err("Unable to execute $cmd" . join("\n", @output));
@@ -233,17 +210,11 @@ sub MonitorMail() {
 
       $unseen{$_} = 1;
     } # for
-
-    debugit "Sleeping for $opts{sleep} minutes";
-    sleep 60 * $opts{sleep};
-    debugit "Ah that was refreshing!";
   } # while
 
   return;
 } # MonitorMail
 
-$SIG{USR2} = \&MonitorMail;
-
 END {
   $IMAP->quit if $IMAP;
 
@@ -281,4 +252,13 @@ $log = Logger->new(
 
 Connect2IMAP;
 
-MonitorMail;
+my $msg = "Now monitoring email for $opts{username}\@$opts{imap}";
+
+$log->msg($msg);
+
+my $cmd = "/usr/local/bin/gt \"$msg\"";
+
+my ($status, @output) = Execute $cmd;
+
+$IMAP->idle(\&MonitorMail);
+