ラジオの「(非公式な)オンエア曲 BOT」を気が向いて作った話

みなさん、ラジオ聴いてますか? ~~自分は普段 radiko で聴いてますが。~~
気になったオンエア曲が Twitter のタイムラインに流れてきてついついいいねしちゃいますよね。~~自分もついついいいねしちゃいます。~~
そんな BOT を作ったのである方から「どうやって作ってるんですか?」と聞かれたりしました。
今回はそんな裏側をソースと共に公開したいと思います。~~もしも自分に何かあった時、誰かがソースをフォークして使えば安心だね!~~

## 作ろうとしたきっかけ
### 作った当時
- 名古屋の [ZIP-FM](http://zip-fm.co.jp/) を聴いていてオンエア曲が気になった
- Twitter の BOT もある
- でも息してない、~~自分としては~~動画サイトなどのリンクまではいらない
  - [J-WAVE の(非公式な) BOT](https://twitter.com/jwave) みたいなシンプルさ
- でもその要件を満たしてる BOT がない
- じゃあ作ろう

### つい最近
- そういえば ZIP-FM の BOT は(自分の作ったやつも)あるけど他局([@FM](http://www.fma.co.jp/)、[Radio NEO](http://radio-neo.com/) など)は見かけ無いよね
- じゃあ作ろう

## 仕組み
- オンエア曲のページなどからオンエア曲情報を取得する
- オンエア曲情報を解析してオンエア時間、アーティスト名、曲名を取得する
- 取得したオンエア時間、アーティスト名、曲名をツイートする
- これを毎分間隔で動くようにスケジューリングしておく

### オンエア曲情報の取得
さて肝心要のオンエア曲情報どこから取得するかです。各局の公式サイトや radiko さん、JFN な放送局では keitai.fm で見ることがありますね。~~タネ明かしすると~~自分の場合はデータの扱いやすさから radiko さんや keitai.fm から取得しています。
ちょっとブラウザの開発者コンソール機能を使って ＠FM の場合 radiko、keitai.fm でどういうふうになっているか見てみましょう。

<img width="320" alt="radiko.jp_console" src="https://qiita-image-store.s3.amazonaws.com/0/19210/b16b39eb-9582-bb22-b09d-f73bd801450c.png"> <img width="320" alt="keitai.fm_console" src="https://qiita-image-store.s3.amazonaws.com/0/19210/fd58dd5c-e0b2-f56b-ba8e-84d8ecbb8212.png">
なにやらそれぞれ定期的に XML ファイルを取得していますね。

#### radiko.jp
いろんな XML ファイルがあるのでちょっと「noa」というキーワードで絞り込んでみます。
<img width="320" alt="radiko.jp_console" src="https://qiita-image-store.s3.amazonaws.com/0/19210/d7e504af-ae4d-ebb3-a241-d773632774a0.png">

どうやら FMAICHI.xml にオンエア曲情報があるみたいです。
<img width="320" alt="radiko.jp_xml" src="https://qiita-image-store.s3.amazonaws.com/0/19210/33097c8e-6852-3895-df08-34e3df54b6c5.png">

radiko は局 ID をキーにして選局しているのでこの ID を変えれば各 FM 局のオンエア曲情報が取得できそうです。~~また XML ファイルを取得するとき更新のチェックも自力でする必要がないので楽できそうですね。~~

```perl:post
$file_name = "$station_id.xml";

$ua = LWP::UserAgent->new();
$ua->agent("Mozilla/5.0");
$res = $ua->mirror("http://radiko.jp/v3/feed/pc/noa/$file_name", "$cache_dir/$file_name");
if (!$res->is_success) {
	print "no update\n";
	exit 1;
}
```

#### keitai.fm
どうやら noamusic.xml にオンエア曲情報があるみたいです。
<img width="320" alt="keitai.fm_xml" src="https://qiita-image-store.s3.amazonaws.com/0/19210/839cd445-ecf9-bb41-d980-d3380181e0f7.png">

keitai.fm は本局のコールサインをキーにして選択しているのでこのコールサインを変えれば各 FM 局のオンエア曲情報が取得できそうです。~~でも XML ファイルを取得するとき更新のチェックも自力でする必要があります。~~

```perl:post
$callsign = $station_id;
$file_name = "$station_id.xml";
$callsign =~ s/JO(\S{2})-FM/\1/g;
$callsign = lc($callsign);

$ua = LWP::UserAgent->new();
$ua->agent("Mozilla/5.0");
$res = $ua->mirror("http://www.keitai.fm/search/view/$callsign/xml/noamusic.xml", "$cache_dir/$file_name");
if (!$res->is_success) {
	print "no update\n";
	exit 1;
}
```

### オンエア曲情報を解析
各サイトから取得した XML データをパースして Twitter に投稿するフォーマットに変換しています。それぞれ下2行は個人的に半角英数字に半角スペース、全角カナ~~の方が精神衛生上落ち着くの~~で揃えるようにしています。

#### radiko.jp
```perl:post
$data = XML::Simple->new(keyattr => [])->XMLin("$cache_dir/$file_name");
$noa = $data->{"noa"}->{"item"}[0];

# TIMESTAMP
$time = Time::Piece->strptime($noa->{"stamp"}, '%Y-%m-%d %H:%M:%S');
$noatime = sprintf("%04d/%02d/%02d %02d:%02d", $time->year, $time->mon, $time->mday, $time->hour, $time->minute);

$make_value = sprintf("%s - %s", $noa->{"artist"}, $noa->{"title"});
$str = encode('utf-8', HTML::Entities::decode($make_value));
$str = Unicode::Japanese->new($str)->z2h->get;
$str = Unicode::Japanese->new($str)->h2zKana->get;
```
radiko で取得したオンエア曲情報は過去のオンエア曲も数曲分入っているので一番上のデータだけ取り出しています。

#### keitai.fm
```perl:post
$noa = XML::Simple->new(keyattr => [])->XMLin("$cache_dir/$file_name");

if (ref($noa->{"artist_name"}) eq "HASH") {
	print "artist_name is hash\n";
	exit 1;
}

# TIMESTAMP
$time = Time::Piece->strptime($noa->{"onair_time"}, '%Y-%m-%dT%H:%M:%S+09:00');
$noatime = sprintf("%04d/%02d/%02d %02d:%02d", $time->year, $time->mon, $time->mday, $time->hour, $time->minute);

$make_value = sprintf("%s - %s", $noa->{"artist_name"}, $noa->{"music_name"});
$str = HTML::Entities::decode($make_value);
$str = Unicode::Japanese->new($str)->z2h->get;
$str = Unicode::Japanese->new($str)->h2zKana->get;
```
keitai.fm で取得したオンエア曲情報は取得時の分のみなのですが、たまに変にパースするところがあるのでそこを検知したら何もせずプログラムが終わるようにしています。

### ツイートする
```perl:post
# コンシューマ セット
my %INIT_PARAMS = (
    consumer_key    => $param->{"CONSUMER_PARAM.CONSUMER_KEY"},
    consumer_secret => $param->{"CONSUMER_PARAM.CONSUMER_SECRET"},
    ssl             => 1,
);

my $t = Net::Twitter::Lite::WithAPIv1_1->new(%INIT_PARAMS);

# トークンをセットする
$t->access_token($param->{"ACCESS_TOKEN.ACCESS_TOKEN"});
$t->access_token_secret($param->{"ACCESS_TOKEN.TOKEN_SECRET"});

binmode(STDOUT, ":utf8");

# 投稿
# delete strings
$str = decode('utf-8', $str);
my $tweet = sprintf("[%s] %s", $noatime, $str);
if(length($tweet) > $max_length) {
    $tweet = substr($tweet, 0, $max_length - 3) . "...";
}

# post to Twitter
my ($status, $statuses, $tmp_status, $ret);
eval {
	# check duplicate tweet
	$statuses = $t->user_timeline({ count => 1 });
	for my $user_tl (@$statuses) {
		$tmp_status = $user_tl->{text};
	}
	$tmp_status =~ s|^\[(\d+)/(\d+)/(\d+) (\d+):(\d+)\] ||;
	print "duplicate?[".$str."][$tmp_status]\n";
	if($str eq $tmp_status){ print "duplicate![".$str."][$tmp_status]\n"; exit -1; }

	$status = $t->update({ status => $tweet });
};
if($@) {
	print STDERR "Tweet WARNING: $@\n";
	$ret = 1;

} else {
	print "Tweet posted.[$tweet]\n";
	$ret = 0;
}
exit $ret;
```
CONSUMER_KEY や ACCESS_TOKEN は基本別ファイルに保存しています。ツイートする際の文字数制限や重複チェックはここで行っています。

## ここまで見たのでソースを出します
- [GitHub - soprano1125/radiko-noa: radiko.jp NOW ON AIR tweet](http://github.com/soprano1125/radiko-noa)
- [GitHub - soprano1125/jfn-noa: JFN NOW ON AIR tweet](http://github.com/soprano1125/jfn-noa)

GitHub には上に書いてある内容以上にやっていることがあるので詳しくは各リポジトリを見てください(ぉ

## その他注意点
- 共通して言えること
  - BOT を動かす場合、各サイトに迷惑をかけない程度に。
- GitHub でフォークせずに使う場合
  - UNIX 系 OS(BSD / Linux)で動く環境で作っています。もしかしたら Windows でも Perl やその他モジュールを導入すれば動く**かも**しれません。(未確認事項)
  - (BOT なので)24時間365日動かせられる PC で使ってください。
  - (もちろんですが)Web 上で動かす想定で作っていません。
- GitHub でフォークして使う場合
  - セキュリティはしっかり配慮してください。(TOKEN が漏れる、最悪乗っ取られるなど)
  - ↑さえ守っていただければ煮るなり焼くなり美味しくいただいてください。

## おまけ
- Twilog や twisave などの各サービスを登録しておけばアーカイブが見れたり、最近どんな曲が多くかかってるか見れたりします。
- たまに BOT アカウントのフォロワーを見ると公式のアカウントがフォローしていて中の人は大変~~驚きながら~~恐縮しています。
~~他局さんでも公式の NOA BOT があったりするのでぜひ公式でも NOA BOT を作ってください。なんでもｓ（ｒｙ~~