コンテンツにスキップ

Musixmatch

1 post with the tag “Musixmatch”

Musixmatchの署名

Musixmatch で歌詞を取得する際に、以下のようなパラメータが確認できます。

  • app_id: mxm-com-v1.0
  • usertoken: ${USER_TOKEN}
  • track_spotify_id: XXXXXXXXXXXXXXXXXXX
  • part: track_lyrics_translation_status,lyrics_crowd,user,subtitle_translated,lyrics_translated,attribution,itunes_commontrack_ids,lyrics_verified_by,labels,track_structure,artist,artist_list,file_upladed_list,uploaded_file_list,artist_image_tagged,artist_image
  • signature: ${signature}
  • signature_protocol: sha256

この${signature}を求めることができれば好きに API を叩けそうです。(流石に制限はありそうですが)

signature を求めるコード

import hmac
import hashlib
import base64
from urllib.parse import urlparse, urlunparse, parse_qsl, urlencode
from datetime import datetime, timezone
def reverse_and_decode_base64(encoded_str: str) -> bytes:
reversed_str = encoded_str[::-1]
try:
decoded_bytes = base64.b64decode(reversed_str)
return decoded_bytes
except Exception as e:
raise
def generate_signature(
url: str,
default_client_key_reversed_base64: str = "=UjMilTNwczNwEDO4UjZ5QjYjZjMlBDNmZjZ4QjM1cjN",
) -> str:
client_key = reverse_and_decode_base64(
default_client_key_reversed_base64
) # -> b'675248f6f40e26cb49f5881077059b25'
parsed_url = urlparse(url)
query_list = parse_qsl(parsed_url.query, keep_blank_values=True)
clean_query_list = [
(k, v) for k, v in query_list if k not in ("signature", "signature_protocol")
]
clean_query = urlencode(clean_query_list, doseq=True)
clean_url = urlunparse(
(
parsed_url.scheme,
parsed_url.netloc,
parsed_url.path,
parsed_url.params,
clean_query,
parsed_url.fragment,
)
)
now = datetime.now(timezone.utc)
year = now.strftime("%Y")
month = now.strftime("%m")
day = now.strftime("%d")
message = f"{clean_url}{year}{month}{day}"
hmac_message = message.encode("utf-8")
signature = hmac.new(client_key, hmac_message, hashlib.sha256).digest()
signature_b64 = base64.b64encode(signature).decode("utf-8")
signed_query_list = clean_query_list + [
("signature", signature_b64),
("signature_protocol", "sha256"),
]
signed_query = urlencode(signed_query_list, doseq=True)
signed_url = urlunparse(
(
parsed_url.scheme,
parsed_url.netloc,
parsed_url.path,
parsed_url.params,
signed_query,
parsed_url.fragment,
)
)
return signed_url

どうでもいい話

何かしらの曲を再生した際にhttps://www.musixmatch.com/ws/1.1/track.get?app_id=...へのリクエストがあるはずです。

ブラウザ > DevTools > ネットワーク > track.get?app_id=... > イニシエータ > 一番上の js ファイル

に以下のような記述があります。

1
u = i.from("=UjMilTNwczNwEDO4UjZ5QjYjZjMlBDNmZjZ4QjM1cjN".split("").reverse().join(""), "base64").toString()
2
CLIENT_KEY: u,
3
4
var tQ = (et = (0,
5
tA.Z)(tj().mark(function e(t) {
6
return tj().wrap(function(e) {
7
for (; ; )
8
switch (e.prev = e.next) {
9
case 0:
10
if (!tR.Nq) {
11
e.next = 4;
12
break
13
}
14
return e.abrupt("return", crypto.subtle.importKey("raw", new TextEncoder().encode(t), {
15
hash: {
16
name: "SHA-256"
17
},
18
name: "HMAC"
19
}, !1, ["sign"]));
20
case 4:
21
case "end":
22
return e.stop()
23
}
24
}, e)
25
})),
26
function(e) {
27
return et.apply(this, arguments)
28
}
29
)
30
, tJ = (en = (0,
31
tA.Z)(tj().mark(function e(t, n) {
32
var r;
33
return tj().wrap(function(e) {
34
for (; ; )
35
switch (e.prev = e.next) {
36
case 0:
37
if (!tR.Nq) {
38
e.next = 7;
39
break
40
}
41
return e.next = 3,
42
crypto.subtle.sign("HMAC", t, new TextEncoder().encode(n));
43
case 3:
44
return r = e.sent,
45
e.abrupt("return", btoa(String.fromCharCode.apply(null, new Uint8Array(r))));
46
case 7:
47
return e.abrupt("return", "");
48
case 8:
49
case "end":
50
return e.stop()
51
}
52
}, e)
53
})),
54
function(e, t) {
55
return en.apply(this, arguments)
56
}
57
)
58
, t0 = void 0
59
, t1 = (er = (0,
60
tA.Z)(tj().mark(function e(t, n) {
61
var r, i, o, a, s, u;
62
return tj().wrap(function(e) {
63
for (; ; )
64
switch (e.prev = e.next) {
65
case 0:
66
if (r = new URL(t),
67
t0) {
68
e.next = 5;
69
break
70
}
71
return e.next = 4,
72
tQ(n);
73
case 4:
74
t0 = e.sent;
75
case 5:
76
return o = (i = new Date).getUTCFullYear().toString(),
77
a = tX(i.getUTCMonth() + 1),
78
s = tX(i.getUTCDate()),
79
r.searchParams.delete("signature"),
80
r.searchParams.delete("signature_protocol"),
81
e.next = 13,
82
tJ(t0, r.toString() + o + a + s);
83
case 13:
84
return u = e.sent,
85
r.searchParams.set("signature", u),
86
r.searchParams.set("signature_protocol", "sha256"),
87
e.abrupt("return", r.toString());
88
case 17:
89
case "end":
90
return e.stop()
91
}
92
}, e)
93
})),

そのため

  • client_key: "=UjMilTNwczNwEDO4UjZ5QjYjZjMlBDNmZjZ4QjM1cjN"を逆順に並び替え、Base64 デコードしたもの
  • clean_url: リクエスト URL からクエリパラメータsignaturesignature_protocolを削除したもの
  • hmac_message: 現在の UTC 日付を取得し、次のように各要素を連結"{clean_url}{year}{month}{day}"
  • signature: client_keyにてhmac_messageを HMAC-SHA256 署名し、Base64 エンコード

という手順で signature を形成していると考えられます。(後出しジャンケン)