From 5885262fe85a9fe80cdf2154042c5ec1238c6a64 Mon Sep 17 00:00:00 2001 From: WorldObservationLog Date: Sun, 12 May 2024 13:10:17 +0800 Subject: [PATCH] feat: more exactly url match --- src/api.py | 12 +++++++++++- src/cmd.py | 8 +++++++- src/url.py | 13 ++++++++----- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/api.py b/src/api.py index 8bfc14b..aecb2e1 100644 --- a/src/api.py +++ b/src/api.py @@ -39,7 +39,7 @@ async def get_m3u8_from_api(endpoint: str, song_id: str, wait_and_retry: bool = if resp == "no_found": if wait_and_retry and recursion_times <= 5: await asyncio.sleep(5) - return await get_m3u8_from_api(endpoint, song_id, recursion_times=recursion_times+1) + return await get_m3u8_from_api(endpoint, song_id, recursion_times=recursion_times + 1) return "" return resp @@ -246,3 +246,13 @@ async def download_m3u8(m3u8_url: str) -> str: async with request_lock: resp = await client.get(m3u8_url) return resp.text + + +@alru_cache +@retry(retry=retry_if_exception_type( + (httpx.TimeoutException, httpcore.ConnectError, SSLError, FileNotFoundError, httpcore.RemoteProtocolError)), + stop=stop_after_attempt(5), + before_sleep=before_sleep_log(logger, logging.WARN)) +async def get_real_url(url: str): + req = await client.get(url, follow_redirects=True, headers={"User-Agent": user_agent_browser}) + return str(req.url) diff --git a/src/cmd.py b/src/cmd.py index 641fe6c..4778794 100644 --- a/src/cmd.py +++ b/src/cmd.py @@ -9,7 +9,7 @@ from prompt_toolkit import PromptSession, print_formatted_text, ANSI from prompt_toolkit.patch_stdout import patch_stdout from src.adb import Device -from src.api import get_token, init_client_and_lock, upload_m3u8_to_api, get_song_info +from src.api import get_token, init_client_and_lock, upload_m3u8_to_api, get_song_info, get_real_url from src.config import Config from src.rip import rip_song, rip_album, rip_artist, rip_playlist from src.types import GlobalAuthParams @@ -91,6 +91,12 @@ class NewInteractiveShell: async def do_download(self, raw_url: str, codec: str, force_download: bool, include: bool = False): url = AppleMusicURL.parse_url(raw_url) + if not url: + real_url = await get_real_url(raw_url) + url = AppleMusicURL.parse_url(real_url) + if not url: + logger.error("Illegal URL!") + return available_device = await self._get_available_device(url.storefront) global_auth_param = GlobalAuthParams.from_auth_params_and_token(available_device.get_auth_params(), self.anonymous_access_token) diff --git a/src/url.py b/src/url.py index 1dbc6d4..7caaec8 100644 --- a/src/url.py +++ b/src/url.py @@ -1,5 +1,6 @@ from urllib.parse import urlparse, parse_qs +import regex from pydantic import BaseModel @@ -18,17 +19,19 @@ class AppleMusicURL(BaseModel): @classmethod def parse_url(cls, url: str): + if not regex.match(r"https://music.apple.com/(.{2})/(song|album|playlist|artist).*/(pl.*|\d*)", url): + return None parsed_url = urlparse(url) paths = parsed_url.path.split("/") storefront = paths[1] url_type = paths[2] match url_type: case URLType.Song: - url_id = paths[4] + url_id = paths[-1] return Song(url=url, storefront=storefront, id=url_id, type=URLType.Song) case URLType.Album: if not parsed_url.query: - url_id = paths[4] + url_id = paths[-1] return Album(url=url, storefront=storefront, id=url_id, type=URLType.Album) else: url_query = parse_qs(parsed_url.query) @@ -36,13 +39,13 @@ class AppleMusicURL(BaseModel): url_id = url_query.get("i")[0] return Song(url=url, storefront=storefront, id=url_id, type=URLType.Song) else: - url_id = paths[4] + url_id = paths[-1] return Album(url=url, storefront=storefront, id=url_id, type=URLType.Album) case URLType.Artist: - url_id = paths[4] + url_id = paths[-1] return Artist(url=url, storefront=storefront, id=url_id, type=URLType.Artist) case URLType.Playlist: - url_id = paths[4] + url_id = paths[-1] return Playlist(url=url, storefront=storefront, id=url_id, type=URLType.Playlist)