首页  编辑  

无限制洛雪高清音源

Tags: /计算机文档/软件应用技巧/   Date Created:
洛雪高清,无限制,免费,免注册音源,基于 无损音乐下载 开发。点击下载 %E6%97%A0%E6%8D%9F%E9%9F%B3%E6%BA%902.js

/*!
 * @name 高品质无损音乐音源
 * @description 基于flac.music.hi.cn API的音源服务
 * @version 1.0
 * @author Kingron
 * @homepage https://flac.music.hi.cn/
*/

const API_BASE_URL = "https://flac.music.hi.cn"
const { EVENT_NAMES, request, on, send, utils, env, version } = globalThis.lx

// 全局cookie存储
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;

	// send(EVENT_NAMES.updateAlert, { 
      // log: args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : String(arg)).join(', '),
      // updateUrl: '' 
    // })
}

function safeEval(code) {
    // 1. 提取所有添加到cbk_var开头的字符串
    const regex = /cbk_var\s*=\s*['"]([^'"]+)['"]\s*\+\s*cbk_var/g;
    const parts = [];
    let match;
    
    while ((match = regex.exec(code)) !== null) {
        parts.unshift(match[1]); // 逆序添加,因为字符串是向前拼接的
    }
    
    // 2. 如果有初始赋值,添加到开头
    const initMatch = code.match(/cbk_var\s*=\s*['"]([^'"]+)['"]\s*;/);
    if (initMatch) {
        parts.unshift(initMatch[1]);
    }
    
    // 3. 拼接所有部分
    return parts.join('');
}

function getRedirect(jsString) {
  if (!jsString) return null;

  // 提取 <script> 标签内的 JS 代码
  const scriptMatch = jsString.match(/<script[^>]*>([\s\S]*?)<\/script>/i);
  if (!scriptMatch) return null;

  const jsCode = scriptMatch[1];
  if (!jsCode.includes("window.location=", "")) return null;
  try {
	return safeEval(jsCode);
  } catch (error) {
	return null;
  }
}

// 提取的f函数
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;
}

// 自定义request函数,允许468状态码
function customRequest(url, options) {
  return new Promise((resolve, reject) => {
    request(url, options, (err, resp) => {
      if (err) return reject(err);
      // 允许200-299和468状态码
      if ((resp.statusCode >= 200 && resp.statusCode < 300) || resp.statusCode === 468) {
        resolve(resp);
      } else {
        reject(new Error(`请求失败,状态码: ${resp.statusCode}, 响应: ${JSON.stringify(resp)}`));
      }
    });
  });
}

// 生成cookie的函数
async function generateCookie() {
  debugger;
  try {
    // 第一步:获取初始页面并提取sl-session和key
	log("第一步获取初始页面...");
    let 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-encoding': 'gzip, deflate, br, zstd',
        'accept-language': 'zh-CN,zh;q=0.9',
		'cache-control': 'no-cache',
		'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
		referer: 'https://flac.music.hi.cn/',
      }
    });
	const redir = getRedirect(response1.body);
	if (redir) {
	  response1 = await customRequest('https://flac.music.hi.cn' + redir, {
      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-encoding': 'gzip, deflate, br, zstd',
			'accept-language': 'zh-CN,zh;q=0.9',
			'cache-control': 'no-cache',
			'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
			referer: 'https://flac.music.hi.cn',
		  }
		});
	}
    log('获取初始页面响应状态码:', response1.statusCode);
    
    // 提取sl-session
    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;
			}
		}
	}

    // 提取key
    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');
    }

    // 第二步:请求issue
    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);

    // 第三步:计算result
    const result = calculateArray(issueData.data);
	log('第三步计算结果: ', result);
    // 第四步:验证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/',
		'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
      },
      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);

    // 第五步:最终请求获取cookie
	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}`,
		'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
        'referer': 'https://flac.music.hi.cn/',
      }
    });
    log('第五步响应状态码:', response4.statusCode);

    // 提取最终的cookie
    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);

    // 生成最终cookie
    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;
  }
}

// 初始化cookie并设置定时更新
async function initCookie() {
  try {
    globalCookie = await generateCookie();
    log('Cookie初始化成功');
    
    // 设置每小时更新一次cookie
    if (cookieUpdateTimer) {
      clearInterval(cookieUpdateTimer);
    }
    cookieUpdateTimer = setInterval(async () => {
      try {
        globalCookie = await generateCookie();
        log('Cookie定时更新成功');
      } catch (error) {
        log('Cookie定时更新失败:', error.message);
      }
    }, 60 * 60 * 1000); // 1小时
  } catch (error) {
    log('Cookie初始化失败,使用默认cookie:', error.message, "堆栈: ", error.stack);
  }
}

// 初始化cookie
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,
        'Referer': API_BASE_URL,
        'X-Requested-With': 'XMLHttpRequest',
        'Cookie': globalCookie, // 使用动态生成的cookie
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/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];

  const name = info.musicInfo.name.toLowerCase();
  const singer = info.musicInfo.singer.toLowerCase();
  const albumName = info.musicInfo.albumName.toLowerCase();
  const interval = timeToSeconds(info.musicInfo.interval);

  let candidates = list.map(item => {
    const itemName = item.name.toLowerCase();
    const itemSinger = item.artist.toLowerCase();
    const itemAlbum = item.album_name.toLowerCase();
    const itemDuration = parseInt(item.duration);
    
    let score = 0;
    let durationDiff = Math.abs(itemDuration - interval);
    
    const nameSimilarity = calculateSimilarity(itemName, name); // 计算名称相似度
    const singerSimilarity = calculateSimilarity(itemSinger, singer); // 计算歌手相似度
    const albumSimilarity = calculateSimilarity(itemAlbum, albumName); // 计算专辑相似度
    
    // 优先级1: 名称+歌手+专辑完全匹配
    if (itemName === name && itemSinger === singer && itemAlbum === albumName) {
      score = 1000 - durationDiff / 1000;
    } else if (itemName === name && itemSinger === singer) { // 优先级2: 名称+歌手+时长最接近
      score = 900 - durationDiff;
    } else if (itemName === name && itemSinger === singer) { // 优先级3: 名称+歌手最接近
      score = 800 + nameSimilarity + singerSimilarity - durationDiff / 100;
    } else if (itemName === name) { // 优先级4: 名称+时长最接近
      score = 700 + nameSimilarity - durationDiff;
    } else if (itemName.includes(name) || name.includes(itemName)) { // 优先级5: 名称最接近
      score = 600 + nameSimilarity - durationDiff / 100;
    } else { // 优先级6: 时长最接近
      score = 500 - durationDiff;
    }

    return { item, score, durationDiff };
  });
  
  // 按分数降序排序
  candidates.sort((a, b) => b.score - a.score || a.durationDiff - b.durationDiff);
  return candidates.length > 0 ? candidates[0].item : list[0];
}

function timeToSeconds(timeStr) {
  const parts = timeStr.split(':');
  if (parts.length === 2) {
    return parseInt(parts[0]) * 60 + parseInt(parts[1]);
  }
  return 0;
}

function calculateSimilarity(str1, str2) {
  if (str1 === str2) return 1;
  if (!str1 || !str2) return 0;
  
  const longer = str1.length > str2.length ? str1 : str2;
  const shorter = str1.length > str2.length ? str2 : str1;
  
  if (longer.includes(shorter)) return 0.8;
  if (shorter.includes(longer)) return 0.7;
  
  return 0;
}

const mapBitrate = {
    '128k': '128',
    '320k': '320',
    flac: '2000',
    flac24bit: '2000',
  }
const mapFormat = {
	'128k': 'mp3',
    '320k': 'mp3',
    flac: 'flac',
    flac24bit: 'flac',
}

// 获取音乐URL
const musicUrl = async(source, info) => {
	log("运行 musicUrl: ", source, info);
	let songid = "";
	let item = null;
	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) {
			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]}&time=${item?.time}&bitrate=${mapBitrate[info.type]}&sign=${item?.sign}`);
	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: true, 
  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: [],
    }
  }
})
无损音源2.js (16.2KB)