feature: add private url scheme

* Also reformat code
* Rename symbol LinkType.Song -> LinkType.Track
This commit is contained in:
Puqns67 2024-10-17 22:28:30 +08:00
parent c00e881577
commit 3cb6653449
Signed by: Puqns67
GPG Key ID: 9669DF042554F536
5 changed files with 126 additions and 43 deletions

View File

@ -1,4 +1,4 @@
from .__version__ import __title__, __description__, __version__
from .__main__ import main from .__main__ import main
from .__version__ import __description__, __title__, __version__
__all__ = ["__title__", "__description__", "__version__", "main"] __all__ = ["__title__", "__description__", "__version__", "main"]

View File

@ -93,7 +93,7 @@ def main(outputs: list[Path], exist: bool, overwrite: bool, quiet: bool, links:
continue continue
match parsed.type: match parsed.type:
case LinkType.Song: case LinkType.Track:
newTrack = api.getDetailsForTrack(parsed.id) newTrack = api.getDetailsForTrack(parsed.id)
savePath = pickOutput(newTrack, outputs, exist) savePath = pickOutput(newTrack, outputs, exist)

View File

@ -29,6 +29,6 @@ class LrcMetaType(Enum):
class LinkType(Enum): class LinkType(Enum):
Song = auto() Track = "track"
Album = auto() Album = "album"
Playlist = auto() Playlist = "playlist"

View File

@ -12,7 +12,8 @@ from .object import NCMTrack
__all__ = ["Link", "parseLink", "testExistTrackSource", "pickOutput"] __all__ = ["Link", "parseLink", "testExistTrackSource", "pickOutput"]
RE_ANDROID_ALBUM_SHARE_LINK_PATH = reCompile(r"^/album/(?P<id>\d*)/?$") RE_SHARE_LINK_ID_BY_PATH = reCompile(r"^/?(?P<id>\d+)$")
RE_SHARE_LINK_ANDROID_ALBUM_PATH = reCompile(r"^/album/(?P<id>\d+)/?$")
RE_SAFE_FILENAME = reCompile(r"\*{2,}") RE_SAFE_FILENAME = reCompile(r"\*{2,}")
TRANSLATER_SAFE_FILENAME = str.maketrans({i: 0x2A for i in ("<", ">", ":", '"', "/", "\\", "|", "?")}) TRANSLATER_SAFE_FILENAME = str.maketrans({i: 0x2A for i in ("<", ">", ":", '"', "/", "\\", "|", "?")})
@ -28,39 +29,72 @@ def parseLink(url: str) -> Link:
contentType: LinkType | None = None contentType: LinkType | None = None
contentId: int | None = None contentId: int | None = None
match parsedUrl.netloc: match parsedUrl.scheme:
case "music.163.com": case "http" | "https":
match parsedUrl.path: match parsedUrl.netloc:
case "/playlist" | "/#/playlist": case "music.163.com":
contentType = LinkType.Playlist match parsedUrl.path:
case "/album" | "/#/album": case "/playlist" | "/#/playlist":
contentType = LinkType.Album contentType = LinkType.Playlist
case "/song" | "/#/song": case "/album" | "/#/album":
contentType = LinkType.Song contentType = LinkType.Album
case _: case "/song" | "/#/song":
# Hack for android client shared album link contentType = LinkType.Track
matchedPath = RE_ANDROID_ALBUM_SHARE_LINK_PATH.match(parsedUrl.path) case _:
if matchedPath is not None: # Hack for android client shared album link
contentType = LinkType.Album matchedPath = RE_SHARE_LINK_ANDROID_ALBUM_PATH.match(parsedUrl.path)
contentId = int(matchedPath["id"]) if matchedPath is not None:
else: contentType = LinkType.Album
raise UnsupportedLinkError(parsedUrl) contentId = int(matchedPath["id"])
case "y.music.163.com": else:
match parsedUrl.path: raise UnsupportedLinkError(parsedUrl)
case "/m/playlist": case "y.music.163.com":
contentType = LinkType.Playlist match parsedUrl.path:
case "/m/song": case "/m/playlist":
contentType = LinkType.Song contentType = LinkType.Playlist
case "/m/song":
contentType = LinkType.Track
case _:
raise UnsupportedLinkError(parsedUrl)
case "163cn.tv":
response = httpGet(url)
if response.status_code != 302:
raise ParseLinkError(f"未知的 Api 响应: {response.status_code}")
newUrl = response.headers.get("Location")
if newUrl is None:
raise ParseLinkError("Api 未返回重定向结果")
return parseLink(newUrl)
case _: case _:
raise UnsupportedLinkError(parsedUrl) raise UnsupportedLinkError(parsedUrl)
case "163cn.tv": case "ncmlyrics": # eg: ncmlyrics://playlist/123456, ncmlyrics://album/12456, ncmlyrics://track/123456
response = httpGet(url) try:
if response.status_code != 302: contentType = LinkType(parsedUrl.netloc)
raise ParseLinkError(f"未知的 Api 响应: {response.status_code}") except ValueError:
newUrl = response.headers.get("Location") raise UnsupportedLinkError(parsedUrl)
if newUrl is None:
raise ParseLinkError("Api 未返回重定向结果") if parsedUrl.path:
return parseLink(newUrl) matched = RE_SHARE_LINK_ID_BY_PATH.match(parsedUrl.path)
if matched is not None:
contentId = int(matched.group("id"))
else:
raise ParseLinkError
case "playlist" | "album" | "track": # eg: playlist:123456, album:/12456, track://123456
try:
contentType = LinkType(parsedUrl.scheme)
except ValueError:
raise UnsupportedLinkError(parsedUrl)
try:
if parsedUrl.netloc:
contentId = int(parsedUrl.netloc)
elif parsedUrl.path:
matched = RE_SHARE_LINK_ID_BY_PATH.match(parsedUrl.path)
if matched is not None:
contentId = int(matched.group("id"))
else:
raise ParseLinkError
except ValueError:
raise ParseLinkError
case _: case _:
raise UnsupportedLinkError(parsedUrl) raise UnsupportedLinkError(parsedUrl)

View File

@ -23,7 +23,7 @@ class TestUtils(TestCase):
self.assertEqual( self.assertEqual(
parseLink("https://music.163.com/song?id=2621105420"), parseLink("https://music.163.com/song?id=2621105420"),
Link(LinkType.Song, 2621105420), Link(LinkType.Track, 2621105420),
msg="Shared song from NCM Windows Client", msg="Shared song from NCM Windows Client",
) )
@ -42,7 +42,7 @@ class TestUtils(TestCase):
self.assertEqual( self.assertEqual(
parseLink("https://music.163.com/#/song?id=2621105420"), parseLink("https://music.163.com/#/song?id=2621105420"),
Link(LinkType.Song, 2621105420), Link(LinkType.Track, 2621105420),
msg="Song from NCM Website", msg="Song from NCM Website",
) )
@ -61,18 +61,55 @@ class TestUtils(TestCase):
self.assertEqual( self.assertEqual(
parseLink("https://y.music.163.com/m/song?id=2604307454"), parseLink("https://y.music.163.com/m/song?id=2604307454"),
Link(LinkType.Song, 2604307454), Link(LinkType.Track, 2604307454),
msg="Shared song from NCM Android Client", msg="Shared song from NCM Android Client",
) )
def test_parseLink_302(self): def test_parseLink_302(self):
self.assertEqual( self.assertEqual(
parseLink("http://163cn.tv/xpaQwii"), parseLink("http://163cn.tv/xpaQwii"),
Link(LinkType.Song, 413077069), Link(LinkType.Track, 413077069),
msg="Shared song from NCM Android Client player", msg="Shared song from NCM Android Client player",
) )
def test_parseLink_UnsupportShareLinkError(self): def test_parseLink_special(self):
self.assertEqual(
parseLink("ncmlyrics://playlist/123456"),
Link(LinkType.Playlist, 123456),
)
self.assertEqual(
parseLink("ncmlyrics://album/123456"),
Link(LinkType.Album, 123456),
)
self.assertEqual(
parseLink("ncmlyrics://track/123456"),
Link(LinkType.Track, 123456),
)
self.assertEqual(
parseLink("playlist:123456"),
Link(LinkType.Playlist, 123456),
)
self.assertEqual(
parseLink("album:/123456"),
Link(LinkType.Album, 123456),
)
self.assertEqual(
parseLink("track://123456"),
Link(LinkType.Track, 123456),
)
def test_parseLink_UnsupportedLinkError(self):
self.assertRaises(
UnsupportedLinkError,
parseLink,
"ftp://ftpserver.com/",
)
self.assertRaises( self.assertRaises(
UnsupportedLinkError, UnsupportedLinkError,
parseLink, parseLink,
@ -91,13 +128,25 @@ class TestUtils(TestCase):
"https://music.163.com/album/123a", "https://music.163.com/album/123a",
) )
def test_parseLink_ParseShareLinkError(self): self.assertRaises(
UnsupportedLinkError,
parseLink,
"ncmlyrics://unsupport/123456",
)
def test_parseLink_ParseLinkError(self):
self.assertRaises( self.assertRaises(
ParseLinkError, ParseLinkError,
parseLink, parseLink,
"https://music.163.com/playlist?id=123a", "https://music.163.com/playlist?id=123a",
) )
self.assertRaises(
ParseLinkError,
parseLink,
"playlist://123456a",
)
def test_testExistTrackSource(self): def test_testExistTrackSource(self):
resources = Path("tests/resource/util/testExistTrackSource") resources = Path("tests/resource/util/testExistTrackSource")