Skip to content

Commit

Permalink
up
Browse files Browse the repository at this point in the history
  • Loading branch information
lgc2333 committed Jun 27, 2024
1 parent 7351758 commit d666c47
Show file tree
Hide file tree
Showing 9 changed files with 105 additions and 49 deletions.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ plugins = [
| `NCM_ILLEGAL_CMD_LIMIT` || `3` | 当未启用 `NCM_ILLEGAL_CMD_FINISH` 时,用户在点歌时输入了多少次非法指令后直接退出点歌,填 `0` 以禁用此功能 |
| `NCM_DELETE_MSG` || `True` | 是否在退出点歌模式后自动撤回歌曲列表与操作提示信息 |
| `NCM_DELETE_MSG_DELAY` || `[0.5, 2.0]` | 自动撤回消息间隔时间(单位秒) |
| `NCM_SEND_MEDIA` || `True` | 是否发送歌曲,如关闭将始终提示使用命令获取播放链接 |
| `NCM_SEND_AS_CARD` || `True` | 在支持的平台下,发送歌曲卡片(目前支持 `OneBot V11``Kritor`|
| `NCM_SEND_AS_FILE` || `False` | 当无法发送卡片或卡片发送失败时,会回退到使用语音发送,启用此配置项将会换成回退到发送歌曲文件 |
| **其他配置** | | | |
Expand All @@ -161,6 +162,7 @@ plugins = [
| `NCM_CARD_SIGN_URL` || `None` | 音卡签名地址(与 LLOneBot 或 NapCat 共用),填写此 URL 后将会把音卡的签名工作交给本插件 |
| `NCM_CARD_SIGN_TIMEOUT` || `5` | 请求音卡签名地址的超时时间 |
| `NCM_OB_V11_LOCAL_MODE` || `False` | 在 OneBot V11 适配器下,是否下载歌曲后使用本地文件路径上传歌曲 |
| `NCM_FFMPEG_EXECUTABLE` || `ffmpeg` | FFmpeg 可执行文件路径,已经加进环境变量可以不用配置,在 OneBot V11 适配器下发送语音需要使用 |

## 🎉 使用

Expand Down Expand Up @@ -241,9 +243,10 @@ Telegram:[@lgc2333](https://t.me/lgc2333)

项目重构

- 支持多平台
- 支持多平台
目前多平台发歌逻辑还不是很完善,如果有建议欢迎提出
- UI 大改
- 新增一些支持的搜索与解析项
- 支持电台与专辑的搜索与解析
- 自动解析对同一歌曲有冷却了,防多 Bot 刷屏
- 配置项变动
- 移除配置项 `NCM_MAX_NAME_LEN``NCM_MAX_ARTIST_LEN``NCM_USE_PLAYWRIGHT`
Expand All @@ -257,8 +260,6 @@ Telegram:[@lgc2333](https://t.me/lgc2333)
可自行寻找音卡签名服务填写于此
- 修改配置项 `NCM_DOWNLOAD_LOCALLY` -> `NCM_OB_V11_LOCAL_MODE`

~~其他的变动懒得写了~~

### 0.5.0

- 适配 Pydantic V1 & V2
Expand Down
2 changes: 1 addition & 1 deletion nonebot_plugin_multincm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"▶ Bot 会自动解析你发送的网易云链接\n" if config.ncm_auto_resolve else ""
)

__version__ = "1.0.0a3"
__version__ = "1.0.0a4"
__plugin_meta__ = PluginMetadata(
name="MultiNCM",
description="网易云多选点歌",
Expand Down
12 changes: 2 additions & 10 deletions nonebot_plugin_multincm/config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
from pathlib import Path
from typing import Annotated, Optional, Tuple
from urllib.parse import quote

from nonebot import get_plugin_config
from pydantic import AnyHttpUrl, BaseModel
Expand All @@ -27,6 +25,7 @@ class ConfigModel(BaseModel):
ncm_illegal_cmd_limit: int = 3
ncm_delete_msg: bool = True
ncm_delete_msg_delay: Tuple[float, float] = (0.5, 2.0)
ncm_send_media: bool = True
ncm_send_as_card: bool = True
ncm_send_as_file: bool = False

Expand All @@ -37,14 +36,7 @@ class ConfigModel(BaseModel):
ncm_card_sign_url: Optional[Annotated[str, AnyHttpUrl]] = None
ncm_card_sign_timeout: int = 5
ncm_ob_v11_local_mode: bool = False

@property
def ncm_list_font_url(self) -> Optional[str]:
return (
quote(p.as_uri())
if self.ncm_list_font and (p := Path(self.ncm_list_font)).exists()
else self.ncm_list_font
)
ncm_ffmpeg_executable: str = "ffmpeg"


config = get_plugin_config(ConfigModel)
5 changes: 3 additions & 2 deletions nonebot_plugin_multincm/interaction/message/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ async def send():
return

receipt = ...
with warning_suppress(f"Send {song} file failed"):
receipt = await send_song_media(song)
if config.ncm_send_media:
with warning_suppress(f"Send {song} file failed"):
receipt = await send_song_media(song)
await (await construct_info_msg(song, tip_command=(receipt is ...))).send(
reply_to=receipt.get_reply() if receipt and (receipt is not ...) else None,
)
Expand Down
73 changes: 43 additions & 30 deletions nonebot_plugin_multincm/interaction/message/song_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@

from cookit.loguru import warning_suppress
from httpx import AsyncClient
from nonebot import logger
from nonebot.matcher import current_bot, current_event
from nonebot_plugin_alconna.uniseg import Receipt, UniMessage, get_exporter

from ...config import config
from ...const import SONG_CACHE_DIR
from ...data_source import BaseSong, SongInfo
from ...utils import encode_silk, ffmpeg_exists


async def download_song(info: SongInfo):
Expand Down Expand Up @@ -58,39 +60,50 @@ async def send_song_media_telegram(info: SongInfo, as_file: bool = False): # no


async def send_song_media_onebot_v11(info: SongInfo, as_file: bool = False):
if not as_file:
raise TypeError("Should fallback using UniMessage")

from nonebot.adapters.onebot.v11 import (
Bot as OB11Bot,
GroupMessageEvent,
MessageEvent,
PrivateMessageEvent,
)
async def send_voice():
if not await ffmpeg_exists():
logger.warning(
"FFmpeg 无法使用,插件将不会把音乐文件转为 silk 格式提交给协议端",
)
raise TypeError("FFmpeg unavailable, fallback to UniMessage")

return await UniMessage.voice(
raw=(await encode_silk(await download_song(info))).read_bytes(),
).send()

async def send_file():
from nonebot.adapters.onebot.v11 import (
Bot as OB11Bot,
GroupMessageEvent,
PrivateMessageEvent,
)

bot = cast(OB11Bot, current_bot.get())
event = cast(MessageEvent, current_event.get())
bot = cast(OB11Bot, current_bot.get())
event = current_event.get()

file = (
(await download_song(info))
if config.ncm_ob_v11_local_mode
else cast(str, (await bot.download_file(url=info.playable_url))["file"])
)
if not isinstance(event, (GroupMessageEvent, PrivateMessageEvent)):
raise TypeError("Event not supported")

if isinstance(event, PrivateMessageEvent):
await bot.upload_private_file(
user_id=event.user_id,
file=file,
name=info.display_filename,
file = (
(await download_song(info))
if config.ncm_ob_v11_local_mode
else cast(str, (await bot.download_file(url=info.playable_url))["file"])
)
elif isinstance(event, GroupMessageEvent):
await bot.upload_group_file(
group_id=event.group_id,
file=file,
name=info.display_filename,
)
else:
raise TypeError("Event not supported")

if isinstance(event, PrivateMessageEvent):
await bot.upload_private_file(
user_id=event.user_id,
file=file,
name=info.display_filename,
)
else:
await bot.upload_group_file(
group_id=event.group_id,
file=file,
name=info.display_filename,
)

return (await send_file()) if as_file else (await send_voice())


async def send_song_media_platform_specific(
Expand Down Expand Up @@ -119,7 +132,7 @@ async def send_song_media(song: BaseSong, as_file: bool = config.ncm_send_as_fil

path = await download_song(info)
with warning_suppress(
f"Failed to send {song} use file path, will try to send using raw bytes",
f"Failed to send {song} using file path, fallback using raw bytes",
):
if not TYPE_CHECKING:
return await send_song_media_uni_msg(path, info, raw=False, as_file=as_file)
Expand Down
4 changes: 2 additions & 2 deletions nonebot_plugin_multincm/render/templates/base.html.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@
const fontFamily = '{{ config.font_family | escape_single_quotes }}';
const css = fontFamily.includes('://')
? `@font-face { font-family: 'Custom'; src: url('${fontFamily}'); }\n` +
`:root { --font-family: 'Custom'; }`
: `:root { --font-family: '${fontFamily}'; }`;
`:root { --font-family: 'Custom', sans-serif; }`
: `:root { --font-family: '${fontFamily}', sans-serif; }`;
const style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
Expand Down
2 changes: 2 additions & 0 deletions nonebot_plugin_multincm/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
calc_min_max_index as calc_min_max_index,
calc_page_number as calc_page_number,
cut_string as cut_string,
encode_silk as encode_silk,
ffmpeg_exists as ffmpeg_exists,
format_alias as format_alias,
format_artists as format_artists,
format_time as format_time,
Expand Down
46 changes: 46 additions & 0 deletions nonebot_plugin_multincm/utils/base.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import asyncio
import json
import math
import time
from pathlib import Path
from typing import TYPE_CHECKING, Any, List, Optional, Tuple, TypeVar
from typing_extensions import ParamSpec

from nonebot.utils import run_sync
from yarl import URL

from ..config import config
Expand Down Expand Up @@ -80,3 +83,46 @@ def cut_string(text: str, length: int = 50) -> str:
if len(text) <= length:
return text
return text[: length - 1] + "…"


async def ffmpeg_exists() -> bool:
proc = await asyncio.create_subprocess_exec(
config.ncm_ffmpeg_executable,
"-version",
stdin=asyncio.subprocess.DEVNULL,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
code = await proc.wait()
return code == 0


async def encode_silk(path: Path, rate: int = 24000) -> Path:
silk_path = path.with_suffix(".silk")
if silk_path.exists():
return silk_path

pcm_path = path.with_suffix(".pcm")
proc = await asyncio.create_subprocess_exec(
config.ncm_ffmpeg_executable,
"-y",
"-i", str(path),
"-f", "s16le", "-ar", f"{rate}", "-ac", "1", str(pcm_path),
stdin=asyncio.subprocess.DEVNULL,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
) # fmt: skip
code = await proc.wait()
if code != 0:
raise RuntimeError(
f"Failed to use ffmpeg to convert {path} to pcm, return code {code}",
)

try:
from pysilk import encode

await run_sync(encode)(pcm_path.open("rb"), silk_path.open("wb"), rate, rate)
finally:
pcm_path.unlink(missing_ok=True)

return silk_path
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ dependencies = [
"nonebot-plugin-waiter>=0.6.2",
"cachetools>=5.3.3",
"yarl>=1.9.4",
"silk-python>=0.2.6",
]
requires-python = ">=3.9,<4.0"
readme = "README.md"
Expand Down

0 comments on commit d666c47

Please sign in to comment.