diff --git a/services/MY5/__init__.py b/services/MY5/__init__.py index 2775898..cf4df40 100644 --- a/services/MY5/__init__.py +++ b/services/MY5/__init__.py @@ -1,24 +1,22 @@ from __future__ import annotations import base64 -import hashlib -import hmac -import json import re +import tempfile +import os from collections.abc import Generator -from datetime import datetime from typing import Any, Union from urllib.parse import urlparse, urlunparse import click +import requests 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.search_result import SearchResult from devine.core.service import Service from devine.core.titles import Episode, Movie, Movies, Series from devine.core.tracks import Chapter, Tracks +from devine.core.utils.sslciphers import SSLCiphers from pywidevine.cdm import Cdm as WidevineCdm @@ -26,7 +24,6 @@ class MY5(Service): """ \b 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 Author: stabbedbybrick @@ -61,9 +58,7 @@ class MY5(Service): self.title = title super().__init__(ctx) - self.gist = self.session.get( - self.config["endpoints"]["gist"].format(timestamp=datetime.now().timestamp()) - ).json() + self.session.headers.update({"user-agent": self.config["user_agent"]}) def search(self) -> Generator[SearchResult, None, None]: params = { @@ -173,32 +168,30 @@ class MY5(Service): # 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: - secret = self.gist["hmac"] + session = self.session + for prefix in ("https://", "http://"): + session.mount(prefix, SSLCiphers()) - timestamp = datetime.now().timestamp() - vod = self.config["endpoints"]["vod"].format(id=asset_id, timestamp=f"{timestamp}") - sig = hmac.new(base64.b64decode(secret), vod.encode(), hashlib.sha256) - auth = base64.urlsafe_b64encode(sig.digest()).decode() - vod += f"&auth={auth}" + cert_binary = base64.b64decode(self.config["certificate"]) + with tempfile.NamedTemporaryFile(delete=False, suffix=".pem") as cert_file: + cert_file.write(cert_binary) + cert_path = cert_file.name + 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] rendition = asset["renditions"][0] diff --git a/services/MY5/config.yaml b/services/MY5/config.yaml index c57323e..de8a030 100644 --- a/services/MY5/config.yaml +++ b/services/MY5/config.yaml @@ -1,8 +1,38 @@ +user_agent: Dalvik/2.1.0 (Linux; U; Android 14; SM-S901B Build/UP1A.231005.007) + endpoints: 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=my5desktop - episodes: https://corona.channel5.com/shows/{show}/episodes.json?platform=my5desktop - single: https://corona.channel5.com/shows/{show}/seasons/{season}/episodes/{episode}.json?platform=my5desktop - vod: https://cassie.channel5.com/api/v2/media/my5desktopng/{id}.json?timestamp={timestamp} + content: https://corona.channel5.com/shows/{show}.json?platform=my5android + episodes: https://corona.channel5.com/shows/{show}/episodes.json?platform=my5android + single: https://corona.channel5.com/shows/{show}/seasons/{season}/episodes/{episode}.json?platform=my5android + auth: https://cassie-auth.channel5.com/api/v2/media/my5androidhydradash/{title_id}.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 \ No newline at end of file