#!/usr/bin/python3
import sys
import os
import o11
import json
import datetime
import pytz
import uuid
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')

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 = '/CosmoteTV_' + user + '.tokens'
user_agent = 'Mozilla/5.0 (Linux; Android 9; SM-G955F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36'

def do_cdm_external(pssh_data, pid, token, account_number):
    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 = {'Content-Type': 'application/octet-stream', 'User-Agent': user_agent, 'X-DeviceAlias': 'MobileAndroid', 'Host': 'widevine.entitlement.eu.theplatform.com', 'Connection': 'Keep-Alive', 'Accept-Encoding': 'gzip'}
    params = {'schema': '1.0', 'releasePid': pid, 'token': token, 'account': 'http://access.auth.theplatform.com/data/Account/' + account_number}
    licence = req.post('https://widevine.entitlement.eu.theplatform.com/wv/web/ModularDrm/getRawWidevineLicense', params=params, 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 = {'authority': 'gr-ampelokipoi-prod-cache02.live.cdn.cosmotetvott.gr', 'accept': '*/*', 'origin': 'null', 'referer': 'https://www.cosmotetvott.gr/', 'User-Agent': user_agent, 'X-DeviceAlias': 'MobileAndroid'}
    response = req.get(url, headers=headers)
    psshs = BeautifulSoup(response.content, features="xml").findAll('cenc:pssh')
    return psshs[1].text if len(psshs) > 1 else (psshs[0].text if psshs else None)

def get_channels(cookies):
    headers = {'Accept': 'application/json', 'Accept-Encoding': 'gzip', 'User-Agent': user_agent, 'X-DeviceAlias': 'MobileAndroid'}
    response = req.get('https://mwapi-prod.cosmotetvott.gr/api/v3.4/epg/tv/channels/all/el', cookies=cookies, headers=headers)
    channels = response.json().get('channels', [])
    return [c for c in channels if c.get('isSubscribed')]

def guid_request(cookies, guid):
    headers = {'authority': 'mwapi-prod.cosmotetvott.gr', 'accept': 'application/json, text/plain, */*', 'origin': 'https://www.cosmotetvott.gr', 'referer': 'https://www.cosmotetvott.gr/', 'User-Agent': user_agent, 'X-DeviceAlias': 'MobileAndroid'}
    params = {'guid': guid}
    response = req.get('https://mwapi-prod.cosmotetvott.gr/api/v3.4/catalog/plays/live/el', params=params, cookies=cookies, headers=headers)
    return response.json().get('items', [{}])[0].get('play', {}).get('href', '')

def el_request(cookies, url, hardwareId='undefined'):
    headers = {'authority': 'mwapi-prod.cosmotetvott.gr', 'accept': 'application/json, text/plain, */*', 'content-type': 'text/plain', 'origin': 'https://www.cosmotetvott.gr', 'referer': 'https://www.cosmotetvott.gr/', 'User-Agent': user_agent, 'X-DeviceAlias': 'MobileAndroid'}
    data = '{"selectorParameters":{"format":"SMIL","tracking":"true","clientId":"androidPlayer"},"device":{"type":"MobileAndroid","name":"SM-G955F","hardwareId":"' + hardwareId + '","osVersion":"Android 9"}}'
    response = req.post(url, cookies=cookies, headers=headers, data=data)
    resp_data = response.json()
    if 'entries' not in resp_data:
        hardwareId = resp_data.get('devices', [{}])[0].get('hardwareId', '')
        return el_request(cookies, url, hardwareId)
    for e in resp_data.get('entries', []):
        if e.get('type') == 'LIVE':
            suspension_url = e.get('checkSuspension', {}).get('href', '')
            return resp_data.get('token', ''), suspension_url.split('=')[1] if '=' in suspension_url else '', e.get('signedPublicUrl', '')
    return '', '', ''

def psu_request(url):
    headers = {'Accept': 'application/smil', 'Content-Type': 'application/smil; charset=UTF-8', 'User-Agent': user_agent, 'X-DeviceAlias': 'MobileAndroid', 'Host': 'link.eu.theplatform.com', 'Connection': 'Keep-Alive', 'Accept-Encoding': 'gzip'}
    response = req.get(url, headers=headers)
    soup = BeautifulSoup(response.content, features="xml")
    video_url = soup.find('video')['src']
    _id, sequence_token, encrypted_lock = '', '', ''
    for m in soup.findAll('meta'):
        name = m.get('name', '')
        if name == 'lockId':
            _id = m.get('content', '')
        elif name == 'lockSequenceToken':
            sequence_token = m.get('content', '')
        elif name == 'lock':
            encrypted_lock = m.get('content', '')
    account_number = soup.find('param')['value'].split('|')[0].split('=')[1]
    response2 = req.get(video_url, allow_redirects=False)
    location_url = response2.headers.get('location', video_url)
    return location_url, _id, sequence_token, encrypted_lock, account_number

def unlock_concurrency(_id, sequence_token, encrypted_lock):
    headers = {'User-Agent': user_agent, 'X-DeviceAlias': 'MobileAndroid', 'Host': 'concurrency.delivery.theplatform.eu', 'Connection': 'Keep-Alive', 'Accept-Encoding': 'gzip'}
    params = {'_clientId': 'androidPlayer', '_id': _id, '_sequenceToken': sequence_token, '_encryptedLock': encrypted_lock, 'form': 'json', 'schema': '1.0'}
    req.get('https://concurrency.delivery.theplatform.eu/concurrency/web/Concurrency/unlock', headers=headers, params=params)

def start_device():
    headers = {'Accept': 'application/json', 'User-Agent': user_agent, 'X-DeviceAlias': 'MobileAndroid', 'Host': 'mwapi-prod.cosmotetvott.gr', 'Connection': 'Keep-Alive', 'Accept-Encoding': 'gzip'}
    response = req.get('https://mwapi-prod.cosmotetvott.gr/api/v3.4/content/start/en?device=MobileAndroid', headers=headers)
    return response.json().get('login', {}).get('ssoLoginLink', {}).get('href', '')

def init_login(client_id, username):
    headers = {'Accept-version': '3.0', 'Content-Type': 'application/json; charset=UTF-8', 'Host': 'account.cosmote.gr', 'Connection': 'Keep-Alive', 'Accept-Encoding': 'gzip', 'User-Agent': 'okhttp/5.0.0-alpha.11'}
    data = {"credentials": {"username": username}, "scope": ["userinfo"], "clientId": client_id, "introduceRequest": {"appVersion": "1.27.6 (11211)", "deviceModel": "SM-G955F", "isRooted": False, "osVersion": "9", "platform": "Android", "threeGEnabled": False}}
    response = req.post('https://account.cosmote.gr/o/cosmoteid-rest/usermanagement/firestarter/cosmotetv', headers=headers, json=data)
    return response.json().get('token', '')

def do_login_request(client_id, token, username, pwd):
    headers = {'RLC-Info': token, 'Accept-version': '3.0', 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json; charset=UTF-8', 'Host': 'account.cosmote.gr', 'Connection': 'Keep-Alive', 'Accept-Encoding': 'gzip', 'User-Agent': 'okhttp/5.0.0-alpha.11'}
    data = {"credentials": {"password": pwd, "username": username}, "scope": ["userinfo"], "clientId": client_id, "introduceRequest": {"appVersion": "1.27.6 (11211)", "deviceModel": "SM-G955F", "installationId": "96226888068895845905918496269317", "isRooted": False, "osVersion": "9", "platform": "Android", "threeGEnabled": False}}
    response = req.post('https://account.cosmote.gr/o/cosmoteid-rest/usermanagement/firestarter/cosmotetv', headers=headers, json=data)
    return response.json().get('ticket', '')

def fetch_ceberus_url(url, ticket):
    headers = {'Accept': 'application/json', 'Authorization': 'Bearer ' + ticket, 'User-Agent': user_agent, 'X-DeviceAlias': 'MobileAndroid', 'Host': 'mwapi-prod.cosmotetvott.gr', 'Connection': 'Keep-Alive', 'Accept-Encoding': 'gzip'}
    response = req.get(url, headers=headers)
    data = response.json()
    if 'availableProfiles' in data:
        return data['availableProfiles'][0]
    return get_ceberus(data.get('availableSubscribers', [{}])[0].get('select', {}).get('href', ''), ticket)

def get_ceberus(url, ticket):
    headers = {'Accept': 'application/json', 'Authorization': 'Bearer ' + ticket, 'User-Agent': user_agent, 'X-DeviceAlias': 'MobileAndroid', 'Host': 'mwapi-prod.cosmotetvott.gr', 'Connection': 'Keep-Alive', 'Accept-Encoding': 'gzip'}
    response = req.get(url, headers=headers)
    return response.json().get('availableProfiles', [{}])[0]

def select_user(profile, ticket):
    url = profile.get('switchProfile', {}).get('href', '')
    headers = {'Accept': 'application/json', 'Authorization': 'Bearer ' + ticket, 'User-Agent': user_agent, 'X-DeviceAlias': 'MobileAndroid', 'Content-Type': 'application/json', 'Host': 'mwapi-prod.cosmotetvott.gr', 'Connection': 'Keep-Alive', 'Accept-Encoding': 'gzip'}
    response = req.post(url, headers=headers, json={}, allow_redirects=False)
    return response.cookies.get_dict()

def check_auth(cookies):
    headers = {'Accept': 'application/json', 'User-Agent': user_agent, 'X-DeviceAlias': 'MobileAndroid', 'Host': 'mwapi-prod.cosmotetvott.gr', 'Connection': 'Keep-Alive', 'Accept-Encoding': 'gzip'}
    response = req.get('https://mwapi-prod.cosmotetvott.gr/api/v3.4/content/start/el?device=MobileAndroid', cookies=cookies, headers=headers, allow_redirects=False)
    return response.headers.get('location', '')

def login():
    print("logging in...", file=sys.stderr)
    start_device_url = start_device()
    client_id = str(uuid.uuid4())
    bearer_token = init_login(client_id, user)
    ticket = do_login_request(client_id, bearer_token, user, password)
    switch_user_url = fetch_ceberus_url(start_device_url, ticket)
    cookies = select_user(switch_user_url, ticket)
    auth_data = {'cookies': cookies}
    json.dump(auth_data, open(os.path.abspath(os.path.dirname(__file__)) + authFile, 'w'))
    print("logged in successfully", file=sys.stderr)

def get_auth():
    auth = json.load(open(os.path.abspath(os.path.dirname(__file__)) + authFile))
    cookies = auth['cookies']
    if 'selectedSubscriberId' not in check_auth(cookies):
        raise Exception("Auth expired")
    return cookies

def get_single_channel(cookies, guid):
    el_url = guid_request(cookies, guid)
    token, pid, signed_public_url = el_request(cookies, el_url)
    video_url, _id, sequence_token, encrypted_lock, account_number = psu_request(signed_public_url)
    unlock_concurrency(_id, sequence_token, encrypted_lock)
    pssh_data = get_pssh_from_mpd(video_url)
    return video_url, pssh_data, pid, token, account_number

def do_action():
    if action == "login":
        login()
        sys.exit()
    try:
        cookies = get_auth()
    except:
        return "error"

    if action == "channels":
        output = {'Channels': []}
        for c in get_channels(cookies):
            output['Channels'].append({'Name': c['title'], 'Mode': "live", 'SessionManifest': True, 'ManifestScript': 'guid=' + c['guid'], 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'guid=' + c['guid'], 'Video': 'best'})
        print(json.dumps(output, indent=2))
    elif action == "events":
        output = {'Events': []}
        for c in get_channels(cookies):
            output['Events'].append({'Name': c['title'], 'Mode': "live", 'SessionManifest': True, 'ManifestScript': 'guid=' + c['guid'], 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'guid=' + c['guid'], '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":
        guid = id.replace('guid=', '') if id.startswith('guid=') else id
        video_url, pssh_data, pid, token, account_number = get_single_channel(cookies, guid)
        output = {"Cdn": [], "ManifestUrl": video_url, "Headers": {"Manifest": {'User-Agent': user_agent}, "Media": {'User-Agent': user_agent}}, "Pid": pid, "Token": token, "AccountNumber": account_number}
        if pssh_data:
            output['Pssh'] = pssh_data
        print(json.dumps(output))
    elif action == "cdm" and cdm_param == "external":
        guid = id.replace('guid=', '') if id.startswith('guid=') else id
        video_url, pssh_data, pid, token, account_number = get_single_channel(cookies, guid)
        if pssh_data:
            for key in do_cdm_external(pssh_data, pid, token, account_number):
                print(key)

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