Changed to not try new images while the monitor is off
[clearscm.git] / bin / announceEmail.pl
1 #!/usr/bin/perl
2
3 =pod
4
5 =head1 NAME $RCSfile: announceEmail.pl,v $
6
7 Monitors an IMAP Server and announce incoming emails by extracting the subject
8 line and from line and then pushing that into "GoogleTalk".
9
10 =head1 VERSION
11
12 =over
13
14 =item Author
15
16 Andrew DeFaria <Andrew@DeFaria.com>
17
18 =item Revision
19
20 $Revision: 1.2 $
21
22 =item Created:
23
24 Thu Apr  4 13:40:10 MST 2019
25
26 =item Modified:
27
28 $Date: 2019/04/04 13:40:10 $
29
30 =back
31
32 =head1 SYNOPSIS
33
34  Usage: announceEmail.pl [-usa|ge] [-h|elp] [-v|erbose] [-de|bug] [-da|emon]
35                          [-use|rname <username>] [-p|assword <password>]
36                          [-i|map <server]
37
38  Where:
39    -usa|ge:      Print this usage
40    -h|elp:       Detailed help
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)
51
52 =head1 DESCRIPTION
53
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:
58
59   "<From> emailed <Subject>"
60
61 =cut
62
63 use strict;
64 use warnings;
65
66 use FindBin;
67 use Getopt::Long;
68 use Pod::Usage;
69 use Mail::IMAPTalk;
70 use MIME::Base64;
71
72 use lib "$FindBin::Bin/../lib";
73
74 use Display;
75 use Logger;
76 use Utils;
77 use TimeUtils;
78
79 my $defaultIMAPServer = 'defaria.com';
80 my $IMAP;
81 my %unseen;
82 my $log;
83
84 my @greetings = (
85   'Incoming message',
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',
90   'Potential spam',
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',
94   'Good news',
95 );
96
97 my %opts = (
98   usage       => sub { pod2usage },
99   help        => sub { pod2usage(-verbose => 2)},
100   verbose     => sub { set_verbose },
101   debug       => sub { set_debug },
102   daemon      => 1,
103   username    => $ENV{USER},
104   password    => $ENV{PASSWORD},
105   imap        => $defaultIMAPServer,
106   usessl      => 0,
107   useblocking => 0,
108   announce    => 0,
109 );
110
111 sub interrupted {
112   if (get_debug) {
113     $log->msg("Turning off debugging");
114     set_debug 0;
115   } else {
116     $log->msg("Turning on debugging");
117     set_debug 1;
118   } # if
119
120   return;
121 } # interrupted
122
123 $SIG{USR1} = \&interrupted;
124
125 sub unseenMsgs() {
126   $IMAP->select('inbox') or
127     $log->err("Unable to select inbox: " . get_last_error(), 1);
128
129   return map { $_=> 0 } @{$IMAP->search('not', 'seen')};
130 } # unseenMsgs 
131
132 sub Connect2IMAP() {
133   $log->dbug("Connecting to $opts{imap} as $opts{username}");
134
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);
142
143   $log->dbug("Connected to $opts{imap} as $opts{username}");
144
145   # Focus on INBOX only
146   $IMAP->select('inbox');
147
148   # Setup %unseen to have each unseen message index set to 0 meaning not read
149   # aloud yet
150   %unseen = unseenMsgs;
151
152   return;
153 } # Connect2IMAP
154
155 sub Say($) {
156   my ($msg) = @_;
157
158   my ($status, @output) = Execute "/usr/local/bin/gt \"$msg\"";
159
160   $log->err("Unable to speak (Status: $status) - "
161           . join ("\n", @output), $status) if $status;
162
163   return;
164 } # Say
165
166 sub MonitorMail() {
167   MONITORMAIL:
168
169   # First close and reselect the INBOX to get its current status
170   $IMAP->close;
171   $IMAP->select('INBOX')
172     or $log->err("Unable to select INBOX - ". $IMAP->errstr(), 1);
173
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;
177
178   # Now clean out any messages in %unseen that were not in the %newUnseen and
179   # marked as previously read
180   for (keys %unseen) {
181     if (defined $newUnseen{$_}) {
182       if ($unseen{$_}) {
183         delete $newUnseen{$_};
184       } # if
185     } else {
186       delete $unseen{$_}
187     } # if
188   } # for
189
190   for (keys %newUnseen) {
191     next if $unseen{$_};
192
193     my $envelope = $IMAP->fetch($_, '(envelope)');
194     my $from     = $envelope->{$_}{envelope}{From};
195     my $subject  = $envelope->{$_}{envelope}{Subject};
196        $subject //= 'Unknown subject';
197
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 '';
201     } # if
202
203     if ($subject =~ /=?\S+?(Q|B)\?(.+)\?=/) {
204       $subject = decode_base64($2);
205     } # if
206
207     # Google Talk doesn't like #
208     $subject =~ s/\#//g;
209
210     # Now speak it!
211     my $logmsg = "From $from $subject";
212
213     my $greeting = $greetings[int rand $#greetings];
214     my $msg      = "$greeting from $from... $subject";
215        $msg      =~ s/\"/\\"/g;
216
217     my $hour = (localtime)[2];
218
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.
221     if ($hour >= 6) {
222       Say $msg;
223       $log->msg($logmsg);
224     } else {
225       $log->msg("$logmsg [silent]");
226     } # if
227
228     $unseen{$_} = 1;
229   } # for
230
231   # Re-establish callback
232   eval { $IMAP->idle(\&MonitorMail) };
233
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");
238
239   # Destroy current IMAP connection
240   $log->dbug("MonitorMail: Destorying IMAP connection to $opts{imap}");
241
242   undef $IMAP;
243
244   # Re-establish connection
245   Connect2IMAP;
246
247   $log->dbug("MonitorMail: Reconnected to IMAP server $opts{imap}");
248
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');
253
254   goto MONITORMAIL;
255
256   return; # To make perlcritic happy
257 } # MonitorMail
258
259 END {
260   # If $log is not yet defined then the exit is not unexpected
261   if ($log) {
262     my $msg = "$FindBin::Script ending unexpectedly!";
263
264     Say $msg;
265
266     $log->err($msg);
267   } # if
268 } # END
269
270 ## Main
271 GetOptions(
272   \%opts,
273   'usage',
274   'help',
275   'verbose',
276   'debug',
277   'daemon!',
278   'username=s',
279   'name=s',
280   'password=s',
281   'imap=s',
282   'usessl',
283   'useblocking',
284   'announce!',
285 ) || pod2usage;
286
287 unless ($opts{password}) {
288   verbose "I need $opts{username}'s password";
289   $opts{password} = GetPassword;
290 } # unless
291
292 $opts{name} //= $opts{imap};
293
294 if ($opts{username} =~ /.*\@(.*)$/) {
295   $opts{name} = $1;
296 } # if
297
298 if ($opts{daemon}) {
299   # Perl complains if we reference $DB::OUT only once
300   my $foo = $DB::OUT;
301   EnterDaemonMode unless defined $DB::OUT;
302 } # if
303
304 $log = Logger->new(
305   path        => '/var/log',
306   name        => "$Logger::me.$opts{name}",
307   timestamped => 'yes',
308   append      => 'yes',
309 );
310
311 Connect2IMAP;
312
313 if ($opts{username} =~ /(.*)\@/) {
314   $opts{user} = $1;
315 } else {
316   $opts{user} = $opts{username};
317 } # if
318
319 my $msg = "Now monitoring email for $opts{user}\@$opts{name}";
320
321 Say $msg if $opts{announce};
322
323 $log->msg($msg);
324
325 MonitorMail;
326
327 # Should not get here
328 $log->err("Falling off the edge of $0", 1);