(?P
(?P
(?P
)(?P
\s*', itemmodule)
+ module_item_actions = module.get_menu_items()
+ contextmenuitems.extend([item for item, fn in module_item_actions])
+ contextmenuactions.extend([fn for item, fn in module_item_actions])
+
+ if len(contextmenuitems) == 0:
+ logger.info('No contextmodule found, build an empty one')
+ contextmenuitems.append(empty_item())
+ contextmenuactions.append(lambda: None)
+
+ ret = xbmcgui.Dialog().contextmenu(contextmenuitems)
+
+ if ret > -1:
+ logger.info('Contextmenu module index', ret, ', label=' + contextmenuitems[ret])
+ contextmenuactions[ret]()
+
+
+def empty_item():
+ return config.get_localized_string(90004)
+
+
+if __name__ == '__main__':
+ build_menu()
+
+
+
diff --git a/core/support.py b/core/support.py
index fd3a51a9..0fdbf45e 100755
--- a/core/support.py
+++ b/core/support.py
@@ -34,8 +34,9 @@ def hdpass_get_servers(item):
for mir_url, srv in scrapertools.find_multiple_matches(mir, patron_option):
mir_url = scrapertools.decodeHtmlentities(mir_url)
logger.debug(mir_url)
- it = item.clone(action="play", quality=quality, title=srv, server=srv, url= mir_url)
- if not servertools.get_server_parameters(srv.lower()): it = hdpass_get_url(it)[0] # do not exists or it's empty
+ it = hdpass_get_url(item.clone(action='play', quality=quality, url=mir_url))[0]
+ # it = item.clone(action="play", quality=quality, title=srv, server=srv, url= mir_url)
+ # if not servertools.get_server_parameters(srv.lower()): it = hdpass_get_url(it)[0] # do not exists or it's empty
ret.append(it)
return ret
# Carica la pagina
@@ -1143,7 +1144,10 @@ def nextPage(itemlist, item, data='', patron='', function_or_level=1, next_page=
if next_page != "":
if resub: next_page = re.sub(resub[0], resub[1], next_page)
if 'http' not in next_page:
- next_page = scrapertools.find_single_match(item.url, 'https?://[a-z0-9.-]+') + (next_page if next_page.startswith('/') else '/' + next_page)
+ if '/' in next_page:
+ next_page = scrapertools.find_single_match(item.url, 'https?://[a-z0-9.-]+') + (next_page if next_page.startswith('/') else '/' + next_page)
+ else:
+ next_page = '/'.join(item.url.split('/')[:-1]) + '/' + next_page
next_page = next_page.replace('&', '&')
logger.debug('NEXT= ', next_page)
itemlist.append(
@@ -1370,15 +1374,27 @@ def addQualityTag(item, itemlist, data, patron):
info('nessun tag qualità trovato')
def get_jwplayer_mediaurl(data, srvName, onlyHttp=False, dataIsBlock=False):
+ from core import jsontools
+
video_urls = []
- block = scrapertools.find_single_match(data, r'sources:\s*\[([^\]]+)\]') if not dataIsBlock else data
+ block = scrapertools.find_single_match(data, r'sources:\s*([^\]]+\])') if not dataIsBlock else data
if block:
- if 'file:' in block:
- sources = scrapertools.find_multiple_matches(block, r'file:\s*"([^"]+)"(?:,label:\s*"([^"]+)")?')
- elif 'src:' in block:
- sources = scrapertools.find_multiple_matches(block, r'src:\s*"([^"]+)",\s*type:\s*"[^"]+"(?:,[^,]+,\s*label:\s*"([^"]+)")?')
+ json = jsontools.load(block)
+ if json:
+ sources = []
+ for s in json:
+ if 'file' in s.keys():
+ src = s['file']
+ else:
+ src = s['src']
+ sources.append((src, s.get('label')))
else:
- sources =[(block.replace('"',''), '')]
+ if 'file:' in block:
+ sources = scrapertools.find_multiple_matches(block, r'file:\s*"([^"]+)"(?:,label:\s*"([^"]+)")?')
+ elif 'src:' in block:
+ sources = scrapertools.find_multiple_matches(block, r'src:\s*"([^"]+)",\s*type:\s*"[^"]+"(?:,[^,]+,\s*label:\s*"([^"]+)")?')
+ else:
+ sources =[(block.replace('"',''), '')]
for url, quality in sources:
quality = 'auto' if not quality else quality
if url.split('.')[-1] != 'mpd':
diff --git a/externalsearch.py b/externalsearch.py
deleted file mode 100644
index 2b837e08..00000000
--- a/externalsearch.py
+++ /dev/null
@@ -1,80 +0,0 @@
-import xbmc, sys, xbmcgui, os
-from platformcode import config, logger
-
-# incliuding folder libraries
-librerias = xbmc.translatePath(os.path.join(config.get_runtime_path(), 'lib'))
-sys.path.insert(0, librerias)
-
-
-from core import tmdb
-from core.item import Item
-
-def execute_search():
- """
- Gather the selected ListItem's attributes in order to compute the `Item` parameters
- and perform the KOD's globalsearch.
- Globalsearch will be executed specifing the content-type of the selected ListItem
-
- NOTE: this method needs the DBTYPE and TMDB_ID specified as ListItem's properties
- """
-
- # These following lines are commented and keep in the code just as reminder.
- # In future, they could be used to filter the search outcome
-
- # ADDON: maybe can we know if the current windows is related to a specific addon?
- # we could skip the ContextMenu if we already are in KOD's window
-
- tmdbid = xbmc.getInfoLabel('ListItem.Property(tmdb_id)')
- mediatype = xbmc.getInfoLabel('ListItem.DBTYPE')
- title = xbmc.getInfoLabel('ListItem.Title')
- year = xbmc.getInfoLabel('ListItem.Year')
- imdb = xbmc.getInfoLabel('ListItem.IMDBNumber')
- # folderPath = xbmc.getInfoLabel('Container.FolderPath')
- # filePath = xbmc.getInfoLabel('ListItem.FileNameAndPath')
- # logger.info("****")
- # logger.info( xbmc.getCondVisibility("String.Contains(Container.FolderPath, 'plugin.video.kod')") )
- # logger.info( xbmc.getCondVisibility("String.Contains(ListItem.FileNameAndPath, 'plugin.video.kod')") )
- # logger.info( xbmc.getCondVisibility("String.IsEqual(ListItem.dbtype,tvshow)") )
- # logger.info( xbmc.getCondVisibility("String.IsEqual(ListItem.dbtype,movie)") )
- # logger.info("****")
-
- # visible = xbmc.getCondVisibility("!String.StartsWith(ListItem.FileNameAndPath, 'plugin://plugin.video.kod/') + [String.IsEqual(ListItem.dbtype,tvshow) | String.IsEqual(ListItem.dbtype,movie)]")
-
- logstr = "Selected ListItem is: 'IMDB: {}' - TMDB: {}' - 'Title: {}' - 'Year: {}'' - 'Type: {}'".format(imdb, tmdbid, title, year, mediatype)
- logger.info(logstr)
-
- if not tmdbid and imdb:
- logger.info('No TMDBid found. Try to get by IMDB')
- it = Item(contentType= mediatype, infoLabels={'imdb_id' : imdb})
- tmdb.set_infoLabels(it)
- tmdbid = it.infoLabels.get('tmdb_id', '')
-
- if not tmdbid:
- logger.info('No TMDBid found. Try to get by Title/Year')
- it = Item(contentTitle= title, contentType= mediatype, infoLabels={'year' : year})
- tmdb.set_infoLabels(it)
- tmdbid = it.infoLabels.get('tmdb_id', '')
-
-
- item = Item(
- action="Search",
- channel="globalsearch",
- contentType= mediatype,
- mode="search",
- text= title,
- type= mediatype,
- infoLabels= {
- 'tmdb_id': tmdbid,
- 'year': year
- },
- folder= False
- )
-
- logger.info("Invoking Item: {}".format(item.tostring()))
-
- itemurl = item.tourl()
- xbmc.executebuiltin("RunPlugin(plugin://plugin.video.kod/?" + itemurl + ")")
-
-
-if __name__ == '__main__':
- execute_search()
diff --git a/lib/cloudscraper/__init__.py b/lib/cloudscraper/__init__.py
new file mode 100644
index 00000000..98e7d51e
--- /dev/null
+++ b/lib/cloudscraper/__init__.py
@@ -0,0 +1,846 @@
+# ------------------------------------------------------------------------------- #
+
+import logging
+import re
+import requests
+import sys
+import ssl
+
+from collections import OrderedDict
+from copy import deepcopy
+
+from requests.adapters import HTTPAdapter
+from requests.sessions import Session
+from requests_toolbelt.utils import dump
+
+from time import sleep
+
+# ------------------------------------------------------------------------------- #
+
+try:
+ import brotli
+except ImportError:
+ pass
+
+try:
+ import copyreg
+except ImportError:
+ import copy_reg as copyreg
+
+try:
+ from HTMLParser import HTMLParser
+except ImportError:
+ if sys.version_info >= (3, 4):
+ import html
+ else:
+ from html.parser import HTMLParser
+
+try:
+ from urlparse import urlparse, urljoin
+except ImportError:
+ from urllib.parse import urlparse, urljoin
+
+# ------------------------------------------------------------------------------- #
+
+from .exceptions import (
+ CloudflareLoopProtection,
+ CloudflareCode1020,
+ CloudflareIUAMError,
+ CloudflareSolveError,
+ CloudflareChallengeError,
+ CloudflareCaptchaError,
+ CloudflareCaptchaProvider
+)
+
+from .interpreters import JavaScriptInterpreter
+from .captcha import Captcha
+from .user_agent import User_Agent
+
+# ------------------------------------------------------------------------------- #
+
+__version__ = '1.2.58'
+
+# ------------------------------------------------------------------------------- #
+
+
+class CipherSuiteAdapter(HTTPAdapter):
+
+ __attrs__ = [
+ 'ssl_context',
+ 'max_retries',
+ 'config',
+ '_pool_connections',
+ '_pool_maxsize',
+ '_pool_block',
+ 'source_address'
+ ]
+
+ def __init__(self, *args, **kwargs):
+ self.ssl_context = kwargs.pop('ssl_context', None)
+ self.cipherSuite = kwargs.pop('cipherSuite', None)
+ self.source_address = kwargs.pop('source_address', None)
+
+ if self.source_address:
+ if isinstance(self.source_address, str):
+ self.source_address = (self.source_address, 0)
+
+ if not isinstance(self.source_address, tuple):
+ raise TypeError(
+ "source_address must be IP address string or (ip, port) tuple"
+ )
+
+ if not self.ssl_context:
+ self.ssl_context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
+ self.ssl_context.set_ciphers(self.cipherSuite)
+ self.ssl_context.set_ecdh_curve('prime256v1')
+ self.ssl_context.options |= (ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1)
+
+ super(CipherSuiteAdapter, self).__init__(**kwargs)
+
+ # ------------------------------------------------------------------------------- #
+
+ def init_poolmanager(self, *args, **kwargs):
+ kwargs['ssl_context'] = self.ssl_context
+ kwargs['source_address'] = self.source_address
+ return super(CipherSuiteAdapter, self).init_poolmanager(*args, **kwargs)
+
+ # ------------------------------------------------------------------------------- #
+
+ def proxy_manager_for(self, *args, **kwargs):
+ kwargs['ssl_context'] = self.ssl_context
+ kwargs['source_address'] = self.source_address
+ return super(CipherSuiteAdapter, self).proxy_manager_for(*args, **kwargs)
+
+# ------------------------------------------------------------------------------- #
+
+
+class CloudScraper(Session):
+
+ def __init__(self, *args, **kwargs):
+ self.debug = kwargs.pop('debug', False)
+ self.delay = kwargs.pop('delay', None)
+ self.cipherSuite = kwargs.pop('cipherSuite', None)
+ self.ssl_context = kwargs.pop('ssl_context', None)
+ self.interpreter = kwargs.pop('interpreter', 'native')
+ self.captcha = kwargs.pop('captcha', {})
+ self.requestPreHook = kwargs.pop('requestPreHook', None)
+ self.requestPostHook = kwargs.pop('requestPostHook', None)
+ self.source_address = kwargs.pop('source_address', None)
+ self.doubleDown = kwargs.pop('doubleDown', True)
+
+ self.allow_brotli = kwargs.pop(
+ 'allow_brotli',
+ True if 'brotli' in sys.modules.keys() else False
+ )
+
+ self.user_agent = User_Agent(
+ allow_brotli=self.allow_brotli,
+ browser=kwargs.pop('browser', None)
+ )
+
+ self._solveDepthCnt = 0
+ self.solveDepth = kwargs.pop('solveDepth', 3)
+
+ super(CloudScraper, self).__init__(*args, **kwargs)
+
+ # pylint: disable=E0203
+ if 'requests' in self.headers['User-Agent']:
+ # ------------------------------------------------------------------------------- #
+ # Set a random User-Agent if no custom User-Agent has been set
+ # ------------------------------------------------------------------------------- #
+ self.headers = self.user_agent.headers
+ if not self.cipherSuite:
+ self.cipherSuite = self.user_agent.cipherSuite
+
+ if isinstance(self.cipherSuite, list):
+ self.cipherSuite = ':'.join(self.cipherSuite)
+
+ self.mount(
+ 'https://',
+ CipherSuiteAdapter(
+ cipherSuite=self.cipherSuite,
+ ssl_context=self.ssl_context,
+ source_address=self.source_address
+ )
+ )
+
+ # purely to allow us to pickle dump
+ copyreg.pickle(ssl.SSLContext, lambda obj: (obj.__class__, (obj.protocol,)))
+
+ # ------------------------------------------------------------------------------- #
+ # Allow us to pickle our session back with all variables
+ # ------------------------------------------------------------------------------- #
+
+ def __getstate__(self):
+ return self.__dict__
+
+ # ------------------------------------------------------------------------------- #
+ # Allow replacing actual web request call via subclassing
+ # ------------------------------------------------------------------------------- #
+
+ def perform_request(self, method, url, *args, **kwargs):
+ return super(CloudScraper, self).request(method, url, *args, **kwargs)
+
+ # ------------------------------------------------------------------------------- #
+ # Raise an Exception with no stacktrace and reset depth counter.
+ # ------------------------------------------------------------------------------- #
+
+ def simpleException(self, exception, msg):
+ self._solveDepthCnt = 0
+ sys.tracebacklimit = 0
+ raise exception(msg)
+
+ # ------------------------------------------------------------------------------- #
+ # debug the request via the response
+ # ------------------------------------------------------------------------------- #
+
+ @staticmethod
+ def debugRequest(req):
+ try:
+ print(dump.dump_all(req).decode('utf-8', errors='backslashreplace'))
+ except ValueError as e:
+ print("Debug Error: {}".format(getattr(e, 'message', e)))
+
+ # ------------------------------------------------------------------------------- #
+ # Unescape / decode html entities
+ # ------------------------------------------------------------------------------- #
+
+ @staticmethod
+ def unescape(html_text):
+ if sys.version_info >= (3, 0):
+ if sys.version_info >= (3, 4):
+ return html.unescape(html_text)
+
+ return HTMLParser().unescape(html_text)
+
+ return HTMLParser().unescape(html_text)
+
+ # ------------------------------------------------------------------------------- #
+ # Decode Brotli on older versions of urllib3 manually
+ # ------------------------------------------------------------------------------- #
+
+ def decodeBrotli(self, resp):
+ if requests.packages.urllib3.__version__ < '1.25.1' and resp.headers.get('Content-Encoding') == 'br':
+ if self.allow_brotli and resp._content:
+ resp._content = brotli.decompress(resp.content)
+ else:
+ logging.warning(
+ 'You\'re running urllib3 {}, Brotli content detected, '
+ 'Which requires manual decompression, '
+ 'But option allow_brotli is set to False, '
+ 'We will not continue to decompress.'.format(requests.packages.urllib3.__version__)
+ )
+
+ return resp
+
+ # ------------------------------------------------------------------------------- #
+ # Our hijacker request function
+ # ------------------------------------------------------------------------------- #
+
+ def request(self, method, url, *args, **kwargs):
+ # pylint: disable=E0203
+ if kwargs.get('proxies') and kwargs.get('proxies') != self.proxies:
+ self.proxies = kwargs.get('proxies')
+
+ # ------------------------------------------------------------------------------- #
+ # Pre-Hook the request via user defined function.
+ # ------------------------------------------------------------------------------- #
+
+ if self.requestPreHook:
+ (method, url, args, kwargs) = self.requestPreHook(
+ self,
+ method,
+ url,
+ *args,
+ **kwargs
+ )
+
+ # ------------------------------------------------------------------------------- #
+ # Make the request via requests.
+ # ------------------------------------------------------------------------------- #
+
+ response = self.decodeBrotli(
+ self.perform_request(method, url, *args, **kwargs)
+ )
+
+ # ------------------------------------------------------------------------------- #
+ # Debug the request via the Response object.
+ # ------------------------------------------------------------------------------- #
+
+ if self.debug:
+ self.debugRequest(response)
+
+ # ------------------------------------------------------------------------------- #
+ # Post-Hook the request aka Post-Hook the response via user defined function.
+ # ------------------------------------------------------------------------------- #
+
+ if self.requestPostHook:
+ response = self.requestPostHook(self, response)
+
+ if self.debug:
+ self.debugRequest(response)
+
+ # Check if Cloudflare anti-bot is on
+ if self.is_Challenge_Request(response):
+ # ------------------------------------------------------------------------------- #
+ # Try to solve the challenge and send it back
+ # ------------------------------------------------------------------------------- #
+
+ if self._solveDepthCnt >= self.solveDepth:
+ _ = self._solveDepthCnt
+ self.simpleException(
+ CloudflareLoopProtection,
+ "!!Loop Protection!! We have tried to solve {} time(s) in a row.".format(_)
+ )
+
+ self._solveDepthCnt += 1
+
+ response = self.Challenge_Response(response, **kwargs)
+ else:
+ if not response.is_redirect and response.status_code not in [429, 503]:
+ self._solveDepthCnt = 0
+
+ return response
+
+ # ------------------------------------------------------------------------------- #
+ # check if the response contains a valid Cloudflare Bot Fight Mode challenge
+ # ------------------------------------------------------------------------------- #
+
+ @staticmethod
+ def is_BFM_Challenge(resp):
+ try:
+ return (
+ resp.headers.get('Server', '').startswith('cloudflare')
+ and re.search(
+ r"\/cdn-cgi\/bm\/cv\/\d+\/api\.js.*?"
+ r"window\['__CF\$cv\$params'\]\s*=\s*{",
+ resp.text,
+ re.M | re.S
+ )
+ )
+ except AttributeError:
+ pass
+
+ return False
+
+ # ------------------------------------------------------------------------------- #
+ # check if the response contains a valid Cloudflare challenge
+ # ------------------------------------------------------------------------------- #
+
+ @staticmethod
+ def is_IUAM_Challenge(resp):
+ try:
+ return (
+ resp.headers.get('Server', '').startswith('cloudflare')
+ and resp.status_code in [429, 503]
+ and re.search(
+ r'