const API_BASE_URL = "https://flac.music.hi.cn"
const { EVENT_NAMES, request, on, send, utils, env, version } = globalThis.lx
let globalCookie = 'sl-session=cfvqX25YrmjOIuvFqDrbpQ==; sl-challenge-server=cloud; sl_jwt_session=cpF4eqtnrWghBeqsta5jqA==; sl_jwt_sign='
let cookieUpdateTimer = null
function log(...args) {
console.log(...args);
if (env !== 'mobile') return;
}
function calculateArray(input) {
let t = 1;
const sum = input.reduce((acc, val) => acc + val, 0);
let cycles = (6 + input.length + sum) % 6 + 6;
while (cycles--) t *= 6;
if (t < 6666) t *= input.length;
if (t > 0x3f940aa) t = Math.floor(t / input.length);
for (let i = 0; i < input.length; i++) {
t += Math.pow(input[i], 3);
t ^= i;
t ^= input[i] + i;
}
const result = [];
while (t > 0) {
result.unshift(t & 63);
t >>= 6;
}
return result;
}
function customRequest(url, options) {
return new Promise((resolve, reject) => {
request(url, options, (err, resp) => {
if (err) return reject(err);
if ((resp.statusCode >= 200 && resp.statusCode < 300) || resp.statusCode === 468) {
resolve(resp);
} else {
reject(new Error(`请求失败,状态码: ${resp.statusCode}, 响应: ${JSON.stringify(resp)}`));
}
});
});
}
async function generateCookie() {
try {
log("第一步获取初始页面...");
const response1 = await customRequest('https://flac.music.hi.cn/', {
method: 'GET',
headers: {
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'accept-language': 'zh-CN,zh;q=0.9',
}
});
log('获取初始页面响应状态码:', response1.statusCode);
let cookies = response1.headers['set-cookie'];
cookies = Array.isArray(cookies) ? cookies : [cookies]
let slSession = '';
if (cookies) {
for (const cookie of cookies) {
const match = cookie.match(/sl-session=([^;]+)/);
if (match) {
slSession = match[1];
break;
}
}
}
let key = '';
if (response1.body) {
const keyMatch = response1.body.match(/SafeLineChallenge\("([^"]+)"/);
if (keyMatch) {
key = keyMatch[1];
}
}
log('提取到的sl-session: ', slSession);
log('提取到的key: ', key);
if (!slSession || !key) {
throw new Error('无法提取sl-session或key');
}
log('第二步请求Issue...');
const response2 = await customRequest(
'https://challenge.rivers.chaitin.cn/challenge/v2/api/issue',
{
method: 'POST',
headers: {
'Accept': '*/*',
'Content-Type': 'application/json',
'Origin': 'https://flac.music.hi.cn',
'Referer': 'https://flac.music.hi.cn/',
},
body: {"client_id":key,"level":1}
}
);
log('请求issue响应状态码:', response2.statusCode);
let issueData = response2.body.data;
log('获取到的issue数据:', issueData);
const result = calculateArray(issueData.data);
log('第三步计算结果: ', result);
log("第四步验证结果...");
const response3 = await customRequest('https://challenge.rivers.chaitin.cn/challenge/v2/api/verify', {
method: 'POST',
headers: {
'Accept': '*/*',
'Content-Type': 'application/json',
'Origin': 'https://flac.music.hi.cn',
'Referer': 'https://flac.music.hi.cn/',
},
body: {
issue_id: issueData.issue_id,
result: result,
serials: [],
client: {
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
platform: 'Win32',
language: 'zh-CN',
vendor: 'Google Inc.',
screen: [1920, 1080],
visitorId: '99999999999999999999999999999999',
score: 0,
target: []
}
}
});
log('第四步响应状态码: ', response3.statusCode);
let jwt = response3.body.data.jwt;
log('获取到的JWT: ', jwt);
log("第五步获取最终 cookie...");
const response4 = await customRequest('https://flac.music.hi.cn/', {
method: 'GET',
headers: {
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'accept-language': 'zh-CN,zh;q=0.9',
'cookie': `sl-session=${slSession}; sl-challenge-server=cloud; sl-challenge-jwt=${jwt}`,
'referer': 'https://flac.music.hi.cn/',
}
});
log('第五步响应状态码:', response4.statusCode);
let finalCookies = response4.headers['set-cookie'];
if (env === 'mobile') finalCookies = finalCookies.split(";");
log("返回的Cookies: ", JSON.stringify(finalCookies));
let slJwtSession = '';
let slJwtSign = '';
let slChallengeJwt = '';
if (finalCookies) {
for (const cookie of finalCookies) {
if (cookie.includes('sl_jwt_session=')) {
const match = cookie.match(/sl_jwt_session=([^;]+)/);
if (match) slJwtSession = match[1];
} else if (cookie.includes('sl_jwt_sign=')) {
const match = cookie.match(/sl_jwt_sign=([^;]+)/);
if (match) slJwtSign = match[1];
} else if (cookie.includes('sl-challenge-jwt=')) {
const match = cookie.match(/sl-challenge-jwt=([^;]+)/);
if (match) slChallengeJwt = match[1];
}
}
}
log('提取的sl_jwt_session:', slJwtSession);
log('提取的sl_jwt_sign:', slJwtSign);
log('提取的sl-challenge-jwt:', slChallengeJwt);
const finalCookie = `sl-session=${slSession};sl-challenge-server=cloud;sl_jwt_session=${slJwtSession};sl_jwt_sign=${slJwtSign};sl-challenge-jwt=${slChallengeJwt}`;
log('生成的新cookie: ', finalCookie);
return finalCookie;
} catch (error) {
log('生成cookie过程中出错: ', error.message, "堆栈: ", error.stack);
throw error;
}
}
async function initCookie() {
try {
globalCookie = await generateCookie();
log('Cookie初始化成功');
if (cookieUpdateTimer) {
clearInterval(cookieUpdateTimer);
}
cookieUpdateTimer = setInterval(async () => {
try {
globalCookie = await generateCookie();
log('Cookie定时更新成功');
} catch (error) {
log('Cookie定时更新失败:', error.message);
}
}, 60 * 60 * 1000);
} catch (error) {
log('Cookie初始化失败,使用默认cookie:', error.message, "堆栈: ", error.stack);
}
}
initCookie();
const httpFetch = (url, body) => new Promise((resolve, reject) => {
log("发送请求 " + url + ", 参数: " + body);
request(
url,
{
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Origin': API_BASE_URL,
'X-Requested-With': 'XMLHttpRequest',
'Cookie': globalCookie,
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
},
body: body,
},
(err, resp) => {
if (err) {
log("请求错误: ", err);
return reject(err)
}
log("服务器返回: ", resp);
if (resp.statusCode === 468) {
initCookie();
reject("Session过期, 重新获取");
}
resolve(resp.body)
}
)
})
function getItem(list, info) {
let filter = list.filter(item => item.id === info.musicInfo.songmid);
if (filter.length > 0) return filter[0];
filter = list.filter(item =>
item.artist.toLowerCase() === info.musicInfo.singer.toLowerCase()
&& item.name.toLowerCase() === info.musicInfo.name.toLowerCase()
);
if (filter.length > 0) return filter[0];
filter = list.filter(item => item.artist.toLowerCase() === info.musicInfo.singer.toLowerCase());
if (filter.length > 0) return filter[0];
filter = list.filter(item => item.name.toLowerCase() === info.musicInfo.name.toLowerCase());
if (filter.length > 0) return filter[0];
return list[0];
}
const mapBitrate = {
'128k': '128',
'320k': '320',
flac: '2000',
flac24bit: '2000',
}
const mapFormat = {
'128k': 'mp3',
'320k': 'mp3',
flac: 'flac',
flac24bit: 'flac',
}
const musicUrl = async(source, info) => {
log("运行 musicUrl: ", source, info);
let songid = "";
if (source == "kw") {
songid = info.musicInfo.songmid;
} else {
const key = encodeURIComponent(info.musicInfo.name + info.musicInfo.singer)
const url = `${API_BASE_URL}/ajax.php?act=search`
const response = await httpFetch(url, `keyword=${key}&page=1&size=30`);
if (response.code === 0) {
const item = getItem(response.data.list, info);
log("获取链接: ", item);
songid = item.id;
} else {
return Promise.reject("搜索失败");
}
}
const url = `${API_BASE_URL}/ajax.php?act=getUrl`
const resp = await httpFetch(url, `songid=${songid}&format=${mapFormat[info.type]}&bitrate=${mapBitrate[info.type]}`);
if (resp.code === 0) {
return Promise.resolve(resp.data.url);
} else {
return Promise.reject("获取歌曲地址失败");
}
}
const lyricUrl = async(source, info) => {
log("运行 lyricUrl", info);
return Promise.resolve('')
}
const picUrl = async(source, info) => {
log("运行 picUrl", info);
return Promise.resolve("")
}
on(EVENT_NAMES.request, ({ source, action, info }) => {
switch (action) {
case 'musicUrl':
return musicUrl(source, info)
.then(data => Promise.resolve(data))
.catch(err => Promise.reject(err))
case 'lyric':
return lyricUrl(source, info)
.then(data => Promise.resolve(data))
.catch(err => Promise.reject(err))
case 'pic':
return picUrl(source, info)
.then(data => Promise.resolve(data))
.catch(err => Promise.reject(err))
default:
error(`action(${action}) not support`)
return Promise.reject('action not support')
}
})
send(EVENT_NAMES.inited, {
status: true,
openDevTools: false,
sources: {
kw: {
name: '酷我音乐',
type: 'music',
actions: ['musicUrl'],
qualitys: ['128k', '320k', 'flac', 'flac24bit'],
},
kg: {
name: '酷狗音乐',
type: 'music',
actions: ['musicUrl'],
qualitys: ['128k', '320k', 'flac', 'flac24bit'],
},
tx: {
name: 'QQ音乐',
type: 'music',
actions: ['musicUrl'],
qualitys: ['128k', '320k', 'flac', 'flac24bit'],
},
wy: {
name: '网易云音乐',
type: 'music',
actions: ['musicUrl'],
qualitys: ['128k', '320k', 'flac', 'flac24bit'],
},
mg: {
name: '咪咕音乐',
type: 'music',
actions: ['musicUrl'],
qualitys: ['128k', '320k', 'flac', 'flac24bit'],
},
local: {
name: '本地音乐',
type: 'music',
actions: ['musicUrl', 'lyric', 'pic'],
qualitys: [],
}
}
})