aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/yt_dlp_plugins/extractor/radiko.py
diff options
context:
space:
mode:
Diffstat (limited to 'yt_dlp_plugins/extractor/radiko.py')
-rw-r--r--yt_dlp_plugins/extractor/radiko.py142
1 files changed, 52 insertions, 90 deletions
diff --git a/yt_dlp_plugins/extractor/radiko.py b/yt_dlp_plugins/extractor/radiko.py
index 3fd19d9..8f56732 100644
--- a/yt_dlp_plugins/extractor/radiko.py
+++ b/yt_dlp_plugins/extractor/radiko.py
@@ -2,6 +2,7 @@ import base64
import datetime
import random
import urllib.parse
+import json
import pkgutil
@@ -26,7 +27,7 @@ import yt_dlp_plugins.extractor.radiko_hacks as hacks
class _RadikoBaseIE(InfoExtractor):
- _FULL_KEY = pkgutil.get_data(__name__, "radiko_aSmartPhone7a.bin")
+ _FULL_KEY = pkgutil.get_data(__name__, "radiko_aSmartPhone8.jpg")
# https://stackoverflow.com/a/58941536
_COORDINATES = {
@@ -50,41 +51,9 @@ class _RadikoBaseIE(InfoExtractor):
"JP43": [32.789827, 130.741667], "JP44": [33.238172, 131.612619], "JP45": [31.911096, 131.423893],
"JP46": [31.560146, 130.557978], "JP47": [26.2124, 127.680932],
}
-
- _MODELS = [
- # Samsung galaxy s7+
- "SC-02H", "SCV33", "SM-G935F", "SM-G935X", "SM-G935W8", "SM-G935K", "SM-G935L", "SM-G935S", "SAMSUNG-SM-G935A", "SM-G935VC", "SM-G9350", "SM-G935P", "SM-G935T", "SM-G935U", "SM-G935R4", "SM-G935V", "SC-02J", "SCV36", "SM-G950F", "SM-G950N", "SM-G950W", "SM-G9500", "SM-G9508", "SM-G950U", "SM-G950U1", "SM-G892A", "SM-G892U", "SC-03J", "SCV35", "SM-G955F", "SM-G955N", "SM-G955W", "SM-G9550", "SM-G955U", "SM-G955U1", "SM-G960F", "SM-G960N", "SM-G9600", "SM-G9608", "SM-G960W", "SM-G960U", "SM-G960U1", "SM-G965F", "SM-G965N", "SM-G9650", "SM-G965W", "SM-G965U", "SM-G965U1",
- # samsung galaxy note
- "SC-01J", "SCV34", "SM-N930F", "SM-N930X", "SM-N930K", "SM-N930L", "SM-N930S", "SM-N930R7", "SAMSUNG-SM-N930A", "SM-N930W8", "SM-N9300", "SGH-N037", "SM-N930R6", "SM-N930P", "SM-N930VL", "SM-N930T", "SM-N930U", "SM-N930R4", "SM-N930V", "SC-01K", "SCV37", "SM-N950F", "SM-N950N", "SM-N950XN", "SM-N950U", "SM-N9500", "SM-N9508", "SM-N950W", "SM-N950U1",
- # KYOCERA
- "WX06K", "404KC", "503KC", "602KC", "KYV32", "E6782", "KYL22", "WX04K", "KYV36", "KYL21", "302KC", "KYV36", "KYV42", "KYV37", "C5155", "SKT01", "KYY24", "KYV35", "KYV41", "E6715", "KYY21", "KYY22", "KYY23", "KYV31", "KYV34", "KYV38", "WX10K", "KYL23", "KYV39", "KYV40",
- # sony xperia z series
- "C6902", "C6903", "C6906", "C6916", "C6943", "L39h", "L39t", "L39u", "SO-01F", "SOL23", "D5503", "M51w", "SO-02F", "D6502", "D6503", "D6543", "SO-03F", "SGP511", "SGP512", "SGP521", "SGP551", "SGP561", "SO-05F", "SOT21", "D6563", "401SO", "D6603", "D6616", "D6643", "D6646", "D6653", "SO-01G", "SOL26", "D6603", "D5803", "D5833", "SO-02G", "D5803", "D6633", "D6683", "SGP611", "SGP612", "SGP621", "SGP641", "E6553", "E6533", "D6708", "402SO", "SO-03G", "SOV31", "SGP712", "SGP771", "SO-05G", "SOT31", "E6508", "501SO", "E6603", "E6653", "SO-01H", "SOV32", "E5803", "E5823", "SO-02H", "E6853", "E6883", "SO-03H", "E6833", "E6633", "E6683", "C6502", "C6503", "C6506", "L35h", "SOL25", "C5306", "C5502", "C5503", "601SO", "F8331", "F8332", "SO-01J", "SOV34", "G8141", "G8142", "G8188", "SO-04J", "701SO", "G8341", "G8342", "G8343", "SO-01K", "SOV36", "G8441", "SO-02K", "602SO", "G8231", "G8232", "SO-03J", "SOV35",
- # sharp
- "605SH", "SH-03J", "SHV39", "701SH", "SH-M06",
- # fujitsu arrows
- "101F", "201F", "202F", "301F", "IS12F", "F-03D", "F-03E", "M01", "M305", "M357", "M555", "M555", "F-11D", "F-06E", "EM01F", "F-05E", "FJT21", "F-01D", "FAR70B", "FAR7", "F-04E", "F-02E", "F-10D", "F-05D", "FJL22", "ISW11F", "ISW13F", "FJL21", "F-074", "F-07D",
-]
-
# range detail :http://www.gsi.go.jp/KOKUJYOHO/CENTER/zenken.htm
- # build number :https://www.androidpolice.com/android-build-number-date-calculator/
- # https://source.android.com/setup/build-numbers
- _ANDROID_VERSIONS = [
- # https://radiko.jp/#!/info/2558 - minimum after 2022-08-24 is android 7
- {"version": "7.0.0", "sdk": "24", "builds": ["NBD92Q", "NBD92N", "NBD92G", "NBD92F", "NBD92E", "NBD92D", "NBD91Z", "NBD91Y", "NBD91X", "NBD91U", "N5D91L", "NBD91P", "NRD91K", "NRD91N", "NBD90Z", "NBD90X", "NBD90W", "NRD91D", "NRD90U", "NRD90T", "NRD90S", "NRD90R", "NRD90M"]},
- {"version": "7.1.0", "sdk": "25", "builds": ["NDE63X", "NDE63V", "NDE63U", "NDE63P", "NDE63L", "NDE63H"]},
- {"version": "7.1.1", "sdk": "25", "builds": ["N9F27M", "NGI77B", "N6F27M", "N4F27P", "N9F27L", "NGI55D", "N4F27O", "N8I11B", "N9F27H", "N6F27I", "N4F27K", "N9F27F", "N6F27H", "N4F27I", "N9F27C", "N6F27E", "N4F27E", "N6F27C", "N4F27B", "N6F26Y", "NOF27D", "N4F26X", "N4F26U", "N6F26U", "NUF26N", "NOF27C", "NOF27B", "N4F26T", "NMF27D", "NMF26X", "NOF26W", "NOF26V", "N6F26R", "NUF26K", "N4F26Q", "N4F26O", "N6F26Q", "N4F26M", "N4F26J", "N4F26I", "NMF26V", "NMF26U", "NMF26R", "NMF26Q", "NMF26O", "NMF26J", "NMF26H", "NMF26F"]},
- {"version": "7.1.2", "sdk": "25", "builds": ["N2G48H", "NZH54D", "NKG47S", "NHG47Q", "NJH47F", "N2G48C", "NZH54B", "NKG47M", "NJH47D", "NHG47O", "N2G48B", "N2G47Z", "NJH47B", "NJH34C", "NKG47L", "NHG47N", "N2G47X", "N2G47W", "NHG47L", "N2G47T", "N2G47R", "N2G47O", "NHG47K", "N2G47J", "N2G47H", "N2G47F", "N2G47E", "N2G47D"]},
- {"version": "8.0.0", "sdk": "26", "builds": ["5650811", "5796467", "5948681", "6107732", "6127070"]},
- {"version": "8.1.0", "sdk": "27", "builds": ["5794017", "6107733", "6037697"]},
- {"version": "9.0.0", "sdk": "28", "builds": ["5948683", "5794013", "6127072"]},
- {"version": "10.0.0", "sdk": "29", "builds": ["5933585", "6969601", "7023426", "7070703"]},
- {"version": "11.0.0", "sdk": "30", "builds": ["RP1A.201005.006", "RQ1A.201205.011", "RQ1A.210105.002"]},
- {"version": "12.0.0", "sdk": "31", "builds": ["SD1A.210817.015.A4", "SD1A.210817.019.B1", "SD1A.210817.037", "SQ1D.220105.007"]},
- ]
-
- _APP_VERSIONS = ["7.5.0", "7.4.17", "7.4.16", "7.4.15", "7.4.14", "7.4.13", "7.4.12", "7.4.11", "7.4.10", "7.4.9", "7.4.8", "7.4.7", "7.4.6", "7.4.5", "7.4.4", "7.4.3", "7.4.2", "7.4.1", "7.4.0", "7.3.8", "7.3.7", "7.3.6", "7.3.1", "7.3.0", "7.2.11", "7.2.10"]
+ _APP_VERSIONS = ["8.1.11"]
_DELIVERED_ONDEMAND = ('radiko.jp',)
_DOESNT_WORK_WITH_FFMPEG = ('tf-f-rpaa-radiko.smartstream.ne.jp', 'si-f-radiko.smartstream.ne.jp', 'alliance-stream-radiko.smartstream.ne.jp')
@@ -112,25 +81,7 @@ class _RadikoBaseIE(InfoExtractor):
# +/- 0 ~ 0.025 --> 0 ~ 1.5' -> +/- 0 ~ 2.77/2.13km
lat = lat + random.random() / 40.0 * (random.choice([1, -1]))
long = long + random.random() / 40.0 * (random.choice([1, -1]))
- coords = f"{round(lat, 6)},{round(long, 6)},gps"
- self.write_debug(coords)
- return coords
-
- def _generate_random_info(self):
- version_info = random.choice(self._ANDROID_VERSIONS)
- android_version = version_info["version"]
- sdk = version_info["sdk"]
- build = random.choice(version_info["builds"])
- model = random.choice(self._MODELS)
-
- info = {
- "X-Radiko-App": "aSmartPhone7a",
- "X-Radiko-App-Version": random.choice(self._APP_VERSIONS),
- "X-Radiko-Device": f"{sdk}.{model}",
- "X-Radiko-User": ''.join(random.choices('0123456789abcdef', k=32)),
- "User-Agent": f"Dalvik/2.1.0 (Linux; U; Android {android_version};{model}/{build})",
- }
- return info
+ return {"latitude": round(lat, 6), "longitude": round(long, 6)}
def _get_station_region(self, station):
regions = self.cache.load("rajiko", "region_index")
@@ -141,17 +92,22 @@ class _RadikoBaseIE(InfoExtractor):
return regions[station]
def _negotiate_token(self, station_region):
- device_info = self._generate_random_info()
- response, auth1_handle = self._download_webpage_handle("https://radiko.jp/v2/api/auth1", None,
- "Authenticating: step 1", headers=device_info)
-
- self.write_debug(response)
-
- auth1_response_headers = auth1_handle.headers
- auth_token = auth1_response_headers["X-Radiko-AuthToken"]
-
- key_length = int(auth1_response_headers["X-Radiko-KeyLength"])
- key_offset = int(auth1_response_headers["X-Radiko-KeyOffset"])
+ ua_header = {"User-Agent": "Mozilla/5.0 (Linux; Android 10; Pixel 4 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.87 Mobile Safari/537.36"}
+ # it's hardcoded in the actual app so its ok to hardcode it here
+
+ user_id = ''.join(random.choices('0123456789abcdef', k=32))
+ auth1 = self._download_json("https://api.radiko.jp/apparea/auth1", None,
+ "Authenticating: step 1", headers=ua_header, data=json.dumps({
+ "app_id": "aSmartPhone8",
+ "app_version": random.choice(self._APP_VERSIONS),
+ "user_id": user_id,
+ "device": "android",
+ }).encode())
+
+ token_info = auth1["auth_token_info"]
+
+ key_length = auth1["key_length"]
+ key_offset = auth1["key_offset"]
self.write_debug(f"KeyLength: {key_length}")
self.write_debug(f"KeyOffset: {key_offset}")
@@ -160,48 +116,54 @@ class _RadikoBaseIE(InfoExtractor):
self.write_debug(partial_key)
coords = self._get_coords(station_region)
- auth2_headers = {
- **device_info,
- "X-Radiko-AuthToken": auth_token,
- "X-Radiko-Location": coords,
- "X-Radiko-Connection": random.choice(("wifi", "mobile",)),
- "X-Radiko-Partialkey": partial_key,
- }
+ self.write_debug(coords)
- auth2 = self._download_webpage("https://radiko.jp/v2/api/auth2", station_region,
- "Authenticating: step 2", headers=auth2_headers)
- self.write_debug(auth2.strip())
- actual_region, region_kanji, region_english = auth2.split(",")
+ auth2 = self._download_json("https://api.radiko.jp/apparea/auth2", station_region,
+ "Authenticating: step 2", headers=ua_header, data=json.dumps({
+ "auth_token": token_info["auth_token"],
+ "partial_key": partial_key,
+ "connection": random.choice(("wifi", "mobile",)),
+ "location": coords,
+ }).encode())
- region_mismatch = actual_region != station_region
+ self.write_debug(auth2)
+
+ actual_regions = traverse_obj(auth2, ("areas", ..., "area_id"))
+
+ region_mismatch = station_region not in actual_regions
if region_mismatch:
- self.report_warning(f"Region mismatch: Expected {station_region}, got {actual_region}. Coords: {coords}.")
+ self.report_warning(f"Region mismatch: Expected {station_region}, got {actual_regions}. Coords: {coords}.")
self.report_warning("Please report this at https://github.com/garret1317/yt-dlp-rajiko/issues")
self.report_warning(auth2.strip())
self.report_warning(auth2_headers)
- auth_data = {
- "token": {
- "X-Radiko-AreaId": actual_region,
- "X-Radiko-AuthToken": auth_token,
+ auth_info = {
+ "headers": {
+ "X-Radiko-AreaId": station_region if not region_mismatch else actual_region[0], # i dont know if we ever get more than 1 region
+ "X-Radiko-AuthToken": token_info["auth_token"],
},
- "user": auth2_headers["X-Radiko-User"],
- "has_tf30": self._has_tf30,
+ "expiry": datetime.datetime.fromisoformat(token_info["expires_at"]).timestamp(),
+ "user_id": user_id,
+ "has_tf30": auth2.get("has_timefree_plus")
}
if not region_mismatch:
- self.cache.store("rajiko", station_region, auth_data)
- return auth_data
+ self.cache.store("rajiko8", station_region, auth_info)
+ self._has_tf30 = auth2.get("has_timefree_plus")
+ return auth_info
def _auth(self, station_region, need_tf30=False):
- cachedata = self.cache.load("rajiko", station_region)
+ cachedata = self.cache.load("rajiko8", station_region)
self.write_debug(cachedata)
if cachedata is not None:
if need_tf30 and not cachedata.get("has_tf30"):
self.write_debug("Cached token doesn't have timefree 30, getting a new one")
return self._negotiate_token(station_region)
+ if cachedata.get("expiry") <= datetime.datetime.now().timestamp():
+ self.write_debug("Token has expired, getting a new one")
+ return self._negotiate_token(station_region)
- auth_headers = cachedata.get("token")
+ auth_headers = cachedata.get("headers")
response = self._download_webpage("https://radiko.jp/v2/api/auth_check", station_region, "Checking cached token",
headers=auth_headers, expected_status=401)
self.write_debug(response)
@@ -210,7 +172,7 @@ class _RadikoBaseIE(InfoExtractor):
return self._negotiate_token(station_region)
def _get_station_meta(self, region, station_id):
- cachedata = self.cache.load("rajiko", station_id)
+ cachedata = self.cache.load("rajiko8", station_id)
now = datetime.datetime.now()
if cachedata is None or cachedata.get("expiry") < now.timestamp():
region = self._download_xml(f"https://radiko.jp/v3/station/list/{region}.xml", station_id,
@@ -282,7 +244,7 @@ class _RadikoBaseIE(InfoExtractor):
playlist_url = update_url_query(url, {
"station_id": station,
"l": "15", # l = length, ie how many seconds in the live m3u8 (max 300)
- "lsid": auth_data["user"],
+ "lsid": auth_data["user_id"],
"type": "b", # a/b = in-region, c = areafree
})
@@ -318,7 +280,7 @@ class _RadikoBaseIE(InfoExtractor):
format_note.append("Ad insertion")
- auth_headers = auth_data["token"]
+ auth_headers = auth_data["headers"]
if delivered_live and timefree and do_as_live_chunks: