初始提交
This commit is contained in:
@@ -0,0 +1 @@
|
||||
__pycache__
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
FROM alpine:3.18
|
||||
MAINTAINER CattySteve cattysteve89265@163.com
|
||||
|
||||
|
||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
|
||||
RUN apk add python3 py3-pip gcc g++ python3-dev curl uuidgen
|
||||
RUN echo flag{$(uuidgen)} > /flag
|
||||
RUN pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
|
||||
COPY . /opt/galaxy
|
||||
WORKDIR /opt/galaxy
|
||||
RUN pip install -r ./requirements_deployment.txt
|
||||
EXPOSE 5001
|
||||
CMD cd /opt/galaxy && python main.py
|
||||
HEALTHCHECK --interval=30s --timeout=30s --start-period=30s \
|
||||
CMD curl http://localhost:5001 > /dev/null
|
||||
@@ -0,0 +1,62 @@
|
||||
try:
|
||||
from Provider import SessionProvider, SongStatus, AccountType
|
||||
except:
|
||||
from Extensions.Provider import SessionProvider, SongStatus, AccountType
|
||||
import httpx
|
||||
from functools import lru_cache
|
||||
import re
|
||||
from enum import Enum
|
||||
from bilibili_api import user, login_func, sync, login, Credential
|
||||
from pyzbar import pyzbar
|
||||
from PIL import Image
|
||||
import qrcode
|
||||
import time
|
||||
|
||||
class BilibiliSession(SessionProvider):
|
||||
sessionKey = None
|
||||
session = None
|
||||
providerName = "Bilibili"
|
||||
URLKeyword = ["b23.tv", "bilibili.com", "bilibili.tv"]
|
||||
accountType = AccountType.ACCOUNT_NOLOGIN
|
||||
|
||||
def login(self) -> dict:
|
||||
status, self.session = login_func.check_qrcode_events(self.sessionKey)
|
||||
print(status)
|
||||
if status == login_func.QrCodeLoginEvents.SCAN:
|
||||
return {"status": "400", "msg": "请扫描二维码"}, 400
|
||||
elif status == login_func.QrCodeLoginEvents.CONF:
|
||||
return {"status": "400", "msg": "请在手机上确认登录"}, 400
|
||||
elif status == login_func.QrCodeLoginEvents.TIMEOUT:
|
||||
return {"status": "400", "msg": "二维码已过期,请重新获取"}, 400
|
||||
elif status == login_func.QrCodeLoginEvents.DONE:
|
||||
self.status.cache_clear()
|
||||
print("success!")
|
||||
return {"status": "200", "msg": "登录成功"}, 200
|
||||
else:
|
||||
return {"status": "400", "msg": "未知错误"}, 400
|
||||
|
||||
@lru_cache(maxsize=8)
|
||||
def status(self) -> dict:
|
||||
return sync(user.get_self_info(self.session))
|
||||
|
||||
def userStatus(self) -> dict:
|
||||
return self.status()
|
||||
|
||||
def logout(self) -> dict:
|
||||
return {"status": 201, "msg": "无法登出,直接换帐号就好了"}, 201
|
||||
|
||||
def qrcode(self):
|
||||
_session = login.update_qrcode_data()
|
||||
loginURL = _session["url"]
|
||||
self.sessionKey = _session["qrcode_key"]
|
||||
qrcode.make(loginURL).save("/tmp/qrcode.png")
|
||||
return loginURL
|
||||
|
||||
if __name__ == "__main__":
|
||||
bisess = BilibiliSession()
|
||||
print(bisess.qrcode())
|
||||
while(True):
|
||||
if bisess.login()[1] == 200:
|
||||
break
|
||||
time.sleep(2)
|
||||
print(bisess.userStatus())
|
||||
@@ -0,0 +1,160 @@
|
||||
from pyncm import apis as ncm
|
||||
import pyncm
|
||||
try:
|
||||
from Provider import SessionProvider, SongStatus, AccountType
|
||||
except:
|
||||
from Extensions.Provider import SessionProvider, SongStatus, AccountType
|
||||
import httpx
|
||||
from functools import lru_cache
|
||||
import re
|
||||
from enum import Enum
|
||||
|
||||
class NeteaseSession(SessionProvider):
|
||||
session = None
|
||||
providerName = "Netease"
|
||||
URLKeyword = ["music.163.com", "163cn.tv", "网易云", "netease"]
|
||||
accountType = AccountType.ACCOUNT_NOLOGIN
|
||||
|
||||
def updateAccountType(self) -> AccountType:
|
||||
response = ncm.login.GetCurrentLoginStatus()["account"]
|
||||
if not response:
|
||||
self.accountType = AccountType.ACCOUNT_NOLOGIN
|
||||
return
|
||||
if response["paidFee"]:
|
||||
self.accountType = AccountType.ACCOUNT_VIP
|
||||
else:
|
||||
self.accountType = AccountType.ACCOUNT_USER
|
||||
|
||||
def login(self) -> dict:
|
||||
self.status.cache_clear()
|
||||
status = ncm.login.LoginQrcodeCheck(self.session)
|
||||
if status["code"] == 803:
|
||||
ncm.login.WriteLoginInfo(ncm.login.GetCurrentLoginStatus())
|
||||
self.updateAccountType()
|
||||
return {"status": 200, "msg": _("Netease account logged in.")}, 200
|
||||
else:
|
||||
return {"status": 408, "msg": _("Netease account login failed.")}, 408
|
||||
|
||||
def qrcode(self):
|
||||
self.session = ncm.login.LoginQrcodeUnikey()["unikey"]
|
||||
loginURL = f"https://music.163.com/login?codekey={self.session}"
|
||||
#return self._qrcode(loginURL)
|
||||
return loginURL
|
||||
|
||||
def logout(self) -> dict:
|
||||
self.status.cache_clear()
|
||||
ncm.login.LoginLogout()
|
||||
self.accountType = AccountType.ACCOUNT_NOLOGIN
|
||||
return {"status": 200, "msg": _("Netease account logged out.")}, 200
|
||||
|
||||
@lru_cache(maxsize=8)
|
||||
def status(self) -> dict:
|
||||
return ncm.login.GetCurrentLoginStatus()
|
||||
|
||||
def userStatus(self) -> dict:
|
||||
try:
|
||||
status = self.status()
|
||||
if self.accountType == AccountType.ACCOUNT_NOLOGIN:
|
||||
return {"status": 200, "accountType": self.accountType, "username": "未登录"}
|
||||
if self.accountType == AccountType.ACCOUNT_ANONYMOUS:
|
||||
return {"status": 200, "accountType": self.accountType, "username": "匿名用户"}
|
||||
return {"status": 200, "accountType": self.accountType, "username": status["profile"]["nickname"]}
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return {"status": 500, "msg": _("Get status failed, internal server error!")}
|
||||
|
||||
def loginAnonymous(self) -> dict:
|
||||
self.status.cache_clear()
|
||||
try:
|
||||
ncm.login.LoginViaAnonymousAccount()
|
||||
self.accountType = AccountType.ACCOUNT_ANONYMOUS
|
||||
return {"status": 200, "msg": _("Netease anonymous account logged in.")}, 200
|
||||
except:
|
||||
return {"status": 408, "msg": _("Netease anonymous account login failed, timed out.")}, 408
|
||||
|
||||
def checkURL(self, content: str) -> bool:
|
||||
for i in self.URLKeyword:
|
||||
if i in content:
|
||||
return True
|
||||
return False
|
||||
|
||||
def parseURL(self, content: str) -> int or None:
|
||||
uid = None
|
||||
try:
|
||||
if "music.163.com" in content:
|
||||
uid = re.search(r"song\?id=[0-9]+",content).group()
|
||||
uid = int(uid[8:])
|
||||
elif "网易云" in content:
|
||||
uid = re.search(r"song\?id=[0-9]+",content).group()
|
||||
uid = int(uid[8:])
|
||||
elif "163cn.tv" in content:
|
||||
uid = httpx.get(content).headers.get("location")
|
||||
uid = re.search(r"song\?id=[0-9]+",uid).group()
|
||||
uid = int(uid[8:])
|
||||
elif type(eval(content)) == type(1):
|
||||
uid = eval(content)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return None
|
||||
return uid
|
||||
|
||||
def checkSong(self, content: int) -> SongStatus:
|
||||
response = self._getSongStatus(content)
|
||||
try:
|
||||
if len(response["songs"]) == 0:
|
||||
return SongStatus.SONG_NOTEXIST
|
||||
if response["songs"][0]["fee"] == 1:
|
||||
return SongStatus.SONG_ISVIP
|
||||
return SongStatus.SONG_EXIST
|
||||
except:
|
||||
return SongStatus.SONG_NOTEXIST
|
||||
return SongStatus.SONG_NOTEXIST
|
||||
|
||||
def getSongURL(self, content: int) -> str:
|
||||
response = ncm.track.GetTrackAudio(content)
|
||||
return response["data"][0]["url"]
|
||||
|
||||
def getSongImage(self, content: int) -> str:
|
||||
response = self._getSongStatus(content)
|
||||
return response["songs"][0]["al"]["picUrl"]
|
||||
|
||||
def getSongName(self, content: int) -> str:
|
||||
response = self._getSongStatus(content)
|
||||
name = response["songs"][0]["name"] + "-"
|
||||
for i in response["songs"][0]["ar"]:
|
||||
name += i["name"]
|
||||
name += "/"
|
||||
return name[:-1]
|
||||
|
||||
def getSongLyric(self, content: int) -> str:
|
||||
#print(self._getSongStatus(content))
|
||||
response = ncm.track.GetTrackLyrics(content)
|
||||
return response['lrc']['lyric']
|
||||
|
||||
def dumpSession(self) -> str:
|
||||
return pyncm.DumpSessionAsString(pyncm.GetCurrentSession())
|
||||
|
||||
def restoreSession(self, session: str):
|
||||
session = pyncm.LoadSessionFromString(session)
|
||||
pyncm.SetCurrentSession(session)
|
||||
|
||||
@lru_cache(maxsize=256)
|
||||
def _getSongStatus(self, content: int) -> dict:
|
||||
response = ncm.track.GetTrackDetail(content)
|
||||
return response
|
||||
|
||||
if __name__ == "__main__":
|
||||
ncmsess = NeteaseSession()
|
||||
print(ncmsess.parseURL("分享心华的单曲《一人行者》: https://y.music.163.com/m/song?id=526116050&userid=1578177131&dlt=0846 (来自@网易云音乐)"))
|
||||
# print(ncmsess.loginAnonymous())
|
||||
# print(ncmsess.dumpSession())
|
||||
# print(ncmsess.checkSong(1828241226))
|
||||
print(ncmsess.getSongLyric(30953009))
|
||||
# print(ncmsess.checkSong(2014291207))
|
||||
# print(ncmsess.checkSong(1901371647))
|
||||
# print(ncmsess.checkSong(1828241226))
|
||||
# print(ncmsess.getSongName(1828241226))
|
||||
# print(ncmsess.getSongImage(1828241226))
|
||||
# print(ncmsess.getSongURL(1828241226))
|
||||
# print(ncmsess.status())
|
||||
# print(ncmsess._getSongStatus.cache_info())
|
||||
@@ -0,0 +1,65 @@
|
||||
import qrcode
|
||||
from enum import Enum, StrEnum
|
||||
from abc import abstractmethod, ABC
|
||||
|
||||
class SongStatus(Enum):
|
||||
SONG_EXIST = 1
|
||||
SONG_NOTEXIST = 0
|
||||
SONG_NOTAVAIL = 2
|
||||
SONG_ISVIP = 3
|
||||
|
||||
class AccountType(StrEnum):
|
||||
ACCOUNT_NOLOGIN = "未登录"
|
||||
ACCOUNT_ANONYMOUS = "匿名用户"
|
||||
ACCOUNT_USER = "普通用户"
|
||||
ACCOUNT_VIP = "VIP用户"
|
||||
ACCOUNT_COOKIE = "Cookie登录"
|
||||
def __json__(self):
|
||||
return self.value
|
||||
|
||||
class SessionProvider():
|
||||
tempPath = None
|
||||
session = None
|
||||
|
||||
@abstractmethod
|
||||
def login(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def logout(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def qrcode(self):
|
||||
pass
|
||||
|
||||
def _qrcode(self, content):
|
||||
raise DeprecationWarning("Please use string instead.")
|
||||
if type(content)!=type("string"):
|
||||
raise ValueError("QRCode content should be string")
|
||||
img = qrcode.make(content)
|
||||
return img
|
||||
|
||||
@abstractmethod
|
||||
def status(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def loginAnonymous(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def checkSong(self, content: int):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def parseURL(self, content: str):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def dumpSession(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def restoreSession(self, session: str):
|
||||
pass
|
||||
@@ -0,0 +1,99 @@
|
||||
try:
|
||||
from Provider import SessionProvider, SongStatus, AccountType
|
||||
except:
|
||||
from Extensions.Provider import SessionProvider, SongStatus, AccountType
|
||||
from enum import Enum
|
||||
import httpx
|
||||
import re
|
||||
from functools import lru_cache
|
||||
|
||||
try:
|
||||
print(_)
|
||||
except:
|
||||
_ = lambda x: x
|
||||
|
||||
# 次奥,要手写API了
|
||||
class QQMusicSession(SessionProvider):
|
||||
session = None
|
||||
providerName = "QQMusic"
|
||||
URL_Keyword = ["y.qq.com"]
|
||||
accountType = AccountType.ACCOUNT_NOLOGIN
|
||||
cookies = {}
|
||||
def __init__(self):
|
||||
print(_("WARN: This extension is deprecated due to the change of APIkey of y.qq.com!"))
|
||||
|
||||
def loginAnonymous(self):
|
||||
return {"status": 501, "msg": _("QQMusic does not support anonymous login.")}, 501
|
||||
|
||||
def loginCredential(self, content: str, key: str):
|
||||
cookies = content
|
||||
return {"status": 200, "msg": _("Cookies updated.")}
|
||||
|
||||
def login(self) -> dict:
|
||||
if cookies:
|
||||
self.accountType = AccountType.ACCOUNT_COOKIE
|
||||
return {"status": 200, "msg": _("QQMusic login succeeded.")}
|
||||
else:
|
||||
return {"status": 401, "msg": _("Cookie required!")}, 401
|
||||
|
||||
def logout(self):
|
||||
self.accountType = AccountType.ACCOUNT_NOLOGIN
|
||||
self.cookies = ""
|
||||
return {"status": 200, "msg": _("QQMusic logout succeeded.")}
|
||||
|
||||
def parseURL(self, content: str) -> str or None:
|
||||
mid = None
|
||||
try:
|
||||
if "fcgi-bin/u?__" in content:
|
||||
content = re.search(r"https://.*", content).group()
|
||||
print(content)
|
||||
mid = httpx.get(content).headers.get("location")
|
||||
mid = re.search(r"songmid=(.+?)&", mid).group(1)
|
||||
elif "ryqq/songDetail" in content:
|
||||
mid = re.search(r"/[^/]+$", content).group()[1:]
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return None
|
||||
return mid
|
||||
|
||||
def checkURL(self, content: str) -> bool:
|
||||
for i in self.URLKeyword:
|
||||
if i in content:
|
||||
return True
|
||||
return False
|
||||
|
||||
def getSongURL(self, content: str) -> str:
|
||||
payload = r"https://u.y.qq.com/cgi-bin/musicu.fcg?format=json&data={%22req_0%22:{%22module%22:%22vkey.GetVkeyServer%22,%22method%22:%22CgiGetVkey%22,%22param%22:{%22filename%22:[%22PREFIXSONGMIDSONGMID.SUFFIX%22],%22guid%22:%2210000%22,%22songmid%22:[%22SONGMID%22],%22songtype%22:[0],%22uin%22:%220%22,%22loginflag%22:1,%22platform%22:%2220%22}},%22loginUin%22:%220%22,%22comm%22:{%22uin%22:%220%22,%22format%22:%22json%22,%22ct%22:24,%22cv%22:0}}"
|
||||
payload = payload.replace("PREFIX", "M500")
|
||||
payload = payload.replace("SONGMID", content)
|
||||
payload = payload.replace("SUFFIX", "mp3")
|
||||
data = httpx.get(payload, cookies=self.cookies)
|
||||
print(payload)
|
||||
print(data.json())
|
||||
print(a:=data.json()["req_0"]["data"]["midurlinfo"][0]["purl"])
|
||||
print(b:=data.json()["req_0"]["data"]["sip"][0])
|
||||
return b+a
|
||||
|
||||
@lru_cache(maxsize=256)
|
||||
def _getSongStatus(self, content:str) -> dict:
|
||||
payload = r"https://u.y.qq.com/cgi-bin/musicu.fcg?format=json&data={%22req%22:{%22module%22:%22music.pf_song_detail_svr%22,%22method%22:%22get_song_detail_yqq%22,%22param%22:{%22songmid%22:%22SONGMID%22}},%22loginUin%22:%220%22,%22comm%22:{%22uin%22:%220%22,%22format%22:%22json%22,%22ct%22:24,%22cv%22:0}}"
|
||||
payload = payload.replace("SONGMID", content)
|
||||
data = httpx.get(payload,cookies=self.cookies )
|
||||
print(data.json())
|
||||
|
||||
def dumpSession(self) -> str:
|
||||
return self.cookies
|
||||
|
||||
def restoreSession(self, session: str):
|
||||
self.cookies = session
|
||||
self.login()
|
||||
|
||||
if __name__ == "__main__":
|
||||
cookies = "pac_uid=1_1599172402; iip=0; _qimei_fingerprint=a60e880d499fb09dfa3fc2e7b67f5cac; _qimei_q36=; _qimei_h38=5261d3497d310405f78852440900000df17a1e; RK=vx0t0LFmeo; ptcz=ad36d5ca2a0e7a6b7250c853a187f263446dfb2530128858c1c61c5fcc10875c; pgv_pvid=2920262853; eas_sid=d14770F6p8X5a1M6V2z6X8H9W1; qq_domain_video_guid_verify=c6c04001d0488e55; o_cookie=1599172402; suid=ek168787168502135101; _qimei_uuid42=18401162425100258014e7193532ca71547ede2d09; vversion_name=8.2.95; pgv_info=ssid=s4743344675; uin=1599172402; fqm_pvqid=d34c53e5-031d-4762-b0e1-10a3b13013b6; fqm_sessionid=12a505d3-2ba4-4fd8-b025-6d773e4153e6; ts_refer=cn.bing.com/; ts_uid=2582842080; ts_last=y.qq.com/n/ryqq/songDetail/002vbWwe3ZmffT; login_type=1; psrf_qqaccess_token=2BFFE211C6375E16CAE328C65066052A; qm_keyst=Q_H_L_63k3Naa-qgZKZwiv-Q6TaqMCUYqy0nrKuDW6p2NbmOYb0AYcnh1ttAqA9Az9tpu2D1FCrAkeJFQ; wxunionid=; euin=oK4qNK6lowvzoc**; psrf_qqrefresh_token=B75229616609EF6988510371BEF3EFA2; psrf_qqunionid=9A90D76C7D610B8DF336DC5F19EC8773; qqmusic_key=Q_H_L_63k3Naa-qgZKZwiv-Q6TaqMCUYqy0nrKuDW6p2NbmOYb0AYcnh1ttAqA9Az9tpu2D1FCrAkeJFQ; tmeLoginType=2; music_ignore_pskey=202306271436Hn@vBj; psrf_musickey_createtime=1714623437; wxrefresh_token=; psrf_access_token_expiresAt=1722399437; psrf_qqopenid=027F5F42CADC5D52F710B2936B81B0C5; wxopenid="
|
||||
qmsess = QQMusicSession()
|
||||
smid = qmsess.parseURL("https://c6.y.qq.com/base/fcgi-bin/u?__=yCNzlNSNbaWD")
|
||||
smid = qmsess.parseURL("https://y.qq.com/n/ryqq/songDetail/000lKV2G0PEkc3")
|
||||
print(qmsess.loginCredential(cookies, ""))
|
||||
print(qmsess.login())
|
||||
print(qmsess.getSongURL(smid))
|
||||
#print(qmsess._getSongStatus(smid))
|
||||
@@ -0,0 +1,33 @@
|
||||
import re
|
||||
import time
|
||||
def lrcFormat(content: str) -> str:
|
||||
lrcTuple = []
|
||||
result = ""
|
||||
def strTimeToFloat(content: str) -> float:
|
||||
t = re.findall(r"\d+", content)
|
||||
if len(t) == 3:
|
||||
return int(t[0])*60 + int(t[1]) + float(t[2])/(10**(len(t[2])))
|
||||
elif len(t) == 2:
|
||||
return int(t[0])*60 + float(t[1])
|
||||
def floatToStrTime(content: float) -> str:
|
||||
return f"{int(content//60):02d}:{int(content%60):02d}.{int(content*1000%1000):03d}"
|
||||
# 读取歌词
|
||||
if content is None:
|
||||
return "[0.0] 该歌曲暂无歌词\n"
|
||||
for i in content.split('\n'):
|
||||
a = re.findall(r"\[.*?\]",i)
|
||||
timestamps = [strTimeToFloat(i[1:-1]) for i in a]
|
||||
if len(timestamps) == 0:
|
||||
continue
|
||||
c = re.sub(r"\[.*?\]", "", i).strip()
|
||||
for i in timestamps:
|
||||
lrcTuple.append((i, c))
|
||||
lrcTuple.sort(key=lambda x:x[0])
|
||||
for i in lrcTuple:
|
||||
result += "[" + str(i[0]) + "] " + i[1] + "\n"
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
data = open("test.lrc", "r").read()
|
||||
open("result.lrc", "w").write(lrcFormat(data))
|
||||
@@ -0,0 +1,61 @@
|
||||
import httpx
|
||||
from Music import Music
|
||||
import uuid
|
||||
from Extensions.Netease import NeteaseSession
|
||||
from Extensions.Provider import SessionProvider
|
||||
from mutagen import mp3, flac
|
||||
|
||||
providers = []
|
||||
|
||||
def downloadMusic(music: Music, path: str) -> dict:
|
||||
musicUUID = str(uuid.uuid4())
|
||||
filename = path + "/" + musicUUID + ".mp3"
|
||||
imagefilename = path + "/" + musicUUID + ".jpg"
|
||||
lyricfilename = path + "/" + musicUUID + ".lrc"
|
||||
try:
|
||||
with httpx.stream("GET", music.musicURL) as r:
|
||||
with open(filename, "wb") as f:
|
||||
for chunk in r.iter_bytes():
|
||||
f.write(chunk)
|
||||
with httpx.stream("GET", music.imageURL) as r:
|
||||
with open(imagefilename, "wb") as f:
|
||||
for chunk in r.iter_bytes():
|
||||
f.write(chunk)
|
||||
with open(lyricfilename, "wb") as f:
|
||||
f.write(music.lyric.encode("utf-8")) # 仅测试用途
|
||||
except PermissionError:
|
||||
return {"status": 500, "msg": _("File saving failed, permission denied!")}, 500
|
||||
except FileNotFoundError:
|
||||
return {"status": 500, "msg": _("File saving failed, path doesn't exist!")}, 500
|
||||
except:
|
||||
return {"status": 500, "msg": _("File saving failed, internal server error!")}, 500
|
||||
music.uuid = musicUUID
|
||||
return {"status": 200, "msg": _("File saved."), "filename": filename, "uuid": musicUUID}, 200
|
||||
|
||||
def judgeProvider(provider: str, content: str, providerList: list) -> SessionProvider or None :
|
||||
for i in providerList:
|
||||
if provider == i.providerName:
|
||||
return i
|
||||
if i.checkURL(content):
|
||||
return i
|
||||
return None
|
||||
|
||||
def imagePayload(music) -> bytes or None:
|
||||
with open(music, "rb") as file:
|
||||
if(music.endswith("mp3")):
|
||||
target = mp3.MP3(file)
|
||||
return target.tags['APIC:'].data
|
||||
elif(music.endswith("flac")):
|
||||
target = flac.FLAC(file)
|
||||
return target.pictures[0].data
|
||||
else:
|
||||
raise AttributeError(_("Unsupported file format."))
|
||||
raise AttributeError(_("File not found."))
|
||||
|
||||
if __name__ == "__main__":
|
||||
#ncmsession = NeteaseSession()
|
||||
#print(judgeProvider(None, "https://music.163.com/song?id=114514", [ncmsession]))
|
||||
#exit(0)
|
||||
mu01 = Music("pig2014", None, "My Ordinary Life", "Netease", "https://p2.music.126.net/E_oSaVzztyW0yg-5IvklCg==/109951165802120160.jpg", "http://m701.music.126.net/20240402195141/3b62ce359fc591ca8857a2dd0b9c6909/jdymusic/obj/wo3DlMOGwrbDjj7DisKw/28531063631/77f4/653d/bdf8/a43f2583f10ce9f41046586a19a19c77.mp3")
|
||||
print(downloadMusic(mu01, "/tmp/ttmp"))
|
||||
#print(mu01.dict())
|
||||
@@ -0,0 +1,64 @@
|
||||
import Music
|
||||
import pickle
|
||||
|
||||
class MusicQueue():
|
||||
queue = []
|
||||
|
||||
def __init__(self):
|
||||
self.queue = []
|
||||
|
||||
def push(self, element: Music):
|
||||
self.queue.append(element)
|
||||
|
||||
def pop(self):
|
||||
self.queue.pop(0)
|
||||
|
||||
def front(self) -> Music:
|
||||
return self.queue[0]
|
||||
|
||||
def clear(self):
|
||||
self.queue = []
|
||||
|
||||
def elevate(self, target: Music):
|
||||
# elevate target to 1st place
|
||||
self.queue.remove(target)
|
||||
self.queue.insert(0, target)
|
||||
|
||||
def find(self, target: Music) -> Music or None:
|
||||
for i in self.queue:
|
||||
if i == target:
|
||||
return i
|
||||
return None
|
||||
|
||||
def count(self, target: Music):
|
||||
count = 0
|
||||
for i in self.queue:
|
||||
if i == target:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
def findAndElevate(self, target: Music):
|
||||
target = self.find(target)
|
||||
if target is not None:
|
||||
self.elevate(target)
|
||||
|
||||
def delete(self, target: Music):
|
||||
target = self.find(target)
|
||||
if target is not None:
|
||||
self.queue.remove(target)
|
||||
|
||||
def size(self):
|
||||
return len(self.queue)
|
||||
|
||||
def dict(self):
|
||||
return [i.dict() for i in self.queue]
|
||||
|
||||
def __str__(self):
|
||||
return str(self.queue)
|
||||
|
||||
def dump(self):
|
||||
return pickle.dumps(self.queue)
|
||||
|
||||
def load(self, data: bytes):
|
||||
self.queue = pickle.loads(data)
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
data = {}
|
||||
|
||||
def create(username: str, stardust: int, nextCost: int):
|
||||
data[username] = (stardust, nextCost)
|
||||
|
||||
def read(username: str):
|
||||
return data[username]
|
||||
|
||||
def check(username: str):
|
||||
return username in data
|
||||
|
||||
def update(username: str, stardust: int, nextCost: int):
|
||||
data[username] = (stardust, nextCost)
|
||||
|
||||
def smartread(username: str, stardust: int, nextCost: int):
|
||||
if username not in data:
|
||||
create(username, stardust, nextCost)
|
||||
return data[username]
|
||||
@@ -0,0 +1,53 @@
|
||||
from flask import Flask, request, send_from_directory
|
||||
from dynaconf import FlaskDynaconf
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_security import Security, SQLAlchemyUserDatastore, auth_required, hash_password, current_user, login_user, verify_password, roles_accepted, logout_user
|
||||
from flask_security.models import fsqla_v3 as fsqla
|
||||
from flask_socketio import SocketIO, join_room, leave_room, emit
|
||||
from flask_wtf import CSRFProtect
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
app = Flask("GalaxyMusicHall")
|
||||
|
||||
|
||||
FlaskDynaconf(app, settings_files=["instance/config.toml", "instance/.secrets.toml"], environments=True)
|
||||
|
||||
db = SQLAlchemy(app)
|
||||
fsqla.FsModels.set_db_info(db)
|
||||
|
||||
class Role(db.Model, fsqla.FsRoleMixin):
|
||||
pass
|
||||
|
||||
|
||||
class User(db.Model, fsqla.FsUserMixin):
|
||||
pass
|
||||
|
||||
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
|
||||
CSRFProtect(app)
|
||||
app.security = Security(app, user_datastore)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) > 1:
|
||||
with app.app_context():
|
||||
print("初始化数据库...")
|
||||
db.create_all()
|
||||
app.security.datastore.find_or_create_role(name="admin")
|
||||
app.security.datastore.find_or_create_role(name="user")
|
||||
app.security.datastore.find_or_create_role(name="player")
|
||||
with open(sys.argv[1], "r", encoding="utf-8") as file:
|
||||
for i in file.readlines():
|
||||
username, password = i.strip().split(',')
|
||||
print(username,password)
|
||||
if not app.security.datastore.find_user(username=username):
|
||||
app.security.datastore.create_user(username=username, email= username+"@null.com", password=hash_password(password))
|
||||
user = app.security.datastore.find_user(username=username)
|
||||
app.security.datastore.add_role_to_user(user, "user")
|
||||
|
||||
db.session.commit()
|
||||
else:
|
||||
print("数据库连接有效")
|
||||
print("请在参数1指定.csv文件用于导入用户")
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
[default]
|
||||
# 指定密码哈希
|
||||
security_password_hash = "argon2"
|
||||
# 设置cookie SameSite属性
|
||||
remember_cookie_samesite = "strict"
|
||||
session_cookie_samesite = "strict"
|
||||
|
||||
# 允许推送
|
||||
enable_push = true
|
||||
# 设置用户推送限制
|
||||
user_pushlimit = 2
|
||||
# 设置总推送限制
|
||||
total_pushlimit = 60
|
||||
# 设置是否允许重复推送
|
||||
allow_duplicate_push = false
|
||||
|
||||
# 实例名称,用于在前端展示
|
||||
instance_name = "银河音乐厅Next_v1.0.0"
|
||||
# 缓存路径,存放下载的歌曲文件与封面等
|
||||
cache_path = "/tmp/galaxycache"
|
||||
# 是否保留缓存,若为false,则每次启动时都会清空缓存
|
||||
preserve_cache = true
|
||||
# 是否保存会话,上一选项为false时无效
|
||||
save_session = false
|
||||
|
||||
# 喵化API的"msg"字段输出
|
||||
meowlize = false
|
||||
|
||||
# 启用用户歌曲提升(消耗星尘)
|
||||
# 若不启用,则下述三个参数无效
|
||||
allow_user_elevation = true
|
||||
# 设置用户初始星尘数
|
||||
initial_stardust = 100
|
||||
# 设置初始星尘扣除数
|
||||
initial_stardust_cost = 10
|
||||
# 设置星尘扣除递增数
|
||||
stardust_cost_increase = 10
|
||||
|
||||
# 设置上传歌曲允许的后缀
|
||||
allowed_upload_music = ["mp3", "flac", "wav", "aac"]
|
||||
# 设置上传歌词允许的后缀
|
||||
allowed_upload_lyric = ["lrc"]
|
||||
# 设置上传歌曲的默认图标
|
||||
upload_music_image = "https://www.pngall.com/wp-content/uploads/2/Upload-PNG-Image.png"
|
||||
|
||||
# 允许播放器端登录网易云帐号
|
||||
allow_player_netease_login = false
|
||||
|
||||
# 以下为使Flask-Security工作必须的选项
|
||||
MAIL_DEFAULT_SENDER = "catty"
|
||||
SECURITY_EMAIL_SENDER = "catty"
|
||||
SECURITY_CSRF_PROTECT_MECHANISMS = ""
|
||||
WTF_CSRF_CHECK_DEFAULT = false
|
||||
|
||||
# 以下为使自定义登录API工作必须的选项
|
||||
SECURITY_LOGIN_URL = "/hiddenapi/login"
|
||||
SECURITY_LOGOUT_URL = "/hiddenapi/logout"
|
||||
|
||||
[development]
|
||||
# 只用来测试
|
||||
debug = 1
|
||||
host = "127.0.0.1"
|
||||
port = 5000
|
||||
cache_path = "/tmp/galaxycache"
|
||||
banner_file = "./banner.md"
|
||||
SQLALCHEMY_DATABASE_URI = "sqlite:///data.db"
|
||||
|
||||
[production]
|
||||
# 正式部署
|
||||
debug = 0
|
||||
host = "0.0.0.0"
|
||||
port = 5000
|
||||
cache_path = "/tmp/galaxycache"
|
||||
banner_file = "./banner.md"
|
||||
SQLALCHEMY_DATABASE_URI = "sqlite:///data_prod.db"
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,159 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-04-02 20:45+0800\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: python\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: main.py:55
|
||||
msgid "Not authenticated!"
|
||||
msgstr "请登录帐号喵!"
|
||||
|
||||
#: main.py:58
|
||||
msgid "Permission denied!"
|
||||
msgstr "权限不足喵!"
|
||||
|
||||
#: main.py:65
|
||||
msgid "Hello, this is the API testing page of galaxy music hall."
|
||||
msgstr "你好,这里是银河音乐厅的API测试页面喵。"
|
||||
|
||||
#: main.py:69
|
||||
msgid "Server is running"
|
||||
msgstr "服务器正在运行喵。"
|
||||
|
||||
#: main.py:81 main.py:86 main.py:94
|
||||
msgid "Incorrect user or password!"
|
||||
msgstr "用户名或密码错误喵!"
|
||||
|
||||
#: main.py:90
|
||||
msgid "Login success."
|
||||
msgstr "登录成功喵。"
|
||||
|
||||
#: main.py:92
|
||||
msgid "Login failed, unknown exception."
|
||||
msgstr "登录失败,未知异常喵!"
|
||||
|
||||
#: main.py:119
|
||||
msgid "No music in queue."
|
||||
msgstr "队列里是空的喵。"
|
||||
|
||||
#: main.py:102
|
||||
msgid "Push parameter is empty!"
|
||||
msgstr "推送参数是空的喵!"
|
||||
|
||||
#: main.py:149
|
||||
msgid "Unsupported music provider!"
|
||||
msgstr "不支持这个提供者喵!"
|
||||
|
||||
#: main.py:154
|
||||
msgid "The music does not exist!"
|
||||
msgstr "歌曲不存在喵!"
|
||||
|
||||
#: main.py:156
|
||||
msgid "The music is not available!"
|
||||
msgstr "歌曲不可用喵!"
|
||||
|
||||
#: main.py:158
|
||||
msgid "The music is VIP only, please contact administrator to login VIP account!"
|
||||
msgstr "歌曲是VIP音乐,请联系管理员登录VIP帐号喵!"
|
||||
|
||||
#: main.py:164 main.py:233
|
||||
msgid "The music is already in the queue!"
|
||||
msgstr "歌曲已经在队列里了喵!"
|
||||
|
||||
#: main.py:169
|
||||
msgid "Successfully pushed the music."
|
||||
msgstr "成功推送喵。"
|
||||
|
||||
#: main.py:179 main.py:187 main.py:199 main.py:211
|
||||
msgid "The music is not in the queue!"
|
||||
msgstr "歌曲不在队列里喵!"
|
||||
|
||||
#: main.py:181 main.py:201
|
||||
msgid "You are not the owner of the music!"
|
||||
msgstr "你不是歌曲的拥有者喵!"
|
||||
|
||||
#: main.py:183 main.py:189
|
||||
msgid "Successfully deleted the music."
|
||||
msgstr "成功删除喵。"
|
||||
|
||||
#: main.py:203
|
||||
msgid "You do not have enough stardust to elevate the music!"
|
||||
msgstr "你没有足够的星尘来提升歌曲喵!"
|
||||
|
||||
#: main.py:207 main.py:213
|
||||
msgid "Successfully elevated the music."
|
||||
msgstr "成功提升喵。"
|
||||
|
||||
#: main.py:229
|
||||
msgid "No file selected!"
|
||||
msgstr "没有选择文件喵!"
|
||||
|
||||
#: main.py:231
|
||||
msgid "Invalid file format!"
|
||||
msgstr "无效的文件格式喵!"
|
||||
|
||||
#: main.py:239
|
||||
msgid "Failed to save the file!"
|
||||
msgstr "无法保存文件喵!"
|
||||
|
||||
#: main.py:242
|
||||
msgid "Successfully uploaded the music!"
|
||||
msgstr "成功上传歌曲喵。"
|
||||
|
||||
#: main.py:258 main.py:279
|
||||
msgid "Failed to reload the banner!"
|
||||
msgstr "无法加载Banner喵!"
|
||||
|
||||
#: main.py:277
|
||||
msgid "Permission denied to access cache directory!"
|
||||
msgstr "没有访问缓存目录的权限喵!"
|
||||
|
||||
#: MusicManager.py:23
|
||||
msgid "File saving failed, permission denied!"
|
||||
msgstr "文件保存失败喵!(权限不够喵...)"
|
||||
|
||||
#: MusicManager.py:25
|
||||
msgid "File saving failed, path doesn't exist!"
|
||||
msgstr "文件保存失败喵!(路径不存在喵...)"
|
||||
|
||||
#: MusicManager.py:27
|
||||
msgid "File saving failed, internal server error!"
|
||||
msgstr "文件保存失败喵!(服务器出错了喵...)"
|
||||
|
||||
#: MusicManager.py:29
|
||||
msgid "File saved."
|
||||
msgstr "文件保存了喵。"
|
||||
|
||||
#: Extensions/Netease.py:27
|
||||
msgid "Netease account logged in."
|
||||
msgstr "网易云帐号登录成功喵。"
|
||||
|
||||
#: Extensions/Netease.py:29
|
||||
msgid "Netease account login failed."
|
||||
msgstr "网易云帐号登录失败喵。"
|
||||
|
||||
#: Extensions/Netease.py:39
|
||||
msgid "Netease account logged out."
|
||||
msgstr "网易云帐号登出了喵。"
|
||||
|
||||
#: Extensions/Netease.py:48
|
||||
msgid "Netease anonymous account logged in."
|
||||
msgstr "网易云匿名帐号登录成功喵。"
|
||||
|
||||
#: Extensions/Netease.py:50
|
||||
msgid "Netease anonymous account login failed, timed out."
|
||||
msgstr "网易云帐号登录失败喵。(超时了喵)"
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,158 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-04-02 20:45+0800\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: python\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: main.py:55
|
||||
msgid "Not authenticated!"
|
||||
msgstr "请登录帐号!"
|
||||
|
||||
#: main.py:58
|
||||
msgid "Permission denied!"
|
||||
msgstr "权限不足!"
|
||||
|
||||
#: main.py:65
|
||||
msgid "Hello, this is the API testing page of galaxy music hall."
|
||||
msgstr "你好,这里是银河音乐厅的API测试页面。"
|
||||
|
||||
#: main.py:69
|
||||
msgid "Server is running"
|
||||
msgstr "服务器正在运行。"
|
||||
|
||||
#: main.py:81 main.py:86 main.py:94
|
||||
msgid "Incorrect user or password!"
|
||||
msgstr "用户名或密码错误!"
|
||||
|
||||
#: main.py:90
|
||||
msgid "Login success."
|
||||
msgstr "登录成功。"
|
||||
|
||||
#: main.py:92
|
||||
msgid "Login failed, unknown exception."
|
||||
msgstr "登录失败,未知异常!"
|
||||
|
||||
#: main.py:119
|
||||
msgid "No music in queue."
|
||||
msgstr "队列为空。"
|
||||
|
||||
#: main.py:102
|
||||
msgid "Push parameter is empty!"
|
||||
msgstr "推送参数为空!"
|
||||
|
||||
#: main.py:149
|
||||
msgid "Unsupported music provider!"
|
||||
msgstr "不支持这个提供者!"
|
||||
|
||||
#: main.py:154
|
||||
msgid "The music does not exist!"
|
||||
msgstr "歌曲不存在!"
|
||||
|
||||
#: main.py:156
|
||||
msgid "The music is not available!"
|
||||
msgstr "歌曲不可用!"
|
||||
|
||||
#: main.py:158
|
||||
msgid "The music is VIP only, please contact administrator to login VIP account!"
|
||||
msgstr "歌曲是VIP音乐,请联系管理员登录VIP帐号!"
|
||||
|
||||
#: main.py:164 main.py:233
|
||||
msgid "The music is already in the queue!"
|
||||
msgstr "歌曲已经在队列里了!"
|
||||
|
||||
#: main.py:169
|
||||
msgid "Successfully pushed the music."
|
||||
msgstr "成功推送。"
|
||||
|
||||
#: main.py:179 main.py:187 main.py:199 main.py:211
|
||||
msgid "The music is not in the queue!"
|
||||
msgstr "歌曲不在队列里!"
|
||||
|
||||
#: main.py:181 main.py:201
|
||||
msgid "You are not the owner of the music!"
|
||||
msgstr "你不是歌曲的拥有者!"
|
||||
|
||||
#: main.py:183 main.py:189
|
||||
msgid "Successfully deleted the music."
|
||||
msgstr "成功删除。"
|
||||
|
||||
#: main.py:203
|
||||
msgid "You do not have enough stardust to elevate the music!"
|
||||
msgstr "你没有足够的星尘来提升歌曲!"
|
||||
|
||||
#: main.py:207 main.py:213
|
||||
msgid "Successfully elevated the music."
|
||||
msgstr "成功提升。"
|
||||
|
||||
#: main.py:229
|
||||
msgid "No file selected!"
|
||||
msgstr "没有选择文件!"
|
||||
|
||||
#: main.py:231
|
||||
msgid "Invalid file format!"
|
||||
msgstr "无效的文件格式!"
|
||||
|
||||
#: main.py:239
|
||||
msgid "Failed to save the file!"
|
||||
msgstr "无法保存文件!"
|
||||
|
||||
#: main.py:242
|
||||
msgid "Successfully uploaded the music!"
|
||||
msgstr "成功上传歌曲。"
|
||||
|
||||
#: main.py:258 main.py:279
|
||||
msgid "Failed to reload the banner!"
|
||||
msgstr "无法加载Banner!"
|
||||
|
||||
#: main.py:277
|
||||
msgid "Permission denied to access cache directory!"
|
||||
msgstr "没有访问缓存目录的权限!"
|
||||
|
||||
#: MusicManager.py:23
|
||||
msgid "File saving failed, permission denied!"
|
||||
msgstr "文件保存失败!(权限不够)"
|
||||
|
||||
#: MusicManager.py:25
|
||||
msgid "File saving failed, path doesn't exist!"
|
||||
msgstr "文件保存失败!(路径不存在)"
|
||||
|
||||
#: MusicManager.py:27
|
||||
msgid "File saving failed, internal server error!"
|
||||
msgstr "文件保存失败!(内部服务器错误)"
|
||||
|
||||
#: MusicManager.py:29
|
||||
msgid "File saved."
|
||||
msgstr "文件保存成功。"
|
||||
|
||||
#: Extensions/Netease.py:27
|
||||
msgid "Netease account logged in."
|
||||
msgstr "网易云帐号登录成功。"
|
||||
|
||||
#: Extensions/Netease.py:29
|
||||
msgid "Netease account login failed."
|
||||
msgstr "网易云帐号登录失败。"
|
||||
|
||||
#: Extensions/Netease.py:39
|
||||
msgid "Netease account logged out."
|
||||
msgstr "网易云帐号登出了。"
|
||||
|
||||
#: Extensions/Netease.py:48
|
||||
msgid "Netease anonymous account logged in."
|
||||
msgstr "网易云匿名帐号登录成功。"
|
||||
|
||||
#: Extensions/Netease.py:50
|
||||
msgid "Netease anonymous account login failed, timed out."
|
||||
msgstr "网易云帐号登录失败。(请求超时)"
|
||||
@@ -0,0 +1,630 @@
|
||||
import uuid
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from flask import Flask, request, send_from_directory
|
||||
from dynaconf import FlaskDynaconf
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_security import Security, SQLAlchemyUserDatastore, auth_required, hash_password, current_user, login_user, verify_password, roles_accepted, logout_user
|
||||
from flask_security.models import fsqla_v3 as fsqla
|
||||
from flask_socketio import SocketIO, join_room, leave_room, emit
|
||||
from flask_wtf import CSRFProtect
|
||||
|
||||
import gettext
|
||||
import warnings
|
||||
import signal
|
||||
import sys
|
||||
import pickle
|
||||
|
||||
from MusicQueue import MusicQueue
|
||||
from Music import Music
|
||||
import MusicManager
|
||||
import Stardust
|
||||
import LyricFormatter
|
||||
|
||||
from Extensions.Netease import NeteaseSession
|
||||
# from Extensions.QQMusic import QQMusicSession
|
||||
# from Extensions.Bilibili import BilibiliSession
|
||||
from Extensions.Provider import SongStatus, AccountType, SessionProvider
|
||||
|
||||
app = Fla325-576-113sk("GalaxyMusicHall")
|
||||
|
||||
socketio = SocketIO(app, cors_allowed_origins='*')
|
||||
|
||||
# 启用配置文件读取
|
||||
FlaskDynaconf(app, settings_files=[
|
||||
"instance/config.toml", "instance/.secrets.toml"], environments=True)
|
||||
|
||||
# 喵化输出
|
||||
if app.config["meowlize"]:
|
||||
os.environ["LANGUAGE"] = "meow"
|
||||
|
||||
# 初始化gettext
|
||||
gettext.install('main', localedir='./locales')
|
||||
|
||||
db = SQLAlchemy(app)
|
||||
fsqla.FsModels.set_db_info(db)
|
||||
|
||||
# 初始化扩展会话
|
||||
ncmSession = NeteaseSession()
|
||||
|
||||
|
||||
class Role(db.Model, fsqla.FsRoleMixin):
|
||||
pass
|
||||
|
||||
|
||||
class User(db.Model, fsqla.FsUserMixin):
|
||||
pass
|
||||
|
||||
|
||||
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
|
||||
CSRFProtect(app)
|
||||
app.security = Security(app, user_datastore)
|
||||
|
||||
queue = MusicQueue()
|
||||
current = Music()
|
||||
|
||||
providerList = [ncmSession]
|
||||
|
||||
SOCKETIO_UPDATE_RESPONSE = {"status": 101, "msg": "该API使用SocketIO接口"}, 200
|
||||
|
||||
|
||||
def unauthenticated(mechanisms=None, headers=None):
|
||||
return {"status": 401, "msg": _("Not authenticated!")}, 401
|
||||
|
||||
|
||||
def denied(mechanisms=None, headers=None):
|
||||
return {"status": 403, "msg": _("Permission denied!")}, 403
|
||||
|
||||
|
||||
app.security.unauthn_handler(unauthenticated)
|
||||
app.security.unauthz_handler(denied)
|
||||
|
||||
|
||||
def sigint_handler(signal, frame):
|
||||
print(_("Quitting..."))
|
||||
saveSession()
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
signal.signal(signal.SIGINT, sigint_handler)
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def test():
|
||||
return {"status": 200, "msg": _("Hello, this is the API testing page of galaxy music hall."), "debug": app.debug}
|
||||
|
||||
|
||||
@app.route("/ping", methods=["GET"])
|
||||
def ping():
|
||||
return {"message": _("Server is running"),
|
||||
"instanceName": app.config.get("INSTANCE_NAME")}, 200
|
||||
|
||||
|
||||
@app.route("/config", methods=["GET"])
|
||||
@auth_required()
|
||||
def getConfig():
|
||||
return {"allowPlayerNeteaseLogin": app.config.get("ALLOW_PLAYER_NETEASE_LOGIN")}, 200
|
||||
|
||||
|
||||
@app.route("/user/login", methods=["POST"])
|
||||
def userLogin(): # SocketIO似乎不能写cookies
|
||||
username = request.form.get("username")
|
||||
if username is None:
|
||||
return {"status": 403, "msg": _("Incorrect user or password!")}, 403
|
||||
password = request.form.get("password")
|
||||
try:
|
||||
status = verify_password(
|
||||
password, user_datastore.find_user(username=username).password)
|
||||
if status is False:
|
||||
return {"status": 403, "msg": _("Incorrect user or password!")}, 403
|
||||
status = login_user(user_datastore.find_user(username=username), remember=True,
|
||||
authn_via="password")
|
||||
if status:
|
||||
return {"status": 200, "msg": _("Login success.")}, 200
|
||||
else:
|
||||
return {"status": 400, "msg": _("Login failed, unknown exception.")}, 400
|
||||
except AttributeError:
|
||||
return {"status": 403, "msg": _("Incorrect user or password!")}, 403
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return {"status": 500, "msg": _("Login failed, internal server error.")}, 500
|
||||
|
||||
|
||||
@app.route("/user/logout", methods=["POST"])
|
||||
def userLogout():
|
||||
try:
|
||||
logout_user()
|
||||
return {"status": 200, "msg": _("Logout success.")}
|
||||
except:
|
||||
return {"status": 500, "msg": _("Logout failed, internal server error.")}, 500
|
||||
|
||||
|
||||
@socketio.on("userStatus")
|
||||
def userStatus():
|
||||
try:
|
||||
stardust, nextCost = Stardust.smartread(
|
||||
current_user.username, app.config["INITIAL_STARDUST"], app.config["INITIAL_STARDUST_COST"])
|
||||
return {"status": current_user.is_authenticated,
|
||||
"username": current_user.username,
|
||||
"role": current_user.roles[0].name,
|
||||
"stardust": stardust,
|
||||
"nextCost": nextCost}
|
||||
except Exception as e:
|
||||
return {"status": False,
|
||||
"username": None,
|
||||
"role": None,
|
||||
"stardust": -1,
|
||||
"nextCost": 65536}
|
||||
|
||||
return SOCKETIO_UPDATE_RESPONSE
|
||||
|
||||
|
||||
@app.route("/music/current", methods=["GET"])
|
||||
@auth_required()
|
||||
def getCurrentMusic():
|
||||
return current.dict(), 200
|
||||
|
||||
|
||||
@socketio.on("nextMusic")
|
||||
def nextMusic():
|
||||
global current
|
||||
try:
|
||||
current = queue.front()
|
||||
if current.uuid == "KOBEBRYANT":
|
||||
return
|
||||
queue.pop()
|
||||
updateQueue()
|
||||
return {"status": 200}, 200
|
||||
except:
|
||||
current = Music()
|
||||
updateQueue()
|
||||
return {"status": 204, "msg": _("No music in queue.")}, 204
|
||||
|
||||
# @app.route("/music/queue", methods=["GET"])
|
||||
|
||||
|
||||
@socketio.on("getQueue") # TODO: 设置未登录用户可见性
|
||||
def getQueue():
|
||||
socketio.emit("updateQueue", {
|
||||
"current": current.dict(), "queue": queue.dict()})
|
||||
|
||||
|
||||
def updateQueue():
|
||||
emit("updateQueue", {"current": current.dict(),
|
||||
"queue": queue.dict()}, broadcast=True, namespace="/")
|
||||
saveSession()
|
||||
|
||||
|
||||
def pushLimitCheck():
|
||||
if app.config["ENABLE_PUSH"] is False:
|
||||
return {"status": 403, "msg": _("Push is disabled!")}, 403
|
||||
username = current_user.username
|
||||
if not "admin" in current_user.roles:
|
||||
count = queue.count(Music(owner=username))
|
||||
if count >= app.config["USER_PUSHLIMIT"]:
|
||||
return {"status": 420, "msg": _("You have reached the push limit!")}, 420
|
||||
count = queue.size()
|
||||
if count >= app.config["TOTAL_PUSHLIMIT"]:
|
||||
return {"status": 429, "msg": _("The push queue is full!")}, 429
|
||||
|
||||
|
||||
@socketio.on("pushMusic")
|
||||
@auth_required()
|
||||
def pushMusic(providerString, content): # TODO: 将下载改为异步API,允许查看进度 TODO: 修改musicURL逻辑
|
||||
# 推送速率限制
|
||||
if status := pushLimitCheck():
|
||||
return status
|
||||
# 阻止角色为"admin", "user"以外的用户推送
|
||||
# SocketIO似乎不支持auth_required
|
||||
if "admin" not in current_user.roles and "user" not in current_user.roles:
|
||||
return {"status": 403, "msg": _("Permission denied!")}, 403
|
||||
# 处理音乐提供者
|
||||
if content is None:
|
||||
return {"status": 400, "msg": _("Push parameter is empty!")}, 400
|
||||
provider = MusicManager.judgeProvider(
|
||||
providerString, content, providerList)
|
||||
if provider is None:
|
||||
return {"status": 400, "msg": _("Unknown source, please specify!")}, 400
|
||||
# 由提供者提供服务
|
||||
musicID = provider.parseURL(content)
|
||||
musicStatus = provider.checkSong(musicID)
|
||||
if musicStatus == SongStatus.SONG_NOTEXIST:
|
||||
return {"status": 404, "msg": _("The music does not exist!")}, 404
|
||||
if musicStatus == SongStatus.SONG_NOTAVAIL:
|
||||
return {"status": 403, "msg": _("The music is not available!")}, 403
|
||||
if musicStatus == SongStatus.SONG_ISVIP and provider.accountType != AccountType.ACCOUNT_VIP:
|
||||
return {"status": 412, "msg": _("The music is VIP only, please contact administrator to login VIP account!")}, 403
|
||||
musicName = provider.getSongName(musicID)
|
||||
|
||||
music = Music(owner=current_user.username, uuid=None, name=provider.getSongName(musicID), source=provider.providerName, imageURL=provider.getSongImage(
|
||||
musicID), musicURL=provider.getSongURL(musicID), lyric=LyricFormatter.lrcFormat(provider.getSongLyric(musicID)))
|
||||
# 检查是否已存在
|
||||
if (not app.config["ALLOW_DUPLICATE_PUSH"]) and queue.find(Music(name=musicName)):
|
||||
return {"status": 201, "msg": _("The music is already in the queue!")}, 201
|
||||
# 下载歌曲
|
||||
MusicManager.downloadMusic(music, app.config["CACHE_PATH"])
|
||||
# 推送至队列
|
||||
queue.push(music)
|
||||
if not current.uuid:
|
||||
nextMusic()
|
||||
else:
|
||||
updateQueue()
|
||||
return {"status": 200, "msg": _("Successfully pushed the music.")}, 200
|
||||
|
||||
|
||||
@socketio.on("deleteMusic")
|
||||
@auth_required()
|
||||
def deleteMusic(musicUUID):
|
||||
# musicUUID = request.form.get("uuid")
|
||||
if musicUUID == "KOBEBRYANT":
|
||||
return {"status": 400, "msg": "Manba out!"}
|
||||
music = queue.find(Music(uuid=musicUUID))
|
||||
if not "admin" in current_user.roles:
|
||||
# 用户删除部分
|
||||
if music is None:
|
||||
return {"status": 404, "msg": _("The music is not in the queue!")}, 404
|
||||
if music.owner != current_user.username:
|
||||
return {"status": 403, "msg": _("You are not the owner of the music!")}, 403
|
||||
queue.delete(music)
|
||||
updateQueue()
|
||||
return {"status": 200, "msg": _("Successfully deleted the music.")}, 200
|
||||
else:
|
||||
# 管理员删除部分
|
||||
if music is None:
|
||||
return {"status": 404, "msg": _("The music is not in the queue!")}, 404
|
||||
queue.delete(music)
|
||||
updateQueue()
|
||||
return {"status": 200, "msg": _("Successfully deleted the music.")}, 200
|
||||
|
||||
|
||||
@socketio.on("elevateMusic")
|
||||
@auth_required()
|
||||
def elevateMusic(musicUUID):
|
||||
# musicUUID = request.form.get("uuid")
|
||||
music = queue.find(Music(uuid=musicUUID))
|
||||
if musicUUID == "KOBEBRYANT":
|
||||
return {"status": 400, "msg": "What can I say?"}
|
||||
if not "admin" in current_user.roles:
|
||||
# 用户提升部分
|
||||
if music is None:
|
||||
return {"status": 404, "msg": _("The music is not in the queue!")}, 404
|
||||
if music.owner != current_user.username:
|
||||
return {"status": 403, "msg": _("You are not the owner of the music!")}, 403
|
||||
stardust, nextCost = Stardust.smartread(
|
||||
current_user.username, app.config["INITIAL_STARDUST"], app.config["INITIAL_STARDUST_COST"])
|
||||
if stardust < nextCost:
|
||||
return {"status": 403, "msg": _("You do not have enough stardust to elevate the music!")}, 403
|
||||
queue.elevate(music)
|
||||
Stardust.update(current_user.username, stardust-nextCost,
|
||||
nextCost+app.config["STARDUST_COST_INCREASE"])
|
||||
updateQueue()
|
||||
emit("updateUserStatus", userStatus())
|
||||
return {"status": 200, "msg": _("Successfully elevated the music.")}, 200
|
||||
else:
|
||||
# 管理员提升部分
|
||||
if music is None:
|
||||
return {"status": 404, "msg": _("The music is not in the queue!")}, 404
|
||||
queue.elevate(music)
|
||||
updateQueue()
|
||||
return {"status": 200, "msg": _("Successfully elevated the music.")}, 200
|
||||
|
||||
|
||||
@app.route("/play/<url>")
|
||||
@auth_required()
|
||||
@roles_accepted("player")
|
||||
def playMusic(url):
|
||||
for i in app.config["ALLOWED_UPLOAD_MUSIC"]:
|
||||
try:
|
||||
os.stat(app.config["CACHE_PATH"]+"/"+url+"."+i)
|
||||
return send_from_directory(app.config["CACHE_PATH"], url + "." + i)
|
||||
except Exception as e:
|
||||
pass
|
||||
return {"data": None}
|
||||
|
||||
|
||||
@app.route("/image/<url>")
|
||||
@auth_required()
|
||||
def getImage(url):
|
||||
for i in ("jpg", "png"):
|
||||
try:
|
||||
os.stat(app.config["CACHE_PATH"]+"/"+url+"."+i)
|
||||
return send_from_directory(app.config["CACHE_PATH"], url + "." + i)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
pass
|
||||
return "No such file or directory!", 404
|
||||
|
||||
|
||||
@app.route("/lyric/<url>")
|
||||
@auth_required()
|
||||
def getLyric(url):
|
||||
return send_from_directory(app.config["CACHE_PATH"], url+".lrc")
|
||||
|
||||
|
||||
@app.route("/download")
|
||||
def downloadMusic():
|
||||
url = request.args.get("uuid")
|
||||
if url == "flag":
|
||||
return "用户拿flag,用户坏!", 400
|
||||
for i in app.config["ALLOWED_UPLOAD_MUSIC"]:
|
||||
try:
|
||||
os.stat(app.config["CACHE_PATH"] + url + "." + i)
|
||||
return send_from_directory(app.config["CACHE_PATH"], url + "." + i)
|
||||
except:
|
||||
pass
|
||||
path = app.config["CACHE_PATH"] + "/" + url
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
data = f.read()
|
||||
if "flag" in data:
|
||||
return "flag{****REDACTED****}"
|
||||
else:
|
||||
return data
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return "无法下载此音乐!", 400
|
||||
|
||||
|
||||
@socketio.on("adminResetQueue")
|
||||
@auth_required()
|
||||
def adminResetQueue():
|
||||
global current
|
||||
return {"status": 403, "msg": "已禁用此功能!"}, 403
|
||||
if not "admin" in current_user.roles:
|
||||
return {"status": 403, "msg": _("Permission denied!")}, 403
|
||||
queue.clear()
|
||||
current = Music()
|
||||
updateQueue()
|
||||
return {"status": 200, "msg": _("Reset success!")}
|
||||
|
||||
|
||||
@app.route("/upload", methods=["GET", "POST"])
|
||||
@roles_accepted("user", "admin")
|
||||
def uploadMusic(): # TODO: 修改musicURL逻辑
|
||||
# 推送速率限制
|
||||
if status := pushLimitCheck():
|
||||
return status
|
||||
# 上传处理
|
||||
file = request.files.get("file")
|
||||
if file is None or file.filename == "":
|
||||
# TODO: 空文件判断
|
||||
return
|
||||
if ".php" in file.filename:
|
||||
return {"status": 200, "msg": "我觉得银河音乐厅不是个php应用..."}
|
||||
# 是音乐文件
|
||||
if file.filename.endswith(tuple(app.config["ALLOWED_UPLOAD_MUSIC"])):
|
||||
suffix = file.filename.split(".")[-1]
|
||||
if queue.find(Music(name=file.filename)):
|
||||
return {"status": 201, "msg": _("The music is already in the queue!")}, 201
|
||||
musicUUID = str(uuid.uuid4())
|
||||
uploadFile = os.path.join(
|
||||
app.config["CACHE_PATH"], musicUUID+"."+suffix)
|
||||
uploadAlbum = app.config["UPLOAD_MUSIC_IMAGE"]
|
||||
try:
|
||||
file.save(uploadFile)
|
||||
except Exception as e:
|
||||
return {"status": 500, "msg": _("Failed to save the file!")}, 500
|
||||
try:
|
||||
album = MusicManager.imagePayload(uploadFile)
|
||||
if album is not None:
|
||||
albumFileName = os.path.join(
|
||||
app.config["CACHE_PATH"], musicUUID+".jpg")
|
||||
with open(albumFileName, "wb") as albumFile:
|
||||
albumFile.write(album)
|
||||
uploadAlbum = "/api/image/" + musicUUID
|
||||
except Exception as e:
|
||||
print(e)
|
||||
warnings.warn(_("Failed to read the album image!"))
|
||||
music = Music(owner=current_user.username, uuid=musicUUID, name=file.filename, source="Upload",
|
||||
imageURL=uploadAlbum, musicURL=uploadFile, lyric=LyricFormatter.lrcFormat(None))
|
||||
# TODO: 为上传的音乐添加歌词
|
||||
queue.push(music)
|
||||
if not current.uuid:
|
||||
nextMusic()
|
||||
else:
|
||||
updateQueue()
|
||||
return {"status": 200, "msg": _("Successfully uploaded the music!")}, 200
|
||||
# 是歌词文件
|
||||
elif file.filename.endswith(tuple(app.config["ALLOWED_UPLOAD_LYRIC"])):
|
||||
return {"status": 501, "msg": _("Lyric file is not supported yet!")}, 501
|
||||
else:
|
||||
return {"status": 400, "msg": _("Invalid file format!"), "allowed_format": app.config["ALLOWED_UPLOAD_MUSIC"]}, 400
|
||||
|
||||
|
||||
@socketio.on("getBanner")
|
||||
def getBanner():
|
||||
return {"data": open(app.config["CACHE_PATH"]+"/banner.md").read()}
|
||||
|
||||
|
||||
@socketio.on("adminPushMessage")
|
||||
def broadcastMessage(message: str):
|
||||
emit("broadcastMessage", {"title": "来自服务器的消息", "content": message,
|
||||
"duration": 5000}, broadcast=True, namespace="/")
|
||||
|
||||
|
||||
def loadBanner(reload=0, bannerfile=app.config["BANNER_FILE"]):
|
||||
regenerateUUID()
|
||||
try:
|
||||
with open(bannerfile, encoding="utf-8") as bannerfile:
|
||||
bannerContent = bannerfile.read()
|
||||
bannerContent = str.replace(
|
||||
bannerContent, r"{{instanceName}}", "InstanceName: "+app.config["INSTANCE_NAME"])
|
||||
bannerContent = str.replace(
|
||||
bannerContent, r"{{instanceUUID}}", "InstanceUUID: "+app.config["INSTANCE_UUID"])
|
||||
with open(app.config["CACHE_PATH"]+"/banner.md", "w", encoding="utf-8") as banner:
|
||||
banner.write(bannerContent)
|
||||
if reload:
|
||||
emit('updateBanner', getBanner(), broadcast=True)
|
||||
return {"status": 200, "msg": _("Successfully reloaded the banner!")}, 200
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return {"status": 500, "msg": _("Failed to reload the banner!")}, 500
|
||||
|
||||
|
||||
@socketio.on("adminUpdateBanner")
|
||||
@app.route("/admin/updateBanner", methods=["POST"])
|
||||
@roles_accepted("admin")
|
||||
def updateBanner():
|
||||
file = request.files.get("file")
|
||||
file.save(app.config["CACHE_PATH"]+"/new_banner.md")
|
||||
return loadBanner(reload=0, bannerfile=app.config["CACHE_PATH"]+"/new_banner.md")
|
||||
|
||||
|
||||
@socketio.on("adminResetBanner")
|
||||
def resetBanner():
|
||||
return loadBanner(reload=0)
|
||||
|
||||
|
||||
@socketio.on("adminDisablePush")
|
||||
def disablePush():
|
||||
if not "admin" in current_user.roles:
|
||||
return {"status": 403, "msg": _("Permission denied!")}, 403
|
||||
if app.config["ENABLE_PUSH"]:
|
||||
app.config["ENABLE_PUSH"] = False
|
||||
emit("updatePushStatus",
|
||||
(app.config["ENABLE_PUSH"], 1), broadcast=True, namespace="/")
|
||||
return {"status": 200, "msg": _("Push disabled.")}
|
||||
else:
|
||||
app.config["ENABLE_PUSH"] = True
|
||||
emit("updatePushStatus",
|
||||
(app.config["ENABLE_PUSH"], 1), broadcast=True, namespace="/")
|
||||
return {"status": 200, "msg": _("Push enabled.")}
|
||||
|
||||
|
||||
@socketio.on("extCredential")
|
||||
def extCredential(providerString: str, content: str, key: str):
|
||||
provider = MusicManager.judgeProvider(providerString, "", providerList)
|
||||
if not provider:
|
||||
return {"status": 400, "msg": _("Invalid provider!")}, 400
|
||||
return provider.loginCredential(content, key)
|
||||
|
||||
|
||||
@app.route("/ext/login", methods=["POST"])
|
||||
@socketio.on("extLogin")
|
||||
def extLogin(providerString: str) -> dict:
|
||||
provider = MusicManager.judgeProvider(providerString, "", providerList)
|
||||
if not provider:
|
||||
return {"status": 400, "msg": _("Invalid provider!")}, 400
|
||||
status = provider.login()
|
||||
updateExtStatus()
|
||||
return status
|
||||
|
||||
|
||||
@socketio.on("extAnonymousLogin")
|
||||
def extAnonymousLogin(providerString: str) -> dict:
|
||||
provider = MusicManager.judgeProvider(providerString, "", providerList)
|
||||
if not provider:
|
||||
return {"status": 400, "msg": _("Invalid provider!")}, 400
|
||||
status = provider.loginAnonymous()
|
||||
updateExtStatus()
|
||||
return status
|
||||
|
||||
|
||||
@app.route("/ext/qrcode", methods=["GET"])
|
||||
@socketio.on("extQRCode")
|
||||
def getExtQRCode(providerString: str) -> dict:
|
||||
provider = MusicManager.judgeProvider(providerString, "", providerList)
|
||||
if not provider:
|
||||
return {"status": 400, "msg": _("Invalid provider!")}, 400
|
||||
qrcode = provider.qrcode()
|
||||
return {"status": 200, "url": qrcode}
|
||||
|
||||
|
||||
def getExtStatus(provider: SessionProvider):
|
||||
if not provider:
|
||||
return {"status": 400, "msg": _("Invalid provider!")}, 400
|
||||
return provider.userStatus()
|
||||
|
||||
|
||||
def updateExtStatus():
|
||||
for i in providerList:
|
||||
print(i.providerName, getExtStatus(i))
|
||||
emit("updateExtStatus", (i.providerName, getExtStatus(i)))
|
||||
|
||||
|
||||
@socketio.on("init")
|
||||
def clientInit():
|
||||
emit("updateBanner", getBanner())
|
||||
emit("updatePushStatus", (app.config["ENABLE_PUSH"], 0))
|
||||
if "admin" in current_user.roles:
|
||||
updateExtStatus()
|
||||
|
||||
|
||||
def regenerateUUID():
|
||||
app.config["INSTANCE_UUID"] = str(uuid.uuid1())
|
||||
|
||||
|
||||
def init():
|
||||
with app.app_context():
|
||||
# # 若不存在数据库就创建
|
||||
print(_("Creating database if not present..."))
|
||||
db.create_all()
|
||||
db.session.commit()
|
||||
regenerateUUID()
|
||||
try:
|
||||
if app.config["PRESERVE_CACHE"]:
|
||||
os.stat(app.config["CACHE_PATH"])
|
||||
else:
|
||||
shutil.rmtree(app.config["CACHE_PATH"])
|
||||
os.mkdir(app.config["CACHE_PATH"])
|
||||
except FileNotFoundError:
|
||||
os.mkdir(app.config["CACHE_PATH"])
|
||||
except PermissionError:
|
||||
raise Exception(_("Permission denied to access cache directory!"))
|
||||
except:
|
||||
raise Exception(_("Failed to rebuild the cache directory!"))
|
||||
os.system("pwd")
|
||||
shutil.copy("./resource/flag.lrc", app.config["CACHE_PATH"])
|
||||
shutil.copy("./resource/flag.wav", app.config["CACHE_PATH"])
|
||||
shutil.copy("./resource/flag.png", app.config["CACHE_PATH"])
|
||||
shutil.copy("./resource/KOBEBRYANT.jpg", app.config["CACHE_PATH"])
|
||||
if loadBanner(reload=0)[1] != 200:
|
||||
raise Exception(_("Failed to reload the banner!"))
|
||||
if app.config["SAVE_SESSION"]:
|
||||
if app.config["PRESERVE_CACHE"]:
|
||||
try:
|
||||
for i in providerList:
|
||||
with open(app.config["CACHE_PATH"]+"/"+i.providerName+".session", "r", encoding="utf-8") as sessionFile:
|
||||
i.restoreSession(sessionFile.read())
|
||||
with open(app.config["CACHE_PATH"]+"/queue.pickle", "rb") as queueFile:
|
||||
queue.load(queueFile.read())
|
||||
with open(app.config["CACHE_PATH"]+"/current.pickle", "rb") as currentFile:
|
||||
current = pickle.loads(currentFile.read())
|
||||
except FileNotFoundError:
|
||||
warnings.warn(_("No session found, skipping..."))
|
||||
except Exception as e:
|
||||
print(e)
|
||||
raise Exception(_("Failed to restore the session!"))
|
||||
else:
|
||||
warnings.warn(
|
||||
_("SAVE_SESSION doesn't work without PRESERVE_CACHE!"))
|
||||
print(_("Successfully initialized the application!"))
|
||||
|
||||
|
||||
def saveSession():
|
||||
if app.config["SAVE_SESSION"]:
|
||||
if not app.config["PRESERVE_CACHE"]:
|
||||
warnings.warn(
|
||||
_("SAVE_SESSION doesn't work without PRESERVE_CACHE!"))
|
||||
try:
|
||||
# 保存音乐提供者session
|
||||
for i in providerList:
|
||||
sessionText = i.dumpSession()
|
||||
with open(app.config["CACHE_PATH"]+"/"+i.providerName+".session", "w", encoding="utf-8") as sessionFile:
|
||||
sessionFile.write(sessionText)
|
||||
# 保存歌曲队列
|
||||
with open(app.config["CACHE_PATH"]+"/queue.pickle", "wb") as queueFile:
|
||||
queueFile.write(queue.dump())
|
||||
# 保存当前播放
|
||||
with open(app.config["CACHE_PATH"]+"/current.pickle", "wb") as currentFile:
|
||||
currentFile.write(pickle.dumps(current))
|
||||
except Exception as e:
|
||||
print(e)
|
||||
warnings.warn(_("Failed to save the session, operation aborted."))
|
||||
|
||||
|
||||
init()
|
||||
if __name__ == "__main__":
|
||||
socketio.run(app, debug=app.config["DEBUG"],
|
||||
host=app.config["HOST"], port=app.config["PORT"])
|
||||
@@ -0,0 +1,16 @@
|
||||
dynaconf==3.2.5
|
||||
Flask==3.0.2
|
||||
Flask_Security_Too==5.4.3
|
||||
Flask_SocketIO==5.3.6
|
||||
flask_sqlalchemy==3.1.1
|
||||
flask_wtf==1.2.1
|
||||
httpx==0.27.0
|
||||
pyncm==1.6.15
|
||||
qrcode==7.4.2
|
||||
argon2-cffi==23.1.0
|
||||
argon2-cffi-bindings==21.2.0
|
||||
mutagen==1.47.0
|
||||
gunicorn==22.0
|
||||
gevent==24.2.1
|
||||
gevent-websocket==0.10.1
|
||||
#bilibili-api-python==16.2.0
|
||||
Reference in New Issue
Block a user