Skip to content

Instantly share code, notes, and snippets.

@panam510
Last active January 8, 2020 08:57
Show Gist options
  • Select an option

  • Save panam510/cceb12880bacc979358a1d28ce10a6a0 to your computer and use it in GitHub Desktop.

Select an option

Save panam510/cceb12880bacc979358a1d28ce10a6a0 to your computer and use it in GitHub Desktop.
#!/usr/bin/perl
# ニコニコチャンネル RSS抽出スクリプト
use 5.14.2;
use warnings;
use utf8;
use Time::Piece;
use Time::Seconds;
use XML::FeedPP;
use Getopt::Std;
$Getopt::Std::STANDARD_HELP_VERSION = 1;
our $VERSION = 20200108;
binmode STDOUT, ":utf8";
our $tzoffset = 32400; #ニコニコ動画側のタイムゾーンオフセット。JST+0900=32400秒決め打ち
# 引数の処理
our($opt_c, $opt_s, $opt_w);
getopts("c:s:w");
my $count = $opt_c // 1; #件数の指定がなければ1件とする
my $search_name = $opt_s; #タイトルで絞る場合の検索文字列
my $is_within_week = $opt_w; #タイムシフト可能期間のものを抽出する指定
my $feedurl = shift;
if ( !(defined($feedurl)) ){
#URLの指定がない
die "feedurl is required.\nUsage: $0 [-c count] [-s string] feedurl\n";
}elsif ( ($count !~ /^[-]?[0-9]+$/) ) {
#件数指定が整数でない
die "count is not number.\nUsage: $0 [-c count] [-s string] feedurl\n";
}
#フィード読み込み
my $feed = XML::FeedPP->new( $feedurl );
(my $mode = $feed->link) =~ s/.*\///; #フィード内にあるURLの末端("video"か"live")を読み、動画か生放送かを判定
my $i = 0;
if ($mode eq "live"){ #末端"live"なら生放送用モード
my %pairs; #キー:時刻、値:放送URLのハッシュ
my $now_epoch = time;
if ($count > 0){
#countが正数のモード。終了済みの放送を、放送終了時刻が近い順に出力。
foreach my $item ($feed->get_item) {
#通常のRSSで使うpubDateでなく、nicoch:end_time=放送終了時刻を使って判断する
#RFC822形式の日時をパース
my $end_time = Time::Piece->strptime($item->get('nicoch:end_time'),'%a, %d %b %Y %H:%M:%S %z');
#比較・ソート用にエポック値としておく
my $end_epoch = $end_time->epoch;
#まだ終わっていない放送は弾き、終了済みの放送のみを処理する
if ( $end_epoch < $now_epoch ) {
#名前での指定がない場合は無条件でハッシュへ、名前指定があれば当てはまる場合だけハッシュへ入れる
if( !(defined($search_name)) || index($item->title,$search_name) != -1 ){
#-wオプションが未指定の場合は無条件でハッシュへセット
if(!$is_within_week){
#放送済みの場合、放送終了時刻をキーにする
$pairs{$end_epoch} = $item->link;
}else{
#-wオプションが指定された場合の処理
#タイムシフト可能期間(終了日から7日後の23:59:59。タイムゾーンはJST+0900決め打ち)を算出、エポック秒に変換
my $ts_limit_epoch = Time::Piece->strptime(($end_time + $tzoffset + ONE_WEEK)->ymd.'T23:59:59+0900',"%FT%T%z")->epoch;
#タイムシフト可能期間内の放送だけハッシュへセット
if( $ts_limit_epoch >= $now_epoch ){
$pairs{$end_epoch} = $item->link;
}
}
}
}
}
#URL出力
foreach my $epoch (sort {$b <=> $a}(keys %pairs)){ #放送終了時刻の降順でソートしつつ処理
my $movieurl = $pairs{$epoch};
#終了済みの放送URLを出力
say $movieurl;
if (++$i >= $count){ #引数で指定された件数を超えたら終了
last;
}
}
}elsif($count < 0){
#countが負数のモード。未来日付の放送を、放送開始時刻が近い順に出力。
$count = abs($count);
foreach my $item ($feed->get_item) {
#nicoch:start_timeを使って判断する
my $start_epoch = Time::Piece->strptime($item->get('nicoch:start_time'),'%a, %d %b %Y %H:%M:%S %z')->epoch;
#開始済みの放送は弾き、未開始の放送のみを処理する
if ( $start_epoch > $now_epoch ) {
#名前での指定がない場合は無条件でハッシュへ、名前指定があれば当てはまる場合だけハッシュへ入れる
if( !(defined($search_name)) || index($item->title,$search_name) != -1 ){
#未放送の場合、放送開始時刻をキーにする
$pairs{$start_epoch} = $item->link;
}
}
}
foreach my $epoch (sort {$a <=> $b}(keys %pairs)){ #放送開始時刻の昇順でソートしつつ処理
my $movieurl = $pairs{$epoch};
#未開始の放送URLを出力
say $movieurl;
if (++$i >= $count){ #引数で指定された件数を超えたら終了
last;
}
}
}else{
#count=0のモード。放送中のものを放送開始時刻順に出力。
foreach my $item ($feed->get_item) {
#nicoch:start_timeを使って判断する
my $start_epoch = Time::Piece->strptime($item->get('nicoch:start_time'),'%a, %d %b %Y %H:%M:%S %z')->epoch;
my $end_epoch = Time::Piece->strptime($item->get('nicoch:end_time'),'%a, %d %b %Y %H:%M:%S %z')->epoch;
if ( ($start_epoch <= $now_epoch) && ($end_epoch >= $now_epoch) ) {
#放送中のみを処理する
if( !(defined($search_name)) || index($item->title,$search_name) != -1 ){
$pairs{$start_epoch} = $item->link;
}
}
}
foreach my $epoch (sort {$a <=> $b}(keys %pairs)){ #放送開始時刻の昇順でソートしつつ処理
my $movieurl = $pairs{$epoch};
#放送中のURLを出力
#「同一チャンネルで複数番組を同時放送」の可能性があるが、未検証。とりあえず全件出るはず
say $movieurl;
}
}
}elsif($mode eq "video"){ #末端"video"なら動画用モード
if ($count < 0){
#動画のときは負のcountを認めない(未来日付の動画はないため)
die "For video feedurl, count can't be negative.\n"
}
#FeedPPの標準メソッドでソートを行う(対象はpubDate)
$feed->normalize;
foreach my $item ($feed->get_item){
#動画URLを新しいものから出力
if( !(defined($search_name)) || index($item->title,$search_name) != -1){
say $item->link;
if (++$i >= $count){ #引数で指定された件数を超えたら終了
last;
}
}
}
}else{
#生放送/動画じゃないっぽいRSSを読んだら終了
die "feedurl: unknown format (Neither http://ch.nicovideo.jp/chXXX/video nor http://ch.nicovideo.jp/chXXX/live)."
}
sub HELP_MESSAGE {
say "Usage: $0 [-c count] [-s string] [-w] feedurl";
say '';
say 'feedurlの例は以下の通り';
say ' http://ch.nicovideo.jp/chXXX/video?rss=2.0';
say ' http://ch.nicovideo.jp/chXXX/live?rss=2.0';
say '';
say 'Option:';
say ' -c count 出力するURLの件数を整数で指定、省略した場合は1。';
say ' 指定したURLが動画の場合と生放送の場合で挙動が異なる。';
say ' 動画 :正の整数のみ許容、動画のURLを新しいものから出力。';
say ' 生放送:正の整数を渡した場合、終了した放送のURLを、終了時刻が新しいものから出力。';
say ' 負の整数を渡した場合、未放送のURLを、開始時刻が近いものから出力。';
say ' 0を渡した場合、放送中のURLを出力。';
say ' -s string 放送のタイトルで絞り込み、ヒットしたURLのみ出力。';
say ' -w 生放送のタイムシフト視聴期間内(放送終了から7日後の23時59分59秒まで)の放送URLのみ出力する。';
say ' 生放送、かつcountが正の整数の場合のみ有効。';
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment