from __future__ import annotations import json import re import sys from collections.abc import Generator from typing import Any, Optional, Union from urllib.parse import urljoin import click from requests import Request from unshackle.core.constants import AnyTrack from unshackle.core.manifests import DASH from unshackle.core.search_result import SearchResult from unshackle.core.service import Service from unshackle.core.titles import Episode, Series, Title_T, Titles_T from unshackle.core.tracks import Chapter, Chapters, Tracks from unshackle.core.utils.sslciphers import SSLCiphers from unshackle.core.utils.xml import load_xml class CBS(Service): """ \b Service code for CBS.com streaming service (https://cbs.com). Credit to @srpen6 for the tip on anonymous session \b Version: 1.0.1 Author: stabbedbybrick Authorization: None Robustness: Widevine: L3: 2160p, DDP5.1 \b Tips: - Input should be complete URLs: SERIES: https://www.cbs.com/shows/tracker/ EPISODE: https://www.cbs.com/shows/video/E0wG_ovVMkLlHOzv7KDpUV9bjeKFFG2v/ \b Common VPN/proxy errors: - SSLError(SSLEOFError(8, '[SSL: UNEXPECTED_EOF_WHILE_READING]')) - ConnectionError: 406 Not Acceptable, 403 Forbidden """ GEOFENCE = ("us",) @staticmethod @click.command(name="CBS", short_help="https://cbs.com", help=__doc__) @click.argument("title", type=str, required=False) @click.pass_context def cli(ctx, **kwargs) -> CBS: return CBS(ctx, **kwargs) def __init__(self, ctx, title): self.title = title super().__init__(ctx) def search(self) -> Generator[SearchResult, None, None]: params = { "term": self.title, "termCount": 50, "showCanVids": "true", } results = self._request("GET", "/apps-api/v3.1/androidphone/contentsearch/search.json", params=params)["terms"] for result in results: yield SearchResult( id_=result.get("path"), title=result.get("title"), description=None, label=result.get("term_type"), url=result.get("path"), ) def get_titles(self) -> Titles_T: title_re = r"https://www\.cbs\.com/shows/(?P