5 =head1 NAME $RCSfile: announceEmail.pl,v $
7 Monitors an IMAP Server and announce incoming emails by extracting the subject
8 line and from line and then pushing that into "GoogleTalk".
16 Andrew DeFaria <Andrew@DeFaria.com>
24 Thu Apr 4 13:40:10 MST 2019
28 $Date: 2019/04/04 13:40:10 $
34 Usage: announceEmail.pl [-usa|ge] [-h|elp] [-v|erbose] [-de|bug]
35 [-use|rname <username>] [-p|assword <password>]
37 [-an|nouce] [-ap|pend] [-da|emon] [-n|name <name>]
38 [-uses|sl] [-useb|locking]
41 -usa|ge Print this usage
43 -v|erbose Verbose mode (Default: -verbose)
44 -de|bug Turn on debugging (Default: Off)
46 -user|name User name to log in with (Default: $USER)
47 -p|assword Password to use (Default: prompted)
48 -i|map IMAP server to talk to (Default: defaria.com)
50 -an|nounce Announce startup (Default: False)
51 -ap|pend Append to logfile (Default: Noappend)
52 -da|emon Run in daemon mode (Default: -daemon)
53 -n|ame Name of account (Default: imap)
54 -uses|sl Whether or not to use SSL to connect (Default: False)
55 -useb|locking Whether to block on socket (Default: False)
59 This script will connect to an IMAP server, login and then monitor the user's
60 INBOX. When new messages arrive it will extract the From address and Subject
61 from the message and compose a message to be used by "Google Talk" to announce
62 the email. The message will be similar to:
64 "<From> emailed <Subject>"
77 use lib "$FindBin::Bin/../lib";
85 my $defaultIMAPServer = 'defaria.com';
92 'You have received a new message',
93 'Hey I found this in your inbox',
94 'For some unknown reason this guy send you a message',
95 'Did you know you just got a message',
97 'You received a communique',
98 'I was looking in your inbox and found a message',
99 'Not sure you want to hear this message',
104 usage => sub { pod2usage },
105 help => sub { pod2usage(-verbose => 2)},
106 verbose => sub { set_verbose },
107 debug => sub { set_debug },
109 username => $ENV{USER},
110 password => $ENV{PASSWORD},
111 imap => $defaultIMAPServer,
116 $log->msg("Turning off debugging");
119 $log->msg("Turning on debugging");
126 $SIG{USR1} = \&interrupted;
129 $IMAP->select('inbox') or
130 $log->err("Unable to select inbox: " . get_last_error(), 1);
132 return map { $_=> 0 } @{$IMAP->search('not', 'seen')};
136 $log->dbug("Connecting to $opts{imap} as $opts{username}");
138 $IMAP = Mail::IMAPTalk->new(
139 Server => $opts{imap},
140 Username => $opts{username},
141 Password => $opts{password},
142 UseSSL => $opts{usessl},
143 UseBlocking => $opts{useblocking},
144 ) or $log->err("Unable to connect to IMAP server $opts{imap}: $@", 1);
146 $log->dbug("Connected to $opts{imap} as $opts{username}");
148 # Focus on INBOX only
149 $IMAP->select('inbox');
151 # Setup %unseen to have each unseen message index set to 0 meaning not read
153 %unseen = unseenMsgs;
160 $log->dbug("Top of MonitorMail loop");
162 # First close and reselect the INBOX to get its current status
164 $IMAP->select('INBOX')
165 or $log->err("Unable to select INBOX - ". $IMAP->errstr(), 1);
167 $log->dbug("Closed and reselected INBOX");
168 # Go through all of the unseen messages and add them to %unseen if they were
169 # not there already from a prior run and read
170 my %newUnseen = unseenMsgs;
172 # Now clean out any messages in %unseen that were not in the %newUnseen and
173 # marked as previously read
174 $log->dbug("Cleaning out unseen");
176 if (defined $newUnseen{$_}) {
178 delete $newUnseen{$_};
185 $log->dbug("Processing new unseen messages");
186 for (keys %newUnseen) {
189 my $envelope = $IMAP->fetch($_, '(envelope)');
190 my $from = $envelope->{$_}{envelope}{From};
191 my $subject = $envelope->{$_}{envelope}{Subject};
192 $subject //= 'Unknown subject';
194 # Extract the name only when the email is of the format "name <email>"
195 if ($from =~ /^"?(.*?)"?\s*\<(\S*)>/) {
196 $from = $1 if $1 ne '';
199 if ($subject =~ /=?\S+?(Q|B)\?(.+)\?=/) {
200 $subject = decode_base64($2);
203 # Google Talk doesn't like #
207 my $logmsg = "From $from $subject";
209 my $greeting = $greetings[int rand $#greetings];
210 my $msg = "$greeting from $from... $subject";
213 my $hour = (localtime)[2];
215 # Only announce if after 6 Am. Note this will announce up until
216 # midnight but that's ok. I want midnight to 6 Am as silent time.
217 $log->dbug("About to speak/log");
219 $log->dbug("Calling speak");
223 $log->msg("$logmsg [silent]");
230 my $startTime = time;
232 # Re-establish callback
233 $log->dbug("Evaling idle");
234 eval { $IMAP->idle(\&MonitorMail) };
236 # If we return from idle then the server went away for some reason. With Gmail
237 # the server seems to time out around 30-40 minutes. Here we simply reconnect
238 # to the imap server and continue to MonitorMail.
239 $log->dbug("MonitorMail: Connection to $opts{imap} ended - lasted "
240 . howlong $startTime);
242 # Destroy current IMAP connection
243 $log->dbug("MonitorMail: Destroying IMAP connection to $opts{imap}");
247 # Re-establish connection
250 $log->dbug("MonitorMail: Reconnected to IMAP server $opts{imap}");
252 # MonitorMail again - the dreaded goto! Seems the cleanest way to restart
253 # in this instance. I could call MonitorMail() recursively but that would
254 # leave junk on the stack.
255 $log->dbug('MonitorMail: Going back to the top of the loop');
259 return; # To make perlcritic happy
263 # If $log is not yet defined then the exit is not unexpected
265 my $msg = "$FindBin::Script ending unexpectedly!";
291 unless ($opts{password}) {
292 verbose "I need $opts{username}'s password";
293 $opts{password} = GetPassword;
296 $opts{name} //= $opts{imap};
298 if ($opts{username} =~ /.*\@(.*)$/) {
303 # Perl complains if we reference $DB::OUT only once
305 EnterDaemonMode unless defined $DB::OUT or get_debug;
310 path => '/var/local/log',
311 name => "$Logger::me.$opts{name}",
312 timestamped => 'yes',
313 append => $opts{append},
318 if ($opts{username} =~ /(.*)\@/) {
321 $opts{user} = $opts{username};
324 my $msg = "Now monitoring email for $opts{user}\@$opts{name}";
326 speak $msg, $log if $opts{announce};
332 # Should not get here
333 $log->err("Falling off the edge of $0", 1);