205 lines
5.8 KiB
JavaScript
205 lines
5.8 KiB
JavaScript
const ProviderNetease = (() => {
|
||
const requestHeader = {
|
||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:93.0) Gecko/20100101 Firefox/93.0"
|
||
};
|
||
|
||
async function findLyrics(info) {
|
||
const searchURL = "https://music.xianqiao.wang/neteaseapiv2/search?limit=10&type=1&keywords=";
|
||
const lyricURL = "https://music.xianqiao.wang/neteaseapiv2/lyric?id=";
|
||
|
||
const cleanTitle = Utils.removeExtraInfo(Utils.removeSongFeat(Utils.normalize(info.title)));
|
||
const finalURL = searchURL + encodeURIComponent(`${cleanTitle} ${info.artist}`);
|
||
|
||
const searchResults = await Spicetify.CosmosAsync.get(finalURL, null, requestHeader);
|
||
const items = searchResults.result.songs;
|
||
if (!items?.length) {
|
||
throw "Cannot find track";
|
||
}
|
||
|
||
// normalized expected album name
|
||
const neAlbumName = Utils.normalize(info.album);
|
||
const expectedAlbumName = Utils.containsHanCharacter(neAlbumName) ? await Utils.toSimplifiedChinese(neAlbumName) : neAlbumName;
|
||
let itemId = items.findIndex(val => Utils.normalize(val.album.name) === expectedAlbumName);
|
||
if (itemId === -1) itemId = items.findIndex(val => Math.abs(info.duration - val.duration) < 3000);
|
||
if (itemId === -1) itemId = items.findIndex(val => val.name === cleanTitle);
|
||
if (itemId === -1) throw "Cannot find track";
|
||
|
||
return await Spicetify.CosmosAsync.get(lyricURL + items[itemId].id, null, requestHeader);
|
||
}
|
||
|
||
const creditInfo = [
|
||
"\\s?作?\\s*词|\\s?作?\\s*曲|\\s?编\\s*曲?|\\s?监\\s*制?",
|
||
".*编写|.*和音|.*和声|.*合声|.*提琴|.*录|.*工程|.*工作室|.*设计|.*剪辑|.*制作|.*发行|.*出品|.*后期|.*混音|.*缩混",
|
||
"原唱|翻唱|题字|文案|海报|古筝|二胡|钢琴|吉他|贝斯|笛子|鼓|弦乐",
|
||
"lrc|publish|vocal|guitar|program|produce|write|mix"
|
||
];
|
||
const creditInfoRegExp = new RegExp(`^(${creditInfo.join("|")}).*(:|:)`, "i");
|
||
|
||
function containCredits(text) {
|
||
return creditInfoRegExp.test(text);
|
||
}
|
||
|
||
function parseTimestamp(line) {
|
||
// ["[ar:Beyond]"]
|
||
// ["[03:10]"]
|
||
// ["[03:10]", "lyrics"]
|
||
// ["lyrics"]
|
||
// ["[03:10]", "[03:10]", "lyrics"]
|
||
// ["[1235,300]", "lyrics"]
|
||
const matchResult = line.match(/(\[.*?\])|([^\[\]]+)/g);
|
||
if (!matchResult?.length || matchResult.length === 1) {
|
||
return { text: line };
|
||
}
|
||
|
||
const textIndex = matchResult.findIndex(slice => !slice.endsWith("]"));
|
||
let text = "";
|
||
|
||
if (textIndex > -1) {
|
||
text = matchResult.splice(textIndex, 1)[0];
|
||
text = Utils.capitalize(Utils.normalize(text, false));
|
||
}
|
||
|
||
const time = matchResult[0].replace("[", "").replace("]", "");
|
||
|
||
return { time, text };
|
||
}
|
||
|
||
function breakdownLine(text) {
|
||
// (0,508)Don't(0,1) (0,151)want(0,1) (0,162)to(0,1) (0,100)be(0,1) (0,157)an(0,1)
|
||
const components = text.split(/\(\d+,(\d+)\)/g);
|
||
// ["", "508", "Don't", "1", " ", "151", "want" , "1" ...]
|
||
const result = [];
|
||
for (let i = 1; i < components.length; i += 2) {
|
||
if (components[i + 1] === " ") continue;
|
||
result.push({
|
||
word: `${components[i + 1]} `,
|
||
time: Number.parseInt(components[i])
|
||
});
|
||
}
|
||
return result;
|
||
}
|
||
|
||
function getKaraoke(list) {
|
||
const lyricStr = list?.klyric?.lyric;
|
||
|
||
if (!lyricStr) {
|
||
return null;
|
||
}
|
||
|
||
const lines = lyricStr.split(/\r?\n/).map(line => line.trim());
|
||
const karaoke = lines
|
||
.map(line => {
|
||
const { time, text } = parseTimestamp(line);
|
||
if (!time || !text) return null;
|
||
|
||
const [key, value] = time.split(",") || [];
|
||
const [start, durr] = [Number.parseFloat(key), Number.parseFloat(value)];
|
||
|
||
if (!Number.isNaN(start) && !Number.isNaN(durr) && !containCredits(text)) {
|
||
return {
|
||
startTime: start,
|
||
// endTime: start + durr,
|
||
text: breakdownLine(text)
|
||
};
|
||
}
|
||
return null;
|
||
})
|
||
.filter(Boolean);
|
||
|
||
if (!karaoke.length) {
|
||
return null;
|
||
}
|
||
|
||
return karaoke;
|
||
}
|
||
|
||
function getSynced(list) {
|
||
const lyricStr = list?.lrc?.lyric;
|
||
let noLyrics = false;
|
||
|
||
if (!lyricStr) {
|
||
return null;
|
||
}
|
||
|
||
const lines = lyricStr.split(/\r?\n/).map(line => line.trim());
|
||
const lyrics = lines
|
||
.map(line => {
|
||
const { time, text } = parseTimestamp(line);
|
||
if (text === "纯音乐, 请欣赏") noLyrics = true;
|
||
if (!time || !text) return null;
|
||
|
||
const [key, value] = time.split(":") || [];
|
||
const [min, sec] = [Number.parseFloat(key), Number.parseFloat(value)];
|
||
if (!Number.isNaN(min) && !Number.isNaN(sec) && !containCredits(text)) {
|
||
return {
|
||
startTime: (min * 60 + sec) * 1000,
|
||
text: text || ""
|
||
};
|
||
}
|
||
return null;
|
||
})
|
||
.filter(Boolean);
|
||
|
||
if (!lyrics.length || noLyrics) {
|
||
return null;
|
||
}
|
||
return lyrics;
|
||
}
|
||
|
||
function getTranslation(list) {
|
||
const lyricStr = list?.tlyric?.lyric;
|
||
|
||
if (!lyricStr) {
|
||
return null;
|
||
}
|
||
|
||
const lines = lyricStr.split(/\r?\n/).map(line => line.trim());
|
||
const translation = lines
|
||
.map(line => {
|
||
const { time, text } = parseTimestamp(line);
|
||
if (!time || !text) return null;
|
||
|
||
const [key, value] = time.split(":") || [];
|
||
const [min, sec] = [Number.parseFloat(key), Number.parseFloat(value)];
|
||
if (!Number.isNaN(min) && !Number.isNaN(sec) && !containCredits(text)) {
|
||
return {
|
||
startTime: (min * 60 + sec) * 1000,
|
||
text: text || ""
|
||
};
|
||
}
|
||
return null;
|
||
})
|
||
.filter(Boolean);
|
||
|
||
if (!translation.length) {
|
||
return null;
|
||
}
|
||
return translation;
|
||
}
|
||
|
||
function getUnsynced(list) {
|
||
const lyricStr = list?.lrc?.lyric;
|
||
let noLyrics = false;
|
||
|
||
if (!lyricStr) {
|
||
return null;
|
||
}
|
||
|
||
const lines = lyricStr.split(/\r?\n/).map(line => line.trim());
|
||
const lyrics = lines
|
||
.map(line => {
|
||
const parsed = parseTimestamp(line);
|
||
if (parsed.text === "纯音乐, 请欣赏") noLyrics = true;
|
||
if (!parsed.text || containCredits(parsed.text)) return null;
|
||
return parsed;
|
||
})
|
||
.filter(Boolean);
|
||
|
||
if (!lyrics.length || noLyrics) {
|
||
return null;
|
||
}
|
||
return lyrics;
|
||
}
|
||
|
||
return { findLyrics, getKaraoke, getSynced, getUnsynced, getTranslation };
|
||
})();
|