root/bloglines2email/trunk/bloglines2email

Revision 1664 (checked in by miyagawa, 15 years ago)

send() is now configured per class method. Don't override it

  • Property svn:executable set to *
  • Property svn:keywords set to Id Revision
Line 
1 #!/usr/local/bin/perl -w
2 # $Id$
3 use strict;
4 use DateTime;
5 use DateTime::Format::Mail;
6 use Encode;
7 use FindBin;
8 use File::Spec;
9 use Getopt::Long;
10 use MIME::Lite;
11 use Template;
12 use WebService::Bloglines;
13 use YAML;
14
15 our $VERSION = '0.10';
16
17 GetOptions(\our %opt, "test", "verbose", "conf=s");
18
19 my $conf = $opt{conf} || File::Spec->catfile($FindBin::Bin, "bloglines2email.conf");
20 my $cfg  = YAML::LoadFile($conf);
21
22 my $bws = WebService::Bloglines->new(
23     username => $cfg->{username},
24     password => $cfg->{password},
25 );
26
27 setup_mailroute($cfg);
28
29 my $mark_read = $opt{test} ? 0 : 1;
30 my @updates = $bws->getitems(0, $mark_read);
31 debug(scalar(@updates) . " feeds updated.");
32 for my $update (@updates) {
33     send_email($cfg, $update);
34 }
35
36 sub setup_mailroute {
37     my $cfg = shift;
38     my $route = $cfg->{mailroute} || { via => 'smtp', host => 'localhost' };
39     my @args  = $route->{host} ? ($route->{host}) : ();
40     MIME::Lite->send($route->{via}, @args);
41 }
42
43 sub debug {
44     my $msg = "@_";
45     chomp($msg);
46     print STDERR encode('utf-8', "$msg\n") if $opt{verbose};
47 }
48
49 sub send_email {
50     my($cfg, $update) = @_;
51     my $feed  = $update->feed;
52     my @items = $update->items;
53     if ($cfg->{'group-items'}) {
54         send_email_feed($cfg, $feed, \@items);
55     }
56     else {
57         for my $item (@items) {
58             send_email_item($cfg, $feed, $item);
59         }
60     }
61 }
62
63 sub send_email_feed {
64     my($cfg, $feed, $items) = @_;
65     my $subject = $feed->{title} || '(no-title)';
66     my $body = join '<hr />', map format_body($feed, $_, $cfg), @$items;
67     do_send_mail($cfg, $feed, $subject, $body);
68 }
69
70 sub send_email_item {
71     my($cfg, $feed, $item) = @_;
72     my $subject = $item->{title} || '(no-title)';
73     my $body    = format_body($feed, $item, $cfg);
74     do_send_mail($cfg, $feed, $subject, $body);
75 }
76
77 sub do_send_mail {
78     my($cfg, $feed, $subject, $body) = @_;
79     debug("Sending $subject to $cfg->{mailto}");
80     my $feed_title = $feed->{title};
81        $feed_title =~ tr/,//d;
82     my $msg = MIME::Lite->new(
83         Date => get_rfc2822_date($cfg),
84         From => encode('MIME-Header', qq("$feed_title" <$cfg->{mailfrom}>)),
85         To   => $cfg->{mailto},
86         Subject => encode('MIME-Header', $subject),
87         Type => 'multipart/related',
88     );
89     $msg->attach(
90         Type => 'text/html; charset=utf-8',
91         Data => encode("utf-8", $body),
92     );
93     $msg->send();
94 }
95
96 sub get_rfc2822_date {
97     my $cfg = shift;
98     my $dt  = @_
99         ? DateTime::Format::Mail->parse_datetime($_[0])
100         : DateTime->now;
101     my $tz = $cfg->{'date-timezone'} || 'local';
102     $dt->set_time_zone($tz);
103     DateTime::Format::Mail->format_datetime($dt);
104 }
105
106 sub format_body {
107     my($feed, $item, $cfg) = @_;
108     my $template = get_template();
109     my $tt = Template->new;
110     $tt->process(\$template, {
111         feed => $feed,
112         item => $item,
113         cfg  => $cfg,
114         get_rfc2822_date => sub { get_rfc2822_date($cfg, @_) },
115         utf8 => sub { encode("utf-8", $_[0]) }
116     }, \my $out) or die $tt->error;
117     $out;
118 }
119
120 sub get_template {
121     return <<'HTML';
122 <div>
123 <div>
124 [% IF feed.image %]<a href="[% feed.image.link %]"><img style="border:0" align="right" src="[% feed.image.url | html %]" alt="[% feed.image.title | html %]" /></a>[% END %]
125 [% var = 'group-items'; IF cfg.$var %]<strong style="font-weight:bold;font-size:1.2em">[% item.title %]</strong><br />[% END %]
126 [% SET link = item.link || item.guid -%]
127 Link: <a href="[% link | html %]">[% link | html %]</a><br />
128 [% IF item.dc.creator %]by [% item.dc.creator | html %][% END %][% IF item.dc.subject %] on [% item.dc.subject %][% END %]</div>
129 [% IF item.description -%]
130 [% IF item.description.match('(?i)^<p[ >]') %][% item.description %][% ELSE %]<div style="padding: 1em 0">[% item.description %]</div>[% END %]
131 [% ELSE %]<br />[% END %]
132 <div style="font-size:0.8em">[% IF item.pubDate %]Posted on [% get_rfc2822_date(item.pubDate) %][% END %] | <a href="[% link | html %]">permalink</a> | <a href="[% feed.link | html %]">[% feed.title | html %]</a>[% var = 'delicious-username'; IF cfg.$var %][% SET u = "http://del.icio.us/" _ cfg.$var; USE delicious = url(u) %] | <a href="[% SET t = feed.title _ ": " _ item.title; delicious(v=3, url=item.link, title=utf8(t)) %]">Post to del.icio.us</a>[% END %]<br clear="all" /></div>
133 </div>
134 HTML
135 }
136
137 =head1 NAME
138
139 bloglines2email - Send Bloglines unread items as HTML mail
140
141 =head1 SYNOPSIS
142
143   % bloglines2email
144   % bloglines2email --conf=/path/to/bloglines2email.conf --test --verbose
145
146 =head1 DESCRIPTION
147
148 C<bloglines2email> is a command line application that fetches
149 Bloglines unread items via Bloglines Web Services and sends them as
150 HTML mail to your address (Gmail is preferrable). It gives you an easy
151 way to manage, browse and search Blog entries rather than using
152 Bloglines interface directly.
153
154 You'd better run this app by crontab like every 5 minutes.
155
156 =head1 REQUIREMENT
157
158 This app requires perl 5.8.0 with following Perl modules installed on your box.
159
160 =over 4
161
162 =item DateTime
163
164 =item DateTime::Format::Mail
165
166 =item MIME::Lite
167
168 =item Template
169
170 =item WebService::Bloglines
171
172 =item YAML
173
174 =back
175
176 =head1 OPTIONS
177
178 This application has following command line options.
179
180 =over 4
181
182 =item --test
183
184 Doesn't mark unread items as read. Default: mark read.
185
186 =item --verbose
187
188 Gives diagnostic messages to STDERR. Default: no verbose.
189
190 =item --conf
191
192 Specifies a path of configuration YAML file. Default:
193 C<bloglines2email.conf> in the same directory as script.
194
195 =back
196
197 =head1 CONFIGURATION
198
199 This app uses C<bloglines2email.conf> that sits beside the script in
200 the same directory (or you can specify the file path using C<--conf>
201 option). The distribution has a sample configuration file named
202 C<bloglines2email.conf.sample> that you can use by copying.
203
204 The config file uses YAML syntax and most of the directives are self-discriptive.
205
206 =over 4
207
208 =item username, password
209
210 Set your username and password for Bloglines.
211
212 =item mailto
213
214 Set email address that this app sends emails to. Gmail address is recommended.
215
216 =item mailfrom
217
218 Set email address that this app uses for C<From:> header.
219
220 =item mailroute
221
222 Set how to send emails. Default is to use SMTP.
223
224 =item group-items (Optional)
225
226 With this directive on (set to 1), C<bloglines2email> groups updated
227 items per feed. That reduces a volume of emails sent, and enables a
228 better user experience with Gmail, thanks to the conversation
229 threading based on C<Subject> header. Strongly recommended.
230
231 =item date-timezone (Optional)
232
233 Sets Date timezone for outgoing email C<Date:> header and I<Posted on>
234 phrase inside email body. Default is to use local timezone on your machine.
235
236 =item delicious-username (Optional)
237
238 Sets your del.icio.us username. With this option set, the email body
239 will have I<Post to del.icio.us> link, which is a handy shortcut for
240 bookmarking items to the social bookmarking service.
241
242 =head1 DEVELOPMENT
243
244 The newest version is always available via subversion:
245
246   svn://svn.bulknews.net/public/bloglines2email/trunk
247
248 And you can browse the files via ViewCVS at:
249
250   http://svn.bulknews.net/viewcvs/public/bloglines2email/trunk
251
252 Feel free to send patches or suggestions to E<lt>miyagawa@bulknews.netE<gt>
253
254 =head1 AUTHOR
255
256 Tatsuhiko Miyagawa E<lt>miyagawa@bulknews.netE<gt>
257
258 This script is free software and licensed under the same terms as Perl
259 (Artistic/GPL).
260
261 =cut
Note: See TracBrowser for help on using the browser.