├── 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 %][% 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 | --------------------------------------------------------------------------------