New tunnel.pl
[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   if (-f "$FindBin::Bin/shh") {
159     $log->msg("Not speaking because we were asked to be quiet - $msg");
160
161     return;
162   } # if
163
164   my ($status, @output) = Execute "/usr/local/bin/gt \"$msg\"";
165
166   $log->err("Unable to speak (Status: $status) - "
167           . join ("\n", @output), $status) if $status;
168
169   return;
170 } # Say
171
172 sub MonitorMail() {
173   MONITORMAIL:
174
175   # First close and reselect the INBOX to get its current status
176   $IMAP->close;
177   $IMAP->select('INBOX')
178     or $log->err("Unable to select INBOX - ". $IMAP->errstr(), 1);
179
180   # Go through all of the unseen messages and add them to %unseen if they were
181   # not there already from a prior run and read
182   my %newUnseen = unseenMsgs;
183
184   # Now clean out any messages in %unseen that were not in the %newUnseen and
185   # marked as previously read
186   for (keys %unseen) {
187     if (defined $newUnseen{$_}) {
188       if ($unseen{$_}) {
189         delete $newUnseen{$_};
190       } # if
191     } else {
192       delete $unseen{$_}
193     } # if
194   } # for
195
196   for (keys %newUnseen) {
197     next if $unseen{$_};
198
199     my $envelope = $IMAP->fetch($_, '(envelope)');
200     my $from     = $envelope->{$_}{envelope}{From};
201     my $subject  = $envelope->{$_}{envelope}{Subject};
202        $subject //= 'Unknown subject';
203
204     # Extract the name only when the email is of the format "name <email>"
205     if ($from =~ /^"?(.*?)"?\s*\<(\S*)>/) {
206       $from = $1 if $1 ne '';
207     } # if
208
209     if ($subject =~ /=?\S+?(Q|B)\?(.+)\?=/) {
210       $subject = decode_base64($2);
211     } # if
212
213     # Google Talk doesn't like #
214     $subject =~ s/\#//g;
215
216     # Now speak it!
217     my $logmsg = "From $from $subject";
218
219     my $greeting = $greetings[int rand $#greetings];
220     my $msg      = "$greeting from $from... $subject";
221        $msg      =~ s/\"/\\"/g;
222
223     my $hour = (localtime)[2];
224
225     # Only announce if after 6 Am. Note this will announce up until
226     # midnight but that's ok. I want midnight to 6 Am as silent time.
227     if ($hour >= 6) {
228       Say $msg;
229       $log->msg($logmsg);
230     } else {
231       $log->msg("$logmsg [silent]");
232     } # if
233
234     $unseen{$_} = 1;
235   } # for
236
237   # Re-establish callback
238   eval { $IMAP->idle(\&MonitorMail) };
239
240   # If we return from idle then the server went away for some reason. With Gmail
241   # the server seems to time out around 30-40 minutes. Here we simply reconnect
242   # to the imap server and continue to MonitorMail.
243   $log->dbug("MonitorMail: Connection to $opts{imap} ended. Reconnecting");
244
245   # Destroy current IMAP connection
246   $log->dbug("MonitorMail: Destorying IMAP connection to $opts{imap}");
247
248   undef $IMAP;
249
250   # Re-establish connection
251   Connect2IMAP;
252
253   $log->dbug("MonitorMail: Reconnected to IMAP server $opts{imap}");
254
255   # MonitorMail again - the dreaded goto! Seems the cleanest way to restart
256   # in this instance. I could call MonitorMail() recursively but that would
257   # leave junk on the stack.
258   $log->dbug('MonitorMail: Going back to the top of the loop');
259
260   goto MONITORMAIL;
261
262   return; # To make perlcritic happy
263 } # MonitorMail
264
265 END {
266   # If $log is not yet defined then the exit is not unexpected
267   if ($log) {
268     my $msg = "$FindBin::Script ending unexpectedly!";
269
270     Say $msg;
271
272     $log->err($msg);
273   } # if
274 } # END
275
276 ## Main
277 GetOptions(
278   \%opts,
279   'usage',
280   'help',
281   'verbose',
282   'debug',
283   'daemon!',
284   'username=s',
285   'name=s',
286   'password=s',
287   'imap=s',
288   'usessl',
289   'useblocking',
290   'announce!',
291 ) || pod2usage;
292
293 unless ($opts{password}) {
294   verbose "I need $opts{username}'s password";
295   $opts{password} = GetPassword;
296 } # unless
297
298 $opts{name} //= $opts{imap};
299
300 if ($opts{username} =~ /.*\@(.*)$/) {
301   $opts{name} = $1;
302 } # if
303
304 if ($opts{daemon}) {
305   # Perl complains if we reference $DB::OUT only once
306   my $foo = $DB::OUT;
307   EnterDaemonMode unless defined $DB::OUT;
308 } # if
309
310 $log = Logger->new(
311   path        => '/var/log',
312   name        => "$Logger::me.$opts{name}",
313   timestamped => 'yes',
314   append      => 'yes',
315 );
316
317 Connect2IMAP;
318
319 if ($opts{username} =~ /(.*)\@/) {
320   $opts{user} = $1;
321 } else {
322   $opts{user} = $opts{username};
323 } # if
324
325 my $msg = "Now monitoring email for $opts{user}\@$opts{name}";
326
327 Say $msg if $opts{announce};
328
329 $log->msg($msg);
330
331 MonitorMail;
332
333 # Should not get here
334 $log->err("Falling off the edge of $0", 1);