#!/usr/bin/python3
import sys
import os
import o11
import json
import datetime
import pytz
import uuid
import urllib.parse
import m3u8
import oauthlib.oauth1.rfc5849.signature as oauth
from pywidevine.cdm import Cdm
from pywidevine.device import Device
from pywidevine.pssh import PSSH

WVD_PATH = './WVD.wvd'

user = o11.parse_params(sys.argv, 'user')
password = o11.parse_params(sys.argv, 'password')

id = o11.parse_params(sys.argv, 'id')
action = o11.parse_params(sys.argv, 'action')

bind = o11.parse_params(sys.argv, 'bind')
proxy = o11.parse_params(sys.argv, 'proxy')
doh = o11.parse_params(sys.argv, 'doh')
worker = o11.parse_params(sys.argv, 'worker')

cdm_param = o11.parse_params(sys.argv, 'cdm')
challenge = o11.parse_params(sys.argv, 'challenge')

o11Session = o11.session(bind=bind, proxy=proxy, worker=worker)
req = o11Session.get_session()
if doh != "":
    o11.dns(doh)

if challenge == "cert":
    challenge = "CAQ="

authFile = '/SlingTV_' + user + '.tokens'
user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0'

def do_cdm_external(pssh_data, token):
    pssh_obj = PSSH(pssh_data)
    device_obj = Device.load(WVD_PATH)
    cdm_obj = Cdm.from_device(device_obj)
    session_id = cdm_obj.open()
    challenge_data = cdm_obj.get_license_challenge(session_id, pssh_obj)
    c_array = list(challenge_data)
    lic_headers = {'Authorization': 'Bearer ' + token, 'Env': 'production', 'Content-Type': 'application/json'}
    data = json.dumps({"message": c_array})
    licence = req.post("https://p-drmwv.movetv.com/widevine/proxy", headers=lic_headers, data=data)
    cdm_obj.parse_license(session_id, licence.content)
    keys = [f"{key.kid.hex}:{key.key.hex()}" for key in cdm_obj.get_keys(session_id) if key.type != 'SIGNING']
    cdm_obj.close(session_id)
    return keys

def get_pssh_from_m3u8(url):
    headers = {'User-Agent': user_agent}
    response = req.get(url, headers=headers)
    m3u8_obj = m3u8.loads(response.content.decode())
    for key in m3u8_obj.session_keys:
        if key and 'base64' in key.uri:
            return key.uri.split(',')[-1]
    return None

def get_channels(token):
    headers = {'User-Agent': user_agent, 'Accept': '*/*', 'CLIENT-CONFIG': 'rn-client-config', 'page_size': 'large', 'Sling-Interaction-ID': str(uuid.uuid4()), 'dma': '553', 'timezone': '-0500', 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json; charset=UTF-8', 'Origin': 'https://watch.sling.com', 'Referer': 'https://watch.sling.com/'}
    response = req.get('https://p-cmwnext.movetv.com/pres/grid_guide_all', headers=headers)
    data = response.json()
    channels = data.get('channels', [])
    unique = {each['title']: each for each in channels}.values()
    return list(unique)

def get_single(token, channel_id):
    headers = {'User-Agent': user_agent, 'Accept': '*/*', 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json; charset=utf-8', 'Origin': 'https://watch.sling.com', 'Referer': 'https://watch.sling.com/'}
    data = json.dumps({"os_version": "10", "device_name": "browser", "os_name": "Windows", "brand": "sling", "qvt": f"https://cbd46b77.cdn.cms.movetv.com/playermetadata/sling/v1/api/channels/{channel_id}/schedule/now/playback_info.qvt", "drm": "widevine", "account_status": "active", "advertiser_id": None, "device_model": "Chrome", "support_mode": "false", "ssai_vod": "true", "ssai_dvr": "true"}).replace(' ', '')
    response = req.post('https://p-streamauth.movetv.com/stream/auth', headers=headers, data=data)
    resp_data = response.json()
    ssai = resp_data.get('ssai_manifest', '').split('?')[0] + '?account_status=active'
    m3u8_url = resp_data.get('m3u8_url', '').split('?')[0] + '?account_status=active'
    jwt = resp_data.get('jwt', '')
    return ssai, m3u8_url, jwt

def request_jwt(oauth_response):
    headers = {'authorization': f'OAuth oauth_consumer_key="Owa77FUtwJZcKau11Vttf4FoU7qnc6HGRCYAUkKH",oauth_nonce="Blrly8KBaABNNTvzZt5nWVLMP7uSKfs0",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1700621529",oauth_token="{oauth_response["oauth_token"]}",oauth_version="1.0"', 'sling-interaction-id': '484069e7-e316-480c-ce2d-ff7610f21208', 'cache-control': 'no-cache, no-store, must-revalidate', 'User-Agent': 'GoogleMobile-9.2.34', 'Content-Type': 'application/json; charset=UTF-8'}
    data = json.dumps({"device_guid": oauth_response['device_guid'], "platform": "android", "product": "sling", "profile_guid": oauth_response['user_guid']}).replace(' ', '')
    client_secret = 'BALomd2e4p4A7xKXjQxq94qwnER5FEMEQlwHxHp3'
    params = oauth.collect_parameters(uri_query="", body=data, headers=headers, exclude_oauth_signature=True, with_realm=False)
    norm_params = oauth.normalize_parameters(params)
    base_string = oauth.signature_base_string("POST", "https://p-cmwnext.movetv.com/cmw/v1/client/jwt", norm_params)
    sig = oauth.sign_hmac_sha1(base_string, client_secret, oauth_response['oauth_token_secret'])
    headers['authorization'] += ',oauth_signature="' + urllib.parse.quote_plus(sig) + '"'
    response = req.post('https://p-cmwnext.movetv.com/cmw/v1/client/jwt', headers=headers, data=data)
    return response.json().get('jwt', '')

def login():
    print("Please provide persist:root data manually in the tokens file", file=sys.stderr)
    print("Login to Sling TV website, go to DevTools -> Application -> LocalStorage and copy 'persist:root' value", file=sys.stderr)
    sys.exit(1)

def do_action():
    if action == "login":
        login()
        sys.exit()
    try:
        auth = json.load(open(os.path.abspath(os.path.dirname(__file__)) + authFile))
        user_data = json.loads(auth['user'])['userData']
        token = request_jwt(user_data)
    except:
        return "error"

    if action == "channels":
        output = {'Channels': []}
        for c in get_channels(token):
            output['Channels'].append({'Name': c['title'], 'Mode': "live", 'SessionManifest': True, 'ManifestScript': 'id=' + c['id'], 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'id=' + c['id'], 'Video': 'best'})
        print(json.dumps(output, indent=2))
    elif action == "events":
        output = {'Events': []}
        for c in get_channels(token):
            output['Events'].append({'Name': c['title'], 'Mode': "live", 'SessionManifest': True, 'ManifestScript': 'id=' + c['id'], 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'id=' + c['id'], 'Video': 'best', 'Autostart': True, 'Start': int(datetime.datetime.now(pytz.UTC).timestamp()), 'End': int((datetime.datetime.now(pytz.UTC) + datetime.timedelta(hours=4)).timestamp())})
        print(json.dumps(output, indent=2))
    elif action == "manifest":
        channel_id = id.replace('id=', '') if id.startswith('id=') else id
        ssai_manifest, m3u8_url, play_token = get_single(token, channel_id)
        pssh_data = get_pssh_from_m3u8(m3u8_url)
        output = {"Cdn": [], "ManifestUrl": ssai_manifest, "Headers": {"Manifest": {'User-Agent': user_agent}, "Media": {'User-Agent': user_agent}}, "M3u8Url": m3u8_url, "PlayToken": play_token}
        if pssh_data:
            output['Pssh'] = pssh_data
        print(json.dumps(output))
    elif action == "cdm" and cdm_param == "external":
        channel_id = id.replace('id=', '') if id.startswith('id=') else id
        ssai_manifest, m3u8_url, play_token = get_single(token, channel_id)
        pssh_data = get_pssh_from_m3u8(m3u8_url)
        if pssh_data:
            for key in do_cdm_external(pssh_data, play_token):
                print(key)

if do_action() == "error":
    print("Error: Please create token file with persist:root data", file=sys.stderr)
    sys.exit(1)
