# -*- coding: utf-8 -*-

from .common import *

class TokenNotFound(Exception): pass

class TokenRenewFailed(Exception): pass

class RequestFailed(Exception): pass


class APIParser():

	@staticmethod
	def getElement(json, path : list, allowNone : bool = False ):
		while json is not None and len(path) > 0:
			json = json.get(path[0])
			del path[0]
		if not allowNone and json is None:
			raise Exception("Element not found")
		return json

	def buildLiveDescription(title, episode, description, start_date, ends_date):
		starting, ending = (None for _ in range(2))
		titleText = f"[COLOR yellow]{title}[/COLOR][CR][COLOR deepskyblue]{episode}[/COLOR]" if \
			episode and episode != title else f"[COLOR yellow]{title}[/COLOR]"
		plotText = f"[CR][CR]{description.replace(chr(10), '')}" if description else ""
		if start_date:
			starting = get_CentralTime(start_date, actual=False).strftime('%H:%M')
		if ends_date:
			ending = get_CentralTime(ends_date, actual=False).strftime('%H:%M')
		if starting and ending:
			return translation(30621).format(starting, ending, titleText, plotText)
		elif ending:
			return translation(30622).format(ending, titleText, plotText)
		elif starting:
			return translation(30623).format(starting, titleText, plotText)
		return titleText+plotText

	def parseLiveChannel(json) -> list:
		id = APIParser.getElement(json, ["id"])
		epgElement = APIParser.getElement(json, ["epgsFromNow"], True)
		epgElementNow = epgElement[0] if epgElement and len(epgElement) > 0 else None
		channel = APIParser.getElement(json, ["name"])
		channelType = APIParser.getElement(json, ["channelType"])
		tier = 'FREE' if channelType == 'FAST' else 'PREMIUM'
		show_uno = APIParser.getElement(epgElementNow, ["title"], True)
		episode_uno = APIParser.getElement(epgElementNow, ["episodeTitle"], True)
		title = f'[B]{channel}[/B] - {show_uno}'
		formatType = APIParser.getElement(epgElementNow, ["type"], True)
		genres = APIParser.getElement(epgElementNow, ["vodMetadata", "format", "genres"], True)
		landscapeImg = APIParser.getElement(epgElementNow, ["imageUrl"], True)
		if landscapeImg is None:
			landscapeImg = APIParser.getElement(json, ["images", "alternativeLandscapeUri"], True)
		description = APIParser.buildLiveDescription(show_uno, episode_uno, \
								APIParser.getElement(epgElementNow, ["description"], True), \
								APIParser.getElement(epgElementNow, ["startPublished"], True), \
								APIParser.getElement(epgElementNow, ["endPublished"], True))
		if epgElement and len(epgElement) > 1:
			epgElementDue = epgElement[1]
			show_due = APIParser.getElement(epgElementDue, ["title"], True)
			episode_due = APIParser.getElement(epgElementDue, ["episodeTitle"], True)
			description += '[CR][CR]'+APIParser.buildLiveDescription(show_due, episode_due, \
										APIParser.getElement(epgElementDue, ["description"], True), \
										APIParser.getElement(epgElementDue, ["startPublished"], True), \
										APIParser.getElement(epgElementDue, ["endPublished"], True))
		if epgElement and len(epgElement) > 2:
			epgElementTre = epgElement[2]
			show_tre = APIParser.getElement(epgElementTre, ["title"], True)
			episode_tre = APIParser.getElement(epgElementTre, ["episodeTitle"], True)
			description += '[CR][CR]'+APIParser.buildLiveDescription(show_tre, episode_tre, \
										APIParser.getElement(epgElementTre, ["description"], True), \
										APIParser.getElement(epgElementTre, ["startPublished"], True), \
										APIParser.getElement(epgElementTre, ["endPublished"], True))
		return (id, channel, title, tier, channelType, formatType, genres, landscapeImg, description)

class API(): # LAST EDITED = 23.08.2025 @realvito
	ENDPOINTS = {
		'GetWatchFavorites' : 'd3771a9f6c262ddfaf38ea86d174920687ca709caa5dd210d35667a95a1f9724',
		'addFavorite' : '22cb1ccd31a2cbe37ed6b3483bd3026edb5283d4a8d5fa4d3b7e8807d56af306',
		'deleteFavorite' : 'ee3b38bdbc9f4e723db702819cd955580cdb530d8574e80c3f75852e74ec7937',
		'WatchFavoriteIds' : 'e2228fd7dfb9f78c5f71d8a7562ed7490b8b7ae348379b3f4ee0c2afd953117e',
		'GetFavoriteIds' : '77e8f3ee705a86812515ada975e1b6037f73e448185dd426777875bbc0511801',
		'OverviewPage' : '28aad4e992bb63330bfcd40a6906af3119d8a2612fa9fd28dae9c19127e247ca',
		'Format' : 'd112638c0184ab5698af7b69532dfe2f12973f7af9cb137b9f70278130b1eafa',
		'SeasonWithFormatAndEpisodes' : 'cc0fbbe17143f549a35efa6f8665ceb9b1cfae44b590f0b2381a9a304304c584',
		'EpisodeDetail' : '2e5ef142c79f8620e8e93c8f21b31a463b16d89a557f7f5f0c4a7e063be96a8a',
		'MovieDetail' : 'b1c360212cc518ddca2b8377813a54fa918ca424c08086204b7bf7d6ef626ac4',
		'LiveTvStations' : '107b56f9dbe4711aff4405a203c42630eadee2e7e5d0ae6093d46e7a85db3c2f',
		'LiveEventsOverviewPage' : '77a8f26d5de76daf801fcd7ae54bebd0ecabbeeb3ecb90889bc4a2e5590b7d20',
		'ExploreWidgetWatch' : 'ba2948302a1161c7ff03cef8aee152df4dac8068fa778380e7914c9dd9260c38',
		'ExploreHighlightWatch' : '6172bce7568431db4cfc05d208d738cfab5bf55068c81b7a4ad2e30d032a0b82',
		'Search' : '9b4d8cd668c4aad6e7dd3eb4b0ce6137159f76da1c68784936a7b72635025f0a',
		'WatchPlayerConfigV3' : 'fea0311fb572b6fded60c5a1a9d652f97f55d182bc4cedbdad676354a8d2797c',
		'TopicWorlds' : '5acf7da4fbbe658febbc529dfcc25ef58ca6f8c6786d4c726a9081585eb6a9af',
		'topicWorldByPathV2' : '6ff0068a9128fa1f207d1882986840c0d97a127ff5c690e310bc747d554472c1',
		'Recommendations' : '485dc7d0e9c030a57ff5820471010b7d31866f6a7f24ab18e4f75ac11b5263e8',
		'MRE' : '0c77404637570adff548e329a48654498c54ce1c36a459d72586ff18999bebaa',
		'PodcastFeed' : 'a70e8b2c1e16d23ba99c0a7f326e2d8b549383e9a79d41a407ecf104391f4575',
		'PodcastDetail' : 'efc69a7094e1c4d7195afd7d9e2597a052d45a2c134304e01a386a089be93334',
		'PodcastRecommendations' : '432363e3cfd7c4076c0fd921774ee55bfa5272ca264834750e6ce218072be8cf'
	}
	APIBASEURL = base64.b64decode(b'aHR0cHM6Ly9jZG4uZ2F0ZXdheS5ub3ctcGx1cy1wcm9kLmF3cy1jYmMuY2xvdWQvZ3JhcGhxbA==').decode()
	APISTUSURL = base64.b64decode(b'aHR0cHM6Ly9zdHVzLnBsYXllci5zdHJlYW1pbmd0ZWNoLmRl').decode()
	AUTHBASEURL = base64.b64decode(b'aHR0cHM6Ly9hdXRoLnJ0bC5kZS9hdXRoL3JlYWxtcy9ydGxwbHVzL3Byb3RvY29sL29wZW5pZC1jb25uZWN0').decode()

	@staticmethod
	def quoteUrl(url : str):
		queryPos = url.find('?') + 1
		if queryPos > 0:
			parameters = url[queryPos:].split('&')
			baseURL = url[:queryPos]
			valuePairs = []
			for param in parameters:
				valuePos = param.find('=')
				valuePairs.append(param[:valuePos] + "=" + quote(param[valuePos+1:], safe=""))
			url = baseURL + "&".join(valuePairs)
		return url

	def postRequest(self, url : str, postData : dict=None, jsonData : json = None, token=None, client=False, opener=None, getUrl=False) -> str:
		headers = {
			'User-Agent': USERAGENT,
			'Referer' : base64.b64decode(b'aHR0cHM6Ly9wbHVzLnJ0bC5kZS8=').decode(),
			'Origin' : base64.b64decode(b'aHR0cHM6Ly9wbHVzLnJ0bC5kZQ==').decode(),
			'Content-Type' : 'application/x-www-form-urlencoded'
		}
		if (postData is None and jsonData is None) or (postData is not None and jsonData is not None):
			return None
		if token is not None:
			headers["Authorization"] = f"Bearer {token}"
		if client:
			headers[base64.b64decode(b'cnRscGx1cy1jbGllbnQtSWQ=').decode()] = base64.b64decode(b'cmNpOnJ0bHBsdXM6d2Vi').decode()
			headers[base64.b64decode(b'cnRscGx1cy1jbGllbnQtVmVyc2lvbg==').decode()] = self.clientVersion
		if postData is not None:
			postData = urlencode(postData)
		if jsonData is not None:
			headers['Content-Type'] = 'application/json; charset=utf-8'
			postData = json.dumps(jsonData)

		headers['Content-Length'] = len(postData)
		req = Request(self.quoteUrl(url), headers=headers)
		debug_MS("(api.postRequest[1]) ------------------------------------------------ STARTS = postRequest -----------------------------------------------")
		debug_MS(f"(api.postRequest[2]) ### URL : {url} ### HEADERS : {headers} ###")
		try:
			if opener is not None:
				query = opener.open(req, postData.encode('utf-8'))
			else: query = urlopen(req, postData.encode('utf-8'))
			if getUrl:
				res = query.geturl()
			else: res = query.read()
		except HTTPError as exc:
			failing(f"(api.postRequest) ERROR - EXEPTION - ERROR ##### STATUS : {exc.code} === FAILURE : {exc.read()} #####")
			raise RequestFailed("Invalid Response")
		debug_MS("(api.postRequest[3]) ------------------------------------------------ ENDING = postRequest -----------------------------------------------")
		return res

	def getRequest(self, url: str, token=None, client=True, opener=None, prefix='Authorization') -> str:
		headers = {
			'User-Agent': USERAGENT,
			'Referer' : base64.b64decode(b'aHR0cHM6Ly9wbHVzLnJ0bC5kZS8=').decode(),
			'Origin' :	base64.b64decode(b'aHR0cHM6Ly9wbHVzLnJ0bC5kZQ==').decode(),
		}
		if token is not None:
			headers[prefix] = f'Bearer {token}' if prefix == 'Authorization' else token
		if client:
			headers[base64.b64decode(b'cnRscGx1cy1jbGllbnQtSWQ=').decode()] = base64.b64decode(b'cmNpOnJ0bHBsdXM6d2Vi').decode()
			headers[base64.b64decode(b'cnRscGx1cy1jbGllbnQtVmVyc2lvbg==').decode()] = self.clientVersion

		req = Request(self.quoteUrl(url), headers=headers)
		debug_MS("(api.getRequest[1]) ------------------------------------------------ STARTS = getRequest -----------------------------------------------")
		debug_MS(f"(api.getRequest[2]) ### URL : {url} ### HEADERS : {headers} ###")
		try:
			if opener is not None:
				query = opener.open(req)
			else: query = urlopen(req)
			res = query.read()
		except HTTPError as exc:
			failing(f"(api.getRequest) ERROR - EXEPTION - ERROR ##### STATUS : {exc.code} === FAILURE : {exc.read()} #####")
			raise Exception("Invalid Response")
		debug_MS("(api.getRequest[3]) ------------------------------------------------ ENDING = getRequest -----------------------------------------------")
		return res

	@staticmethod
	def isTokenValid(token : str) -> bool:
		valid = True
		try:
			base64Parts = token.split(".")
			token = "%s==" % base64Parts[1]
			userData = json.loads(base64.b64decode(token))
			if "licenceEndDate" in userData:
				licenceEndDate = userData["licenceEndDate"].split("+")[4]
				try:
					licenceEndDateTS = get_CentralTime(licenceEndDate)
					licenceEndDateTS = time.mktime(licenceEndDateTS)
					if licenceEndDateTS < (time.time() + 60):
						valid = False
				except: valid = False
			if (valid and "exp" in userData and userData["exp"] > (time.time() + 60)):
				valid = True
			else: valid = False
		except: valid = False
		return valid

	@staticmethod
	def parseTokenConfig(config):
		postData = {}
		for pair in config.split(","):
			splittedPair = pair.split(":")
			postData[splittedPair[0]] = splittedPair[1].replace('"','')
		return postData

	@staticmethod
	def getConfig():
		baseEndPoint = base64.b64decode(b'aHR0cHM6Ly9wbHVzLnJ0bC5kZS8=').decode()
		headers = {"User-Agent": USERAGENT}
		req = Request(API.quoteUrl(baseEndPoint), headers=headers)
		query = urlopen(req)
		res = query.read().decode("UTF-8")
		try: jsName = re.findall(r'<script src="(main[A-z0-9\-\.]+\.js)"', res, re.S)[-1]
		except: return None
		req = Request(API.quoteUrl(baseEndPoint+jsName), headers=headers)
		query = urlopen(req)
		res = query.read().decode("UTF-8")
		return res

	@staticmethod
	def getClientVersion():
		baseEndPoint = base64.b64decode(b'aHR0cHM6Ly9wbHVzLnJ0bC5kZS9hc3NldHMvY29uZmlnL2NvbmZpZy5qc29u=').decode()
		headers = {"User-Agent": USERAGENT}
		req = Request(API.quoteUrl(baseEndPoint), headers=headers)
		query = urlopen(req)
		jsonData = json.loads(query.read())
		try: return jsonData["version"]
		except: return None

	@staticmethod
	def getClientID() -> str:
		text = API.getConfig()
		m = re.search(r'clientId:"([^"]+)"', text)
		if m: return m.group(1)
		return None

	def receiveToken(self) -> str:
		text = API.getConfig()
		m = re.search(r'anonymousCredentials:{([^}]+)}', text)
		if m:
			authURL = f'{API.AUTHBASEURL}/token'
			postData = API.parseTokenConfig(m.group(1))
			try:
				res = self.postRequest(authURL, postData=postData)
				jsonData = json.loads(res)
				token = jsonData.get("access_token")
			except RequestFailed: return None
			return token
		return None

	def renewToken(self):
		authURL = API.AUTHBASEURL + "/token?ngsw-bypass="
		postData = {
				"grant_type": "refresh_token",
				"refresh_token": self.refreshToken,
				"client_id": self.clientID
		}
		try:
			res = self.postRequest(authURL, postData=postData)
			jsonData = json.loads(res)
			token = jsonData.get("access_token")
			refresh_token = jsonData.get("refresh_token")
			idd_token = jsonData.get("id_token")
			return token, refresh_token, idd_token
		except RequestFailed: pass
		return None, None, None

	def getToken(self):
		return self.token

	def getRefreshToken(self):
		return self.refreshToken

	def getIdentToken(self):
		return self.iddToken

	def __init__(self, token=None, refresh_token=None, clientID=None, clientVersion=None):
		self.refreshToken = refresh_token
		self.token = None
		if clientID is None:
			self.clientID = API.getClientID()
		else:
			self.clientID = clientID
		if clientVersion is None:
			self.clientVersion = API.getClientVersion()
		else:
			self.clientVersion = clientVersion
		if token is None or token == "":
			token = self.receiveToken()
			if self.isTokenValid(token):
				self.token = token
		else:
			if self.isTokenValid(token):
				self.token = token
			elif self.refreshToken is not None and self.refreshToken != "":
				if self.isTokenValid(self.refreshToken):
					self.token, self.refreshToken, self.iddToken = self.renewToken()
					if self.token is None or self.refreshToken is None:
						raise TokenRenewFailed()
				else:
					raise TokenRenewFailed()
			else:
				self.token = self.receiveToken()
		if self.token is None:
			raise TokenNotFound()

	class Login():
		def __init__(self, api):
			self.api = api
			code_verifier = ''.join(random.SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(96))
			hashed = hashlib.sha256(code_verifier.encode('ascii')).digest()
			encoded = base64.urlsafe_b64encode(hashed)
			code_challenge = encoded.decode('ascii')[:-1]
			state = uuid.uuid4()
			nonce = uuid.uuid4()
			reqURL = f'{API.AUTHBASEURL}/auth?client_id={self.api.clientID}&redirect_uri={base64.b64decode(b"aHR0cHM6Ly9wbHVzLnJ0bC5kZS8=").decode()}&state={state}&response_mode=query&response_type=code&scope=openid email&nonce={nonce}&prompt=login&code_challenge={code_challenge}&code_challenge_method=S256'
			cj = CookieJar()
			self.opener = build_opener(HTTPCookieProcessor(cj))
			res = self.api.getRequest(reqURL, opener=self.opener)
			reqURL = re.findall('<form id="rtlplus-form-login" action="([^"]*)"', res.decode('UTF-8'))[0]
			self.loginURL = html.unescape(reqURL)
			self.code_verifier = code_verifier
			self.access_token = None
			self.refresh_token = None
			self.idd_token = None

		def sendLogin(self, username, password):
			if self.loginURL is None or self.api.clientID is None or self.code_verifier is None:
				return False
			data ={
				"credentialId": "",
				"rememberMe": "on",
				"username": username,
				"password": password
			}
			try: url = self.api.postRequest(self.loginURL, postData=data, opener=self.opener, getUrl=True)
			except: return False
			code = parse_qs(url)['code'][0]
			data = {
				"code": code,
				"grant_type": "authorization_code",
				"client_id": self.api.clientID,
				"redirect_uri": base64.b64decode(b'aHR0cHM6Ly9wbHVzLnJ0bC5kZS8=').decode(),
				"code_verifier": self.code_verifier
			}
			authURL = f'{API.AUTHBASEURL}/token?ngsw-bypass='
			try: res = self.api.postRequest(authURL, postData=data, opener=self.opener)
			except: return False

			try:
				data = json.loads(res)
				self.access_token = data["access_token"]
				self.refresh_token = data["refresh_token"]
				self.idd_token = data["id_token"]
				self.api.token = self.access_token
			except:
				failing(f"(api.sendLogin) ERROR - EXEPTION - ERROR ##### CONTENT_TEXT : {res.text} #####")
				return False
			return True

		def getAccessToken(self):
			return self.access_token

		def getRefreshToken(self):
			return self.refresh_token

		def getIdentToken(self):
			return self.idd_token

	def getWatchlist(self):
		watches = []
		requestURL = f'{API.APIBASEURL}?operationName=GetWatchFavorites&variables={{}}&extensions={{"persistedQuery":{{"version":1,"sha256Hash":"{API.ENDPOINTS["GetWatchFavorites"]}"}}}}'
		res = self.getRequest(requestURL, self.token)
		jsonData = json.loads(res)
		debug_MS(f"(api.getWatchlist[1]) ### JSON_RESULTS : {jsonData} ###")
		for element in APIParser.getElement(jsonData, ["data", "getFavoritesV3", "elements"]):
			watches.append(element)
		return watches

	def modifyWatchlist(self, wish_id, action):
		data = {
			"operationName": action,
			"variables": {
				"id": wish_id
			},
			"extensions": {
				"persistedQuery": {
					"version": 1,
					"sha256Hash": API.ENDPOINTS[action]
					}
				}
			}
		try:
			self.postRequest(API.APIBASEURL, jsonData=data, token=self.token, client=True)
			return True
		except: return False

	def getFavorites(self, cache_data=None):
		besties = []
		if cache_data is None:
			requestURL = f'{API.APIBASEURL}?operationName=WatchFavoriteIds&variables={{}}&extensions={{"persistedQuery":{{"version":1,"sha256Hash":"{API.ENDPOINTS["WatchFavoriteIds"]}"}}}}'
			res = self.getRequest(requestURL, self.token)
			jsonData = json.loads(res)
			debug_MS(f"(api.getFavorites[1]) ### JSON_RESULTS : {jsonData} ###")
			elements = APIParser.getElement(jsonData, ["data", "getFavoritesV3", "elements"], True)
			if elements is not None:
				for element in elements:
					if element.get('__typename') in ['Movie', 'Format'] and element.get('id'):
						besties.append(element['id'])
			if besties: preserve(FAVORIT_FILE, 1, besties)
		else: besties = cache_data
		return besties

	def getOverviews(self, case_id, cache_file, offset=0, limit=50, cache_data=None):
		jsonData, (RECORD, stature) = {}, ([] for _ in range(2))
		if cache_data is None:
			while True:
				requestURL = f'{API.APIBASEURL}?operationName=OverviewPage&variables={{"pagination":{{"offset":{offset},"limit":{limit}}},"filter":{{"elementType":"{case_id}"}}}}&extensions={{"persistedQuery":{{"version":1,"sha256Hash":"{API.ENDPOINTS["OverviewPage"]}"}}}}'
				res = self.getRequest(requestURL, self.token)
				jsonData = json.loads(res)
				stature += APIParser.getElement(jsonData, ["data", "watchOverviewPage", "elements"])
				totalItems = APIParser.getElement(jsonData, ["data", "watchOverviewPage", "pageInfo", "totalCount"])
				offset += limit
				RECORD.append(jsonData)
				if offset >= totalItems:
					break
			if RECORD: preserve(cache_file, 12, RECORD)
		else:
			for result in cache_data:
				stature += APIParser.getElement(result, ["data", "watchOverviewPage", "elements"])
		return stature

	def getSeasons(self, series_id):
		requestURL = f'{API.APIBASEURL}?operationName=Format&variables={{"id":"{series_id}"}}&extensions={{"persistedQuery":{{"version":1,"sha256Hash":"{API.ENDPOINTS["Format"]}"}}}}'
		res = self.getRequest(requestURL, self.token)
		jsonData = json.loads(res)
		debug_MS(f"(api.getSeasons[1]) ### JSON_RESULTS : {jsonData} ###")
		return APIParser.getElement(jsonData, ["data", "format"])

	def getEpisodes(self, season_id, offset=0, limit=30):
		episodes = []
		while True:
			requestURL = f'{API.APIBASEURL}?operationName=SeasonWithFormatAndEpisodes&variables={{"seasonId":"{season_id}","offset":{offset},"limit":{limit}}}&extensions={{"persistedQuery":{{"version":1,"sha256Hash":"{API.ENDPOINTS["SeasonWithFormatAndEpisodes"]}"}}}}'
			res = self.getRequest(requestURL, self.token)
			jsonData = json.loads(res)
			debug_MS(f"(api.getEpisodes[1]) ### JSON_RESULTS : {jsonData} ###")
			episodes += APIParser.getElement(jsonData, ["data", "season", "episodes"])
			totalItems = APIParser.getElement(jsonData, ["data", "season", "numberOfEpisodes"])
			offset += limit
			if offset >= totalItems:
				break
		return episodes

	def getMovies(self, movie_id):
		requestURL = f'{API.APIBASEURL}?operationName=MovieDetail&variables={{"id":"{movie_id}" }}&extensions={{"persistedQuery":{{"version":1,"sha256Hash":"{API.ENDPOINTS["MovieDetail"]}"}}}}'
		res = self.getRequest(requestURL, self.token)
		jsonData = json.loads(res)
		debug_MS(f"(api.getMovies[1]) ### JSON_RESULTS : {[jsonData]} ###")
		return [jsonData]

	def getStations(self, case_id):
		stations = []
		requestURL = f'{API.APIBASEURL}?operationName=LiveTvStations&variables={{"epgCount":4,"filter":{{"channelTypes":["{case_id}"]}}}}&extensions={{"persistedQuery":{{"version":1,"sha256Hash":"{API.ENDPOINTS["LiveTvStations"]}"}}}}'
		res = self.getRequest(requestURL, self.token)
		jsonData = json.loads(res)
		debug_MS(f"(api.getStations[1]) ### JSON_RESULTS : {jsonData} ###")
		elements = APIParser.getElement(jsonData, ["data", "liveTvStations"])
		for channel in elements:
			stations.append(APIParser.parseLiveChannel(channel))
		return stations

	def getEvents(self):
		requestURL = f'{API.APIBASEURL}?operationName=LiveEventsOverviewPage&variables={{}}&extensions={{"persistedQuery":{{"version":1,"sha256Hash":"{API.ENDPOINTS["LiveEventsOverviewPage"]}"}}}}'
		res = self.getRequest(requestURL, self.token)
		jsonData = json.loads(res)
		debug_MS(f"(api.getEvents[1]) ### JSON_RESULTS : {jsonData} ###")
		return APIParser.getElement(jsonData, ["data", "liveEventsOverview", "teaserRows"])

	def getRecommendOverview(self, offset=0, take=50, cache_data=None):
		jsonData = {}
		if cache_data is None:
			requestURL = f'{API.APIBASEURL}?operationName=ExploreWidgetWatch&variables={{"area":"home","offset":{offset},"take":{take}}}&extensions={{"persistedQuery":{{"version":1,"sha256Hash":"{API.ENDPOINTS["ExploreWidgetWatch"]}"}}}}'
			res = self.getRequest(requestURL, self.token)
			jsonData = json.loads(res)
			preserve(RECOM_FILE, 12, jsonData)
		else: jsonData = cache_data
		return APIParser.getElement(jsonData, ["data", "editorialView", "widgets", "items"])

	def getTopicworldOverview(self, offset=0, take=50):
		requestURL = f'{API.APIBASEURL}?operationName=TopicWorlds&variables={{"offset":{offset},"take":{take}}}&extensions={{"persistedQuery":{{"version":1,"sha256Hash":"{API.ENDPOINTS["TopicWorlds"]}"}}}}'
		res = self.getRequest(requestURL, self.token)
		jsonData = json.loads(res)
		debug_MS(f"(api.getTopicworldOverview[1]) ### JSON_RESULTS : {jsonData} ###")
		return APIParser.getElement(jsonData, ["data", "topicWorlds", "elements"])

	def getRecommendsContent(self, trace_id, offset=0, take=50, cache_data=None):
		jsonData, recoms = {}, []
		if cache_data is None:
			requestURL = f'{API.APIBASEURL}?operationName=ExploreWidgetWatch&variables={{"area":"home","offset":{offset},"take":{take}}}&extensions={{"persistedQuery":{{"version":1,"sha256Hash":"{API.ENDPOINTS["ExploreWidgetWatch"]}"}}}}'
			res = self.getRequest(requestURL, self.token)
			jsonData = json.loads(res)
			preserve(RECOM_FILE, 12, jsonData)
		else: jsonData = cache_data
		debug_MS(f"(api.getRecommendContent[1]) ### JSON_RESULTS : {jsonData} ###")
		for result in APIParser.getElement(jsonData, ["data", "editorialView", "widgets", "items"]):
			id = APIParser.getElement(result, ["id"], True)
			if id == trace_id:
				elements = APIParser.getElement(result, ["elements"])
				for element in elements:
					if element is None:
						continue
					recoms.append(element)
		return recoms

	def getTopicworldsContent(self, path_id, case_id=None):
		topics = []
		requestURL = f'{API.APIBASEURL}?operationName=topicWorldByPathV2&variables={{"path":"{path_id}"}}&extensions={{"persistedQuery":{{"version":1,"sha256Hash":"{API.ENDPOINTS["topicWorldByPathV2"]}"}}}}'
		res = self.getRequest(requestURL, self.token)
		jsonData = json.loads(res)
		debug_MS(f"(api.getTopicworldsContent[1]) ### JSON_RESULTS : {jsonData} ###")
		if case_id == 'sports_folder':
			return APIParser.getElement(jsonData, ["data", "topicWorldByPathV2", "content", "elements"])
		for result in APIParser.getElement(jsonData, ["data", "topicWorldByPathV2", "content", "elements"]):
			elements = APIParser.getElement(result, ["content", "items"], True)
			if not elements: continue
			for element in elements:
				if element is None: continue
				topics.append(element)
		return topics

	def getSearchResults(self, query):
		search = []
		requestURL = f'{API.APIBASEURL}?operationName=Search&variables={{"input":{{"term":"{query}","scopes":["WATCH","MUSIC","MAGAZINE"]}}}}&extensions={{"persistedQuery":{{"version":1,"sha256Hash":"{API.ENDPOINTS["Search"]}"}}}}'
		res = self.getRequest(requestURL, self.token)
		jsonData = json.loads(res)
		debug_MS(f"(api.getSearchResults[1]) ### JSON_RESULTS : {jsonData} ###")
		for result in APIParser.getElement(jsonData, ["data", "search", "result"]):
			titleKey = APIParser.getElement(result, ["titleKey"])
			if titleKey == 'WATCH':
				elements = APIParser.getElement(result, ["items"])
				for element in elements:
					search.append(element)
		return search

	def _parseManifest(self, jsonData):
		playbackURL, licenseURL = (None for _ in range(2))
		sources = APIParser.getElement(jsonData, ["sources"])
		if sources is not None:
			for source in sources:
				playbackURL = APIParser.getElement(source, ["url"])
				isYospace = APIParser.getElement(source, ["isYospace"])
				if isYospace is None or isYospace is False:
					break
		if playbackURL is None:
			playbackURL = APIParser.getElement(jsonData, ["videoUrl"])
		for licenseAdapt in APIParser.getElement(jsonData, ["licenses"]):
			licenseType = APIParser.getElement(licenseAdapt, ["type"])
			if licenseType == 'WIDEVINE':
				if 'uri' in licenseAdapt and 'href' in str(APIParser.getElement(licenseAdapt, ["uri"])):
					licenseURL = APIParser.getElement(licenseAdapt, ["uri", "href"])
				else:
					licenseURL = APIParser.getElement(licenseAdapt, ["licenseUrl"])
		#log(f"(api._parseManifest[2]) ### PLAYBACK_URL : {playbackURL} || LICENSE_URL : {licenseURL} ###")
		return playbackURL, licenseURL

	def getPlaybackURL(self, play_id, best_qualtiy):
		STUSPLAY, (playbackURL, licenseURL) = False, (None for _ in range(2))
		if ':station:' in play_id or ':live-events:' in play_id: # /livestream/linear/rrn:watch:videohub:station:1?platform=web
			play_mode = 'livestream/linear' if ':station:' in play_id else 'liveevent' # /liveevent/rrn:watch:live-events:american-football:974807?platform=web
			QUERY, CLIENT, PREFIX, STUSPLAY = f'{API.APISTUSURL}/{play_mode}/{play_id}?platform=web', False, 'x-auth-token', True
		elif ':fast_station:' in play_id or ':episode:' in play_id or ':movie:' in play_id: # /fast-playout-variants/rrn:watch:videohub:fast_station:60?platform=web // Geändert 03.04.25
			play_mode = 'fast' if ':fast_station:' in play_id else 'watch' # /watch-playout-variants/rrn:watch:videohub:episode:984787?platform=web // Geändert 03.04.25
			QUERY, CLIENT, PREFIX, STUSPLAY = f'{API.APISTUSURL}/{play_mode}-playout-variants/{play_id}?platform=web', False, 'x-auth-token', True
		else:
			QUERY, CLIENT, PREFIX = f'{API.APIBASEURL}?operationName=WatchPlayerConfigV3&variables={{"platform":"WEB","id": "{play_id}" }}&extensions={{"persistedQuery":{{"version":1,"sha256Hash":"{API.ENDPOINTS["WatchPlayerConfigV3"]}"}}}}', True, 'Authorization'
		res = self.getRequest(QUERY, self.token, client=CLIENT, prefix=PREFIX)
		jsonData = json.loads(res)
		playoutVariants = APIParser.getElement(jsonData, []) if any(tag in play_id for tag in [':station:', ':live-events:', ':fast_station:', ':episode:', ':movie:']) else APIParser.getElement(jsonData, ["data", "watchPlayerConfigV3", "playoutVariants"])
		for variant in playoutVariants:
			debug_MS(f"(api.getPlaybackURL[1]) ### VARIANTS : {variant} ###")
			manifestType = APIParser.getElement(variant, ["name"]) if STUSPLAY is True else APIParser.getElement(variant, ["type"])
			if manifestType == 'dashhd':
				playbackURL, licenseURL = self._parseManifest(variant)
				if best_qualtiy:
					break
			if manifestType in ['dashsd', 'dashfree']: # Für Event-Streams gibt es auch'dashfree', sonst'dashsd' als Eintrag (23.11.2024)
				playbackURL, licenseURL = self._parseManifest(variant)
				if best_qualtiy is False:
					break
		debug_MS(f"(api.getPlaybackURL[2]) ### BEST_QUALITY : {best_qualtiy} || PLAYBACK_URL : {playbackURL} || LICENSE_URL : {licenseURL} ###")
		return playbackURL, licenseURL
