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] [-da|emon]
35 [-use|rname <username>] [-p|assword <password>]
39 -usa|ge: Print this usage
41 -v|erbose: Verbose mode (Default: -verbose)
42 -de|bug: Turn on debugging (Default: Off)
43 -da|emon: Run in daemon mode (Default: -daemon)
44 -user|name: User name to log in with (Default: $USER)
45 -p|assword: Password to use (Default: prompted)
46 -n|ame: Name of account (Default: imap)
47 -i|map: IMAP server to talk to (Default: defaria.com)
48 -uses|sl: Whether or not to use SSL to connect (Default: False)
49 -useb|locking Whether to block on socket (Default: False)
50 -a-nnounce Announce startup (Default: False)
54 This script will connect to an IMAP server, login and then monitor the user's
55 INBOX. When new messages arrive it will extract the From address and Subject
56 from the message and compose a message to be used by "Google Talk" to announce
57 the email. The message will be similar to:
59 "<From> emailed <Subject>"
72 use lib "$FindBin::Bin/../lib";
79 my $defaultIMAPServer = 'defaria.com';
86 'You have received a new message',
87 'Hey I found this in your inbox',
88 'For some unknown reason this guy send you a message',
89 'Did you know you just got a message',
91 'You received a communique',
92 'I was looking in your inbox and found a message',
93 'Not sure you want to hear this message',
98 usage => sub { pod2usage },
99 help => sub { pod2usage(-verbose => 2)},
100 verbose => sub { set_verbose },
101 debug => sub { set_debug },
103 username => $ENV{USER},
104 password => $ENV{PASSWORD},
105 imap => $defaultIMAPServer,
113 $log->msg("Turning off debugging");
116 $log->msg("Turning on debugging");
123 $SIG{USR1} = \&interrupted;
126 $IMAP->select('inbox') or
127 $log->err("Unable to select inbox: " . get_last_error(), 1);
129 return map { $_=> 0 } @{$IMAP->search('not', 'seen')};
133 $log->dbug("Connecting to $opts{imap} as $opts{username}");
135 $IMAP = Mail::IMAPTalk->new(
136 Server => $opts{imap},
137 Username => $opts{username},
138 Password => $opts{password},
139 UseSSL => $opts{usessl},
140 UseBlocking => $opts{useblocking},
141 ) or $log->err("Unable to connect to IMAP server $opts{imap}: $@", 1);
143 $log->dbug("Connected to $opts{imap} as $opts{username}");
145 # Focus on INBOX only
146 $IMAP->select('inbox');
148 # Setup %unseen to have each unseen message index set to 0 meaning not read
150 %unseen = unseenMsgs;
158 my ($status, @output) = Execute "/usr/local/bin/gt \"$msg\"";
160 $log->err("Unable to speak (Status: $status) - "
161 . join ("\n", @output), $status) if $status;
169 # First close and reselect the INBOX to get its current status
171 $IMAP->select('INBOX')
172 or $log->err("Unable to select INBOX - ". $IMAP->errstr(), 1);
174 # Go through all of the unseen messages and add them to %unseen if they were
175 # not there already from a prior run and read
176 my %newUnseen = unseenMsgs;
178 # Now clean out any messages in %unseen that were not in the %newUnseen and
179 # marked as previously read
181 if (defined $newUnseen{$_}) {
183 delete $newUnseen{$_};
190 for (keys %newUnseen) {
193 my $envelope = $IMAP->fetch($_, '(envelope)');
194 my $from = $envelope->{$_}{envelope}{From};
195 my $subject = $envelope->{$_}{envelope}{Subject};
196 $subject //= 'Unknown subject';
198 # Extract the name only when the email is of the format "name <email>"
199 if ($from =~ /^"?(.*?)"?\s*\<(\S*)>/) {
200 $from = $1 if $1 ne '';
203 if ($subject =~ /=?\S+?(Q|B)\?(.+)\?=/) {
204 $subject = decode_base64($2);
207 # Google Talk doesn't like #
211 my $logmsg = "From $from $subject";
213 my $greeting = $greetings[int rand $#greetings];
214 my $msg = "$greeting from $from... $subject";
217 my $hour = (localtime)[2];
219 # Only announce if after 6 Am. Note this will announce up until
220 # midnight but that's ok. I want midnight to 6 Am as silent time.
225 $log->msg("$logmsg [silent]");
231 # Re-establish callback
232 eval { $IMAP->idle(\&MonitorMail) };
234 # If we return from idle then the server went away for some reason. With Gmail
235 # the server seems to time out around 30-40 minutes. Here we simply reconnect
236 # to the imap server and continue to MonitorMail.
237 $log->dbug("MonitorMail: Connection to $opts{imap} ended. Reconnecting");
239 # Destroy current IMAP connection
240 $log->dbug("MonitorMail: Destorying IMAP connection to $opts{imap}");
244 # Re-establish connection
247 $log->dbug("MonitorMail: Reconnected to IMAP server $opts{imap}");
249 # MonitorMail again - the dreaded goto! Seems the cleanest way to restart
250 # in this instance. I could call MonitorMail() recursively but that would
251 # leave junk on the stack.
252 $log->dbug('MonitorMail: Going back to the top of the loop');
256 return; # To make perlcritic happy
260 # If $log is not yet defined then the exit is not unexpected
262 my $msg = "$FindBin::Script ending unexpectedly!";
287 unless ($opts{password}) {
288 verbose "I need $opts{username}'s password";
289 $opts{password} = GetPassword;
292 $opts{name} //= $opts{imap};
294 if ($opts{username} =~ /.*\@(.*)$/) {
299 # Perl complains if we reference $DB::OUT only once
301 EnterDaemonMode unless defined $DB::OUT;
306 name => "$Logger::me.$opts{name}",
307 timestamped => 'yes',
313 if ($opts{username} =~ /(.*)\@/) {
316 $opts{user} = $opts{username};
319 my $msg = "Now monitoring email for $opts{user}\@$opts{name}";
321 Say $msg if $opts{announce};
327 # Should not get here
328 $log->err("Falling off the edge of $0", 1);