[Bps-public-commit] r11161 - in Metasocial: . bin doc etc lib lib/Metasocial/Action lib/Metasocial/Model lib/Metasocial/Notification lib/Metasocial/Notification/EmailError log share share/po share/web share/web/static share/web/templates t var var/mason

jesse at bestpractical.com jesse at bestpractical.com
Sun Mar 23 15:24:58 EDT 2008


Author: jesse
Date: Sun Mar 23 15:24:58 2008
New Revision: 11161

Added:
   Metasocial/Makefile.PL
   Metasocial/TODO
   Metasocial/bin/
   Metasocial/bin/jifty   (contents, props changed)
   Metasocial/bin/mailgate   (contents, props changed)
   Metasocial/doc/
   Metasocial/etc/
   Metasocial/etc/config.yml
   Metasocial/lib/
   Metasocial/lib/Metasocial/
   Metasocial/lib/Metasocial/Action/
   Metasocial/lib/Metasocial/Action/EmailDispatch.pm
   Metasocial/lib/Metasocial/Model/
   Metasocial/lib/Metasocial/Model/Message.pm
   Metasocial/lib/Metasocial/Model/Subscription.pm
   Metasocial/lib/Metasocial/Model/User.pm
   Metasocial/lib/Metasocial/Notification/
   Metasocial/lib/Metasocial/Notification/EmailError/
   Metasocial/lib/Metasocial/Notification/EmailError.pm
   Metasocial/lib/Metasocial/Notification/EmailError/Loop.pm
   Metasocial/log/
   Metasocial/share/
   Metasocial/share/po/
   Metasocial/share/web/
   Metasocial/share/web/static/
   Metasocial/share/web/templates/
   Metasocial/t/
   Metasocial/t/insert_message.t
   Metasocial/var/
   Metasocial/var/jifty-server.pid
   Metasocial/var/mason/
Modified:
   Metasocial/   (props changed)

Log:
Merging down an experiement

Added: Metasocial/Makefile.PL
==============================================================================
--- (empty file)
+++ Metasocial/Makefile.PL	Sun Mar 23 15:24:58 2008
@@ -0,0 +1,7 @@
+use inc::Module::Install;
+
+name        'Metasocial';
+version     '0.01';
+requires    'Jifty' => '0.71129';
+
+WriteAll;

Added: Metasocial/TODO
==============================================================================
--- (empty file)
+++ Metasocial/TODO	Sun Mar 23 15:24:58 2008
@@ -0,0 +1,7 @@
+What is this thing:
+
+    * tag based mailing list manager
+    * users subscibe to address keywords
+    * users mail to keywords
+    * system redistributes 
+

Added: Metasocial/bin/jifty
==============================================================================
--- (empty file)
+++ Metasocial/bin/jifty	Sun Mar 23 15:24:58 2008
@@ -0,0 +1,11 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+use File::Basename qw(dirname); 
+use UNIVERSAL::require;
+
+use Jifty;
+use Jifty::Script;
+
+local $SIG{INT} = sub { warn "Stopped\n"; exit; };
+Jifty::Script->dispatch();

Added: Metasocial/bin/mailgate
==============================================================================
--- (empty file)
+++ Metasocial/bin/mailgate	Sun Mar 23 15:24:58 2008
@@ -0,0 +1,113 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+use Getopt::Long;
+use LWP::UserAgent;
+use Pod::Usage;
+use Jifty::YAML;
+
+# Option parsing
+my %opts;
+GetOptions( \%opts, "address=s", "url=s", "timeout=i", "sender=s", "help" )
+  or pod2usage( -exitval => 1, -verbose => 1 );
+pod2usage( -exitval => 1, -verbose => 1 ) if $opts{help} or not $opts{url};
+
+# Where the email was sent to
+my $address = $opts{address} || $ENV{RECIPIENT};
+warn("$0: No --address argument, and RECIPIENT env variable is empty!\n") && exit(100)
+  unless $address;
+
+# Who sent it?
+my $sender = $opts{sender} || $ENV{SENDER};
+warn("$0: No --sender argument, and SENDER env variable is empty!\n") && exit(100)
+  unless $sender;
+
+
+
+
+# Grab the message from STDIN
+my $body = do { local (@ARGV, $/); <> };
+warn("$0: No message passed on STDIN\n") && exit(100)
+  unless $body =~ /\S/;
+
+# Set up POST parameters
+my %args = ( "J:A-dispatch" => "Metasocial::Action::EmailDispatch",
+             "J:A:F-address-dispatch" => $address,
+             "J:A:F-envelope_sender-dispatch" => $sender,
+             "J:A:F-email-dispatch" => $body
+             );
+
+# Send 'er away!
+my $ua = LWP::UserAgent->new();
+$ua->timeout( $opts{timeout} ) if defined $opts{timeout};
+my $r = $ua->post("$opts{url}/__jifty/webservices/yaml", { %args });
+if ($r->is_success) {
+    my $data = Jifty::YAML::Load($r->content);
+    warn $data->{dispatch}{_content}{"Message-ID"}. " from $sender to $address: ".$data->{dispatch}{error}."\n" if $data->{dispatch}{error};
+    warn $data->{dispatch}{_content}{"Message-ID"}. " from $sender to $address: $_ error is ".$data->{dispatch}{field_errors}{$_}."\n" for keys %{$data->{dispatch}{field_errors} || {}};
+    exit( $data->{dispatch}{failure} ? 100 : 0 );
+} else {
+    warn "HTTP timeout";
+    exit 1;
+}
+
+=head1 SYNOPSIS
+
+    mailgate --help       # This text
+
+    mailgate --url http://hiveminder.com/
+
+=head1 OPTIONS
+
+=over 3
+
+=item --url
+
+The only required parameter; this specifies the complete base URL to
+the Hiveminder server that the mail is destined for.
+
+=item --address
+
+Specifies the published address that the mail is destined for.  If
+this option is omitted, C<mailgate> looks at the C<RECIPIENT>
+environment variable.  The address determines what will be done with
+the email when it is received.
+
+=item --timeout
+
+Sets the timeout before the connection aborts.  The default is three
+minutes, which should be more than sufficient.
+
+=item --sender
+
+Sets the envelope sender of the message. This will be pulled from the
+C<SENDER> environment variable if not specified.
+
+=back
+
+=head1 EXIT CODES
+
+=over
+
+=item 0
+
+The message was delivered successfully.
+
+=item 1
+
+There was a temporary failure.  The only temporary failure at the
+moment is a lack of a C<--url> parameter; every other error has very
+little likelyhood of getting any better with a retry.
+
+=item 100
+
+Permanent failure; a reason will often be printed to standard error.
+This includes lack of C<--address> or C<RECIPIENT> env variable, lack
+of C<--sender> or C<SENDER> env variable, lack of body, HTTP timeout,
+or any error message from the C<EmailDispatch> action.
+
+=back
+
+=cut
+

Added: Metasocial/etc/config.yml
==============================================================================
--- (empty file)
+++ Metasocial/etc/config.yml	Sun Mar 23 15:24:58 2008
@@ -0,0 +1,77 @@
+--- 
+framework: 
+  AdminMode: 1
+  ApplicationClass: Metasocial
+  ApplicationName: Metasocial
+  ApplicationUUID: 83F120C6-F791-11DC-A375-8464EC1EE577
+  ConfigFileVersion: 4
+  Database: 
+    AutoUpgrade: 1
+    CheckSchema: 1
+    Database: metasocial
+    Driver: SQLite
+    Host: localhost
+    Password: ''
+    RecordBaseClass: Jifty::DBI::Record::Cachable
+    User: ''
+    Version: 0.0.1
+  DevelMode: 1
+  L10N: 
+    PoDir: share/po
+  LogLevel: INFO
+  Mailer: Sendmail
+  MailerArgs: []
+
+  Plugins: 
+    - 
+      LetMe: {}
+
+    - 
+      SkeletonApp: {}
+
+    - 
+      REST: {}
+
+    - 
+      Halo: {}
+
+    - 
+      ErrorTemplates: {}
+
+    - 
+      OnlineDocs: {}
+
+    - 
+      CompressedCSSandJS: {}
+
+    - 
+      AdminUI: {}
+
+    - User: {}
+    - Authentication::Password: {}
+    - OpenID: {}
+  PubSub: 
+    Backend: Memcached
+    Enable: ~
+  SkipAccessControl: 0
+  TemplateClass: Metasocial::View
+  View: 
+    FallbackHandler: Jifty::View::Mason::Handler
+    Handlers: 
+      - Jifty::View::Static::Handler
+      - Jifty::View::Declare::Handler
+      - Jifty::View::Mason::Handler
+  Web: 
+    BaseURL: http://localhost
+    DataDir: var/mason
+    Globals: []
+
+    MasonConfig: 
+      autoflush: 0
+      default_escape_flags: h
+      error_format: text
+      error_mode: fatal
+    Port: 8888
+    ServeStaticFiles: 1
+    StaticRoot: share/web/static
+    TemplateRoot: share/web/templates

Added: Metasocial/lib/Metasocial/Action/EmailDispatch.pm
==============================================================================
--- (empty file)
+++ Metasocial/lib/Metasocial/Action/EmailDispatch.pm	Sun Mar 23 15:24:58 2008
@@ -0,0 +1,163 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Metasocial::Action::EmailDispatch
+
+=cut
+
+package Metasocial::Action::EmailDispatch;
+use base qw/Metasocial::Action Jifty::Action/;
+
+use Metasocial::Notification::EmailError;
+
+=head2 arguments
+
+address
+
+email
+
+envelope_sender '--sender' => 'gooduser at example.com'
+
+=cut
+
+sub arguments {
+    {   address         => {},
+        email           => {},
+        envelope_sender => {},
+    };
+}
+
+=head2 setup
+
+Parse the email, and always provide a C<Message-ID> property in the
+content of the L<Jifty::Result>.  This is not in L</take_action>,
+because we want the message-id even if validation fails.
+
+=cut
+
+sub setup {
+    my $self = shift;
+    my $email = Email::MIME->new( $self->argument_value('email') || '' );
+    $self->result->content( "Message-ID" => $email->header("Message-ID") );
+    return 1;
+}
+
+=head2 validate_address
+
+Make sure the email address looks sane
+
+=cut
+
+sub validate_address {
+    my $self  = shift;
+    my $email = shift;
+
+    if ( $email =~ /./ ) {
+        return $self->validation_ok('address');
+    } else {
+        return $self->validation_error( address => "Address '$email' didn't match a known address" );
+    }
+
+}
+
+=head2 take_action
+
+Dispatches the C<email> based on the C<address> by looking up the
+
+=cut
+
+sub take_action {
+    my $self = shift;
+    warn "Taking action";
+    my $email = Email::MIME->new( $self->argument_value('email') );
+    $email->header_set( 'X-Metasocial-delivered-to', $self->argument_value('address') );
+
+    # Load the sending address, making it a user if need be
+    my ($from) = $self->argument_value('envelope_sender');
+    return unless $from;
+
+    if ( $from =~ /^postmaster\@metasocial.com$/ ) {
+
+        # If we're looping, drop it in the floor.
+        $self->result->error("Postmaster loop");
+        return;
+    }
+
+    if ( ( $email->header('X-Metasocial') || '' ) eq Jifty->config->framework('Web')->{BaseURL} ) {
+        $self->send_bounce( 'Metasocial::Notification::EmailError::Loop', $self );
+        $self->result->error("Self-loop");
+        return;
+    }
+
+    # Make sure this isn't a dup
+    if ( defined $email->header("Message-ID") ) {
+        my $already = Metasocial::Model::TaskEmailCollection->new;
+        $already->limit( column => "message_id",   value => $email->header("Message-ID") );
+        $already->limit( column => "delivered_to", value => $self->argument_value('address') );
+        if ( $already->count ) {
+            $self->result->error("Duplicate message-ID");
+            return;
+        }
+    }
+
+    my $sender = Metasocial::Model::User->new( current_user => Metasocial::CurrentUser->superuser );
+    $sender->load_or_create( email => $from );
+
+    # Load that as a CurrentUser
+    my $current = Metasocial::CurrentUser->new( id => $sender->id );
+    Jifty->web->temporary_current_user($current);
+    my $address;
+
+    my $action = Jifty->web->new_action(
+        class   => "CreateMessage",
+        moniker => "email_dispatch",
+        arguments => { message => $email->as_string, },
+
+        # Clobber the current user -- knowing the auth token is
+        # enough to let you comment on the task, no matter what
+        # address you're sending from.
+        current_user => Metasocial::CurrentUser->superuser
+    );
+
+    $action->run;
+    warn YAML::Dump( $action->result );
+    if ( $action->result->failure ) {
+        $self->send_bounce( 'Metasocial::Notification::EmailError', $action );
+        Jifty->log->warn("$action failed");
+        $self->result->error("Sub-action failed");
+        $self->result->content( result => $action->result );
+    } else {
+        $self->result->message("Dispatched to an action $action");
+        $self->result->content( result => $action->result );
+    }
+
+    Jifty->web->temporary_current_user(undef);
+}
+
+=head2 send_bounce CLASS ACTION
+
+Sends a bounce message to the envelope sender.  C<CLASS> is the
+Notification class to use to send it, and C<ACTION> is provided to it
+as an C<action> argument.
+
+=cut
+
+sub send_bounce {
+    my $self   = shift;
+    my $class  = shift;
+    my $action = shift;
+
+    # Generate a bounce with the error message
+    my $to = Metasocial::Model::User->new( current_user => Metasocial::CurrentUser->superuser );
+    $to->load_or_create( email => $self->argument_value('envelope_sender') );
+    $class->new(
+        to      => $to,
+        address => $self->argument_value('address'),
+        result  => $action->result,
+        email   => $self->argument_value('email'),
+    )->send;
+}
+
+1;

Added: Metasocial/lib/Metasocial/Model/Message.pm
==============================================================================
--- (empty file)
+++ Metasocial/lib/Metasocial/Model/Message.pm	Sun Mar 23 15:24:58 2008
@@ -0,0 +1,276 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Metasocial::Model::Message
+
+=head1 DESCRIPTION
+
+An email related to an existing task.  This is displayed as a comment
+on the task.  Emails have a C<sender>, which is a L<Metasocial::Model::User>
+object, and a C<message>, which is a C<RFC 2822>-formatted email
+message, including both headers and body.
+
+=cut
+
+
+package Metasocial::Model::Message;
+use Encode;
+use Email::MIME::ContentType 'parse_content_type';
+use Email::MIME::Attachment::Stripper;
+use Text::Quoted qw();
+use List::Util qw(reduce);
+use base qw( Metasocial::Record );
+use HTML::Scrubber;
+
+use Jifty::DBI::Schema;
+
+use Jifty::Record schema {
+    column message => type is 'bytea', render_as 'Textarea', is immutable, label is 'Message';
+    column sender => type is 'varchar', is immutable, is protected, is case_sensitive;
+    column message_id   => type is 'varchar', label is 'Message ID',   is immutable, is protected, is case_sensitive;
+    column delivered_to => type is 'varchar', label is 'Delivered to', is immutable, is protected, is case_sensitive;
+};
+
+
+=head2 create
+
+create takes two named arguments: a C<message>, and either a
+L<Metasocial::Model::Task> id C<task_id>, or a
+L<Metasocial::Model::TaskTransaction> id C<transaction_id>.  If a task id s
+provided, a new transaction of type "email" is added to the task, and
+this email is attached to it.  If a transaction id is passed,
+C<message> is expected to be a C<RFC 2822> email message.
+
+Perhaps in the future, we'll have a clever way to read out the
+task_id from the message itself.
+
+=cut
+
+sub create {
+    my $self = shift;
+    my %args = ( 
+        task_id        => undef,
+        transaction_id => undef,
+        message        => undef,
+        sender      => undef,
+        @_);
+
+
+    my $email = Email::Simple->new($args{'message'});
+    return(undef, "No message given") unless $args{'message'};
+    my @sender_objects = Email::Address->parse($email->header('From'));
+    my $sender_address = '';
+    if (my $obj = shift @sender_objects) {
+        $sender_address = $obj->address;
+    }
+    
+
+    my ( $id, $msg ) = $self->SUPER::create(
+        message        => $email->as_string,
+        sender      => $sender_address,
+        message_id     => $email->header('Message-ID'),
+        delivered_to   => ($email->header('X-Delivered-to') || undef),
+    );
+
+    unless ($self->id) {
+        return(undef, $msg);
+    }
+
+    # Add attachments if we have them
+    #my $mime = Email::MIME::Attachment::Stripper->new( $args{'message'} );
+    #my @attachments = $mime->attachments;
+
+    return ($self->id, "Email recorded");
+}
+
+=head2 header STRING
+
+Manipulates the C<header> of an L<Email::Simple> object.
+
+=cut
+
+sub header {
+    my $self = shift;
+    my $message = Email::Simple->new($self->message);
+    return $message->header(@_);
+}
+
+=head2 body
+
+Extracts the C<message> into an L<Email::Simple> object, and returns
+the body.
+
+=cut
+
+sub body {
+    my $self = shift;
+    my $email = Email::MIME->new( $self->message ) ;
+
+    my $body = $self->extract_body($email);
+    return undef unless defined $body;
+    
+    $body = $self->_remove_quoted($body);
+    return $body;
+}
+
+=head2 extract_body EMAIL
+
+Class method to extract the textual body from an Email::MIME email,
+and to attempt to guess the encoding appropriately
+
+=cut
+
+sub extract_body {
+    my $self = shift;
+    my $email = shift;
+
+    my $body = ( grep { ($_->content_type || '') =~ m'^text/plain'i } $self->_flatten($email->parts) )[0]
+      || $email;
+
+    my $charset = $body->content_type
+      ? parse_content_type( $body->content_type )->{charset}
+      : '';
+
+    if (($body->content_type || 'text/plain') !~ m|^text/|i) {
+        return undef;
+    }
+
+    $body = $body->body;
+    Encode::_utf8_off($body);
+    $body = Jifty::I18N->promote_encoding($body, $charset);
+    $body =~ s/\s*\n-- \n.*$//s;
+
+    return $body;
+}
+
+sub _flatten {
+    my $self = shift;
+    my @result;
+    for (@_) {
+        if ( ( $_->content_type || '' ) =~ m'^multipart/'i ) {
+            my @parts = $_->parts;
+            if (@parts > 1 or $parts[0] ne $_) {
+                push @result, $self->_flatten( $_->parts );
+            }
+        } else {
+            push @result, $_;
+        }
+    }
+    return @result;
+}
+
+=head2 _remove_quoted
+
+Removes quoted text.
+
+=cut
+
+sub _remove_quoted {
+    my ($self, $text) = @_;
+
+    my $non_empty   = ".+";
+    my $newline     = "\\n";
+    my $quote_char  = "(?: > | \\| )";
+    my $quoted_intro = qr/(?! $quote_char ) $non_empty (?: said | wrote | writes? ) :?/ox;
+
+    # trim body
+    $text =~ s/^\s*//;
+    $text =~ s/\s*$//;
+
+    # Figure out the structure of the message
+    my @structure;
+    my $parts = Text::Quoted::extract( $text );
+    my %types = ( HASH => 'unquoted', ARRAY => 'quoted' );
+
+    for my $part ( @$parts ) {
+        push @structure,
+            ( ref $part eq 'HASH' and $part->{empty} )                    ? 'empty' :
+            ( ref $part eq 'HASH' and $part->{raw} and  $part->{raw} =~ /^$quoted_intro$/ ) ? 'intro' : $types{ ref $part };
+    }
+
+    # Combines equal adjacent elements into one so we can compare
+    # against simple basic structures
+    my @simplified;
+    reduce { push @simplified, $a if $a ne $b; return $b; }
+      grep { $_ !~ /^(?:empty|intro)$/ } @structure, 'END';
+    
+    my $simple = join ' ', @simplified;
+
+    # if it's not one of these two cases, it's probably an interleaved
+    # reply and there's not much we can do
+    my $remove_quoted = $simple eq 'unquoted quoted'                ? 'before' :
+                        $simple =~ m/^quoted unquoted(?: quoted)?$/ ? 'after'  :
+                                                                      0;
+                                                                      
+    if ( $remove_quoted ) {
+        $text = '';
+        for my $part ( @$parts ) {
+            if ( ref $part eq 'ARRAY' ) {
+                if    ( $remove_quoted eq 'before' ) { last }
+                elsif ( $remove_quoted eq 'after' )  { next }
+            }
+            next if $part->{raw} =~ /^$quoted_intro$/;
+            $text .= $part->{raw}."\n";
+        }
+    }
+
+    # trim again, just in case
+    $text =~ s/^\s*//;
+    $text =~ s/\s*$//;
+
+    return $text;
+}
+
+=head2 formatted_body
+
+Format the body using L<Text::Markdown> and L<HTML::Scrubber>
+and auto-linkify URLs.
+
+=cut
+
+{
+    my $scrubber = HTML::Scrubber->new();
+    $scrubber->default(
+                       0,
+                       {
+                        '*' => 0,
+                        id  => 1,
+                        class => 1,
+                        target => 1,
+                        href => qr{^(?:http:|ftp:|https:|mailto:|/)}i, #}
+                       }
+                      );
+    $scrubber->deny('*');
+    $scrubber->allow(qw/a b u p br i hr em strong span div ul ol li dl dt dd pre blockquote/);
+
+sub formatted_body {
+    my $self = shift;
+    my $body = $self->body;
+    unless (defined $body) {
+        return "<i>This message contains no readable content.</i>";
+    }
+
+    $body = Metasocial->autolinkify( $body ) if $body;
+    $body = Metasocial->text2html( $body ) if $body;
+
+    return $scrubber->scrub($body);
+
+    return $body;
+}
+}
+
+=head2 autogenerate_action
+
+Only generate Search and Create actions for this model.
+
+=cut
+
+sub autogenerate_action {
+    my $class = shift;
+    my $right = shift;
+    return($right eq "Search" or $right eq "Create");
+}
+
+1;

Added: Metasocial/lib/Metasocial/Model/Subscription.pm
==============================================================================
--- (empty file)
+++ Metasocial/lib/Metasocial/Model/Subscription.pm	Sun Mar 23 15:24:58 2008
@@ -0,0 +1,15 @@
+use strict;
+use warnings;
+
+package Metasocial::Model::Subscription;
+use Jifty::DBI::Schema;
+
+use Metasocial::Record schema {
+    column subscriber => references Metasocial::Model::User by 'id';
+    column address_match => type is 'text';
+};
+
+# Your model-specific methods go here.
+
+1;
+

Added: Metasocial/lib/Metasocial/Model/User.pm
==============================================================================
--- (empty file)
+++ Metasocial/lib/Metasocial/Model/User.pm	Sun Mar 23 15:24:58 2008
@@ -0,0 +1,18 @@
+use strict;
+use warnings;
+
+package Metasocial::Model::User;
+use Jifty::DBI::Schema;
+
+use Metasocial::Record schema {
+    column blah => type is 'text';
+};
+
+use Jifty::Plugin::User::Mixin::Model::User; # name, email, email_confirmed
+use Jifty::Plugin::Authentication::Password::Mixin::Model::User;
+use Jifty::Plugin::OpenID::Mixin::Model::User;
+
+# Your model-specific methods go here.
+
+1;
+

Added: Metasocial/lib/Metasocial/Notification/EmailError.pm
==============================================================================
--- (empty file)
+++ Metasocial/lib/Metasocial/Notification/EmailError.pm	Sun Mar 23 15:24:58 2008
@@ -0,0 +1,97 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Metasocial::Notification::EmailError -- A bounce message when there's an
+                                  error processing incoming mail
+
+=head1 DESCRIPTION
+
+We send an EmailError notification when we receive incoming mail that
+we get an error handling, for whatever reason.
+
+=cut
+
+package Metasocial::Notification::EmailError;
+
+use base qw(Metasocial::Notification);
+
+__PACKAGE__->mk_accessors(qw(result email address));
+
+=head2 result
+
+A Jifty::Result of the action that failed that caused us to send this
+bounce.
+
+=head2 email
+
+The text of the email that we received that generated the error.
+
+=head2 address
+
+The address to which the user attempted to send mail.
+
+=head2 setup
+
+Set up our subject and sender
+
+=cut
+
+sub setup {
+    my $self = shift;
+
+    $self->subject('Hiveminder.com -- Error processing email');
+    $self->from('Metasocial Mailer Daemon <>');
+}
+
+=head2 preface
+
+Return an apologetic message explaining that an error happened,
+including the text of the error.
+
+=cut
+
+sub preface {
+    my $self = shift;
+    my $to = $self->address;
+    my $error = $self->result->error;
+
+    return <<"END_PREFACE";
+We're sorry, but we encountered an error processing your email to us
+at $to.
+
+The error was: $error
+
+We hope we didn't mess up your day too badly with this. Drop us a line
+at support\@hiveminder.com if you need help fixing this problem, or
+try again in a little while.
+
+END_PREFACE
+
+}
+
+=head2 parts
+
+Returns the body, as well as an attachment of the original message.
+
+=cut
+
+sub parts {
+    my $self = shift;
+    return [
+        @{$self->SUPER::parts},
+        Email::MIME->create(
+            attributes => {
+                content_type => 'text/plain',
+                disposition => 'attachment',
+            },
+            body => Encode::encode_utf8($self->email),
+           )
+       ];
+}
+
+
+
+
+1;

Added: Metasocial/lib/Metasocial/Notification/EmailError/Loop.pm
==============================================================================
--- (empty file)
+++ Metasocial/lib/Metasocial/Notification/EmailError/Loop.pm	Sun Mar 23 15:24:58 2008
@@ -0,0 +1,42 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Metasocial::Notification::EmailError::Loop -- A bounce message when there's an
+                                               error processing incoming mail
+
+=head1 DESCRIPTION
+
+We send a Loop EmailError notification when we receive email that appears to be from hiveminder.
+
+=cut
+
+package Metasocial::Notification::EmailError::Loop;
+
+use base qw(Metasocial::Notification::EmailError);
+
+=head2 preface
+
+Return an apologetic message explaining that an error happened,
+including the text of the error.
+
+=cut
+
+sub preface {
+    my $self = shift;
+    my $to = $self->address;
+
+    return <<"END_PREFACE";
+The message you sent appears to be part of a mail loop. If you're not sure
+what happened, please contact us on the web at:
+    @{[Jifty->web->url( path => '/' )]}
+
+I'm quite sorry for any trouble this might have caused you.
+
+END_PREFACE
+
+}
+
+
+1;

Added: Metasocial/t/insert_message.t
==============================================================================
--- (empty file)
+++ Metasocial/t/insert_message.t	Sun Mar 23 15:24:58 2008
@@ -0,0 +1,16 @@
+#!/usr/bin/perl
+
+
+use warnings;
+use strict;
+
+my @sample = (<DATA>);
+warn join('', at sample);
+
+
+__DATA__
+From: sample at example.com
+To: inbox at mymail.com
+Subject: test
+
+foo

Added: Metasocial/var/jifty-server.pid
==============================================================================
--- (empty file)
+++ Metasocial/var/jifty-server.pid	Sun Mar 23 15:24:58 2008
@@ -0,0 +1 @@
+42717
\ No newline at end of file



More information about the Bps-public-commit mailing list