miyagawaさんの以下のプログラムを組み合わせて使うことで、radikoで録音した番組をPodcastプレイヤーで再生することができる。
- miyagawa/ripdiko: Rip radiko programs
- miyagawa/dircaster: Generate RSS 2.0 podcast out of directory with *.mp3s
おおまかな流れは以下の感じ。
- cronで起動されたripdikoがradikoを録音し、指定フォルダにMP3ファイルで保存する
- cronで起動したdircasterが指定フォルダにあるMP3ファイルを解析し、Podcast配信用のXMLを出力する
- Podcastプレイヤーで2で生成されるXMLを購読しておけば、番組が更新されるたびに通知され聞くことができる
- ただし、1のMP3ファイル、2のXMLはインターネットから参照できる場所に出力する必要がある(HTTPサーバが必要)
Radikoプレミアムへの対応
ただ、残念なことにripdikoはradikoプレミアムへは対応しておらず、エリアフリーで他県の番組を録音することができない。
このため、uru2/rec_radiko_ts: Radiko timefree program recorderを参考にして、radikoプレミアムへログインしエリアフリーで番組を録音できるようにした。
環境変数RIPDIKO_MAIL
でメールアドレス、RIPDIKO_PASS
にパスワードを設定しておくことで、他県の番組も録音することができる。
diff --git a/bin/ripdiko b/bin/ripdiko
index ebc756a..2ca3b70 100755
--- a/bin/ripdiko
+++ b/bin/ripdiko
@@ -8,6 +8,7 @@ require 'open-uri'
require 'nokogiri'
require 'date'
require 'fileutils'
+require 'json'
module Notifier
SCRIPTS = ENV['RIPDIKO_SCRIPTS'] || "#{ENV['HOME']}/.ripdiko/scripts"
@@ -61,6 +62,43 @@ class DownloadTask
@output = ENV['RIPDIKO_OUTPUT'] == nil ? "file" : ENV['RIPDIKO_OUTPUT']
end
+ def login
+ uri = URI.parse("https://radiko.jp/v4/api/member/login")
+ data = {
+ "mail" => ENV['RIPDIKO_MAIL'],
+ "pass" => ENV['RIPDIKO_PASS']
+ }
+
+ return "" if data["mail"].nil? || data["pass"].nil?
+ encoded_data = URI.encode_www_form(data)
+ http = Net::HTTP.new(uri.host, uri.port)
+ http.use_ssl = (uri.scheme == "https")
+
+ request = Net::HTTP::Post.new(uri.path, {'Content-Type' => 'application/x-www-form-urlencoded'})
+ request.body = encoded_data
+ response = http.request(request)
+
+ return JSON.parse(response.body)["radiko_session"] || ""
+ end
+
+ def logout
+ uri = URI.parse("https://radiko.jp/v4/api/member/logout")
+ data = {
+ "radiko_session" => @radiko_session
+ }
+ encoded_data = URI.encode_www_form(data)
+ http = Net::HTTP.new(uri.host, uri.port)
+ http.use_ssl = (uri.scheme == "https")
+
+ request = Net::HTTP::Post.new(uri.path, {'Content-Type' => 'application/x-www-form-urlencoded'})
+ request.body = encoded_data
+ response = http.request(request)
+
+ @radiko_session = ""
+ end
+
+
+
def authenticate
res = fetch 'https://radiko.jp/apps/js/playerCommon.js?_=20171113', {}
if /new RadikoJSPlayer\(.*?'pc_html5',\s*'(\w+)'/ === res.body
@@ -79,11 +117,16 @@ class DownloadTask
abort "auth1 failed"
end
+ @radiko_session = login
+ value = URI.encode_www_form_component(@radiko_session)
+
+ auth2_url_param = @radiko_session.size > 0 ? "?radiko_session=#{value}" : ""
+
length = res['X-Radiko-KeyLength'].to_i
offset = res['X-Radiko-KeyOffset'].to_i
partial_key = Base64.strict_encode64(auth_key.byteslice(offset, length))
- res = fetch "https://radiko.jp/v2/api/auth2",
+ res = fetch "https://radiko.jp/v2/api/auth2#{auth2_url_param}",
'X-Radiko-User' => "dummy_user",
'X-Radiko-Device' => "pc",
'X-Radiko-Authtoken' => token,
@@ -106,6 +149,40 @@ class DownloadTask
def now_playing(station, area)
now = Time.now
+
+ station_list = {
+ "802" => "JP25", "ABC" => "JP25", "ABCR-KOSHIEN" => "JP1", "ABS" => "JP5",
+ "AFB" => "JP2", "AFM" => "JP5", "AIR-G" => "JP1", "ALPHA-STATION" => "JP25",
+ "BAYFM78" => "JP8", "BSN" => "JP15", "BSS" => "JP31", "CBC" => "JP21",
+ "CCL" => "JP25", "CRK" => "JP25", "CROSSFM" => "JP40", "CRT" => "JP9",
+ "DATEFM" => "JP4", "E-RADIO" => "JP25", "FBC" => "JP18", "FM-FUJI" => "JP19",
+ "FM-OKAYAMA" => "JP33", "FM-SANIN" => "JP31", "FM807" => "JP36",
+ "FMAICHI" => "JP21", "FMF" => "JP7", "FMFUKUI" => "JP18", "FMFUKUOKA" => "JP40",
+ "FMGIFU" => "JP21", "FMGUNMA" => "JP10", "FMI" => "JP3", "FMJ" => "JP8",
+ "FMK" => "JP43", "FMKAGAWA" => "JP37", "FMMIE" => "JP24", "FMN" => "JP20",
+ "FMNAGASAKI" => "JP42", "FMNIIGATA" => "JP15", "FMO" => "JP25", "FMS" => "JP41",
+ "FMT" => "JP8", "FMTOYAMA" => "JP16", "FMY" => "JP35", "FM_OITA" => "JP44",
+ "FM_OKINAWA" => "JP47", "GBS" => "JP21", "HBC" => "JP1", "HELLOFIVE" => "JP17",
+ "HFM" => "JP34", "HI-SIX" => "JP39", "IBC" => "JP3", "IBS" => "JP8",
+ "INT" => "JP8", "JOAK" => "JP8", "JOAK-FM" => "JP1", "JOBK" => "JP25",
+ "JOCK" => "JP16", "JOEU-FM" => "JP38", "JOFK" => "JP31", "JOHK" => "JP2",
+ "JOIK" => "JP1", "JOLK" => "JP40", "JORF" => "JP8", "JOYFM" => "JP45",
+ "JOZK" => "JP36", "JRT" => "JP36", "K-MIX" => "JP22", "KBC" => "JP40",
+ "KBS" => "JP25", "KISSFMKOBE" => "JP25", "KNB" => "JP16", "KRY" => "JP35",
+ "LFR" => "JP8", "LOVEFM" => "JP40", "MBC" => "JP46", "MBS" => "JP25",
+ "MRO" => "JP17", "MRT" => "JP45", "MYUFM" => "JP46", "NACK5" => "JP8",
+ "NBC" => "JP41", "NORTHWAVE" => "JP1", "OBC" => "JP25", "OBS" => "JP44",
+ "QRR" => "JP8", "RAB" => "JP2", "RADIOBERRY" => "JP9", "RBC" => "JP47",
+ "RCC" => "JP34", "RFC" => "JP7", "RFM" => "JP6", "RKB" => "JP40",
+ "RKC" => "JP39", "RKK" => "JP43", "RN1" => "JP1", "RN2" => "JP1",
+ "RNB" => "JP38", "RNC" => "JP37", "ROK" => "JP47", "RSK" => "JP33",
+ "SBC" => "JP20", "SBS" => "JP22", "STV" => "JP1", "TBC" => "JP4",
+ "TBS" => "JP8", "TOKAIRADIO" => "JP21", "WBS" => "JP30", "YBC" => "JP6",
+ "YBS" => "JP19", "YFM" => "JP8", "ZIP-FM" => "JP21"
+ }
+ # 都道府県コードの変換(自地域以外のラジオ局を指定された場合)
+ area = station_list[station] if station_list[station] != area
+
doc = Nokogiri::XML(URI.parse("https://radiko.jp/v3/program/now/#{area}.xml").open)
node = doc.xpath(%Q|//station[@id="#{station}"]|).first
@@ -209,6 +286,8 @@ class DownloadTask
when "fifo"
FileUtils.rm tempfile
end
+
+ logout
end
def fetch(url, headers)