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)
58 $SIG{USR1}: Toggles debug option
59 $SIG{USR2}: Reestablishes connection to IMAP server
63 This script will connect to an IMAP server, login and then monitor the user's
64 INBOX. When new messages arrive it will extract the From address and Subject
65 from the message and compose a message to be used by "Google Talk" to announce
66 the email. The message will be similar to:
68 "<From> emailed <Subject>"
81 use lib "$FindBin::Bin/../lib";
89 my $defaultIMAPServer = 'defaria.com';
96 'You have received a new message',
97 'Hey I found this in your inbox',
98 'For some unknown reason this guy send you a message',
99 'Did you know you just got a message',
101 'You received a communique',
102 'I was looking in your inbox and found a message',
103 'Not sure you want to hear this message',
105 "What's this? A new message",
108 my $icon = '/home/andrew/.icons/Thunderbird.jpg';
109 my $timeout = 5 * 1000;
112 usage => sub { pod2usage },
113 help => sub { pod2usage(-verbose => 2)},
114 verbose => sub { set_verbose },
115 debug => sub { set_debug },
117 username => $ENV{USER},
118 password => $ENV{PASSWORD},
119 imap => $defaultIMAPServer,
125 my $cmd = "notify-send -i $icon -t $timeout '$msg'";
134 notify 'Turning off debugging';
137 notify ('Turning on debugging');
147 my $msg = "Re-establishing connection to $opts{imap} as $opts{username}";
156 $SIG{USR1} = \&interrupted;
157 $SIG{USR2} = \&restart;
160 $IMAP->select('inbox') or
161 $log->err("Unable to select inbox: " . get_last_error(), 1);
163 return map { $_=> 0 } @{$IMAP->search('not', 'seen')};
167 $log->dbug("Connecting to $opts{imap} as $opts{username}");
169 # Destroy any old connections
172 $IMAP = Mail::IMAPTalk->new(
173 Server => $opts{imap},
174 Username => $opts{username},
175 Password => $opts{password},
176 UseSSL => $opts{usessl},
177 UseBlocking => $opts{useblocking},
178 ) or $log->err("Unable to connect to IMAP server $opts{imap}: $@", 1);
180 $log->dbug("Connected to $opts{imap} as $opts{username}");
182 # Focus on INBOX only
183 $IMAP->select('inbox');
185 # Setup %unseen to have each unseen message index set to 0 meaning not read
187 %unseen = unseenMsgs;
194 $log->dbug("Top of MonitorMail loop");
196 # First close and reselect the INBOX to get its current status
198 $IMAP->select('INBOX')
199 or $log->err("Unable to select INBOX - ". $IMAP->errstr(), 1);
201 $log->dbug("Closed and reselected INBOX");
202 # Go through all of the unseen messages and add them to %unseen if they were
203 # not there already from a prior run and read
204 my %newUnseen = unseenMsgs;
206 # Now clean out any messages in %unseen that were not in the %newUnseen and
207 # marked as previously read
208 $log->dbug("Cleaning out unseen");
210 if (defined $newUnseen{$_}) {
212 delete $newUnseen{$_};
219 $log->dbug("Processing new unseen messages");
220 for (keys %newUnseen) {
223 my $envelope = $IMAP->fetch($_, '(envelope)');
224 my $from = $envelope->{$_}{envelope}{From};
225 my $subject = $envelope->{$_}{envelope}{Subject};
226 $subject //= 'Unknown subject';
228 # Extract the name only when the email is of the format "name <email>"
229 if ($from =~ /^"?(.*?)"?\s*\<(\S*)>/) {
230 $from = $1 if $1 ne '';
233 if ($subject =~ /=?\S+?(Q|B)\?(.+)\?=/) {
234 $subject = decode_base64($2);
237 # Google Talk doesn't like #
240 # Remove long strings of numbers like order numbers. They are uninteresting
242 $subject =~ s/\s+\S*\d{$longNumber,}\S*\s*//g;
245 my $logmsg = "From $from $subject";
247 my $greeting = $greetings[int rand $#greetings];
248 my $msg = "$greeting from $from... $subject";
251 my $hour = (localtime)[2];
253 # Only announce if after 6 Am. Note this will announce up until
254 # midnight but that's ok. I want midnight to 6 Am as silent time.
255 $log->dbug("About to speak/log");
257 $log->dbug("Calling speak");
261 $log->msg("$logmsg [silent]");
268 my $startTime = time;
270 # Re-establish callback
271 $log->dbug("Evaling idle");
272 eval { $IMAP->idle(\&MonitorMail) };
274 # If we return from idle then the server went away for some reason. With Gmail
275 # the server seems to time out around 30-40 minutes. Here we simply reconnect
276 # to the imap server and continue to MonitorMail.
283 # If $log is not yet defined then the exit is not unexpected
285 my $msg = "$FindBin::Script ending unexpectedly!";
311 unless ($opts{password}) {
312 verbose "I need $opts{username}'s password";
313 $opts{password} = GetPassword;
316 $opts{name} //= $opts{imap};
318 if ($opts{username} =~ /.*\@(.*)$/) {
323 # Perl complains if we reference $DB::OUT only once
325 EnterDaemonMode unless defined $DB::OUT or get_debug;
330 path => '/var/local/log',
331 name => "$Logger::me.$opts{name}",
332 timestamped => 'yes',
333 append => $opts{append},
338 if ($opts{username} =~ /(.*)\@/) {
341 $opts{user} = $opts{username};
344 my $msg = "Now monitoring email for $opts{user}\@$opts{name}";
346 speak $msg, $log if $opts{announce};
352 # Should not get here
353 $log->err("Falling off the edge of $0", 1);