|
import crypto from 'node:crypto' |
|
|
|
import config from '../../config.js' |
|
import { debugLog, makeRequest } from '../utils.js' |
|
|
|
function _getGuid() { |
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (character) => { |
|
const random = 16 * Math.random() | 0 |
|
const value = character === 'x' ? random : 3 & random | 8 |
|
|
|
return value.toString(16) |
|
}) |
|
} |
|
|
|
let sourceInfo = { |
|
guid: null |
|
} |
|
|
|
function init() { |
|
sourceInfo.guid = _getGuid() |
|
|
|
debugLog('musixmatch', 5, { message: `New guid: ${sourceInfo.guid}` }) |
|
} |
|
|
|
function _signUrl(url) { |
|
const time = new Date |
|
const year = time.getUTCFullYear() |
|
let month = time.getUTCMonth() + 1 |
|
month < 10 && (month = '0' + month) |
|
let day = time.getUTCDate() |
|
day < 10 && (day = '0' + day) |
|
|
|
let superKey = crypto.createHmac('sha1', config.search.sources.musixmatch.signatureSecret) |
|
superKey = superKey.update(url + year + month + day) |
|
superKey = superKey.digest('base64') |
|
|
|
return url + `&signature=${encodeURIComponent(superKey)}&signature_protocol=sha1` |
|
} |
|
|
|
function _normalizeLanguage(language) { |
|
switch (language) { |
|
case 'ara': return 'ar' |
|
case 'afr': return 'af' |
|
case 'ind': return 'id' |
|
case 'kan': return 'kn' |
|
case 'kor': return 'ko' |
|
case 'rkr': return 'rk' |
|
case 'zho': return 'zh' |
|
case 'rz0': return 'rz' |
|
case 'zht': return 'z1' |
|
case 'ces': return 'cs' |
|
case 'deu': return 'de' |
|
case 'nld': return 'nl' |
|
case 'spa': return 'es' |
|
case 'dan': return 'da' |
|
case 'ell': return 'el' |
|
case 'eng': return 'en' |
|
case 'fas': return 'fa' |
|
case 'fin': return 'fi' |
|
case 'fra': return 'fr' |
|
case 'heb': return 'he' |
|
case 'hin': return 'hi' |
|
case 'ita': return 'it' |
|
case 'jpn': return 'ja' |
|
case 'hun': return 'hu' |
|
case 'rja': return 'rj' |
|
case 'nor': return 'no' |
|
case 'pol': return 'pl' |
|
case 'por': return 'pt' |
|
case 'pt-br': return 'pt-br' |
|
case 'rus': return 'ru' |
|
case 'ron': return 'ro' |
|
case 'tur': return 'tr' |
|
case 'lit': return 'lt' |
|
case 'mas': return 'ms' |
|
case 'mkd': return 'mk' |
|
case 'sqi': return 'sq' |
|
case 'hye': return 'hy' |
|
case 'aze': return 'az' |
|
case 'ben': return 'bn' |
|
case 'bos': return 'bs' |
|
case 'bul': return 'bg' |
|
case 'hrv': return 'hr' |
|
case 'est': return 'et' |
|
case 'fil': return 'f1' |
|
case 'kat': return 'ka' |
|
case 'hat': return 'ht' |
|
case 'isl': return 'is' |
|
case 'kaz': return 'kk' |
|
case 'kir': return 'ky' |
|
case 'lao': return 'lo' |
|
case 'lav': return 'lv' |
|
case 'mon': return 'mn' |
|
case 'msa': return 'ms' |
|
case 'nep': return 'ne' |
|
case 'pan': return 'pa' |
|
case 'srp': return 'sr' |
|
case 'slk': return 'sk' |
|
case 'slv': return 'sl' |
|
case 'swe': return 'se' |
|
case 'tha': return 'th' |
|
case 'ukr': return 'uk' |
|
case 'uzb': return 'uz' |
|
case 'vie': return 'vi' |
|
} |
|
} |
|
|
|
async function search(query) { |
|
init() |
|
const { body: data } = await makeRequest(_signUrl(`https://www.musixmatch.com/ws/1.1/macro.search?app_id=community-app-v1.0&part=track_artist,track_lyrics_translation_status&guid=${sourceInfo.guid}&format=json&q=${encodeURIComponent(query)}&page_size=1`), { |
|
method: 'GET' |
|
}) |
|
|
|
return data.message.body.macro_result_list.track_list[0].track |
|
} |
|
|
|
async function loadLyrics(decodedTrack, language) { |
|
const searchResults = await search(`${decodedTrack.title} ${decodedTrack.artist}`) |
|
|
|
if (!searchResults) return null |
|
|
|
const { body: data } = await makeRequest(_signUrl(`https://www.musixmatch.com/ws/1.1/track.lyrics.get?page_size=100&page=1&commontrack_id=${searchResults.commontrack_id}&format=json&app_id=community-app-v1.0&guid=${sourceInfo.guid}`), { |
|
method: 'GET' |
|
}) |
|
|
|
const lyricsEvents = data.message.body.lyrics.lyrics_body.split('\n').map((text) => { |
|
return { |
|
text |
|
} |
|
}) |
|
|
|
if (language && language !== searchResults.lyrics_language && searchResults.track_lyrics_translation_status.find((status) => _normalizeLanguage(status.to) === language)) { |
|
const { body: data } = await makeRequest(_signUrl(`https://www.musixmatch.com/ws/1.1/crowd.track.translations.get?page_size=1&selected_language=${language}&track_id=${searchResults.track_id}&format=json&app_id=community-app-v1.0&guid=${sourceInfo.guid}`), { |
|
method: 'GET' |
|
}) |
|
|
|
lyricsEvents.forEach((text, i) => { |
|
if (text.text === '') return; |
|
|
|
data.message.body.translations_list.forEach((translation) => { |
|
if (text.text && text.text.replace(/′/, '\'') === translation.translation.matched_line) { |
|
lyricsEvents[i].text = translation.translation.description |
|
} |
|
}) |
|
}) |
|
} else { |
|
language = searchResults.lyrics_language |
|
} |
|
|
|
return { |
|
loadType: 'lyricsSingle', |
|
data: { |
|
name: language, |
|
synced: false, |
|
data: lyricsEvents, |
|
rtl: false |
|
} |
|
} |
|
} |
|
|
|
export default { |
|
init, |
|
loadLyrics |
|
} |