#!/usr/bin/perl

 use MIME::Lite;
 use Net::SMTP;
 use strict;

 # Name:       mygroup-email.pl
 # Programmer: Lawrence Widman
 # Date:       12/25/2011 - 12/26/2011
 # Purpose     read a text file of email addresses and optional text
 # and logic:  messages, send one prepared PDF attachment together with a
 #             default text message or the optional one for a given
 #             recipient, and log the results into a separate text file 
 #             that can be used in place of the original input text file.
 #             The new log file entries include tag set to the year the
 #             email was sent so that new recipients can be added to
 #             either the original input file or the log file and this
 #             script rerun without re-sending the mail to recipients to
 #             whom it was originally sent.
 # Formats:    The format of the input and output files is ^field 1|field 2>field3$, where
 #               field1 is the email address of the recipient,
 #               field2 is the text to be placed in the body of the email,
 #                 (no new-lines are allowed: any text after a newline will be lost.)
 #               field3 is the tag field that shows which address have been sent the letter,
 #                 in a given year.
 #             The output file is the same as the input file (assuming no newlines in the text
 #             bodies), except the the tag is added for the current year for all new mail sent.
 # Todo:       check to see that the email is not returned.

 if( ($#ARGV+1) lt 3 ){
   usage();
 }
 my $filename_attachment = @ARGV[0];
 my $filename_input      = @ARGV[1];
 my $filename_output     = @ARGV[2];
 my $filename_log        = $filename_output . ".log";
 open my $log_output, ">>", $filename_log
    or die "Cannot open log output $filename_log";
# from http://www.go4expert.com/forums/showthread.php?t=15533
 my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) =
      localtime(time);
 $year += 1900;
 $mon++; # zero-based to facilitate picking label from array
 my $date_str = sprintf("%02d/%02d/%4d %02d:%02d:%02d",
                         $mon, $mday, $year, $hour, $min, $sec);
 print $log_output
       "$date_str\n".
       "Using message file $filename_attachment\n".
       "      input file $filename_input\n".
       "      output file $filename_output\n".
       "      log file $filename_log\n";

 my $default_text_body    = 'YOUR DEFAULT TEXT MESSAGE GOES HERE. NO NEWLINES (\n)!';
 my $default_from_address = 'YOUR EMAIL ADDRESS GOES HERE';
 my $default_subject      = 'YOUR DEFAULT SUBJECT LINE GOES HERE';

 open my $fp_msg, "<", $filename_attachment
    or die "Cannot open attachment $filename_attachment";
 close $fp_msg;  # we just wanted to make sure we could read it

 my %our_addresses  = undef;
 my %our_msg_bodies = undef;
 read_file(\%our_addresses, \%our_msg_bodies, $filename_input, 'T');
 read_file(\%our_addresses, '', $filename_output, 'F');

 my $num = scalar keys %our_addresses;
 $num--; # why is this more than the number of entries we read?
 print $log_output "Processing $num entries\n";
 foreach my $to_address ( sort keys %our_addresses ){
   next if $to_address eq '';
   my $value = %our_addresses->{$to_address};
   my $msg_body = $default_text_body;
   if( exists( %our_msg_bodies->{$to_address} ) ){
      $msg_body = %our_msg_bodies->{$to_address};
   };
   if( $value =~ m/$year/ ){
     print $log_output "mail was already sent to $to_address\n";
   } else {
     print $log_output
           "sending mail to $to_address with message body:\n$msg_body\n";
     # http://www.akadia.com/services/email_attachments_using_perl.html
     send_msg( $msg_body, $filename_attachment, $to_address );
     # we are assumming the mail was sent successfully....
   }
 }

 open my $fp_out, ">", $filename_output
    or die "Cannot open message file $filename_output";
 foreach my $key ( sort keys %our_addresses ){
   next if $key eq '';
   my $value    = %our_addresses->{$key};
   my $msg_text = %our_msg_bodies->{$key};
   if( $value !~ m/$year/ ){   
     if( $value eq '' ){
       $value = $year;
     } else {
       $value .= " $year";
     }
   }
   print $fp_out "$key|$msg_text>$value\n";
 }
 close $fp_out;

 exit;

 sub usage(){
   print "This program reads email addresses from a file and sends\n";
   print "a message to each address. It then writes each address to\n";
   print "a backup file with a tag delimited by a pipe (|) after the\n";
   print "email address so that the message will not be sent again if\n";
   print "the program is run again against the original file.\n";
   print "The backup file is first read to get the current tags, then\n";
   print "overwritten with the old and new tags. If this script crashes\n";
   print "it will likely be corrupted, so keep a backup.\n";
   print "";
   print "Usage: < attachment filename (the letter) > < input filename > < outputfilename >\n";
   print "  eg, winter-letter.2011.pdf email-list.txt email-list.sent-in-2011.txt\n";
   print "The format of the input and output files is ^field 1|field 2>field3$, where\n";
   print "  field1 is the email address of the recipient,\n";
   print "  field2 is the text to be placed in the body of the email,\n";
   print "   (no new-lines are allowed: any text after a newline will be lost.\n";
   print "  field3 is the tag field that shows which address have been sent the letter,\n";
   print "   in a given year.\n";
   print "The output file is the same as the input file (assuming no newlines in the text\n";
   print "bodies, except the the tag is added for the current year for all new mail sent.\n";
   exit;
 }

 sub read_file(){
   my $our_addresses_ptr  = shift;
   my $our_msg_bodies_ptr = shift;
   my $filename           = shift;
   my $initial_file_p     = shift;
   my $return_value = open my $fp_in, "<", $filename;
   if( $return_value eq undef ){
     if( $initial_file_p eq 'T' ){
       print "Cannot open message file $filename, so I'm quitting.\n";
       exit;
     } else {
       print $log_output "Cannot open message file $filename. Since this is the output file, will create it.\n";
       return;
     }
   }
   while( <$fp_in> ){
      chomp( $_ );
      # skip comments and blank lines
      next if $_ =~ m/^#/;
      next if $_ =~ m/^$/;
      # see whether the user forgot to add "|>" at the end of the email
      # address. If so, we add it here to facilitate processing below
      if( $_ !~ m/^.*\|.*>.*$/ ){
        print "$_ does not contain | or >, so I'll add 'em\n";
        $_ .= "|>";
      }
      # match pattern: [|tag(s)>[message body]]
      $_ =~ m/([\w\d.@, <>]+)[ ]*\|[ ]*([\w\d!,. ]*)>[ ]*([\w\d ]*)$/;
      if( ! exists( $our_addresses_ptr->{ $1 } ) ){
        # if this is the backup file, ignore any entries that aren't in the initial file
        if ($initial_file_p ne 'T'){
           print $log_output "ignoring extra entry $1\n";
           next;
        }
        if( $3 eq undef ){
          $our_addresses_ptr->{ $1 } = '';  # mark not sent
        } else {
          $our_addresses_ptr->{ $1 } = $3;  # marked as sent
        }
        # the message body appears only in the input file, so we store it here
        if( $our_msg_bodies_ptr ne '' ){
          if( length $2  > 0 ){
            $our_msg_bodies_ptr->{ $1 } = $2;
          } else {
            $our_msg_bodies_ptr->{ $1 } = $default_text_body;
          }
        }
      } else {
        if( $3 eq undef ){
#         $our_addresses_ptr->{ $1 } = '';  # mark not sent
        } else {
          $our_addresses_ptr->{ $1 } = $3;  # marked as sent
        }
      }
   }
   close $fp_in;
 }

# http://www.akadia.com/services/email_attachments_using_perl.html
sub send_msg(){
  my(  $msg_body, $filename_attachment, $to_address ) = @_;

  ### Adjust sender, recipient and your SMTP mailhost
  my $from_address = $default_from_address;
  my $to_address   = $to_address;
  my $mail_host    = 'localhost';

  ### Adjust subject and body message
  my $subject      = $default_subject;
  my $message_body = $msg_body;

  ### Adjust the filenames
  my $my_file_pdf   = $filename_attachment;
  my $your_file_pdf = $filename_attachment;

  ### Create the multipart container
  my $msg = MIME::Lite->new (
    From => $from_address,
    To => $to_address,
    Subject => $subject,
    Type =>'multipart/mixed'
  ) or die "Error creating multipart container: $!\n";

  ### Add the text message part
  $msg->attach (
    Type => 'TEXT',
    Data => $message_body
  ) or die "Error adding the text message part: $!\n";

  ### Add the PDF file
  $msg->attach (
    Type => 'application/pdf',
    Path => $my_file_pdf,
    Filename => $your_file_pdf,
    Disposition => 'attachment'
  ) or die "Error adding $my_file_pdf: $!\n";

  ### Send the Message
  MIME::Lite->send('smtp', $mail_host, Timeout=>60);
  $msg->send;
}