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>]
36 [-i|map <server>] [-t|imeout <secs>]
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)
49 -t|imeout <s> Timeout IMAP idle call (Sefault: 1200 seconds or 20 minutes)
51 -an|nounce Announce startup (Default: False)
52 -ap|pend Append to logfile (Default: Noappend)
53 -da|emon Run in daemon mode (Default: -daemon)
54 -n|ame Name of account (Default: imap)
55 -uses|sl Whether or not to use SSL to connect (Default: False)
56 -useb|locking Whether to block on socket (Default: False)
59 $SIG{USR1}: Toggles debug option
60 $SIG{USR2}: Reestablishes connection to IMAP server
64 This script will connect to an IMAP server, login and then monitor the user's
65 INBOX. When new messages arrive it will extract the From address and Subject
66 from the message and compose a message to be used by "Google Talk" to announce
67 the email. The message will be similar to:
69 "<From> emailed <Subject>"
81 use Proc::ProcessTable;
83 use lib "$FindBin::Bin/../lib";
91 local $0 = "$FindBin::Script " . join ' ', @ARGV;
93 my $processes = Proc::ProcessTable->new;
95 for my $process (@{$processes->table}) {
96 if ($process->cmndline eq $0 and $process->pid != $$) {
97 verbose "$FindBin::Script already running";
103 my $defaultIMAPServer = 'defaria.com';
110 'You have received a new message',
111 'Hey I found this in your inbox',
112 'For some unknown reason this guy send you a message',
113 'Did you know you just got a message',
115 'You received a communique',
116 'I was looking in your inbox and found a message',
117 'Not sure you want to hear this message',
119 "What's this? A new message",
122 my $icon = '/home/andrew/.icons/Thunderbird.jpg';
123 my $notifyTimeout = 5 * 1000;
124 my $IMAPTimeout = 20 * 60;
127 usage => sub { pod2usage },
128 help => sub { pod2usage(-verbose => 2)},
129 verbose => sub { set_verbose },
130 debug => sub { set_debug },
132 timeout => $IMAPTimeout,
133 username => $ENV{USER},
134 password => $ENV{PASSWORD},
135 imap => $defaultIMAPServer,
141 my $cmd = "notify-send -i $icon -t $notifyTimeout '$msg'";
150 notify 'Turning off debugging';
153 notify ('Turning on debugging');
164 my $msg = "Re-establishing connection to $opts{imap} as $opts{username}";
173 $SIG{USR1} = \&interrupted;
174 $SIG{USR2} = \&restart;
177 $IMAP->select('inbox') or
178 $log->err("Unable to select inbox: " . get_last_error(), 1);
180 return map { $_=> 0 } @{$IMAP->search('not', 'seen')};
184 $log->dbug("Connecting to $opts{imap} as $opts{username}");
186 # Destroy any old connections
189 $IMAP = Mail::IMAPTalk->new(
190 Server => $opts{imap},
191 Username => $opts{username},
192 Password => $opts{password},
193 UseSSL => $opts{usessl},
194 UseBlocking => $opts{useblocking},
195 ) or $log->err("Unable to connect to IMAP server $opts{imap}: $@", 1);
197 $log->dbug("Connected to $opts{imap} as $opts{username}");
199 # Focus on INBOX only
200 $IMAP->select('inbox');
202 # Setup %unseen to have each unseen message index set to 0 meaning not read
204 %unseen = unseenMsgs;
211 $log->dbug("Top of MonitorMail loop");
213 # First close and reselect the INBOX to get its current status
215 $IMAP->select('INBOX')
216 or $log->err("Unable to select INBOX - ". $IMAP->errstr(), 1);
218 $log->dbug("Closed and reselected INBOX");
219 # Go through all of the unseen messages and add them to %unseen if they were
220 # not there already from a prior run and read
221 my %newUnseen = unseenMsgs;
223 # Now clean out any messages in %unseen that were not in the %newUnseen and
224 # marked as previously read
225 $log->dbug("Cleaning out unseen");
227 if (defined $newUnseen{$_}) {
229 delete $newUnseen{$_};
236 $log->dbug("Processing new unseen messages");
237 for (keys %newUnseen) {
240 my $envelope = $IMAP->fetch($_, '(envelope)');
241 my $from = $envelope->{$_}{envelope}{From};
242 my $subject = $envelope->{$_}{envelope}{Subject};
243 $subject //= 'Unknown subject';
245 # Extract the name only when the email is of the format "name <email>"
246 if ($from =~ /^"?(.*?)"?\s*\<(\S*)>/) {
247 $from = $1 if $1 ne '';
250 if ($subject =~ /=?\S+?(Q|B)\?(.+)\?=/) {
251 $subject = decode_base64($2);
254 # Google Talk doesn't like #
257 # Remove long strings of numbers like order numbers. They are uninteresting
259 $subject =~ s/\s+\S*\d{$longNumber,}\S*\s*//g;
262 my $logmsg = "From $from $subject";
264 my $greeting = $greetings[int rand $#greetings];
265 my $msg = "$greeting from $from... $subject";
266 my $hour = (localtime)[2];
268 # Only announce if after 6 Am. Note this will announce up until
269 # midnight but that's ok. I want midnight to 6 Am as silent time.
270 $log->dbug("About to speak/log");
273 $log->dbug("Calling speak");
276 $log->msg("$logmsg [silent nighttime]");
283 my $startTime = time;
285 # Re-establish callback
286 $log->dbug("Calling IMAP->idle");
288 $IMAP->idle(\&MonitorMail, $opts{timeout})
291 my $msg = 'Returned from IMAP->idle ';
294 speak($msg . $@, $log);
296 $log->msg($msg . 'no error');
299 # If we return from idle then the server went away for some reason. With Gmail
300 # the server seems to time out around 30-40 minutes. Here we simply reconnect
301 # to the imap server and continue to MonitorMail.
302 unless ($IMAP->get_response_code('timeout')) {
303 $msg = "IMAP Idle for $opts{name} timed out in " . howlong $startTime, time;
314 # If $log is not yet defined then the exit is not unexpected
316 my $msg = "$FindBin::Script $opts{name} ending unexpectedly!";
343 unless ($opts{password}) {
344 verbose "I need $opts{username}'s password";
345 $opts{password} = GetPassword;
348 $opts{name} //= $opts{imap};
350 if ($opts{username} =~ /.*\@(.*)$/) {
355 # Perl complains if we reference $DB::OUT only once
357 EnterDaemonMode unless defined $DB::OUT or get_debug;
362 path => '/var/local/log',
363 name => "$Logger::me.$opts{name}",
364 timestamped => 'yes',
365 append => $opts{append},
370 if ($opts{username} =~ /(.*)\@/) {
373 $opts{user} = $opts{username};
376 my $msg = "Now monitoring email for $opts{user}\@$opts{name}";
378 speak $msg, $log if $opts{announce};
384 # Should not get here
385 $log->err("Falling off the edge of $0", 1);