/[notice-sender]/trunk/Nos.pm
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Diff of /trunk/Nos.pm

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 62 by dpavlin, Wed Jun 22 12:31:45 2005 UTC revision 75 by dpavlin, Wed Aug 24 21:27:40 2005 UTC
# Line 16  our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all' Line 16  our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'
16  our @EXPORT = qw(  our @EXPORT = qw(
17  );  );
18    
19  our $VERSION = '0.5';  our $VERSION = '0.8';
20    
21  use Class::DBI::Loader;  use Class::DBI::Loader;
22  use Email::Valid;  use Email::Valid;
# Line 27  use Email::Simple; Line 27  use Email::Simple;
27  use Email::Address;  use Email::Address;
28  use Mail::DeliveryStatus::BounceParser;  use Mail::DeliveryStatus::BounceParser;
29  use Class::DBI::AbstractSearch;  use Class::DBI::AbstractSearch;
30    use Mail::Alias;
31    use Cwd qw(abs_path);
32    
33    
34  =head1 NAME  =head1 NAME
# Line 60  encoded) or anything else. Line 62  encoded) or anything else.
62  It will just queue your e-mail message to particular list (sending it to  It will just queue your e-mail message to particular list (sending it to
63  possibly remote Notice Sender SOAP server just once), send it out at  possibly remote Notice Sender SOAP server just once), send it out at
64  reasonable rate (so that it doesn't flood your e-mail infrastructure) and  reasonable rate (so that it doesn't flood your e-mail infrastructure) and
65  track replies.  keep track replies.
66    
67  It is best used to send smaller number of messages to more-or-less fixed  It is best used to send small number of messages to more-or-less fixed
68  list of recipients while allowing individual responses to be examined.  list of recipients while allowing individual responses to be examined.
69  Tipical use include replacing php e-mail sending code with SOAP call to  Tipical use include replacing php e-mail sending code with SOAP call to
70  Notice Sender. It does support additional C<ext_id> field for each member  Notice Sender. It does support additional C<ext_id> field for each member
# Line 70  which can be used to track some unique i Line 72  which can be used to track some unique i
72  particular user.  particular user.
73    
74  It comes with command-line utility C<sender.pl> which can be used to perform  It comes with command-line utility C<sender.pl> which can be used to perform
75  all available operation from scripts (see C<perldoc sender.pl>).  all available operation from scripts (see C<sender.pl --man>).
76  This command is also useful for debugging while writing client SOAP  This command is also useful for debugging while writing client SOAP
77  application.  application.
78    
# Line 114  sub new { Line 116  sub new {
116    
117          $self->{'hash_len'} ||= 8;          $self->{'hash_len'} ||= 8;
118    
119            $self->{'loader'}->find_class('received')->set_sql(
120                    'received' => qq{
121                            select
122                                    received.id as id,
123                                    lists.name as list,
124                                    users.ext_id as ext_id,
125                                    users.email as email,
126                                    bounced,received.date as date
127                            from received
128                            join lists on lists.id = list_id
129                            join users on users.id = user_id
130                    },
131            );
132    
133          $self ? return $self : return undef;          $self ? return $self : return undef;
134  }  }
135    
136    
137  =head2 new_list  =head2 create_list
138    
139  Create new list. Required arguments are name of C<list> and  Create new list. Required arguments are name of C<list>, C<email> address
140  C<email> address.  and path to C<aliases> file.
141    
142   $nos->new_list(   $nos->create_list(
143          list => 'My list',          list => 'My list',
144          from => 'Outgoing from comment',          from => 'Outgoing from comment',
145          email => 'my-list@example.com',          email => 'my-list@example.com',
146            aliases => '/etc/mail/mylist',
147            archive => '/path/to/mbox/archive',
148   );   );
149    
150  Returns ID of newly created list.  Returns ID of newly created list.
# Line 135  Calls internally C<_add_list>, see detai Line 153  Calls internally C<_add_list>, see detai
153    
154  =cut  =cut
155    
156  sub new_list {  sub create_list {
157          my $self = shift;          my $self = shift;
158    
159          my $arg = {@_};          my $arg = {@_};
# Line 154  sub new_list { Line 172  sub new_list {
172  }  }
173    
174    
175    =head2 drop_list
176    
177    Delete list from database.
178    
179     my $ok = drop_list(
180            list => 'My list'
181            aliases => '/etc/mail/mylist',
182     );
183    
184    Returns false if list doesn't exist.
185    
186    =cut
187    
188    sub drop_list {
189            my $self = shift;
190    
191            my $args = {@_};
192    
193            croak "need list to delete" unless ($args->{'list'});
194    
195            $args->{'list'} = lc($args->{'list'});
196    
197            my $aliases = $args->{'aliases'} || croak "need path to aliases file";
198    
199            my $lists = $self->{'loader'}->find_class('lists');
200    
201            my $this_list = $lists->search( name => $args->{'list'} )->first || return;
202    
203            $self->_remove_alias( email => $this_list->email, aliases => $aliases);
204    
205            $this_list->delete || croak "can't delete list\n";
206    
207            return $lists->dbi_commit || croak "can't commit";
208    }
209    
210    
211  =head2 add_member_to_list  =head2 add_member_to_list
212    
213  Add new member to list  Add new member to list
# Line 227  List all members of some list. Line 281  List all members of some list.
281          list => 'My list',          list => 'My list',
282   );   );
283    
284  Returns array of hashes with user informations like this:  Returns array of hashes with user information like this:
285    
286   $member = {   $member = {
287          name => 'Dobrica Pavlinusic',          name => 'Dobrica Pavlinusic',
# Line 435  Send e-mail using SMTP server at 127.0.0 Line 489  Send e-mail using SMTP server at 127.0.0
489    
490  =back  =back
491    
492    Any other driver name will try to use C<Email::Send::that_driver> module.
493    
494  Default sleep wait between two messages is 3 seconds.  Default sleep wait between two messages is 3 seconds.
495    
496    This method will return number of succesfully sent messages.
497    
498  =cut  =cut
499    
500  sub send_queued_messages {  sub send_queued_messages {
# Line 449  sub send_queued_messages { Line 507  sub send_queued_messages {
507          my $sleep = $arg->{'sleep'};          my $sleep = $arg->{'sleep'};
508          $sleep ||= 3 unless defined($sleep);          $sleep ||= 3 unless defined($sleep);
509    
510            # number of messages sent o.k.
511            my $ok = 0;
512    
513          my $email_send_driver = 'Email::Send::IO';          my $email_send_driver = 'Email::Send::IO';
514          my @email_send_options;          my @email_send_options;
515    
516          if (lc($driver) eq 'smtp') {          if (lc($driver) eq 'smtp') {
517                  $email_send_driver = 'Email::Send::SMTP';                  $email_send_driver = 'Email::Send::SMTP';
518                  @email_send_options = ['127.0.0.1'];                  @email_send_options = ['127.0.0.1'];
519            } elsif ($driver && $driver ne '') {
520                    $email_send_driver = 'Email::Send::' . $driver;
521          } else {          } else {
522                  warn "dumping all messages to STDERR\n";                  warn "dumping all messages to STDERR\n";
523          }          }
# Line 489  sub send_queued_messages { Line 552  sub send_queued_messages {
552                          if ($sent->search( message_id => $m->message_id, user_id => $u->user_id )) {                          if ($sent->search( message_id => $m->message_id, user_id => $u->user_id )) {
553                                  print "SKIP $to_email message allready sent\n";                                  print "SKIP $to_email message allready sent\n";
554                          } else {                          } else {
555                                  print "=> $to_email\n";                                  print "=> $to_email ";
556    
557                                  my $secret = $m->list_id->name . " " . $u->user_id->email . " " . $m->message_id;                                  my $secret = $m->list_id->name . " " . $u->user_id->email . " " . $m->message_id;
558                                  my $auth = Email::Auth::AddressHash->new( $secret, $self->{'hash_len'} );                                  my $auth = Email::Auth::AddressHash->new( $secret, $self->{'hash_len'} );
# Line 515  sub send_queued_messages { Line 578  sub send_queued_messages {
578                                  $m_obj->header_set('X-Nos-Hash', $hash);                                  $m_obj->header_set('X-Nos-Hash', $hash);
579    
580                                  # really send e-mail                                  # really send e-mail
581                                    my $sent_status;
582    
583                                  if (@email_send_options) {                                  if (@email_send_options) {
584                                          send $email_send_driver => $m_obj->as_string, @email_send_options;                                          $sent_status = send $email_send_driver => $m_obj->as_string, @email_send_options;
585                                  } else {                                  } else {
586                                          send $email_send_driver => $m_obj->as_string;                                          $sent_status = send $email_send_driver => $m_obj->as_string;
587                                  }                                  }
588    
589                                  $sent->create({                                  croak "can't send e-mail: $sent_status\n\nOriginal e-mail follows:\n".$m_obj->as_string unless ($sent_status);
590                                          message_id => $m->message_id,                                  my @bad;
591                                          user_id => $u->user_id,                                  @bad = @{ $sent_status->prop('bad') } if (eval { $sent_status->can('prop') });
592                                          hash => $hash,                                  croak "failed sending to ",join(",",@bad) if (@bad);
593                                  });  
594                                  $sent->dbi_commit;                                  if ($sent_status) {
595    
596                                            $sent->create({
597                                                    message_id => $m->message_id,
598                                                    user_id => $u->user_id,
599                                                    hash => $hash,
600                                            });
601                                            $sent->dbi_commit;
602    
603                                            print " - $sent_status\n";
604    
605                                            $ok++;
606                                    } else {
607                                            warn "ERROR: $sent_status\n";
608                                    }
609    
610                                  if ($sleep) {                                  if ($sleep) {
611                                          warn "sleeping $sleep seconds\n";                                          warn "sleeping $sleep seconds\n";
# Line 539  sub send_queued_messages { Line 618  sub send_queued_messages {
618                  $m->dbi_commit;                  $m->dbi_commit;
619          }          }
620    
621            return $ok;
622    
623  }  }
624    
625  =head2 inbox_message  =head2 inbox_message
# Line 637  sub inbox_message { Line 718  sub inbox_message {
718  #       print "message_id: ",($message_id || "not found")," -- $is_bounce\n";  #       print "message_id: ",($message_id || "not found")," -- $is_bounce\n";
719  }  }
720    
721    =head2 received_messages
722    
723    Returns all received messages for given list or user.
724    
725     my @received = $nos->received_message(
726            list => 'My list',
727            email => "john.doe@example.com",
728     );
729    
730    This method is used by C<sender.pl> when receiving e-mail messages.
731    
732    =cut
733    
734    sub received_messages {
735            my $self = shift;
736    
737            my $arg = {@_};
738    
739            croak "need list name or email" unless ($arg->{'list'} || $arg->{'email'});
740    
741            $arg->{'list'} = lc($arg->{'list'});
742            $arg->{'email'} = lc($arg->{'email'});
743    
744            my $rcvd = $self->{'loader'}->find_class('received')->search_received();
745    
746            return $rcvd;
747    }
748    
749    
750  =head1 INTERNAL METHODS  =head1 INTERNAL METHODS
751    
752  Beware of dragons! You shouldn't need to call those methods directly.  Beware of dragons! You shouldn't need to call those methods directly.
753    
754    
755    =head2 _add_aliases
756    
757    Add or update alias in C</etc/aliases> (or equivalent) file for selected list
758    
759     my $ok = $nos->add_aliases(
760            list => 'My list',
761            email => 'my-list@example.com',
762            aliases => '/etc/mail/mylist',
763            archive => '/path/to/mbox/archive',
764    
765     );
766    
767    C<archive> parametar is optional.
768    
769    Return false on failure.
770    
771    =cut
772    
773    sub _add_aliases {
774            my $self = shift;
775    
776            my $arg = {@_};
777    
778            foreach my $o (qw/list email aliases/) {
779                    croak "need $o option" unless ($arg->{$o});
780            }
781    
782            my $aliases = $arg->{'aliases'};
783            my $email = $arg->{'email'};
784            my $list = $arg->{'list'};
785    
786            unless (-e $aliases) {
787                    warn "aliases file $aliases doesn't exist, creating empty\n";
788                    open(my $fh, '>', $aliases) || croak "can't create $aliases: $!";
789                    close($fh);
790                    chmod 0777, $aliases || warn "can't change permission to 0777";
791            }
792    
793            die "FATAL: aliases file $aliases is not writable\n" unless (-w $aliases);
794    
795            my $a = new Mail::Alias($aliases) || croak "can't open aliases file $aliases: $!";
796    
797            my $target = '';
798    
799            if (my $archive = $arg->{'archive'}) {
800                    $target .= "$archive, ";
801    
802                    if (! -e $archive) {
803                            warn "please make sure that file $archive is writable for your e-mail user (defaulting to bad 777 permission for now)";
804    
805                            open(my $fh, '>', $archive) || croak "can't create archive file $archive: $!";
806                            close($fh);
807                            chmod 0777, $archive || croak "can't chmod archive file $archive to 0777: $!";
808                    }
809            }
810    
811            # resolve my path to absolute one
812            my $self_path = abs_path($0);
813            $self_path =~ s#/[^/]+$##;
814            $self_path =~ s#/t/*$#/#;
815    
816            $target .= qq#| cd $self_path && ./sender.pl --inbox="$list"#;
817    
818            if ($a->exists($email)) {
819                    $a->update($email, $target) or croak "can't update alias ".$a->error_check;
820            } else {
821                    $a->append($email, $target) or croak "can't add alias ".$a->error_check;
822            }
823    
824            #$a->write($aliases) or croak "can't save aliases $aliases ".$a->error_check;
825    
826            return 1;
827    }
828    
829  =head2 _add_list  =head2 _add_list
830    
831  Create new list  Create new list
# Line 650  Create new list Line 834  Create new list
834          list => 'My list',          list => 'My list',
835          from => 'Outgoing from comment',          from => 'Outgoing from comment',
836          email => 'my-list@example.com',          email => 'my-list@example.com',
837            aliases => '/etc/mail/mylist',
838   );   );
839    
840  Returns C<Class::DBI> object for created list.  Returns C<Class::DBI> object for created list.
# Line 668  sub _add_list { Line 853  sub _add_list {
853    
854          my $name = lc($arg->{'list'}) || confess "can't add list without name";          my $name = lc($arg->{'list'}) || confess "can't add list without name";
855          my $email = lc($arg->{'email'}) || confess "can't add list without e-mail";          my $email = lc($arg->{'email'}) || confess "can't add list without e-mail";
856            my $aliases = lc($arg->{'aliases'}) || confess "can't add list without path to aliases file";
857    
858          my $from_addr = $arg->{'from'};          my $from_addr = $arg->{'from'};
859    
860          my $lists = $self->{'loader'}->find_class('lists');          my $lists = $self->{'loader'}->find_class('lists');
861    
862            $self->_add_aliases(
863                    list => $name,
864                    email => $email,
865                    aliases => $aliases,
866            ) || warn "can't add alias $email for list $name";
867    
868          my $l = $lists->find_or_create({          my $l = $lists->find_or_create({
869                  name => $name,                  name => $name,
870                  email => $email,                  email => $email,
# Line 691  sub _add_list { Line 884  sub _add_list {
884  }  }
885    
886    
887    
888  =head2 _get_list  =head2 _get_list
889    
890  Get list C<Class::DBI> object.  Get list C<Class::DBI> object.
# Line 711  sub _get_list { Line 905  sub _get_list {
905          return $lists->search({ name => lc($name) })->first;          return $lists->search({ name => lc($name) })->first;
906  }  }
907    
908    
909    =head2 _remove_alias
910    
911    Remove list alias
912    
913     my $ok = $nos->_remove_alias(
914            email => 'mylist@example.com',
915            aliases => '/etc/mail/mylist',
916     );
917    
918    Returns true if list is removed or false if list doesn't exist. Dies in case of error.
919    
920    =cut
921    
922    sub _remove_alias {
923            my $self = shift;
924    
925            my $arg = {@_};
926    
927            my $email = lc($arg->{'email'}) || confess "can't remove alias without email";
928            my $aliases = lc($arg->{'aliases'}) || confess "can't remove alias without list";
929    
930            my $a = new Mail::Alias($aliases) || croak "can't open aliases file $aliases: $!";
931    
932            if ($a->exists($email)) {
933                    $a->delete($email) || croak "can't remove alias $email";
934            } else {
935                    return 0;
936            }
937    
938            return 1;
939    
940    }
941    
942  ###  ###
943  ### SOAP  ### SOAP
944  ###  ###
# Line 735  methods below). Line 963  methods below).
963    
964  my $nos;  my $nos;
965    
966    
967    =head2 new
968    
969    Create new SOAP object
970    
971     my $soap = new Nos::SOAP(
972            dsn => 'dbi:Pg:dbname=notices',
973            user => 'dpavlin',
974            passwd => '',
975            debug => 1,
976            verbose => 1,
977            hash_len => 8,
978            aliases => '/etc/aliases',
979     );
980    
981    If you are writing SOAP server (like C<soap.cgi> example), you will need to
982    call this method once to make new instance of Nos::SOAP and specify C<dsn>
983    and options for it.
984    
985    =cut
986    
987  sub new {  sub new {
988          my $class = shift;          my $class = shift;
989          my $self = {@_};          my $self = {@_};
990    
991            croak "need aliases parametar" unless ($self->{'aliases'});
992    
993          bless($self, $class);          bless($self, $class);
994    
995          $nos = new Nos( @_ ) || die "can't create Nos object";          $nos = new Nos( @_ ) || die "can't create Nos object";
# Line 746  sub new { Line 998  sub new {
998  }  }
999    
1000    
1001  =head2 NewList  =head2 CreateList
1002    
1003   $message_id = NewList(   $message_id = CreateList(
1004          list => 'My list',          list => 'My list',
1005          from => 'Name of my list',          from => 'Name of my list',
1006          email => 'my-list@example.com'          email => 'my-list@example.com'
# Line 756  sub new { Line 1008  sub new {
1008    
1009  =cut  =cut
1010    
1011  sub NewList {  sub CreateList {
1012          my $self = shift;          my $self = shift;
1013    
1014            my $aliases = $nos->{'aliases'} || croak "need 'aliases' argument to new constructor";
1015    
1016          if ($_[0] !~ m/^HASH/) {          if ($_[0] !~ m/^HASH/) {
1017                  return $nos->new_list(                  return $nos->create_list(
1018                          list => $_[0], from => $_[1], email => $_[2],                          list => $_[0], from => $_[1], email => $_[2],
1019                            aliases => $aliases,
1020                  );                  );
1021          } else {          } else {
1022                  return $nos->new_list( %{ shift @_ } );                  return $nos->create_list( %{ shift @_ }, aliases => $aliases );
1023          }          }
1024  }  }
1025    
1026    
1027    =head2 DropList
1028    
1029     $ok = DropList(
1030            list => 'My list',
1031     );
1032    
1033    =cut
1034    
1035    sub DropList {
1036            my $self = shift;
1037    
1038            my $aliases = $nos->{'aliases'} || croak "need 'aliases' argument to new constructor";
1039    
1040            if ($_[0] !~ m/^HASH/) {
1041                    return $nos->drop_list(
1042                            list => $_[0],
1043                            aliases => $aliases,
1044                    );
1045            } else {
1046                    return $nos->drop_list( %{ shift @_ }, aliases => $aliases );
1047            }
1048    }
1049    
1050  =head2 AddMemberToList  =head2 AddMemberToList
1051    
1052   $member_id = AddMemberToList(   $member_id = AddMemberToList(
# Line 801  sub AddMemberToList { Line 1079  sub AddMemberToList {
1079    
1080  Returns array of hashes with user informations, see C<list_members>.  Returns array of hashes with user informations, see C<list_members>.
1081    
 Returning arrays from SOAP calls is somewhat fuzzy (at least to me). It  
 seems that SOAP::Lite client thinks that it has array with one element which  
 is array of hashes with data.  
   
1082  =cut  =cut
1083    
1084  sub ListMembers {  sub ListMembers {
# Line 865  sub AddMessageToList { Line 1139  sub AddMessageToList {
1139          }          }
1140  }  }
1141    
1142    =head1 UNIMPLEMENTED FUNCTIONS
1143    
1144    This is a stub for documentation of unimplemented functions.
1145    
1146    =head2 MessagesReceived
1147    
1148     my @result = MessagesReceived(
1149            list => 'My list',
1150            email => 'jdoe@example.com',
1151     );
1152    
1153    You can specify just C<list> or C<email> or any combination of those.
1154    
1155    It will return array of hashes with following structure:
1156    
1157     {
1158            id => 42,                       # unique ID of received message
1159            list => 'My list',              # useful only of filtering by email
1160            ext_id => 9999,                 # ext_id from message user
1161            email => 'jdoe@example.com',    # e-mail of user
1162            bounced => 0,                   # true value if message is bounce
1163            date => '2005-08-24 18:57:24',  # date of recival in ISO format
1164     }
1165    
1166    =head2 MessagesReceivedByDate
1167    
1168    =head2 MessagesReceivedByDateWithContent
1169    
1170    =head2 ReceivedMessasgeContent
1171    
1172    Return content of received message.
1173    
1174     my $mail_body = ReceivedMessageContent( id => 42 );
1175    
1176    =cut
1177    
1178    
1179    
1180    
1181  ###  ###
1182    
1183    =head1 NOTE ON ARRAYS IN SOAP
1184    
1185    Returning arrays from SOAP calls is somewhat fuzzy (at least to me). It
1186    seems that SOAP::Lite client thinks that it has array with one element which
1187    is array of hashes with data.
1188    
1189  =head1 EXPORT  =head1 EXPORT
1190    
1191  Nothing.  Nothing.

Legend:
Removed from v.62  
changed lines
  Added in v.75

  ViewVC Help
Powered by ViewVC 1.1.26