#!/usr/bin/python3
import sys
import os
import o11
import json
import datetime
import pytz
import uuid
import urllib
import m3u8
import hmac
import hashlib
import base64
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 = 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 = '/Sling_' + 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:
        pssh = key.uri.split(',')[-1]
        return pssh
    return None

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)
    data = response.json()
    return data.get('ssai_manifest', '').split('?')[0] + '?account_status=active', data.get('m3u8_url', '').split('?')[0] + '?account_status=active', data.get('jwt', '')

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 request_jwt(oauth_response):
    headers = {'authorization': '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)
    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) if m3u8_url else None
        output = {"Cdn": [], "ManifestUrl": ssai_manifest, "Headers": {"Manifest": {'User-Agent': user_agent}, "Media": {'User-Agent': user_agent}}, "PlayToken": play_token}
        if pssh_data:
            output['Pssh'] = pssh_data
        print(json.dumps(output))
    elif action == "cdm" and cdm == "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 m3u8_url else None
        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)
