Fixed announceEmail.pl to be less busy
[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    -i|map:     IMAP server to talk to (Default: defaria.com)
47    -uses|sl:   Whether or not to use SSL to connect (Default: False)
48
49 =head1 DESCRIPTION
50
51 This script will connect to an IMAP server, login and then monitor the user's
52 INBOX. When new messages arrive it will extract the From address and Subject
53 from the message and compose a message to be used by "Google Talk" to announce
54 the email. The message will be similar to:
55
56   "<From> emailed <Subject>"
57
58 =cut
59
60 use strict;
61 use warnings;
62
63 use FindBin;
64 use Getopt::Long;
65 use Pod::Usage;
66 use Mail::IMAPTalk;
67 use MIME::Base64;
68
69 use lib "$FindBin::Bin/../lib";
70
71 use Display;
72 use Logger;
73 use Utils;
74
75 my $defaultIMAPServer = 'defaria.com';
76 my $IMAP;
77 my %unseen;
78 my $log;
79
80 my @greetings = (
81   'Incoming message',
82   'You have received a new message',
83   'Hey I found this in your inbox',
84   'For some unknown reason this guy send you a message',
85   'Did you know you just got a message',
86   'Potential spam',
87   'You received a communique',
88   'I was looking in your inbox and found a message',
89   'Not sure you want to hear this message',
90   'Good news',
91 );
92
93 my %opts = (
94   usage    => sub { pod2usage },
95   help     => sub { pod2usage(-verbose => 2)},
96   verbose  => sub { set_verbose },
97   debug    => sub { set_debug },
98   daemon   => 1,
99   username => $ENV{USER},
100   password => $ENV{PASSWORD},
101   imap     => $defaultIMAPServer,
102   usessl   => 0,
103 );
104
105 sub interrupted {
106   if (get_debug) {
107     $log->msg("Turning off debugging");
108     set_debug 0;
109   } else {
110     $log->msg("Turning on debugging");
111     set_debug 1;
112   } # if
113
114   return;
115 } # interrupted
116
117 $SIG{USR1} = \&interrupted;
118
119 sub unseenMsgs() {
120   $IMAP->select('inbox') or
121     $log->err("Unable to select inbox: " . get_last_error(), 1);
122
123   return map { $_=> 0 } @{$IMAP->search('not', 'seen')};
124 } # unseenMsgs 
125
126 sub Connect2IMAP() {
127   $log->msg("Connecting to $opts{imap} as $opts{username}");
128
129   $IMAP = Mail::IMAPTalk->new(
130     Server   => $opts{imap},
131     Username => $opts{username},
132     Password => $opts{password},
133     UseSSL   => $opts{usessl},
134   ) or $log->err("Unable to connect to IMAP server $opts{imap}: $@", 1);
135
136   $log->msg('Connected');
137
138   # Focus on INBOX only
139   $IMAP->select('inbox');
140
141   # Setup %unseen to have each unseen message index set to 0 meaning not read
142   # aloud yet
143   %unseen = unseenMsgs;
144
145   return;
146 } # Connect2IMAP
147
148 sub MonitorMail() {
149   # First close and reselect the INBOX to get its current status
150   $IMAP->close;
151   $IMAP->select('INBOX')
152     or $log->err("Unable to select INBOX - ". $IMAP->errstr(), 1);
153
154   # Go through all of the unseen messages and add them to %unseen if they were
155   # not there already from a prior run and read
156   my %newUnseen = unseenMsgs;
157
158   # Now clean out any messages in %unseen that were not in the %newUnseen and
159   # marked as previously read
160   for (keys %unseen) {
161     if (defined $newUnseen{$_}) {
162       if ($unseen{$_}) {
163         delete $newUnseen{$_};
164       } # if
165     } else {
166       delete $unseen{$_}
167     } # if
168   } # for
169
170   for (keys %newUnseen) {
171     next if $unseen{$_};
172
173     my $envelope = $IMAP->fetch($_, '(envelope)');
174     my $from     = $envelope->{$_}{envelope}{From};
175     my $subject  = $envelope->{$_}{envelope}{Subject};
176        $subject //= 'Unknown subject';
177
178     # Extract the name only when the email is of the format "name <email>"
179     if ($from =~ /^"?(.*?)"?\s*\<(\S*)>/) {
180       $from = $1 if $1 ne '';
181     } # if
182
183     if ($subject =~ /=?\S+?(Q|B)\?(.+)\?=/) {
184       $subject = decode_base64($2);
185     } # if
186
187     # Google Talk doesn't like #
188     $subject =~ s/\#//g;
189
190     # Now speak it!
191     my $logmsg = "From $from $subject";
192
193     my $greeting = $greetings[int rand $#greetings];
194     my $msg      = "$greeting from $from... " . quotemeta $subject;
195        $msg      =~ s/\"/\\"/g;
196
197     # Log it
198     $log->msg($logmsg);
199
200     my $hour = (localtime)[2];
201
202     # Only announce if after 6 Am. Note this will announce up until
203     # midnight but that's ok. I want midnight to 6 Am as silent time.
204     if ($hour > 6) {
205       my $cmd = "/usr/local/bin/gt \"$msg\"";
206       my ($status, @output) = Execute $cmd;
207
208       if ($status) {
209         $log->err("Unable to execute $cmd" . join("\n", @output));
210       } # if
211     } # if
212
213     $unseen{$_} = 1;
214   } # for
215
216   # Re-establish callback
217   $IMAP->idle(\&MonitorMail);
218
219   # Should not get here
220   $log->err("Unable to re-establish IDLE callback from IMAP server $opts{imap}", 1);
221 } # MonitorMail
222
223 END {
224   $IMAP->quit if $IMAP;
225
226   $log->msg("$FindBin::Script ending unexpectedly!");
227 } # END
228
229 ## Main
230 GetOptions(
231   \%opts,
232   'usage',
233   'help',
234   'verbose',
235   'debug',
236   'daemon!',
237   'username=s',
238   'password=s',
239   'imap=s',
240   'usessl',
241   'sleep',
242 );
243
244 unless ($opts{password}) {
245   verbose "I need $opts{username}'s password";
246   $opts{password} = GetPassword;
247 } # unless
248
249 $opts{debug} = get_debug;
250
251 if ($opts{daemon}) {
252   EnterDaemonMode unless defined $DB::OUT;
253 } # if
254
255 $log = Logger->new(
256   path        => '/var/log',
257   timestamped => 'yes',
258   append      => 'yes',
259 );
260
261 Connect2IMAP;
262
263 my $msg = "Now monitoring email for $opts{username}\@$opts{imap}";
264
265 $log->msg($msg);
266
267 my $cmd = "/usr/local/bin/gt \"$msg\"";
268
269 my ($status, @output) = Execute $cmd;
270
271 $IMAP->idle(\&MonitorMail);
272
273 # Should not get here
274 $log->err("Falling off the edge of $0", 1);