import base64
import re
from json import loads as jsloads
import requests
from urllib.parse import quote, urlparse
from alko import client, source_utils
from alko.control import setting as getSetting


class source:
	timeout = 7
	priority = 1
	pack_capable = False # packs parsed in sources function
	hasMovies = True
	hasEpisodes = True
	def _manifest_info(self, manifest_url):
		if not manifest_url: return None
		try:
			if '://' not in manifest_url:
				manifest_url = 'https://%s' % manifest_url
			parsed = urlparse(manifest_url)
			if not parsed.scheme or not parsed.netloc: return None
			path = parsed.path or ''
			if path.endswith('/manifest.json'):
				path = path[:-len('/manifest.json')]
			marker = '/stremio/'
			marker_index = path.find(marker)
			if marker_index == -1:
				base_path = path.rstrip('/')
				base_url = '%s://%s%s' % (parsed.scheme, parsed.netloc, base_path)
				return {'base_url': base_url.rstrip('/'), 'uuid': None, 'password': None}
			base_path = path[:marker_index]
			base_url = '%s://%s%s' % (parsed.scheme, parsed.netloc, base_path)
			remaining = path[marker_index + len(marker):].lstrip('/')
			parts = [p for p in remaining.split('/') if p]
			uuid = password = None
			if len(parts) >= 2 and parts[0] != 'u':
				uuid, password = parts[0], parts[1]
			return {'base_url': base_url.rstrip('/'), 'uuid': uuid, 'password': password}
		except:
			return None
	def _stream_base_from_manifest(self, manifest_url):
		if not manifest_url: return None
		try:
			if '://' not in manifest_url:
				manifest_url = 'https://%s' % manifest_url
			parsed = urlparse(manifest_url)
			if not parsed.scheme or not parsed.netloc: return None
			path = parsed.path or ''
			for suffix in ('/manifest.json', '/manifest'):
				if path.endswith(suffix):
					path = path[:-len(suffix)]
					break
			base_url = '%s://%s%s' % (parsed.scheme, parsed.netloc, path)
			return base_url.rstrip('/')
		except:
			return None
	def _normalize_base_link_for_catalogue_match(self, value):
		try:
			value = str(value or '').strip()
			if not value: return ''
			base = self._stream_base_from_manifest(value) or value.rstrip('/')
			return base.lower().rstrip('/')
		except:
			return ''

	def _catalogue_id_belongs_to_this_manifest(self, catalogue_id='', catalogue_base_url='', catalogue_video_id=False):
		# Un ID interne de catalogue ne doit être envoyé à AIOStreams que si
		# AIOStreams pointe vers le même manifest que le catalogue d'origine.
		# Sinon un ID fs:/catalogue interne peut empêcher AIOStreams de chercher
		# avec l'IMDb/TMDb standard et masquer les résultats du manifest.
		catalogue_id = str(catalogue_id or '').strip()
		if not catalogue_id: return False
		catalogue_base = self._normalize_base_link_for_catalogue_match(catalogue_base_url)
		provider_base = self._normalize_base_link_for_catalogue_match(getattr(self, 'stream_base', '') or getattr(self, 'base_link', ''))
		if catalogue_base and provider_base:
			return catalogue_base == provider_base
		if catalogue_id.lower().startswith('fs:'):
			return provider_base and ('french-stream' in provider_base or 'frenchstream' in provider_base)
		return True

	def _standard_request_id(self, imdb='', tmdb=''):
		imdb = str(imdb or '').strip()
		tmdb = str(tmdb or '').strip()
		if imdb: return imdb
		if tmdb:
			return tmdb if tmdb.lower().startswith('tmdb:') else 'tmdb:%s' % tmdb
		return ''
	def _stream_url(self, imdb, season=None, episode=None, full_episode_id=False):
		if not self.stream_base: return None
		# Encodage complet de l'ID : certains catalogues utilisent des IDs internes
		# contenant ':'; Stremio les encode avant d'ajouter :season:episode.
		stream_id = quote(str(imdb), safe='')
		if season is not None and episode is not None:
			if full_episode_id:
				return '%s/stream/series/%s.json' % (self.stream_base, stream_id)
			return '%s%s' % (self.stream_base, self.tvStream_link % (stream_id, season, episode))
		return '%s%s' % (self.stream_base, self.movieStream_link % stream_id)
	def _release_title_from_stream(self, stream, meta=None, behavior=None):
		meta = meta or stream.get('meta') or {}
		behavior = behavior or stream.get('behaviorHints') or {}
		release_title = meta.get('originalTitle') or meta.get('title') or meta.get('name') or ''
		if not release_title:
			release_title = behavior.get('filename') or ''
		if not release_title:
			for key in ('title', 'description'):
				text = stream.get(key) or ''
				if not text: continue
				text = text.replace('\u2508\u27a4', '\n')
				match = re.search(r'([A-Za-z0-9].*?\.(?:mkv|mp4|avi|m2ts|m4v|ts))', text, re.IGNORECASE)
				if match:
					return match.group(1).strip()
				line = text.split('\n')[0].strip()
				if line:
					release_title = line
					break
		if not release_title:
			release_title = stream.get('name') or ''
		return release_title
	def _indexer_from_description(self, description):
		if not description: return None
		text = description.replace('\u2508\u27a4', '\n')
		match = re.search('[\U0001F50D\U0001F50E]\\s*([^\\n\\r]+)', text)
		if not match: return None
		value = match.group(1).strip()
		return value or None
	def _known_tracker_label(self, text):
		try:
			if not text: return None
			value = str(text).lower()
			for old, new in (('é', 'e'), ('è', 'e'), ('ê', 'e'), ('ë', 'e'), ('à', 'a'), ('â', 'a'), ('î', 'i'), ('ï', 'i'), ('ô', 'o'), ('ù', 'u'), ('û', 'u'), ('ç', 'c')):
				value = value.replace(old, new)
			compact = re.sub(r'[^a-z0-9]+', ' ', value).strip()
			if re.search(r'\bfree\s*telecharg', compact): return 'Free-Telecharger'
			if re.search(r'\bwawa\s*city\b|\bwawacity\b', compact): return 'Wawacity'
			if re.search(r'\bdarki\s*api\b|\bdarkiapi\b', compact): return 'Hydracker'
			if re.search(r'\bwa\s*source\b|\bwasource\b', compact): return 'WASource'
			if re.search(r'\bmovix\b', compact): return 'Movix'
			if re.search(r'\bc\s*411\b|\bc411\b', compact): return 'C411'
			if re.search(r'\btorr(?:ent)?\s*9\b|\btorr9\b|\btorrent9\b', compact): return 'Torr9'
			if re.search(r'\bygg(?:torrent)?\b', compact): return 'YGG'
			if re.search(r'\bthe\s*old\s*school\b|\bold\s*school\b|\btos\b', compact): return 'The Old School'
			if re.search(r'\bla\s*cale\b|\blacale\b', compact): return 'La Cale'
			if re.search(r'\bgeneration\s*free\b|\bgenerationfree\b|\bgen\s*free\b', compact): return 'Generation-Free'
			if re.search(r'\btr4ker(?:\s*net)?\b', compact): return 'TR4KER'
			if re.search(r'\bg3\s*mini\s*(?:track3r|tracker)?\b|\bg3mini\s*(?:track3r|tracker)?\b', compact): return 'G3MINI TRACK3R'
		except:
			pass
		return None

	def _clean_tracker_label(self, value):
		try:
			if value in ('', None): return None
			value = str(value).replace('\u2508\u27a4', ' ')
			for symbol in ('🔍', '🔎', '☁', '📦', '🌎', '📁', '🎥', '🎞', '🏷'):
				value = value.replace(symbol, ' ')
			value = re.sub(r'\s+', ' ', value).strip(' -|,;')
			known = self._known_tracker_label(value)
			if known: return known
			return value or None
		except:
			return None

	def _tracker_from_stream(self, stream, meta=None, behavior=None):
		meta = meta or stream.get('meta') or {}
		behavior = behavior or stream.get('behaviorHints') or {}
		# Les manifests WA/Stremio indiquent parfois le vrai site dans la description
		# avec l'icône de recherche, pendant que le champ addon reste générique.
		# On lit donc d'abord le texte visible par Stremio pour éviter d'afficher
		# Wawacity à la place de Free-Telecharger.
		for text in (
			stream.get('description'), stream.get('title'), stream.get('name'),
			behavior.get('filename'), stream.get('folderName'), stream.get('filename'),
			meta.get('indexer'), meta.get('tracker'), meta.get('site'), meta.get('source')
		):
			known = self._known_tracker_label(text)
			if known: return known
		for text in (stream.get('description'), stream.get('title'), stream.get('name'), behavior.get('filename')):
			tracker = self._clean_tracker_label(self._indexer_from_description(text or ''))
			if tracker: return tracker
		for value in (
			meta.get('indexer'), meta.get('tracker'), meta.get('site'), meta.get('source'),
			stream.get('indexer'), stream.get('tracker'), stream.get('site'), stream.get('source'),
			behavior.get('indexer'), behavior.get('tracker'), behavior.get('site'), behavior.get('source'),
			stream.get('addon'), stream.get('name')
		):
			tracker = self._clean_tracker_label(value)
			if tracker: return tracker
		return None

	def _known_aio_manifest_label(self, text):
		# Libellé court du manifest d'origine quand AIOStreams agrège plusieurs sources.
		# Sert uniquement à l'affichage dans le sélecteur; les champs métier restent inchangés.
		try:
			if not text: return None
			value = str(text).lower()
			for old, new in (('é', 'e'), ('è', 'e'), ('ê', 'e'), ('ë', 'e'), ('à', 'a'), ('â', 'a'), ('î', 'i'), ('ï', 'i'), ('ô', 'o'), ('ù', 'u'), ('û', 'u'), ('ç', 'c')):
				value = value.replace(old, new)
			compact = re.sub(r'[^a-z0-9]+', ' ', value).strip()
			if re.search(r'\bbaguettio\b|\bbgt\b', compact): return 'BGT'
			if re.search(r'\bwa\s*stream\b|\bwastream\b|\bwawacity\b|\bwawa\s*city\b|\bfree\s*telecharg|\bdarki\s*api\b|\bdarkiapi\b|\bwa\s*source\b|\bwasource\b|\bmovix\b', compact): return 'WA'
			if re.search(r'\bstream\s*fusion\b|\bstreamfusion\b|\bsf\b', compact): return 'SF'
			if re.search(r'\bcomet\b', compact): return 'COMET'
			if re.search(r'\btorrentio\b', compact): return 'TORR'
			if re.search(r'\bmedia\s*fusion\b|\bmediafusion\b', compact): return 'MF'
			if re.search(r'\bstrem\s*thru\b|\bstremthru\b|\btorz\b', compact): return 'STZ'
		except:
			pass
		return None

	def _aio_manifest_from_stream(self, stream, meta=None, behavior=None, tracker=None):
		# AIOStreams peut masquer le manifest réel derrière le statut AD/DB.
		# On garde ce libellé séparé pour l'UI afin d'afficher BGT/WA/SF/etc.
		try:
			meta = meta or stream.get('meta') or {}
			behavior = behavior or stream.get('behaviorHints') or {}
			for value in (
				meta.get('addon'), meta.get('addonName'), meta.get('provider'), meta.get('source'),
				stream.get('addon'), stream.get('addonName'), stream.get('provider'), stream.get('source'),
				stream.get('name'), stream.get('title'), tracker,
				behavior.get('addon'), behavior.get('provider'), behavior.get('source'), behavior.get('filename'),
				stream.get('description')
			):
				label = self._known_aio_manifest_label(value)
				if label: return label
		except:
			pass
		return None

	def _debrid_from_url(self, url):
		if not url: return None
		text = url.lower()
		if 'real-debrid' in text or 'realdebrid' in text: return 'RD'
		if 'torbox' in text: return 'TB'
		try:
			parsed = urlparse(text)
			host = parsed.netloc or ''
			path = parsed.path or ''
		except:
			host, path = '', text
		if re.search(r'/(rd)(/|$)', path) or re.search(r'(^|[./-])rd([./-]|$)', host):
			return 'RD'
		if re.search(r'/(tb)(/|$)', path) or re.search(r'(^|[./-])tb([./-]|$)', host):
			return 'TB'
		return None
	def _append_debrid_label(self, tracker, debrid_label):
		if not debrid_label: return tracker
		if tracker:
			if re.search(r'\\b%s\\b' % re.escape(debrid_label), tracker, re.I):
				return tracker
			return '%s %s' % (tracker, debrid_label)
		return debrid_label
	def __init__(self):
		self.language = ['en', 'fr']
		self.authorization = None
		manifest_url = getSetting('aiostreams_custom.manifest_url', '').strip()
		if not manifest_url:
			manifest_url = getSetting('aiostreams.manifest_url', '').strip()
		manifest = self._manifest_info(manifest_url) if manifest_url else None
		if manifest and manifest.get('base_url'):
			self.base_link = manifest['base_url']
			if manifest.get('uuid') and manifest.get('password'):
				credentials = '%s:%s' % (manifest['uuid'], manifest['password'])
				basic = base64.b64encode(credentials.encode('utf-8')).decode('utf-8')
				self.authorization = 'Basic %s' % basic
		else:
			custom_link = getSetting('aiostreams_custom.custom_url', '').strip()
			if not custom_link:
				custom_link = getSetting('aiostreams.custom_url', '').strip()
			self.base_link = custom_link.rstrip('/') if custom_link else None
		use_stremio_api = getSetting('aiostreams_custom.use_stremio_api', '').strip()
		if use_stremio_api == '':
			use_stremio_api = getSetting('aiostreams.use_stremio_api', 'false')
		self.use_stremio_api = use_stremio_api == 'true'
		self.stream_base = self._stream_base_from_manifest(manifest_url) if manifest_url else None
		if not self.stream_base:
			self.stream_base = self.base_link
		self.movieSearch_link = '/api/v1/search'
		self.tvSearch_link = '/api/v1/search'
		self.movieStream_link = '/stream/movie/%s.json'
		self.tvStream_link = '/stream/series/%s:%s:%s.json'
		self.min_seeders = 0
		prefer_debrid = getSetting('aiostreams_custom.prefer_debrid', '').strip()
		if prefer_debrid == '':
			prefer_debrid = getSetting('aiostreams.prefer_debrid', 'true')
		self.prefer_debrid = prefer_debrid == 'true'
		if self.use_stremio_api:
			self.prefer_debrid = False

	def sources(self, data, hostDict):
		sources = []
		if not data: return sources
		if self.use_stremio_api:
			if not self.stream_base: return sources
		else:
			if not self.base_link: return sources
		sources_append = sources.append
		try:
			title = data['tvshowtitle'] if 'tvshowtitle' in data else data['title']
			title = title.replace('&', 'and').replace('Special Victims Unit', 'SVU').replace('/', ' ').replace(':', ' ')
			title = re.sub(r'\s+', ' ', title).strip()
			aliases = data['aliases']
			episode_title = data['title'] if 'tvshowtitle' in data else None
			total_seasons = data['total_seasons'] if 'tvshowtitle' in data else None
			year = data['year']
			imdb = data.get('imdb')
			catalogue_id = data.get('catalogue_id')
			catalogue_base_url = data.get('catalogue_base_url') or ''
			catalogue_video_id = data.get('catalogue_video_id') is True
			if catalogue_id is not None:
				catalogue_id = str(catalogue_id).strip()
				if catalogue_id.lower() in ('', 'none', 'null', 'false', '0'):
					catalogue_id = ''
			else:
				catalogue_id = ''
			if imdb is not None:
				imdb = str(imdb).strip()
				if imdb.lower() in ('', 'none', 'null', 'false', '0'):
					imdb = ''
			else:
				imdb = ''
			tmdb = data.get('tmdb')
			if tmdb is not None:
				tmdb = str(tmdb).strip()
				if tmdb.lower() in ('', 'none', 'null', 'false', '0'):
					tmdb = ''
			else:
				tmdb = ''
			use_catalogue_id = self._catalogue_id_belongs_to_this_manifest(catalogue_id, catalogue_base_url, catalogue_video_id)
			if 'tvshowtitle' in data:
				season = data['season']
				episode = data['episode']
				hdlr = 'S%02dE%02d' % (int(season), int(episode))
				# Les catalogues Stremio peuvent utiliser leur propre identifiant interne
				# pour les épisodes. Si cet identifiant est présent, il doit être
				# transmis au manifest avant l'IMDb, comme le fait Stremio.
				# L'identifiant doit être encodé entièrement, car certains IDs internes
				# contiennent des ':'; sinon le manifest reçoit un ID tronqué.
				request_id = catalogue_id if use_catalogue_id else self._standard_request_id(imdb, tmdb)
				if not request_id: return sources
				if self.use_stremio_api:
					url = self._stream_url(request_id, season, episode, catalogue_video_id)
				else:
					url = '%s%s' % (self.base_link, self.tvSearch_link)
					if catalogue_video_id:
						params = {'type': 'series', 'id': '%s' % request_id}
					else:
						params = {'type': 'series', 'id': '%s:%s:%s' % (request_id, season, episode)}
			else:
				hdlr = year
				request_id = catalogue_id if use_catalogue_id else self._standard_request_id(imdb, tmdb)
				if not request_id: return sources
				if self.use_stremio_api:
					url = self._stream_url(request_id)
				else:
					url = '%s%s' % (self.base_link, self.movieSearch_link)
					params = {'type': 'movie', 'id': '%s' % request_id}
			# log_utils.log('url = %s' % url)
			if 'timeout' in data: self.timeout = int(data['timeout'])
			if self.use_stremio_api:
				if not url: return sources
				results = client.request(url, headers=self._headers(), timeout=self.timeout)
				files = jsloads(results).get('streams') or [] if results else []
			else:
				results = requests.get(url, params=params, headers=self._headers(), timeout=self.timeout)
				files = results.json()['data']['results']
			# Les résultats AIOStreams doivent respecter la configuration du manifest.
			# Aucun filtre de langue alkoFlix n'est appliqué ici.
		except:
			source_utils.scraper_error('AIOSTREAMS_CUSTOM')
			return sources

		if self.use_stremio_api:
			for file in files:
				try:
					package, episode_start = None, 0
					meta = file.get('meta') or {}
					behavior = file.get('behaviorHints') or {}
					direct_url = file.get('url')
					info_hash = file.get('infoHash') or file.get('infohash') or file.get('info_hash')
					raw_info_hash = info_hash
					source_type = (meta.get('type') or file.get('type') or '').lower()
					if source_type == 'nzb': source_type = 'usenet'
					if source_type == 'http': source_type = 'direct'
					if direct_url and str(direct_url).lower().startswith('magnet:'):
						source_type = 'torrent'
					if not source_type:
						source_type = 'torrent' if info_hash else 'direct'
					use_direct = bool(direct_url) and not str(direct_url).lower().startswith('magnet:')
					if self.prefer_debrid and info_hash and source_type == 'torrent':
						use_direct = False
					if not info_hash and not use_direct and direct_url:
						match = re.search(r'btih:([A-Fa-f0-9]{40})', direct_url)
						if match: info_hash = match.group(1)
					info_hash = None if use_direct else info_hash

					release_title = self._release_title_from_stream(file, meta, behavior)
					name = source_utils.clean_name(release_title)
					if not name: continue

					if total_seasons is not None:
						valid, last_season = source_utils.filter_show_pack(title, aliases, imdb, year, season, name, total_seasons)
						if valid:
							package = 'show'
						else:
							valid, episode_start, episode_end = source_utils.filter_season_pack(title, aliases, year, season, name)
							if valid: package = 'season'
					name_info = source_utils.info_from_name(name, title, year, hdlr, episode_title)
					name_info = re.sub(r'\b4khdhub(?:\.com)?\b', '', name_info, flags=re.I)
					name_info = re.sub(r'\.{2,}', '.', name_info)
					language = source_utils.release_language(name_info)

					if use_direct:
						if not direct_url: continue
						url = direct_url
					else:
						if not info_hash: continue
						url = 'magnet:?xt=urn:btih:%s&dn=%s' % (info_hash, name)

					seeders = 0

					quality, info = source_utils.get_release_quality(name_info, url)
					dsize = 0
					try:
						size_bytes = float(meta.get('size') or behavior.get('videoSize') or 0)
						if size_bytes > 0:
							size = f"{size_bytes / 1073741824:.2f} GB"
							dsize, isize = source_utils._size(size)
							info.insert(0, isize)
					except:
						dsize = 0
					info = ' | '.join(info)

					if use_direct:
						item = {
							'source': source_type or 'direct', 'language': language, 'direct': True, 'debridonly': False,
							'provider': 'aiostreams_custom', 'url': url, 'name': name, 'name_info': name_info,
							'quality': quality, 'info': info, 'size': dsize, 'seeders': seeders,
							'aiostreams_direct': True, 'debrid': 'aiostreams', 'bypass_filter': True, 'true_size': True,
							'provider_name': 'AIOStreams Custom'
						}
					else:
						item = {
							'source': 'torrent', 'language': language, 'direct': False, 'debridonly': True,
							'provider': 'aiostreams_custom', 'hash': info_hash, 'url': url, 'name': name, 'name_info': name_info,
							'quality': quality, 'info': info, 'size': dsize, 'seeders': seeders, 'bypass_filter': True, 'true_size': True,
							'provider_name': 'AIOStreams Custom'
						}
					tracker = self._tracker_from_stream(file, meta, behavior)
					if source_type == 'usenet' and not tracker: tracker = 'usenet'
					if use_direct and source_type == 'debrid' and not raw_info_hash:
						debrid_label = self._debrid_from_url(direct_url)
						tracker = self._append_debrid_label(tracker, debrid_label)
					manifest_label = self._aio_manifest_from_stream(file, meta, behavior, tracker)
					if manifest_label: item['aio_manifest'] = manifest_label
					if tracker:
						if not use_direct:
							tracker = 'AIO | %s' % tracker if not tracker.upper().startswith('AIO | ') else tracker
						item['tracker'] = tracker
					if package: item['package'] = package
					if package == 'show': item.update({'last_season': last_season})
					if episode_start: item.update({'episode_start': episode_start, 'episode_end': episode_end}) # for partial season packs
					sources_append(item)
				except:
					source_utils.scraper_error('AIOSTREAMS_CUSTOM')
			return sources

		for file in files:
			try:
				package, episode_start = None, 0
				direct_url = file.get('url')
				use_direct = bool(direct_url) and not direct_url.lower().startswith('magnet:')
				source_type = (file.get('type') or 'direct').lower()
				tracker = self._tracker_from_stream(file)
				if not tracker:
					if self.prefer_debrid and source_type == 'usenet':
						indexer = file.get('indexer')
						addon = file.get('addon')
						if indexer and addon:
							tracker = '%s - %s' % (indexer, addon)
						else:
							tracker = indexer or addon
					else:
						tracker = file.get('addon') or file.get('indexer') or file.get('tracker')
				if source_type == 'usenet' and not tracker: tracker = 'usenet'
				hash = file.get('infoHash') or file.get('infohash') or file.get('info_hash') or file.get('hash')
				raw_hash = hash
				if not hash and direct_url:
					match = re.search(r'btih:([A-Fa-f0-9]{40})', direct_url)
					if match:
						hash = match.group(1)
					else:
						match = re.search(r'\b([A-Fa-f0-9]{40})\b', direct_url)
						if match: hash = match.group(1)
				if self.prefer_debrid and hash:
					use_direct = False
				hash = None if use_direct else hash
				if use_direct and source_type == 'debrid' and not raw_hash:
					debrid_label = self._debrid_from_url(direct_url)
					tracker = self._append_debrid_label(tracker, debrid_label)
				file_title = (file['folderName'] or file['filename']).replace('┈➤', '\n').split('\n')

				name = source_utils.clean_name(file_title[0])

				if total_seasons is not None:
					valid, last_season = source_utils.filter_show_pack(title, aliases, imdb, year, season, name, total_seasons)
					if valid:
						package = 'show'
					else:
						valid, episode_start, episode_end = source_utils.filter_season_pack(title, aliases, year, season, name)
						if valid: package = 'season'
				name_info = source_utils.info_from_name(name, title, year, hdlr, episode_title)
				name_info = re.sub(r'\b4khdhub(?:\.com)?\b', '', name_info, flags=re.I)
				name_info = re.sub(r'\.{2,}', '.', name_info)
				language = source_utils.release_language(name_info)

				if use_direct:
					url = direct_url
				else:
					url = 'magnet:?xt=urn:btih:%s&dn=%s' % (hash, name)

				try:
					seeders = file['seeders']
					if self.min_seeders > seeders: continue
				except: seeders = 0

				quality, info = source_utils.get_release_quality(name_info, url)
				try:
					size = f"{float(file['size']) / 1073741824:.2f} GB"
					dsize, isize = source_utils._size(size)
					info.insert(0, isize)
				except: dsize = 0
				info = ' | '.join(info)

				if use_direct:
					item = {
						'source': source_type, 'language': language, 'direct': True, 'debridonly': False,
						'provider': 'aiostreams_custom', 'url': url, 'name': name, 'name_info': name_info,
						'quality': quality, 'info': info, 'size': dsize, 'seeders': seeders,
						'aiostreams_direct': True, 'debrid': 'aiostreams', 'bypass_filter': True, 'true_size': True,
						'provider_name': 'AIOStreams Custom'
					}
				else:
					item = {
						'source': 'torrent', 'language': language, 'direct': False, 'debridonly': True,
						'provider': 'aiostreams_custom', 'hash': hash, 'url': url, 'name': name, 'name_info': name_info,
						'quality': quality, 'info': info, 'size': dsize, 'seeders': seeders, 'bypass_filter': True, 'true_size': True,
						'provider_name': 'AIOStreams Custom'
					}
				manifest_label = self._aio_manifest_from_stream(file, tracker=tracker)
				if manifest_label: item['aio_manifest'] = manifest_label
				if tracker:
					if not use_direct:
						tracker = 'AIO | %s' % tracker if not tracker.upper().startswith('AIO | ') else tracker
					item['tracker'] = tracker
				if package: item['package'] = package
				if package == 'show': item.update({'last_season': last_season})
				if episode_start: item.update({'episode_start': episode_start, 'episode_end': episode_end}) # for partial season packs
				sources_append(item)
			except:
				source_utils.scraper_error('AIOSTREAMS_CUSTOM')
		return sources

	def _headers(self):
		if self.authorization:
			return {'Authorization': self.authorization}
		return {'x-aiostreams-user-data': (
			'ewogICJwcmVzZXRzIjogWwogICAgewogICAgICAidHlwZSI6ICJ0b3JyZW50aW8iLAogICAgICAiaW5z'
			'dGFuY2VJZCI6ICJlN2IiLAogICAgICAiZW5hYmxlZCI6IGZhbHNlLAogICAgICAib3B0aW9ucyI6IHsK'
			'ICAgICAgICAibmFtZSI6ICJUb3JyZW50aW8iLAogICAgICAgICJ0aW1lb3V0IjogNjUwMCwKICAgICAg'
			'ICAicmVzb3VyY2VzIjogWwogICAgICAgICAgInN0cmVhbSIsCiAgICAgICAgICAiY2F0YWxvZyIsCiAg'
			'ICAgICAgICAibWV0YSIKICAgICAgICBdLAogICAgICAgICJwcm92aWRlcnMiOiBbXSwKICAgICAgICAi'
			'dXNlTXVsdGlwbGVJbnN0YW5jZXMiOiBmYWxzZQogICAgICB9CiAgICB9LAogICAgewogICAgICAidHlw'
			'ZSI6ICJjb21ldCIsCiAgICAgICJpbnN0YW5jZUlkIjogImY3YiIsCiAgICAgICJlbmFibGVkIjogdHJ1'
			'ZSwKICAgICAgIm9wdGlvbnMiOiB7CiAgICAgICAgIm5hbWUiOiAiQ29tZXQiLAogICAgICAgICJ0aW1l'
			'b3V0IjogNjUwMCwKICAgICAgICAicmVzb3VyY2VzIjogWwogICAgICAgICAgInN0cmVhbSIKICAgICAg'
			'ICBdLAogICAgICAgICJpbmNsdWRlUDJQIjogdHJ1ZSwKICAgICAgICAicmVtb3ZlVHJhc2giOiBmYWxz'
			'ZQogICAgICB9CiAgICB9LAogICAgewogICAgICAidHlwZSI6ICJtZWRpYWZ1c2lvbiIsCiAgICAgICJp'
			'bnN0YW5jZUlkIjogIjQ1MCIsCiAgICAgICJlbmFibGVkIjogdHJ1ZSwKICAgICAgIm9wdGlvbnMiOiB7'
			'CiAgICAgICAgIm5hbWUiOiAiTWVkaWFGdXNpb24iLAogICAgICAgICJ0aW1lb3V0IjogNjUwMCwKICAg'
			'ICAgICAicmVzb3VyY2VzIjogWwogICAgICAgICAgInN0cmVhbSIsCiAgICAgICAgICAiY2F0YWxvZyIs'
			'CiAgICAgICAgICAibWV0YSIKICAgICAgICBdLAogICAgICAgICJ1c2VDYWNoZWRSZXN1bHRzT25seSI6'
			'IHRydWUsCiAgICAgICAgImVuYWJsZVdhdGNobGlzdENhdGFsb2dzIjogZmFsc2UsCiAgICAgICAgImRv'
			'd25sb2FkVmlhQnJvd3NlciI6IGZhbHNlLAogICAgICAgICJjb250cmlidXRvclN0cmVhbXMiOiBmYWxz'
			'ZSwKICAgICAgICAiY2VydGlmaWNhdGlvbkxldmVsc0ZpbHRlciI6IFtdLAogICAgICAgICJudWRpdHlG'
			'aWx0ZXIiOiBbXQogICAgICB9CiAgICB9LAogICAgewogICAgICAidHlwZSI6ICJzdHJlbXRocnVUb3J6'
			'IiwKICAgICAgImluc3RhbmNlSWQiOiAiYWVkIiwKICAgICAgImVuYWJsZWQiOiB0cnVlLAogICAgICAi'
			'b3B0aW9ucyI6IHsKICAgICAgICAibmFtZSI6ICJTdHJlbVRocnUgVG9yeiIsCiAgICAgICAgInRpbWVv'
			'dXQiOiA2NTAwLAogICAgICAgICJyZXNvdXJjZXMiOiBbCiAgICAgICAgICAic3RyZWFtIgogICAgICAg'
			'IF0sCiAgICAgICAgImluY2x1ZGVQMlAiOiBmYWxzZSwKICAgICAgICAidXNlTXVsdGlwbGVJbnN0YW5j'
			'ZXMiOiBmYWxzZQogICAgICB9CiAgICB9LAogICAgewogICAgICAidHlwZSI6ICJ0b3JyZW50cy1kYiIs'
			'CiAgICAgICJpbnN0YW5jZUlkIjogImRkMiIsCiAgICAgICJlbmFibGVkIjogdHJ1ZSwKICAgICAgIm9w'
			'dGlvbnMiOiB7CiAgICAgICAgIm5hbWUiOiAiVG9ycmVudHNEQiIsCiAgICAgICAgInRpbWVvdXQiOiA2'
			'NTAwLAogICAgICAgICJyZXNvdXJjZXMiOiBbCiAgICAgICAgICAic3RyZWFtIiwKICAgICAgICAgICJj'
			'YXRhbG9nIiwKICAgICAgICAgICJtZXRhIgogICAgICAgIF0sCiAgICAgICAgInByb3ZpZGVycyI6IFsK'
			'ICAgICAgICAgICJ5dHMiLAogICAgICAgICAgIjEzMzd4IiwKICAgICAgICAgICJ0b3JyZW50Y3N2IiwK'
			'ICAgICAgICAgICIxbG91IiwKICAgICAgICAgICJueWFhIiwKICAgICAgICAgICJza3RvcnJlbnQiLAog'
			'ICAgICAgICAgIjF0YW1pbGJsYXN0ZXJzIiwKICAgICAgICAgICJsaW1ldG9ycmVudCIsCiAgICAgICAg'
			'ICAiMXRhbWlsbXYiLAogICAgICAgICAgInJhcmdiIiwKICAgICAgICAgICJrbmFiZW4iLAogICAgICAg'
			'ICAgInRoZXBpcmF0ZWJheSIsCiAgICAgICAgICAia2lja2Fzc3RvcnJlbnRzIiwKICAgICAgICAgICJh'
			'bmltZXRvc2hvIiwKICAgICAgICAgICJleHRyZW1seW10b3JyZW50cyIsCiAgICAgICAgICAieWdndG9y'
			'cmVudCIsCiAgICAgICAgICAidG9reW90b3NobyIsCiAgICAgICAgICAicnV0b3IiLAogICAgICAgICAg'
			'InJ1dHJhY2tlciIsCiAgICAgICAgICAidG9ycmVudDkiLAogICAgICAgICAgImlsY29yc2Fyb25lcm8i'
			'LAogICAgICAgICAgIm1hbnVhbCIKICAgICAgICBdLAogICAgICAgICJpbmNsdWRlUDJQIjogZmFsc2Us'
			'CiAgICAgICAgInVzZU11bHRpcGxlSW5zdGFuY2VzIjogZmFsc2UKICAgICAgfQogICAgfQogIF0sCiAg'
			'ImZvcm1hdHRlciI6IHsKICAgICJpZCI6ICJ0b3JyZW50aW8iLAogICAgImRlZmluaXRpb24iOiB7CiAg'
			'ICAgICJuYW1lIjogIiIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICIiCiAgICB9CiAgfSwKICAic29ydENy'
			'aXRlcmlhIjogewogICAgImdsb2JhbCI6IFtdCiAgfQp9'
		)}
