nodelink / src /sources /musixmatch.js
flameface's picture
Upload 25 files
b58c6cb verified
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
}