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:
marco
2022-09-15 19:15:51 +02:00
parent 749b54a772
commit 164efd8af7
41 changed files with 1062 additions and 941 deletions

View File

@@ -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 &quot;mostra server&quot; 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]

View File

@@ -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",

View File

@@ -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:

View File

@@ -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

View File

@@ -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()

View File

@@ -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('/', '_')

View File

@@ -134,8 +134,11 @@ def episodios(item):
action = 'findvideos'
item.contentType = 'tvshow'
blacklist = ['']
patron = r'(?P<episode>\d+(?:&#215;|×)?\d+\-\d+|\d+(?:&#215;|×)\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+(?:&#215;|×)?\d+\-\d+|\d+(?:&#215;|×)\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()

View File

@@ -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": []
}

View File

@@ -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|×|&#215;)\d+|Puntata \d+)[;]?[ ]?(?P<title>[^<-]+))?(?P<data>.*?)(?:<br|</p)'
# debug=True
patron = r'(?:/>|<p>)\s*(?:(?P<episode>\d+(?:x|×|&#215;)\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

View File

@@ -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": []
}
}

View File

@@ -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()

View File

@@ -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()

View File

@@ -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'>[^"<]+'

View File

@@ -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:

View File

@@ -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()

View File

@@ -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('/', '_')

View File

@@ -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": []
}
}

View File

@@ -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]()

View File

@@ -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

View File

@@ -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)

View File

@@ -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."
)
# ------------------------------------------------------------------------------- #

View File

@@ -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

View File

@@ -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 = {

View File

@@ -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]

View File

@@ -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

View File

@@ -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

View 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)
# ------------------------------------------------------------------------------- #

View File

@@ -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

View File

@@ -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]

View File

@@ -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/

View File

@@ -1,5 +1,6 @@
[
"platformcode.contextmenu.search",
"platformcode.contextmenu.tvshow_options",
"platformcode.contextmenu.trailer"
"platformcode.contextmenu.trailer",
"platformcode.contextmenu.show_servers"
]

View 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)

View File

@@ -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):

View File

@@ -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:

View File

@@ -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:

View File

@@ -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"

View File

@@ -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"

View File

@@ -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]))

View File

@@ -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
}
]
}
}

View File

@@ -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("\"", "\'")

View File

@@ -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
}
]
}