From e9e121c3c4e2cb374afea2eab7ad4f5e8a4e429b Mon Sep 17 00:00:00 2001 From: Nacho Date: Sun, 3 Dec 2023 15:46:26 +0800 Subject: [PATCH] fix bug #24 --- README.md | 1 - nonebot-plugin-ncm/__init__.py | 54 ++------- nonebot-plugin-ncm/config.py | 3 - nonebot-plugin-ncm/data_source.py | 192 +++++++++++++++++------------- pyproject.toml | 2 +- 5 files changed, 117 insertions(+), 135 deletions(-) diff --git a/README.md b/README.md index d39a603..5539e6f 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,6 @@ | ncm_ctcode | 否 | 86 | 手机号区域码 | | ncm_phone | 是 | | 网易云绑定的手机号(留空则二维码登录) | | ncm_password | 是 | | 网易云账号密码(留空则短信登录) | -| ncm_playlist_zip | 否 | False | 是否开启歌单打包上传 | | ncm_bitrate | 否 | 320 | 下载码率(单位K) <=96: m4a, >=320:flac, 96< mp3 <320| ``` # 这是示例 diff --git a/nonebot-plugin-ncm/__init__.py b/nonebot-plugin-ncm/__init__.py index 1641a66..2e55866 100644 --- a/nonebot-plugin-ncm/__init__.py +++ b/nonebot-plugin-ncm/__init__.py @@ -1,10 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -from dataclasses import dataclass -from pathlib import Path -from typing import Tuple, Any, Union, Optional, Type, Set, Dict +from typing import Tuple, Any, Union -import nonebot from nonebot import on_regex, on_command, on_message from nonebot.adapters.onebot.v11 import (Message, Bot, MessageSegment, @@ -13,12 +10,11 @@ from nonebot.log import logger from nonebot.matcher import Matcher from nonebot.params import CommandArg, RegexGroup, Arg +from nonebot.plugin import PluginMetadata from nonebot.rule import Rule -from pydantic.main import BaseModel -from .data_source import nncm, ncm_config, setting, Q, cmd from .config import Config -from nonebot.plugin import PluginMetadata +from .data_source import nncm, ncm_config, setting, Q, cmd __plugin_meta__ = PluginMetadata( name="网易云无损音乐下载", @@ -127,7 +123,7 @@ async def receive_song(bot: Bot, ): _id = await nncm.search_song(keyword=song.extract_plain_text(), limit=1) message_id = await bot.send(event=event, message=Message(MessageSegment.music(type_="163", id_=_id))) - nncm.get_song(message_id=message_id["message_id"], nid=_id, bot_id=bot.self_id) + nncm.get_song(message_id=message_id["message_id"], nid=_id) # try: # except ActionFailed as e: @@ -141,7 +137,7 @@ async def music_receive(bot: Bot, event: Union[GroupMessageEvent, PrivateMessage nid = regroup[1] logger.info(f"已识别NID:{nid}的歌曲") - nncm.get_song(nid=nid, message_id=event.message_id, bot_id=bot.self_id) + nncm.get_song(nid=nid, message_id=event.message_id) @playlist_regex.handle() @@ -149,7 +145,7 @@ async def music_list_receive(bot: Bot, event: Union[GroupMessageEvent, PrivateMe regroup: Tuple[Any, ...] = RegexGroup()): lid = regroup[0] logger.info(f"已识别LID:{lid}的歌单") - nncm.get_playlist(lid=lid, message_id=event.message_id, bot_id=bot.self_id) + nncm.get_playlist(lid=lid, message_id=event.message_id) @music_reply.handle() @@ -157,42 +153,12 @@ async def music_reply_receive(bot: Bot, event: Union[GroupMessageEvent, PrivateM info = nncm.check_message(int(event.dict()["reply"]["message_id"])) if info is None: return - if info["type"] == "song" and await song_is_open(event) and info["bot_id"] == bot.self_id: + if info["type"] == "song" and await song_is_open(event): await bot.send(event=event, message="少女祈祷中🙏...上传时间较久,请勿重复发送命令") - data = await nncm.music_check(info["nid"]) - if isinstance(data, list): - data = data[0] - if data: - if isinstance(event, GroupMessageEvent): - await nncm.upload_group_data_file(event.group_id, data, bot_id=info["bot_id"]) - elif isinstance(event, PrivateMessageEvent): - await nncm.upload_private_data_file(event.user_id, data, bot_id=info["bot_id"]) - else: - logger.error("数据库中未有该音乐地址数据") - await bot.send(event=event, message="数据库中未有该音乐地址数据") - - elif info["type"] == "playlist" and await playlist_is_open(event) and info["bot_id"] == bot.self_id: + await nncm.music_check(info["nid"], event) + elif info["type"] == "playlist" and await playlist_is_open(event): await bot.send(event=event, message=info["lmsg"] + "\n下载中,上传时间较久,请勿重复发送命令") - not_zips = await nncm.download(ids=info["ids"], lid=info["lid"], is_zip=ncm_config.ncm_playlist_zip) - filename = f"{info['lid']}.zip" - data = Path.cwd().joinpath("music").joinpath(filename) - if ncm_config.ncm_playlist_zip: - logger.debug(f"Upload:{filename}") - if isinstance(event, GroupMessageEvent): - await nncm.upload_group_file(group_id=event.group_id, file=str(data), name=filename, - bot_id=info["bot_id"]) - elif isinstance(event, PrivateMessageEvent): - await nncm.upload_private_file(user_id=event.user_id, file=str(data), name=filename, - bot_id=info["bot_id"]) - else: - for i in not_zips: - file = i["file"] - filename = i["filename"] - logger.debug(f"Upload:{filename}") - if isinstance(event, GroupMessageEvent): - await nncm.upload_group_file(group_id=event.group_id, file=file, name=filename) - elif isinstance(event, PrivateMessageEvent): - await nncm.upload_private_file(user_id=event.user_id, file=file, name=filename) + await nncm.music_check(info["ids"], event, info["lid"]) @ncm_set.handle() diff --git a/nonebot-plugin-ncm/config.py b/nonebot-plugin-ncm/config.py index e38a317..db6723c 100644 --- a/nonebot-plugin-ncm/config.py +++ b/nonebot-plugin-ncm/config.py @@ -17,9 +17,6 @@ class Config(BaseModel, extra=Extra.ignore): ncm_password: str = "" '''密码''' - - ncm_playlist_zip: bool = False - '''上传歌单时是否压缩''' ncm_bitrate: int = 320 '''下载码率(单位K) 96及以下为m4a,320及以上为flac,中间mp3''' diff --git a/nonebot-plugin-ncm/data_source.py b/nonebot-plugin-ncm/data_source.py index 914f53d..9e44820 100644 --- a/nonebot-plugin-ncm/data_source.py +++ b/nonebot-plugin-ncm/data_source.py @@ -1,30 +1,24 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -import os -import zipfile -from pathlib import Path -from datetime import datetime -from typing import Union, List, Optional, Dict +import asyncio import re - -import qrcode import time -from aiofile import async_open +from datetime import datetime +from pathlib import Path +from typing import Union, List, Dict, cast -import httpx import nonebot -from nonebot.utils import run_sync - -from pyncm import apis, Session, GetCurrentSession, DumpSessionAsString, LoadSessionFromString, SetCurrentSession -from pyncm.apis.cloudsearch import SONG, USER, PLAYLIST - -from nonebot.log import logger +import qrcode from nonebot.adapters.onebot.v11 import (MessageSegment, Message, ActionFailed, NetworkError, Bot, GroupMessageEvent, PrivateMessageEvent) +from nonebot.log import logger +from nonebot.matcher import current_bot +from pyncm import apis, GetCurrentSession, DumpSessionAsString, LoadSessionFromString, SetCurrentSession +from pyncm.apis.cloudsearch import SONG, USER, PLAYLIST +from tinydb import TinyDB, Query from .config import ncm_config -from tinydb import TinyDB, Query # ============数据库导入============= dbPath = Path("db") @@ -126,19 +120,52 @@ def get_qrcode(self): return True time.sleep(1) - def detail(self, ids: list) -> list: + def detail_names(self, ids: List[int]) -> List[str]: songs: list = self.api.track.GetTrackDetail(song_ids=ids)["songs"] detail = [(data["name"] + "-" + ",".join([names["name"] for names in data["ar"]])) for data in songs] return detail - async def music_check(self, nid: int) -> Union[List[Dict[str, Union[str, int]]], Dict[str, Union[str, int]], None]: - nid = int(nid) - info = music.search(Q["id"] == nid) - if info: - path = Path(info[0]["file"]) - if path.is_file(): - return info[0] - return await self.download(ids=[nid]) + @logger.catch() + def get_detail(self, ids: List[int]): + data: list = self.api.track.GetTrackAudio(song_ids=ids, bitrate=ncm_config.ncm_bitrate * 1000)["data"] + names: list = self.detail_names(ids) + for i in range(len(ids)): + data[i]['ncm_name'] = names[i] + return data + + async def music_check(self, nid: Union[int, List[int]], event: Union[GroupMessageEvent, PrivateMessageEvent], + lid: int = None): + """判断数据库中是否有缓存,有则使用缓存,没有则新下载""" + tasks = [] + if lid: + del_nid = [] + for i in nid: + info = music.search(Q["id"] == i) + if info: + try: + tasks.append(asyncio.create_task(self.upload_data_file(event=event, data=info[0]))) + del_nid.append(i) + except Exception: + continue + for j in del_nid: + nid.remove(j) + else: + nid = int(nid) + info = music.search(Q["id"] == nid) + if info: + try: + tasks.append(asyncio.create_task(self.upload_data_file(event=event, data=info[0]))) + return + except Exception as e: + if isinstance(e, ActionFailed) and e.info.get("retcode") != 10003: + logger.error(e) + return + if tasks: + await asyncio.gather(*tasks) + if nid: + if isinstance(nid, int): + nid = [nid] + await self.start_upload(ids=nid, event=event) async def search_song(self, keyword: str, limit: int = 1) -> int: # 搜索歌曲 res = self.api.cloudsearch.GetSearchResult(keyword=keyword, stype=SONG, limit=limit) @@ -165,10 +192,8 @@ def check_message(message_id: int): return flag[0] if flag else None @staticmethod - def get_song(nid: int, message_id: int, bot_id: str): + def get_song(nid: int, message_id: int): """解析歌曲id,并且加入缓存 - - :param bot_id: :param message_id: :param nid: :return: @@ -179,10 +204,10 @@ def get_song(nid: int, message_id: int, bot_id: str): "lid": 0, "ids": [], "lmsg": "", - "bot_id": bot_id, + "bot_id": "", "time": int(time.time())}) - def get_playlist(self, lid: int, message_id: int, bot_id: str): + def get_playlist(self, lid: int, message_id: int): lid = int(lid) data = self.api.playlist.GetPlaylistInfo(lid) # logger.info(data) @@ -198,23 +223,23 @@ def get_playlist(self, lid: int, message_id: int, bot_id: str): "lmsg": f"歌单:{raw['name']}\r\n创建者:{raw['creator']['nickname']}\r\n歌曲总数:{raw['trackCount']}\r\n" f"标签:{tags}\r\n播放次数:{raw['playCount']}\r\n收藏:{raw['subscribedCount']}\r\n" f"评论:{raw['commentCount']}\r\n分享:{raw['shareCount']}\r\nListID:{lid}", - "bot_id": bot_id, + "bot_id": "", "time": int(time.time())}) - async def upload_group_data_file(self, group_id: int, data: Dict[str, Union[str, int]], bot_id: str): - await self.upload_group_file(group_id=group_id, file=data["file"], name=data["filename"], bot_id=bot_id) - - async def upload_private_data_file(self, user_id: int, data: Dict[str, Union[str, int]], bot_id: str): - await self.upload_private_file(user_id=user_id, file=data["file"], name=data["filename"], bot_id=bot_id) + async def upload_data_file(self, event: Union[GroupMessageEvent, PrivateMessageEvent], + data: Dict[str, Union[str, int]]): + if isinstance(event, GroupMessageEvent): + await self.upload_group_file(group_id=event.group_id, file=data["file"], name=data["filename"]) + elif isinstance(event, PrivateMessageEvent): + await self.upload_private_file(user_id=event.user_id, file=data["file"], name=data["filename"]) @staticmethod - async def upload_group_file(group_id: int, file: str, name: str, bot_id: str): + async def upload_group_file(group_id: int, file: str, name: str): + bot: Bot = cast(Bot, current_bot.get()) try: - bot: Bot = nonebot.get_bot(bot_id) await bot.upload_group_file(group_id=group_id, file=file, name=name) except (ActionFailed, NetworkError) as e: logger.error(e) - bot: Bot = nonebot.get_bot(bot_id) if isinstance(e, ActionFailed) and e.info["wording"] == "server" \ " requires unsupported ftn upload": await bot.send_group_msg(group_id=group_id, message=Message(MessageSegment.text( @@ -226,64 +251,59 @@ async def upload_group_file(group_id: int, file: str, name: str, bot_id: str): "上传超时(一般来说还在传,建议等待五分钟)"))) @staticmethod - async def upload_private_file(user_id: int, file: str, name: str, bot_id: str): + async def upload_private_file(user_id: int, file: str, name: str): + bot: Bot = cast(Bot, current_bot.get()) try: - bot: Bot = nonebot.get_bot(bot_id) await bot.upload_private_file(user_id=user_id, file=file, name=name) except (ActionFailed, NetworkError) as e: logger.error(e) if isinstance(e, NetworkError): - bot: Bot = nonebot.get_bot(bot_id) - await bot.send_private_msg(user_id=user_id, message=Message(MessageSegment.text( + await bot.send_private_msg(user_id=user_id, message=Message(MessageSegment.text( "[ERROR] 文件上传失败\r\n[原因] 上传超时(一般来说还在传,建议等待五分钟)"))) - @run_sync - def get_zip(self, lid: int, filenames: list): - zip_file_new = f'{lid}.zip' - with zipfile.ZipFile(str(Path.cwd().joinpath("music").joinpath(zip_file_new)), 'w', zipfile.ZIP_DEFLATED) as z: - for f in filenames: - z.write(str(f), f.name) - return zip_file_new + # @run_sync + # def get_zip(self, lid: int, filenames: list): + # zip_file_new = f'{lid}.zip' + # with zipfile.ZipFile(str(Path.cwd().joinpath("music").joinpath(zip_file_new)), 'w', zipfile.ZIP_DEFLATED) as z: + # for f in filenames: + # z.write(str(f), f.name) + # return zip_file_new + async def upload(self, data: dict, fr: str, event: Union[GroupMessageEvent, PrivateMessageEvent]): + if data["code"] == 404: + logger.error("未从网易云读取到下载地址") + return None + url = data["url"] + nid = data["id"] + filename = f"{data['ncm_name']}.{data['type']}" + filename = re.sub('[\/:*?"<>|]', '-', filename) + bot = cast(Bot, current_bot.get()) + download_ret: Dict[str, str] = await bot.download_file(url=url) + file = download_ret["file"] + cf = { + "id": int(nid), + "file": str(file), # 获取文件位置 + "filename": filename, # 获取文件名 + "from": fr, # 判断来自单曲还是歌单 + "time": datetime.now().strftime("%Y-%m-%d %H:%M:%S") # 获取时间 + } + info = music.search(Q["id"] == nid) + if info: # 数据库储存 + music.update(cf, Q["id"] == nid) + else: + music.insert(cf) + logger.debug(f"Download:{filename}") + await self.upload_data_file(event=event, data=cf) - async def download(self, ids: List[int], lid: int = 0, is_zip=False) -> Optional[List[Dict[str, Union[str, int]]]]: + async def start_upload(self, ids: List[int], event: Union[GroupMessageEvent, PrivateMessageEvent]): """一般地 320k及以上即 flac, 320k及以下即 mp3,96k及以下即 m4a """ - data: list = self.api.track.GetTrackAudio(song_ids=ids, bitrate=ncm_config.ncm_bitrate * 1000)["data"] - name: list = self.detail(ids) - filenames = [] - not_zips = [] + data: list = self.get_detail(ids) for i in range(len(ids)): - if data[i]["code"] == 404: - logger.error("未从网易云读取到下载地址") - return None - url = data[i]["url"] - nid = data[i]["id"] - filename = f"{name[i]}.{data[i]['type']}" - filename = re.sub('[\/:*?"<>|]', '-', filename) - file = Path.cwd().joinpath("music").joinpath(filename) - config = { - "id": int(nid), - "file": str(file), # 获取文件位置 - "filename": filename, # 获取文件名 - "from": "song" if len(ids) == 1 else "list", # 判断来自单曲还是歌单 - "time": datetime.now().strftime("%Y-%m-%d %H:%M:%S") # 获取时间 - } - filenames.append(file) - not_zips.append(config) - info = music.search(Q["id"] == nid) - if info: # 数据库储存 - music.update(config, Q["id"] == nid) - else: - music.insert(config) - async with httpx.AsyncClient() as client: # 下载歌曲 - async with client.stream("GET", url=url) as r: - async with async_open(file, 'wb') as out_file: - async for chunk in r.aiter_bytes(): - await out_file.write(chunk) - logger.debug(f"Download:{filename}") - if is_zip: - await self.get_zip(lid=lid, filenames=filenames) - return not_zips + await self.upload(data[i], "song" if len(ids) == 1 else "list", event) + + # if is_zip: + # await self.get_zip(lid=lid, filenames=filenames) + # return not_zips nncm = Ncm() diff --git a/pyproject.toml b/pyproject.toml index e1f41a8..29607d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "nonebot-plugin-ncm" -version = "1.6.8" +version = "1.6.9" description = "基于go-cqhttp与nonebot2的 网易云 无损音乐下载" license = "Apache License 2.0" authors = ["kitUIN "]