diff options
Diffstat (limited to 'lib/Sietima/Role')
-rw-r--r-- | lib/Sietima/Role/AvoidDups.pm | 64 | ||||
-rw-r--r-- | lib/Sietima/Role/Debounce.pm | 58 | ||||
-rw-r--r-- | lib/Sietima/Role/Headers.pm | 164 | ||||
-rw-r--r-- | lib/Sietima/Role/ManualSubscription.pm | 64 | ||||
-rw-r--r-- | lib/Sietima/Role/NoMail.pm | 48 | ||||
-rw-r--r-- | lib/Sietima/Role/NoSpoof.pm | 54 | ||||
-rw-r--r-- | lib/Sietima/Role/ReplyTo.pm | 136 | ||||
-rw-r--r-- | lib/Sietima/Role/SubjectTag.pm | 73 | ||||
-rw-r--r-- | lib/Sietima/Role/SubscriberOnly.pm | 102 | ||||
-rw-r--r-- | lib/Sietima/Role/SubscriberOnly/Drop.pm | 43 | ||||
-rw-r--r-- | lib/Sietima/Role/SubscriberOnly/Moderate.pm | 282 | ||||
-rw-r--r-- | lib/Sietima/Role/WithMailStore.pm | 48 | ||||
-rw-r--r-- | lib/Sietima/Role/WithOwner.pm | 67 | ||||
-rw-r--r-- | lib/Sietima/Role/WithPostAddress.pm | 60 |
14 files changed, 834 insertions, 429 deletions
diff --git a/lib/Sietima/Role/AvoidDups.pm b/lib/Sietima/Role/AvoidDups.pm index e0a5bae..7f7a5c6 100644 --- a/lib/Sietima/Role/AvoidDups.pm +++ b/lib/Sietima/Role/AvoidDups.pm @@ -4,26 +4,9 @@ use Sietima::Policy; use Email::Address; use namespace::clean; -# VERSION +our $VERSION = '1.1.1'; # VERSION # ABSTRACT: prevent people from receiving the same message multiple times -=head1 SYNOPSIS - - my $sietima = Sietima->with_traits('AvoidDups')->new(\%args); - -=head1 DESCRIPTION - -A L<< C<Sietima> >> list with this role applied will not send a -message to a subscriber, if that subscriber is already mentioned in -the C<To:> or C<Cc:> header fields, because they can be assumed to be -already receiving the message directly from the sender. - -=modif C<subscribers_to_send_to> - -Filters out subscribers that L<match|Sietima::Subscriber/match> the -addresses in the C<To:> or C<Cc:> headers of the incoming email. - -=cut around subscribers_to_send_to => sub ($orig,$self,$mail) { my @already_receiving = map { @@ -43,3 +26,48 @@ around subscribers_to_send_to => sub ($orig,$self,$mail) { }; 1; + +__END__ + +=pod + +=encoding UTF-8 + +=head1 NAME + +Sietima::Role::AvoidDups - prevent people from receiving the same message multiple times + +=head1 VERSION + +version 1.1.1 + +=head1 SYNOPSIS + + my $sietima = Sietima->with_traits('AvoidDups')->new(\%args); + +=head1 DESCRIPTION + +A L<< C<Sietima> >> list with this role applied will not send a +message to a subscriber, if that subscriber is already mentioned in +the C<To:> or C<Cc:> header fields, because they can be assumed to be +already receiving the message directly from the sender. + +=head1 MODIFIED METHODS + +=head2 C<subscribers_to_send_to> + +Filters out subscribers that L<match|Sietima::Subscriber/match> the +addresses in the C<To:> or C<Cc:> headers of the incoming email. + +=head1 AUTHOR + +Gianni Ceccarelli <dakkar@thenautilus.net> + +=head1 COPYRIGHT AND LICENSE + +This software is copyright (c) 2023 by Gianni Ceccarelli <dakkar@thenautilus.net>. + +This is free software; you can redistribute it and/or modify it under +the same terms as the Perl 5 programming language system itself. + +=cut diff --git a/lib/Sietima/Role/Debounce.pm b/lib/Sietima/Role/Debounce.pm index e6bd087..9e143b4 100644 --- a/lib/Sietima/Role/Debounce.pm +++ b/lib/Sietima/Role/Debounce.pm @@ -3,9 +3,41 @@ use Moo::Role; use Sietima::Policy; use namespace::clean; -# VERSION +our $VERSION = '1.1.1'; # VERSION # ABSTRACT: avoid mail loops + +my $been_there = 'X-Been-There'; + +around munge_mail => sub ($orig,$self,$incoming_mail) { + my $return_path = $self->return_path->address; + if (my $there = $incoming_mail->header_str($been_there)) { + return if $there =~ m{\b\Q$return_path\E\b}; + } + + $incoming_mail->header_str_set( + $been_there => $return_path, + ); + + return $self->$orig($incoming_mail); +}; + +1; + +__END__ + +=pod + +=encoding UTF-8 + +=head1 NAME + +Sietima::Role::Debounce - avoid mail loops + +=head1 VERSION + +version 1.1.1 + =head1 SYNOPSIS my $sietima = Sietima->with_traits('Debounce')->new(\%args); @@ -18,28 +50,24 @@ have that same header. This prevents messages bounced by other services from being looped between the mailing list and those other services. -=modif C<munge_mail> +=head1 MODIFIED METHODS + +=head2 C<munge_mail> If the incoming email contains our C<X-Been-There:> header, this method will return an empty list (essentially dropping the message). Otherwise, the header is added, and the email is processed normally. -=cut +=head1 AUTHOR -my $been_there = 'X-Been-There'; +Gianni Ceccarelli <dakkar@thenautilus.net> -around munge_mail => sub ($orig,$self,$incoming_mail) { - my $return_path = $self->return_path->address; - if (my $there = $incoming_mail->header_str($been_there)) { - return if $there =~ m{\b\Q$return_path\E\b}; - } +=head1 COPYRIGHT AND LICENSE - $incoming_mail->header_str_set( - $been_there => $return_path, - ); +This software is copyright (c) 2023 by Gianni Ceccarelli <dakkar@thenautilus.net>. - return $self->$orig($incoming_mail); -}; +This is free software; you can redistribute it and/or modify it under +the same terms as the Perl 5 programming language system itself. -1; +=cut diff --git a/lib/Sietima/Role/Headers.pm b/lib/Sietima/Role/Headers.pm index fce7cf8..e4422c6 100644 --- a/lib/Sietima/Role/Headers.pm +++ b/lib/Sietima/Role/Headers.pm @@ -6,61 +6,9 @@ use Types::Standard qw(Str); use Sietima::Types qw(HeaderUriFromThings); use namespace::clean; -# VERSION +our $VERSION = '1.1.1'; # VERSION # ABSTRACT: adds standard list-related headers to messages -=head1 SYNOPSIS - - my $sietima = Sietima->with_traits('Headers')->new({ - %args, - name => $name_of_the_list, - }); - -=head1 DESCRIPTION - -A L<< C<Sietima> >> list with this role applied will add, to each -outgoing message, the set of headers defined in RFC 2919 and RFC 2369. - -This role uses the L<< C<list_addresses>|Sietima/list_addresses >> -method to determine what headers to add. - -If the C<name> attribute is set, a C<List-Id:> header will be added, -with a value built out of the name and the C<< -$self->list_addresses->{return_path} >> value (which is normally the -same as the L<< C<return_path>|Sietima/return_path >> attribute). - -Other C<List-*:> headers are built from the other values in the -C<list_addresses> hashref. Each of those values can be: - -=begin :list - -* an L<< C<Sietima::HeaderURI> >> object - -* a thing that can be passed to that class's constructor: - -=for :list -* an L<< C<Email::Address> >> object -* a L<< C<URI> >> object -* a string parseable as either - -* an arrayref containing any mix of the above - -=end :list - -As a special case, if C<< $self->list_addresses->{post} >> exists and -is false, the C<List-Post> header will have the value C<NO> to -indicate that the list does not accept incoming messages (e.g. it's an -announcement list). - -=attr C<name> - -Optional string, the name of the mailing list. If this attribute is -set, a C<List-Id:> header will be added, with a value built out of the -name and the C<< $self->list_addresses->{return_path} >> value (which -is normally the same as the L<< C<return_path>|Sietima/return_path >> -attribute). - -=cut has name => ( isa => Str, @@ -118,12 +66,6 @@ sub _add_headers_to($self,$message) { return; } -=modif C<munge_mail> - -This method adds list-management headers to each message returned by -the original method. - -=cut around munge_mail => sub ($orig,$self,$mail) { my @messages = $self->$orig($mail); @@ -132,3 +74,107 @@ around munge_mail => sub ($orig,$self,$mail) { }; 1; + +__END__ + +=pod + +=encoding UTF-8 + +=head1 NAME + +Sietima::Role::Headers - adds standard list-related headers to messages + +=head1 VERSION + +version 1.1.1 + +=head1 SYNOPSIS + + my $sietima = Sietima->with_traits('Headers')->new({ + %args, + name => $name_of_the_list, + }); + +=head1 DESCRIPTION + +A L<< C<Sietima> >> list with this role applied will add, to each +outgoing message, the set of headers defined in RFC 2919 and RFC 2369. + +This role uses the L<< C<list_addresses>|Sietima/list_addresses >> +method to determine what headers to add. + +If the C<name> attribute is set, a C<List-Id:> header will be added, +with a value built out of the name and the C<< +$self->list_addresses->{return_path} >> value (which is normally the +same as the L<< C<return_path>|Sietima/return_path >> attribute). + +Other C<List-*:> headers are built from the other values in the +C<list_addresses> hashref. Each of those values can be: + +=over 4 + +=item * + +an L<< C<Sietima::HeaderURI> >> object + +=item * + +a thing that can be passed to that class's constructor: + +=over 4 + +=item * + +an L<< C<Email::Address> >> object + +=item * + +a L<< C<URI> >> object + +=item * + +a string parseable as either + +=back + +=item * + +an arrayref containing any mix of the above + +=back + +As a special case, if C<< $self->list_addresses->{post} >> exists and +is false, the C<List-Post> header will have the value C<NO> to +indicate that the list does not accept incoming messages (e.g. it's an +announcement list). + +=head1 ATTRIBUTES + +=head2 C<name> + +Optional string, the name of the mailing list. If this attribute is +set, a C<List-Id:> header will be added, with a value built out of the +name and the C<< $self->list_addresses->{return_path} >> value (which +is normally the same as the L<< C<return_path>|Sietima/return_path >> +attribute). + +=head1 MODIFIED METHODS + +=head2 C<munge_mail> + +This method adds list-management headers to each message returned by +the original method. + +=head1 AUTHOR + +Gianni Ceccarelli <dakkar@thenautilus.net> + +=head1 COPYRIGHT AND LICENSE + +This software is copyright (c) 2023 by Gianni Ceccarelli <dakkar@thenautilus.net>. + +This is free software; you can redistribute it and/or modify it under +the same terms as the Perl 5 programming language system itself. + +=cut diff --git a/lib/Sietima/Role/ManualSubscription.pm b/lib/Sietima/Role/ManualSubscription.pm index c2711f0..5c884ec 100644 --- a/lib/Sietima/Role/ManualSubscription.pm +++ b/lib/Sietima/Role/ManualSubscription.pm @@ -4,11 +4,45 @@ use Sietima::Policy; use Sietima::HeaderURI; use namespace::clean; -# VERSION +our $VERSION = '1.1.1'; # VERSION # ABSTRACT: adds standard list-related headers to messages with 'Sietima::Role::WithOwner'; + +around list_addresses => sub($orig,$self) { + my $list_name = $self->name // 'the list'; + + return +{ + $self->$orig->%*, + subscribe => Sietima::HeaderURI->new_from_address( + $self->owner, + { subject => "Please add me to $list_name" }, + ), + unsubscribe => Sietima::HeaderURI->new_from_address( + $self->owner, + { subject => "Please remove me from $list_name" }, + ), + }; +}; + + +1; + +__END__ + +=pod + +=encoding UTF-8 + +=head1 NAME + +Sietima::Role::ManualSubscription - adds standard list-related headers to messages + +=head1 VERSION + +version 1.1.1 + =head1 SYNOPSIS my $sietima = Sietima->with_traits( @@ -26,29 +60,23 @@ C<Headers>|Sietima::Role::Headers >>) applied will add, to each outgoing message, headers specifying that to subscribe and unsubscribe, people sould email the list owner. -=modif C<list_addresses> +=head1 MODIFIED METHODS + +=head2 C<list_addresses> This method declares two "addresses", C<subscribe> and C<unsubscribe>. Both are C<mailto:> URLs for the list L<owner|Sietima::Role::WithOwner/owner>, with different subjects. -=cut +=head1 AUTHOR -around list_addresses => sub($orig,$self) { - my $list_name = $self->name // 'the list'; +Gianni Ceccarelli <dakkar@thenautilus.net> - return +{ - $self->$orig->%*, - subscribe => Sietima::HeaderURI->new_from_address( - $self->owner, - { subject => "Please add me to $list_name" }, - ), - unsubscribe => Sietima::HeaderURI->new_from_address( - $self->owner, - { subject => "Please remove me from $list_name" }, - ), - }; -}; +=head1 COPYRIGHT AND LICENSE +This software is copyright (c) 2023 by Gianni Ceccarelli <dakkar@thenautilus.net>. -1; +This is free software; you can redistribute it and/or modify it under +the same terms as the Perl 5 programming language system itself. + +=cut diff --git a/lib/Sietima/Role/NoMail.pm b/lib/Sietima/Role/NoMail.pm index 10071d6..0f333ea 100644 --- a/lib/Sietima/Role/NoMail.pm +++ b/lib/Sietima/Role/NoMail.pm @@ -3,9 +3,33 @@ use Moo::Role; use Sietima::Policy; use namespace::clean; -# VERSION +our $VERSION = '1.1.1'; # VERSION # ABSTRACT: don't send mail to those who don't want it + +around subscribers_to_send_to => sub ($orig,$self,$mail) { + return [ + grep { $_->prefs->{wants_mail} // 1 } + $self->$orig($mail)->@*, + ]; +}; + +1; + +__END__ + +=pod + +=encoding UTF-8 + +=head1 NAME + +Sietima::Role::NoMail - don't send mail to those who don't want it + +=head1 VERSION + +version 1.1.1 + =head1 SYNOPSIS my $sietima = Sietima->with_traits('NoMail')->new({ @@ -22,18 +46,22 @@ A L<< C<Sietima> >> list with this role applied will not send messages to subscribers that have the C<wants_mail> preference set to a false value. -=modif C<subscribers_to_send_to> +=head1 MODIFIED METHODS + +=head2 C<subscribers_to_send_to> Filters out subscribers that have the C<wants_mail> preference set to a false value. -=cut +=head1 AUTHOR -around subscribers_to_send_to => sub ($orig,$self,$mail) { - return [ - grep { $_->prefs->{wants_mail} // 1 } - $self->$orig($mail)->@*, - ]; -}; +Gianni Ceccarelli <dakkar@thenautilus.net> -1; +=head1 COPYRIGHT AND LICENSE + +This software is copyright (c) 2023 by Gianni Ceccarelli <dakkar@thenautilus.net>. + +This is free software; you can redistribute it and/or modify it under +the same terms as the Perl 5 programming language system itself. + +=cut diff --git a/lib/Sietima/Role/NoSpoof.pm b/lib/Sietima/Role/NoSpoof.pm index ba703cb..88efdc7 100644 --- a/lib/Sietima/Role/NoSpoof.pm +++ b/lib/Sietima/Role/NoSpoof.pm @@ -4,9 +4,41 @@ use Sietima::Policy; use Email::Address; use namespace::clean; -# VERSION +our $VERSION = '1.1.1'; # VERSION # ABSTRACT: never sends out messages from subscribers' addresses + +with 'Sietima::Role::WithPostAddress'; + +around munge_mail => sub ($orig,$self,$incoming_mail) { + my $sender = $self->post_address->address; + my ($from) = Email::Address->parse($incoming_mail->header_str('From')); + + $from->address($sender); + + $incoming_mail->header_str_set( + From => $from, + ); + + return $self->$orig($incoming_mail); +}; + +1; + +__END__ + +=pod + +=encoding UTF-8 + +=head1 NAME + +Sietima::Role::NoSpoof - never sends out messages from subscribers' addresses + +=head1 VERSION + +version 1.1.1 + =head1 SYNOPSIS my $sietima = Sietima->with_traits('NoSpoof')->new(\%args); @@ -21,21 +53,15 @@ C<post_address>|Sietima::Role::WithPostAddress >> (this is a This will make the list DMARC-compliant. -=cut +=head1 AUTHOR -with 'Sietima::Role::WithPostAddress'; +Gianni Ceccarelli <dakkar@thenautilus.net> -around munge_mail => sub ($orig,$self,$incoming_mail) { - my $sender = $self->post_address->address; - my ($from) = Email::Address->parse($incoming_mail->header_str('From')); +=head1 COPYRIGHT AND LICENSE - $from->address($sender); +This software is copyright (c) 2023 by Gianni Ceccarelli <dakkar@thenautilus.net>. - $incoming_mail->header_str_set( - From => $from, - ); +This is free software; you can redistribute it and/or modify it under +the same terms as the Perl 5 programming language system itself. - return $self->$orig($incoming_mail); -}; - -1; +=cut diff --git a/lib/Sietima/Role/ReplyTo.pm b/lib/Sietima/Role/ReplyTo.pm index 5ba828b..416e387 100644 --- a/lib/Sietima/Role/ReplyTo.pm +++ b/lib/Sietima/Role/ReplyTo.pm @@ -5,9 +5,79 @@ use Types::Standard qw(Bool); use List::AllUtils qw(part); use namespace::clean; -# VERSION +our $VERSION = '1.1.1'; # VERSION # ABSTRACT: munge the C<Reply-To> header + +with 'Sietima::Role::WithPostAddress'; + + +has munge_reply_to => ( + is => 'ro', + isa => Bool, + default => 0, +); + + +around munge_mail => sub ($orig,$self,$mail) { + my @messages = $self->$orig($mail); + my @ret; + for my $m (@messages) { + my ($leave,$munge) = part { + my $m = $_->prefs->{munge_reply_to}; + defined $m ? ( + $m ? 1 : 0 + ) : ( $self->munge_reply_to ? 1 : 0 ) + } $m->to->@*; + + if (not ($munge and $munge->@*)) { + # nothing to do + push @ret,$m; + } + elsif (not ($leave and $leave->@*)) { + # all these recipients want munging + $m->mail->header_str_set('Reply-To',$self->post_address->address); + push @ret,$m; + } + else { + # some want it, some don't: create two different messages + my $leave_message = Sietima::Message->new({ + mail => $m->mail, + from => $m->from, + to => $leave, + }); + + my $munged_mail = Email::MIME->new($m->mail->as_string); + $munged_mail->header_str_set('Reply-To',$self->post_address->address); + + my $munged_message = Sietima::Message->new({ + mail => $munged_mail, + from => $m->from, + to => $munge, + }); + + push @ret,$leave_message,$munged_message; + } + } + return @ret; +}; + +1; + +__END__ + +=pod + +=encoding UTF-8 + +=head1 NAME + +Sietima::Role::ReplyTo - munge the C<Reply-To> header + +=head1 VERSION + +version 1.1.1 + =head1 SYNOPSIS my $sietima = Sietima->with_traits('ReplyTo')->new({ @@ -35,26 +105,18 @@ not touched. This is a "sub-role" of L<< C<WithPostAddress>|Sietima::Role::WithPostAddress >>. -=cut - -with 'Sietima::Role::WithPostAddress'; +=head1 ATTRIBUTES -=attr C<munge_reply_to> +=head2 C<munge_reply_to> Optional boolean, defaults to false. If set to a true value, all messages will have their C<Reply-To:> header set to the value of the L<< /C<post_address> >> attribute. This setting can be overridden by individual subscribers with the C<munge_reply_to> preference. -=cut - -has munge_reply_to => ( - is => 'ro', - isa => Bool, - default => 0, -); +=head1 MODIFIED METHODS -=modif C<munge_mail> +=head2 C<munge_mail> For each message returned by the original method, this method partitions the subscribers, who are recipients of the message, @@ -73,49 +135,15 @@ don't, this method will clone the message, modify the header in one copy, set the appropriate part of the recipients to each copy, and pass both through. -=cut +=head1 AUTHOR -around munge_mail => sub ($orig,$self,$mail) { - my @messages = $self->$orig($mail); - my @ret; - for my $m (@messages) { - my ($leave,$munge) = part { - my $m = $_->prefs->{munge_reply_to}; - defined $m ? ( - $m ? 1 : 0 - ) : ( $self->munge_reply_to ? 1 : 0 ) - } $m->to->@*; +Gianni Ceccarelli <dakkar@thenautilus.net> - if (not ($munge and $munge->@*)) { - # nothing to do - push @ret,$m; - } - elsif (not ($leave and $leave->@*)) { - # all these recipients want munging - $m->mail->header_str_set('Reply-To',$self->post_address->address); - push @ret,$m; - } - else { - # some want it, some don't: create two different messages - my $leave_message = Sietima::Message->new({ - mail => $m->mail, - from => $m->from, - to => $leave, - }); - - my $munged_mail = Email::MIME->new($m->mail->as_string); - $munged_mail->header_str_set('Reply-To',$self->post_address->address); +=head1 COPYRIGHT AND LICENSE - my $munged_message = Sietima::Message->new({ - mail => $munged_mail, - from => $m->from, - to => $munge, - }); +This software is copyright (c) 2023 by Gianni Ceccarelli <dakkar@thenautilus.net>. - push @ret,$leave_message,$munged_message; - } - } - return @ret; -}; +This is free software; you can redistribute it and/or modify it under +the same terms as the Perl 5 programming language system itself. -1; +=cut diff --git a/lib/Sietima/Role/SubjectTag.pm b/lib/Sietima/Role/SubjectTag.pm index 7602405..d215f5e 100644 --- a/lib/Sietima/Role/SubjectTag.pm +++ b/lib/Sietima/Role/SubjectTag.pm @@ -4,9 +4,44 @@ use Sietima::Policy; use Types::Standard qw(Str); use namespace::clean; -# VERSION +our $VERSION = '1.1.1'; # VERSION # ABSTRACT: add a tag to messages' subjects + +has subject_tag => ( + is => 'ro', + isa => Str, + required => 1, +); + + +around munge_mail => sub ($orig,$self,$mail) { + my $tag = '['.$self->subject_tag.']'; + my $subject = $mail->header_str('Subject'); + unless ($subject =~ m{\Q$tag\E}) { + $mail->header_str_set( + Subject => "$tag $subject", + ); + } + return $self->$orig($mail); +}; + +1; + +__END__ + +=pod + +=encoding UTF-8 + +=head1 NAME + +Sietima::Role::SubjectTag - add a tag to messages' subjects + +=head1 VERSION + +version 1.1.1 + =head1 SYNOPSIS my $sietima = Sietima->with_traits('SubjectTag')->new({ @@ -19,7 +54,9 @@ use namespace::clean; A L<< C<Sietima> >> list with this role applied will prepend the given tag to every outgoing message's C<Subject:> header. -=attr C<subject_tag> +=head1 ATTRIBUTES + +=head2 C<subject_tag> Required string. This string, enclosed by square brackets, will be prepended to the C<Subject:> header of outgoing messages. For example, @@ -30,30 +67,22 @@ If the incoming message's C<Subject:> header already contains the tag, the header will not be modified. This prevents getting subjects like "[foo] Re: [foo] Re: [foo] new stuff". -=cut +=head1 MODIFIED METHODS -has subject_tag => ( - is => 'ro', - isa => Str, - required => 1, -); - -=modif C<munge_mail> +=head2 C<munge_mail> The subject of the incoming email is modified to add the tag (unless it's already there). The email is then processed normally. -=cut +=head1 AUTHOR -around munge_mail => sub ($orig,$self,$mail) { - my $tag = '['.$self->subject_tag.']'; - my $subject = $mail->header_str('Subject'); - unless ($subject =~ m{\Q$tag\E}) { - $mail->header_str_set( - Subject => "$tag $subject", - ); - } - return $self->$orig($mail); -}; +Gianni Ceccarelli <dakkar@thenautilus.net> -1; +=head1 COPYRIGHT AND LICENSE + +This software is copyright (c) 2023 by Gianni Ceccarelli <dakkar@thenautilus.net>. + +This is free software; you can redistribute it and/or modify it under +the same terms as the Perl 5 programming language system itself. + +=cut diff --git a/lib/Sietima/Role/SubscriberOnly.pm b/lib/Sietima/Role/SubscriberOnly.pm index 112f85f..2ecb160 100644 --- a/lib/Sietima/Role/SubscriberOnly.pm +++ b/lib/Sietima/Role/SubscriberOnly.pm @@ -7,9 +7,52 @@ use Types::Standard qw(Object CodeRef); use Type::Params -sigs; use namespace::clean; -# VERSION +our $VERSION = '1.1.1'; # VERSION # ABSTRACT: base role for "closed" lists + +requires 'munge_mail_from_non_subscriber'; + +our $let_it_pass=0; ## no critic(ProhibitPackageVars) + + +around munge_mail => sub ($orig,$self,$mail) { + my ($from) = Email::Address->parse( $mail->header_str('from') ); + if ( $let_it_pass or + any { $_->match($from) } $self->subscribers->@* ) { + $self->$orig($mail); + } + else { + $self->munge_mail_from_non_subscriber($mail); + } +}; + + +signature_for ignoring_subscriberonly => ( + method => Object, + positional => [ CodeRef ], +); +sub ignoring_subscriberonly($self,$code) { + local $let_it_pass = 1; + return $code->($self); +} + +1; + +__END__ + +=pod + +=encoding UTF-8 + +=head1 NAME + +Sietima::Role::SubscriberOnly - base role for "closed" lists + +=head1 VERSION + +version 1.1.1 + =head1 SYNOPSIS package Sietima::Role::SubscriberOnly::MyPolicy; @@ -29,7 +72,9 @@ with messages from non-subscribers. See L<< C<Sietima::Role::SubscriberOnly::Drop> >> and L<< C<Sietima::Role::SubscriberOnly::Moderate> >> for useable roles. -=require C<munge_mail_from_non_subscriber> +=head1 REQUIRED METHODS + +=head2 C<munge_mail_from_non_subscriber> sub munge_mail_from_non_subscriber($self,$mail) { ... } @@ -41,52 +86,37 @@ example to forward the email to the owner of the list. It can also have side-effects, like storing a copy of the message to approve later. -=cut +=head1 METHODS -requires 'munge_mail_from_non_subscriber'; +=head2 C<ignoring_subscriberonly> -our $let_it_pass=0; ## no critic(ProhibitPackageVars) + $sietima->ignoring_subscriberonly(sub($s) { + $s->handle_mail($mail); + }); + +This method provides a way to run Sietima ignoring the "subscriber +only" beaviour. Your coderef will be passed a Sietima object that will +behave exactly as the invocant of this method, minus this role's +modifications. + +=head1 MODIFIED METHODS -=modif C<munge_mail> +=head2 C<munge_mail> If the incoming email's C<From:> header contains an address that L<matches|Sietima::Subscriber/match> any of the subscribers, the email is processed normally. Otherwise, L<< /C<munge_mail_from_non_subscriber> >> is invoked. -=cut +=head1 AUTHOR -around munge_mail => sub ($orig,$self,$mail) { - my ($from) = Email::Address->parse( $mail->header_str('from') ); - if ( $let_it_pass or - any { $_->match($from) } $self->subscribers->@* ) { - $self->$orig($mail); - } - else { - $self->munge_mail_from_non_subscriber($mail); - } -}; +Gianni Ceccarelli <dakkar@thenautilus.net> -=method C<ignoring_subscriberonly> +=head1 COPYRIGHT AND LICENSE - $sietima->ignoring_subscriberonly(sub($s) { - $s->handle_mail($mail); - }); +This software is copyright (c) 2023 by Gianni Ceccarelli <dakkar@thenautilus.net>. -This method provides a way to run Sietima ignoring the "subscriber -only" beaviour. Your coderef will be passed a Sietima object that will -behave exactly as the invocant of this method, minus this role's -modifications. +This is free software; you can redistribute it and/or modify it under +the same terms as the Perl 5 programming language system itself. =cut - -signature_for ignoring_subscriberonly => ( - method => Object, - positional => [ CodeRef ], -); -sub ignoring_subscriberonly($self,$code) { - local $let_it_pass = 1; - return $code->($self); -} - -1; diff --git a/lib/Sietima/Role/SubscriberOnly/Drop.pm b/lib/Sietima/Role/SubscriberOnly/Drop.pm index d9de94e..0eb63b0 100644 --- a/lib/Sietima/Role/SubscriberOnly/Drop.pm +++ b/lib/Sietima/Role/SubscriberOnly/Drop.pm @@ -3,9 +3,31 @@ use Moo::Role; use Sietima::Policy; use namespace::clean; -# VERSION +our $VERSION = '1.1.1'; # VERSION # ABSTRACT: drop messages from non-subscribers + +with 'Sietima::Role::SubscriberOnly'; + + +sub munge_mail_from_non_subscriber { } + +1; + +__END__ + +=pod + +=encoding UTF-8 + +=head1 NAME + +Sietima::Role::SubscriberOnly::Drop - drop messages from non-subscribers + +=head1 VERSION + +version 1.1.1 + =head1 SYNOPSIS my $sietima = Sietima->with_traits('SubscribersOnly::Drop')->new({ @@ -21,16 +43,21 @@ subscribers. This is a "sub-role" of L<< C<SubscribersOnly>|Sietima::Role::SubscriberOnly >>. -=cut - -with 'Sietima::Role::SubscriberOnly'; +=head1 METHODS -=method C<munge_mail_from_non_subscriber> +=head2 C<munge_mail_from_non_subscriber> Does nothing, returns an empty list. -=cut +=head1 AUTHOR -sub munge_mail_from_non_subscriber { } +Gianni Ceccarelli <dakkar@thenautilus.net> -1; +=head1 COPYRIGHT AND LICENSE + +This software is copyright (c) 2023 by Gianni Ceccarelli <dakkar@thenautilus.net>. + +This is free software; you can redistribute it and/or modify it under +the same terms as the Perl 5 programming language system itself. + +=cut diff --git a/lib/Sietima/Role/SubscriberOnly/Moderate.pm b/lib/Sietima/Role/SubscriberOnly/Moderate.pm index 42e5bc3..1450597 100644 --- a/lib/Sietima/Role/SubscriberOnly/Moderate.pm +++ b/lib/Sietima/Role/SubscriberOnly/Moderate.pm @@ -4,9 +4,143 @@ use Sietima::Policy; use Email::Stuffer; use namespace::clean; -# VERSION +our $VERSION = '1.1.1'; # VERSION # ABSTRACT: moderate messages from non-subscribers + +with 'Sietima::Role::SubscriberOnly', + 'Sietima::Role::WithMailStore', + 'Sietima::Role::WithOwner'; + + +sub munge_mail_from_non_subscriber ($self,$mail) { + my $id = $self->mail_store->store($mail,'moderation'); + my $notice = Email::Stuffer + ->from($self->return_path->address) + ->to($self->owner->address) + ->subject("Message held for moderation - ".$mail->header_str('subject')) + ->text_body("Use id $id to refer to it") + ->attach( + $mail->as_string, + content_type => 'message/rfc822', + # some clients, most notably Claws-Mail, seem to have + # problems with encodings other than this + encoding => '7bit', + ); + + return Sietima::Message->new({ + mail => $notice->email, + from => $self->return_path, + to => [ $self->owner ], + }); +} + + +sub resume ($self,$mail_id) { + my $mail = $self->mail_store->retrieve_by_id($mail_id); + $self->ignoring_subscriberonly( + sub($s) { $s->handle_mail($mail) }, + ); + $self->mail_store->remove($mail_id); +} + + +sub drop ($self,$mail_id) { + $self->mail_store->remove($mail_id); +} + + +sub list_mails_in_moderation_queue ($self,$runner,@) { + my $mails = $self->mail_store->retrieve_by_tags('moderation'); + $runner->out(sprintf 'There are %d messages held for moderation:',scalar($mails->@*)); + for my $mail ($mails->@*) { + $runner->out(sprintf '* %s %s "%s" (%s)', + $mail->{id}, + $mail->{mail}->header_str('From')//'<no from>', + $mail->{mail}->header_str('Subject')//'<no subject>', + $mail->{mail}->header_str('Date')//'<no date>', + ); + } +} + + +sub show_mail_from_moderation_queue ($self,$runner,@) { + my $id = $runner->parameters->{'mail-id'}; + my $mail = $self->mail_store->retrieve_by_id($id); + $runner->out("Message $id:"); + $runner->out($mail->as_string =~ s{\r\n}{\n}gr); +} + + +sub resume_mail_from_moderation_queue ($self,$runner,@) { + $self->resume($runner->parameters->{'mail-id'}); +} + + +sub drop_mail_from_moderation_queue ($self,$runner,@) { + $self->drop($runner->parameters->{'mail-id'}); +} + + +around command_line_spec => sub ($orig,$self) { + my $spec = $self->$orig(); + + # this allows us to tab-complete identifiers from the shell! + my $list_mail_ids = sub ($self,$runner,$args) { + $self->mail_store->retrieve_ids_by_tags('moderation'); + }; + # a little factoring: $etc->($command_name) generates the spec for + # sub-commands that require a mail id + my $etc = sub($cmd) { + return ( + summary => "$cmd the given mail, currently held for moderation", + parameters => [ + { + name => 'mail-id', + required => 1, + summary => "id of the mail to $cmd", + completion => { op => $list_mail_ids }, + }, + ], + ); + }; + + $spec->{subcommands}{'list-held'} = { + op => 'list_mails_in_moderation_queue', + summary => 'list all mails currently held for moderation', + }; + $spec->{subcommands}{'show-held'} = { + op => 'show_mail_from_moderation_queue', + $etc->('show'), + }; + $spec->{subcommands}{'resume-held'} = { + op => 'resume_mail_from_moderation_queue', + $etc->('resume'), + }; + $spec->{subcommands}{'drop-held'} = { + op => 'drop_mail_from_moderation_queue', + $etc->('drop'), + }; + + return $spec; +}; + +1; + +__END__ + +=pod + +=encoding UTF-8 + +=head1 NAME + +Sietima::Role::SubscriberOnly::Moderate - moderate messages from non-subscribers + +=head1 VERSION + +version 1.1.1 + =head1 SYNOPSIS my $sietima = Sietima->with_traits('SubscribersOnly::Moderate')->new({ @@ -31,43 +165,15 @@ C<SubscribersOnly>|Sietima::Role::SubscriberOnly >>, L<< C<WithMailStore>|Sietima::Role::WithMailStore >>, and L<< C<WithOwner>|Sietima::Role::WithOwner >>. -=cut - -with 'Sietima::Role::SubscriberOnly', - 'Sietima::Role::WithMailStore', - 'Sietima::Role::WithOwner'; +=head1 METHODS -=method C<munge_mail_from_non_subscriber> +=head2 C<munge_mail_from_non_subscriber> L<Stores|Sietima::MailStore/store> the email with the C<moderation> tag, and forwards it to the L<list owner|Sietima::Role::WithOwner/owner>. -=cut - -sub munge_mail_from_non_subscriber ($self,$mail) { - my $id = $self->mail_store->store($mail,'moderation'); - my $notice = Email::Stuffer - ->from($self->return_path->address) - ->to($self->owner->address) - ->subject("Message held for moderation - ".$mail->header_str('subject')) - ->text_body("Use id $id to refer to it") - ->attach( - $mail->as_string, - content_type => 'message/rfc822', - # some clients, most notably Claws-Mail, seem to have - # problems with encodings other than this - encoding => '7bit', - ); - - return Sietima::Message->new({ - mail => $notice->email, - from => $self->return_path, - to => [ $self->owner ], - }); -} - -=method C<resume> +=head2 C<resume> $sietima->resume($mail_id); @@ -78,17 +184,7 @@ C<ignoring_subscriberonly>|Sietima::Role::SubscriberOnly/ignoring_subscriberonly >>. This will make sure that the email is not caught again by the subscriber-only filter. -=cut - -sub resume ($self,$mail_id) { - my $mail = $self->mail_store->retrieve_by_id($mail_id); - $self->ignoring_subscriberonly( - sub($s) { $s->handle_mail($mail) }, - ); - $self->mail_store->remove($mail_id); -} - -=method C<drop> +=head2 C<drop> $sietima->drop($mail_id); @@ -96,13 +192,7 @@ Given the identifier returned when L<storing|Sietima::MailStore/store>-ing an email, this method deletes the email from the store. -=cut - -sub drop ($self,$mail_id) { - $self->mail_store->remove($mail_id); -} - -=method C<list_mails_in_moderation_queue> +=head2 C<list_mails_in_moderation_queue> $sietima->list_mails_in_moderation_queue($sietima_runner); @@ -114,22 +204,7 @@ L<< C<Sietima::Runner> >> object. This method is usually invoked from the command line, see L<< /C<command_line_spec> >>. -=cut - -sub list_mails_in_moderation_queue ($self,$runner,@) { - my $mails = $self->mail_store->retrieve_by_tags('moderation'); - $runner->out(sprintf 'There are %d messages held for moderation:',scalar($mails->@*)); - for my $mail ($mails->@*) { - $runner->out(sprintf '* %s %s "%s" (%s)', - $mail->{id}, - $mail->{mail}->header_str('From')//'<no from>', - $mail->{mail}->header_str('Subject')//'<no subject>', - $mail->{mail}->header_str('Date')//'<no date>', - ); - } -} - -=method C<show_mail_from_moderation_queue> +=head2 C<show_mail_from_moderation_queue> $sietima->show_mail_from_moderation_queue($sietima_runner); @@ -140,16 +215,7 @@ out|App::Spec::Runner/out> via the L<< C<Sietima::Runner> >> object. This method is usually invoked from the command line, see L<< /C<command_line_spec> >>. -=cut - -sub show_mail_from_moderation_queue ($self,$runner,@) { - my $id = $runner->parameters->{'mail-id'}; - my $mail = $self->mail_store->retrieve_by_id($id); - $runner->out("Message $id:"); - $runner->out($mail->as_string =~ s{\r\n}{\n}gr); -} - -=method C<resume_mail_from_moderation_queue> +=head2 C<resume_mail_from_moderation_queue> $sietima->resume_mail_from_moderation_queue($sietima_runner); @@ -160,13 +226,7 @@ it. This method is usually invoked from the command line, see L<< /C<command_line_spec> >>. -=cut - -sub resume_mail_from_moderation_queue ($self,$runner,@) { - $self->resume($runner->parameters->{'mail-id'}); -} - -=method C<drop_mail_from_moderation_queue> +=head2 C<drop_mail_from_moderation_queue> $sietima->drop_mail_from_moderation_queue($sietima_runner); @@ -176,13 +236,9 @@ of the message requested from the command line, and L<drops|/drop> it. This method is usually invoked from the command line, see L<< /C<command_line_spec> >>. -=cut +=head1 MODIFIED METHODS -sub drop_mail_from_moderation_queue ($self,$runner,@) { - $self->drop($runner->parameters->{'mail-id'}); -} - -=modif C<command_line_spec> +=head2 C<command_line_spec> This method adds the following sub-commands for the command line: @@ -220,49 +276,15 @@ identifier is expected as a positional parameter. =back -=cut +=head1 AUTHOR -around command_line_spec => sub ($orig,$self) { - my $spec = $self->$orig(); +Gianni Ceccarelli <dakkar@thenautilus.net> - # this allows us to tab-complete identifiers from the shell! - my $list_mail_ids = sub ($self,$runner,$args) { - $self->mail_store->retrieve_ids_by_tags('moderation'); - }; - # a little factoring: $etc->($command_name) generates the spec for - # sub-commands that require a mail id - my $etc = sub($cmd) { - return ( - summary => "$cmd the given mail, currently held for moderation", - parameters => [ - { - name => 'mail-id', - required => 1, - summary => "id of the mail to $cmd", - completion => { op => $list_mail_ids }, - }, - ], - ); - }; +=head1 COPYRIGHT AND LICENSE - $spec->{subcommands}{'list-held'} = { - op => 'list_mails_in_moderation_queue', - summary => 'list all mails currently held for moderation', - }; - $spec->{subcommands}{'show-held'} = { - op => 'show_mail_from_moderation_queue', - $etc->('show'), - }; - $spec->{subcommands}{'resume-held'} = { - op => 'resume_mail_from_moderation_queue', - $etc->('resume'), - }; - $spec->{subcommands}{'drop-held'} = { - op => 'drop_mail_from_moderation_queue', - $etc->('drop'), - }; +This software is copyright (c) 2023 by Gianni Ceccarelli <dakkar@thenautilus.net>. - return $spec; -}; +This is free software; you can redistribute it and/or modify it under +the same terms as the Perl 5 programming language system itself. -1; +=cut diff --git a/lib/Sietima/Role/WithMailStore.pm b/lib/Sietima/Role/WithMailStore.pm index 7ca4b4e..7fc1e37 100644 --- a/lib/Sietima/Role/WithMailStore.pm +++ b/lib/Sietima/Role/WithMailStore.pm @@ -4,9 +4,33 @@ use Sietima::Policy; use Sietima::Types qw(MailStore MailStoreFromHashRef); use namespace::clean; -# VERSION +our $VERSION = '1.1.1'; # VERSION # ABSTRACT: role for lists with a store for messages + +has mail_store => ( + is => 'ro', + isa => MailStore, + required => 1, + coerce => MailStoreFromHashRef, +); + +1; + +__END__ + +=pod + +=encoding UTF-8 + +=head1 NAME + +Sietima::Role::WithMailStore - role for lists with a store for messages + +=head1 VERSION + +version 1.1.1 + =head1 SYNOPSIS my $sietima = Sietima->with_traits('WithMailStore')->new({ @@ -25,7 +49,9 @@ On its own, this role is not very useful, but other roles (like L<< C<SubscriberOnly::Moderate>|Sietima::Role::SubscriberOnly::Moderate >>) can have uses for an object that can persistently store messages. -=attr C<mail_store> +=head1 ATTRIBUTES + +=head2 C<mail_store> Required instance of an object that consumes the L<< C<Sietima::MailStore> >> role. Instead of passing an instance, you can @@ -33,13 +59,15 @@ pass a hashref (like in the L</synopsis>): the C<class> key provides the class name, and the rest of the hash will be passed to its constructor. -=cut +=head1 AUTHOR -has mail_store => ( - is => 'ro', - isa => MailStore, - required => 1, - coerce => MailStoreFromHashRef, -); +Gianni Ceccarelli <dakkar@thenautilus.net> -1; +=head1 COPYRIGHT AND LICENSE + +This software is copyright (c) 2023 by Gianni Ceccarelli <dakkar@thenautilus.net>. + +This is free software; you can redistribute it and/or modify it under +the same terms as the Perl 5 programming language system itself. + +=cut diff --git a/lib/Sietima/Role/WithOwner.pm b/lib/Sietima/Role/WithOwner.pm index 1dfd362..6800ab0 100644 --- a/lib/Sietima/Role/WithOwner.pm +++ b/lib/Sietima/Role/WithOwner.pm @@ -4,9 +4,41 @@ use Sietima::Policy; use Sietima::Types qw(Address AddressFromStr); use namespace::clean; -# VERSION +our $VERSION = '1.1.1'; # VERSION # ABSTRACT: role for lists with an owner + +has owner => ( + is => 'ro', + isa => Address, + required => 1, + coerce => AddressFromStr, +); + + +around list_addresses => sub($orig,$self) { + return +{ + $self->$orig->%*, + owner => $self->owner, + }; +}; + +1; + +__END__ + +=pod + +=encoding UTF-8 + +=head1 NAME + +Sietima::Role::WithOwner - role for lists with an owner + +=head1 VERSION + +version 1.1.1 + =head1 SYNOPSIS my $sietima = Sietima->with_traits('WithOwner')->new({ @@ -23,31 +55,28 @@ On its own, this role is not very useful, but other roles (like L<< C<SubscriberOnly::Moderate>|Sietima::Role::SubscriberOnly::Moderate >>) can have uses for an owner address. -=attr C<owner> +=head1 ATTRIBUTES + +=head2 C<owner> Required instance of L<< C<Email::Address> >>, coercible from a string. This is the address of the owner of the list. -=cut - -has owner => ( - is => 'ro', - isa => Address, - required => 1, - coerce => AddressFromStr, -); +=head1 MODIFIED METHODS -=modif C<list_addresses> +=head2 C<list_addresses> This method declares the C<owner> address. -=cut +=head1 AUTHOR -around list_addresses => sub($orig,$self) { - return +{ - $self->$orig->%*, - owner => $self->owner, - }; -}; +Gianni Ceccarelli <dakkar@thenautilus.net> -1; +=head1 COPYRIGHT AND LICENSE + +This software is copyright (c) 2023 by Gianni Ceccarelli <dakkar@thenautilus.net>. + +This is free software; you can redistribute it and/or modify it under +the same terms as the Perl 5 programming language system itself. + +=cut diff --git a/lib/Sietima/Role/WithPostAddress.pm b/lib/Sietima/Role/WithPostAddress.pm index 333c5e3..f8ad443 100644 --- a/lib/Sietima/Role/WithPostAddress.pm +++ b/lib/Sietima/Role/WithPostAddress.pm @@ -4,9 +4,40 @@ use Sietima::Policy; use Sietima::Types qw(Address AddressFromStr); use namespace::clean; -# VERSION +our $VERSION = '1.1.1'; # VERSION # ABSTRACT: role for lists with a posting address + +has post_address => ( + is => 'lazy', + isa => Address, + coerce => AddressFromStr, +); +sub _build_post_address($self) { $self->return_path } + +around list_addresses => sub($orig,$self) { + return +{ + $self->$orig->%*, + post => $self->post_address, + }; +}; + +1; + +__END__ + +=pod + +=encoding UTF-8 + +=head1 NAME + +Sietima::Role::WithPostAddress - role for lists with a posting address + +=head1 VERSION + +version 1.1.1 + =head1 SYNOPSIS my $sietima = Sietima->with_traits('WithPostAddress')->new({ @@ -24,26 +55,23 @@ On its own, this role is not very useful, but other roles (like L<< C<ReplyTo>|Sietima::Role::ReplyTo >>) can have uses for a post address. -=attr C<post_address> +=head1 ATTRIBUTES + +=head2 C<post_address> An L<< C<Email::Address> >> object, defaults to the value of the L<< C<return_path>|Sietima/return_path >> attribute. This is the address that the mailing list receives messages at. -=cut +=head1 AUTHOR -has post_address => ( - is => 'lazy', - isa => Address, - coerce => AddressFromStr, -); -sub _build_post_address($self) { $self->return_path } +Gianni Ceccarelli <dakkar@thenautilus.net> -around list_addresses => sub($orig,$self) { - return +{ - $self->$orig->%*, - post => $self->post_address, - }; -}; +=head1 COPYRIGHT AND LICENSE -1; +This software is copyright (c) 2023 by Gianni Ceccarelli <dakkar@thenautilus.net>. + +This is free software; you can redistribute it and/or modify it under +the same terms as the Perl 5 programming language system itself. + +=cut |