96da9823b2df9700ba64bb597e0bcf055ca9b02f
[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 $icon    = '/home/andrew/.icons/Thunderbird.jpg';
109 my $timeout = 5 * 1000;
110
111 my %opts = (
112   usage       => sub { pod2usage },
113   help        => sub { pod2usage(-verbose => 2)},
114   verbose     => sub { set_verbose },
115   debug       => sub { set_debug },
116   daemon      => 1,
117   username    => $ENV{USER},
118   password    => $ENV{PASSWORD},
119   imap        => $defaultIMAPServer,
120 );
121
122 sub notify($) {
123   my ($msg) = @_;
124
125   my $cmd = "notify-send -i $icon -t $timeout '$msg'";
126
127   Execute $cmd;
128 } # notify
129
130 sub interrupted {
131   if (get_debug) {
132     notify 'Turning off debugging';
133     set_debug 0;
134   } else {
135     notify ('Turning on debugging');
136     set_debug 1;
137   } # if
138
139   return;
140 } # interrupted
141
142 sub Connect2IMAP;
143
144 sub restart {
145   my $msg = "Re-establishing connection to $opts{imap} as $opts{username}";
146
147   $log->dbug($msg);
148
149   Connect2IMAP;
150
151   goto MONITORMAIL;
152 } # restart
153
154 $SIG{USR1} = \&interrupted;
155 $SIG{USR2} = \&restart;
156
157 sub unseenMsgs() {
158   $IMAP->select('inbox') or
159     $log->err("Unable to select inbox: " . get_last_error(), 1);
160
161   return map { $_=> 0 } @{$IMAP->search('not', 'seen')};
162 } # unseenMsgs 
163
164 sub Connect2IMAP() {
165   $log->dbug("Connecting to $opts{imap} as $opts{username}");
166
167   # Destroy any old connections
168   undef $IMAP;
169
170   $IMAP = Mail::IMAPTalk->new(
171     Server      => $opts{imap},
172     Username    => $opts{username},
173     Password    => $opts{password},
174     UseSSL      => $opts{usessl},
175     UseBlocking => $opts{useblocking},
176   ) or $log->err("Unable to connect to IMAP server $opts{imap}: $@", 1);
177
178   $log->dbug("Connected to $opts{imap} as $opts{username}");
179
180   # Focus on INBOX only
181   $IMAP->select('inbox');
182
183   # Setup %unseen to have each unseen message index set to 0 meaning not read
184   # aloud yet
185   %unseen = unseenMsgs;
186
187   return;
188 } # Connect2IMAP
189
190 sub MonitorMail() {
191   MONITORMAIL:
192   $log->dbug("Top of MonitorMail loop");
193
194   # First close and reselect the INBOX to get its current status
195   $IMAP->close;
196   $IMAP->select('INBOX')
197     or $log->err("Unable to select INBOX - ". $IMAP->errstr(), 1);
198
199   $log->dbug("Closed and reselected INBOX");
200   # Go through all of the unseen messages and add them to %unseen if they were
201   # not there already from a prior run and read
202   my %newUnseen = unseenMsgs;
203
204   # Now clean out any messages in %unseen that were not in the %newUnseen and
205   # marked as previously read
206   $log->dbug("Cleaning out unseen");
207   for (keys %unseen) {
208     if (defined $newUnseen{$_}) {
209       if ($unseen{$_}) {
210         delete $newUnseen{$_};
211       } # if
212     } else {
213       delete $unseen{$_}
214     } # if
215   } # for
216
217   $log->dbug("Processing new unseen messages");
218   for (keys %newUnseen) {
219     next if $unseen{$_};
220
221     my $envelope = $IMAP->fetch($_, '(envelope)');
222     my $from     = $envelope->{$_}{envelope}{From};
223     my $subject  = $envelope->{$_}{envelope}{Subject};
224        $subject //= 'Unknown subject';
225
226     # Extract the name only when the email is of the format "name <email>"
227     if ($from =~ /^"?(.*?)"?\s*\<(\S*)>/) {
228       $from = $1 if $1 ne '';
229     } # if
230
231     if ($subject =~ /=?\S+?(Q|B)\?(.+)\?=/) {
232       $subject = decode_base64($2);
233     } # if
234
235     # Google Talk doesn't like #
236     $subject =~ s/\#//g;
237
238     # Now speak it!
239     my $logmsg = "From $from $subject";
240
241     my $greeting = $greetings[int rand $#greetings];
242     my $msg      = "$greeting from $from... $subject";
243        $msg      =~ s/\"/\\"/g;
244
245     my $hour = (localtime)[2];
246
247     # Only announce if after 6 Am. Note this will announce up until
248     # midnight but that's ok. I want midnight to 6 Am as silent time.
249     $log->dbug("About to speak/log");
250     if ($hour >= 7) {
251       $log->dbug("Calling speak");
252       speak $msg, $log;
253       $log->msg($logmsg);
254     } else {
255       $log->msg("$logmsg [silent]");
256     } # if
257
258     $unseen{$_} = 1;
259   } # for
260
261   # Let's time things
262   my $startTime = time;
263
264   # Re-establish callback
265   $log->dbug("Evaling idle");
266   eval { $IMAP->idle(\&MonitorMail) };
267
268   # If we return from idle then the server went away for some reason. With Gmail
269   # the server seems to time out around 30-40 minutes. Here we simply reconnect
270   # to the imap server and continue to MonitorMail.
271   restart;
272
273   return;
274 } # MonitorMail
275
276 END {
277   # If $log is not yet defined then the exit is not unexpected
278   if ($log) {
279     my $msg = "$FindBin::Script ending unexpectedly!";
280
281     speak $msg, $log;
282
283     $log->err($msg);
284   } # if
285 } # END
286
287 ## Main
288 GetOptions(
289   \%opts,
290   'usage',
291   'help',
292   'verbose',
293   'debug',
294   'daemon!',
295   'username=s',
296   'name=s',
297   'password=s',
298   'imap=s',
299   'usessl',
300   'useblocking',
301   'announce!',
302   'append',
303 ) || pod2usage;
304
305 unless ($opts{password}) {
306   verbose "I need $opts{username}'s password";
307   $opts{password} = GetPassword;
308 } # unless
309
310 $opts{name} //= $opts{imap};
311
312 if ($opts{username} =~ /.*\@(.*)$/) {
313   $opts{name} = $1;
314 } # if
315
316 if ($opts{daemon}) {
317   # Perl complains if we reference $DB::OUT only once
318   no warnings;
319   EnterDaemonMode unless defined $DB::OUT or get_debug;
320   use warnings;
321 } # if
322
323 $log = Logger->new(
324   path        => '/var/local/log',
325   name        => "$Logger::me.$opts{name}",
326   timestamped => 'yes',
327   append      => $opts{append},
328 );
329
330 Connect2IMAP;
331
332 if ($opts{username} =~ /(.*)\@/) {
333   $opts{user} = $1;
334 } else {
335   $opts{user} = $opts{username};
336 } # if
337
338 my $msg = "Now monitoring email for $opts{user}\@$opts{name}";
339
340 speak $msg, $log if $opts{announce};
341
342 $log->msg($msg);
343
344 MonitorMail;
345
346 # Should not get here
347 $log->err("Falling off the edge of $0", 1);