この記事は2年以上前に書いたものです。
そのため情報が古い可能性があります。ご了承ください。m(_ _)m
そのため情報が古い可能性があります。ご了承ください。m(_ _)m
時間かけて探せば良い感じのが有りそうだけど、すぐには見つからなかったのでperlで書いてみた。
仕様的なもの。
- 常駐せずに、10分おきくらいにcronで動かす。
- 1日分のtweetは、$timeline_fileに溜め込む。
- 1日分の切り替えは、Log::Dispatch::FileRotateに任せる。
(微妙にずれる場合もあるけど、細かいことは(゚ε゚)キニシナイ!!) - $timeline_fileへの出力は、Log::Log4perl の INFOで。
- kindleへ送信するタイミングは、$timeline_fileのサイズが小さくなったら。
(FileRotateで切り替わったらファイルサイズが小さくなるので、それ契機) - kindleへの送信するデータは、htmlファイル。
- 送信先は、@kindle.com or @free.kindle.com。
(自動的に変換して、端末へ配信してくれる)
gettimeline.pl(gettimeline.zip)
#!/usr/bin/perl
use 5.010;
use strict;
use warnings;
use Encode;
use DateTime;
use DateTime::TimeZone;
use DateTime::Format::DateParse;
use Data::Dumper;
use HTML::Entities;
use MIME::Lite;
use Net::Twitter;
use Log::Log4perl qw(:easy);
# Twitter key and token
my $consumer_key = 'xxxxxxxxxxxxxxxxxxxxx';
my $consumer_secret = 'yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy';
my $token = 'nnnnnnnnnn-zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz';
my $token_secret = 'sssssssssssssssssssssssssssssssssssssssssss';
# log
my $logfile = '/home/user/tool/twitter.log';
my $timeline_file = '/home/user/tool/timeline.log';
# mail
my $mailfrom = 'xxxx@xxx.xxx';
my $mailto = 'yyyyy@free.kindle.com';
# title
my $title_fmt = 'Twitter_Timeline_%Y-%m-%d(%a)';
my $tid_prefix = '__TID__:';
my $tz = DateTime::TimeZone->new(name => 'local');
&loginit;
&main;
exit;
sub main{
my $filesize = -s $timeline_file;
&gettl;
if($filesize > -s $timeline_file){
say 'send to kindle';
&send2kindle;
}
}
sub send2kindle{
my $dt = DateTime->now(time_zone => $tz);
$dt->subtract(days => 1);
my $title = $dt->strftime($title_fmt);
my $fname = $title . '.html';
my $timeline_file1 = $timeline_file . '.1';
open my $fh, '<', "$timeline_file1"
or die "failed to open file: $!";
my $rawdata = do { local $/; <$fh> };
my @rawdata_array = grep(!/^$tid_prefix \d+$/, split(/\n/, $rawdata));
my $htmldata = <<_HEADER_;
<html>
<head>
<title>$title</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
<h3>$title</h3>
<pre>
_HEADER_
$htmldata .= Encode::decode_utf8(join("\n", @rawdata_array));
$htmldata .= <<_FOOTER_;
</pre>
</body>
</html>
_FOOTER_
my $subject = $title;
my $msg = MIME::Lite->new(
From => "$mailfrom",
To => "$mailto",
Subject => "$subject",
Type => 'multipart/mixed'
);
$msg->attach(
Type => 'text/plain; charset="iso-2022-jp"',
Data => "no body"
);
$msg->attach(
Type => 'text/html',
Data => "$htmldata",
Filename => "$fname",
Disposition => 'attachment'
);
$msg->attr('content-type.charset' => 'UTF-8');
# WARN Dumper($msg);
# WARN $htmldata;
$msg->send();
}
sub gettl{
my $nt = Net::Twitter->new(
traits => [qw/OAuth API::REST/],
consumer_key => $consumer_key,
consumer_secret => $consumer_secret,
access_token => $token,
access_token_secret => $token_secret,
);
# WARN "TWEET: " . $tweet;
my @tl = $nt->home_timeline({count => 200});
my @rtl = reverse(@{$tl[0]});
my $tail_cmd = "tail -2 $timeline_file";
my $grep_cmd = "grep -e "$tid_prefix [0-9]\\+"";
my $sed_cmd = "sed -r "s/[^0-9]+([0-9]+)/\\1/"";
my $lastid = `$tail_cmd | $grep_cmd | $sed_cmd`;
chomp $lastid;
if($lastid){
for my $i (0 .. $#rtl) {
if($lastid eq $rtl[$i]->{id}){
#say "0 -> " . $i . " ($#rtl)";
splice(@rtl, 0, $i+1);
#say " -> " . $#rtl;
last;
}
}
}
my $id = '';
foreach my $t(@rtl){
WARN $t->{id};
$id = $t->{id};
my $s = &mklinktag($t->{text});
my $dt = DateTime::Format::DateParse->parse_datetime($t->{created_at});
$dt->set_time_zone($tz);
INFO encode_utf8($t->{user}{name}) .
' (<a href="https://twitter.com/' . $t->{user}{screen_name} . '">' .
'@' . $t->{user}{screen_name} . '</a>)' . "\n" .
encode_utf8($s) . "\n" .
$dt->strftime('(%Y/%m/%d %H:%M:%S)') . "\n";
# WARN Dumper($t);
WARN Dumper($s);
}
INFO "$tid_prefix " . $id . "\n" if $id;
}
sub mklinktag{
my ($s) = @_;
$s = encode_entities($s, q{&<>"'});
$s =~ s!(https?://)([0-9a-zA-Z/\.]+)!<a href="$1$2">$2</a>!go;
$s =~ s!@([a-zA-Z0-9_]+)!<a href="https://twitter.com/$1">\@$1</a>!go;
$s;
}
sub loginit{
my $logsize = 10*1024*1024;
Log::Log4perl->init(\ qq{
log4perl.logger = INFO, AppError, Tweet
# filter range
log4perl.filter.MatchRange = Log::Log4perl::Filter::LevelRange
log4perl.filter.MatchRange.LevelMin = WARN
log4perl.filter.MatchRange.LevelMax = ERROR
log4perl.filter.MatchRange.AcceptOnMatch = true
# Error appender
log4perl.appender.AppError = Log::Dispatch::FileRotate
log4perl.appender.AppError.filename = $logfile
log4perl.appender.AppError.max = 10
log4perl.appender.AppError.autoflush = 1
log4perl.appender.AppError.size = $logsize
log4perl.appender.AppError.mode = append
log4perl.appender.AppError.layout = Log::Log4perl::Layout::PatternLayout
log4perl.appender.AppError.layout.ConversionPattern = %d %02X{client} %p %F{1} %L: %m %n
log4perl.appender.AppError.Filter = MatchRange
# Filter to match level WARN
log4perl.filter.MatchInfo = Log::Log4perl::Filter::LevelMatch
log4perl.filter.MatchInfo.LevelToMatch = INFO
log4perl.filter.MatchInfo.AcceptOnMatch = true
# Error appender
log4perl.appender.Tweet = Log::Dispatch::FileRotate
log4perl.appender.Tweet.filename = $timeline_file
log4perl.appender.Tweet.max = 10
log4perl.appender.Tweet.autoflush = 1
log4perl.appender.Tweet.DatePattern = yyyy-MM-dd
log4perl.appender.Tweet.TZ = JST
log4perl.appender.Tweet.mode = append
log4perl.appender.Tweet.layout = Log::Log4perl::Layout::PatternLayout
log4perl.appender.Tweet.layout.ConversionPattern = %m %n
log4perl.appender.Tweet.Filter = MatchInfo
});
}
use 5.010;
use strict;
use warnings;
use Encode;
use DateTime;
use DateTime::TimeZone;
use DateTime::Format::DateParse;
use Data::Dumper;
use HTML::Entities;
use MIME::Lite;
use Net::Twitter;
use Log::Log4perl qw(:easy);
# Twitter key and token
my $consumer_key = 'xxxxxxxxxxxxxxxxxxxxx';
my $consumer_secret = 'yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy';
my $token = 'nnnnnnnnnn-zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz';
my $token_secret = 'sssssssssssssssssssssssssssssssssssssssssss';
# log
my $logfile = '/home/user/tool/twitter.log';
my $timeline_file = '/home/user/tool/timeline.log';
my $mailfrom = 'xxxx@xxx.xxx';
my $mailto = 'yyyyy@free.kindle.com';
# title
my $title_fmt = 'Twitter_Timeline_%Y-%m-%d(%a)';
my $tid_prefix = '__TID__:';
my $tz = DateTime::TimeZone->new(name => 'local');
&loginit;
&main;
exit;
sub main{
my $filesize = -s $timeline_file;
&gettl;
if($filesize > -s $timeline_file){
say 'send to kindle';
&send2kindle;
}
}
sub send2kindle{
my $dt = DateTime->now(time_zone => $tz);
$dt->subtract(days => 1);
my $title = $dt->strftime($title_fmt);
my $fname = $title . '.html';
my $timeline_file1 = $timeline_file . '.1';
open my $fh, '<', "$timeline_file1"
or die "failed to open file: $!";
my $rawdata = do { local $/; <$fh> };
my @rawdata_array = grep(!/^$tid_prefix \d+$/, split(/\n/, $rawdata));
my $htmldata = <<_HEADER_;
<html>
<head>
<title>$title</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
<h3>$title</h3>
<pre>
_HEADER_
$htmldata .= Encode::decode_utf8(join("\n", @rawdata_array));
$htmldata .= <<_FOOTER_;
</pre>
</body>
</html>
_FOOTER_
my $subject = $title;
my $msg = MIME::Lite->new(
From => "$mailfrom",
To => "$mailto",
Subject => "$subject",
Type => 'multipart/mixed'
);
$msg->attach(
Type => 'text/plain; charset="iso-2022-jp"',
Data => "no body"
);
$msg->attach(
Type => 'text/html',
Data => "$htmldata",
Filename => "$fname",
Disposition => 'attachment'
);
$msg->attr('content-type.charset' => 'UTF-8');
# WARN Dumper($msg);
# WARN $htmldata;
$msg->send();
}
sub gettl{
my $nt = Net::Twitter->new(
traits => [qw/OAuth API::REST/],
consumer_key => $consumer_key,
consumer_secret => $consumer_secret,
access_token => $token,
access_token_secret => $token_secret,
);
# WARN "TWEET: " . $tweet;
my @tl = $nt->home_timeline({count => 200});
my @rtl = reverse(@{$tl[0]});
my $tail_cmd = "tail -2 $timeline_file";
my $grep_cmd = "grep -e "$tid_prefix [0-9]\\+"";
my $sed_cmd = "sed -r "s/[^0-9]+([0-9]+)/\\1/"";
my $lastid = `$tail_cmd | $grep_cmd | $sed_cmd`;
chomp $lastid;
if($lastid){
for my $i (0 .. $#rtl) {
if($lastid eq $rtl[$i]->{id}){
#say "0 -> " . $i . " ($#rtl)";
splice(@rtl, 0, $i+1);
#say " -> " . $#rtl;
last;
}
}
}
my $id = '';
foreach my $t(@rtl){
WARN $t->{id};
$id = $t->{id};
my $s = &mklinktag($t->{text});
my $dt = DateTime::Format::DateParse->parse_datetime($t->{created_at});
$dt->set_time_zone($tz);
INFO encode_utf8($t->{user}{name}) .
' (<a href="https://twitter.com/' . $t->{user}{screen_name} . '">' .
'@' . $t->{user}{screen_name} . '</a>)' . "\n" .
encode_utf8($s) . "\n" .
$dt->strftime('(%Y/%m/%d %H:%M:%S)') . "\n";
# WARN Dumper($t);
WARN Dumper($s);
}
INFO "$tid_prefix " . $id . "\n" if $id;
}
sub mklinktag{
my ($s) = @_;
$s = encode_entities($s, q{&<>"'});
$s =~ s!(https?://)([0-9a-zA-Z/\.]+)!<a href="$1$2">$2</a>!go;
$s =~ s!@([a-zA-Z0-9_]+)!<a href="https://twitter.com/$1">\@$1</a>!go;
$s;
}
sub loginit{
my $logsize = 10*1024*1024;
Log::Log4perl->init(\ qq{
log4perl.logger = INFO, AppError, Tweet
# filter range
log4perl.filter.MatchRange = Log::Log4perl::Filter::LevelRange
log4perl.filter.MatchRange.LevelMin = WARN
log4perl.filter.MatchRange.LevelMax = ERROR
log4perl.filter.MatchRange.AcceptOnMatch = true
# Error appender
log4perl.appender.AppError = Log::Dispatch::FileRotate
log4perl.appender.AppError.filename = $logfile
log4perl.appender.AppError.max = 10
log4perl.appender.AppError.autoflush = 1
log4perl.appender.AppError.size = $logsize
log4perl.appender.AppError.mode = append
log4perl.appender.AppError.layout = Log::Log4perl::Layout::PatternLayout
log4perl.appender.AppError.layout.ConversionPattern = %d %02X{client} %p %F{1} %L: %m %n
log4perl.appender.AppError.Filter = MatchRange
# Filter to match level WARN
log4perl.filter.MatchInfo = Log::Log4perl::Filter::LevelMatch
log4perl.filter.MatchInfo.LevelToMatch = INFO
log4perl.filter.MatchInfo.AcceptOnMatch = true
# Error appender
log4perl.appender.Tweet = Log::Dispatch::FileRotate
log4perl.appender.Tweet.filename = $timeline_file
log4perl.appender.Tweet.max = 10
log4perl.appender.Tweet.autoflush = 1
log4perl.appender.Tweet.DatePattern = yyyy-MM-dd
log4perl.appender.Tweet.TZ = JST
log4perl.appender.Tweet.mode = append
log4perl.appender.Tweet.layout = Log::Log4perl::Layout::PatternLayout
log4perl.appender.Tweet.layout.ConversionPattern = %m %n
log4perl.appender.Tweet.Filter = MatchInfo
});
}
↑のコードで送信されたファイルのスクリーンショットが↓。
アイコンとかも持って来たほうが見栄えが良いんだろうけど、とりあえずシンプルなこれで使ってみる。