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 hmacimport hashlibimport base64from urllib.parse import urlparse, urlunparse, parse_qsl, urlencodefrom 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 ファイル
に以下のような記述があります。
1u = i.from("=UjMilTNwczNwEDO4UjZ5QjYjZjMlBDNmZjZ4QjM1cjN".split("").reverse().join(""), "base64").toString()2CLIENT_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 break13 }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 break40 }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 059 , 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 break70 }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 からクエリパラメータsignature
とsignature_protocol
を削除したものhmac_message
: 現在の UTC 日付を取得し、次のように各要素を連結"{clean_url}{year}{month}{day}"
signature
:client_key
にてhmac_message
を HMAC-SHA256 署名し、Base64 エンコード
という手順で signature
を形成していると考えられます。(後出しジャンケン)