Updated update system
[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]
35                          [-use|rname <username>] [-p|assword <password>]
36                          [-i|map <server>]
37                          [-an|nouce] [-ap|pend] [-da|emon] [-n|name <name>]
38                          [-uses|sl] [-useb|locking]
39
40  Where:
41    -usa|ge       Print this usage
42    -h|elp        Detailed help
43    -v|erbose     Verbose mode (Default: -verbose)
44    -de|bug       Turn on debugging (Default: Off)
45
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
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)
56
57  Signals:
58    $SIG{USR1}:   Toggles debug option
59    $SIG{USR2}:   Reestablishes connection to IMAP server
60
61 =head1 DESCRIPTION
62
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:
67
68   "<From> emailed <Subject>"
69
70 =cut
71
72 use strict;
73 use warnings;
74
75 use FindBin;
76 use Getopt::Long;
77 use Pod::Usage;
78 use Mail::IMAPTalk;
79 use MIME::Base64;
80
81 use lib "$FindBin::Bin/../lib";
82
83 use Display;
84 use Logger;
85 use Speak;
86 use TimeUtils;
87 use Utils;
88
89 my $defaultIMAPServer = 'defaria.com';
90 my $IMAP;
91 my %unseen;
92 my $log;
93
94 my @greetings = (
95   'Incoming message',
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',
100   'Potential spam',
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',
104   'Good news',
105   "What's this? A new message",
106 );
107
108 my %opts = (
109   usage       => sub { pod2usage },
110   help        => sub { pod2usage(-verbose => 2)},
111   verbose     => sub { set_verbose },
112   debug       => sub { set_debug },
113   daemon      => 1,
114   username    => $ENV{USER},
115   password    => $ENV{PASSWORD},
116   imap        => $defaultIMAPServer,
117 );
118
119 sub interrupted {
120   if (get_debug) {
121     $log->msg("Turning off debugging");
122     set_debug 0;
123   } else {
124     $log->msg("Turning on debugging");
125     set_debug 1;
126   } # if
127
128   return;
129 } # interrupted
130
131 sub Connect2IMAP;
132
133 sub restart {
134   $log->dbug("Re-establishing connection to $opts{imap} as $opts{username}");
135   Connect2IMAP;
136
137   goto MONITORMAIL;
138 } # restart
139
140 $SIG{USR1} = \&interrupted;
141 $SIG{USR2} = \&restart;
142
143 sub unseenMsgs() {
144   $IMAP->select('inbox') or
145     $log->err("Unable to select inbox: " . get_last_error(), 1);
146
147   return map { $_=> 0 } @{$IMAP->search('not', 'seen')};
148 } # unseenMsgs 
149
150 sub Connect2IMAP() {
151   $log->dbug("Connecting to $opts{imap} as $opts{username}");
152
153   # Destroy any old connections
154   undef $IMAP;
155
156   $IMAP = Mail::IMAPTalk->new(
157     Server      => $opts{imap},
158     Username    => $opts{username},
159     Password    => $opts{password},
160     UseSSL      => $opts{usessl},
161     UseBlocking => $opts{useblocking},
162   ) or $log->err("Unable to connect to IMAP server $opts{imap}: $@", 1);
163
164   $log->dbug("Connected to $opts{imap} as $opts{username}");
165
166   # Focus on INBOX only
167   $IMAP->select('inbox');
168
169   # Setup %unseen to have each unseen message index set to 0 meaning not read
170   # aloud yet
171   %unseen = unseenMsgs;
172
173   return;
174 } # Connect2IMAP
175
176 sub MonitorMail() {
177   MONITORMAIL:
178   $log->dbug("Top of MonitorMail loop");
179
180   # First close and reselect the INBOX to get its current status
181   $IMAP->close;
182   $IMAP->select('INBOX')
183     or $log->err("Unable to select INBOX - ". $IMAP->errstr(), 1);
184
185   $log->dbug("Closed and reselected INBOX");
186   # Go through all of the unseen messages and add them to %unseen if they were
187   # not there already from a prior run and read
188   my %newUnseen = unseenMsgs;
189
190   # Now clean out any messages in %unseen that were not in the %newUnseen and
191   # marked as previously read
192   $log->dbug("Cleaning out unseen");
193   for (keys %unseen) {
194     if (defined $newUnseen{$_}) {
195       if ($unseen{$_}) {
196         delete $newUnseen{$_};
197       } # if
198     } else {
199       delete $unseen{$_}
200     } # if
201   } # for
202
203   $log->dbug("Processing new unseen messages");
204   for (keys %newUnseen) {
205     next if $unseen{$_};
206
207     my $envelope = $IMAP->fetch($_, '(envelope)');
208     my $from     = $envelope->{$_}{envelope}{From};
209     my $subject  = $envelope->{$_}{envelope}{Subject};
210        $subject //= 'Unknown subject';
211
212     # Extract the name only when the email is of the format "name <email>"
213     if ($from =~ /^"?(.*?)"?\s*\<(\S*)>/) {
214       $from = $1 if $1 ne '';
215     } # if
216
217     if ($subject =~ /=?\S+?(Q|B)\?(.+)\?=/) {
218       $subject = decode_base64($2);
219     } # if
220
221     # Google Talk doesn't like #
222     $subject =~ s/\#//g;
223
224     # Now speak it!
225     my $logmsg = "From $from $subject";
226
227     my $greeting = $greetings[int rand $#greetings];
228     my $msg      = "$greeting from $from... $subject";
229        $msg      =~ s/\"/\\"/g;
230
231     my $hour = (localtime)[2];
232
233     # Only announce if after 6 Am. Note this will announce up until
234     # midnight but that's ok. I want midnight to 6 Am as silent time.
235     $log->dbug("About to speak/log");
236     if ($hour >= 7) {
237       $log->dbug("Calling speak");
238       speak $msg, $log;
239       $log->msg($logmsg);
240     } else {
241       $log->msg("$logmsg [silent]");
242     } # if
243
244     $unseen{$_} = 1;
245   } # for
246
247   # Let's time things
248   my $startTime = time;
249
250   # Re-establish callback
251   $log->dbug("Evaling idle");
252   eval { $IMAP->idle(\&MonitorMail) };
253
254   # If we return from idle then the server went away for some reason. With Gmail
255   # the server seems to time out around 30-40 minutes. Here we simply reconnect
256   # to the imap server and continue to MonitorMail.
257   restart;
258
259   return;
260 } # MonitorMail
261
262 END {
263   # If $log is not yet defined then the exit is not unexpected
264   if ($log) {
265     my $msg = "$FindBin::Script ending unexpectedly!";
266
267     speak $msg, $log;
268
269     $log->err($msg);
270   } # if
271 } # END
272
273 ## Main
274 GetOptions(
275   \%opts,
276   'usage',
277   'help',
278   'verbose',
279   'debug',
280   'daemon!',
281   'username=s',
282   'name=s',
283   'password=s',
284   'imap=s',
285   'usessl',
286   'useblocking',
287   'announce!',
288   'append',
289 ) || pod2usage;
290
291 unless ($opts{password}) {
292   verbose "I need $opts{username}'s password";
293   $opts{password} = GetPassword;
294 } # unless
295
296 $opts{name} //= $opts{imap};
297
298 if ($opts{username} =~ /.*\@(.*)$/) {
299   $opts{name} = $1;
300 } # if
301
302 if ($opts{daemon}) {
303   # Perl complains if we reference $DB::OUT only once
304   no warnings;
305   EnterDaemonMode unless defined $DB::OUT or get_debug;
306   use warnings;
307 } # if
308
309 $log = Logger->new(
310   path        => '/var/local/log',
311   name        => "$Logger::me.$opts{name}",
312   timestamped => 'yes',
313   append      => $opts{append},
314 );
315
316 Connect2IMAP;
317
318 if ($opts{username} =~ /(.*)\@/) {
319   $opts{user} = $1;
320 } else {
321   $opts{user} = $opts{username};
322 } # if
323
324 my $msg = "Now monitoring email for $opts{user}\@$opts{name}";
325
326 speak $msg, $log if $opts{announce};
327
328 $log->msg($msg);
329
330 MonitorMail;
331
332 # Should not get here
333 $log->err("Falling off the edge of $0", 1);