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: 600 seconds or 10 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>"
82 use lib "$FindBin::Bin/../lib";
90 local $0 = "$FindBin::Script " . join ' ', @ARGV;
92 my $defaultIMAPServer = 'defaria.com';
99 'You have received a new message',
100 'Hey I found this in your inbox',
101 'For some unknown reason this guy send you a message',
102 'Did you know you just got a message',
104 'You received a communique',
105 'I was looking in your inbox and found a message',
106 'Not sure you want to hear this message',
108 "What's this? A new message",
111 my $icon = '/home/andrew/.icons/Thunderbird.jpg';
112 my $timeout = 5 * 1000;
115 usage => sub { pod2usage },
116 help => sub { pod2usage(-verbose => 2)},
117 verbose => sub { set_verbose },
118 debug => sub { set_debug },
120 timeout => 600, # 10 minutes
121 username => $ENV{USER},
122 password => $ENV{PASSWORD},
123 imap => $defaultIMAPServer,
129 my $cmd = "notify-send -i $icon -t $timeout '$msg'";
138 notify 'Turning off debugging';
141 notify ('Turning on debugging');
151 # $log->dbug("Re-establishing connection to $opts{imap} as $opts{username}");
158 $SIG{USR1} = \&interrupted;
159 #$SIG{USR2} = \&restart;
162 $IMAP->select('inbox') or
163 $log->err("Unable to select inbox: " . get_last_error(), 1);
165 return map { $_=> 0 } @{$IMAP->search('not', 'seen')};
169 $log->dbug("Connecting to $opts{imap} as $opts{username}");
171 # Destroy any old connections
174 $IMAP = Mail::IMAPTalk->new(
175 Server => $opts{imap},
176 Username => $opts{username},
177 Password => $opts{password},
178 UseSSL => $opts{usessl},
179 UseBlocking => $opts{useblocking},
180 ) or $log->err("Unable to connect to IMAP server $opts{imap}: $@", 1);
182 $log->dbug("Connected to $opts{imap} as $opts{username}");
184 # Focus on INBOX only
185 $IMAP->select('inbox');
187 # Setup %unseen to have each unseen message index set to 0 meaning not read
189 %unseen = unseenMsgs;
196 $log->dbug("Top of MonitorMail loop");
198 # First close and reselect the INBOX to get its current status
200 $IMAP->select('INBOX')
201 or $log->err("Unable to select INBOX - ". $IMAP->errstr(), 1);
203 $log->dbug("Closed and reselected INBOX");
204 # Go through all of the unseen messages and add them to %unseen if they were
205 # not there already from a prior run and read
206 my %newUnseen = unseenMsgs;
208 # Now clean out any messages in %unseen that were not in the %newUnseen and
209 # marked as previously read
210 $log->dbug("Cleaning out unseen");
212 if (defined $newUnseen{$_}) {
214 delete $newUnseen{$_};
221 $log->dbug("Processing new unseen messages");
222 for (keys %newUnseen) {
225 my $envelope = $IMAP->fetch($_, '(envelope)');
226 my $from = $envelope->{$_}{envelope}{From};
227 my $subject = $envelope->{$_}{envelope}{Subject};
228 $subject //= 'Unknown subject';
230 # Extract the name only when the email is of the format "name <email>"
231 if ($from =~ /^"?(.*?)"?\s*\<(\S*)>/) {
232 $from = $1 if $1 ne '';
235 if ($subject =~ /=?\S+?(Q|B)\?(.+)\?=/) {
236 $subject = decode_base64($2);
239 # Google Talk doesn't like #
242 # Remove long strings of numbers like order numbers. They are uninteresting
244 $subject =~ s/\s+\S*\d{$longNumber,}\S*\s*//g;
247 my $logmsg = "From $from $subject";
249 my $greeting = $greetings[int rand $#greetings];
250 my $msg = "$greeting from $from... $subject";
253 my $hour = (localtime)[2];
255 # Only announce if after 6 Am. Note this will announce up until
256 # midnight but that's ok. I want midnight to 6 Am as silent time.
257 $log->dbug("About to speak/log");
259 $log->dbug("Calling speak");
263 $log->msg("$logmsg [silent]");
270 my $startTime = time;
272 # Re-establish callback
273 $log->dbug("Evaling idle");
274 eval { $IMAP->idle(\&MonitorMail, $opts{timeout}) };
276 $log->err("Unable to set IMAP Idle - AS $@", 1) if $@;
277 $log->msg("IMAP Idle for $opts{name} timed out in " . howlong $startTime, time);
279 # If we return from idle then the server went away for some reason. With Gmail
280 # the server seems to time out around 30-40 minutes. Here we simply return
281 # back to main which will re-establish the connection and call us again.
282 unless ($IMAP->get_response_code('timeout')) {
283 my $errstr = $IMAP->get_last_error;
285 $log->dbug("$opts{name} went away - $errstr");
292 # If $log is not yet defined then the exit is not unexpected
294 my $msg = "$FindBin::Script $opts{name} ended unexpectedly!";
321 unless ($opts{password}) {
322 verbose "I need $opts{username}'s password";
323 $opts{password} = GetPassword;
326 $opts{name} //= $opts{imap};
328 if ($opts{username} =~ /.*\@(.*)$/) {
333 # Perl complains if we reference $DB::OUT only once
335 EnterDaemonMode unless defined $DB::OUT or get_debug;
340 path => '/var/local/log',
341 name => "$Logger::me.$opts{name}",
342 timestamped => 'yes',
343 append => $opts{append},
346 if ($opts{username} =~ /(.*)\@/) {
349 $opts{user} = $opts{username};
352 my $msg = "Now monitoring email for $opts{user}\@$opts{name}";
354 # Changed to loop here - better than using a goto. This kinda kills the idea of
355 # using siguser2 to interrupt announceEmail.pl to kick it into re-establishing
360 speak $msg, $log if $opts{announce};
366 $log->dbug("$opts{name} timed out! Re-establishing connection");
369 # Should not get here
370 $log->err("Falling off the edge of $0", 1);