Skip to content

Commit

Permalink
fix bug #24
Browse files Browse the repository at this point in the history
  • Loading branch information
kitUIN committed Dec 3, 2023
1 parent 37fdf25 commit e9e121c
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 135 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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|
```
# 这是示例
Expand Down
54 changes: 10 additions & 44 deletions nonebot-plugin-ncm/__init__.py
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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="网易云无损音乐下载",
Expand Down Expand Up @@ -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:
Expand All @@ -141,58 +137,28 @@ 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()
async def music_list_receive(bot: Bot, event: Union[GroupMessageEvent, PrivateMessageEvent],
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()
async def music_reply_receive(bot: Bot, event: Union[GroupMessageEvent, PrivateMessageEvent]):
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()
Expand Down
3 changes: 0 additions & 3 deletions nonebot-plugin-ncm/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'''

Expand Down
192 changes: 106 additions & 86 deletions nonebot-plugin-ncm/data_source.py
Original file line number Diff line number Diff line change
@@ -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")
Expand Down Expand Up @@ -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)
Expand All @@ -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:
Expand All @@ -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)
Expand All @@ -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(
Expand All @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>"]
Expand Down

0 comments on commit e9e121c

Please sign in to comment.