├── MANIFEST.SKIP
├── bloglines2email.conf.sample
├── Makefile.PL
├── Changes
└── bloglines2email
/MANIFEST.SKIP:
--------------------------------------------------------------------------------
1 | \bRCS\b
2 | \bCVS\b
3 | ^MANIFEST\.
4 | ^Makefile$
5 | ~$
6 | \.old$
7 | ^blib/
8 | ^pm_to_blib
9 | ^MakeMaker-\d
10 | \.gz$
11 | \.cvsignore
12 | bloglines2email.conf$
13 | \.svn
14 |
--------------------------------------------------------------------------------
/bloglines2email.conf.sample:
--------------------------------------------------------------------------------
1 | username: your-username@bloglines
2 | password: your-password
3 | mailto: mail-address@example.com
4 | mailfrom: mail-address@example.com
5 | mailroute:
6 | via: smtp
7 | host: localhost:25
8 | # mailroute:
9 | # via: sendmail
10 | group-items: 1
11 | date-timezone: Asia/Tokyo
12 | delicious-username: your-delicious-username
13 |
--------------------------------------------------------------------------------
/Makefile.PL:
--------------------------------------------------------------------------------
1 | use ExtUtils::MakeMaker;
2 | WriteMakefile(
3 | 'NAME' => 'bloglines2email',
4 | 'VERSION_FROM' => 'bloglines2email',
5 | 'PREREQ_PM' => {
6 | WebService::Bloglines => 0.06,
7 | MIME::Lite => 0,
8 | Template => 0,
9 | YAML => 0,
10 | DateTime::Format::Mail => 0,
11 | DateTime => 0,
12 | },
13 | EXE_FILES => [ 'bloglines2email' ],
14 | );
15 |
--------------------------------------------------------------------------------
/Changes:
--------------------------------------------------------------------------------
1 | A version history for bloglines2email
2 |
3 | 0.10 Thu Sep 8 20:15:53 UTC 2005
4 | * Added 'group-items' config for packing entries per feed in a single email
5 | - Tweaked design for Gmail
6 | * Added 'date-timezone' config to change timezone of email
7 | * Added 'Post to del.icio.us' option with delicious-username config
8 | - Misc bug fixes for items entity handling
9 | * Added 'mailroute' config to specify SMTP or sendmail
10 |
11 | 0.01 2005-08-26 06:17:07 UTC
12 | * First version
13 |
--------------------------------------------------------------------------------
/bloglines2email:
--------------------------------------------------------------------------------
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 '
', 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 |
123 |
124 | [% IF feed.image %]
![[% feed.image.title | html %]]([% feed.image.url | html %])
[% END %]
125 | [% var = 'group-items'; IF cfg.$var %]
[% item.title %][% END %]
126 | [% SET link = item.link || item.guid -%]
127 | Link:
[% link | html %]
128 | [% IF item.dc.creator %]by [% item.dc.creator | html %][% END %][% IF item.dc.subject %] on [% item.dc.subject %][% END %]
129 | [% IF item.description -%]
130 | [% IF item.description.match('(?i)^
]') %][% item.description %][% ELSE %]
[% item.description %]
[% END %]
131 | [% ELSE %]
[% END %]
132 |
[% IF item.pubDate %]Posted on [% get_rfc2822_date(item.pubDate) %][% END %] |
permalink |
[% feed.title | html %][% var = 'delicious-username'; IF cfg.$var %][% SET u = "http://del.icio.us/" _ cfg.$var; USE delicious = url(u) %] |
Post to del.icio.us[% END %]
133 |
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 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 in the same directory as script.
194 |
195 | =back
196 |
197 | =head1 CONFIGURATION
198 |
199 | This app uses C 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 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 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 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 header. Strongly recommended.
230 |
231 | =item date-timezone (Optional)
232 |
233 | Sets Date timezone for outgoing email C header and I
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 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 Emiyagawa@bulknews.netE
253 |
254 | =head1 AUTHOR
255 |
256 | Tatsuhiko Miyagawa Emiyagawa@bulknews.netE
257 |
258 | This script is free software and licensed under the same terms as Perl
259 | (Artistic/GPL).
260 |
261 | =cut
262 |
--------------------------------------------------------------------------------