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も同じファイルに情報があります)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
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
},

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

1
2
3
4
5
6
7
8
9
10
11
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に答えがあります

1
2
3
4
5
6
7
8
9
10
11
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

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