radiko録音(ダウンロード)への道 その2

Step2.トークン認証

ここはちょっと面倒くさいです。

トークン認証するには、https://radiko.jp/v2/api/auth2 に以下のHeaderでGETします。

X-Radiko-AuthToken: Step1で取得したX-Radiko-AuthToken
X-Radiko-PartialKey: 何らかのデータ
X-Radiko-Device: pc
X-Radiko-User: dummy_user

さて、ここで問題となるのが”X-Radiko-PartialKey”で、これがないとトークン認証できません

X-Radiko-PartialKeyを求めるには、http://radiko.jp/apps/js/radikoJSPlayer.jsとhttp://radiko.jp/apps/js/playerCommon.jsにヒントが隠されています。

playerCommon.jsをよく見てみると…送っているHeader情報が見えますね
(Step1も同じファイルに情報があります)

  function (t, e, n) {
    "use strict";
    Object.defineProperty(e, "__esModule", {
      value: !0
    }), n(90);
    var r = n(243),
      a = function () {
        function t(t, e) {
          this._authenticated = !1, this._appId = t, this._authKey = e, this._device = r.default.getDevice()
        }
        return t.prototype.auth1 = function () {
          var t = new Headers({
            "X-Radiko-App": this._appId,
            "X-Radiko-App-Version": "0.0.1",
            "X-Radiko-User": "dummy_user",
            "X-Radiko-Device": this._device
          });
          return fetch("https://" + location.host + "/v2/api/auth1", {
            headers: t,
            method: "get",
            credentials: "include"
          }).catch(function () {
            console.log("auth1 error")
          })
        }, t.prototype.auth2 = function () {
          var t = new Headers({
            "X-Radiko-AuthToken": this._token,
            "X-Radiko-Partialkey": <strong>this._partialKey</strong>,
            "X-Radiko-User": "dummy_user",
            "X-Radiko-Device": this._device
          });
          return fetch("https://" + location.host + "/v2/api/auth2", {
            headers: t,
            method: "get",
            credentials: "include"
          }).catch(function () {
            console.log("auth2 error")
          })
        }, t.createPartialkey = function (t, e, n) {
          for (var r = new Uint8Array(t, e, n), a = "", i = 0; i < r.length; i++) a += String.fromCharCode(r[i]);
          return btoa(a)
        }, Object.defineProperty(t.prototype, "areaId", {
          get: function () {
            return this._areaId
          },
          enumerable: !0,
          configurable: !0
        }), Object.defineProperty(t.prototype, "areaNameKanji", {
          get: function () {
            return this._areaNameKanji
          },
          enumerable: !0,
          configurable: !0
        }), Object.defineProperty(t.prototype, "areaNameEn", {
          get: function () {
            return this._areaNameEn
          },
          enumerable: !0,
          configurable: !0
        }), Object.defineProperty(t.prototype, "authenticated", {
          get: function () {
            return this._authenticated
          },
          enumerable: !0,
          configurable: !0
        }), Object.defineProperty(t.prototype, "token", {
          get: function () {
            return this._token
          },
          enumerable: !0,
          configurable: !0
        }), t.prototype.request = function () {
          var e = this;
          return this.auth1().then(function (n) {
            var a = n.headers.get("x-radiko-authtoken"),
              i = n.headers.get("x-radiko-keyoffset"),
              s = n.headers.get("x-radiko-keylength");
            return null == a || null == i || null == s ? void console.log("not found playlist_create_url") : (e._token = a, e._partialKey = t.createPartialkey(r.default.str2Buffer(e._authKey), +i, +s), e.auth2())
          }).then(function (t) {
            return t.text()
          }).then(function (t) {
            t = t.trim();
            var n = t.split(","),
              r = n[0],
              a = n[1],
              i = n[2];
            return e._areaId = r, e._areaNameKanji = a, e._areaNameEn = i, e._authenticated = !0, !0
          })
        }, t
      }
      ();
    e.default = a
  },

必要なところだけ抜き出すと、

i = n.headers.get("x-radiko-keyoffset");
s = n.headers.get("x-radiko-keylength");

e._partialKey = t.createPartialkey(r.default.str2Buffer(e._authKey), +i, +s);


t.createPartialkey = function (t, e, n) {
	for (var r = new Uint8Array(t, e, n), a = "", i = 0; i < r.length; i++) a += String.fromCharCode(r[i]);
		return btoa(a);
	}
}

“_authKey”のi文字目からs文字を抜き出して、Base64エンコードしていることがわかります
※なんでわざわざ文字列をバイト列にして抜き出し(Uint8Array)てから、再度文字列に戻してる(String.fromCharCode)んですかね???

じゃあ、肝心の_authkeyはというと、playerCommon.jsに答えがあります

            player = new RadikoJSPlayer($audio[0], 'pc_html5', 'bcd151073c03b352e1ef2fd66c32209da9ca0afa', {
                onPlayStateChange: onPlayStateChange,
                onCurrentTimeChange: onCurrentTimeChange,
                onFragmentPlaying: onFragmentPlaying,
                onLoadStateReady: onLoadStateReady,
                onLoadStateError: onLoadStateError,
                onAuthFailure: onAuthFailure,
                isRadikoAreafree: isRadikoAreafree,
                isStationInArea: isStationInArea,
                onHLSPlaybackComplete: onHLSPlaybackComplete
            });

_authkeyは’bcd151073c03b352e1ef2fd66c32209da9ca0afa’です!

Step1で取得した、X-Radiko-KeyLength: 16、X-Radiko-KeyOffset: 22から、
X-Radiko-PartialKeyは、’d66c32209da9ca0a’をBASE64エンコードした、’ZDY2YzMyMjA5ZGE5Y2EwYQ==’となりました。

curlでGETすると、

curl -c cookie.txt -H 'X-Radiko-AuthToken: Zv-wZAx3hMCFEQ7dz-okdA' 
 -H 'X-Radiko-PartialKey: ZDY2YzMyMjA5ZGE5Y2EwYQ==' 
 -H 'X-Radiko-User: dummy_user' 
 -H 'X-Radiko-Device: pc' 
 -I -L https://radiko.jp/v2/api/auth2

トークン認証成功すれば、HTTP200が帰ってきます(失敗は401)

HTTP/1.1 200 OK
Server: nginx
Date: Sat, 23 Jun 2018 08:31:55 GMT
Content-Type: text/plain
Connection: keep-alive
Access-Control-Expose-Headers: X-Radiko-AuthToken, X-Radiko-Partialkey, X-Radiko-AppType, X-Radiko-AuthWait, X-Radiko-Delay, X-Radiko-KeyLength, X-Radiko-KeyOffset
Access-Control-Allow-Credentials: true

とりあえずここまで
次はダウンロードです。