/*! 
 * @name 星海音乐源
 * @description 主API：GDAPI | 直连上游：api.yaohud.cn（HMAC签名） | ChKSz API网易云高音质
 * @version v2.3.3
 * @author 万去了了
 * @homepage https://zrcdy.dpdns.org/
 * @lastUpdate 2026-06-06
 * 
 * @version v2.3.3：1,修复桌面版完全无法播放的致命 Bug，2,优化，3.发现个 bug，手机端用户最好不要更新4,高考祝福：6月7日高考启幕，祝所有考生提笔有神，合笔如愿，金榜题名。

 */

const DEBUG_MODE = false; // true=全部日志 false=仅错误

const UPDATE_CONFIG = {
    versionApiUrl: 'https://zrcdy.dpdns.org/lx/version.php',
    latestScriptUrl: 'https://zrcdy.dpdns.org/lx/vers.php',
    currentVersion: 'v2.3.3'
};

const STABLE_SOURCES_API_URL = 'https://zrcdy.dpdns.org/lx/stable_sources.php';
const MAIN_API_BASE = 'https://music-api.gdstudio.xyz/api.php?use_xbridge3=true&loader_name=forest&need_sec_link=1&sec_link_scene=im&theme=light';
const DIRECT_API_BASE = 'https://api.yaohud.cn/api/music/';
const SIGN_PROVIDER_URL = 'https://zrcdy.dpdns.org/lx/api/api.php?get_sign_only=1';
const FALLBACK_PROXY_URL = 'https://zrcdy.dpdns.org/lx/api/api.php';
const NETEASE_VIP_API = 'https://api.chksz.top/api/163_music';

let musicSourceEnabled = true;
let serverCheckCompleted = false;
let backupApiAvailable = false;
let stableSourcesList = null;
let mainApiSourceMap = {};
let availablePlatforms = [];

let yaohuPlatformStatus = { kg: 'unknown', qq: 'unknown', migu: 'unknown' };
let gdApiStatus = 'unknown';
let neteaseVipApiStatus = 'unknown';

const ALL_PLATFORMS = ['wy', 'tx', 'kw', 'kg', 'mg'];
const MUSIC_QUALITY_FULL = {
    wy: ['128k', '192k', '320k', 'flac', 'flac24bit', 'hires', 'jyeffect', 'sky', 'jymaster'],
    tx: ['128k', '192k', '320k', 'flac', 'flac24bit'],
    kw: ['128k', '192k', '320k', 'flac', 'flac24bit'],
    kg: ['128k', '192k', '320k', 'flac', 'flac24bit'],
    mg: ['128k', '192k', '320k', 'flac', 'flac24bit']
};
const PLATFORM_NAME_MAP = {
    wy: '网易云音乐', tx: 'QQ音乐', kw: '酷我音乐', kg: '酷狗音乐', mg: '咪咕音乐'
};
const DIRECT_SOURCE_PATH = { kg: 'kg', tx: 'qq', mg: 'migu' };
const NETEASE_VIP_LEVEL_MAP = { hires: 'hires', jyeffect: 'jyeffect', sky: 'sky', jymaster: 'jymaster' };
const NETEASE_VIP_QUALITY_SET = new Set(['hires', 'jyeffect', 'sky', 'jymaster']);

const { EVENT_NAMES, request, on, send } = globalThis.lx;

// ============================ 工具函数 ============================
function log(...args) {
    if (!DEBUG_MODE) {
        const msg = args.join(' ');
        if (/错误|失败|异常|不可用|维护|完全失败|无结果|降级|离线/.test(msg)) console.log('[星海]', ...args);
    } else console.log('[星海]', ...args);
}

function delay(ms) { return new Promise(r => setTimeout(r, ms)); }
function logSimple(action, source, musicInfo, status, extra = '') {
    log(`[${action}] 平台:${source} | 歌曲:${musicInfo.name || '未知'} | 状态:${status}${extra ? ' | ' + extra : ''}`);
}

// ★ 修复桌面版 Buffer 问题
function safeParseBody(body) {
    if (typeof Buffer !== 'undefined' && Buffer.isBuffer(body)) body = body.toString();
    if (typeof body === 'string') {
        const t = body.trim();
        if (t.startsWith('{') || t.startsWith('[') || t.startsWith('"')) {
            try { return JSON.parse(t); } catch (e) {}
        }
    }
    return body;
}

function buildQueryString(params) {
    const parts = [];
    for (const key in params) {
        if (params.hasOwnProperty(key)) {
            let value = params[key];
            if (value !== undefined && value !== null && value !== '') {
                value = String(value).trim();
                value = encodeURIComponent(value).replace(/%20/g, '');
                parts.push(encodeURIComponent(key) + '=' + value);
            }
        }
    }
    return parts.join('&');
}

function mapQuality(target, avail) {
    const pm = { '臻品母带': 'jymaster', '臻品音质2.0': 'sky', '臻品音质AI': 'jyeffect', '臻品音质': 'jyeffect', 'Hires 无损24-Bit': 'hires', 'Hi-Res': 'hires', 'FLAC': 'flac', '320k': '320k', '192k': '192k', '128k': '128k' };
    if (avail.includes(target)) return target;
    const m = pm[target];
    if (m && avail.includes(m)) return m;
    const order = ['jymaster', 'sky', 'jyeffect', 'hires', 'flac24bit', 'flac', '320k', '192k', '128k'];
    for (const q of order) if (avail.includes(q)) return q;
    return avail[0] || '128k';
}

const httpFetch = (url, options = {}) => new Promise((resolve, reject) => {
    request(url, options, (err, resp) => {
        if (err) return reject(new Error(`网络异常：${err.message}`));
        const body = safeParseBody(resp.body);
        resolve({ body, statusCode: resp.statusCode, headers: resp.headers || {} });
    });
});

const compareVersions = (remote, current) => {
    const p = v => v.replace(/^v/, '').split('.').map(x => { const n = parseInt(x, 10); return isNaN(n) ? x : n; });
    const r = p(remote), c = p(current);
    for (let i = 0; i < Math.max(r.length, c.length); i++) {
        const rv = r[i] ?? (typeof c[i] === 'number' ? 0 : ''), cv = c[i] ?? (typeof r[i] === 'number' ? 0 : '');
        if (typeof rv === 'number' && typeof cv === 'number') { if (rv > cv) return true; if (rv < cv) return false; }
        else if (typeof rv === 'string' && typeof cv === 'string') { if (rv > cv) return true; if (rv < cv) return false; }
        else { if (typeof rv === 'number' && typeof cv === 'string') return true; if (typeof rv === 'string' && typeof cv === 'number') return false; }
    }
    return false;
};

function trimSpacesOnly(s) { return s ? s.replace(/\s+/g, ' ').trim() : ''; }
function removeBracketsContent(s) { if (!s) return ''; let c = s.replace(/[（(][^）)]*[）)]/g, ''); return c.replace(/\s+/g, ' ').trim(); }
function removeSpecialChars(s) { if (!s) return ''; let c = removeBracketsContent(s); c = c.replace(/[^\u4e00-\u9fa5\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af\u1100-\u11ff\u3130-\u318fa-zA-Z0-9\s]/g, ''); return c.replace(/\s+/g, ' ').trim(); }
function cleanStrict(s) { return s ? removeSpecialChars(s).replace(/\s+/g, '').trim() : ''; }

function stringMatchScore(a, b) {
    if (!a || !b) return 0;
    const s1 = a.toLowerCase().replace(/\s+/g, ' ').trim(), s2 = b.toLowerCase().replace(/\s+/g, ' ').trim();
    if (s1 === s2) return 1; if (s1.includes(s2) || s2.includes(s1)) return 0.9;
    let m = 0; for (let i = 0; i < Math.min(s1.length, s2.length); i++) if (s1[i] === s2[i]) m++;
    return m / Math.max(s1.length, s2.length);
}
function findBestMatchSong(name, singer, songs) {
    if (!songs || !songs.length) return null;
    let bi = -1, bs = -1;
    songs.forEach((s, i) => {
        const sn = s.title || s.name || '', ss = s.singer || s.author || '';
        const ns = stringMatchScore(name, sn), sc = stringMatchScore(singer, ss);
        const ts = ns * 0.6 + sc * 0.4;
        if (ts > bs) { bs = ts; bi = i; }
    });
    return bs >= 0.3 && bi >= 0 ? songs[bi] : null;
}

// ============================ 签名 ============================
let cachedCredential = null, credentialExpireTime = 0;
async function fetchCredentials() {
    const now = Date.now();
    if (cachedCredential && now < credentialExpireTime) return cachedCredential;
    log('获取临时签名...');
    try {
        const resp = await httpFetch(SIGN_PROVIDER_URL, { timeout: 5000 });
        if (resp.statusCode !== 200) throw new Error(`HTTP ${resp.statusCode}`);
        let data = resp.body;
        if (typeof data === 'string') data = JSON.parse(data);
        cachedCredential = data;
        credentialExpireTime = now + (data.expire_in ? data.expire_in - 5 : 55) * 1000;
        return cachedCredential;
    } catch (err) {
        log('签名获取失败:', err.message);
        if (cachedCredential && now < credentialExpireTime) return cachedCredential;
        throw new Error('签名凭证不可用');
    }
}
async function signedFetch(url, options = {}) {
    const cred = await fetchCredentials();
    const headers = { 'X-Api-Key': cred.api_key, 'X-Api-Timestamp': String(cred.timestamp), 'X-Api-Sign': cred.sign, 'Content-Type': 'application/json', ...(options.headers || {}) };
    log(`直连请求: ${url}`);
    try {
        const resp = await httpFetch(url, { ...options, headers });
        log(`直连响应 [${resp.statusCode}]: ${JSON.stringify(resp.body).substring(0, 300)}`);
        return resp;
    } catch (e) { log(`直连异常: ${e.message}`); throw e; }
}

// ============================ 稳定源 ============================
const fetchStableSources = async () => {
    log('获取稳定源...');
    try {
        const resp = await httpFetch(STABLE_SOURCES_API_URL, { timeout: 5000, headers: { 'User-Agent': 'LX-Music-Mobile' } });
        if (resp.statusCode !== 200) throw new Error(`HTTP ${resp.statusCode}`);
        let data = resp.body; if (typeof data === 'string') data = JSON.parse(data);
        if (!Array.isArray(data) || data.length === 0) throw new Error('数据为空');
        stableSourcesList = data; log('稳定源:', stableSourcesList);
    } catch (err) {
        log('稳定源失败，默认:', err.message);
        stableSourcesList = ['netease', 'kuwo'];
    }
};
const buildPlatformsFromStableSources = () => {
    const map = { netease: 'wy', tencent: 'tx', kuwo: 'kw', kugou: 'kg', migu: 'mg' };
    mainApiSourceMap = {};
    stableSourcesList.forEach(s => { const c = map[s]; if (c) mainApiSourceMap[c] = s; });
    availablePlatforms = [...ALL_PLATFORMS];
    log('GDAPI映射:', mainApiSourceMap);
};

// ============================ 平台可用性判断（新增容错：unknown 视为可用） ============================
function isPlatformAvailable(platform) {
    if (platform === 'wy') {
        const gdOk = mainApiSourceMap['wy'] && (gdApiStatus === 'available' || gdApiStatus === 'unknown');
        const vipOk = neteaseVipApiStatus === 'available' || neteaseVipApiStatus === 'unknown';
        return gdOk || vipOk;
    }
    if (platform === 'kw') return mainApiSourceMap['kw'] && (gdApiStatus === 'available' || gdApiStatus === 'unknown');
    const gdOk = mainApiSourceMap[platform] && (gdApiStatus === 'available' || gdApiStatus === 'unknown');
    const dp = DIRECT_SOURCE_PATH[platform];
    if (!dp) return gdOk;
    const yaohuOk = (yaohuPlatformStatus[dp] === 'available' || yaohuPlatformStatus[dp] === 'unknown') && backupApiAvailable;
    // 如果 backupApiAvailable 为 false，但 yaohu 状态 unknown，也允许尝试（冒险模式）
    const riskyOk = (yaohuPlatformStatus[dp] === 'available' || yaohuPlatformStatus[dp] === 'unknown') && !backupApiAvailable;
    return gdOk || yaohuOk || riskyOk;
}
function filterAvailablePlatforms() {
    const before = availablePlatforms.length;
    availablePlatforms = availablePlatforms.filter(p => isPlatformAvailable(p));
    log(`平台过滤: ${before} -> ${availablePlatforms.length}`);
    if (availablePlatforms.length === 0) {
        log('过滤后无平台，恢复全平台');
        availablePlatforms = [...ALL_PLATFORMS];
    }
}

// ============================ 服务器状态 ============================
const fetchServerStatus = async () => {
    log('检查服务器状态...');
    for (let a = 0; a <= 2; a++) {
        if (a > 0) await delay(1000);
        try {
            const resp = await httpFetch(UPDATE_CONFIG.versionApiUrl, { timeout: 5000, headers: { 'User-Agent': 'LX-Music-Mobile' } });
            if (resp.statusCode !== 200) throw new Error(`HTTP ${resp.statusCode}`);
            const data = typeof resp.body === 'object' ? resp.body : JSON.parse(resp.body);
            if (!data) throw new Error('数据无效');
            if (data.yaohu_api?.platforms) for (let p in data.yaohu_api.platforms) {
                yaohuPlatformStatus[p] = data.yaohu_api.platforms[p].status || 'unknown';
                log(`Yaohu [${p}]: ${yaohuPlatformStatus[p]}`);
            } else { const ov = data.yaohu_api?.status || 'unknown'; for (let p in yaohuPlatformStatus) yaohuPlatformStatus[p] = ov; }
            gdApiStatus = data.gd_api?.status || 'unknown'; log(`GD: ${gdApiStatus}`);
            neteaseVipApiStatus = data.netease_vip_api?.status || 'unknown'; log(`VIP: ${neteaseVipApiStatus}`);
            backupApiAvailable = data.server_status?.online !== false;
            log(`代理: ${backupApiAvailable ? '在线' : '离线'}`);
            return { enabled: backupApiAvailable, remoteVersion: data.version };
        } catch (e) { log(`状态检查失败${a+1}: ${e.message}`); }
    }
    for (let p in yaohuPlatformStatus) yaohuPlatformStatus[p] = 'unknown'; // 保持 unknown 以允许尝试
    gdApiStatus = 'unknown'; neteaseVipApiStatus = 'unknown'; backupApiAvailable = false;
    return { enabled: false, remoteVersion: null };
};

// ============================ 搜索与匹配 ============================
function extractSongsFromData(data, upstreamSource) {
    if (!data || data.code !== 200) return [];
    if (upstreamSource === 'qq' || upstreamSource === 'tx') return data.data?.songs || [];
    if (Array.isArray(data.data)) return data.data;
    return data.data?.songs || [];
}

function isDefinitelyNoResult(body) {
    if (!body) return false;
    const data = typeof body === 'object' ? body : JSON.parse(body);
    return data?.code === 404 && Array.isArray(data?.data) && data.data.length === 0;
}

async function directSearch(upstreamSource, keyword, limit = 10) {
    const params = { key: '8Sbg8jJCnrssIDGDaz9', msg: keyword, g: String(limit) };
    if (upstreamSource === 'migu') { params.num = String(limit); delete params.g; }
    const url = `${DIRECT_API_BASE}${upstreamSource}?${buildQueryString(params)}`;
    try {
        const resp = await signedFetch(url);
        if (resp.statusCode !== 200) return [];
        const data = resp.body;
        if (data.code !== 200) { if (isDefinitelyNoResult(data)) throw new Error('NO_RESULT'); return []; }
        const songs = extractSongsFromData(data, upstreamSource);
        if (songs.length === 0 && isDefinitelyNoResult(data)) throw new Error('NO_RESULT');
        return songs;
    } catch (e) { if (e.message === 'NO_RESULT') throw e; return []; }
}

async function proxySearch(proxySource, keyword, limit = 10) {
    if (!proxySource) throw new Error('缺少source参数');
    const params = { source: proxySource, msg: keyword, g: String(limit) };
    if (proxySource === 'migu') { params.num = String(limit); delete params.g; }
    const url = `${FALLBACK_PROXY_URL}?${buildQueryString(params)}`;
    try {
        const resp = await httpFetch(url);
        if (resp.statusCode !== 200) return [];
        const data = resp.body;
        if (data.code !== 200) { if (isDefinitelyNoResult(data)) throw new Error('NO_RESULT'); return []; }
        const songs = extractSongsFromData(data, proxySource);
        if (songs.length === 0 && isDefinitelyNoResult(data)) throw new Error('NO_RESULT');
        return songs;
    } catch (e) { if (e.message === 'NO_RESULT') throw e; return []; }
}

async function smartSearchBestMatch(source, songName, singer, useProxy = false) {
    const upstream = DIRECT_SOURCE_PATH[source];
    if (!upstream) throw new Error('不支持该平台');
    const search = useProxy ? proxySearch : directSearch;
    const cleanSong = removeSpecialChars(songName), cleanSinger = removeSpecialChars(singer || '');
    let songs, noRes = false;
    try { songs = await search(upstream, songName, 20); } catch (e) { if (e.message === 'NO_RESULT') noRes = true; }
    if (!noRes && songs?.length) { const best = findBestMatchSong(songName, singer, songs); if (best) return best; }
    if (cleanSong !== songName) {
        try { songs = await search(upstream, cleanSong, 20); } catch (e) { if (e.message === 'NO_RESULT') noRes = true; }
        if (!noRes && songs?.length) { const best = findBestMatchSong(songName, singer, songs); if (best) return best; }
    }
    if (cleanSinger) {
        try { songs = await search(upstream, cleanSong + ' ' + cleanSinger, 30); } catch (e) { if (e.message === 'NO_RESULT') noRes = true; }
        if (!noRes && songs?.length) { const best = findBestMatchSong(songName, singer, songs); if (best) return best; }
    }
    return null;
}

function isDirectAllowedForSource(source) {
    const up = DIRECT_SOURCE_PATH[source];
    if (!up) return false;
    const st = yaohuPlatformStatus[up] || 'unknown';
    return st === 'available' || st === 'unknown';
}

// ============================ 音频URL获取（放宽状态限制） ============================
async function getMusicUrlFromMainAPI(source, songId, apiQuality) {
    if (gdApiStatus === 'unavailable') throw new Error('GD API 不可用');
    const apiSource = mainApiSourceMap[source];
    if (!apiSource) throw new Error('GD不支持此平台');
    const url = `${MAIN_API_BASE}&types=url&source=${apiSource}&id=${songId}&br=${apiQuality}`;
    const resp = await httpFetch(url, { headers: { 'User-Agent': 'LX-Music-Mobile' } });
    const data = typeof resp.body === 'object' ? resp.body : JSON.parse(resp.body);
    if (!data.url) throw new Error('GD未返回音频地址');
    return data.url;
}

async function getMusicUrlFromNeteaseVIP(songId, quality) {
    if (neteaseVipApiStatus === 'unavailable') throw new Error('VIP API 不可用');
    const level = NETEASE_VIP_LEVEL_MAP[quality] || 'jymaster';
    const url = `${NETEASE_VIP_API}?id=${songId}&level=${level}`;
    const resp = await httpFetch(url, { headers: { 'User-Agent': 'LX-Music-Mobile' } });
    if (resp.statusCode !== 200) throw new Error(`VIP HTTP ${resp.statusCode}`);
    const data = typeof resp.body === 'object' ? resp.body : JSON.parse(resp.body);
    if (data.code !== 200 || !data.data?.url) throw new Error('VIP未返回音频');
    return data.data.url;
}

async function getMusicUrlViaDirect(source, musicInfo, quality) {
    if (!isDirectAllowedForSource(source)) throw new Error('直连不可用');
    const songName = musicInfo.name || '', singer = musicInfo.singer || '';
    const upstream = DIRECT_SOURCE_PATH[source];
    const best = await smartSearchBestMatch(source, songName, singer, false);
    if (!best) throw new Error('直连搜索无匹配');
    const n = best.n || best.index || 1;
    const params = { key: '8Sbg8jJCnrssIDGDaz9', msg: songName, n: String(n) };
    if (source === 'kg') params.quality = 'flac'; else if (source === 'tx') params.size = 'hq';
    const detailUrl = `${DIRECT_API_BASE}${upstream}?${buildQueryString(params)}`;
    const resp = await signedFetch(detailUrl);
    if (resp.statusCode !== 200) throw new Error(`详情请求失败`);
    const detail = resp.body;
    if (detail.code !== 200) throw new Error(detail.msg || '详情失败');
    const url = detail.data?.play_url || detail.data?.music_url || detail.data?.url || detail.data?.musicurl;
    if (!url) throw new Error('未找到音频地址');
    return url;
}

async function getMusicUrlViaProxy(source, musicInfo, quality) {
    // 即使 backupApiAvailable 为 false，若映射存在且状态不是 unavailable，也可冒险尝试
    const proxySource = DIRECT_SOURCE_PATH[source];
    if (!proxySource) throw new Error('无代理映射');
    const songName = musicInfo.name || '', singer = musicInfo.singer || '';
    const best = await smartSearchBestMatch(source, songName, singer, true);
    if (!best) throw new Error('代理搜索无匹配');
    const n = best.n || best.index || 1;
    const params = { source: proxySource, msg: songName, n: String(n) };
    if (proxySource === 'kg') params.quality = 'flac'; else if (proxySource === 'qq') params.size = 'hq';
    const detailUrl = `${FALLBACK_PROXY_URL}?${buildQueryString(params)}`;
    const resp = await httpFetch(detailUrl);
    if (resp.statusCode !== 200) throw new Error(`代理详情失败`);
    const detail = resp.body;
    if (detail.code !== 200) throw new Error(detail.msg || '获取失败');
    const url = detail.data?.play_url || detail.data?.music_url || detail.data?.url || detail.data?.musicurl;
    if (!url) throw new Error('代理未返回音频');
    return url;
}

const handleGetMusicUrl = async (source, musicInfo, quality) => {
    if (!musicSourceEnabled) throw new Error('服务不可用');
    if (!serverCheckCompleted) throw new Error('初始化未完成');
    const songId = musicInfo.hash ?? musicInfo.songmid ?? musicInfo.id;
    if (!songId) throw new Error('歌曲信息不完整');
    logSimple('解析音频', source, musicInfo, '开始');
    const avail = MUSIC_QUALITY_FULL[source] || ['128k','192k','320k','flac'];
    let actual = mapQuality(quality, avail);
    if (actual !== quality) log(`音质映射: ${quality} -> ${actual}`);
    let finalUrl = null, lastErr = null;

    // 网易云VIP
    if (source === 'wy' && NETEASE_VIP_QUALITY_SET.has(actual) && neteaseVipApiStatus !== 'unavailable') {
        try {
            finalUrl = await getMusicUrlFromNeteaseVIP(songId, actual);
            logSimple('解析音频', source, musicInfo, '成功(VIP)');
            return finalUrl;
        } catch (e) { lastErr = e; log('VIP失败，回退'); actual = 'flac24bit'; }
    }

    // GDAPI
    if (mainApiSourceMap[source] && gdApiStatus !== 'unavailable') {
        try {
            const brMap = { '128k': '128', '192k': '192', '320k': '320', 'flac': '740', 'flac24bit': '999' };
            finalUrl = await getMusicUrlFromMainAPI(source, songId, brMap[actual] || '320');
            logSimple('解析音频', source, musicInfo, '成功(GD)');
        } catch (e) { lastErr = e; log('GD失败'); }
    } else log('GD跳过');

    // 备用：直连/代理
    if (!finalUrl && DIRECT_SOURCE_PATH[source]) {
        // 允许尝试条件：直连可用，或者代理（backupApiAvailable 或 冒险模式）
        const canTryDirect = isDirectAllowedForSource(source);
        if (canTryDirect) {
            try {
                finalUrl = await getMusicUrlViaDirect(source, musicInfo, actual);
                logSimple('解析音频', source, musicInfo, '成功(直连)');
            } catch (e) { lastErr = e; log('直连失败'); }
        }
        if (!finalUrl) {
            // 代理冒险：只要不是明确 unavailable，且之前没成功，就尝试
            try {
                finalUrl = await getMusicUrlViaProxy(source, musicInfo, actual);
                logSimple('解析音频', source, musicInfo, '成功(代理)');
            } catch (e) { lastErr = e; log('代理失败'); }
        }
    }

    if (!finalUrl) throw new Error(`无法获取: ${lastErr?.message || '未知'}`);
    return finalUrl;
};

// ============================ 搜索 ============================
async function handleSearch(source, info) {
    if (!['kg', 'tx', 'mg'].includes(source)) throw new Error('不支持搜索');
    const keyword = info.key || info.keyword || '';
    if (!keyword) throw new Error('关键词为空');
    const limit = info.limit || 20;
    const upstream = DIRECT_SOURCE_PATH[source];
    if (isDirectAllowedForSource(source)) {
        const params = { key: '8Sbg8jJCnrssIDGDaz9', msg: keyword, g: String(limit) };
        if (upstream === 'migu') { params.num = String(limit); delete params.g; }
        const url = `${DIRECT_API_BASE}${upstream}?${buildQueryString(params)}`;
        try {
            const resp = await signedFetch(url);
            if (resp.statusCode === 200 && resp.body.code === 200) {
                const songs = extractSongsFromData(resp.body, upstream);
                return { list: songs.map((s, i) => ({ singer: s.singer || s.author || '', name: s.title || s.name || '', album: s.album || '', source, songmid: s.hash || s.mid || s.id || String(i), interval: s.duration ? parseInt(s.duration) * 1000 : null, img: s.cover || '', lrc: null, hash: s.hash || s.mid || s.id || String(i), albumId: s.albumid || '', lyricUrl: null })), total: songs.length, limit, page: 1, source };
            }
        } catch (e) { log('直连搜索失败，降级代理'); }
    }
    // 代理搜索
    const proxySource = DIRECT_SOURCE_PATH[source];
    if (!proxySource) throw new Error('无代理映射');
    const params = { source: proxySource, msg: keyword, g: String(limit) };
    if (proxySource === 'migu') { params.num = String(limit); delete params.g; }
    const url = `${FALLBACK_PROXY_URL}?${buildQueryString(params)}`;
    const resp = await httpFetch(url);
    if (resp.statusCode !== 200) throw new Error(`搜索HTTP ${resp.statusCode}`);
    const data = resp.body;
    if (data.code !== 200) throw new Error(data.msg || '搜索失败');
    const songs = extractSongsFromData(data, proxySource);
    return { list: songs.map((s, i) => ({ singer: s.singer || s.author || '', name: s.title || s.name || '', album: s.album || '', source, songmid: s.hash || s.mid || s.id || String(i), interval: s.duration ? parseInt(s.duration) * 1000 : null, img: s.cover || '', lrc: null, hash: s.hash || s.mid || s.id || String(i), albumId: s.albumid || '', lyricUrl: null })), total: songs.length, limit, page: 1, source };
}

// ============================ 构建 & 事件 ============================
const buildMusicSources = (platforms) => {
    const obj = {};
    platforms.forEach(p => obj[p] = { name: PLATFORM_NAME_MAP[p] || p, type: 'music', actions: ['musicUrl'], qualitys: MUSIC_QUALITY_FULL[p] });
    return obj;
};

on(EVENT_NAMES.request, ({ action, source, info }) => {
    if (action === 'musicUrl') return handleGetMusicUrl(source, info.musicInfo, info.type);
    if (action === 'search') return handleSearch(source, info);
    return Promise.reject(new Error('不支持的操作'));
});

// ============================ 初始化 ============================
(async () => {
    log('星海音乐源 v2.3.2 启动');
    try {
        const server = await fetchServerStatus();
        musicSourceEnabled = true; // 始终保持
        backupApiAvailable = server.enabled;
        await fetchStableSources();
        buildPlatformsFromStableSources();
        filterAvailablePlatforms();
        fetchCredentials().catch(() => {});
        serverCheckCompleted = true;
        send(EVENT_NAMES.inited, { status: true, openDevTools: false, sources: buildMusicSources(availablePlatforms), initStatus: 'ready' });
        log(`初始化完成，平台: ${availablePlatforms.join(',')}`);
        setTimeout(checkAutoUpdate, 3000);
    } catch (e) {
        log('初始化异常，降级', e.message);
        stableSourcesList = ['netease', 'kuwo'];
        buildPlatformsFromStableSources();
        if (availablePlatforms.length === 0) availablePlatforms = [...ALL_PLATFORMS];
        musicSourceEnabled = true;
        backupApiAvailable = false;
        serverCheckCompleted = true;
        send(EVENT_NAMES.inited, { status: true, openDevTools: false, sources: buildMusicSources(availablePlatforms), initStatus: 'degraded' });
        setTimeout(checkAutoUpdate, 3000);
    }
})();

async function checkAutoUpdate() {
    try {
        const resp = await httpFetch(UPDATE_CONFIG.versionApiUrl, { timeout: 10000, headers: { 'User-Agent': 'LX-Music-Mobile' } });
        if (resp.statusCode !== 200) return;
        let data = resp.body; if (typeof data === 'string') data = JSON.parse(data.trim().replace(/^\uFEFF/, ''));
        if (!data?.version) return;
        if (compareVersions(data.version, UPDATE_CONFIG.currentVersion)) {
            send(EVENT_NAMES.updateAlert, { log: `发现新版本 ${data.version}\n${data.changelog || ''}`, updateUrl: data.update_url || UPDATE_CONFIG.latestScriptUrl });
        }
    } catch (e) { log('更新检查失败'); }
}