Fixes to raid
[clearscm.git] / bin / announceEmail.pl
index 3cbc12e..cf3dc49 100755 (executable)
@@ -17,7 +17,7 @@ Andrew DeFaria <Andrew@DeFaria.com>
 
 =item Revision
 
-$Revision: 1.0 $
+$Revision: 1.2 $
 
 =item Created:
 
@@ -29,22 +29,34 @@ $Date: 2019/04/04 13:40:10 $
 
 =back
 
-=hade1 SYNOPSIS
+=head1 SYNOPSIS
 
- Usage: announceEmail.pl [-usa|ge] [-h|elp] [-v|erbose] [-de|bug] [-da|emon]
+ Usage: announceEmail.pl [-usa|ge] [-h|elp] [-v|erbose] [-de|bug]
                          [-use|rname <username>] [-p|assword <password>]
-                         [-i|map <server]
+                         [-i|map <server>]
+                         [-an|nouce] [-ap|pend] [-da|emon] [-n|name <name>]
+                         [-uses|sl] [-useb|locking]
 
  Where:
-   -usa|ge:    Print this usage
-   -h|elp:     Detailed help
-   -v|erbose:  Verbose mode (Default: -verbose)
-   -de|bug:    Turn on debugging (Default: Off)
-   -da|emon:   Run in daemon mode (Default: -daemon)
-   -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)
+   -usa|ge       Print this usage
+   -h|elp        Detailed help
+   -v|erbose     Verbose mode (Default: Not verbose)
+   -de|bug       Turn on debugging (Default: Off)
+
+   -user|name    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)
+
+   -an|nounce    Announce startup (Default: False)
+   -ap|pend      Append to logfile (Default: Noappend)
+   -da|emon      Run in daemon mode (Default: -daemon)
+   -n|ame        Name of account (Default: imap)
+   -uses|sl      Whether or not to use SSL to connect (Default: False)
+   -useb|locking Whether to block on socket (Default: False)
+
+ Signals:
+   $SIG{USR1}:   Toggles debug option
+   $SIG{USR2}:   Reestablishes connection to IMAP server
 
 =head1 DESCRIPTION
 
@@ -63,140 +75,221 @@ 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";
 
 use Display;
 use Logger;
+use Speak;
+use TimeUtils;
 use Utils;
 
 my $defaultIMAPServer = 'defaria.com';
-my $defaultSleeptime  = 1;
 my $IMAP;
 my %unseen;
 my $log;
 
+my @greetings = (
+  'Incoming message',
+  'You have received a new message',
+  'Hey I found this in your inbox',
+  'For some unknown reason this guy send you a message',
+  'Did you know you just got a message',
+  'Potential spam',
+  'You received a communique',
+  'I was looking in your inbox and found a message',
+  'Not sure you want to hear this message',
+  'Good news',
+  "What's this? A new message",
+);
+
+my $icon    = '/home/andrew/.icons/Thunderbird.jpg';
+my $timeout = 5 * 1000;
+
 my %opts = (
-  usage    => sub { pod2usage },
-  help     => sub { pod2usage(-verbose => 2)},
-  verbose  => sub { set_verbose },
-  debug    => sub { set_debug },
-  daemon   => 1,
-  username => $ENV{USER},
-  password => $ENV{PASSWORD},
-  imap     => $defaultIMAPServer,
-  sleep    => $defaultSleeptime,
+  usage       => sub { pod2usage },
+  help        => sub { pod2usage(-verbose => 2)},
+  verbose     => sub { set_verbose },
+  debug       => sub { set_debug },
+  daemon      => 1,
+  username    => $ENV{USER},
+  password    => $ENV{PASSWORD},
+  imap        => $defaultIMAPServer,
 );
 
-sub unseenMsgs() {
-  my %unseenMsgs;
+sub notify($) {
+  my ($msg) = @_;
 
-  for (my $i = 1; $i <= $IMAP->status; $i++) {
-    $unseenMsgs{$i} = 0 unless $IMAP->seen($i);
-  } # for
+  my $cmd = "notify-send -i $icon -t $timeout '$msg'";
 
-  return %unseenMsgs;
-} # unseenMsgs 
+  Execute $cmd;
 
-sub Connect2IMAP() {
-  $log->msg("Connecting to $opts{imap} as $opts{username}");
+  return;
+} # notify
+
+sub interrupted {
+  if (get_debug) {
+    notify 'Turning off debugging';
+    set_debug 0;
+  } else {
+    notify ('Turning on debugging');
+    set_debug 1;
+  } # if
+
+  return;
+} # interrupted
+
+sub restart;
 
-  $IMAP = Net::IMAP::Simple->new($opts{imap}) ||
-    error("Unable to connect to IMAP server $opts{imap}: " . $Net::IMAP::Simple::errstr, 1);
+sub Connect2IMAP;
 
-  $log->msg("Connected");
+$SIG{USR1} = \&interrupted;
+$SIG{USR2} = \&restart;
 
-  $log->msg("Logging onto $opts{imap} as $opts{username}");
+sub unseenMsgs() {
+  $IMAP->select('inbox') or
+    $log->err("Unable to select inbox: " . get_last_error(), 1);
+
+  return map { $_=> 0 } @{$IMAP->search('not', 'seen')};
+} # unseenMsgs 
+
+sub Connect2IMAP() {
+  $log->dbug("Connecting to $opts{imap} as $opts{username}");
 
-  unless ($IMAP->login($opts{username}, $opts{password})) {
-    $log->err("Login to $opts{imap} as $opts{username} failed: " . $IMAP->errstr, 1);
-  } # unless
+  # Destroy any old connections
+  undef $IMAP;
 
-  $log->msg("Logged on");
+  $IMAP = Mail::IMAPTalk->new(
+    Server      => $opts{imap},
+    Username    => $opts{username},
+    Password    => $opts{password},
+    UseSSL      => $opts{usessl},
+    UseBlocking => $opts{useblocking},
+  ) or $log->err("Unable to connect to IMAP server $opts{imap}: $@", 1);
+
+  $log->dbug("Connected to $opts{imap} as $opts{username}");
 
   # 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
   %unseen = unseenMsgs;
+
+  return;
 } # Connect2IMAP
 
 sub MonitorMail() {
-  $log->msg("Monitoring email");
-
-  while () {
-    # First close and reselect the INBOX to get its current status
-    $IMAP->close;
-    $IMAP->select('INBOX');
-
-    # Go through all of the unseen messages and add them to %unseen if they were
-    # not there already from a prior run and read
-    my %newUnseen = unseenMsgs;
-
-    # Now clean out any messages in %unseen that were not in the %newUnseen and
-    # marked as previously read
-    for (keys %unseen) {
-      if (defined $newUnseen{$_}) {
-        if ($unseen{$_}) {
-          delete $newUnseen{$_};
-        } # if
-      } else {
-        delete $unseen{$_}
+  MONITORMAIL:
+  $log->dbug("Top of MonitorMail loop");
+
+  # First close and reselect the INBOX to get its current status
+  $IMAP->close;
+  $IMAP->select('INBOX')
+    or $log->err("Unable to select INBOX - ". $IMAP->errstr(), 1);
+
+  $log->dbug("Closed and reselected INBOX");
+  # Go through all of the unseen messages and add them to %unseen if they were
+  # not there already from a prior run and read
+  my %newUnseen = unseenMsgs;
+
+  # Now clean out any messages in %unseen that were not in the %newUnseen and
+  # marked as previously read
+  $log->dbug("Cleaning out unseen");
+  for (keys %unseen) {
+    if (defined $newUnseen{$_}) {
+      if ($unseen{$_}) {
+        delete $newUnseen{$_};
       } # if
-    } # for
+    } else {
+      delete $unseen{$_}
+    } # if
+  } # for
 
-    for (keys %newUnseen) {
-      next if $unseen{$_};
+  $log->dbug("Processing new unseen messages");
+  for (keys %newUnseen) {
+    next if $unseen{$_};
 
-      my $email = Email::Simple->new(join '', @{$IMAP->top($_)});
+    my $envelope = $IMAP->fetch($_, '(envelope)');
+    my $from     = $envelope->{$_}{envelope}{From};
+    my $subject  = $envelope->{$_}{envelope}{Subject};
+       $subject //= 'Unknown subject';
 
-      my $from = $email->header('From');
+    # Extract the name only when the email is of the format "name <email>"
+    if ($from =~ /^"?(.*?)"?\s*\<(\S*)>/) {
+      $from = $1 if $1 ne '';
+    } # if
 
-      # Extract the name only when the email is of the format "name <email>"
-      if ($from =~ /^(.*)\<(\S*)>/) {
-        $from = $1 if $1 ne '';
-      } # if
+    if ($subject =~ /=?\S+?(Q|B)\?(.+)\?=/) {
+      $subject = decode_base64($2);
+    } # if
 
-      my $subject = $email->header('Subject');
+    # Google Talk doesn't like #
+    $subject =~ s/\#//g;
 
-      if ($subject =~ /=?\S+?(Q|B)\?(.+)\?=/) {
-        $subject = decode_base64($2);
-      } # if
+    # Remove long strings of numbers like order numbers. They are uninteresting
+    my $longNumber = 5;
+    $subject =~ s/\s+\S*\d{$longNumber,}\S*\s*//g;
 
-      # Now speak it!
-      my $logmsg = "From $from $subject";
-      my $msg = "Message from $from... " . quotemeta $subject;
-      $msg =~ s/\"/\\"/g;
+    # Now speak it!
+    my $logmsg = "From $from $subject";
 
-      if (get_verbose) {
-        $log->msg($logmsg);
-      } else {
-        $log->log($logmsg);
-      } # if
+    my $greeting = $greetings[int rand $#greetings];
+    my $msg      = "$greeting from $from... $subject";
+       $msg      =~ s/\"/\\"/g;
 
-      my $cmd = "/usr/local/bin/gt \"$msg\"";
+    my $hour = (localtime)[2];
 
-      my ($status, @output) = Execute $cmd;
+    # 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.
+    $log->dbug("About to speak/log");
+    if ($hour >= 7) {
+      $log->dbug("Calling speak");
+      speak $msg, $log;
+      $log->msg($logmsg);
+    } else {
+      $log->msg("$logmsg [silent]");
+    } # if
 
-      if ($status) {
-        $log->err("Unable to execute $cmd" . join("\n", @output));
-      } # if
+    $unseen{$_} = 1;
+  } # for
+
+  # Let's time things
+  my $startTime = time;
+
+  # Re-establish callback
+  $log->dbug("Evaling idle");
+  eval { $IMAP->idle(\&MonitorMail) };
 
-      $unseen{$_} = 1;
-    } # for
+  # If we return from idle then the server went away for some reason. With Gmail
+  # the server seems to time out around 30-40 minutes. Here we simply reconnect
+  # to the imap server and continue to MonitorMail.
+  restart;
 
-    verbose "Sleeping for $opts{sleep} minutes";
-    sleep 60 * $opts{sleep};
-    verbose "Ah that was refreshing!";
-  } # while
+  return;
 } # MonitorMail
 
+sub restart {
+  my $msg = "Re-establishing connection to $opts{imap} as $opts{username}";
+
+  $log->dbug($msg);
+
+  Connect2IMAP;
+
+  MonitorMail;
+} # restart
+
 END {
-  $IMAP->quit if $IMAP;
+  # If $log is not yet defined then the exit is not unexpected
+  if ($log) {
+    my $msg = "$FindBin::Script ending unexpectedly!";
+
+    speak $msg, $log;
+
+    $log->err($msg);
+  } # if
 } # END
 
 ## Main
@@ -208,23 +301,55 @@ GetOptions(
   'debug',
   'daemon!',
   'username=s',
+  'name=s',
   'password=s',
   'imap=s',
-  'sleep',
-);
+  'usessl',
+  'useblocking',
+  'announce!',
+  'append',
+) || pod2usage;
 
 unless ($opts{password}) {
   verbose "I need $opts{username}'s password";
   $opts{password} = GetPassword;
 } # unless
 
-EnterDaemonMode if $opts{daemon};
+$opts{name} //= $opts{imap};
+
+if ($opts{username} =~ /.*\@(.*)$/) {
+  $opts{name} = $1;
+} # if
+
+if ($opts{daemon}) {
+  # Perl complains if we reference $DB::OUT only once
+  no warnings;
+  EnterDaemonMode unless defined $DB::OUT or get_debug;
+  use warnings;
+} # if
 
 $log = Logger->new(
-  path        => '/var/log',
+  path        => '/var/local/log',
+  name        => "$Logger::me.$opts{name}",
   timestamped => 'yes',
-  append      => 'yes',
+  append      => $opts{append},
 );
 
 Connect2IMAP;
+
+if ($opts{username} =~ /(.*)\@/) {
+  $opts{user} = $1;
+} else {
+  $opts{user} = $opts{username};
+} # if
+
+my $msg = "Now monitoring email for $opts{user}\@$opts{name}";
+
+speak $msg, $log if $opts{announce};
+
+$log->msg($msg);
+
 MonitorMail;
+
+# Should not get here
+$log->err("Falling off the edge of $0", 1);