#!/usr/bin/python3
import sys
import os
import o11
import base64
import json
import datetime
import pytz
import re
import jwt
import requests
from urllib import parse
from pywidevine.cdm import Cdm
from pywidevine.device import Device
from pywidevine.pssh import PSSH
from bs4 import BeautifulSoup

WVD_PATH = './WVD.wvd'

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

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')
drm = o11.parse_params(sys.argv, 'drm')
kid = o11.parse_params(sys.argv, 'kid')
pssh = o11.parse_params(sys.argv, 'pssh')
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 = '/TOD_' + user + '.tokens'
user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36'
device_id = 'a20f2bd7-06f3-46b5-bd73-c535be735e10'

def do_cdm_external(pssh_data, lic_url, lic_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)
    lic_headers = {'Origin': 'https://www.tod.tv', 'Referer': 'https://www.tod.tv/', 'User-Agent': user_agent, 'x-dt-auth-token': lic_token, 'authorization': lic_token, 'Content-Type': 'application/octet-stream'}
    licence = req.post(lic_url, headers=lic_headers, data=challenge_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_mpd(url):
    headers = {'user-agent': user_agent}
    response = req.get(url, headers=headers)
    soup = BeautifulSoup(response.content, features="xml")
    pssh_elem = soup.find('pssh')
    if pssh_elem:
        lic_url = soup.find('ms:laurl')
        return pssh_elem.text, response.url, lic_url['licenseUrl'] if lic_url else None
    return None, response.url, None

def get_url(video_id):
    headers = {'content-type': 'application/json', 'origin': 'https://www.tod.tv', 'user-agent': user_agent}
    response = req.get('https://feedpublisher.tod.tv/divauni/BEINMENA/fe/video/videodata/v2/' + video_id, headers=headers)
    for s in response.json().get('sources', []):
        if s.get('drm', {}).get('widevine', {}).get('enabled'):
            return s['uri'], s['drm']['widevine']['licenseUrl'], s['drm']['widevine']['contentKeyData'], s['origin']
    return None, None, None, None

def get_single(account_token, video_id, url, kid_val, provider):
    headers = {'content-type': 'application/json', 'origin': 'https://www.tod.tv', 'user-agent': user_agent}
    decoded = jwt.decode(account_token, options={"verify_signature": False})
    json_data = {'Type': 1, 'User': decoded['aat'], 'VideoId': video_id, 'VideoSource': url, 'VideoKind': 'live', 'AssetState': '2', 'PlayerType': 'HTML5', 'VideoSourceFormat': 'DASH', 'VideoSourceName': 'Desktop-DASH', 'DRMType': 'widevine', 'AuthType': 'Token', 'ContentKeyData': kid_val, 'Other': f'{device_id}|web_browser'}
    response = req.post(f'https://entitlement.tod.tv/entitlement/api/video/{provider}/diva/open', headers=headers, json=json_data)
    data = response.json()
    return data.get('ContentUrl', ''), data.get('AuthToken', '')

def get_channels():
    headers = {'user-agent': user_agent}
    response = req.get('https://www.tod.tv/en/channels', headers=headers)
    soup = BeautifulSoup(response.content, features='lxml')
    r = []
    for s in soup.findAll('script'):
        if s.text.startswith('window.__data'):
            data = json.loads(s.text.replace('window.__data = ', ''))
            for l in data['cache']['list'].values():
                lst = l.get('list', {})
                if lst.get('title') in ['Sports Channels', 'Live Movies Channels']:
                    for i in lst.get('items', []):
                        r.append({'title': i['title'], 'id': i['id']})
    return r

def get_video_id(token, c_id):
    headers = {'accept': 'application/json', 'origin': 'https://www.tod.tv', 'user-agent': user_agent, 'x-authorization': 'Bearer ' + token}
    params = {'delivery': 'stream', 'device': 'web_browser', 'ff': 'idp,ldp,rpt,cd,hlr,v2s', 'lang': 'en-US', 'resolution': 'HD-1080', 'sub': 'Subscriber'}
    response = req.get(f'https://me.bein-massive.com/api/account/items/{c_id}/videos', params=params, headers=headers)
    data = response.json()
    return data[0]['url'] if data else None

def refresh_token(account_token):
    headers = {'accept': 'application/json', 'content-type': 'application/json', 'origin': 'https://www.tod.tv', 'user-agent': user_agent}
    json_data = {'token': account_token, 'cookieType': 'Persistent'}
    response = req.post('https://me-cdn.bein-massive.com/api/authorization/refresh', headers=headers, json=json_data)
    return response.json().get('value', '')

def login():
    print("Please provide account token from browser. Use action=login token=YOUR_TOKEN", file=sys.stderr)
    if token_param:
        auth_data = {'accountToken': token_param}
        json.dump(auth_data, open(os.path.abspath(os.path.dirname(__file__)) + authFile, 'w'))
        print("Token saved successfully", file=sys.stderr)

def do_action():
    if action == "login":
        login()
        sys.exit()
    try:
        auth = json.load(open(os.path.abspath(os.path.dirname(__file__)) + authFile))
        account_token = refresh_token(auth['accountToken'])
        auth['accountToken'] = account_token
        json.dump(auth, open(os.path.abspath(os.path.dirname(__file__)) + authFile, 'w'))
    except:
        return "error"

    if action == "channels":
        output = {'Channels': []}
        for chan in get_channels():
            output['Channels'].append({'Name': chan['title'], 'Mode': "live", 'SessionManifest': True, 'ManifestScript': 'id=' + str(chan['id']), 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'id=' + str(chan['id']), 'Video': 'best'})
        print(json.dumps(output, indent=2))
    elif action == "events":
        output = {'Events': []}
        for chan in get_channels():
            output['Events'].append({'Name': chan['title'], 'Mode': "live", 'SessionManifest': True, 'ManifestScript': 'id=' + str(chan['id']), 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'id=' + str(chan['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":
        c_id = id
        video_id = get_video_id(account_token, c_id)
        if video_id:
            url, lic_url, kid_val, provider = get_url(video_id)
            if url:
                content_url, lic_token = get_single(account_token, video_id, url, kid_val, provider)
                pssh_data, final_url, lic_url2 = get_pssh_from_mpd(content_url)
                output = {"Cdn": [], "ManifestUrl": final_url, "Headers": {"Manifest": {'User-Agent': user_agent}, "Media": {'User-Agent': user_agent}}, "LicenseUrl": lic_url or lic_url2, "LicenseToken": lic_token}
                if pssh_data:
                    output['Pssh'] = pssh_data
                print(json.dumps(output))
    elif action == "cdm" and cdm == "external":
        c_id = id
        video_id = get_video_id(account_token, c_id)
        if video_id:
            url, lic_url, kid_val, provider = get_url(video_id)
            if url:
                content_url, lic_token = get_single(account_token, video_id, url, kid_val, provider)
                pssh_data, final_url, lic_url2 = get_pssh_from_mpd(content_url)
                if pssh_data:
                    for key in do_cdm_external(pssh_data, lic_url or lic_url2, lic_token):
                        print(key)

if do_action() == "error":
    login()
    do_action()
