#!/usr/local/bin/perl ################################# # # # mixi RSS + LocalCache mod # # # # Created by Kero # # # ################################# # このスクリプトのご使用は、ご自身の責任でお願いします。 # HTMLの構造が変わると途端に使えなくなると思いますので、直す気力のある人推奨 # (っていうか、突貫で書いてるのでソース汚すぎ……) # Ver upしたのを作った人は、作者にソースとトラバください。 # http://mo.kerosoft.com/0120/ package main; use strict; # 使ってるライブラリ類は用意のこと # yum install perl-Digest-SHA1とかで簡単に入るはずだから # 多分レンタルサーバーでこれだけ準備するのは辛いかと use LWP::UserAgent; use HTTP::Request::Common; use DateTime; use Digest::SHA1 qw/sha1/; use HTTP::Request; use MIME::Base64 qw/encode_base64/; use CGI; use HTML::TagParser; # これだけはCPANからpmをポンと置けばよい。(./HTML/TagParser.pm) use HTTP::Cookies; use Data::Dumper; use XML::TreePP; use XML::Twig; use Encode; my $v = "2.7"; # 保存先の相対パス my $folder = q|./mixi/|; # 保存先のURL my $baseurl = q|https://your_server_name/mixi/|; # あなたのメールアドレス my $email = q|your@mail.address|; # あなたのログインパスワード my $passwd = q|LOGIN_PASSWORD|; # あなたのmixi ID my $id = q|0000000|; ################################ 設定は、ここまでのはず ### 準備 ### # 一般的には日記の中にはプライベートなことも含まれるため、BASIC認証をかけた上でhttpsを使うのが # 好ましいでしょう。が、このcgiをhttpsから呼ぶと、中身に入ってる絵文字がhttpなため、 # 混合ゾーン警告が出ます。それを防ぐため、絵文字もローカルキャッシュにしておきます。 # このスクリプトの下にemojiというフォルダを作り、 # http://img.mixi.jp/img/emoji/[1-246].gifを展開してダウンロードしておきます。(ログイン不要) # 落とすのが面倒な人は、以下のスクリプトで置換してるところをコメントアウト。(2カ所) ### アクセス方法 ### # ブラウザからmixi.cgi?(diary|members|log)のようにアクセス # SSH環境からはmixi.cgi (diary|members|log)のように打つ(テスト用) # 出力するものは全てRSS(またはエラーメッセージ)です。 # ?diaryが呼ばれた場合のみ、日記全文と画像類を取得してローカルキャッシュします。 # 鯖のスペックや回線スピードにもよりますが、処理が終わるまでに10秒~20秒程度と # 時間がかかるので放置のこと。 my %params = ( 'diary' => "http://mixi.jp/atom/updates/r=1/member_id=$id/-/diary", # 日記 'members' => "http://mixi.jp/atom/friends/r=1/member_id=$id", # マイミク一覧 'log' => "http://mixi.jp/atom/tracks/r=2/member_id=$id", # あしあと # APIは他にもあるので、必要に応じてどうぞ ); my $cgi = new CGI; my $param = $cgi->param('type') || shift; if(!defined($params{$param})){ print qq|Status: 404 Not Found\n|; print qq|Content-Type: text/html; charset=utf-8\n\n|; print qq|要求が不正です。|; exit; } my $nonce = sha1(sha1(time().{}.rand().$$)); my $now = DateTime->now->iso8601.'Z'; my $digest = encode_base64(sha1($nonce.$now.$passwd||''),''); my $credentials = sprintf(qq(UsernameToken Username="%s", PasswordDigest="%s", Nonce="%s", Created="%s"), $email, $digest, encode_base64($nonce,''), $now); my $req = HTTP::Request->new(GET => $params{$param}); $req->header( Accept => 'application/x.atom+xml, application/xml, text/xml, */*' ); $req->header( 'X-WSSE' => $credentials ); # LWP::Authen::Wsseを入れてればこれでいいらしい。 #my $ua = LWP::UserAgent->new; #$ua->credentials('mixi:80', '', $email, $passwd); #my $res = $ua->get("http://mixi.jp/atom/updates/r=1/member_id=${id}/-/diary"); #print q|Content-Type: |.$res->header('Content-Type')."\n\n"; # RSSをもらいにいく my $res = LWP::UserAgent->new->request($req); if($res->is_success){ print q|Content-Type: |.$res->header('Content-Type').qq|\n\n|; if($param ne "diary"){ my $tpp = XML::TreePP->new; $tpp->set(force_array => ['entry'], ignore_error => 1); my $hash = $tpp->parse($res->content); # 最終更新日時を、API取得日時ではなく、エントリの最新の更新日時とする # (RSSリーダーでRSS取得の度に更新マークがでまくるのを防止) $hash->{feed}->{updated} = $hash->{feed}->{entry}[0]->{updated}; my $twig = new XML::Twig; $twig->set_indent(" "x4); $twig->parse($tpp->write($hash)); $twig->set_pretty_print("indented"); print $twig->sprint; exit; } # 以下は日記だけの処理 (my $content = $res->content) =~ s!.*?\((.+?)\)!by $1!ig; # ログイン my $ua = LWP::UserAgent->new; my $cookie = HTTP::Cookies->new( ignore_discard => 1 ); $ua->cookie_jar($cookie); $req = HTTP::Request->new('POST',q|https://mixi.jp/login.pl|); $req->content_type('application/x-www-form-urlencoded'); $req->content(qq|next_url=%2fhome%2epl&email=$email&password=$passwd&sticky=on&x=0&y=0|); $res = $ua->request($req); if($res->is_success){ # 各ユーザーの日記をhtmlに落としていく my @diary_urls = ($content =~ m{}ig); # 保存先のURL my @diary_db; foreach(@diary_urls){ $_ =~ s/&/&/g; $ua = LWP::UserAgent->new; $ua->cookie_jar($cookie); $req = HTTP::Request->new(GET => $_); $req->header( 'Connection' => "Keep-Alive" ); $res = $ua->request($req); if($res->is_success){ my $article = HTML::TagParser->new($res->content); my ($diary_author, $diary_title) = $article->getElementsByTagName("title")->innerText() =~ m{\[mixi\] (.*?) \| (.*?)$}i; # my $diary_body = $article->getElementById("diary_body")->innerText; とやりたいが、 # HTML::TagParserの制約上タグが落ちる。ここでは、どうしてもタグ付きのままとりたいので、致し方ない…… (my $content = $res->content) =~ s/\r|\n//g; Encode::from_to($content, "euc-jp", "utf8"); my ($diary_body) = ($content =~ m!
]*?>(.+)
\n*<\!--/viewDiaryBox-->!i); $diary_body =~ s!
!
!g; # WIDE DASHと絵文字をローカルに変換 $diary_body =~ s/\xE3\x80\x9C/~/g; $diary_body =~ s!http://img.mixi.jp/img/emoji/!../emoji/!g; my $diary_date = ($article->getElementsByTagName("dd"))->innerText; my($dy,$dm,$dd,$th,$tm) = $diary_date =~ m/(\d+)年(\d+)月(\d+)日(\d+)\:(\d+)/; chomp(my $gmtdate = `date --date "$dy/$dm/${dd} $th:$tm:00 9 hours ago" "+%Y-%m-%dT%H:%M:%SZ"`); my $diary_pics; { # 日記からリンクしているAlbum photoを保存 while($content =~ m!!g){ # 1記事にこのタグは複数ある可能性あり my $addr = qq|http://mixi.jp/|.$1; my($img_aid, $img_num, $img_oid) = ($addr =~ m/album_id=(\d+)&number=(\d+)&owner_id=(\d+)/); if(! -e qq|$folder$img_oid-$img_aid-$img_num.jpg|){ my $imghtml = $ua->request(HTTP::Request->new(GET => $addr)); next if(! $imghtml->is_success); # アルバムの時はimgタグが貼ってあるhtmlを吐かれるので、それを辿る my ($imgaddr) = ($imghtml->content =~ m!request(HTTP::Request->new(GET => $imgaddr)); next if(! $imgres->is_success); if($imgres->is_success){ open(IMG, "> $folder$img_oid-$img_aid-$img_num.jpg"); flock(IMG, 2); binmode(IMG); print IMG $imgres->content; close(IMG); } } # いま処理したタグをローカルアドレスに置換 $diary_body =~ s!!!; # 1件だけ処理 } # 日記に貼り付けているDiary photoを保存 my ($diary_photo) = ($content =~ m!
(.+?)
!); # 1記事にこのタグは1個だけ while($diary_photo =~ m/'(show_diary_picture.pl\?[^']+)'/g){ my $addr = qq|http://mixi.jp/|.$1; my($img_oid, $img_id, $img_num) = ($addr =~ m/owner_id=(\d+)&id=(\d+)&number=(\d+)/); if(! -e qq|$folder$img_oid-$img_id-$img_num.jpg|){ my $imgres = $ua->request(HTTP::Request->new(GET => $addr)); next if(! $imgres->is_success); # imgタグを見つけてダウンロード my ($imgaddr) = ($imgres->content =~ m!request(HTTP::Request->new(GET => $imgaddr)); if($imgres->is_success){ open(IMG, "> $folder$img_oid-$img_id-$img_num.jpg"); flock(IMG, 2); binmode(IMG); print IMG $imgres->content; close(IMG); } } push(@{$diary_pics}, "$folder$img_oid-$img_id-$img_num.jpg"); } } my $comments; my $cnt = 0; # コメント類を取得 my (@c_names, @c_dates, @c_texts); eval{ while($content =~ m!]+>(.+?)!ig){ push(@c_names, $1); } }; eval{ while($content =~ m!(.+?)!ig){ push(@c_dates, $1); } }; eval{ while($content =~ m!\d+\:\d+
(.+?)
!g){ $cnt++; my $txt = $1; # WIDE DASHと絵文字をローカルに置換 $txt =~ s/\xE3\x80\x9C/~/g; $txt =~ s!http://img.mixi.jp/img/emoji/!../emoji/!g; push(@c_texts, $txt); } }; # ハッシュを作る for(my $i=0; $i<=$#c_texts; $i++){ push(@{$comments}, { name => $c_names[$i], date => $c_dates[$i], text => $c_texts[$i] }); } my ($did, $oid) = $_ =~ m{id=(\d+)&(?:amp;)?owner_id=(\d+)}; # 日記記事1つのハッシュ my $record = { orgurl => $_, url => "./$oid-$did.html", fullurl => "$baseurl$oid-$did.html", author => $diary_author, title => $diary_title, body => $diary_body, date => $diary_date, updated => $gmtdate, comments => $comments, comments_cnt => $cnt, pics => $diary_pics, }; # ファイルに open(LOG, "> $folder$oid-$did.html"); flock(LOG, 2); print LOG &getHTML($record); close(LOG); # 外部blogの場合は追加しない(ローカルキャッシュ対象外、RSSは出力する) push(@diary_db, $record) if($diary_title ne ""); }else{ warn "Diary: ".$res->status_line."\n"; } } &writeIndex(@diary_db); # アドレス全部書き換え $content =~ s{http://mixi.jp/home.pl}{$baseurl}i; # 日付書き替え(mixi本家の隠しAPIの日付は、常に取得時間になり、RSSリーダーにnewつきまくってウザ!!) my $tpp = XML::TreePP->new; $tpp->set(force_array => ['entry'], ignore_error => 1); my $hash = $tpp->parse($content); foreach(@{$hash->{feed}->{entry}}){ foreach my $db (@diary_db){ if($_->{link}->{'-href'} eq $db->{orgurl}){ $_->{link}->{'-href'} = $db->{fullurl}; $_->{updated} = $db->{updated}; $_->{title} .= "(".$db->{comments_cnt}.")"; last; } } # 外部blogの場合は、投稿時間不明なため日付セクションを消す if($_->{link}->{'-href'} !~ m!^$baseurl!){ # ついでに外部blogへのジャンクションページも要らん my ($url) = ($_->{link}->{'-href'} =~ m!url=(.+?)&owner_id=\d+!); $url =~ tr/+/ /; $url =~ s/%([0-9A-Fa-f][0-9A-Fa-f])/pack('H2', $1)/eg; $_->{link}->{'-href'} = $url; $_->{updated} = ""; } } $hash->{feed}->{updated} = $hash->{feed}->{entry}[0]->{updated}; my $twig = new XML::Twig; $twig->set_indent(" "x4); $twig->parse($tpp->write($hash)); $twig->set_pretty_print("indented"); print encode("utf8", $twig->sprint); }else{ warn "Login: ".$res->status_line."\n"; } }else{ warn "RSS: ".$res->status_line."\n"; } # ここでローカルキャッシュの各記事のhtmlを作ります sub getHTML{ my $g = $_[0]; my ($pics, $comments); if(defined($g->{pics})){ foreach my $pic (@{$g->{pics}}){ my ($fn) = ($pic =~ m{/?([^/]+?)$}ig); `wget -q -O $folder$fn $pic` if(! -e $folder.$fn); # 落とし損ねてたら、もっかい落とす…とかいう若干意味不明な保険 $pics .= qq|\n|; } $pics .= "
\n"; } if(defined($g->{comments})){ foreach(@{$g->{comments}}){ $comments .= qq|

$_->{date} by $_->{name}

\n$_->{text}
\n|; } } return qq| $g->{author}の日記 - $g->{title}

$g->{title}

$g->{date} by. $g->{author}

$pics $g->{body}
$comments |; } # 各記事へのリンクを作ります。 sub writeIndex{ my @list; foreach(@_){ push(@list, qq|
  • $_->{author} - $_->{title} ($_->{date})
  • |) if($_->{updated} ne ""); } open(LOG, "${folder}list.txt"); # xmlつくればソートとかできたんだろうけど、面倒なのでパス my @log = ; foreach my $a (@list){ chomp($a); my $flag = 1; foreach my $b (@log){ chomp($b); $flag = 0 if($a eq $b); } unshift(@log, $a) if($flag); } close(LOG); open(LOG, "> ${folder}list.txt"); flock(LOG, 2); print LOG join("\n", @log); close(LOG); open(LOG, "> ${folder}index.html"); flock(LOG, 2); print LOG qq| mixi日記一覧

    mixi日記一覧

      @log
    |; close(LOG); }