Compare commits

..

No commits in common. '34dbeb27bc830ceaa217825c1b8ffb45d295b098' and '9614ff15f09c1e252d006b3c49aa5d25b0fc575c' have entirely different histories.

  1. 24
      src/api.py
  2. 21
      src/decrypt.py
  3. 4
      src/exceptions.py

@ -6,7 +6,7 @@ import httpx
import regex import regex
from async_lru import alru_cache from async_lru import alru_cache
from loguru import logger from loguru import logger
from tenacity import retry, retry_if_exception_type, stop_after_attempt, before_sleep_log, wait_random_exponential from tenacity import retry, retry_if_exception_type, stop_after_attempt, before_sleep_log
from src.models import * from src.models import *
from src.models.song_data import Datum from src.models.song_data import Datum
@ -14,7 +14,7 @@ from src.models.song_data import Datum
client: httpx.AsyncClient client: httpx.AsyncClient
download_lock: asyncio.Semaphore download_lock: asyncio.Semaphore
request_lock: asyncio.Semaphore request_lock: asyncio.Semaphore
retry_times = 32 retry_times = 10
user_agent_browser = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" user_agent_browser = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
user_agent_itunes = "iTunes/12.11.3 (Windows; Microsoft Windows 10 x64 Professional Edition (Build 19041); x64) AppleWebKit/7611.1022.4001.1 (dt:2)" user_agent_itunes = "iTunes/12.11.3 (Windows; Microsoft Windows 10 x64 Professional Edition (Build 19041); x64) AppleWebKit/7611.1022.4001.1 (dt:2)"
user_agent_app = "Music/5.7 Android/10 model/Pixel6GR1YH build/1234 (dt:66)" user_agent_app = "Music/5.7 Android/10 model/Pixel6GR1YH build/1234 (dt:66)"
@ -27,11 +27,10 @@ def init_client_and_lock(proxy: str, parallel_num: int):
else: else:
client = httpx.AsyncClient() client = httpx.AsyncClient()
download_lock = asyncio.Semaphore(parallel_num) download_lock = asyncio.Semaphore(parallel_num)
request_lock = asyncio.Semaphore(256) request_lock = asyncio.Semaphore(64)
@retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)), @retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)),
wait=wait_random_exponential(multiplier=1, max=60),
stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN)) stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN))
async def get_m3u8_from_api(endpoint: str, song_id: str, wait_and_retry: bool = False, recursion_times: int = 0) -> str: async def get_m3u8_from_api(endpoint: str, song_id: str, wait_and_retry: bool = False, recursion_times: int = 0) -> str:
async with request_lock: async with request_lock:
@ -45,7 +44,6 @@ async def get_m3u8_from_api(endpoint: str, song_id: str, wait_and_retry: bool =
@retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)), @retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)),
wait=wait_random_exponential(multiplier=1, max=60),
stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN)) stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN))
async def upload_m3u8_to_api(endpoint: str, m3u8_url: str, song_info: Datum): async def upload_m3u8_to_api(endpoint: str, m3u8_url: str, song_info: Datum):
async with request_lock: async with request_lock:
@ -62,7 +60,6 @@ async def upload_m3u8_to_api(endpoint: str, m3u8_url: str, song_info: Datum):
@retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)), @retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)),
wait=wait_random_exponential(multiplier=1, max=60),
stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN)) stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN))
async def get_token(): async def get_token():
async with request_lock: async with request_lock:
@ -75,7 +72,6 @@ async def get_token():
@alru_cache @alru_cache
@retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)), @retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)),
wait=wait_random_exponential(multiplier=1, max=60),
stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN)) stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN))
async def download_song(url: str) -> bytes: async def download_song(url: str) -> bytes:
async with download_lock: async with download_lock:
@ -84,7 +80,6 @@ async def download_song(url: str) -> bytes:
@alru_cache @alru_cache
@retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)), @retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)),
wait=wait_random_exponential(multiplier=1, max=60),
stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN)) stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN))
async def get_album_info(album_id: str, token: str, storefront: str, lang: str): async def get_album_info(album_id: str, token: str, storefront: str, lang: str):
async with request_lock: async with request_lock:
@ -100,7 +95,6 @@ async def get_album_info(album_id: str, token: str, storefront: str, lang: str):
@alru_cache @alru_cache
@retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)), @retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)),
wait=wait_random_exponential(multiplier=1, max=60),
stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN)) stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN))
async def get_playlist_info_and_tracks(playlist_id: str, token: str, storefront: str, lang: str): async def get_playlist_info_and_tracks(playlist_id: str, token: str, storefront: str, lang: str):
async with request_lock: async with request_lock:
@ -117,7 +111,6 @@ async def get_playlist_info_and_tracks(playlist_id: str, token: str, storefront:
@alru_cache @alru_cache
@retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)), @retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)),
wait=wait_random_exponential(multiplier=1, max=60),
stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN)) stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN))
async def get_playlist_tracks(playlist_id: str, token: str, storefront: str, lang: str, offset: int = 0): async def get_playlist_tracks(playlist_id: str, token: str, storefront: str, lang: str, offset: int = 0):
async with request_lock: async with request_lock:
@ -136,7 +129,6 @@ async def get_playlist_tracks(playlist_id: str, token: str, storefront: str, lan
@alru_cache @alru_cache
@retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)), @retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)),
wait=wait_random_exponential(multiplier=1, max=60),
stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN)) stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN))
async def get_cover(url: str, cover_format: str, cover_size: str): async def get_cover(url: str, cover_format: str, cover_size: str):
async with request_lock: async with request_lock:
@ -148,7 +140,6 @@ async def get_cover(url: str, cover_format: str, cover_size: str):
@alru_cache @alru_cache
@retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)), @retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)),
wait=wait_random_exponential(multiplier=1, max=60),
stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN)) stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN))
async def get_song_info(song_id: str, token: str, storefront: str, lang: str): async def get_song_info(song_id: str, token: str, storefront: str, lang: str):
async with request_lock: async with request_lock:
@ -165,7 +156,6 @@ async def get_song_info(song_id: str, token: str, storefront: str, lang: str):
@alru_cache @alru_cache
@retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)), @retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)),
wait=wait_random_exponential(multiplier=1, max=60),
stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN)) stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN))
async def get_song_lyrics(song_id: str, storefront: str, token: str, dsid: str, account_token: str, lang: str) -> str: async def get_song_lyrics(song_id: str, storefront: str, token: str, dsid: str, account_token: str, lang: str) -> str:
async with request_lock: async with request_lock:
@ -180,7 +170,6 @@ async def get_song_lyrics(song_id: str, storefront: str, token: str, dsid: str,
@alru_cache @alru_cache
@retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)), @retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)),
wait=wait_random_exponential(multiplier=1, max=60),
stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN)) stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN))
async def get_albums_from_artist(artist_id: str, storefront: str, token: str, lang: str, offset: int = 0): async def get_albums_from_artist(artist_id: str, storefront: str, token: str, lang: str, offset: int = 0):
async with request_lock: async with request_lock:
@ -198,7 +187,6 @@ async def get_albums_from_artist(artist_id: str, storefront: str, token: str, la
@alru_cache @alru_cache
@retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)), @retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)),
wait=wait_random_exponential(multiplier=1, max=60),
stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN)) stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN))
async def get_songs_from_artist(artist_id: str, storefront: str, token: str, lang: str, offset: int = 0): async def get_songs_from_artist(artist_id: str, storefront: str, token: str, lang: str, offset: int = 0):
async with request_lock: async with request_lock:
@ -216,7 +204,6 @@ async def get_songs_from_artist(artist_id: str, storefront: str, token: str, lan
@alru_cache @alru_cache
@retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)), @retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)),
wait=wait_random_exponential(multiplier=1, max=60),
stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN)) stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN))
async def get_artist_info(artist_id: str, storefront: str, token: str, lang: str): async def get_artist_info(artist_id: str, storefront: str, token: str, lang: str):
async with request_lock: async with request_lock:
@ -229,7 +216,6 @@ async def get_artist_info(artist_id: str, storefront: str, token: str, lang: str
@alru_cache @alru_cache
@retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)), @retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)),
wait=wait_random_exponential(multiplier=1, max=60),
stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN)) stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN))
async def download_m3u8(m3u8_url: str) -> str: async def download_m3u8(m3u8_url: str) -> str:
async with request_lock: async with request_lock:
@ -239,7 +225,6 @@ async def download_m3u8(m3u8_url: str) -> str:
@alru_cache @alru_cache
@retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)), @retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)),
wait=wait_random_exponential(multiplier=1, max=60),
stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN)) stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN))
async def get_real_url(url: str): async def get_real_url(url: str):
req = await client.get(url, follow_redirects=True, headers={"User-Agent": user_agent_browser}) req = await client.get(url, follow_redirects=True, headers={"User-Agent": user_agent_browser})
@ -248,7 +233,6 @@ async def get_real_url(url: str):
@alru_cache @alru_cache
@retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)), @retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)),
wait=wait_random_exponential(multiplier=1, max=60),
stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN)) stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN))
async def get_album_by_upc(upc: str, storefront: str, token: str): async def get_album_by_upc(upc: str, storefront: str, token: str):
req = await client.get(f"https://amp-api.music.apple.com/v1/catalog/{storefront}/albums", req = await client.get(f"https://amp-api.music.apple.com/v1/catalog/{storefront}/albums",
@ -267,7 +251,6 @@ async def get_album_by_upc(upc: str, storefront: str, token: str):
@alru_cache @alru_cache
@retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)), @retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)),
wait=wait_random_exponential(multiplier=1, max=60),
stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN)) stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN))
async def exist_on_storefront_by_song_id(song_id: str, storefront: str, check_storefront: str, token: str, lang: str): async def exist_on_storefront_by_song_id(song_id: str, storefront: str, check_storefront: str, token: str, lang: str):
if storefront.upper() == check_storefront.upper(): if storefront.upper() == check_storefront.upper():
@ -282,7 +265,6 @@ async def exist_on_storefront_by_song_id(song_id: str, storefront: str, check_st
@alru_cache @alru_cache
@retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)), @retry(retry=retry_if_exception_type((httpx.HTTPError, SSLError, FileNotFoundError)),
wait=wait_random_exponential(multiplier=1, max=60),
stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN)) stop=stop_after_attempt(retry_times), before_sleep=before_sleep_log(logger, logging.WARN))
async def exist_on_storefront_by_album_id(album_id: str, storefront: str, check_storefront: str, token: str, lang: str): async def exist_on_storefront_by_album_id(album_id: str, storefront: str, check_storefront: str, token: str, lang: str):
if storefront.upper() == check_storefront.upper(): if storefront.upper() == check_storefront.upper():

@ -2,18 +2,16 @@ import asyncio
import logging import logging
from loguru import logger from loguru import logger
from tenacity import retry, retry_if_exception_type, stop_after_attempt, before_sleep_log, RetryCallState from tenacity import retry, retry_if_exception_type, stop_after_attempt, before_sleep_log
from src.adb import Device from src.adb import Device
from src.exceptions import DecryptException, RetryableDecryptException from src.exceptions import DecryptException
from src.models.song_data import Datum from src.models.song_data import Datum
from src.mp4 import SongInfo, SampleInfo from src.mp4 import SongInfo, SampleInfo
from src.types import defaultId, prefetchKey from src.types import defaultId, prefetchKey
retry_count = {}
@retry(retry=retry_if_exception_type(DecryptException), stop=stop_after_attempt(3),
@retry(retry=retry_if_exception_type(RetryableDecryptException), stop=stop_after_attempt(3),
before_sleep=before_sleep_log(logger, logging.WARN)) before_sleep=before_sleep_log(logger, logging.WARN))
async def decrypt(info: SongInfo, keys: list[str], manifest: Datum, device: Device) -> bytes: async def decrypt(info: SongInfo, keys: list[str], manifest: Datum, device: Device) -> bytes:
async with device.decryptLock: async with device.decryptLock:
@ -36,19 +34,10 @@ async def decrypt(info: SongInfo, keys: list[str], manifest: Datum, device: Devi
last_index = sample.descIndex last_index = sample.descIndex
try: try:
result = await decrypt_sample(writer, reader, sample) result = await decrypt_sample(writer, reader, sample)
except RetryableDecryptException as e: except DecryptException as e:
if 0 <= retry_count.get(device.device.serial, 0) < 3 or 4 <= retry_count.get(device.device.serial, 0) < 6:
logger.warning(f"Failed to decrypt song: {manifest.attributes.artistName} - {manifest.attributes.name}, retrying")
writer.write(bytes([0, 0, 0, 0])) writer.write(bytes([0, 0, 0, 0]))
writer.close() writer.close()
raise e raise e
elif retry_count == 3:
logger.warning(f"Failed to decrypt song: {manifest.attributes.artistName} - {manifest.attributes.name}, re-injecting")
device.restart_inject_frida()
raise e
else:
logger.error(f"Failed to decrypt song: {manifest.attributes.artistName} - {manifest.attributes.name}")
raise DecryptException
decrypted += result decrypted += result
writer.write(bytes([0, 0, 0, 0])) writer.write(bytes([0, 0, 0, 0]))
writer.close() writer.close()
@ -60,5 +49,5 @@ async def decrypt_sample(writer: asyncio.StreamWriter, reader: asyncio.StreamRea
writer.write(sample.data) writer.write(sample.data)
result = await reader.read(len(sample.data)) result = await reader.read(len(sample.data))
if not result: if not result:
raise RetryableDecryptException raise DecryptException
return result return result

@ -24,7 +24,3 @@ class NotTimeSyncedLyricsException(Exception):
class CodecNotFoundException(Exception): class CodecNotFoundException(Exception):
... ...
class RetryableDecryptException(Exception):
...

Loading…
Cancel
Save