[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