| Plagger documentation | Contained in the Plagger distribution. |
Plagger::Plugin::Publish::Gmail - Notify updates to your email account
- module: Publish::Gmail
config:
mailto: example@gmail.com
mailfrom: you@example.net
This plugin creates HTML emails and sends them to your Gmail mailbox.
Your email address to send updates to. Required.
Email address to send email from. Defaults to plagger@localhost.
Hash to specify how to send emails. Defaults to:
mailroute:
via: smtp
host: localhost
the value of via would be either smtp, smtp_tls or sendmail.
mailroute:
via: sendmail
command: /usr/sbin/sendmail
Flag to attach enclosures as Email attachments. Defaults to 0.
Tatsuhiko Miyagawa
| Plagger documentation | Contained in the Plagger distribution. |
package Plagger::Plugin::Publish::Gmail; use strict; use base qw( Plagger::Plugin ); our $VERSION = '0.10'; use DateTime; use DateTime::Format::Mail; use Encode; use Encode::MIME::Header; use HTML::Entities; use HTML::Parser; use MIME::Lite; our %TLSConn; sub rule_hook { 'publish.feed' } sub register { my($self, $context) = @_; $context->register_hook( $self, 'publish.init' => \&initialize, 'publish.feed' => \¬ify, ); } sub init { my $self = shift; $self->SUPER::init(@_); $self->conf->{mailto} or Plagger->context->error("mailto is required"); $self->conf->{mailfrom} ||= 'plagger@localhost'; } sub initialize { my($self,$context) = @_; # authenticate POP before SMTP if (my $conf = $self->conf->{pop3}) { require Net::POP3; my $pop = Net::POP3->new($conf->{host}); if ($pop->apop($conf->{username}, $conf->{password})) { $context->log(info => 'APOP login succeed'); } elsif ($pop->login($conf->{username}, $conf->{password})) { $context->log(info => 'POP3 login succeed'); } else { $context->log(error => 'POP3 login error'); } $pop->quit; } } sub notify { my($self, $context, $args) = @_; return if $args->{feed}->count == 0; my $feed = $args->{feed}; my $subject = $feed->title || '(no-title)'; my @enclosure_cb; if ($self->conf->{attach_enclosures}) { for my $entry ($args->{feed}->entries) { push @enclosure_cb, $self->prepare_enclosures($entry); } } my $encoding = $self->conf->{encoding} || 'utf-8'; my $body = $self->templatize('gmail_notify.tt', { feed => $feed, encoding => $encoding }); my $cfg = $self->conf; $context->log(info => "Sending $subject to $cfg->{mailto}"); my $feed_title = $feed->title; $feed_title =~ tr/,//d; my $now = Plagger::Date->now(timezone => $context->conf->{timezone}); my $msg = MIME::Lite->new( Date => $now->format('Mail'), From => encode('MIME-Header', qq("$feed_title" <$cfg->{mailfrom}>)), To => $cfg->{mailto}, Subject => encode('MIME-Header', $subject), Type => 'multipart/related', ); $msg->replace("X-Mailer" => "Plagger/$Plagger::VERSION"); $msg->attach( Type => "text/html; charset=$encoding", Data => encode($encoding, $body, Encode::FB_HTMLCREF), Encoding => 'quoted-printable', ); for my $cb (@enclosure_cb) { $cb->($msg); } my $route = $cfg->{mailroute} || { via => 'smtp', host => 'localhost' }; $route->{via} ||= 'smtp'; eval { if ($route->{via} eq 'smtp_tls') { $self->{tls_args} = [ $route->{host}, User => $route->{username}, Password => $route->{password}, Port => $route->{port} || 587, Timeout => $route->{timeout} || 300, ]; $msg->send_by_smtp_tls(@{ $self->{tls_args} }); } elsif ($route->{via} eq 'sendmail') { my %param = (FromSender => "<$cfg->{mailfrom}>"); $param{Sendmail} = $route->{command} if defined $route->{command}; $msg->send('sendmail', %param); } else { my @args = $route->{host} ? ($route->{host}) : (); $msg->send($route->{via}, @args); } }; if ($@) { $context->log(error => "Error while sending emails: $@"); } } sub prepare_enclosures { my($self, $entry) = @_; if (grep $_->is_inline, $entry->enclosures) { # replace inline enclosures to cid: entities my %url2enclosure = map { $_->url => $_ } $entry->enclosures; my $output; my $p = HTML::Parser->new(api_version => 3); $p->handler( default => sub { $output .= $_[0] }, "text" ); $p->handler( start => sub { my($tag, $attr, $attrseq, $text) = @_; # TODO: use HTML::Tagset? if (my $url = $attr->{src}) { if (my $enclosure = $url2enclosure{$url}) { $attr->{src} = "cid:" . $self->enclosure_id($enclosure); } $output .= $self->generate_tag($tag, $attr, $attrseq); } else { $output .= $text; } }, "tag, attr, attrseq, text"); $p->parse($entry->body); $p->eof; $entry->body($output); } return sub { my $msg = shift; for my $enclosure (grep $_->local_path, $entry->enclosures) { if (!-e $enclosure->local_path) { Plagger->context->log(warning => $enclosure->local_path . " doesn't exist. Skip"); next; } my %param = ( Type => $enclosure->type, Path => $enclosure->local_path, Filename => $enclosure->filename, ); if ($enclosure->is_inline) { $param{Id} = '<' . $self->enclosure_id($enclosure) . '>'; $param{Disposition} = 'inline'; } else { $param{Disposition} = 'attachment'; } $msg->attach(%param); } } } sub generate_tag { my($self, $tag, $attr, $attrseq) = @_; return "<$tag " . join(' ', map { $_ eq '/' ? '/' : sprintf qq(%s="%s"), $_, encode_entities($attr->{$_}, q(<>"')) } @$attrseq) . '>'; } sub enclosure_id { my($self, $enclosure) = @_; return Digest::MD5::md5_hex($enclosure->url->as_string) . '@Plagger'; } sub DESTORY { my $self = shift; return unless $self->{tls_args}; my $conn_key = join "|", @{ $self->{tls_args} }; eval { local $SIG{__WARN__} = sub { }; $TLSConn{$conn_key} && $TLSConn{$conn_key}->quit; }; # known error from Gmail SMTP if ($@ && $@ !~ /An error occurred disconnecting from the mail server/) { warn $@; } } # hack MIME::Lite to support TLS Authentication *MIME::Lite::send_by_smtp_tls = sub { my($self, @args) = @_; ### We need the "From:" and "To:" headers to pass to the SMTP mailer: my $hdr = $self->fields(); my($from) = MIME::Lite::extract_addrs( $self->get('From') ); my $to = $self->get('To'); ### Sanity check: defined($to) or Carp::croak "send_by_smtp_tls: missing 'To:' address\n"; ### Get the destinations as a simple array of addresses: my @to_all = MIME::Lite::extract_addrs($to); if ($MIME::Lite::AUTO_CC) { foreach my $field (qw(Cc Bcc)) { my $value = $self->get($field); push @to_all, MIME::Lite::extract_addrs($value) if defined($value); } } ### Create SMTP TLS client: require Net::SMTP::TLS; my $conn_key = join "|", @args; my $smtp; unless ($smtp = $TLSConn{$conn_key}) { $smtp = $TLSConn{$conn_key} = MIME::Lite::SMTP::TLS->new(@args) or Carp::croak("Failed to connect to mail server: $!\n"); } $smtp->mail($from); $smtp->to(@to_all); $smtp->data(); ### MIME::Lite can print() to anything with a print() method: $self->print_for_smtp($smtp); $smtp->dataend(); 1; }; @MIME::Lite::SMTP::TLS::ISA = qw( Net::SMTP::TLS ); sub MIME::Lite::SMTP::TLS::print { shift->datasend(@_) } 1; __END__