refactor(MY5): Change endpoints to android
- The old method of fetching hmac keys from javascript has been removed, along with the github that hosted them. - The required cert is included in the code itself and not needed separately
This commit is contained in:
parent
fcc87ac317
commit
62d226671d
@ -1,24 +1,22 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import hashlib
|
|
||||||
import hmac
|
|
||||||
import json
|
|
||||||
import re
|
import re
|
||||||
|
import tempfile
|
||||||
|
import os
|
||||||
from collections.abc import Generator
|
from collections.abc import Generator
|
||||||
from datetime import datetime
|
|
||||||
from typing import Any, Union
|
from typing import Any, Union
|
||||||
from urllib.parse import urlparse, urlunparse
|
from urllib.parse import urlparse, urlunparse
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
import requests
|
||||||
from click import Context
|
from click import Context
|
||||||
from Crypto.Cipher import AES
|
|
||||||
from Crypto.Util.Padding import unpad
|
|
||||||
from devine.core.manifests.dash import DASH
|
from devine.core.manifests.dash import DASH
|
||||||
from devine.core.search_result import SearchResult
|
from devine.core.search_result import SearchResult
|
||||||
from devine.core.service import Service
|
from devine.core.service import Service
|
||||||
from devine.core.titles import Episode, Movie, Movies, Series
|
from devine.core.titles import Episode, Movie, Movies, Series
|
||||||
from devine.core.tracks import Chapter, Tracks
|
from devine.core.tracks import Chapter, Tracks
|
||||||
|
from devine.core.utils.sslciphers import SSLCiphers
|
||||||
from pywidevine.cdm import Cdm as WidevineCdm
|
from pywidevine.cdm import Cdm as WidevineCdm
|
||||||
|
|
||||||
|
|
||||||
@ -26,7 +24,6 @@ class MY5(Service):
|
|||||||
"""
|
"""
|
||||||
\b
|
\b
|
||||||
Service code for Channel 5's My5 streaming service (https://channel5.com).
|
Service code for Channel 5's My5 streaming service (https://channel5.com).
|
||||||
Credit to @Diazole(https://github.com/Diazole/my5-dl) for solving the hmac.
|
|
||||||
|
|
||||||
\b
|
\b
|
||||||
Author: stabbedbybrick
|
Author: stabbedbybrick
|
||||||
@ -61,9 +58,7 @@ class MY5(Service):
|
|||||||
self.title = title
|
self.title = title
|
||||||
super().__init__(ctx)
|
super().__init__(ctx)
|
||||||
|
|
||||||
self.gist = self.session.get(
|
self.session.headers.update({"user-agent": self.config["user_agent"]})
|
||||||
self.config["endpoints"]["gist"].format(timestamp=datetime.now().timestamp())
|
|
||||||
).json()
|
|
||||||
|
|
||||||
def search(self) -> Generator[SearchResult, None, None]:
|
def search(self) -> Generator[SearchResult, None, None]:
|
||||||
params = {
|
params = {
|
||||||
@ -173,32 +168,30 @@ class MY5(Service):
|
|||||||
|
|
||||||
# Service specific functions
|
# Service specific functions
|
||||||
|
|
||||||
def decrypt_data(self, media: str) -> dict:
|
|
||||||
key = base64.b64decode(self.gist["key"])
|
|
||||||
|
|
||||||
r = self.session.get(media)
|
|
||||||
if not r.ok:
|
|
||||||
raise ConnectionError(r.json().get("message"))
|
|
||||||
|
|
||||||
content = r.json()
|
|
||||||
|
|
||||||
iv = base64.urlsafe_b64decode(content["iv"])
|
|
||||||
data = base64.urlsafe_b64decode(content["data"])
|
|
||||||
|
|
||||||
cipher = AES.new(key=key, iv=iv, mode=AES.MODE_CBC)
|
|
||||||
decrypted_data = unpad(cipher.decrypt(data), AES.block_size)
|
|
||||||
return json.loads(decrypted_data)
|
|
||||||
|
|
||||||
def get_playlist(self, asset_id: str) -> tuple:
|
def get_playlist(self, asset_id: str) -> tuple:
|
||||||
secret = self.gist["hmac"]
|
session = self.session
|
||||||
|
for prefix in ("https://", "http://"):
|
||||||
|
session.mount(prefix, SSLCiphers())
|
||||||
|
|
||||||
timestamp = datetime.now().timestamp()
|
cert_binary = base64.b64decode(self.config["certificate"])
|
||||||
vod = self.config["endpoints"]["vod"].format(id=asset_id, timestamp=f"{timestamp}")
|
with tempfile.NamedTemporaryFile(delete=False, suffix=".pem") as cert_file:
|
||||||
sig = hmac.new(base64.b64decode(secret), vod.encode(), hashlib.sha256)
|
cert_file.write(cert_binary)
|
||||||
auth = base64.urlsafe_b64encode(sig.digest()).decode()
|
cert_path = cert_file.name
|
||||||
vod += f"&auth={auth}"
|
try:
|
||||||
|
r = session.get(url=self.config["endpoints"]["auth"].format(title_id=asset_id), cert=cert_path)
|
||||||
|
except requests.RequestException as e:
|
||||||
|
if "Max retries exceeded" in str(e):
|
||||||
|
raise ConnectionError(
|
||||||
|
"Permission denied. If you're behind a VPN/proxy, you might be blocked"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise ConnectionError(f"Failed to request assets: {str(e)}")
|
||||||
|
finally:
|
||||||
|
os.remove(cert_path)
|
||||||
|
|
||||||
data = self.decrypt_data(vod)
|
data = r.json()
|
||||||
|
if not data.get("assets"):
|
||||||
|
raise ValueError(f"Could not find asset: {data}")
|
||||||
|
|
||||||
asset = [x for x in data["assets"] if x["drm"] == "widevine"][0]
|
asset = [x for x in data["assets"] if x["drm"] == "widevine"][0]
|
||||||
rendition = asset["renditions"][0]
|
rendition = asset["renditions"][0]
|
||||||
|
@ -1,8 +1,38 @@
|
|||||||
|
user_agent: Dalvik/2.1.0 (Linux; U; Android 14; SM-S901B Build/UP1A.231005.007)
|
||||||
|
|
||||||
endpoints:
|
endpoints:
|
||||||
base: https://corona.channel5.com
|
base: https://corona.channel5.com
|
||||||
gist: https://gist.githubusercontent.com/stabbedbybrick/8726c719721eac50a28f7bc3c94f18e9/raw/s.txt?={timestamp}
|
content: https://corona.channel5.com/shows/{show}.json?platform=my5android
|
||||||
content: https://corona.channel5.com/shows/{show}.json?platform=my5desktop
|
episodes: https://corona.channel5.com/shows/{show}/episodes.json?platform=my5android
|
||||||
episodes: https://corona.channel5.com/shows/{show}/episodes.json?platform=my5desktop
|
single: https://corona.channel5.com/shows/{show}/seasons/{season}/episodes/{episode}.json?platform=my5android
|
||||||
single: https://corona.channel5.com/shows/{show}/seasons/{season}/episodes/{episode}.json?platform=my5desktop
|
auth: https://cassie-auth.channel5.com/api/v2/media/my5androidhydradash/{title_id}.json
|
||||||
vod: https://cassie.channel5.com/api/v2/media/my5desktopng/{id}.json?timestamp={timestamp}
|
|
||||||
search: https://corona.channel5.com/shows/search.json
|
search: https://corona.channel5.com/shows/search.json
|
||||||
|
|
||||||
|
certificate: |
|
||||||
|
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tDQpNSUlDdXpDQ0FpU2dBd0lCQWdJRVhMU1BGVEFOQmdrcWhraUc5dzBCQVFVRkFE
|
||||||
|
QmxNUXN3Q1FZRFZRUUdFd0pIDQpRakVWTUJNR0ExVUVCd3dNUkdWbVlYVnNkQ0JEYVhSNU1SSXdFQVlEVlFRS0RBbERhR0Z1Ym1W
|
||||||
|
c0lEVXhEekFODQpCZ05WQkFzTUJrTmhjM05wWlRFYU1CZ0dBMVVFQXd3UlEyRnpjMmxsSUUxbFpHbGhJRUYxZEdnd0hoY05NVGt3
|
||||||
|
DQpOREUxTVRRd016QXhXaGNOTWprd05ERTFNVFF3TXpBeFdqQ0JqakVMTUFrR0ExVUVCaE1DUjBJeEVqQVFCZ05WDQpCQW9NQ1VO
|
||||||
|
b1lXNXVaV3dnTlRFWE1CVUdBMVVFQ3d3T1EyRnpjMmxsSUdOc2FXVnVkSE14VWpCUUJnTlZCQU1NDQpTVU5oYzNOcFpTQlRaV3ht
|
||||||
|
TFhOcFoyNWxaQ0JEWlhKMGFXWnBZMkYwWlNCbWIzSWdUWGsxSUVGdVpISnZhV1FnDQpUbVY0ZENCSFpXNGdZMnhwWlc1MElERTFO
|
||||||
|
VFV6TXpZNU9ERXdnWjh3RFFZSktvWklodmNOQVFFQkJRQURnWTBBDQpNSUdKQW9HQkFNbVVTSHFCZ3pwbThXelVHZ2VDSWZvSTI3
|
||||||
|
QlovQmNmWktpbnl5dXFNVlpDNXRLaUtaRWpydFV4DQpoMXFVcDJSSkN3Ui9RcENPQ2RQdFhzMENzekZvd1ByTlY4RHFtUXZqbzY5
|
||||||
|
dlhvTEM3c2RLUjQ1cEFUQU8vY3JLDQorTUFPUXo1VWEyQ1ZrYnY1SCtaMVhWWndqbm1qNGJHZEJHM005b0NzQlVqTEh0bm1nQSty
|
||||||
|
QWdNQkFBR2pUakJNDQpNQjBHQTFVZERnUVdCQlNVVUhrY3JKNUVkVTVWM2ZJbXQra1ljdkdnZFRBTEJnTlZIUThFQkFNQ0E3Z3dD
|
||||||
|
UVlEDQpWUjBUQkFJd0FEQVRCZ05WSFNVRUREQUtCZ2dyQmdFRkJRY0RBakFOQmdrcWhraUc5dzBCQVFVRkFBT0JnUUFpDQpHNi84
|
||||||
|
OUFEaDhEOUs0OXZjeklMQ2pqbGh6bG5US09GM2l1Um0vSjZYaWtxY3RxSDF0a01na0FXcHAwQldBRm9IDQpJbU5WSEtKdTRnZXgy
|
||||||
|
cEtLejNqOVlRNG5EWENQVTdVb0N2aDl5TTNYT0RITWZRT01sZkRtMU9GZkh2QkJvSHNVDQpHSE9EQTkwQi8xcU0xSlFaZzBOVjZi
|
||||||
|
UllrUytCOWdtSFI4dXhtZktrL0E9PQ0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ0KLS0tLS1CRUdJTiBQUklWQVRFIEtFWS0t
|
||||||
|
LS0tDQpNSUlDZHdJQkFEQU5CZ2txaGtpRzl3MEJBUUVGQUFTQ0FtRXdnZ0pkQWdFQUFvR0JBTW1VU0hxQmd6cG04V3pVDQpHZ2VD
|
||||||
|
SWZvSTI3QlovQmNmWktpbnl5dXFNVlpDNXRLaUtaRWpydFV4aDFxVXAyUkpDd1IvUXBDT0NkUHRYczBDDQpzekZvd1ByTlY4RHFt
|
||||||
|
UXZqbzY5dlhvTEM3c2RLUjQ1cEFUQU8vY3JLK01BT1F6NVVhMkNWa2J2NUgrWjFYVlp3DQpqbm1qNGJHZEJHM005b0NzQlVqTEh0
|
||||||
|
bm1nQStyQWdNQkFBRUNnWUFjTVY4SnN6OTFWWnlDaWcreDZTTnpZdlhHDQo3bTd4bFBSeEdqYXlQclZ6eVJ1YmJnNitPKzFoNS9G
|
||||||
|
MFc4SWxwb21oOFdLUDhTMnl0RXBFQmhLbDRHN001WXdqDQp0SCtCVXFNMTNjbFdiQkxuQTZMT2RVeEVDTVhIUktjdHk5UE52UlJQ
|
||||||
|
cU9aV0YycDc5U1BFdFY5Q2o1SXNaVUdNDQpRcHYybk5oN1M2MUZGRVRuSVFKQkFPTXJNd2tnOGQzbksyS0lnVUNrcEtCRHlGTUJj
|
||||||
|
UXN0NG82VkxvVjNjenBwDQpxMW5FWGx4WnduMFh6Ni9GVjRWdTZYTjJLLzQxL2pCeWdTUlFXa05YVThNQ1FRRGpLYXVpdE1UajBM
|
||||||
|
ajU3QkJ3DQppNkNON0VFeUJSSkZaVGRSMDM4ZzkxSEFoUkVXVWpuQ0Vrc1UwcTl4TUNOdnM3OFN4RmQ1ODg5RUJQTnd1RDdvDQor
|
||||||
|
NTM1QWtFQTNwVTNYbHh2WUhQZktKNkR0cWtidlFSdFJoZUZnZVNsdGZzcUtCQVFVVTIwWFRKeEdwL0FWdjE3DQp1OGZxcDQwekpM
|
||||||
|
VEhDa0F4SFpzME9qYVpHcDU0TFFKQWJtM01iUjA1ZFpINnlpdlMxaE5hYW9QR01iMjdZeGJRDQpMS3dHNmd5d3BrbEp4RE1XdHR4
|
||||||
|
VHVYeXVJdlVHMVA5cFRJTThEeUhSeVR3cTU4bjVjeU1XYVFKQkFMVFRwZkVtDQoxdWhCeUd0NEtab3dYM2dhREpVZGU0ZjBwN3Ry
|
||||||
|
RFZGcExDNVJYcVVBQXNBQ2pzTHNYaEFadlovUEEwUDBiU2hmDQp4cUFRa2lnYmNKRXdxdjQ9DQotLS0tLUVORCBQUklWQVRFIEtF
|
||||||
|
WS0tLS0t
|
Loading…
x
Reference in New Issue
Block a user