Skip to content

哒哒哒#2

Closed
NanoRocky wants to merge 7 commits intochuyegzs:mainfrom
NanoRocky:main
Closed

哒哒哒#2
NanoRocky wants to merge 7 commits intochuyegzs:mainfrom
NanoRocky:main

Conversation

@NanoRocky
Copy link
Copy Markdown
Collaborator

@NanoRocky NanoRocky commented Feb 18, 2026

Warning

提醒

     · 该版本移除了对 酷我音乐 和 酷狗音乐 的支持。

⚙️ 更改

     1. 增加对 PHP 版本 MetingAPI 的支持,可通过 api_type 切换。
     2. 增加对QQ 音乐卡片的支持,通过 use_music_card 开启。因 AstrBox 有 BUG ,暂无法发送 JSON 消息,需要等待 5208 修复后卡片功能才能正常工作。代码内已添加检测,在不支持的版本时会在控制台输出警告。
     3. 增加可用指令。
     4. “点歌” 变更为直接播放搜索结果第一首歌,“搜歌” 替代原本的搜索列表,“播歌” 用于选择列表内的音乐。
     5. BUG 修复,增加错误处理。
     ...

Copilot AI review requested due to automatic review settings February 18, 2026 16:07
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds support for PHP-based MetingAPI and QQ Music card features while removing support for Kugou and Kuwo music sources. The changes include significant refactoring of the download and session management logic, new configuration options, and enhanced music card functionality via an external signing API.

Changes:

  • Added api_type configuration to switch between Node.js (type 1) and PHP (type 2) MetingAPI implementations
  • Added QQ Music card support with use_music_card configuration and external signing API integration
  • Removed Kugou and Kuwo music source support from SOURCE_DISPLAY and related command handlers

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 20 comments.

File Description
main.py Major refactoring: removed lifecycle methods (initialize/terminate), rewrote download logic using Path objects, added music card implementation with JSON message support, simplified session management, updated command handlers to remove Kugou/Kuwo support
_conf_schema.json Added new configuration fields (api_type, use_music_card, api_sign_url), updated hints to reflect removed music sources, modified default value for use_music_card

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread _conf_schema.json
"use_music_card": {
"description": "使用音乐卡片",
"type": "bool",
"hint": "是否使用音乐卡片显示搜索结果,默认为 true",
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hint says "默认为 true" but the default is actually set to true on line 23. However, this creates a discrepancy with line 595 in main.py which uses False as the fallback. Consider either changing the default here to false to match the code, or updating the code to use true as the default.

Copilot uses AI. Check for mistakes.
Comment thread main.py
Comment thread main.py
Comment on lines +242 to +248
if "私网 IP" in reason or "本地地址" in reason:
# 允许用户明确配置私网 API,但给予警告
logger.warning(
f"Meting API 地址解析为私网地址 ({url}),这可能存在 SSRF 风险,请确保配置安全。"
)
else:
return False, reason
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SSRF protection is being weakened here. When a private/local IP is detected in the API URL, the code only logs a warning but continues execution (returns True). This allows potential SSRF attacks where users could configure the API to point to internal services. If this is intentional for legitimate local development scenarios, consider adding a configuration flag to explicitly allow private IPs rather than silently permitting them after just a warning.

Copilot uses AI. Check for mistakes.
Comment thread main.py Outdated
Comment on lines +409 to +410
# yield event.plain_result("歌曲播放完成")
pass
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The completion message "歌曲播放完成" has been removed (changed to pass). While this might be intentional to reduce spam, users may appreciate feedback that playback has completed, especially for longer songs with multiple segments. Consider making this configurable or at least document why this change was made.

Copilot uses AI. Check for mistakes.
Comment thread main.py
Comment on lines +571 to +574
except Exception as e:
logger.error(f"下载歌曲时发生错误: {e}")
retry_count += 1
await asyncio.sleep(1)
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The generic exception handler at lines 571-574 catches all exceptions, increments retry_count, but doesn't properly clean up the temp_file. Additionally, it retries for exceptions that shouldn't be retried (like programming errors). Consider being more specific about which exceptions warrant a retry, and ensure temp_file cleanup happens in all exception cases.

Copilot uses AI. Check for mistakes.
Comment thread main.py
if total_size == 0:
raise DownloadError("下载的文件为空")

if not detected_format:
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When detected_format is None, it defaults to "mp3" without validation. This could lead to incorrect file extensions being applied to non-MP3 files, potentially causing playback failures. Consider keeping the original behavior of raising an AudioFormatError when format detection fails, or at least log a warning about the assumption being made.

Suggested change
if not detected_format:
if not detected_format:
logger.warning(
"未能自动检测音频格式,默认使用 mp3 扩展名,这可能导致非 MP3 文件播放异常"
)

Copilot uses AI. Check for mistakes.
Comment thread main.py
Comment on lines 150 to +166
async def _ensure_initialized(self):
"""确保插件已初始化(惰性初始化)"""
if self._initialized:
return

if self._init_lock is None:
self._init_lock = asyncio.Lock()

async with self._init_lock:
if self._initialized:
return

logger.info("MetingAPI 点歌插件正在初始化...")

self._sessions_lock = asyncio.Lock()
self._audio_locks_lock = asyncio.Lock()
self._download_semaphore = asyncio.Semaphore(3)
if not self._http_session:
self._http_session = aiohttp.ClientSession(
timeout=REQUEST_TIMEOUT,
headers={
"Referer": "https://astrbot.app/",
"User-Agent": f"AstrBot/{VERSION}",
"UAK": "AstrBot/plugin_meting",
},
)

self._http_session = aiohttp.ClientSession(timeout=REQUEST_TIMEOUT)
if not self._cleanup_task:
self._cleanup_task = asyncio.create_task(self._periodic_cleanup())
self._initialized = True
logger.info("MetingAPI 点歌插件初始化完成")

async def initialize(self):
"""插件初始化(框架调用)"""
await self._ensure_initialized()

def _get_config(self, key: str, default=None, validator=None):
"""获取配置值,支持类型和范围校验

Args:
key: 配置键
default: 默认值
validator: 校验函数,接受配置值,返回校验是否通过

Returns:
配置值或默认值
"""
if not self.config:
return default

value = self.config.get(key, default)
if validator is not None and not validator(value):
return default

return value

def get_api_url(self) -> str:
"""获取 API 地址

Returns:
str: API 地址,如果未配置则返回空字符串
"""
return self._get_config("api_url", "", lambda x: isinstance(x, str) and x)

def get_default_source(self) -> str:
"""获取默认音源

Returns:
str: 默认音源,默认为 netease
"""
return self._get_config(
"default_source", "netease", lambda x: x in SOURCE_DISPLAY
)
self._initialized = True
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The initialize() method has been removed. If the AstrBot framework expects this method to be present for plugin lifecycle management, its absence could cause issues. The plugin now relies entirely on lazy initialization via _ensure_initialized(). Verify that this is compatible with the framework's plugin lifecycle expectations.

Copilot uses AI. Check for mistakes.
Comment thread main.py
Comment on lines +269 to 272
"""Get plugin data directory"""
# Fallback to a standard location if context lookup fails
return Path("data/plugins/astrbot_plugin_meting")

Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The get_data_dir method returns a hardcoded path and includes a comment suggesting it's a fallback. However, this method doesn't actually attempt to use the context object at all - it always returns the fallback path. This could lead to issues with plugin data isolation if multiple instances are running or if the plugin directory structure changes. Consider implementing proper context-based path resolution or removing the misleading comment.

Suggested change
"""Get plugin data directory"""
# Fallback to a standard location if context lookup fails
return Path("data/plugins/astrbot_plugin_meting")
"""Get plugin data directory, preferring context-specific location when available."""
# Try to use a context-provided data directory if it exists; fall back to a standard path.
context = getattr(self, "context", None)
data_dir_base: Path | None = None
if context is not None and hasattr(context, "data_dir"):
try:
data_dir_base = Path(context.data_dir)
except TypeError:
data_dir_base = None
if data_dir_base is None:
data_dir_base = Path("data") / "plugins"
return data_dir_base / "astrbot_plugin_meting"

Copilot uses AI. Check for mistakes.
Comment thread main.py Outdated
Comment on lines +387 to +392
# 转换为 Voice 组件? Or Record?
# AstrBot explicitly uses Record for file-based voice?
# Let's assume Record is fine as it's imported.
# But better to check what voice_result does.
# event.voice_result creates Chain([Record(...)]) usually.

Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments on lines 387-391 appear to be internal development notes that should be removed before merging. These comments discuss implementation uncertainty about whether to use Voice or Record components, which suggests incomplete decision-making during development.

Suggested change
# 转换为 Voice 组件? Or Record?
# AstrBot explicitly uses Record for file-based voice?
# Let's assume Record is fine as it's imported.
# But better to check what voice_result does.
# event.voice_result creates Chain([Record(...)]) usually.

Copilot uses AI. Check for mistakes.
Comment thread main.py Outdated
Comment on lines +822 to +823
except Exception as e:
yield event.plain_result(f"搜索失败: {e}")
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error handling is overly broad - all exceptions are caught and result in a generic "搜索失败" message. This makes debugging difficult and hides different error types (network errors, JSON parsing errors, API errors) from users. Consider providing more specific error messages based on exception type, similar to how it was handled in the original code.

Suggested change
except Exception as e:
yield event.plain_result(f"搜索失败: {e}")
except aiohttp.ClientError as e:
logger.error("搜索歌曲时发生网络错误(source=%s, kw=%s): %s", source, kw, e)
yield event.plain_result("搜索失败:网络错误,请稍后重试")
except asyncio.TimeoutError:
logger.error("搜索歌曲时请求超时(source=%s, kw=%s)", source, kw)
yield event.plain_result("搜索失败:请求超时,请稍后重试")
except (aiohttp.ContentTypeError, ValueError) as e:
logger.error("搜索歌曲时解析返回数据失败(source=%s, kw=%s): %s", source, kw, e)
yield event.plain_result("搜索失败:服务返回了无效数据,请稍后重试")
except Exception as e:
logger.exception("搜索歌曲时发生未知错误(source=%s, kw=%s): %s", source, kw, e)
yield event.plain_result("搜索失败,请稍后重试")

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread main.py

if success_count > 0:
yield event.plain_result("歌曲播放完成")
pass
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty pass statement after yield in success case. Either remove the line or add a return statement if early termination is intended.

Suggested change
pass

Copilot uses AI. Check for mistakes.
Comment thread main.py Outdated
Comment thread _conf_schema.json
Comment thread main.py
Comment on lines +550 to +552
if temp_file and temp_file.exists():
temp_file.unlink()
return None
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error recovery logic discards all errors silently. Consider logging which specific error type (DownloadError, UnsafeURLError, AudioFormatError) caused the failure before returning None, as this would aid debugging.

Copilot uses AI. Check for mistakes.
NanoRocky and others added 3 commits February 19, 2026 00:31
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@chuyegzs
Copy link
Copy Markdown
Owner

感谢提供喵,但酷狗和酷我支持暂不打算移除,官方@meting/core核心库并未移除,我打算添加自定义参数,用于适配更多版本的MetingAPI(有的人公益带会员但调用参数改过这种),点歌指令“搜歌”,搜歌 xxx,然后点歌 x即可,就不会太麻烦,音乐卡片在我这边个人不会使用,所以暂不同步,已更新1.0.4版本,可直接使用哦(音乐卡片真不会搞哈哈哈),在此非常感谢你喵~~~~~~~

@NanoRocky
Copy link
Copy Markdown
Collaborator Author

NanoRocky commented Feb 20, 2026

感谢提供喵,但酷狗和酷我支持暂不打算移除,官方@meting/core核心库并未移除,我打算添加自定义参数,用于适配更多版本的MetingAPI(有的人公益带会员但调用参数改过这种),点歌指令“搜歌”,搜歌 xxx,然后点歌 x即可,就不会太麻烦,音乐卡片在我这边个人不会使用,所以暂不同步,已更新1.0.4版本,可直接使用哦(音乐卡片真不会搞哈哈哈),在此非常感谢你喵~~~~~~~

噢噢,抱歉由于我一开始修改时并没有打算 PR 的,所以进行了较大改动。后面完善后,感觉可以提个 PR 的时候,已经改成这样了呜呜(
不过..个人感觉卡片的效果会好不少呢,不再需要服务器算力进行分段,也不再需要服务器缓存音频文件,直接返回歌曲源URL。音质更高,也有音乐封面,点击也可以直接跳转到音乐官方页面。
至于 MetingAPI ,我是添加了对 这个我修改过的 PHP 版本的接口 的支持..

emmm..
那就这样叭喵...
如果有需要的话,就辛苦初叶拷贝卡片相关代码到仓库,或者再补全一下酷我和酷狗的支持啦w,果咩纳塞~

@NanoRocky
Copy link
Copy Markdown
Collaborator Author

感谢提供喵,但酷狗和酷我支持暂不打算移除,官方@meting/core核心库并未移除,我打算添加自定义参数,用于适配更多版本的MetingAPI(有的人公益带会员但调用参数改过这种),点歌指令“搜歌”,搜歌 xxx,然后点歌 x即可,就不会太麻烦,音乐卡片在我这边个人不会使用,所以暂不同步,已更新1.0.4版本,可直接使用哦(音乐卡片真不会搞哈哈哈),在此非常感谢你喵~~~~~~~

噢噢,抱歉由于我一开始修改时并没有打算 PR 的,所以进行了较大改动。后面完善后,感觉可以提个 PR 的时候,已经改成这样了呜呜( 不过..个人感觉卡片的效果会好不少呢,不再需要服务器算力进行分段,也不再需要服务器缓存音频文件,直接返回歌曲源URL。也有音乐封面,点击也可以直接跳转到音乐官方页面。 至于 MetingAPI ,我是添加了对 这个我修改过的 PHP 版本的接口 的支持..

emmm.. 那就这样叭喵... 如果有需要的话,就辛苦初叶拷贝卡片相关代码到仓库,或者再补全一下酷我和酷狗的支持啦w,果咩纳塞~

或者也可以让我重新把模块加到新版本里~需要的话喵...

@chuyegzs
Copy link
Copy Markdown
Owner

音乐卡片本人不会用,老是提示出错,俺也不会配置所以没加喵,你可以给一下教程喵,目前1.0.4有1.Node API 2.PHP API 3.自定义参数。对于直接返回歌曲URL的话,会比较长,对于便捷性就会差一些,毕竟有那时间我直接去lx音乐添加源不更简单吗哈哈哈,当然有的人喜欢,所以说是可以有的

我做这个其实之前没打算上架的哈哈哈,就想自己用,然后想了想还是上架吧,有的人可能需要呢

关于酷狗和酷我,我自己的API目前只有网易云,QQ音乐和酷狗,酷我核心库支持但我修改的版本不支持,酷狗我不用,别人需要酷狗才开发的,我也只测试了QQ音乐与网易云,有需要可以测试测试,有问题就提Issues或PR喵~~~

在此非常感谢你提的PR喵~~~~~

@chuyegzs
Copy link
Copy Markdown
Owner

感谢提供喵,但酷狗和酷我支持暂不打算移除,官方@meting/core核心库并未移除,我打算添加自定义参数,用于适配更多版本的MetingAPI(有的人公益带会员但调用参数改过这种),点歌指令“搜歌”,搜歌 xxx,然后点歌 x即可,就不会太麻烦,音乐卡片在我这边个人不会使用,所以暂不同步,已更新1.0.4版本,可直接使用哦(音乐卡片真不会搞哈哈哈),在此非常感谢你喵~~~~~~~

噢噢,抱歉由于我一开始修改时并没有打算 PR 的,所以进行了较大改动。后面完善后,感觉可以提个 PR 的时候,已经改成这样了呜呜( 不过..个人感觉卡片的效果会好不少呢,不再需要服务器算力进行分段,也不再需要服务器缓存音频文件,直接返回歌曲源URL。也有音乐封面,点击也可以直接跳转到音乐官方页面。至于 MetingAPI ,我是添加了对 这个我修改过的 PHP 版本的接口的支持..
嗯......那就这样叭喵...如果有需要的话,就辛苦初叶拷贝卡片相关代码到仓库,或者再补全一下酷我和酷狗的支持啦w,果咩纳塞~

或者也可以让我重新把模块加到新版本里~需要的话喵...

我会将你拉入贡献的,谢谢你喵

@chuyegzs
Copy link
Copy Markdown
Owner

你记得同意下哦

@NanoRocky
Copy link
Copy Markdown
Collaborator Author

音乐卡片本人不会用,老是提示出错,俺也不会配置所以没加喵,你可以给一下教程喵,目前1.0.4有1.Node API 2.PHP API 3.自定义参数。对于直接返回歌曲URL的话,会比较长,对于便捷性就会差一些,毕竟有那时间我直接去lx音乐添加源不更简单吗哈哈哈,当然有的人喜欢,所以说是可以有的

我做这个其实之前没打算上架的哈哈哈,就想自己用,然后想了想还是上架吧,有的人可能需要呢

关于酷狗和酷我,我自己的API目前只有网易云,QQ音乐和酷狗,酷我核心库支持但我修改的版本不支持,酷狗我不用,别人需要酷狗才开发的,我也只测试了QQ音乐与网易云,有需要可以测试测试,有问题就提Issues或PR喵~~~

在此非常感谢你提的PR喵~~~~~

诶,我的 API 也只兼容 QQ音乐和网易云 呢,这俩足够覆盖大部分音乐了..
那我重新整理一下 pr 到新版本里叭w

至于便捷性差..没有叭..
它的效果是这样的~
1771645946295

@NanoRocky
Copy link
Copy Markdown
Collaborator Author

感谢提供喵,但酷狗和酷我支持暂不打算移除,官方@meting/core核心库并未移除,我打算添加自定义参数,用于适配更多版本的MetingAPI(有的人公益带会员但调用参数改过这种),点歌指令“搜歌”,搜歌 xxx,然后点歌 x即可,就不会太麻烦,音乐卡片在我这边个人不会使用,所以暂不同步,已更新1.0.4版本,可直接使用哦(音乐卡片真不会搞哈哈哈),在此非常感谢你喵~~~~~~~

噢噢,抱歉由于我一开始修改时并没有打算 PR 的,所以进行了较大改动。后面完善后,感觉可以提个 PR 的时候,已经改成这样了呜呜( 不过..个人感觉卡片的效果会好不少呢,不再需要服务器算力进行分段,也不再需要服务器缓存音频文件,直接返回歌曲源URL。也有音乐封面,点击也可以直接跳转到音乐官方页面。至于 MetingAPI ,我是添加了对 这个我修改过的 PHP 版本的接口的支持..
嗯......那就这样叭喵...如果有需要的话,就辛苦初叶拷贝卡片相关代码到仓库,或者再补全一下酷我和酷狗的支持啦w,果咩纳塞~

或者也可以让我重新把模块加到新版本里~需要的话喵...

我会将你拉入贡献的,谢谢你喵

!好诶!谢谢~

@chuyegzs
Copy link
Copy Markdown
Owner

我还以为是图片样式的,看来确实没有

@NanoRocky
Copy link
Copy Markdown
Collaborator Author

我还以为是图片样式的,看来确实没有

图片的话..就不会涉及到 JSON 了喵~

@chuyegzs
Copy link
Copy Markdown
Owner

对于指令的话,建议是越少越好,要不然不好记

@chuyegzs
Copy link
Copy Markdown
Owner

我还以为是图片样式的,看来确实没有

图片的话.. 就不会涉及到 JSON 了喵~

我是废物喵~~~

@chuyegzs
Copy link
Copy Markdown
Owner

可以加个联系方式喵~~~这样我觉得比较好喵~~~,个人觉得,不行的话也没关系

@NanoRocky
Copy link
Copy Markdown
Collaborator Author

对于指令的话,建议是越少越好,要不然不好记

这个...对不起,上面那个版本因为原本没打算 PR 的所以一顿瞎改了(
正常提 PR 我不会直接修改原功能的~

@NanoRocky
Copy link
Copy Markdown
Collaborator Author

可以加个联系方式喵~~~这样我觉得比较好喵~~~,个人觉得,不行的话也没关系

好诶!我的 Github 主页下方就有联系方式喵~ 快来扩扩(?

@chuyegzs
Copy link
Copy Markdown
Owner

对于指令的话,建议是越少越好,要不然不好记

这个... 对不起,上面那个版本因为原本没打算 PR 的所以一顿瞎改了( 正常提 PR 我不会直接修改原功能的~

哈哈哈,你不提的话我还不会添加关于PHP版本的MetingAPI支持,不会优化指令了喵~

@chuyegzs
Copy link
Copy Markdown
Owner

可以加个联系方式喵~~~这样我觉得比较好喵~~~,个人觉得,不行的话也没关系

好诶! 我的 Github 主页下方就有联系方式喵~ 快来扩扩(?

好哦~~~~

@NanoRocky NanoRocky closed this Feb 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants