KoD 1.7.6
- fix di routine ai canali/server\n- disabilitati cb01anime e tantifilm\n- aggiunta opzione mostra server nel menu contestuale della libreria\n- più opzioni per quanto riguarda l'aggiornamento della videoteca\n\n
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
<addon id="plugin.video.kod" name="Kodi on Demand" version="1.7.5" provider-name="KoD Team">
|
||||
<addon id="plugin.video.kod" name="Kodi on Demand" version="1.7.6" provider-name="KoD Team">
|
||||
<requires>
|
||||
<!-- <import addon="script.module.libtorrent" optional="true"/> -->
|
||||
<import addon="metadata.themoviedb.org"/>
|
||||
@@ -27,9 +27,10 @@
|
||||
<screenshot>resources/media/screenshot-2.png</screenshot>
|
||||
<screenshot>resources/media/screenshot-3.png</screenshot>
|
||||
</assets>
|
||||
<news>- Aggiunti nuovi canali: 1337x e filmstreaming
|
||||
- fix cinemalibero, altadefinizione01
|
||||
- workaround per puntante non funzionanti quando si aggiorna la videoteca
|
||||
<news>- fix di routine ai canali/server
|
||||
- disabilitati cb01anime e tantifilm
|
||||
- aggiunta opzione "mostra server" nel menu contestuale della libreria
|
||||
- più opzioni per quanto riguarda l'aggiornamento della videoteca
|
||||
</news>
|
||||
<description lang="it">Naviga velocemente sul web e guarda i contenuti presenti</description>
|
||||
<disclaimer>[COLOR red]The owners and submitters to this addon do not host or distribute any of the content displayed by these addons nor do they have any affiliation with the content providers.[/COLOR]
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"animeuniverse": "https://www.animeuniverse.it",
|
||||
"animeworld": "https://www.animeworld.tv",
|
||||
"aniplay": "https://aniplay.it",
|
||||
"casacinema": "https://www.casacinema.page",
|
||||
"casacinema": "https://www.casacinema.lol",
|
||||
"cb01anime": "https://www.cineblog01.red",
|
||||
"cinemalibero": "https://cinemalibero.cafe",
|
||||
"cinetecadibologna": "http://cinestore.cinetecadibologna.it",
|
||||
|
||||
@@ -20,12 +20,14 @@ headers = [['Referer', host]]
|
||||
@support.menu
|
||||
def mainlist(item):
|
||||
|
||||
film = ['/genere/film/',
|
||||
film = ['/category/film/',
|
||||
('Al Cinema', ['/al-cinema/', 'peliculas']),
|
||||
('Generi', ['', 'genres']),
|
||||
('Sub-ITA', ['/sub-ita/', 'peliculas'])]
|
||||
# ('Sub-ITA', ['/sub-ita/', 'peliculas'])
|
||||
]
|
||||
|
||||
tvshow = ['/genere/serie-tv/']
|
||||
tvshow = ['/category/serie-tv/',
|
||||
('Aggiornamenti Serie TV', ['/aggiornamenti-serie-tv/', 'peliculas']),]
|
||||
|
||||
search = ''
|
||||
|
||||
@@ -37,12 +39,19 @@ def genres(item):
|
||||
action = 'peliculas'
|
||||
blacklist = ['Scegli il Genere', 'Film', 'Serie TV', 'Sub-Ita', 'Anime']
|
||||
patronMenu = r'<option value="(?P<url>[^"]+)">(?P<title>[^<]+)'
|
||||
def itemlistHook(itemlist):
|
||||
itl = []
|
||||
for item in itemlist:
|
||||
if len(item.fulltitle) != 3:
|
||||
itl.append(item)
|
||||
return itl
|
||||
return locals()
|
||||
|
||||
|
||||
def search(item, text):
|
||||
logger.debug(text)
|
||||
item.url = "{}/?s={}".format(host, text)
|
||||
item.url = "{}/search/{}/feed/rss2/".format(host, text)
|
||||
item.args = 'search'
|
||||
|
||||
try:
|
||||
return peliculas(item)
|
||||
@@ -59,9 +68,10 @@ def peliculas(item):
|
||||
n = '22' if '/?s=' in item.url else '8'
|
||||
item.contentType = "undefined"
|
||||
action = 'check'
|
||||
# patron = r'data-src="(?P<thumb>http[^"]+)(?:[^>]+>){' + n + r'}\s*<a href="(?P<url>[^"]+)[^>]+>\s*(?P<title>[^\[\(\<]+)(?:\[(?P<quality>[^\]]+)\])?\s*(?:\((?P<lang>[a-zA-z-]+)\))?\s*(?:\((?P<year>\d+)\))?\s*</a>\s*</h2>'
|
||||
patron = r'data-src="(?P<poster>http[^"]+)(?:[^>]+>){7,18}\s*<a href="(?P<url>[^"]+)[^>]+>\s*(?P<title>[^\[\(\<]+)(?:\[(?P<quality>[^\]]+)\])?\s*(?:\((?P<lang>[a-zA-z-]+)\))?\s*(?:\((?P<year>\d+)\))?\s*</a>\s*</h2>'
|
||||
patronNext = r'href="([^"]+)[^>]+>»'
|
||||
patron = r'src="(?P<poster>http[^"]+)(?:[^>]+>){4}\s*<a href="(?P<url>[^"]+)[^>]+>\s*(?P<title>[^\[\(\<]+)(?:\[(?P<quality>[^\]]+)\])?\s*(?:\((?P<lang>[a-zA-z-]+)\))?\s*(?:\((?P<year>\d+)\))?\s*</a>\s*</h2>'
|
||||
if item.args == 'search':
|
||||
patron = r'<title>(?P<title>[^\[\(\<]+)(?:\[(?P<quality>[^\]]+)\])?\s*(?:\((?P<lang>[a-zA-z-]+)\))?\s*(?:\((?P<year>\d+)\))?\s*[^>]+>\s*<link>(?P<url>[^<]+)'
|
||||
patronNext = r'href="([^"]+)[^>]+>Successivo'
|
||||
return locals()
|
||||
|
||||
|
||||
@@ -70,15 +80,15 @@ def episodios(item):
|
||||
item.quality = ''
|
||||
data = item.data
|
||||
action='findvideos'
|
||||
# patronBlock = r'[Ss]tagione.*?\s(?P<lang>(?:[Ss][Uu][Bb][-]?)?[Ii][Tt][Aa])(?: in )?(?P<quality>[^<]*)?(?:[^>]+>){4}(?P<block>.*?)/p>'
|
||||
patronBlock = r'<strong>\s*\w+\s*[Ss]tagione.*?(?P<lang>(?:[Ss][Uu][Bb][-]?)?[Ii][Tt][Aa])(?: in )?(?P<quality>[^<]*)?(?:[^>]+>){4}(?P<block>.*?)/p>'
|
||||
patron = r'(?P<season>\d+)&[^:]+;(?P<episode>\d+)(?P<data>.*?)(?:<br|$)'
|
||||
# debug=True
|
||||
patronBlock = r'<span>\s*\w+\s*[Ss]tagione.*?(?P<lang>(?:[Ss][Uu][Bb][-]?)?[Ii][Tt][Aa])(?: in )?(?P<quality>[^<]*)?(?:[^>]+>){4}(?P<block>.*?)/p>'
|
||||
patron = r'(?P<season>\d+)x(?P<episode>\d+)[^>]+>(?P<data>.*?)(?:</table)'
|
||||
return locals()
|
||||
|
||||
|
||||
def check(item):
|
||||
item.data = httptools.downloadpage(item.url).data
|
||||
if 'rel="tag">Serie TV' in item.data:
|
||||
if 'Stagione' in item.data:
|
||||
item.contentType = 'tvshow'
|
||||
return episodios(item)
|
||||
else:
|
||||
|
||||
@@ -111,9 +111,9 @@ def episodios(item):
|
||||
data = item.data
|
||||
|
||||
if '<h6>Streaming</h6>' in data:
|
||||
patron = r'<td style[^>]+>\s*.*?(?:<span[^>]+)?<strong>(?P<title>[^<]+)<\/strong>.*?<td style[^>]+>\s*<a href="(?P<url>[^"]+)"[^>]+>'
|
||||
patron = r'<td style[^>]+>\s*.*?(?:<span[^>]+)?<strong>(?P<title>[^<]+)<\/strong>.*?<td style[^>]+>\s*<a href="(?P<url>[^"]+)"[^>]+>(?P<episode>\d+)'
|
||||
else:
|
||||
patron = r'<a\s*href="(?P<url>[^"]+)"\s*title="(?P<title>[^"]+)"\s*class="btn btn-dark mb-1">'
|
||||
patron = r'<a\s*href="(?P<url>[^"]+)"\s*title="(?P<title>[^"]+)"\s*class="btn btn-dark mb-1">(?P<episode>\d+)'
|
||||
def itemHook(item):
|
||||
support.info(item)
|
||||
if item.url.startswith('//'): item.url= 'https:' + item.url
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
from lib import js2py
|
||||
from core import support
|
||||
from platformcode import config
|
||||
from platformcode.logger import debug
|
||||
|
||||
host = support.config.get_channel_url()
|
||||
__channel__ = 'animesaturn'
|
||||
@@ -170,7 +171,7 @@ def check(item):
|
||||
@support.scrape
|
||||
def episodios(item):
|
||||
if item.contentType != 'movie': anime = True
|
||||
patron = r'episodi-link-button">\s*<a href="(?P<url>[^"]+)"[^>]+>\s*(?P<title>[^<]+)</a>'
|
||||
patron = r'episodi-link-button">\s*<a href="(?P<url>[^"]+)"[^>]+>\s*(?P<title>[^\d<]+(?P<episode>\d+))\s*</a>'
|
||||
return locals()
|
||||
|
||||
|
||||
|
||||
@@ -224,7 +224,9 @@ def findvideos(item):
|
||||
from hashlib import md5
|
||||
|
||||
# Calculate Token
|
||||
client_ip = support.httptools.downloadpage('https://scws.xyz/videos/{}'.format(item.scws_id), headers=headers).json.get('client_ip')
|
||||
client_ip = support.httptools.downloadpage('https://scws.xyz/videos/{}'.format(item.scws_id), headers=headers).json.get(
|
||||
'client_ip')
|
||||
logger.debug(client_ip)
|
||||
expires = int(time() + 172800)
|
||||
token = b64encode(md5('{}{} Yc8U6r8KjAKAepEA'.format(expires, client_ip).encode('utf-8')).digest()).decode('utf-8').replace('=', '').replace('+', '-').replace('/', '_')
|
||||
|
||||
|
||||
@@ -134,8 +134,11 @@ def episodios(item):
|
||||
action = 'findvideos'
|
||||
item.contentType = 'tvshow'
|
||||
blacklist = ['']
|
||||
patron = r'(?P<episode>\d+(?:×|×)?\d+\-\d+|\d+(?:×|×)\d+)[;]?(?:(?P<title>[^<]+)<(?P<data>.*?)|(\2[ ])(?:<(\3.*?)))(?:<br />|</p>)'
|
||||
patronBlock = r'<strong>(?P<block>(?:.+?Stagione*.+?(?P<lang>[Ii][Tt][Aa]|[Ss][Uu][Bb][\-]?[iI][tT][aA]))?(?:.+?|</strong>)(/?:</span>)?</p>.*?</p>)'
|
||||
# debug = True
|
||||
patron = r'"season-no">(?P<season>\d+)x(?P<episode>\d+)(?:[^>]+>){5}\s*(?P<title>[^<]+)(?P<data>.*?)</table>'
|
||||
# patron = r'(?P<episode>\d+(?:×|×)?\d+\-\d+|\d+(?:×|×)\d+)[;]?(?:(?P<title>[^<]+)<(?P<data>.*?)|(\2[ ])(?:<(\3.*?)))(?:<br />|</p>)'
|
||||
patronBlock = r'<span>(?:.+?Stagione*.+?(?P<lang>[Ii][Tt][Aa]|[Ss][Uu][Bb][\-]?[iI][tT][aA]))?.*?</span>.*?class="content(?P<block>.*?)(?:"accordion-item|<script>)'
|
||||
# patronBlock = r'<strong>(?P<block>(?:.+?Stagione*.+?(?P<lang>[Ii][Tt][Aa]|[Ss][Uu][Bb][\-]?[iI][tT][aA]))?(?:.+?|</strong>)(/?:</span>)?</p>.*?</p>)'
|
||||
return locals()
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"id": "cb01anime",
|
||||
"name": "Cb01anime",
|
||||
"language": ["ita", "vos", "sub-ita"],
|
||||
"active": true,
|
||||
"thumbnail": "cb01anime.png",
|
||||
"banner": "cb01anime.png",
|
||||
"categories": ["anime"],
|
||||
"settings": []
|
||||
}
|
||||
"id": "cb01anime",
|
||||
"name": "Cb01anime",
|
||||
"language": ["ita", "vos", "sub-ita"],
|
||||
"active": false,
|
||||
"thumbnail": "cb01anime.png",
|
||||
"banner": "cb01anime.png",
|
||||
"categories": ["anime"],
|
||||
"settings": []
|
||||
}
|
||||
@@ -97,25 +97,21 @@ def peliculas(item):
|
||||
@support.scrape
|
||||
def episodios(item):
|
||||
data = item.data
|
||||
# support.dbg()
|
||||
# debugBlock = True
|
||||
|
||||
if item.args == 'anime':
|
||||
logger.debug("Anime :", item)
|
||||
# blacklist = ['Clipwatching', 'Verystream', 'Easybytez', 'Flix555', 'Cloudvideo']
|
||||
patron = r'<a target=(?P<url>[^>]+>(?P<title>Episodio\s(?P<episode>\d+))(?::)?(?:(?P<title2>[^<]+))?.*?(?:<br|</p))'
|
||||
patronBlock = r'(?:Stagione (?P<season>\d+))?(?:</span><br />|</span></p>|strong></p>)(?P<block>.*?)(?:<div style="margin-left|<span class="txt_dow">)'
|
||||
item.contentType = 'tvshow'
|
||||
elif item.args == 'wrestling':
|
||||
|
||||
logger.debug("Wrestling :", item)
|
||||
# debugBlock = True
|
||||
elif item.args == 'sport':
|
||||
logger.debug("Sport :", item)
|
||||
patron = r'(?:/>|<p>)\s*(?P<title>[^-]+)-(?P<data>.+?)(?:<br|</p)'
|
||||
patronBlock = r'</strong>\s*</p>(?P<block>.*?</p>)'
|
||||
item.contentType = 'tvshow'
|
||||
elif item.args == 'serie' or item.contentType == 'tvshow':
|
||||
logger.debug("Serie :", item)
|
||||
# debugBlock = True
|
||||
patron = r'(?:/>|<p>)\s*(?:(?P<episode>\d+(?:x|×|×)\d+|Puntata \d+)[;]?[ ]?(?P<title>[^<–-]+))?(?P<data>.*?)(?:<br|</p)'
|
||||
# debug=True
|
||||
patron = r'(?:/>|<p>)\s*(?:(?P<episode>\d+(?:x|×|×)\d+|Puntata \d+)(?:-(?P<episode2>\d+))?[;]?[ ]?(?P<title>[^<–-]+))?(?P<data>.*?)(?:<br|</p)'
|
||||
patronBlock = r'Stagione\s(?:[Uu]nica)?(?:(?P<lang>iTA|ITA|Sub-ITA|Sub-iTA))?.*?</strong>(?P<block>.+?)(?:strong>|<div class="at-below)'
|
||||
item.contentType = 'tvshow'
|
||||
else:
|
||||
@@ -128,8 +124,9 @@ def episodios(item):
|
||||
|
||||
def itemlistHook(itl):
|
||||
ret = []
|
||||
if item.args == 'wrestling':
|
||||
if item.args == 'sport':
|
||||
return itl
|
||||
# support.dbg()
|
||||
for it in itl:
|
||||
ep = scrapertools.find_single_match(it.title, r'(\d+x\d+)')
|
||||
if not ep and 'http' in it.data: # stagione intera
|
||||
@@ -211,22 +208,28 @@ def newest(categoria):
|
||||
def check(item):
|
||||
logger.debug()
|
||||
data = support.match(item.url, headers=headers).data
|
||||
# support.dbg()
|
||||
if data:
|
||||
ck = support.match(data, patron=r'Supportaci condividendo quest[oa] ([^:]+)').match.lower()
|
||||
if ck == 'film':
|
||||
item.contentType = 'movie'
|
||||
item.data = data
|
||||
# item.action = 'findvideos'
|
||||
return findvideos(item)
|
||||
ck = str(support.match(data, patronBlock=r'Genere:(.*?)</span>', patron=r'tag">([^<]+)').matches).lower()
|
||||
|
||||
elif ck in ['serie tv', 'wrestling wwe', 'anime']:
|
||||
if 'serie tv' in ck or 'anime' in ck:# in ['serie tv', 'wrestling wwe', 'anime']:
|
||||
if 'anime' in ck:
|
||||
item.args = 'anime'
|
||||
elif 'sport' in ck:
|
||||
item.args = 'sport'
|
||||
else:
|
||||
item.args = 'serie'
|
||||
item.contentType = 'tvshow'
|
||||
item.args = ck.split()[0]
|
||||
item.data = data
|
||||
itemlist = episodios(item)
|
||||
if not itemlist:
|
||||
item.data = data
|
||||
return findvideos(item)
|
||||
else:
|
||||
item.contentType = 'movie'
|
||||
item.data = data
|
||||
# item.action = 'findvideos'
|
||||
return findvideos(item)
|
||||
|
||||
return itemlist
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
"id": "dreamsub",
|
||||
"name": "DreamSub",
|
||||
"language": ["ita", "sub-ita"],
|
||||
"active": true,
|
||||
"active": false,
|
||||
"thumbnail": "dreamsub.png",
|
||||
"banner": "dreamsub.png",
|
||||
"categories": ["anime", "vos"],
|
||||
"settings": []
|
||||
}
|
||||
}
|
||||
@@ -64,9 +64,11 @@ def newest(categoria):
|
||||
|
||||
@support.scrape
|
||||
def peliculas(item):
|
||||
# debug=True
|
||||
if item.args == 'last':
|
||||
patronBlock = r'inseriti:(?P<block>.*?)<div class="block-showmore'
|
||||
patron = r'item-movie">[^>]+><a href="(?P<url>[^"]+)[^>]+><img data-src="(?P<thumb>[^"]+)(?:[^>]+>){6}(?P<title>[^<]+)(?:[^>]+>){6}(?P<quality>[^<]+)'
|
||||
patron = r'item-movie">[^>]+><a href="(?P<url>[^"]+)[^>]+><img data-src="(?P<thumb>[^"]+)(?:[^>]+>){6}(?P<title>[^<]+)(?:[^>]+>){4}(?P<year>\d+)?(?:[^>]+>){2}(?P<quality>[^<]+)'
|
||||
# patron = r'item-movie">[^>]+><a href="(?P<url>[^"]+)[^>]+><img data-src="(?P<thumb>[^"]+)(?:[^>]+>){6}(?P<title>[^<]+)(?:[^>]+>){6}(?P<quality>[^<]+)'
|
||||
patronNext = r'<a href="([^"]+)">&rarr'
|
||||
return locals()
|
||||
|
||||
|
||||
@@ -44,10 +44,10 @@ def peliculas(item):
|
||||
|
||||
@support.scrape
|
||||
def episodios(item):
|
||||
patronBlock = r'<div class="tab-pane fade" id="season-(?P<season>.)"(?P<block>.*?)</div>'
|
||||
patronBlock = r'<div class="tab-pane fade" id="season-(?P<season>.)"(?P<block>.*?)</ul>\s*</div>'
|
||||
patron = r'<a href="#" allowfullscreen data-link="(?P<url>[^"]+).*?title="(?P<title>[^"]+)(?P<lang>[sS][uU][bB]-?[iI][tT][aA])?\s*">(?P<episode>[^<]+)'
|
||||
action = 'findvideos'
|
||||
# debug = True
|
||||
# debugBlock = True
|
||||
return locals()
|
||||
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ def mainlist(item):
|
||||
@support.scrape
|
||||
def peliculas(item):
|
||||
# debug = True
|
||||
action = 'findvideos'
|
||||
sceneTitle = item.args[2]
|
||||
if item.args[1] in ['tvshow', 'anime', 'music', 'other']:
|
||||
patron = r'>[^"<]+'
|
||||
|
||||
@@ -25,16 +25,19 @@ def mainlist(item):
|
||||
|
||||
@support.scrape
|
||||
def peliculas(item):
|
||||
data = item.data
|
||||
if item.args == 'sala':
|
||||
patronBlock = r'insala(?P<block>.*?)<header>'
|
||||
patron = r'<img src="(?P<thumb>[^"]+)[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>\s*(?P<rating>[^<]+)[^>]+>[^>]+>(?P<quality>[^<]+)[^>]+>[^>]+>[^>]+>[^>]+><a href="(?P<url>[^"]+)">(?P<title>[^<]+)[^>]+>[^>]+>[^>]+>(?P<year>\d{4})'
|
||||
elif item.args == 'az':
|
||||
patron = r'<img src="(?P<thumb>[^"]+)[^>]+>[^>]+>[^>]+>[^>]+><a href="(?P<url>[^"]+)[^>]+>(?P<title>[^<]+)<[^>]+>[^>]+>[^>]+>.*?<span class="labelimdb">(?P<rating>[^>]+)<'
|
||||
if item.text:
|
||||
data = support.httptools.downloadpage(host + '/?s=' + item.text, post={'story': item.text, 'do': 'search', 'subaction': 'search'}).data
|
||||
patron = '<img src="(?P<thumb>[^"]+)(?:[^>]+>){8}\s*<a href="(?P<url>[^"]+)[^>]+>(?P<title>[^<]+)(?:[^>]+>){4}IMDb\s(?P<rating>[^<]+)(?:[^>]+>){2}(?P<year>\d+)'
|
||||
else:
|
||||
patron = r'<img src="(?P<thumb>[^"]+)[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>\s*(?P<rating>[^<]+)[^>]+>[^>]+>(?P<quality>[^<]+)[^>]+>[^>]+>[^>]+>[^>]+><a href="(?P<url>[^"]+)">(?P<title>[^<]+)[^>]+>[^>]+>[^>]+>(?P<year>\d{4})[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>\s*(?P<plot>[^<]+)<[^>]+>'
|
||||
if item.args == 'sala':
|
||||
patronBlock = r'insala(?P<block>.*?)<header>'
|
||||
patron = r'<img src="(?P<thumb>[^"]+)[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>\s*(?P<rating>[^<]+)[^>]+>[^>]+>(?P<quality>[^<]+)[^>]+>[^>]+>[^>]+>[^>]+><a href="(?P<url>[^"]+)">(?P<title>[^<]+)[^>]+>[^>]+>[^>]+>(?P<year>\d{4})'
|
||||
elif item.args == 'az':
|
||||
patron = r'<img src="(?P<thumb>[^"]+)[^>]+>[^>]+>[^>]+>[^>]+><a href="(?P<url>[^"]+)[^>]+>(?P<title>[^<]+)<[^>]+>[^>]+>[^>]+>.*?<span class="labelimdb">(?P<rating>[^>]+)<'
|
||||
else:
|
||||
patron = r'<img src="(?P<thumb>[^"]+)[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>\s*(?P<rating>[^<]+)[^>]+>[^>]+>(?P<quality>[^<]+)[^>]+>[^>]+>[^>]+>[^>]+><a href="(?P<url>[^"]+)">(?P<title>[^<]+)[^>]+>[^>]+>[^>]+>(?P<year>\d{4})[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>\s*(?P<plot>[^<]+)<[^>]+>'
|
||||
|
||||
patronNext = 'href="([^>]+)">»'
|
||||
patronNext = 'href="([^>]+)">»'
|
||||
|
||||
return locals()
|
||||
|
||||
@@ -56,7 +59,7 @@ def genres(item):
|
||||
|
||||
def search(item, text):
|
||||
info(text)
|
||||
item.data = support.httptools.downloadpage(host + '/?s=' + text, post={'story': text, 'do': 'search', 'subaction': 'search'}).data
|
||||
item.text = text
|
||||
try:
|
||||
return peliculas(item)
|
||||
except:
|
||||
|
||||
@@ -92,7 +92,7 @@ def live(item):
|
||||
plot += '\n\nA Seguire:\n[B]{}[/B]\n{}'.format(guide.get('nextListing', {}).get('mediasetlisting$epgTitle', ''),guide.get('nextListing', {}).get('description', ''))
|
||||
itemlist.append(item.clone(title=support.typo(title, 'bold'),
|
||||
fulltitle=title, callSign=it['callSign'],
|
||||
urls=[guide['publicUrl']],
|
||||
# urls=[guide['publicUrl']],
|
||||
plot=plot,
|
||||
url=url,
|
||||
action='findvideos',
|
||||
@@ -243,7 +243,7 @@ def findvideos(item):
|
||||
|
||||
lic_url = 'https://widevine.entitlement.theplatform.eu/wv/web/ModularDrm/getRawWidevineLicense?releasePid={pid}&account=http://access.auth.theplatform.com/data/Account/2702976343&schema=1.0&token={token}|Accept=*/*&Content-Type=&User-Agent={ua}|R{{SSM}}|'
|
||||
url = ''
|
||||
|
||||
# support.dbg()
|
||||
if item.urls:
|
||||
url = ''
|
||||
pid = ''
|
||||
@@ -265,12 +265,12 @@ def findvideos(item):
|
||||
return support.server(item, itemlist=[item], Download=False, Videolibrary=False)
|
||||
|
||||
elif item.video_id:
|
||||
payload = '{"contentId":"' + item.video_id + ' ","streamType":"VOD","delivery":"Streaming","createDevice":true}'
|
||||
res = session.post('https://api-ott-prod-fe.mediaset.net/PROD/play/playback/check/v2.0?sid=' + sid, data=payload).json()['response']['mediaSelector']
|
||||
payload = {"contentId":item.video_id, "streamType":"VOD", "delivery":"Streaming", "createDevice":"true", "overrideAppName":"web//mediasetplay-web/5.2.4-6ad16a4"}
|
||||
res = session.post('https://api-ott-prod-fe.mediaset.net/PROD/play/playback/check/v2.0?sid=' + sid, json=payload).json()['response']['mediaSelector']
|
||||
|
||||
else:
|
||||
payload = '{"channelCode":"' + item.callSign + '","streamType":"LIVE","delivery":"Streaming","createDevice":true}'
|
||||
res = session.post('https://api-ott-prod-fe.mediaset.net/PROD/play/playback/check/v2.0?sid=' + sid, data=payload).json()['response']['mediaSelector']
|
||||
payload = {"channelCode":item.callSign, "streamType":"LIVE", "delivery":"Streaming", "createDevice":"true", "overrideAppName":"web//mediasetplay-web/5.2.4-6ad16a4"}
|
||||
res = session.post('https://api-ott-prod-fe.mediaset.net/PROD/play/playback/check/v2.0?sid=' + sid, json=payload).json()['response']['mediaSelector']
|
||||
|
||||
url = res['url']
|
||||
mpd = True if 'dash' in res['formats'].lower() else False
|
||||
@@ -292,7 +292,6 @@ def findvideos(item):
|
||||
|
||||
|
||||
def get_from_id(item):
|
||||
logger.debug()
|
||||
sessionKey = session.get(sessionUrl.format(uuid=str(uuid.uuid4())), verify=False).json()['sessionKey']
|
||||
session.headers.update({'x-session': sessionKey})
|
||||
res = session.get(entry.format(id=item.args)).json()
|
||||
|
||||
@@ -252,7 +252,8 @@ def play(item):
|
||||
return []
|
||||
|
||||
# Calculate Token
|
||||
client_ip = httptools.downloadpage('https://api.ipify.org/').data
|
||||
client_ip = support.httptools.downloadpage('https://scws.xyz/videos/{}'.format(scws_id), headers=headers).json.get('client_ip')
|
||||
logger.debug(client_ip)
|
||||
expires = int(time() + 172800)
|
||||
token = b64encode(md5('{}{} Yc8U6r8KjAKAepEA'.format(expires, client_ip).encode('utf-8')).digest()).decode('utf-8').replace('=', '').replace('+', '-').replace('/', '_')
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"id": "tantifilm",
|
||||
"name": "Tantifilm",
|
||||
"language": ["ita"],
|
||||
"active": true,
|
||||
"thumbnail": "tantifilm.png",
|
||||
"banner": "tantifilm.png",
|
||||
"id": "tantifilm",
|
||||
"name": "Tantifilm",
|
||||
"language": ["ita"],
|
||||
"active": false,
|
||||
"thumbnail": "tantifilm.png",
|
||||
"banner": "tantifilm.png",
|
||||
"categories": ["tvshow", "movie", "anime"],
|
||||
"not_active":["include_in_newest_anime", "include_in_newest_peliculas"],
|
||||
"not_active": ["include_in_newest_anime", "include_in_newest_peliculas"],
|
||||
"settings": []
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
from platformcode import config, logger
|
||||
import xbmc, sys, xbmcgui, os
|
||||
|
||||
|
||||
librerias = xbmc.translatePath(os.path.join(config.get_runtime_path(), 'lib'))
|
||||
sys.path.insert(0, librerias)
|
||||
|
||||
from core import jsontools, support
|
||||
from core.item import Item
|
||||
|
||||
addon_id = config.get_addon_core().getAddonInfo('id')
|
||||
|
||||
@@ -15,6 +17,7 @@ f.close()
|
||||
|
||||
|
||||
def build_menu():
|
||||
# from core.support import dbg;dbg()
|
||||
tmdbid = xbmc.getInfoLabel('ListItem.Property(tmdb_id)')
|
||||
mediatype = xbmc.getInfoLabel('ListItem.DBTYPE')
|
||||
title = xbmc.getInfoLabel('ListItem.Title')
|
||||
@@ -24,9 +27,9 @@ def build_menu():
|
||||
containerPath = xbmc.getInfoLabel('Container.FolderPath')
|
||||
|
||||
logstr = "Selected ListItem is: 'IMDB: {}' - TMDB: {}' - 'Title: {}' - 'Year: {}'' - 'Type: {}'".format(imdb, tmdbid, title, year, mediatype)
|
||||
logger.info(logstr)
|
||||
logger.info(filePath)
|
||||
logger.info(containerPath)
|
||||
logger.debug(logstr)
|
||||
logger.debug(filePath)
|
||||
logger.debug(containerPath)
|
||||
|
||||
contextmenuitems = []
|
||||
contextmenuactions = []
|
||||
@@ -35,20 +38,20 @@ def build_menu():
|
||||
logger.debug('check contextmenu', itemmodule)
|
||||
module = __import__(itemmodule, None, None, [itemmodule])
|
||||
|
||||
logger.info('Add contextmenu item ->', itemmodule)
|
||||
logger.debug('Add contextmenu item ->', 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')
|
||||
logger.debug('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])
|
||||
logger.debug('Contextmenu module index', ret, ', label=' + contextmenuitems[ret])
|
||||
contextmenuactions[ret]()
|
||||
|
||||
|
||||
|
||||
@@ -66,6 +66,9 @@ class CipherSuiteAdapter(host_header_ssl.HostHeaderSSLAdapter):
|
||||
try:
|
||||
ip = doh.query(domain)[0]
|
||||
logger.info('Query DoH: ' + domain + ' = ' + str(ip))
|
||||
# IPv6 address
|
||||
if ':' in ip:
|
||||
ip = '[' + ip + ']'
|
||||
self.writeToCache(domain, ip)
|
||||
except Exception:
|
||||
logger.error('Failed to resolve hostname, fallback to normal dns')
|
||||
@@ -96,7 +99,7 @@ class CipherSuiteAdapter(host_header_ssl.HostHeaderSSLAdapter):
|
||||
domain = parse.netloc
|
||||
else:
|
||||
raise requests.exceptions.URLRequired
|
||||
if not scrapertools.find_single_match(domain, '\d+\.\d+\.\d+\.\d+'):
|
||||
if not scrapertools.find_single_match(domain, '\d+\.\d+\.\d+\.\d+') and ':' not in domain:
|
||||
ip = self.getIp(domain)
|
||||
else:
|
||||
ip = None
|
||||
|
||||
@@ -1033,7 +1033,13 @@ class Tmdb(object):
|
||||
if len(results) > 1:
|
||||
from lib.fuzzy_match import algorithims
|
||||
if self.search_type == 'multi':
|
||||
results.sort(key=lambda r: algorithims.trigram(text_simple, r.get('name', '') if r.get('media_type') == 'tv' else r.get('title', '')), reverse=True)
|
||||
if self.search_year:
|
||||
for r in results:
|
||||
if (r.get('release_date', '') and r.get('release_date', '')[:4] == self.search_year) or (r.get('first_air_date', '') and r.get('first_air_date', '')[:4] == self.search_year):
|
||||
results = [r]
|
||||
break
|
||||
if len(results) > 1:
|
||||
results.sort(key=lambda r: algorithims.trigram(text_simple, r.get('name', '') if r.get('media_type') == 'tv' else r.get('title', '')), reverse=True)
|
||||
else:
|
||||
results.sort(key=lambda r: algorithims.trigram(text_simple, r.get('name', '') if self.search_type == 'tv' else r.get('title', '')), reverse=True)
|
||||
|
||||
|
||||
@@ -1,20 +1,14 @@
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
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:
|
||||
@@ -28,37 +22,23 @@ except ImportError:
|
||||
import copy_reg as copyreg
|
||||
|
||||
try:
|
||||
from HTMLParser import HTMLParser
|
||||
from urlparse import urlparse
|
||||
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 urllib.parse import urlparse
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
from .exceptions import (
|
||||
CloudflareLoopProtection,
|
||||
CloudflareCode1020,
|
||||
CloudflareIUAMError,
|
||||
CloudflareSolveError,
|
||||
CloudflareChallengeError,
|
||||
CloudflareCaptchaError,
|
||||
CloudflareCaptchaProvider
|
||||
CloudflareIUAMError
|
||||
)
|
||||
|
||||
from .interpreters import JavaScriptInterpreter
|
||||
from .captcha import Captcha
|
||||
from .cloudflare import Cloudflare
|
||||
from .user_agent import User_Agent
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
__version__ = '1.2.58'
|
||||
__version__ = '1.2.62'
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
@@ -79,6 +59,8 @@ class CipherSuiteAdapter(HTTPAdapter):
|
||||
self.ssl_context = kwargs.pop('ssl_context', None)
|
||||
self.cipherSuite = kwargs.pop('cipherSuite', None)
|
||||
self.source_address = kwargs.pop('source_address', None)
|
||||
self.server_hostname = kwargs.pop('server_hostname', None)
|
||||
self.ecdhCurve = kwargs.pop('ecdhCurve', 'prime256v1')
|
||||
|
||||
if self.source_address:
|
||||
if isinstance(self.source_address, str):
|
||||
@@ -91,14 +73,32 @@ class CipherSuiteAdapter(HTTPAdapter):
|
||||
|
||||
if not self.ssl_context:
|
||||
self.ssl_context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
|
||||
|
||||
self.ssl_context.orig_wrap_socket = self.ssl_context.wrap_socket
|
||||
self.ssl_context.wrap_socket = self.wrap_socket
|
||||
|
||||
if self.server_hostname:
|
||||
self.ssl_context.server_hostname = self.server_hostname
|
||||
|
||||
self.ssl_context.set_ciphers(self.cipherSuite)
|
||||
self.ssl_context.set_ecdh_curve('prime256v1')
|
||||
self.ssl_context.set_ecdh_curve(self.ecdhCurve)
|
||||
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 wrap_socket(self, *args, **kwargs):
|
||||
if hasattr(self.ssl_context, 'server_hostname') and self.ssl_context.server_hostname:
|
||||
kwargs['server_hostname'] = self.ssl_context.server_hostname
|
||||
self.ssl_context.check_hostname = False
|
||||
else:
|
||||
self.ssl_context.check_hostname = True
|
||||
|
||||
return self.ssl_context.orig_wrap_socket(*args, **kwargs)
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
def init_poolmanager(self, *args, **kwargs):
|
||||
kwargs['ssl_context'] = self.ssl_context
|
||||
kwargs['source_address'] = self.source_address
|
||||
@@ -118,15 +118,21 @@ class CloudScraper(Session):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.debug = kwargs.pop('debug', False)
|
||||
|
||||
self.disableCloudflareV1 = kwargs.pop('disableCloudflareV1', 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.doubleDown = kwargs.pop('doubleDown', True)
|
||||
self.interpreter = kwargs.pop('interpreter', 'native')
|
||||
|
||||
self.requestPreHook = kwargs.pop('requestPreHook', None)
|
||||
self.requestPostHook = kwargs.pop('requestPostHook', None)
|
||||
|
||||
self.cipherSuite = kwargs.pop('cipherSuite', None)
|
||||
self.ecdhCurve = kwargs.pop('ecdhCurve', 'prime256v1')
|
||||
self.source_address = kwargs.pop('source_address', None)
|
||||
self.doubleDown = kwargs.pop('doubleDown', True)
|
||||
self.server_hostname = kwargs.pop('server_hostname', None)
|
||||
self.ssl_context = kwargs.pop('ssl_context', None)
|
||||
|
||||
self.allow_brotli = kwargs.pop(
|
||||
'allow_brotli',
|
||||
@@ -159,8 +165,10 @@ class CloudScraper(Session):
|
||||
'https://',
|
||||
CipherSuiteAdapter(
|
||||
cipherSuite=self.cipherSuite,
|
||||
ssl_context=self.ssl_context,
|
||||
source_address=self.source_address
|
||||
ecdhCurve=self.ecdhCurve,
|
||||
server_hostname=self.server_hostname,
|
||||
source_address=self.source_address,
|
||||
ssl_context=self.ssl_context
|
||||
)
|
||||
)
|
||||
|
||||
@@ -199,21 +207,7 @@ class CloudScraper(Session):
|
||||
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)
|
||||
print(f"Debug Error: {getattr(e, 'message', e)}")
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# Decode Brotli on older versions of urllib3 manually
|
||||
@@ -225,10 +219,10 @@ class CloudScraper(Session):
|
||||
resp._content = brotli.decompress(resp.content)
|
||||
else:
|
||||
logging.warning(
|
||||
'You\'re running urllib3 {}, Brotli content detected, '
|
||||
f'You\'re running urllib3 {requests.packages.urllib3.__version__}, 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__)
|
||||
'We will not continue to decompress.'
|
||||
)
|
||||
|
||||
return resp
|
||||
@@ -275,480 +269,44 @@ class CloudScraper(Session):
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
if self.requestPostHook:
|
||||
response = self.requestPostHook(self, response)
|
||||
newResponse = self.requestPostHook(self, response)
|
||||
|
||||
if self.debug:
|
||||
self.debugRequest(response)
|
||||
if response != newResponse: # Give me walrus in 3.7!!!
|
||||
response = newResponse
|
||||
if self.debug:
|
||||
print('==== requestPostHook Debug ====')
|
||||
self.debugRequest(response)
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
if not self.disableCloudflareV1:
|
||||
cloudflareV1 = Cloudflare(self)
|
||||
|
||||
# Check if Cloudflare anti-bot is on
|
||||
if self.is_Challenge_Request(response):
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# Try to solve the challenge and send it back
|
||||
# Check if Cloudflare v1 anti-bot is on
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
if self._solveDepthCnt >= self.solveDepth:
|
||||
_ = self._solveDepthCnt
|
||||
self.simpleException(
|
||||
CloudflareLoopProtection,
|
||||
"!!Loop Protection!! We have tried to solve {} time(s) in a row.".format(_)
|
||||
)
|
||||
if cloudflareV1.is_Challenge_Request(response):
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# Try to solve the challenge and send it back
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
self._solveDepthCnt += 1
|
||||
if self._solveDepthCnt >= self.solveDepth:
|
||||
_ = self._solveDepthCnt
|
||||
self.simpleException(
|
||||
CloudflareLoopProtection,
|
||||
f"!!Loop Protection!! We have tried to solve {_} time(s) in a row."
|
||||
)
|
||||
|
||||
response = self.Challenge_Response(response, **kwargs)
|
||||
else:
|
||||
if not response.is_redirect and response.status_code not in [429, 503]:
|
||||
self._solveDepthCnt = 0
|
||||
self._solveDepthCnt += 1
|
||||
|
||||
response = cloudflareV1.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'<form .*?="challenge-form" action="/.*?__cf_chl_jschl_tk__=\S+"',
|
||||
resp.text,
|
||||
re.M | re.S
|
||||
)
|
||||
)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# check if the response contains new Cloudflare challenge
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
@staticmethod
|
||||
def is_New_IUAM_Challenge(resp):
|
||||
try:
|
||||
return (
|
||||
resp.headers.get('Server', '').startswith('cloudflare')
|
||||
and resp.status_code in [429, 503]
|
||||
and re.search(
|
||||
r'cpo.src\s*=\s*"/cdn-cgi/challenge-platform/\S+orchestrate/jsch/v1',
|
||||
resp.text,
|
||||
re.M | re.S
|
||||
)
|
||||
and re.search(r'window._cf_chl_enter\s*[\(=]', resp.text, re.M | re.S)
|
||||
)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# check if the response contains a v2 hCaptcha Cloudflare challenge
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
@staticmethod
|
||||
def is_New_Captcha_Challenge(resp):
|
||||
try:
|
||||
return (
|
||||
CloudScraper.is_Captcha_Challenge(resp)
|
||||
and re.search(
|
||||
r'cpo.src\s*=\s*"/cdn-cgi/challenge-platform/\S+orchestrate/captcha/v1',
|
||||
resp.text,
|
||||
re.M | re.S
|
||||
)
|
||||
and re.search(r'\s*id="trk_captcha_js"', resp.text, re.M | re.S)
|
||||
)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# check if the response contains a Cloudflare hCaptcha challenge
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
@staticmethod
|
||||
def is_Captcha_Challenge(resp):
|
||||
try:
|
||||
return (
|
||||
resp.headers.get('Server', '').startswith('cloudflare')
|
||||
and resp.status_code == 403
|
||||
and re.search(
|
||||
r'action="/\S+__cf_chl_captcha_tk__=\S+',
|
||||
resp.text,
|
||||
re.M | re.DOTALL
|
||||
)
|
||||
)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# check if the response contains Firewall 1020 Error
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
@staticmethod
|
||||
def is_Firewall_Blocked(resp):
|
||||
try:
|
||||
return (
|
||||
resp.headers.get('Server', '').startswith('cloudflare')
|
||||
and resp.status_code == 403
|
||||
and re.search(
|
||||
r'<span class="cf-error-code">1020</span>',
|
||||
resp.text,
|
||||
re.M | re.DOTALL
|
||||
)
|
||||
)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# Wrapper for is_Captcha_Challenge, is_IUAM_Challenge, is_Firewall_Blocked
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
def is_Challenge_Request(self, resp):
|
||||
if self.is_Firewall_Blocked(resp):
|
||||
self.simpleException(
|
||||
CloudflareCode1020,
|
||||
'Cloudflare has blocked this request (Code 1020 Detected).'
|
||||
)
|
||||
|
||||
if self.is_New_Captcha_Challenge(resp):
|
||||
self.simpleException(
|
||||
CloudflareChallengeError,
|
||||
'Detected a Cloudflare version 2 Captcha challenge, This feature is not available in the opensource (free) version.'
|
||||
)
|
||||
|
||||
if self.is_New_IUAM_Challenge(resp):
|
||||
self.simpleException(
|
||||
CloudflareChallengeError,
|
||||
'Detected a Cloudflare version 2 challenge, This feature is not available in the opensource (free) version.'
|
||||
)
|
||||
|
||||
if self.is_Captcha_Challenge(resp) or self.is_IUAM_Challenge(resp):
|
||||
if self.debug:
|
||||
print('Detected a Cloudflare version 1 challenge.')
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# Try to solve cloudflare javascript challenge.
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
def IUAM_Challenge_Response(self, body, url, interpreter):
|
||||
try:
|
||||
formPayload = re.search(
|
||||
r'<form (?P<form>.*?="challenge-form" '
|
||||
r'action="(?P<challengeUUID>.*?'
|
||||
r'__cf_chl_jschl_tk__=\S+)"(.*?)</form>)',
|
||||
body,
|
||||
re.M | re.DOTALL
|
||||
).groupdict()
|
||||
|
||||
if not all(key in formPayload for key in ['form', 'challengeUUID']):
|
||||
self.simpleException(
|
||||
CloudflareIUAMError,
|
||||
"Cloudflare IUAM detected, unfortunately we can't extract the parameters correctly."
|
||||
)
|
||||
|
||||
payload = OrderedDict()
|
||||
for challengeParam in re.findall(r'^\s*<input\s(.*?)/>', formPayload['form'], re.M | re.S):
|
||||
inputPayload = dict(re.findall(r'(\S+)="(\S+)"', challengeParam))
|
||||
if inputPayload.get('name') in ['r', 'jschl_vc', 'pass']:
|
||||
payload.update({inputPayload['name']: inputPayload['value']})
|
||||
|
||||
except AttributeError:
|
||||
self.simpleException(
|
||||
CloudflareIUAMError,
|
||||
"Cloudflare IUAM detected, unfortunately we can't extract the parameters correctly."
|
||||
)
|
||||
|
||||
hostParsed = urlparse(url)
|
||||
|
||||
try:
|
||||
payload['jschl_answer'] = JavaScriptInterpreter.dynamicImport(
|
||||
interpreter
|
||||
).solveChallenge(body, hostParsed.netloc)
|
||||
except Exception as e:
|
||||
self.simpleException(
|
||||
CloudflareIUAMError,
|
||||
"Unable to parse Cloudflare anti-bots page: {}".format(getattr(e, 'message', e))
|
||||
)
|
||||
|
||||
return {
|
||||
'url': "{}://{}{}".format(hostParsed.scheme, hostParsed.netloc, self.unescape(formPayload['challengeUUID'])),
|
||||
'data': payload
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# Try to solve the Captcha challenge via 3rd party.
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
def captcha_Challenge_Response(self, provider, provider_params, body, url):
|
||||
try:
|
||||
formPayload = re.search(
|
||||
r'<form (?P<form>.*?="challenge-form" '
|
||||
r'action="(?P<challengeUUID>.*?__cf_chl_captcha_tk__=\S+)"(.*?)</form>)',
|
||||
body,
|
||||
re.M | re.DOTALL
|
||||
).groupdict()
|
||||
|
||||
if not all(key in formPayload for key in ['form', 'challengeUUID']):
|
||||
self.simpleException(
|
||||
CloudflareCaptchaError,
|
||||
"Cloudflare Captcha detected, unfortunately we can't extract the parameters correctly."
|
||||
)
|
||||
|
||||
payload = OrderedDict(
|
||||
re.findall(
|
||||
r'(name="r"\svalue|data-ray|data-sitekey|name="cf_captcha_kind"\svalue)="(.*?)"',
|
||||
formPayload['form']
|
||||
)
|
||||
)
|
||||
|
||||
captchaType = 'reCaptcha' if payload['name="cf_captcha_kind" value'] == 're' else 'hCaptcha'
|
||||
|
||||
except (AttributeError, KeyError):
|
||||
self.simpleException(
|
||||
CloudflareCaptchaError,
|
||||
"Cloudflare Captcha detected, unfortunately we can't extract the parameters correctly."
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# Pass proxy parameter to provider to solve captcha.
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
if self.proxies and self.proxies != self.captcha.get('proxy'):
|
||||
self.captcha['proxy'] = self.proxies
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# Pass User-Agent if provider supports it to solve captcha.
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
self.captcha['User-Agent'] = self.headers['User-Agent']
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# Submit job to provider to request captcha solve.
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
captchaResponse = Captcha.dynamicImport(
|
||||
provider.lower()
|
||||
).solveCaptcha(
|
||||
captchaType,
|
||||
url,
|
||||
payload['data-sitekey'],
|
||||
provider_params
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# Parse and handle the response of solved captcha.
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
dataPayload = OrderedDict([
|
||||
('r', payload.get('name="r" value', '')),
|
||||
('cf_captcha_kind', payload['name="cf_captcha_kind" value']),
|
||||
('id', payload.get('data-ray')),
|
||||
('g-recaptcha-response', captchaResponse)
|
||||
])
|
||||
|
||||
if captchaType == 'hCaptcha':
|
||||
dataPayload.update({'h-captcha-response': captchaResponse})
|
||||
|
||||
hostParsed = urlparse(url)
|
||||
|
||||
return {
|
||||
'url': "{}://{}{}".format(hostParsed.scheme, hostParsed.netloc, self.unescape(formPayload['challengeUUID'])),
|
||||
'data': dataPayload
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# Attempt to handle and send the challenge response back to cloudflare
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
def Challenge_Response(self, resp, **kwargs):
|
||||
if self.is_Captcha_Challenge(resp):
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# double down on the request as some websites are only checking
|
||||
# if cfuid is populated before issuing Captcha.
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
if self.doubleDown:
|
||||
resp = self.decodeBrotli(
|
||||
self.perform_request(resp.request.method, resp.url, **kwargs)
|
||||
)
|
||||
|
||||
if not self.is_Captcha_Challenge(resp):
|
||||
return resp
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# if no captcha provider raise a runtime error.
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
if not self.captcha or not isinstance(self.captcha, dict) or not self.captcha.get('provider'):
|
||||
self.simpleException(
|
||||
CloudflareCaptchaProvider,
|
||||
"Cloudflare Captcha detected, unfortunately you haven't loaded an anti Captcha provider "
|
||||
"correctly via the 'captcha' parameter."
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# if provider is return_response, return the response without doing anything.
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
if self.captcha.get('provider') == 'return_response':
|
||||
return resp
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# Submit request to parser wrapper to solve captcha
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
submit_url = self.captcha_Challenge_Response(
|
||||
self.captcha.get('provider'),
|
||||
self.captcha,
|
||||
resp.text,
|
||||
resp.url
|
||||
)
|
||||
else:
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# Cloudflare requires a delay before solving the challenge
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
if not self.delay:
|
||||
try:
|
||||
delay = float(
|
||||
re.search(
|
||||
r'submit\(\);\r?\n\s*},\s*([0-9]+)',
|
||||
resp.text
|
||||
).group(1)
|
||||
) / float(1000)
|
||||
if isinstance(delay, (int, float)):
|
||||
self.delay = delay
|
||||
except (AttributeError, ValueError):
|
||||
self.simpleException(
|
||||
CloudflareIUAMError,
|
||||
"Cloudflare IUAM possibility malformed, issue extracing delay value."
|
||||
)
|
||||
|
||||
sleep(self.delay)
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
submit_url = self.IUAM_Challenge_Response(
|
||||
resp.text,
|
||||
resp.url,
|
||||
self.interpreter
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# Send the Challenge Response back to Cloudflare
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
if submit_url:
|
||||
|
||||
def updateAttr(obj, name, newValue):
|
||||
try:
|
||||
obj[name].update(newValue)
|
||||
return obj[name]
|
||||
except (AttributeError, KeyError):
|
||||
obj[name] = {}
|
||||
obj[name].update(newValue)
|
||||
return obj[name]
|
||||
|
||||
cloudflare_kwargs = deepcopy(kwargs)
|
||||
cloudflare_kwargs['allow_redirects'] = False
|
||||
cloudflare_kwargs['data'] = updateAttr(
|
||||
cloudflare_kwargs,
|
||||
'data',
|
||||
submit_url['data']
|
||||
)
|
||||
|
||||
urlParsed = urlparse(resp.url)
|
||||
cloudflare_kwargs['headers'] = updateAttr(
|
||||
cloudflare_kwargs,
|
||||
'headers',
|
||||
{
|
||||
'Origin': '{}://{}'.format(urlParsed.scheme, urlParsed.netloc),
|
||||
'Referer': resp.url
|
||||
}
|
||||
)
|
||||
|
||||
challengeSubmitResponse = self.request(
|
||||
'POST',
|
||||
submit_url['url'],
|
||||
**cloudflare_kwargs
|
||||
)
|
||||
|
||||
if challengeSubmitResponse.status_code == 400:
|
||||
self.simpleException(
|
||||
CloudflareSolveError,
|
||||
'Invalid challenge answer detected, Cloudflare broken?'
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# Return response if Cloudflare is doing content pass through instead of 3xx
|
||||
# else request with redirect URL also handle protocol scheme change http -> https
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
if not challengeSubmitResponse.is_redirect:
|
||||
return challengeSubmitResponse
|
||||
|
||||
else:
|
||||
cloudflare_kwargs = deepcopy(kwargs)
|
||||
cloudflare_kwargs['headers'] = updateAttr(
|
||||
cloudflare_kwargs,
|
||||
'headers',
|
||||
{'Referer': challengeSubmitResponse.url}
|
||||
)
|
||||
|
||||
if not urlparse(challengeSubmitResponse.headers['Location']).netloc:
|
||||
redirect_location = urljoin(
|
||||
challengeSubmitResponse.url,
|
||||
challengeSubmitResponse.headers['Location']
|
||||
)
|
||||
else:
|
||||
redirect_location = challengeSubmitResponse.headers['Location']
|
||||
|
||||
return self.request(
|
||||
resp.request.method,
|
||||
redirect_location,
|
||||
**cloudflare_kwargs
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# We shouldn't be here...
|
||||
# Re-request the original query and/or process again....
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
return self.request(resp.request.method, resp.url, **kwargs)
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
@classmethod
|
||||
@@ -761,7 +319,7 @@ class CloudScraper(Session):
|
||||
if sess:
|
||||
for attr in ['auth', 'cert', 'cookies', 'headers', 'hooks', 'params', 'proxies', 'data']:
|
||||
val = getattr(sess, attr, None)
|
||||
if val:
|
||||
if val is not None:
|
||||
setattr(scraper, attr, val)
|
||||
|
||||
return scraper
|
||||
@@ -782,7 +340,7 @@ class CloudScraper(Session):
|
||||
'doubleDown',
|
||||
'captcha',
|
||||
'interpreter',
|
||||
'source_address'
|
||||
'source_address',
|
||||
'requestPreHook',
|
||||
'requestPostHook'
|
||||
] if field in kwargs
|
||||
@@ -793,7 +351,7 @@ class CloudScraper(Session):
|
||||
resp = scraper.get(url, **kwargs)
|
||||
resp.raise_for_status()
|
||||
except Exception:
|
||||
logging.error('"{}" returned an error. Could not collect tokens.'.format(url))
|
||||
logging.error(f'"{url}" returned an error. Could not collect tokens.')
|
||||
raise
|
||||
|
||||
domain = urlparse(resp.url).netloc
|
||||
@@ -801,11 +359,12 @@ class CloudScraper(Session):
|
||||
cookie_domain = None
|
||||
|
||||
for d in scraper.cookies.list_domains():
|
||||
if d.startswith('.') and d in ('.{}'.format(domain)):
|
||||
if d.startswith('.') and d in (f'.{domain}'):
|
||||
cookie_domain = d
|
||||
break
|
||||
else:
|
||||
cls.simpleException(
|
||||
cls,
|
||||
CloudflareIUAMError,
|
||||
"Unable to find Cloudflare cookies. Does the site actually "
|
||||
"have Cloudflare IUAM (I'm Under Attack Mode) enabled?"
|
||||
@@ -813,7 +372,6 @@ class CloudScraper(Session):
|
||||
|
||||
return (
|
||||
{
|
||||
'__cfduid': scraper.cookies.get('__cfduid', '', domain=cookie_domain),
|
||||
'cf_clearance': scraper.cookies.get('cf_clearance', '', domain=cookie_domain)
|
||||
},
|
||||
scraper.headers['User-Agent']
|
||||
@@ -834,9 +392,9 @@ class CloudScraper(Session):
|
||||
|
||||
if ssl.OPENSSL_VERSION_INFO < (1, 1, 1):
|
||||
print(
|
||||
"DEPRECATION: The OpenSSL being used by this python install ({}) does not meet the minimum supported "
|
||||
f"DEPRECATION: The OpenSSL being used by this python install ({ssl.OPENSSL_VERSION}) does not meet the minimum supported "
|
||||
"version (>= OpenSSL 1.1.1) in order to support TLS 1.3 required by Cloudflare, "
|
||||
"You may encounter an unexpected Captcha or cloudflare 1020 blocks.".format(ssl.OPENSSL_VERSION)
|
||||
"You may encounter an unexpected Captcha or cloudflare 1020 blocks."
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
@@ -103,7 +103,7 @@ class captchaSolver(Captcha):
|
||||
|
||||
response = polling2.poll(
|
||||
lambda: self.session.get(
|
||||
'{}/res.php'.format(self.host),
|
||||
f'{self.host}/res.php',
|
||||
params={
|
||||
'key': self.api_key,
|
||||
'action': 'reportbad',
|
||||
@@ -138,7 +138,7 @@ class captchaSolver(Captcha):
|
||||
|
||||
response = polling2.poll(
|
||||
lambda: self.session.get(
|
||||
'{}/res.php'.format(self.host),
|
||||
f'{self.host}/res.php',
|
||||
params={
|
||||
'key': self.api_key,
|
||||
'action': 'get',
|
||||
@@ -195,7 +195,7 @@ class captchaSolver(Captcha):
|
||||
|
||||
response = polling2.poll(
|
||||
lambda: self.session.post(
|
||||
'{}/in.php'.format(self.host),
|
||||
f'{self.host}/in.php',
|
||||
data=data,
|
||||
allow_redirects=False,
|
||||
timeout=30
|
||||
|
||||
@@ -36,7 +36,7 @@ class captchaSolver(reCaptcha):
|
||||
def checkErrorStatus(response):
|
||||
if response.status_code in [500, 502]:
|
||||
raise reCaptchaServiceUnavailable(
|
||||
'9kw: Server Side Error {}'.format(response.status_code)
|
||||
f'9kw: Server Side Error {response.status_code}'
|
||||
)
|
||||
|
||||
error_codes = {
|
||||
|
||||
@@ -25,12 +25,12 @@ class Captcha(ABC):
|
||||
def dynamicImport(cls, name):
|
||||
if name not in captchaSolvers:
|
||||
try:
|
||||
__import__('{}.{}'.format(cls.__module__, name))
|
||||
__import__(f'{cls.__module__}.{name}')
|
||||
if not isinstance(captchaSolvers.get(name), Captcha):
|
||||
raise ImportError('The anti captcha provider was not initialized.')
|
||||
except ImportError as e:
|
||||
sys.tracebacklimit = 0
|
||||
logging.error('Unable to load {} anti captcha provider -> {}'.format(name, e))
|
||||
logging.error(f'Unable to load {name} anti captcha provider -> {e}')
|
||||
raise
|
||||
|
||||
return captchaSolvers[name]
|
||||
|
||||
@@ -36,7 +36,7 @@ class captchaSolver(Captcha):
|
||||
def checkErrorStatus(response):
|
||||
if response.status_code in [500, 502]:
|
||||
raise CaptchaServiceUnavailable(
|
||||
'CapMonster: Server Side Error {}'.format(response.status_code)
|
||||
f'CapMonster: Server Side Error {response.status_code}'
|
||||
)
|
||||
|
||||
payload = response.json()
|
||||
@@ -66,7 +66,7 @@ class captchaSolver(Captcha):
|
||||
|
||||
response = polling2.poll(
|
||||
lambda: self.session.post(
|
||||
'{}/getTaskResult'.format(self.host),
|
||||
f'{self.host}/getTaskResult',
|
||||
json={
|
||||
'clientKey': self.clientKey,
|
||||
'taskId': taskID
|
||||
@@ -101,9 +101,9 @@ class captchaSolver(Captcha):
|
||||
'task': {
|
||||
'websiteURL': url,
|
||||
'websiteKey': siteKey,
|
||||
'softId': 37,
|
||||
'type': 'NoCaptchaTask' if captchaType == 'reCaptcha' else 'HCaptchaTask'
|
||||
}
|
||||
},
|
||||
'softId': 37
|
||||
}
|
||||
|
||||
if self.proxy:
|
||||
@@ -113,7 +113,7 @@ class captchaSolver(Captcha):
|
||||
|
||||
response = polling2.poll(
|
||||
lambda: self.session.post(
|
||||
'{}/createTask'.format(self.host),
|
||||
f'{self.host}/createTask',
|
||||
json=data,
|
||||
allow_redirects=False,
|
||||
timeout=30
|
||||
|
||||
@@ -68,7 +68,7 @@ class captchaSolver(Captcha):
|
||||
|
||||
response = polling2.poll(
|
||||
lambda: self.session.post(
|
||||
'{}/user'.format(self.host),
|
||||
f'{self.host}/user',
|
||||
headers={'Accept': 'application/json'},
|
||||
data={
|
||||
'username': self.username,
|
||||
@@ -100,7 +100,7 @@ class captchaSolver(Captcha):
|
||||
|
||||
response = polling2.poll(
|
||||
lambda: self.session.post(
|
||||
'{}/captcha/{}/report'.format(self.host, jobID),
|
||||
f'{self.host}/captcha/{jobID}/report',
|
||||
headers={'Accept': 'application/json'},
|
||||
data={
|
||||
'username': self.username,
|
||||
@@ -137,7 +137,7 @@ class captchaSolver(Captcha):
|
||||
|
||||
response = polling2.poll(
|
||||
lambda: self.session.get(
|
||||
'{}/captcha/{}'.format(self.host, jobID),
|
||||
f'{self.host}/captcha/{jobID}',
|
||||
headers={'Accept': 'application/json'}
|
||||
),
|
||||
check_success=_checkRequest,
|
||||
@@ -203,7 +203,7 @@ class captchaSolver(Captcha):
|
||||
|
||||
response = polling2.poll(
|
||||
lambda: self.session.post(
|
||||
'{}/captcha'.format(self.host),
|
||||
f'{self.host}/captcha',
|
||||
headers={'Accept': 'application/json'},
|
||||
data=data,
|
||||
allow_redirects=False
|
||||
|
||||
490
lib/cloudscraper/cloudflare.py
Normal file
490
lib/cloudscraper/cloudflare.py
Normal file
@@ -0,0 +1,490 @@
|
||||
# Cloudflare V1
|
||||
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
|
||||
from copy import deepcopy
|
||||
from collections import OrderedDict
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
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 (
|
||||
CloudflareCode1020,
|
||||
CloudflareIUAMError,
|
||||
CloudflareSolveError,
|
||||
CloudflareChallengeError,
|
||||
CloudflareCaptchaError,
|
||||
CloudflareCaptchaProvider
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
from .captcha import Captcha
|
||||
from .interpreters import JavaScriptInterpreter
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
|
||||
class Cloudflare():
|
||||
|
||||
def __init__(self, cloudscraper):
|
||||
self.cloudscraper = cloudscraper
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# 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)
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# 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'<form .*?="challenge-form" action="/.*?__cf_chl_jschl_tk__=\S+"',
|
||||
resp.text,
|
||||
re.M | re.S
|
||||
)
|
||||
)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# check if the response contains new Cloudflare challenge
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
@staticmethod
|
||||
def is_New_IUAM_Challenge(resp):
|
||||
try:
|
||||
return (
|
||||
resp.headers.get('Server', '').startswith('cloudflare')
|
||||
and resp.status_code in [429, 503]
|
||||
and re.search(
|
||||
r'''cpo.src\s*=\s*['"]/cdn-cgi/challenge-platform/\S+orchestrate/jsch/v1''',
|
||||
resp.text,
|
||||
re.M | re.S
|
||||
)
|
||||
)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# check if the response contains a v2 hCaptcha Cloudflare challenge
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
def is_New_Captcha_Challenge(self, resp):
|
||||
try:
|
||||
return (
|
||||
self.is_Captcha_Challenge(resp)
|
||||
and re.search(
|
||||
r'''cpo.src\s*=\s*['"]/cdn-cgi/challenge-platform/\S+orchestrate/(captcha|managed)/v1''',
|
||||
resp.text,
|
||||
re.M | re.S
|
||||
)
|
||||
)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# check if the response contains a Cloudflare hCaptcha challenge
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
@staticmethod
|
||||
def is_Captcha_Challenge(resp):
|
||||
try:
|
||||
return (
|
||||
resp.headers.get('Server', '').startswith('cloudflare')
|
||||
and resp.status_code == 403
|
||||
and re.search(
|
||||
r'''action="/\S+__cf_chl(|_f)_tk=\S+''',
|
||||
resp.text,
|
||||
re.M | re.DOTALL
|
||||
)
|
||||
)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# check if the response contains Firewall 1020 Error
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
@staticmethod
|
||||
def is_Firewall_Blocked(resp):
|
||||
try:
|
||||
return (
|
||||
resp.headers.get('Server', '').startswith('cloudflare')
|
||||
and resp.status_code == 403
|
||||
and re.search(
|
||||
r'<span class="cf-error-code">1020</span>',
|
||||
resp.text,
|
||||
re.M | re.DOTALL
|
||||
)
|
||||
)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# Wrapper for is_Captcha_Challenge, is_IUAM_Challenge, is_Firewall_Blocked
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
def is_Challenge_Request(self, resp):
|
||||
if self.is_Firewall_Blocked(resp):
|
||||
self.cloudscraper.simpleException(
|
||||
CloudflareCode1020,
|
||||
'Cloudflare has blocked this request (Code 1020 Detected).'
|
||||
)
|
||||
|
||||
if self.is_New_Captcha_Challenge(resp):
|
||||
self.cloudscraper.simpleException(
|
||||
CloudflareChallengeError,
|
||||
'Detected a Cloudflare version 2 Captcha challenge, This feature is not available in the opensource (free) version.'
|
||||
)
|
||||
|
||||
if self.is_New_IUAM_Challenge(resp):
|
||||
self.cloudscraper.simpleException(
|
||||
CloudflareChallengeError,
|
||||
'Detected a Cloudflare version 2 challenge, This feature is not available in the opensource (free) version.'
|
||||
)
|
||||
|
||||
if self.is_Captcha_Challenge(resp) or self.is_IUAM_Challenge(resp):
|
||||
if self.cloudscraper.debug:
|
||||
print('Detected a Cloudflare version 1 challenge.')
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# Try to solve cloudflare javascript challenge.
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
def IUAM_Challenge_Response(self, body, url, interpreter):
|
||||
try:
|
||||
formPayload = re.search(
|
||||
r'<form (?P<form>.*?="challenge-form" '
|
||||
r'action="(?P<challengeUUID>.*?'
|
||||
r'__cf_chl_jschl_tk__=\S+)"(.*?)</form>)',
|
||||
body,
|
||||
re.M | re.DOTALL
|
||||
).groupdict()
|
||||
|
||||
if not all(key in formPayload for key in ['form', 'challengeUUID']):
|
||||
self.cloudscraper.simpleException(
|
||||
CloudflareIUAMError,
|
||||
"Cloudflare IUAM detected, unfortunately we can't extract the parameters correctly."
|
||||
)
|
||||
|
||||
payload = OrderedDict()
|
||||
for challengeParam in re.findall(r'^\s*<input\s(.*?)/>', formPayload['form'], re.M | re.S):
|
||||
inputPayload = dict(re.findall(r'(\S+)="(\S+)"', challengeParam))
|
||||
if inputPayload.get('name') in ['r', 'jschl_vc', 'pass']:
|
||||
payload.update({inputPayload['name']: inputPayload['value']})
|
||||
|
||||
except AttributeError:
|
||||
self.cloudscraper.simpleException(
|
||||
CloudflareIUAMError,
|
||||
"Cloudflare IUAM detected, unfortunately we can't extract the parameters correctly."
|
||||
)
|
||||
|
||||
hostParsed = urlparse(url)
|
||||
|
||||
try:
|
||||
payload['jschl_answer'] = JavaScriptInterpreter.dynamicImport(
|
||||
interpreter
|
||||
).solveChallenge(body, hostParsed.netloc)
|
||||
except Exception as e:
|
||||
self.cloudscraper.simpleException(
|
||||
CloudflareIUAMError,
|
||||
f"Unable to parse Cloudflare anti-bots page: {getattr(e, 'message', e)}"
|
||||
)
|
||||
|
||||
return {
|
||||
'url': f"{hostParsed.scheme}://{hostParsed.netloc}{self.unescape(formPayload['challengeUUID'])}",
|
||||
'data': payload
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# Try to solve the Captcha challenge via 3rd party.
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
def captcha_Challenge_Response(self, provider, provider_params, body, url):
|
||||
try:
|
||||
formPayload = re.search(
|
||||
r'<form (?P<form>.*?="challenge-form" '
|
||||
r'action="(?P<challengeUUID>.*?__cf_chl_captcha_tk__=\S+)"(.*?)</form>)',
|
||||
body,
|
||||
re.M | re.DOTALL
|
||||
).groupdict()
|
||||
|
||||
if not all(key in formPayload for key in ['form', 'challengeUUID']):
|
||||
self.cloudscraper.simpleException(
|
||||
CloudflareCaptchaError,
|
||||
"Cloudflare Captcha detected, unfortunately we can't extract the parameters correctly."
|
||||
)
|
||||
|
||||
payload = OrderedDict(
|
||||
re.findall(
|
||||
r'(name="r"\svalue|data-ray|data-sitekey|name="cf_captcha_kind"\svalue)="(.*?)"',
|
||||
formPayload['form']
|
||||
)
|
||||
)
|
||||
|
||||
captchaType = 'reCaptcha' if payload['name="cf_captcha_kind" value'] == 're' else 'hCaptcha'
|
||||
|
||||
except (AttributeError, KeyError):
|
||||
self.cloudscraper.simpleException(
|
||||
CloudflareCaptchaError,
|
||||
"Cloudflare Captcha detected, unfortunately we can't extract the parameters correctly."
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# Pass proxy parameter to provider to solve captcha.
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
if self.cloudscraper.proxies and self.cloudscraper.proxies != self.cloudscraper.captcha.get('proxy'):
|
||||
self.cloudscraper.captcha['proxy'] = self.proxies
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# Pass User-Agent if provider supports it to solve captcha.
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
self.cloudscraper.captcha['User-Agent'] = self.cloudscraper.headers['User-Agent']
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# Submit job to provider to request captcha solve.
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
captchaResponse = Captcha.dynamicImport(
|
||||
provider.lower()
|
||||
).solveCaptcha(
|
||||
captchaType,
|
||||
url,
|
||||
payload['data-sitekey'],
|
||||
provider_params
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# Parse and handle the response of solved captcha.
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
dataPayload = OrderedDict([
|
||||
('r', payload.get('name="r" value', '')),
|
||||
('cf_captcha_kind', payload['name="cf_captcha_kind" value']),
|
||||
('id', payload.get('data-ray')),
|
||||
('g-recaptcha-response', captchaResponse)
|
||||
])
|
||||
|
||||
if captchaType == 'hCaptcha':
|
||||
dataPayload.update({'h-captcha-response': captchaResponse})
|
||||
|
||||
hostParsed = urlparse(url)
|
||||
|
||||
return {
|
||||
'url': f"{hostParsed.scheme}://{hostParsed.netloc}{self.unescape(formPayload['challengeUUID'])}",
|
||||
'data': dataPayload
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# Attempt to handle and send the challenge response back to cloudflare
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
def Challenge_Response(self, resp, **kwargs):
|
||||
if self.is_Captcha_Challenge(resp):
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# double down on the request as some websites are only checking
|
||||
# if cfuid is populated before issuing Captcha.
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
if self.cloudscraper.doubleDown:
|
||||
resp = self.cloudscraper.decodeBrotli(
|
||||
self.cloudscraper.perform_request(resp.request.method, resp.url, **kwargs)
|
||||
)
|
||||
|
||||
if not self.is_Captcha_Challenge(resp):
|
||||
return resp
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# if no captcha provider raise a runtime error.
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
if (
|
||||
not self.cloudscraper.captcha
|
||||
or not isinstance(self.cloudscraper.captcha, dict)
|
||||
or not self.cloudscraper.captcha.get('provider')
|
||||
):
|
||||
self.cloudscraper.simpleException(
|
||||
CloudflareCaptchaProvider,
|
||||
"Cloudflare Captcha detected, unfortunately you haven't loaded an anti Captcha provider "
|
||||
"correctly via the 'captcha' parameter."
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# if provider is return_response, return the response without doing anything.
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
if self.cloudscraper.captcha.get('provider') == 'return_response':
|
||||
return resp
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# Submit request to parser wrapper to solve captcha
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
submit_url = self.captcha_Challenge_Response(
|
||||
self.cloudscraper.captcha.get('provider'),
|
||||
self.cloudscraper.captcha,
|
||||
resp.text,
|
||||
resp.url
|
||||
)
|
||||
else:
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# Cloudflare requires a delay before solving the challenge
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
if not self.cloudscraper.delay:
|
||||
try:
|
||||
delay = float(
|
||||
re.search(
|
||||
r'submit\(\);\r?\n\s*},\s*([0-9]+)',
|
||||
resp.text
|
||||
).group(1)
|
||||
) / float(1000)
|
||||
if isinstance(delay, (int, float)):
|
||||
self.cloudscraper.delay = delay
|
||||
except (AttributeError, ValueError):
|
||||
self.cloudscraper.simpleException(
|
||||
CloudflareIUAMError,
|
||||
"Cloudflare IUAM possibility malformed, issue extracing delay value."
|
||||
)
|
||||
|
||||
time.sleep(self.cloudscraper.delay)
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
submit_url = self.IUAM_Challenge_Response(
|
||||
resp.text,
|
||||
resp.url,
|
||||
self.cloudscraper.interpreter
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# Send the Challenge Response back to Cloudflare
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
if submit_url:
|
||||
|
||||
def updateAttr(obj, name, newValue):
|
||||
try:
|
||||
obj[name].update(newValue)
|
||||
return obj[name]
|
||||
except (AttributeError, KeyError):
|
||||
obj[name] = {}
|
||||
obj[name].update(newValue)
|
||||
return obj[name]
|
||||
|
||||
cloudflare_kwargs = deepcopy(kwargs)
|
||||
cloudflare_kwargs['allow_redirects'] = False
|
||||
cloudflare_kwargs['data'] = updateAttr(
|
||||
cloudflare_kwargs,
|
||||
'data',
|
||||
submit_url['data']
|
||||
)
|
||||
|
||||
urlParsed = urlparse(resp.url)
|
||||
cloudflare_kwargs['headers'] = updateAttr(
|
||||
cloudflare_kwargs,
|
||||
'headers',
|
||||
{
|
||||
'Origin': f'{urlParsed.scheme}://{urlParsed.netloc}',
|
||||
'Referer': resp.url
|
||||
}
|
||||
)
|
||||
|
||||
challengeSubmitResponse = self.cloudscraper.request(
|
||||
'POST',
|
||||
submit_url['url'],
|
||||
**cloudflare_kwargs
|
||||
)
|
||||
|
||||
if challengeSubmitResponse.status_code == 400:
|
||||
self.cloudscraper.simpleException(
|
||||
CloudflareSolveError,
|
||||
'Invalid challenge answer detected, Cloudflare broken?'
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# Return response if Cloudflare is doing content pass through instead of 3xx
|
||||
# else request with redirect URL also handle protocol scheme change http -> https
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
if not challengeSubmitResponse.is_redirect:
|
||||
return challengeSubmitResponse
|
||||
|
||||
else:
|
||||
cloudflare_kwargs = deepcopy(kwargs)
|
||||
cloudflare_kwargs['headers'] = updateAttr(
|
||||
cloudflare_kwargs,
|
||||
'headers',
|
||||
{'Referer': challengeSubmitResponse.url}
|
||||
)
|
||||
|
||||
if not urlparse(challengeSubmitResponse.headers['Location']).netloc:
|
||||
redirect_location = urljoin(
|
||||
challengeSubmitResponse.url,
|
||||
challengeSubmitResponse.headers['Location']
|
||||
)
|
||||
else:
|
||||
redirect_location = challengeSubmitResponse.headers['Location']
|
||||
|
||||
return self.cloudscraper.request(
|
||||
resp.request.method,
|
||||
redirect_location,
|
||||
**cloudflare_kwargs
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# We shouldn't be here...
|
||||
# Re-request the original query and/or process again....
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
return self.cloudscraper.request(resp.request.method, resp.url, **kwargs)
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
@@ -28,9 +28,9 @@ def _pythonVersion():
|
||||
|
||||
if interpreter == 'PyPy':
|
||||
interpreter_version = \
|
||||
'{}.{}.{}'.format(sys.pypy_version_info.major, sys.pypy_version_info.minor, sys.pypy_version_info.micro)
|
||||
f'{sys.pypy_version_info.major}.{sys.pypy_version_info.minor}.{sys.pypy_version_info.micro}'
|
||||
if sys.pypy_version_info.releaselevel != 'final':
|
||||
interpreter_version = '{}{}'.format(interpreter_version, sys.pypy_version_info.releaselevel)
|
||||
interpreter_version = f'{interpreter_version}{sys.pypy_version_info.releaselevel}'
|
||||
return {
|
||||
'name': interpreter,
|
||||
'version': interpreter_version
|
||||
|
||||
@@ -93,14 +93,14 @@ class User_Agent():
|
||||
else:
|
||||
if self.browser and self.browser not in self.browsers:
|
||||
sys.tracebacklimit = 0
|
||||
raise RuntimeError('Sorry "{}" browser is not valid, valid browsers are [{}].'.format(self.browser), ", ".join(self.browsers))
|
||||
raise RuntimeError(f'Sorry "{self.browser}" browser is not valid, valid browsers are [{", ".join(self.browsers)}].')
|
||||
|
||||
if not self.platform:
|
||||
self.platform = random.SystemRandom().choice(self.platforms)
|
||||
|
||||
if self.platform not in self.platforms:
|
||||
sys.tracebacklimit = 0
|
||||
raise RuntimeError('Sorry the platform "{}" is not valid, valid platforms are [{)}]'.format(self.platform, ", ".join(self.platforms)))
|
||||
raise RuntimeError(f'Sorry the platform "{self.platform}" is not valid, valid platforms are [{", ".join(self.platforms)}]')
|
||||
|
||||
filteredAgents = self.filterAgents(user_agents['user_agents'])
|
||||
|
||||
@@ -111,7 +111,7 @@ class User_Agent():
|
||||
|
||||
if not filteredAgents[self.browser]:
|
||||
sys.tracebacklimit = 0
|
||||
raise RuntimeError('Sorry "{}" browser was not found with a platform of "{}".'.format(self.browser, self.platform))
|
||||
raise RuntimeError(f'Sorry "{self.browser}" browser was not found with a platform of "{self.platform}".')
|
||||
|
||||
self.cipherSuite = user_agents['cipherSuite'][self.browser]
|
||||
self.headers = user_agents['headers'][self.browser]
|
||||
|
||||
@@ -24,7 +24,11 @@ else:
|
||||
_urlopen = urllib2.urlopen
|
||||
_Request = urllib2.Request
|
||||
|
||||
def query(name, type='A', server=DOH_SERVER, path="/dns-query", fallback=True):
|
||||
# ipv6 = ':' in _urlopen('https://api64.ipify.org/').read().decode()
|
||||
ipv6 = False
|
||||
|
||||
|
||||
def query(name, type='AAAA' if ipv6 else 'A', server=DOH_SERVER, path="/dns-query", fallback=True):
|
||||
"""
|
||||
Returns domain name query results retrieved by using DNS over HTTPS protocol
|
||||
# Reference: https://developers.cloudflare.com/1.1.1.1/dns-over-https/json-format/
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
[
|
||||
"platformcode.contextmenu.search",
|
||||
"platformcode.contextmenu.tvshow_options",
|
||||
"platformcode.contextmenu.trailer"
|
||||
"platformcode.contextmenu.trailer",
|
||||
"platformcode.contextmenu.show_servers"
|
||||
]
|
||||
23
platformcode/contextmenu/show_servers.py
Normal file
23
platformcode/contextmenu/show_servers.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import xbmc
|
||||
|
||||
from core.item import Item
|
||||
from platformcode import config
|
||||
|
||||
|
||||
def get_menu_items():
|
||||
mediatype = xbmc.getInfoLabel('ListItem.DBTYPE')
|
||||
filePath = xbmc.getInfoLabel('ListItem.FileNameAndPath')
|
||||
res = []
|
||||
if 'kod' in filePath and mediatype in ['movie', 'episode'] and config.get_setting('autoplay'):
|
||||
res = [(config.get_localized_string(70192), execute)]
|
||||
return res
|
||||
|
||||
|
||||
def execute():
|
||||
from core import filetools
|
||||
from platformcode.launcher import run
|
||||
filePath = xbmc.getInfoLabel('ListItem.FileNameAndPath')
|
||||
item = Item().fromurl(filetools.read(filePath))
|
||||
item.disableAutoplay = True
|
||||
|
||||
run(item)
|
||||
@@ -1782,11 +1782,12 @@ def prevent_busy():
|
||||
|
||||
|
||||
def fakeVideo(sleep = False):
|
||||
mediaurl = os.path.join(config.get_runtime_path(), "resources", "kod.mp4")
|
||||
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, xbmcgui.ListItem(path=mediaurl))
|
||||
while not is_playing():
|
||||
xbmc.sleep(200)
|
||||
xbmc.Player().stop()
|
||||
if len(sys.argv) > 1:
|
||||
mediaurl = os.path.join(config.get_runtime_path(), "resources", "kod.mp4")
|
||||
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, xbmcgui.ListItem(path=mediaurl))
|
||||
while not is_playing():
|
||||
xbmc.sleep(200)
|
||||
xbmc.Player().stop()
|
||||
|
||||
|
||||
def channelImport(channelId):
|
||||
|
||||
@@ -50,7 +50,7 @@ def loadCommits(page=1):
|
||||
|
||||
# ret -> aggiornato, necessita reload service
|
||||
def check(background=False):
|
||||
if not addon.getSetting('addon_update_enabled'):
|
||||
if not config.get_setting('addon_update_enabled'):
|
||||
return False, False
|
||||
logger.info('Cerco aggiornamenti..')
|
||||
commits = loadCommits()
|
||||
@@ -156,7 +156,7 @@ def check(background=False):
|
||||
xbmc.sleep(1000)
|
||||
updated = True
|
||||
|
||||
if addon.getSetting("addon_update_message"):
|
||||
if config.get_setting("addon_update_message"):
|
||||
if background:
|
||||
platformtools.dialog_notification(config.get_localized_string(20000), config.get_localized_string(80040) % commits[0]['sha'][:7], time=3000, sound=False)
|
||||
try:
|
||||
|
||||
@@ -627,7 +627,7 @@ def set_content(content_type, silent=False, custom=False):
|
||||
else:
|
||||
seleccion = values.index('metadata.tvshows.themoviedb.org.python')
|
||||
else:
|
||||
seleccion = platformtools.dialog_select(config.get_localized_string(70094), scraper)
|
||||
seleccion = platformtools.dialog_select(config.get_localized_string(70094 if content_type == 'movie' else 70107), scraper)
|
||||
|
||||
# Configure scraper
|
||||
if seleccion != -1:
|
||||
|
||||
@@ -2549,7 +2549,11 @@ msgid " Update waiting time"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#60607"
|
||||
msgid "When Kodi starts"
|
||||
msgid "Each time you start Kodi"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#60608"
|
||||
msgid "Each time you start Kodi and daily"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#60609"
|
||||
|
||||
@@ -2548,8 +2548,12 @@ msgid " Update waiting time"
|
||||
msgstr " Tempo di attesa aggiornamento"
|
||||
|
||||
msgctxt "#60607"
|
||||
msgid "When Kodi starts"
|
||||
msgstr "All'avvio di Kodi"
|
||||
msgid "Each time you start Kodi"
|
||||
msgstr "Ad ogni avvio di Kodi"
|
||||
|
||||
msgctxt "#60608"
|
||||
msgid "Each time you start Kodi and daily"
|
||||
msgstr "Ad ogni avvio di Kodi e giornaliero"
|
||||
|
||||
msgctxt "#60609"
|
||||
msgid "10 sec"
|
||||
|
||||
@@ -23,22 +23,17 @@ def test_video_exists(page_url):
|
||||
def get_video_url(page_url, premium=False, user="", password="", video_password=""):
|
||||
global data, response
|
||||
logger.info("(page_url='%s')" % page_url)
|
||||
# from core.support import dbg
|
||||
# dbg()
|
||||
|
||||
video_urls = []
|
||||
id = scrapertools.find_single_match(page_url, '/e/(\w+)')
|
||||
post = {"id": id}
|
||||
token = scrapertools.find_single_match(data, '<meta name="csrf-token" content="([^"]+)')
|
||||
data = httptools.downloadpage("https://streamlare.com/api/video/stream/get",
|
||||
post=post, headers={'X-CSRF-TOKEN': token, 'X-Requested-With': 'XMLHttpRequest',
|
||||
'X-XSRF-TOKEN': httptools.urlparse.unquote(response.cookies.get('XSRF-TOKEN'))}).json
|
||||
# src = data["result"]["Original"]["src"]
|
||||
# media_url = ''.join([chr(51 ^ c) for c in base64.b64decode(src)])
|
||||
media_url = data["result"]["file"]
|
||||
video_urls.append(["MP4", media_url])
|
||||
data = httptools.downloadpage("https://streamlare.com/api/video/stream/get", post=post).data.replace("\\","")
|
||||
matches = scrapertools.find_multiple_matches(data, 'label":"([^"]+).*?file":"([^"]+)')
|
||||
for res, media_url in matches:
|
||||
media_url += "|User-Agent=%s" %(httptools.get_user_agent())
|
||||
video_urls.append(["MP4", media_url])
|
||||
return video_urls
|
||||
|
||||
|
||||
def get_filename(page_url):
|
||||
from core import jsontools
|
||||
file = jsontools.load(scrapertools.decodeHtmlentities(httptools.downloadpage(page_url).data.split(':file="')[1].split('"')[0]))
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"ignore_urls": [],
|
||||
"patterns": [
|
||||
{
|
||||
"pattern": "(?:streamsb|sbembed|sbembed1|sbplay1|sbplay|pelistop|tubesb|playersb|embedsb|watchsb|streamas|sbfast|sbfull|viewsb|sbvideo|cloudemb|sbplay2|japopav|javplaya|ssbstream)\\.\\w{2,5}/(?:embed-|d/|e/)?([A-z0-9]+)",
|
||||
"pattern": "(?:streamsb|sbembed|sbembed1|sbplay1|sbplay|pelistop|tubesb|playersb|embedsb|watchsb|streamas|sbfast|sbfull|viewsb|sbvideo|cloudemb|sbplay2|japopav|javplaya|ssbstream|sbthe|sbspeed)\\.\\w{2,5}/(?:embed-|d/|e/)?([A-z0-9]+)",
|
||||
"url": "https://streamsb.net/d/\\1"
|
||||
},
|
||||
{
|
||||
@@ -26,4 +26,4 @@
|
||||
"visible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
12
service.py
12
service.py
@@ -122,7 +122,10 @@ def check_for_update(overwrite=True):
|
||||
estado_verify_playcount_series = False
|
||||
|
||||
try:
|
||||
if overwrite or (config.get_setting("update", "videolibrary") != 0 and hoy.strftime('%Y-%m-%d') != config.get_setting('updatelibrary_last_check', 'videolibrary')):
|
||||
if overwrite or \
|
||||
config.get_setting("update", "videolibrary") in [4, 5] or \
|
||||
(config.get_setting("update", "videolibrary") not in [0, 4] and hoy.strftime('%Y-%m-%d') != config.get_setting('updatelibrary_last_check', 'videolibrary')):
|
||||
|
||||
config.set_setting("updatelibrary_last_check", hoy.strftime('%Y-%m-%d'), "videolibrary")
|
||||
|
||||
heading = config.get_localized_string(60601)
|
||||
@@ -434,8 +437,8 @@ class AddonMonitor(xbmc.Monitor):
|
||||
|
||||
def scheduleVideolibrary(self):
|
||||
self.update_setting = config.get_setting("update", "videolibrary")
|
||||
# 2= daily 3=daily and when kodi starts
|
||||
if self.update_setting == 2 or self.update_setting == 3:
|
||||
# 2 = Daily, 3 = When Kodi starts and daily, 5 = Each time you start Kodi and daily
|
||||
if self.update_setting in [2, 3, 5]:
|
||||
self.update_hour = config.get_setting("everyday_delay", "videolibrary") * 4
|
||||
schedule.every().day.at(str(self.update_hour).zfill(2) + ':00').do(run_threaded, check_for_update, (False,)).tag('videolibrary')
|
||||
logger.debug('scheduled videolibrary at ' + str(self.update_hour).zfill(2) + ':00')
|
||||
@@ -498,8 +501,7 @@ if __name__ == "__main__":
|
||||
updater.refreshLang()
|
||||
|
||||
# prepare to replace strSettings
|
||||
path_settings = xbmc.translatePath(
|
||||
"special://profile/addon_data/metadata.tvshows.themoviedb.org/settings.xml")
|
||||
path_settings = xbmc.translatePath("special://profile/addon_data/metadata.tvshows.themoviedb.org/settings.xml")
|
||||
settings_data = filetools.read(path_settings)
|
||||
strSettings = ' '.join(settings_data.split()).replace("> <", "><")
|
||||
strSettings = strSettings.replace("\"", "\'")
|
||||
|
||||
@@ -1,284 +1,285 @@
|
||||
{
|
||||
"id": "videolibrary",
|
||||
"name": "Videoteca",
|
||||
"active": false,
|
||||
"language": ["*"],
|
||||
"settings": [
|
||||
{
|
||||
"id": "update",
|
||||
"type": "list",
|
||||
"label": "@60601",
|
||||
"default": 1,
|
||||
"visible": true,
|
||||
"lvalues": [
|
||||
"@60602",
|
||||
"@60603",
|
||||
"@60604",
|
||||
"@60605"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "update_wait",
|
||||
"type": "list",
|
||||
"label": "@60606",
|
||||
"default": 0,
|
||||
"enabled": "eq(-1,@60603)|eq(-1,@60605)",
|
||||
"lvalues": [
|
||||
"No",
|
||||
"@60609",
|
||||
"@60610",
|
||||
"@60611",
|
||||
"@60612"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "everyday_delay",
|
||||
"type": "list",
|
||||
"label": "@60613",
|
||||
"default": 1,
|
||||
"enabled": "eq(-2,@60604)|eq(-2,@60605)",
|
||||
"lvalues": [
|
||||
"00:00",
|
||||
"04:00",
|
||||
"08:00",
|
||||
"12:00",
|
||||
"16:00",
|
||||
"20:00"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "updatetvshows_interval",
|
||||
"type": "list",
|
||||
"label": "@60614",
|
||||
"default": 0,
|
||||
"enabled": "!eq(-3,@60615)",
|
||||
"lvalues": [
|
||||
"@60616",
|
||||
"@60617"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "search_new_content",
|
||||
"type": "list",
|
||||
"label": "@60618",
|
||||
"default": 0,
|
||||
"visible": false,
|
||||
"enabled": "!eq(-4,@60615)",
|
||||
"lvalues": [
|
||||
"@60619",
|
||||
"@60620"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "local_episodes",
|
||||
"type": "bool",
|
||||
"label": "@80042",
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"id": "lab_1",
|
||||
"type": "label",
|
||||
"label": "@60650",
|
||||
"enabled": true,
|
||||
"visible": true
|
||||
},
|
||||
{
|
||||
"id": "scraper_movies",
|
||||
"type": "list",
|
||||
"label": "@60651",
|
||||
"enabled": false,
|
||||
"default": 0,
|
||||
"visible": false,
|
||||
"lvalues": [
|
||||
"TMDB",
|
||||
"None"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "scraper_tvshows",
|
||||
"type": "list",
|
||||
"label": "@60652",
|
||||
"default": 0,
|
||||
"visible": false,
|
||||
"lvalues": [
|
||||
"TMDB",
|
||||
"TVDB"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "tvdb_retry_eng",
|
||||
"type": "bool",
|
||||
"label": "@60653",
|
||||
"default": true,
|
||||
"enabled": "eq(-1,TVDB)",
|
||||
"visible": false
|
||||
},
|
||||
{
|
||||
"id": "info_language",
|
||||
"type": "list",
|
||||
"label": "@60662",
|
||||
"enabled": true,
|
||||
"default": 4,
|
||||
"lvalues": [
|
||||
"de",
|
||||
"en",
|
||||
"es",
|
||||
"fr",
|
||||
"it",
|
||||
"pt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "max_links",
|
||||
"type": "list",
|
||||
"label": "@60624",
|
||||
"default": 0,
|
||||
"enabled": true,
|
||||
"visible": true,
|
||||
"lvalues": [
|
||||
"@60625",
|
||||
"30",
|
||||
"60",
|
||||
"90",
|
||||
"120",
|
||||
"150",
|
||||
"180",
|
||||
"210"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "white_list_order",
|
||||
"type": "bool",
|
||||
"label": "@60626",
|
||||
"enabled": true,
|
||||
"visible": false,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"id": "quit_channel_name",
|
||||
"type": "bool",
|
||||
"label": "@60627",
|
||||
"enabled": true,
|
||||
"visible": false,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"id": "replace_VD",
|
||||
"type": "bool",
|
||||
"label": "@60628",
|
||||
"enabled": "eq(-4,@60623)",
|
||||
"visible": false,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"id": "db_mode",
|
||||
"type": "bool",
|
||||
"label": "@60629",
|
||||
"default": false,
|
||||
"enabled": true,
|
||||
"visible": true
|
||||
},
|
||||
{
|
||||
"id": "xbmc_host",
|
||||
"type": "text",
|
||||
"label": "@60632",
|
||||
"visible": true,
|
||||
"enabled": "eq(-1,true)"
|
||||
},
|
||||
{
|
||||
"id": "xbmc_puerto",
|
||||
"type": "text",
|
||||
"label": "@60633",
|
||||
"enabled": "eq(-2,true)",
|
||||
"visible": true
|
||||
},
|
||||
{
|
||||
"id": "mark_as_watched",
|
||||
"type": "bool",
|
||||
"label": "@60634",
|
||||
"default": true,
|
||||
"enabled": true,
|
||||
"visible": true
|
||||
},
|
||||
{
|
||||
"id": "sync_trakt",
|
||||
"type": "label",
|
||||
"label": "@60637",
|
||||
"enabled": true,
|
||||
"visible": true
|
||||
},
|
||||
{
|
||||
"id": "sync_trakt_watched",
|
||||
"type": "bool",
|
||||
"label": "@60638",
|
||||
"default": false,
|
||||
"visible": false,
|
||||
"enabled": "eq(-3,true)"
|
||||
},
|
||||
{
|
||||
"id": "sync_trakt_notification",
|
||||
"type": "bool",
|
||||
"label": "@60639",
|
||||
"default": true,
|
||||
"visible": true,
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"id": "sync_trakt_new_tvshow",
|
||||
"type": "bool",
|
||||
"label": "@60640",
|
||||
"default": false,
|
||||
"enabled": true,
|
||||
"visible": true
|
||||
},
|
||||
{
|
||||
"id": "sync_trakt_new_tvshow_wait",
|
||||
"type": "bool",
|
||||
"label": "@60641",
|
||||
"default": true,
|
||||
"visible": true,
|
||||
"enabled": "eq(-1,true)"
|
||||
},
|
||||
{
|
||||
"id": "show_all_seasons",
|
||||
"type": "bool",
|
||||
"label": "@60642",
|
||||
"default": true
|
||||
},
|
||||
{
|
||||
"id": "no_pile_on_seasons",
|
||||
"type": "list",
|
||||
"label": "@60643",
|
||||
"default": 1,
|
||||
"lvalues": [
|
||||
"@60648",
|
||||
"@60644",
|
||||
"@60616"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ask_channel",
|
||||
"type": "bool",
|
||||
"label": "@60645",
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"id": "original_title_folder",
|
||||
"type": "bool",
|
||||
"label": "@60646",
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"id": "lowerize_title",
|
||||
"type": "bool",
|
||||
"label": "@70703",
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"id": "verify_playcount",
|
||||
"type": "bool",
|
||||
"label": "@70526",
|
||||
"default": false
|
||||
}
|
||||
]
|
||||
}
|
||||
"id": "videolibrary",
|
||||
"name": "Videoteca",
|
||||
"active": false,
|
||||
"language": ["*"],
|
||||
"settings": [{
|
||||
"id": "update",
|
||||
"type": "list",
|
||||
"label": "@60601",
|
||||
"default": 1,
|
||||
"visible": true,
|
||||
"lvalues": [
|
||||
"@60602",
|
||||
"@60603",
|
||||
"@60604",
|
||||
"@60605",
|
||||
"@60607",
|
||||
"@60608"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "update_wait",
|
||||
"type": "list",
|
||||
"label": "@60606",
|
||||
"default": 0,
|
||||
"enabled": "eq(-1,@60603)|eq(-1,@60605)|eq(-1,@60607)|eq(-1,@60608)",
|
||||
"lvalues": [
|
||||
"No",
|
||||
"@60609",
|
||||
"@60610",
|
||||
"@60611",
|
||||
"@60612"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "everyday_delay",
|
||||
"type": "list",
|
||||
"label": "@60613",
|
||||
"default": 1,
|
||||
"enabled": "eq(-2,@60604)|eq(-2,@60605)|eq(-2,@60608)",
|
||||
"lvalues": [
|
||||
"00:00",
|
||||
"04:00",
|
||||
"08:00",
|
||||
"12:00",
|
||||
"16:00",
|
||||
"20:00"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "updatetvshows_interval",
|
||||
"type": "list",
|
||||
"label": "@60614",
|
||||
"default": 0,
|
||||
"enabled": "!eq(-3,@60615)",
|
||||
"lvalues": [
|
||||
"@60616",
|
||||
"@60617"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "search_new_content",
|
||||
"type": "list",
|
||||
"label": "@60618",
|
||||
"default": 0,
|
||||
"visible": false,
|
||||
"enabled": "!eq(-4,@60615)",
|
||||
"lvalues": [
|
||||
"@60619",
|
||||
"@60620"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "local_episodes",
|
||||
"type": "bool",
|
||||
"label": "@80042",
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"id": "lab_1",
|
||||
"type": "label",
|
||||
"label": "@60650",
|
||||
"enabled": true,
|
||||
"visible": true
|
||||
},
|
||||
{
|
||||
"id": "scraper_movies",
|
||||
"type": "list",
|
||||
"label": "@60651",
|
||||
"enabled": false,
|
||||
"default": 0,
|
||||
"visible": false,
|
||||
"lvalues": [
|
||||
"TMDB",
|
||||
"None"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "scraper_tvshows",
|
||||
"type": "list",
|
||||
"label": "@60652",
|
||||
"default": 0,
|
||||
"visible": false,
|
||||
"lvalues": [
|
||||
"TMDB",
|
||||
"TVDB"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "tvdb_retry_eng",
|
||||
"type": "bool",
|
||||
"label": "@60653",
|
||||
"default": true,
|
||||
"enabled": "eq(-1,TVDB)",
|
||||
"visible": false
|
||||
},
|
||||
{
|
||||
"id": "info_language",
|
||||
"type": "list",
|
||||
"label": "@60662",
|
||||
"enabled": true,
|
||||
"default": 4,
|
||||
"lvalues": [
|
||||
"de",
|
||||
"en",
|
||||
"es",
|
||||
"fr",
|
||||
"it",
|
||||
"pt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "max_links",
|
||||
"type": "list",
|
||||
"label": "@60624",
|
||||
"default": 0,
|
||||
"enabled": true,
|
||||
"visible": true,
|
||||
"lvalues": [
|
||||
"@60625",
|
||||
"30",
|
||||
"60",
|
||||
"90",
|
||||
"120",
|
||||
"150",
|
||||
"180",
|
||||
"210"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "white_list_order",
|
||||
"type": "bool",
|
||||
"label": "@60626",
|
||||
"enabled": true,
|
||||
"visible": false,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"id": "quit_channel_name",
|
||||
"type": "bool",
|
||||
"label": "@60627",
|
||||
"enabled": true,
|
||||
"visible": false,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"id": "replace_VD",
|
||||
"type": "bool",
|
||||
"label": "@60628",
|
||||
"enabled": "eq(-4,@60623)",
|
||||
"visible": false,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"id": "db_mode",
|
||||
"type": "bool",
|
||||
"label": "@60629",
|
||||
"default": false,
|
||||
"enabled": true,
|
||||
"visible": true
|
||||
},
|
||||
{
|
||||
"id": "xbmc_host",
|
||||
"type": "text",
|
||||
"label": "@60632",
|
||||
"visible": true,
|
||||
"enabled": "eq(-1,true)"
|
||||
},
|
||||
{
|
||||
"id": "xbmc_puerto",
|
||||
"type": "text",
|
||||
"label": "@60633",
|
||||
"enabled": "eq(-2,true)",
|
||||
"visible": true
|
||||
},
|
||||
{
|
||||
"id": "mark_as_watched",
|
||||
"type": "bool",
|
||||
"label": "@60634",
|
||||
"default": true,
|
||||
"enabled": true,
|
||||
"visible": true
|
||||
},
|
||||
{
|
||||
"id": "sync_trakt",
|
||||
"type": "label",
|
||||
"label": "@60637",
|
||||
"enabled": true,
|
||||
"visible": true
|
||||
},
|
||||
{
|
||||
"id": "sync_trakt_watched",
|
||||
"type": "bool",
|
||||
"label": "@60638",
|
||||
"default": false,
|
||||
"visible": false,
|
||||
"enabled": "eq(-3,true)"
|
||||
},
|
||||
{
|
||||
"id": "sync_trakt_notification",
|
||||
"type": "bool",
|
||||
"label": "@60639",
|
||||
"default": true,
|
||||
"visible": true,
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"id": "sync_trakt_new_tvshow",
|
||||
"type": "bool",
|
||||
"label": "@60640",
|
||||
"default": false,
|
||||
"enabled": true,
|
||||
"visible": true
|
||||
},
|
||||
{
|
||||
"id": "sync_trakt_new_tvshow_wait",
|
||||
"type": "bool",
|
||||
"label": "@60641",
|
||||
"default": true,
|
||||
"visible": true,
|
||||
"enabled": "eq(-1,true)"
|
||||
},
|
||||
{
|
||||
"id": "show_all_seasons",
|
||||
"type": "bool",
|
||||
"label": "@60642",
|
||||
"default": true
|
||||
},
|
||||
{
|
||||
"id": "no_pile_on_seasons",
|
||||
"type": "list",
|
||||
"label": "@60643",
|
||||
"default": 1,
|
||||
"lvalues": [
|
||||
"@60648",
|
||||
"@60644",
|
||||
"@60616"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ask_channel",
|
||||
"type": "bool",
|
||||
"label": "@60645",
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"id": "original_title_folder",
|
||||
"type": "bool",
|
||||
"label": "@60646",
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"id": "lowerize_title",
|
||||
"type": "bool",
|
||||
"label": "@70703",
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"id": "verify_playcount",
|
||||
"type": "bool",
|
||||
"label": "@70526",
|
||||
"default": false
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user