mirror of
https://github.com/hanxi/xiaomusic.git
synced 2026-05-16 10:56:46 +08:00
feat: 增加搜索多结果选择功能 (#864)
新增功能: - 搜索结果多条记录时通过TTS告知用户匹配数量 - 支持用户重新呼叫'第X个'来选择并播放指定歌曲 - 实现记忆机制:选择后保留待选列表,支持持续多次选择 - 新增配置项 fuzzy_match_max_results 控制最大返回数量(默认100) 优化改进: - 搜索结果排序:从随机排序改为按文件名自然排序(custom_sort_key) - 日志输出优化:多结果时每个歌曲分行显示,带序号便于查看 修改文件: - command_handler.py: 添加待选择状态检查逻辑,优先匹配'第X个'指令 - config.py: 新增 fuzzy_match_max_results 配置项 - device_player.py: 添加 _pending_selection 属性、多结果处理逻辑、handle_selection 方法、优化日志格式 - music_library.py: 将 random.shuffle 改为 sort(key=custom_sort_key) 自然排序 - xiaomusic.py: 新增 select_index 命令处理方法
This commit is contained in:
@@ -88,6 +88,14 @@ class CommandHandler:
|
||||
Returns:
|
||||
tuple: (命令值, 命令参数),未匹配返回 (None, None)
|
||||
"""
|
||||
# 检查是否有待选择的歌曲
|
||||
if device._pending_selection:
|
||||
patternarg = r"^第?([零一二三四五六七八九十百千万亿]+)[个首条集]$"
|
||||
matcharg = re.match(patternarg, query)
|
||||
if matcharg:
|
||||
self.log.info(f"匹配到选择指令. query:{query}")
|
||||
return "select_index", query
|
||||
|
||||
# 优先处理完全匹配
|
||||
opvalue = self.check_full_match_cmd(device, query, ctrl_panel)
|
||||
if opvalue:
|
||||
|
||||
@@ -142,6 +142,8 @@ class Config:
|
||||
enable_fuzzy_match: bool = (
|
||||
os.getenv("XIAOMUSIC_ENABLE_FUZZY_MATCH", "true").lower() == "true"
|
||||
)
|
||||
# 模糊搜索返回的最大结果数量(用于多结果选择功能)
|
||||
fuzzy_match_max_results: int = int(os.getenv("XIAOMUSIC_FUZZY_MATCH_MAX_RESULTS", "100"))
|
||||
stop_tts_msg: str = os.getenv("XIAOMUSIC_STOP_TTS_MSG", "收到,再见")
|
||||
enable_config_example: bool = False
|
||||
|
||||
|
||||
@@ -77,6 +77,8 @@ class XiaoMusicDevice:
|
||||
# 关机定时器
|
||||
self._stop_timer = None
|
||||
self._last_cmd = None
|
||||
self._pending_selection = None
|
||||
self._pending_selection_count = 0
|
||||
self.update_playlist()
|
||||
|
||||
# 添加歌曲定时器
|
||||
@@ -237,6 +239,12 @@ class XiaoMusicDevice:
|
||||
search_key: 搜索关键词
|
||||
allow_download: 是否允许下载(True: _play行为,False: playlocal行为)
|
||||
"""
|
||||
# 清除旧的待选择状态
|
||||
if self._pending_selection:
|
||||
self.log.info(f"清除旧的待选择状态,重新搜索: {name}")
|
||||
self._pending_selection = None
|
||||
self._pending_selection_count = 0
|
||||
|
||||
# 初始检查逻辑
|
||||
if not search_key and not name:
|
||||
if self.check_play_next():
|
||||
@@ -253,9 +261,21 @@ class XiaoMusicDevice:
|
||||
self.log.info(f"没有歌曲播放了 name:{name} search_key:{search_key}")
|
||||
return
|
||||
|
||||
# 模糊搜索
|
||||
names = self.xiaomusic.music_library.find_real_music_name(name, n=1)
|
||||
self.log.info(f"play_internal. names:{names} {len(names)}")
|
||||
# 模糊搜索(支持多结果选择)
|
||||
max_results = self.config.fuzzy_match_max_results
|
||||
names = self.xiaomusic.music_library.find_real_music_name(name, n=max_results)
|
||||
self.log.info(f"play_internal. 搜索关键词:{name} 匹配数量:{len(names)}")
|
||||
if len(names) > 1:
|
||||
for idx, music_name in enumerate(names, 1):
|
||||
self.log.info(f" 第{idx}个: {music_name}")
|
||||
|
||||
if len(names) > 1:
|
||||
self._pending_selection = names
|
||||
self._pending_selection_count = len(names)
|
||||
selection_text = f"共找到{len(names)}条匹配记录,请重新呼叫小爱同学并告诉她第几个"
|
||||
self.log.info(selection_text)
|
||||
await self.xiaomusic.do_tts(self.did, selection_text)
|
||||
return
|
||||
|
||||
if not names:
|
||||
# 检查本地是否存在歌曲,不存在则根据参数决定是否下载
|
||||
@@ -1041,3 +1061,18 @@ class XiaoMusicDevice:
|
||||
if name in music_list.get("所有电台", []):
|
||||
return "所有电台"
|
||||
return "全部"
|
||||
|
||||
async def handle_selection(self, index):
|
||||
"""处理用户选择第几个歌曲
|
||||
|
||||
Args:
|
||||
index: 用户选择的序号(从1开始)
|
||||
"""
|
||||
if not self._pending_selection or index < 1 or index > len(self._pending_selection):
|
||||
await self.xiaomusic.do_tts(self.did, "选择无效")
|
||||
return
|
||||
|
||||
selected_name = self._pending_selection[index - 1]
|
||||
self.log.info(f"用户选择了第{index}个: {selected_name}")
|
||||
# 保持待选择状态不变,支持用户继续选择其他歌曲
|
||||
await self._playmusic(selected_name)
|
||||
|
||||
@@ -596,7 +596,7 @@ class MusicLibrary:
|
||||
if name in real_names:
|
||||
return [name]
|
||||
|
||||
# 音乐不在查找结果同时n大于1, 模糊匹配模式,扩大范围再找,最后保留随机 n 个
|
||||
# 音乐不在查找结果同时n大于1, 模糊匹配模式,扩大范围再找,最后按文件名自然排序
|
||||
if n > 1:
|
||||
real_names = find_best_match(
|
||||
name,
|
||||
@@ -605,7 +605,7 @@ class MusicLibrary:
|
||||
n=n * 2,
|
||||
extra_search_index=self._extra_index_search,
|
||||
)
|
||||
random.shuffle(real_names)
|
||||
real_names.sort(key=custom_sort_key)
|
||||
self.log.info(f"没找到歌曲【{name}】")
|
||||
return real_names[:n]
|
||||
|
||||
|
||||
@@ -507,6 +507,23 @@ class XiaoMusic:
|
||||
return
|
||||
await self.do_tts(did, f"播放列表{list_name}中找不到第${index}个")
|
||||
|
||||
# 口令:选择第几个
|
||||
async def select_index(self, did="", arg1="", **kwargs):
|
||||
patternarg = r"^第?([零一二三四五六七八九十百千万亿]+)[个首条集]$"
|
||||
matcharg = re.match(patternarg, arg1)
|
||||
if not matcharg:
|
||||
return
|
||||
|
||||
chinese_index = matcharg.groups()[0]
|
||||
index = chinese_to_number(chinese_index)
|
||||
|
||||
device = self.device_manager.devices.get(did)
|
||||
if not device:
|
||||
self.log.warning(f"设备 did:{did} 不存在")
|
||||
return
|
||||
|
||||
await device.handle_selection(index)
|
||||
|
||||
# 口令:播放歌曲
|
||||
async def play(self, did="", arg1="", **kwargs):
|
||||
parts = arg1.split("|")
|
||||
|
||||
Reference in New Issue
Block a user