/** * 一起听功能模块 * 与AI角色一起听歌聊天 */ import { getSettings, splitAIMessages } from './config.js'; import { currentChatIndex } from './chat.js'; import { requestSave } from './save-manager.js'; import { refreshChatList } from './ui.js'; import { searchMusic, playMusic, togglePlay, getCurrentSong, formatDuration } from './music.js'; import { showToast } from './toast.js'; import { escapeHtml, sleep } from './utils.js'; // ========== SVG 图标 ========== const LISTEN_ICON = ''; const BACK_ICON = ''; const SEARCH_ICON = ''; const PLAY_ICON = ''; const PAUSE_ICON = ''; const PREV_ICON = ''; const NEXT_ICON = ''; const CHAT_ICON = ''; const PLAYLIST_ICON = ''; const SEND_ICON = ''; const CLOSE_ICON = ''; const HEART_ICON = ''; const LOOP_ICON = ''; // ========== 状态管理 ========== let listenState = { isActive: false, isConnected: false, currentSong: null, messages: [], contact: null, contactIndex: -1, startTime: null, isPlaying: false, connectTimeout: null, dotsInterval: null, chatVisible: false, audioElement: null, progressInterval: null, pauseTimeout: null, // 暂停后自动播放下一首的计时器 playMode: 'normal', // 播放模式: 'normal' | 'loop' | 'shuffle' songsSinceAIChange: 0, // AI换歌保底计数器 }; // 导出图标供其他模块使用 export { LISTEN_ICON }; // ========== 页面显示/隐藏 ========== /** * 显示一起听搜索页面 */ export function showListenSearchPage() { const page = document.getElementById('wechat-listen-search-page'); if (page) { page.classList.remove('hidden'); // 聚焦输入框 setTimeout(() => { const input = document.getElementById('wechat-listen-search-input'); if (input) input.focus(); }, 100); } } /** * 隐藏一起听搜索页面 */ export function hideListenSearchPage() { const page = document.getElementById('wechat-listen-search-page'); if (page) page.classList.add('hidden'); } /** * 显示等待页面 */ function showWaitingPage(song, contact) { const page = document.getElementById('wechat-listen-waiting-page'); if (!page) return; const settings = getSettings(); // 调试日志 console.log('[一起听等待页面] 数据检查:', { userAvatar: settings.userAvatar, contactAvatar: contact.avatar, songCover: song.cover, contactName: contact.name }); // 小图显示用户头像 const avatarEl = document.getElementById('wechat-listen-waiting-avatar'); if (avatarEl) { // 先清除旧内容 avatarEl.innerHTML = ''; if (settings.userAvatar) { avatarEl.innerHTML = ``; } else { avatarEl.textContent = (settings.userName || 'User').charAt(0); } } // 大图显示角色头像(带雷达动画的) const coverEl = document.getElementById('wechat-listen-waiting-cover'); if (coverEl) { // 先清除旧值 coverEl.src = ''; coverEl.style.background = ''; if (contact.avatar) { coverEl.src = contact.avatar; } else { // 如果没有头像,用纯色背景 coverEl.style.background = '#333'; } } // 设置角色名 const nameEl = document.getElementById('wechat-listen-waiting-name'); if (nameEl) { nameEl.textContent = contact.name || 'TA'; } page.classList.remove('hidden'); } /** * 隐藏等待页面 */ function hideWaitingPage() { const page = document.getElementById('wechat-listen-waiting-page'); if (page) page.classList.add('hidden'); clearInterval(listenState.dotsInterval); } /** * 显示一起听主页面 */ function showListenTogetherPage() { const page = document.getElementById('wechat-listen-together-page'); if (!page) return; const settings = getSettings(); const contact = listenState.contact; const song = listenState.currentSong; // 设置用户头像 const userAvatarEl = document.getElementById('wechat-listen-user-avatar'); if (userAvatarEl) { if (settings.userAvatar) { userAvatarEl.innerHTML = ``; } else { userAvatarEl.textContent = (settings.userName || 'User').charAt(0); } } // 设置AI头像 const aiAvatarEl = document.getElementById('wechat-listen-ai-avatar'); if (aiAvatarEl) { const firstChar = contact.name ? contact.name.charAt(0) : '?'; if (contact.avatar) { aiAvatarEl.innerHTML = ``; } else { aiAvatarEl.textContent = firstChar; } } // 设置歌曲信息 const coverEl = document.getElementById('wechat-listen-cover'); const nameEl = document.getElementById('wechat-listen-song-name'); const artistEl = document.getElementById('wechat-listen-song-artist'); if (coverEl && song.cover) coverEl.src = song.cover; if (nameEl) nameEl.textContent = song.name || '未知歌曲'; if (artistEl) artistEl.textContent = song.artist || '未知歌手'; // 初始化播放按钮状态 updatePlayButton(); page.classList.remove('hidden'); bindListenEvents(); } /** * 隐藏一起听主页面 */ function hideListenTogetherPage() { const page = document.getElementById('wechat-listen-together-page'); if (page) page.classList.add('hidden'); } // ========== 核心逻辑 ========== /** * 开始一起听 * @param {Object} song - 歌曲信息 * @param {number} contactIndex - 联系人索引 */ export async function startListenTogether(song, contactIndex = currentChatIndex) { if (listenState.isActive) return; if (contactIndex < 0) { showToast('请先选择聊天对象'); return; } const settings = getSettings(); const contact = settings.contacts[contactIndex]; if (!contact) { showToast('联系人不存在'); return; } // 初始化状态 listenState = { isActive: true, isConnected: false, currentSong: song, messages: [], contact: contact, contactIndex: contactIndex, startTime: null, isPlaying: false, connectTimeout: null, dotsInterval: null, chatVisible: false, audioElement: null, progressInterval: null, pauseTimeout: null, playMode: 'normal', songsSinceAIChange: 0, }; // 隐藏搜索页,显示等待页面 hideListenSearchPage(); showWaitingPage(song, contact); // 开始等待动画 startWaitingAnimation(); // 2-4秒后AI"加入" const joinDelay = 2000 + Math.random() * 2000; listenState.connectTimeout = setTimeout(() => { if (listenState.isActive && !listenState.isConnected) { onAIJoined(); } }, joinDelay); } /** * 开始等待动画 */ function startWaitingAnimation() { const dotsEl = document.getElementById('wechat-listen-waiting-dots'); if (!dotsEl) return; let dotCount = 0; clearInterval(listenState.dotsInterval); listenState.dotsInterval = setInterval(() => { dotCount = (dotCount + 1) % 4; dotsEl.textContent = '.'.repeat(dotCount || 1); }, 500); } /** * AI加入后 */ async function onAIJoined() { listenState.isConnected = true; listenState.startTime = Date.now(); clearInterval(listenState.dotsInterval); clearTimeout(listenState.connectTimeout); // 隐藏等待页面,显示主页面 hideWaitingPage(); showListenTogetherPage(); // 开始播放音乐 await playListenSong(); // AI主动发送第一条消息 await triggerAIGreeting(); } /** * 播放当前歌曲 */ async function playListenSong() { const song = listenState.currentSong; if (!song) return; try { // 使用music.js的playMusic函数 await playMusic(song.id, song.platform, song.name, song.artist); listenState.isPlaying = true; updatePlayButton(); startProgressUpdate(); // 监听歌曲结束事件 const audio = document.getElementById('wechat-music-audio'); if (audio) { listenState.audioElement = audio; audio.addEventListener('ended', onSongEnded); } } catch (e) { console.error('[可乐] 一起听播放失败:', e); showToast('播放失败'); } } /** * 歌曲结束时的处理 */ async function onSongEnded() { if (!listenState.isActive) return; listenState.isPlaying = false; updatePlayButton(); // 计数器+1 listenState.songsSinceAIChange++; // 20%几率AI换歌,或保底5首必换(所有模式下都有效) if (Math.random() < 0.2 || listenState.songsSinceAIChange >= 5) { listenState.songsSinceAIChange = 0; // 重置计数器 await aiSelectSong(); return; } // 根据播放模式处理 if (listenState.playMode === 'loop') { // 单曲循环:重新播放当前歌曲 await playListenSong(); } else if (listenState.playMode === 'shuffle') { // 随机播放:播放随机歌曲 await playRandomSong(); } // 正常模式不做处理,等待用户操作 } /** * 切换单曲循环模式 */ function toggleLoopMode() { const loopBtn = document.getElementById('wechat-listen-loop-btn'); const shuffleBtn = document.getElementById('wechat-listen-shuffle-btn'); if (listenState.playMode === 'loop') { // 取消单曲循环 listenState.playMode = 'normal'; loopBtn?.classList.remove('active'); showToast('已关闭单曲循环'); } else { // 开启单曲循环 listenState.playMode = 'loop'; loopBtn?.classList.add('active'); shuffleBtn?.classList.remove('active'); showToast('单曲循环'); } } /** * 切换随机播放模式 */ function toggleShuffleMode() { const loopBtn = document.getElementById('wechat-listen-loop-btn'); const shuffleBtn = document.getElementById('wechat-listen-shuffle-btn'); if (listenState.playMode === 'shuffle') { // 取消随机播放 listenState.playMode = 'normal'; shuffleBtn?.classList.remove('active'); showToast('已关闭随机播放'); } else { // 开启随机播放 listenState.playMode = 'shuffle'; shuffleBtn?.classList.add('active'); loopBtn?.classList.remove('active'); showToast('随机播放'); } } /** * 随机播放歌曲 */ async function playRandomSong() { try { // 随机关键词列表 const keywords = [ '热门', '流行', '经典', '抖音', '网红', '伤感', '甜蜜', '治愈', '怀旧', '浪漫', '周杰伦', '林俊杰', '邓紫棋', '薛之谦', '陈奕迅', 'Taylor Swift', 'Ed Sheeran', 'Bruno Mars', '说唱', '民谣', '摇滚', '电子', 'R&B' ]; const randomKeyword = keywords[Math.floor(Math.random() * keywords.length)]; // 搜索歌曲 const results = await searchMusic(randomKeyword); if (results && results.length > 0) { // 随机选择一首 const randomIndex = Math.floor(Math.random() * Math.min(results.length, 10)); const newSong = results[randomIndex]; listenState.currentSong = newSong; // 更新界面 const coverEl = document.getElementById('wechat-listen-cover'); const nameEl = document.getElementById('wechat-listen-song-name'); const artistEl = document.getElementById('wechat-listen-song-artist'); if (coverEl && newSong.cover) coverEl.src = newSong.cover; if (nameEl) nameEl.textContent = newSong.name || '未知歌曲'; if (artistEl) artistEl.textContent = newSong.artist || '未知歌手'; // 播放 await playMusic(newSong.id, newSong.platform, newSong.name, newSong.artist); listenState.isPlaying = true; updatePlayButton(); // AI对新歌的反应 await triggerAIAutoNextReaction(newSong); } } catch (e) { console.error('[可乐] 随机播放失败:', e); } } /** * AI选择歌曲(20%几率触发) */ async function aiSelectSong() { if (!listenState.isConnected || !listenState.contact) return; try { const { callListenTogetherAI } = await import('./ai.js'); // 获取最近5条消息 const recentMessages = listenState.messages.slice(-5); const messagesContext = recentMessages.map(m => `${m.role === 'user' ? '用户' : '你'}: ${m.content}` ).join('\n'); // 构建AI选歌的prompt const prompt = `[这首歌播放完了,请你选择下一首想听的歌,根据你们刚才的聊天氛围和你的喜好来选。 最近的对话: ${messagesContext || '(刚开始听歌)'} 请回复格式: 1. 先说一句为什么想听这首歌(简短自然,1-2句话) 2. 然后用 [换歌:歌名] 格式选择歌曲 示例:突然想听点轻快的|||[换歌:晴天]]`; showListenTypingIndicator(); const aiResponse = await callListenTogetherAI(listenState.contact, prompt, recentMessages, listenState.currentSong); hideListenTypingIndicator(); if (aiResponse) { // 处理回复 const parts = splitAIMessages(aiResponse); for (const part of parts) { const text = filterListenMessage(part); // 检查是否包含换歌标签 const changeSongMatch = text.match(/\[换歌[::]\s*(.+?)\]/); if (changeSongMatch) { const songKeyword = changeSongMatch[1].trim(); // 显示AI的说明文字(去掉换歌标签) const displayText = text.replace(/\[换歌[::][^\]]*\]/g, '').trim(); if (displayText) { addListenMessage('ai', displayText); } // 搜索并播放新歌 await changeSongByKeyword(songKeyword, true); break; } else if (text) { addListenMessage('ai', text); } } } } catch (err) { console.error('[可乐] AI选歌失败:', err); hideListenTypingIndicator(); } } /** * 根据关键词换歌 */ async function changeSongByKeyword(keyword, isAIChange = false) { try { const results = await searchMusic(keyword); if (results && results.length > 0) { const newSong = results[0]; listenState.currentSong = newSong; // 更新界面 const coverEl = document.getElementById('wechat-listen-cover'); const nameEl = document.getElementById('wechat-listen-song-name'); const artistEl = document.getElementById('wechat-listen-song-artist'); if (coverEl) coverEl.src = newSong.cover || ''; if (nameEl) nameEl.textContent = newSong.name || '未知歌曲'; if (artistEl) artistEl.textContent = newSong.artist || '未知歌手'; // 播放新歌 await playListenSong(); // 如果不是AI换的歌,通知AI对换歌做出反应 if (!isAIChange) { await triggerAISongChangeReaction(newSong); } } else { showToast('未找到歌曲'); } } catch (e) { console.error('[可乐] 换歌失败:', e); showToast('换歌失败'); } } /** * AI对用户换歌的反应 */ async function triggerAISongChangeReaction(newSong) { if (!listenState.isConnected || !listenState.contact) return; try { const { callListenTogetherAI } = await import('./ai.js'); const prompt = `[用户换了一首歌,新歌是《${newSong.name}》- ${newSong.artist}。请对换歌做出反应,表达你对这首歌的看法或感受。记得发送2-4条消息,每条换行分隔]`; showListenTypingIndicator(); const aiResponse = await callListenTogetherAI( listenState.contact, prompt, listenState.messages.slice(-5), newSong ); hideListenTypingIndicator(); await processAIResponse(aiResponse); } catch (err) { hideListenTypingIndicator(); console.error('[可乐] AI换歌反应失败:', err); } } /** * AI主动发送开场消息 */ async function triggerAIGreeting() { if (!listenState.isConnected || !listenState.contact) return; showListenTypingIndicator(); try { const { callListenTogetherAI } = await import('./ai.js'); const song = listenState.currentSong; const prompt = `[用户邀请你一起听歌,歌曲是《${song.name}》- ${song.artist},你刚刚加入了一起听。请用你的方式自然地打个招呼,并对这首歌发表一些看法。记得发送2-4条消息,每条换行分隔,像真实聊天一样有层次感]`; const aiResponse = await callListenTogetherAI( listenState.contact, prompt, [], song ); hideListenTypingIndicator(); await processAIResponse(aiResponse); } catch (err) { hideListenTypingIndicator(); console.error('[可乐] 一起听AI开场白失败:', err); } } /** * 过滤消息 - 只允许纯文字,过滤所有特殊格式 */ function filterListenMessage(text) { if (!text) return ''; let reply = text.trim(); // 过滤 meme 表情包 reply = reply.replace(/<\s*meme\s*>[\s\S]*?<\s*\/\s*meme\s*>/gi, '').trim(); // 过滤 [表情:xxx] reply = reply.replace(/\[表情[::][^\]]*\]/g, '').trim(); // 过滤 [照片:xxx] reply = reply.replace(/\[照片[::][^\]]*\]/g, '').trim(); // 过滤 [语音:xxx] reply = reply.replace(/\[语音[::][^\]]*\]/g, '').trim(); // 过滤 [音乐:xxx](但保留[换歌:xxx]) reply = reply.replace(/\[(?:分享)?音乐[::][^\]]*\]/g, '').trim(); // 过滤 [回复:xxx] 引用格式 reply = reply.replace(/\[回复[::][^\]]*\]/g, '').trim(); // 过滤中文小括号内容(动作/语气描述) reply = reply.replace(/([^)]*)/g, '').trim(); // 过滤英文小括号内容 reply = reply.replace(/\([^)]*\)/g, '').trim(); return reply; } /** * 处理AI回复 - 纯文字消息,按换行分条发送 */ async function processAIResponse(aiResponse) { if (!aiResponse) return; // 先用 ||| 分割,再按换行分割 let parts = splitAIMessages(aiResponse); // 对每个部分再按换行分割 const allParts = []; for (const part of parts) { // 按换行符分割成多条消息 const lines = part.split(/\n+/).map(l => l.trim()).filter(l => l); allParts.push(...lines); } for (const part of allParts) { if (!listenState.isConnected) break; let reply = filterListenMessage(part); if (!reply) continue; // 检查是否包含换歌标签 const changeSongMatch = reply.match(/\[换歌[::]\s*(.+?)\]/); if (changeSongMatch) { const songKeyword = changeSongMatch[1].trim(); // 显示AI的说明文字(去掉换歌标签) const displayText = reply.replace(/\[换歌[::][^\]]*\]/g, '').trim(); if (displayText) { showListenTypingIndicator(); await sleep(400 + Math.random() * 600); hideListenTypingIndicator(); addListenMessage('ai', displayText); } // 搜索并播放新歌 await changeSongByKeyword(songKeyword, true); continue; } // 直接发送纯文字消息 showListenTypingIndicator(); await sleep(400 + Math.random() * 600); hideListenTypingIndicator(); if (listenState.isConnected) { addListenMessage('ai', reply); } } } /** * 用户发送消息 */ async function sendListenMessage() { const input = document.getElementById('wechat-listen-input-text'); if (!input) return; const message = input.value.trim(); if (!message || !listenState.isConnected) return; input.value = ''; // 显示用户消息 addListenMessage('user', message); // 显示typing showListenTypingIndicator(); try { const { callListenTogetherAI } = await import('./ai.js'); const song = listenState.currentSong; const aiResponse = await callListenTogetherAI( listenState.contact, message, listenState.messages.slice(0, -1), song ); hideListenTypingIndicator(); await processAIResponse(aiResponse); } catch (err) { hideListenTypingIndicator(); console.error('[可乐] 一起听消息回复失败:', err); } } // ========== UI 更新 ========== /** * 显示typing指示器 */ function showListenTypingIndicator() { const messagesEl = document.getElementById('wechat-listen-messages'); if (!messagesEl) return; messagesEl.classList.remove('hidden'); hideListenTypingIndicator(); const typingDiv = document.createElement('div'); typingDiv.className = 'wechat-listen-msg ai'; typingDiv.id = 'wechat-listen-typing'; typingDiv.innerHTML = `
`; messagesEl.appendChild(typingDiv); messagesEl.scrollTop = messagesEl.scrollHeight; } /** * 隐藏typing指示器 */ function hideListenTypingIndicator() { const typingEl = document.getElementById('wechat-listen-typing'); if (typingEl) typingEl.remove(); } /** * 添加聊天消息 */ function addListenMessage(role, content) { const messagesEl = document.getElementById('wechat-listen-messages'); if (!messagesEl) return; messagesEl.classList.remove('hidden'); // 添加到状态 listenState.messages.push({ role, content, timestamp: Date.now() }); // 创建消息元素 const msgDiv = document.createElement('div'); msgDiv.className = `wechat-listen-msg ${role} fade-in`; msgDiv.textContent = content; messagesEl.appendChild(msgDiv); messagesEl.scrollTop = messagesEl.scrollHeight; // 限制显示的消息数量 const msgs = messagesEl.querySelectorAll('.wechat-listen-msg:not(#wechat-listen-typing)'); if (msgs.length > 15) { msgs[0].remove(); } } /** * 更新播放按钮状态 */ function updatePlayButton() { const playBtn = document.getElementById('wechat-listen-play-btn'); if (playBtn) { playBtn.innerHTML = listenState.isPlaying ? PAUSE_ICON : PLAY_ICON; } // 更新唱片旋转 const disc = document.getElementById('wechat-listen-disc'); if (disc) { if (listenState.isPlaying) { disc.classList.add('rotating'); disc.classList.remove('paused'); } else { disc.classList.add('paused'); } } } /** * 处理播放/暂停点击 * 暂停3秒后自动播放下一首 */ function handlePlayPauseClick() { togglePlay(); listenState.isPlaying = !listenState.isPlaying; updatePlayButton(); // 清除之前的暂停计时器 if (listenState.pauseTimeout) { clearTimeout(listenState.pauseTimeout); listenState.pauseTimeout = null; } // 如果暂停了,启动3秒后自动播放下一首的计时器 if (!listenState.isPlaying && listenState.isActive) { listenState.pauseTimeout = setTimeout(async () => { if (!listenState.isPlaying && listenState.isActive) { await autoPlayNextSong(); } }, 3000); } } /** * 自动播放下一首歌(暂停3秒后触发) */ async function autoPlayNextSong() { if (!listenState.isActive || !listenState.currentSong) return; try { // 搜索相似歌曲或随机歌曲 const currentSong = listenState.currentSong; const keyword = currentSong.artist || currentSong.name; const results = await searchMusic(keyword); if (results && results.length > 1) { // 找一首不同的歌 const newSong = results.find(s => s.id !== currentSong.id) || results[1]; listenState.currentSong = newSong; // 更新界面 const coverEl = document.getElementById('wechat-listen-cover'); const nameEl = document.getElementById('wechat-listen-song-name'); const artistEl = document.getElementById('wechat-listen-song-artist'); if (coverEl) coverEl.src = newSong.cover || ''; if (nameEl) nameEl.textContent = newSong.name || '未知歌曲'; if (artistEl) artistEl.textContent = newSong.artist || '未知歌手'; // 播放新歌 await playListenSong(); // AI 对自动换歌做出反应 await triggerAIAutoNextReaction(newSong); } } catch (e) { console.error('[可乐] 自动播放下一首失败:', e); } } /** * AI 对自动换歌的反应 */ async function triggerAIAutoNextReaction(newSong) { if (!listenState.isConnected || !listenState.contact) return; try { const { callListenTogetherAI } = await import('./ai.js'); const prompt = `[歌曲自动切换到了《${newSong.name}》- ${newSong.artist},请对新歌做出反应,发送2-3条消息,每条换行分隔]`; showListenTypingIndicator(); const aiResponse = await callListenTogetherAI( listenState.contact, prompt, listenState.messages.slice(-3), newSong ); hideListenTypingIndicator(); await processAIResponse(aiResponse); } catch (err) { hideListenTypingIndicator(); console.error('[可乐] AI自动换歌反应失败:', err); } } /** * 开始进度条更新 */ function startProgressUpdate() { clearInterval(listenState.progressInterval); listenState.progressInterval = setInterval(() => { const audio = listenState.audioElement || document.getElementById('wechat-music-audio'); if (!audio) return; const currentTime = audio.currentTime || 0; const duration = audio.duration || 0; const progress = duration > 0 ? (currentTime / duration) * 100 : 0; const currentEl = document.getElementById('wechat-listen-current-time'); const durationEl = document.getElementById('wechat-listen-duration'); const fillEl = document.getElementById('wechat-listen-progress-fill'); const sliderEl = document.getElementById('wechat-listen-slider'); if (currentEl) currentEl.textContent = formatDuration(currentTime); if (durationEl) durationEl.textContent = formatDuration(duration); if (fillEl) fillEl.style.width = progress + '%'; if (sliderEl) sliderEl.value = progress; }, 500); } // ========== 事件绑定 ========== let listenEventsBound = false; let searchEventsBound = false; /** * 绑定搜索页面事件 */ export function bindListenSearchEvents() { if (searchEventsBound) return; searchEventsBound = true; // 返回按钮 document.getElementById('wechat-listen-search-back')?.addEventListener('click', () => { hideListenSearchPage(); }); // 搜索输入 const searchInput = document.getElementById('wechat-listen-search-input'); let searchTimeout = null; searchInput?.addEventListener('input', (e) => { clearTimeout(searchTimeout); searchTimeout = setTimeout(() => { doListenSearch(e.target.value.trim()); }, 500); }); searchInput?.addEventListener('keydown', (e) => { if (e.key === 'Enter') { clearTimeout(searchTimeout); doListenSearch(e.target.value.trim()); } }); // 搜索结果点击 document.getElementById('wechat-listen-search-results')?.addEventListener('click', (e) => { const item = e.target.closest('.wechat-listen-search-item'); if (!item) return; const song = { id: item.dataset.id, platform: item.dataset.platform, name: item.dataset.name, artist: item.dataset.artist, cover: item.dataset.cover, }; startListenTogether(song); }); } /** * 执行搜索 */ async function doListenSearch(keyword) { const resultsEl = document.getElementById('wechat-listen-search-results'); if (!resultsEl) return; if (!keyword) { resultsEl.innerHTML = '
输入关键词搜索歌曲
'; return; } resultsEl.innerHTML = '
搜索中...
'; try { const results = await searchMusic(keyword); if (!results || results.length === 0) { resultsEl.innerHTML = '
未找到结果
'; return; } let html = ''; for (const song of results) { html += `
${escapeHtml(song.name)}
${escapeHtml(song.artist)} - ${escapeHtml(song.platform)}
`; } resultsEl.innerHTML = html; } catch (err) { console.error('[可乐] 一起听搜索失败:', err); resultsEl.innerHTML = '
搜索失败
'; } } /** * 绑定一起听主页面事件 */ function bindListenEvents() { if (listenEventsBound) return; listenEventsBound = true; // 发送消息 document.getElementById('wechat-listen-send-btn')?.addEventListener('click', sendListenMessage); // 输入框回车发送 document.getElementById('wechat-listen-input-text')?.addEventListener('keypress', (e) => { if (e.key === 'Enter') { sendListenMessage(); } }); // 播放/暂停 document.getElementById('wechat-listen-play-btn')?.addEventListener('click', handlePlayPauseClick); // 搜索按钮 - 打开换歌面板 document.getElementById('wechat-listen-search-btn')?.addEventListener('click', showChangeSongPanel); // 单曲循环按钮 document.getElementById('wechat-listen-loop-btn')?.addEventListener('click', toggleLoopMode); // 随机播放按钮 document.getElementById('wechat-listen-shuffle-btn')?.addEventListener('click', toggleShuffleMode); // 结束按钮 document.getElementById('wechat-listen-end-btn')?.addEventListener('click', exitListenTogether); // 换歌面板关闭 document.getElementById('wechat-listen-change-close')?.addEventListener('click', hideChangeSongPanel); // 换歌搜索 const changeInput = document.getElementById('wechat-listen-change-input'); let changeSearchTimeout = null; changeInput?.addEventListener('input', (e) => { clearTimeout(changeSearchTimeout); changeSearchTimeout = setTimeout(() => { doChangeSongSearch(e.target.value.trim()); }, 500); }); // 换歌搜索结果点击 document.getElementById('wechat-listen-change-results')?.addEventListener('click', (e) => { const item = e.target.closest('.wechat-listen-change-item'); if (!item) return; const song = { id: item.dataset.id, platform: item.dataset.platform, name: item.dataset.name, artist: item.dataset.artist, cover: item.dataset.cover, }; changeSong(song); hideChangeSongPanel(); }); // 取消一起听 document.getElementById('wechat-listen-cancel')?.addEventListener('click', cancelListenTogether); // 返回按钮(主页面的返回) document.getElementById('wechat-listen-back-btn')?.addEventListener('click', exitListenTogether); // 进度条拖动 const slider = document.getElementById('wechat-listen-slider'); slider?.addEventListener('change', (e) => { const audio = listenState.audioElement || document.getElementById('wechat-music-audio'); if (audio && audio.duration) { audio.currentTime = (e.target.value / 100) * audio.duration; } }); } /** * 背景颜色映射 */ const LISTEN_BACKGROUNDS = { 'starry': 'linear-gradient(135deg, #1a1a2e 0%, #16213e 30%, #0f3460 60%, #533483 100%)', 'orange': 'linear-gradient(135deg, #f97316 0%, #ea580c 50%, #c2410c 100%)', 'pink': 'linear-gradient(135deg, #ec4899 0%, #f472b6 50%, #f9a8d4 100%)', 'white': '#fff' }; let currentBg = 'starry'; /** * 切换颜色选择器显示 */ function toggleColorPicker() { const picker = document.getElementById('wechat-listen-color-picker'); if (picker) { picker.classList.toggle('hidden'); } } /** * 隐藏颜色选择器 */ function hideColorPicker() { const picker = document.getElementById('wechat-listen-color-picker'); if (picker) { picker.classList.add('hidden'); } } /** * 处理颜色选项点击 */ function handleColorOptionClick(e) { const option = e.target.closest('.wechat-listen-color-option'); if (!option) return; const bgType = option.dataset.bg; if (!bgType || !LISTEN_BACKGROUNDS[bgType]) return; // 更新页面背景 const page = document.getElementById('wechat-listen-together-page'); if (page) { page.style.background = LISTEN_BACKGROUNDS[bgType]; // 如果是白色背景,需要调整文字颜色 if (bgType === 'white') { page.classList.add('light-bg'); } else { page.classList.remove('light-bg'); } } // 更新选中状态 document.querySelectorAll('.wechat-listen-color-option').forEach(opt => { opt.classList.remove('active'); }); option.classList.add('active'); currentBg = bgType; hideColorPicker(); } /** * 显示换歌面板 */ function showChangeSongPanel() { const panel = document.getElementById('wechat-listen-change-panel'); if (panel) { panel.classList.remove('hidden'); document.getElementById('wechat-listen-change-input')?.focus(); } } /** * 隐藏换歌面板 */ function hideChangeSongPanel() { const panel = document.getElementById('wechat-listen-change-panel'); if (panel) panel.classList.add('hidden'); } /** * 换歌搜索 */ async function doChangeSongSearch(keyword) { const resultsEl = document.getElementById('wechat-listen-change-results'); if (!resultsEl) return; if (!keyword) { resultsEl.innerHTML = ''; return; } resultsEl.innerHTML = '
搜索中...
'; try { const results = await searchMusic(keyword); if (!results || results.length === 0) { resultsEl.innerHTML = '
未找到结果
'; return; } let html = ''; for (const song of results.slice(0, 10)) { html += `
${escapeHtml(song.name)}
${escapeHtml(song.artist)}
`; } resultsEl.innerHTML = html; } catch (err) { resultsEl.innerHTML = '
搜索失败
'; } } /** * 换歌 */ async function changeSong(song) { listenState.currentSong = song; // 更新界面 const coverEl = document.getElementById('wechat-listen-cover'); const nameEl = document.getElementById('wechat-listen-song-name'); const artistEl = document.getElementById('wechat-listen-song-artist'); if (coverEl) coverEl.src = song.cover || ''; if (nameEl) nameEl.textContent = song.name || '未知歌曲'; if (artistEl) artistEl.textContent = song.artist || '未知歌手'; // 播放新歌 await playListenSong(); // 通知AI对换歌做出反应 await triggerAISongChangeReaction(song); } /** * 取消一起听(等待页面) */ function cancelListenTogether() { clearInterval(listenState.dotsInterval); clearTimeout(listenState.connectTimeout); clearInterval(listenState.progressInterval); clearTimeout(listenState.pauseTimeout); hideWaitingPage(); listenState = { isActive: false, isConnected: false, currentSong: null, messages: [], contact: null, contactIndex: -1, startTime: null, isPlaying: false, connectTimeout: null, dotsInterval: null, chatVisible: false, audioElement: null, progressInterval: null, pauseTimeout: null, playMode: 'normal', songsSinceAIChange: 0, }; } /** * 退出一起听 */ export async function exitListenTogether() { if (!listenState.isActive) return; clearInterval(listenState.dotsInterval); clearTimeout(listenState.connectTimeout); clearInterval(listenState.progressInterval); clearTimeout(listenState.pauseTimeout); // 移除音频结束监听 if (listenState.audioElement) { listenState.audioElement.removeEventListener('ended', onSongEnded); } // 保存一起听记录(不显示在聊天里) const contact = listenState.contact; const song = listenState.currentSong; const messages = [...listenState.messages]; if (contact && messages.length > 0) { saveListenHistory(); } // 隐藏所有页面 hideWaitingPage(); hideListenTogetherPage(); hideListenSearchPage(); hideChangeSongPanel(); // 重置状态 listenState = { isActive: false, isConnected: false, currentSong: null, messages: [], contact: null, contactIndex: -1, startTime: null, isPlaying: false, connectTimeout: null, dotsInterval: null, chatVisible: false, audioElement: null, progressInterval: null, pauseTimeout: null, playMode: 'normal', songsSinceAIChange: 0, }; // AI 结束一起听后的回复 if (contact && song) { await triggerAIListenEndReply(contact, song, messages); } } /** * AI 结束一起听后的回复 */ async function triggerAIListenEndReply(contact, song, messages) { try { const { callAI } = await import('./ai.js'); const { appendMessage, showTypingIndicator, hideTypingIndicator } = await import('./chat.js'); // 显示打字指示器 showTypingIndicator(contact); // 构建提示 const recentMsgs = messages.slice(-5).map(m => `${m.role === 'user' ? '用户' : '你'}: ${m.content}` ).join('\n'); const prompt = `[刚才和用户一起听了《${song.name}》- ${song.artist},一起听已经结束了。请根据刚才的聊天氛围,说一句告别或感想,简短自然,不要使用任何特殊格式标签。 刚才的聊天: ${recentMsgs || '(没有聊天)'}]`; const aiResponse = await callAI(contact, prompt); hideTypingIndicator(); if (aiResponse && aiResponse.trim()) { let reply = aiResponse.split('|||')[0].trim(); reply = reply.replace(/^\[.*?\]\s*/, ''); reply = reply.replace(/<\s*meme\s*>[\s\S]*?<\s*\/\s*meme\s*>/gi, '').trim(); if (reply) { const settings = getSettings(); const now = new Date(); const timeStr = now.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }).replace(/\//g, '-'); // 添加到聊天历史 if (!contact.chatHistory) contact.chatHistory = []; contact.chatHistory.push({ role: 'assistant', content: reply, time: timeStr, timestamp: Date.now() }); appendMessage('assistant', reply, contact); contact.lastMessage = reply; requestSave(); refreshChatList(); } } } catch (err) { console.error('[可乐] AI一起听结束回复失败:', err); // 隐藏typing import('./chat.js').then(m => m.hideTypingIndicator()); } } /** * 保存一起听历史记录(不显示在聊天中) */ function saveListenHistory() { const settings = getSettings(); const contact = listenState.contact; if (!contact) return; const now = new Date(); const timeStr = `${now.getFullYear()}-${(now.getMonth() + 1).toString().padStart(2, '0')}-${now.getDate().toString().padStart(2, '0')} ${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`; // 计算时长 let durationStr = '00:00'; if (listenState.startTime) { const elapsed = Math.floor((Date.now() - listenState.startTime) / 1000); const minutes = Math.floor(elapsed / 60).toString().padStart(2, '0'); const seconds = (elapsed % 60).toString().padStart(2, '0'); durationStr = `${minutes}:${seconds}`; } // 保存到联系人的一起听历史(仅保存记录,不显示在聊天中) if (!Array.isArray(contact.listenHistory)) { contact.listenHistory = []; } contact.listenHistory.push({ song: listenState.currentSong, duration: durationStr, time: timeStr, timestamp: Date.now(), messages: listenState.messages.map(m => ({ role: m.role, content: m.content })) }); // 限制历史记录数量 if (contact.listenHistory.length > 50) { contact.listenHistory = contact.listenHistory.slice(-50); } requestSave(); } /** * 初始化一起听功能 */ export function initListenTogether() { bindListenSearchEvents(); }